1 /*
2 * sound.cxx
3 *
4 * Sound driver implementation.
5 *
6 * Portable Windows Library
7 *
8 * Copyright (c) 1993-1998 Equivalence Pty. Ltd.
9 *
10 * The contents of this file are subject to the Mozilla Public License
11 * Version 1.0 (the "License"); you may not use this file except in
12 * compliance with the License. You may obtain a copy of the License at
13 * http://www.mozilla.org/MPL/
14 *
15 * Software distributed under the License is distributed on an "AS IS"
16 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17 * the License for the specific language governing rights and limitations
18 * under the License.
19 *
20 * The Original Code is Portable Windows Library.
21 *
22 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
23 *
24 * Portions are Copyright (C) 1993 Free Software Foundation, Inc.
25 * All Rights Reserved.
26 *
27 * Contributor(s): Derek Smithies - from a modification of oss module.
28 *
29 * $Revision: 28013 $
30 * $Author: rjongbloed $
31 * $Date: 2012-07-12 20:57:54 -0500 (Thu, 12 Jul 2012) $
32 */
33
34
35 #pragma implementation "sound_pulse.h"
36
37 #include <pulse/context.h>
38 #include <pulse/error.h>
39 #include <pulse/introspect.h>
40 #include <pulse/thread-mainloop.h>
41 #include <pulse/volume.h>
42 //#include <pulse/gccmacro.h>
43 #include <pulse/sample.h>
44 #include <ptlib.h>
45 #include <ptclib/random.h>
46
47 #include "sound_pulse.h"
48
49
50
51 PCREATE_SOUND_PLUGIN(Pulse, PSoundChannelPulse);
52
53 static pa_threaded_mainloop* paloop;
54 static pa_context* context;
55
56 class PulseContext {
57 private:
notify_cb(pa_context * c,void * userdata)58 static void notify_cb(pa_context *c,void *userdata) {
59 pa_threaded_mainloop_signal(paloop,0);
60 }
61 public:
PulseContext()62 PulseContext() {
63 paloop=pa_threaded_mainloop_new();
64 pa_threaded_mainloop_start(paloop);
65 pa_threaded_mainloop_lock(paloop);
66 pa_proplist *proplist=pa_proplist_new();
67 pa_proplist_sets(proplist,"media.role","phone");
68 /* TODO: I wasn't able to make module-cork-music-on-phone do what I expected */
69 context=pa_context_new_with_proplist(pa_threaded_mainloop_get_api(paloop),"ptlib",proplist);
70 pa_proplist_free(proplist);
71 pa_context_connect(context,NULL,PA_CONTEXT_NOFLAGS ,NULL);
72 pa_context_set_state_callback(context,notify_cb,NULL);
73 while (pa_context_get_state(context)<PA_CONTEXT_READY) {
74 pa_threaded_mainloop_wait(paloop);
75 }
76 pa_context_set_state_callback(context,NULL,NULL);
77 pa_threaded_mainloop_unlock(paloop);
78 }
~PulseContext()79 ~PulseContext() {
80 pa_context_disconnect(context);
81 pa_context_unref(context);
82 pa_threaded_mainloop_stop(paloop);
83 pa_threaded_mainloop_free(paloop);
84 }
signal()85 static void signal() {
86 pa_threaded_mainloop_signal(paloop,0);
87 }
88
89 };
90
91 static PulseContext pamain;
92
93 class PulseLock {
94 public:
95
PulseLock()96 PulseLock() {
97 pa_threaded_mainloop_lock(paloop);
98 }
99
~PulseLock()100 ~PulseLock() {
101 pa_threaded_mainloop_unlock(paloop);
102 }
103
wait()104 void wait() {
105 pa_threaded_mainloop_wait(paloop);
106 }
107
waitFor(pa_operation * operation)108 bool waitFor(pa_operation* operation) {
109 if (!operation)
110 return false;
111
112 while (pa_operation_get_state(operation)==PA_OPERATION_RUNNING) {
113 pa_threaded_mainloop_wait(paloop);
114 }
115 bool toReturn=pa_operation_get_state(operation)==PA_OPERATION_DONE;
116 pa_operation_unref(operation);
117 return toReturn;
118 }
119
120 };
121
122 ///////////////////////////////////////////////////////////////////////////////
PSoundChannelPulse()123 PSoundChannelPulse::PSoundChannelPulse()
124 {
125 PTRACE(6, "Pulse\tConstructor for no args");
126 PSoundChannelPulse::Construct();
127 setenv ("PULSE_PROP_media.role", "phone", true);
128 }
129
130
PSoundChannelPulse(const PString & device,Directions dir,unsigned numChannels,unsigned sampleRate,unsigned bitsPerSample)131 PSoundChannelPulse::PSoundChannelPulse(const PString & device,
132 Directions dir,
133 unsigned numChannels,
134 unsigned sampleRate,
135 unsigned bitsPerSample)
136 {
137 PTRACE(6, "Pulse\tConstructor with many args\n");
138
139 PAssert((bitsPerSample == 16), PInvalidParameter);
140 Construct();
141 ss.rate = sampleRate;
142 ss.channels = numChannels;
143 Open(device, dir, numChannels, sampleRate, bitsPerSample);
144 }
145
146
Construct()147 void PSoundChannelPulse::Construct()
148 {
149 PTRACE(6, "Pulse\tConstruct ");
150 os_handle = -1;
151 s = NULL;
152 ss.format = PA_SAMPLE_S16LE;
153 }
154
155
~PSoundChannelPulse()156 PSoundChannelPulse::~PSoundChannelPulse()
157 {
158 PTRACE(6, "Pulse\tDestructor ");
159 Close();
160 }
161
162
163
sink_info_cb(pa_context * c,const pa_sink_info * i,int eol,void * userdata)164 static void sink_info_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata)
165 {
166 if (eol) {
167 PulseContext::signal();
168 } else {
169 ((PStringArray*) userdata)->AppendString(i->name);
170 }
171 }
172
source_info_cb(pa_context * c,const pa_source_info * i,int eol,void * userdata)173 static void source_info_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata)
174 {
175 if (eol) {
176 PulseContext::signal();
177 } else {
178 /* Ignore monitor sources */
179 if (i->monitor_of_sink==PA_INVALID_INDEX)
180 ((PStringArray*) userdata)->AppendString(i->name);
181 }
182 }
183
GetDeviceNames(Directions dir)184 PStringArray PSoundChannelPulse::GetDeviceNames(Directions dir)
185 {
186 PTRACE(6, "Pulse\tReport devicenames as \"PulseAudio\"");
187 PulseLock lock;
188 PStringArray devices;
189 devices.AppendString("PulseAudio"); // Default device
190 pa_operation* operation;
191 if (dir==Player) {
192 operation=
193 pa_context_get_sink_info_list(context,sink_info_cb,&devices);
194 } else {
195 operation=
196 pa_context_get_source_info_list(context,source_info_cb,&devices);
197 }
198 lock.waitFor(operation);
199 return devices;
200 }
201
202
GetDefaultDevice(Directions dir)203 PString PSoundChannelPulse::GetDefaultDevice(Directions dir)
204 {
205 PTRACE(6, "Pulse\t report default device as \"PulseAudio\"");
206 PStringArray devicenames;
207 devicenames = PSoundChannelPulse::GetDeviceNames(dir);
208
209 return devicenames[0];
210 }
211
stream_notify_cb(pa_stream * s,void * userdata)212 static void stream_notify_cb(pa_stream *s, void *userdata) {
213 PulseContext::signal();
214 }
215
stream_write_cb(pa_stream * s,size_t nbytes,void * userdata)216 static void stream_write_cb(pa_stream* s,size_t nbytes,void *userdata) {
217 PulseContext::signal();
218 }
219
Open(const PString & _device,Directions _dir,unsigned _numChannels,unsigned _sampleRate,unsigned _bitsPerSample)220 PBoolean PSoundChannelPulse::Open(const PString & _device,
221 Directions _dir,
222 unsigned _numChannels,
223 unsigned _sampleRate,
224 unsigned _bitsPerSample)
225 {
226 PWaitAndSignal m(deviceMutex);
227 PTRACE(6, "Pulse\t Open on device name of " << _device);
228 Close();
229 direction = _dir;
230 mNumChannels = _numChannels;
231 mSampleRate = _sampleRate;
232 mBitsPerSample = _bitsPerSample;
233 Construct();
234
235 PulseLock lock;
236 char *app = getenv ("PULSE_PROP_application.name");
237 PStringStream appName, streamName;
238 if (app != NULL)
239 appName << app;
240 else
241 appName << "PTLib plugin ";
242 if (_dir == Player)
243 streamName << ::hex << PRandom::Number();
244 else
245 streamName << ::hex << PRandom::Number();
246
247 ss.rate = _sampleRate;
248 ss.channels = _numChannels;
249 ss.format = PA_SAMPLE_S16LE;
250
251 const char* dev;
252 if (_device=="PulseAudio") {
253 /* Default device */
254 dev=NULL;
255 } else {
256 dev=_device;
257 }
258 s=pa_stream_new(context,appName.GetPointer(),&ss,NULL);
259 pa_stream_set_state_callback(s,stream_notify_cb,NULL);
260
261 if (s == NULL) {
262 PTRACE(2, ": pa_stream_new() failed: " << pa_strerror(pa_context_errno(context)));
263 PTRACE(2, ": pa_stream_new() uses stream " << streamName);
264 PTRACE(2, ": pa_stream_new() uses rate " << PINDEX(ss.rate));
265 PTRACE(2, ": pa_stream_new() uses channels " << PINDEX(ss.channels));
266 return PFalse;
267 }
268
269 if (_dir == Player) {
270 int err=pa_stream_connect_playback(s,dev,NULL,PA_STREAM_NOFLAGS,NULL,NULL);
271 if (err) {
272 PTRACE(2, ": pa_connect_playback() failed: " << pa_strerror(err));
273 pa_stream_unref(s);
274 s=NULL;
275 return PFalse;
276 }
277 pa_stream_set_write_callback(s,stream_write_cb,NULL);
278 } else {
279 int err=pa_stream_connect_record(s,dev,NULL,PA_STREAM_NOFLAGS);
280 if (err) {
281 PTRACE(2, ": pa_connect_record() failed: " << pa_strerror(pa_context_errno(context)));
282 pa_stream_unref(s);
283 s=NULL;
284 return PFalse;
285 }
286 pa_stream_set_read_callback(s,stream_write_cb,NULL);
287 /* No input yet */
288 record_len=0;
289 record_data=NULL;
290 }
291
292 /* Wait for stream to become ready */
293 while (pa_stream_get_state(s)<PA_STREAM_READY) lock.wait();
294 if (pa_stream_get_state(s)!=PA_STREAM_READY) {
295 PTRACE(2, "stream state is " << pa_stream_get_state(s));
296 pa_stream_unref(s);
297 s=NULL;
298 return PFalse;
299 }
300
301 os_handle = 1;
302 return PTrue;
303 }
304
Close()305 PBoolean PSoundChannelPulse::Close()
306 {
307 PWaitAndSignal m(deviceMutex);
308 PTRACE(6, "Pulse\tClose");
309 PulseLock lock;
310
311 if (s == NULL)
312 return PTrue;
313
314 /* Remove the reference. The main loop keeps going and will drain the output */
315 pa_stream_disconnect(s);
316 pa_stream_unref(s);
317 s = NULL;
318 os_handle = -1;
319
320 return PTrue;
321 }
322
IsOpen() const323 PBoolean PSoundChannelPulse::IsOpen() const
324 {
325 PTRACE(6, "Pulse\t report is open as " << (os_handle >= 0));
326 PulseLock lock;
327 return os_handle >= 0;
328 }
329
Write(const void * buf,PINDEX len)330 PBoolean PSoundChannelPulse::Write(const void * buf, PINDEX len)
331 {
332 PWaitAndSignal m(deviceMutex);
333 PTRACE(6, "Pulse\tWrite " << len << " bytes");
334 PulseLock lock;
335 char* buff=(char*) buf;
336
337 if (!os_handle) {
338 PTRACE(4, ": Pulse audio Write() failed as device closed");
339 return PFalse;
340 }
341
342 size_t toWrite=len;
343 while (toWrite) {
344 size_t ws;
345 while ((ws=pa_stream_writable_size(s))<=0) lock.wait();
346 if (ws>toWrite) ws=toWrite;
347 int err=pa_stream_write(s,buff,ws,NULL,0,PA_SEEK_RELATIVE);
348 if (err) {
349 PTRACE(4, ": pa_stream_write() failed: " << pa_strerror(err));
350 return PFalse;
351 }
352 toWrite-=ws;
353 buff+=ws;
354 }
355
356 lastWriteCount = len;
357
358 PTRACE(6, "Pulse\tWrite completed");
359 return PTrue;
360 }
361
Read(void * buf,PINDEX len)362 PBoolean PSoundChannelPulse::Read(void * buf, PINDEX len)
363 {
364 PWaitAndSignal m(deviceMutex);
365 PTRACE(6, "Pulse\tRead " << len << " bytes");
366 PulseLock lock;
367 char* buff=(char*) buf;
368
369 if (!os_handle) {
370 PTRACE(4, ": Pulse audio Read() failed as device closed");
371 return PFalse;
372 }
373
374 size_t toRead=len;
375 while (toRead) {
376 while (!record_len) {
377 /* Fill the record buffer first */
378 pa_stream_peek(s,&record_data,&record_len);
379 if (!record_len) lock.wait();
380 }
381 size_t toCopy=toRead<record_len ? toRead : record_len;
382 memcpy(buff,record_data,toCopy);
383 toRead-=toCopy;
384 buff+=toCopy;
385 record_data=((char*) record_data)+toCopy;
386 record_len-=toCopy;
387 /* Buffer empty? */
388 if (!record_len) pa_stream_drop(s);
389 }
390
391 lastReadCount = len;
392
393 PTRACE(6, "Pulse\tRead completed of " <<len << " bytes");
394 return PTrue;
395 }
396
397
SetFormat(unsigned numChannels,unsigned sampleRate,unsigned bitsPerSample)398 PBoolean PSoundChannelPulse::SetFormat(unsigned numChannels,
399 unsigned sampleRate,
400 unsigned bitsPerSample)
401 {
402 PTRACE(6, "Pulse\tSet format");
403
404 ss.rate = sampleRate;
405 ss.channels = numChannels;
406 PAssert((bitsPerSample == 16), PInvalidParameter);
407
408 return PTrue;
409 }
410
411 // Get the number of channels (mono/stereo) in the sound.
GetChannels() const412 unsigned PSoundChannelPulse::GetChannels() const
413 {
414 PTRACE(6, "Pulse\tGetChannels return "
415 << ss.channels << " channel(s)");
416 return ss.channels;
417 }
418
419 // Get the sample rate in samples per second.
GetSampleRate() const420 unsigned PSoundChannelPulse::GetSampleRate() const
421 {
422 PTRACE(6, "Pulse\tGet sample rate return "
423 << ss.rate << " samples per second");
424 return ss.rate;
425 }
426
427 // Get the sample size in bits per sample.
GetSampleSize() const428 unsigned PSoundChannelPulse::GetSampleSize() const
429 {
430 return 16;
431 }
432
SetBuffers(PINDEX size,PINDEX count)433 PBoolean PSoundChannelPulse::SetBuffers(PINDEX size, PINDEX count)
434 {
435 PTRACE(6, "Pulse\tSet buffers to " << size << " and " << count);
436 bufferSize = size;
437 bufferCount = count;
438
439 return PTrue;
440 }
441
442
GetBuffers(PINDEX & size,PINDEX & count)443 PBoolean PSoundChannelPulse::GetBuffers(PINDEX & size, PINDEX & count)
444 {
445 size = bufferSize;
446 count = bufferCount;
447 PTRACE(6, "Pulse\t report buffers as " << size << " and " << count);
448 return PTrue;
449 }
450
451
PlaySound(const PSound & sound,PBoolean wait)452 PBoolean PSoundChannelPulse::PlaySound(const PSound & sound, PBoolean wait)
453 {
454
455 return PFalse;
456 }
457
458
PlayFile(const PFilePath & filename,PBoolean wait)459 PBoolean PSoundChannelPulse::PlayFile(const PFilePath & filename, PBoolean wait)
460 {
461 return PFalse;
462 }
463
464
HasPlayCompleted()465 PBoolean PSoundChannelPulse::HasPlayCompleted()
466 {
467 return PTrue;
468 }
469
470
WaitForPlayCompletion()471 PBoolean PSoundChannelPulse::WaitForPlayCompletion()
472 {
473 return PTrue;
474 }
475
476
RecordSound(PSound & sound)477 PBoolean PSoundChannelPulse::RecordSound(PSound & sound)
478 {
479 return PFalse;
480 }
481
482
RecordFile(const PFilePath & filename)483 PBoolean PSoundChannelPulse::RecordFile(const PFilePath & filename)
484 {
485 return PFalse;
486 }
487
488
StartRecording()489 PBoolean PSoundChannelPulse::StartRecording()
490 {
491 return PFalse;
492 }
493
494
IsRecordBufferFull()495 PBoolean PSoundChannelPulse::IsRecordBufferFull()
496 {
497 return PFalse;
498 }
499
500
AreAllRecordBuffersFull()501 PBoolean PSoundChannelPulse::AreAllRecordBuffersFull()
502 {
503 return PFalse;
504 }
505
506
WaitForRecordBufferFull()507 PBoolean PSoundChannelPulse::WaitForRecordBufferFull()
508 {
509 return PFalse;
510 }
511
512
WaitForAllRecordBuffersFull()513 PBoolean PSoundChannelPulse::WaitForAllRecordBuffersFull()
514 {
515 return PFalse;
516 }
517
sink_volume_cb(pa_context * context,const pa_sink_info * i,int eol,void * userdata)518 static void sink_volume_cb(pa_context* context,const pa_sink_info* i,int eol,void* userdata) {
519 if (!eol) {
520 *((pa_cvolume*) userdata)=i->volume;
521 PulseContext::signal();
522 }
523 }
524
source_volume_cb(pa_context * context,const pa_source_info * i,int eol,void * userdata)525 static void source_volume_cb(pa_context* context,const pa_source_info* i,int eol,void* userdata) {
526 if (!eol) {
527 *((pa_cvolume*) userdata)=i->volume;
528 PulseContext::signal();
529 }
530 }
531
SetVolume(unsigned newVal)532 PBoolean PSoundChannelPulse::SetVolume(unsigned newVal)
533 {
534 if (s) {
535 PulseLock lock;
536 int dev=pa_stream_get_device_index(s);
537 pa_operation* operation;
538 pa_cvolume volume;
539 if (direction==Player) {
540 operation=pa_context_get_sink_info_by_index(context,dev,sink_volume_cb,&volume);
541 } else {
542 operation=pa_context_get_source_info_by_index(context,dev,source_volume_cb,&volume);
543 }
544 if (!lock.waitFor(operation)) return PFalse;
545 pa_cvolume_scale(&volume,newVal*PA_VOLUME_NORM/100);
546 if (direction==Player) {
547 pa_context_set_sink_volume_by_index(context,dev,&volume,NULL,NULL);
548 } else {
549 pa_context_set_source_volume_by_index(context,dev,&volume,NULL,NULL);
550 }
551 }
552 return PTrue;
553 }
554
GetVolume(unsigned & devVol)555 PBoolean PSoundChannelPulse::GetVolume(unsigned &devVol)
556 {
557 if (s) {
558 PulseLock lock;
559 int dev=pa_stream_get_device_index(s);
560 pa_operation* operation;
561 pa_cvolume volume;
562 if (direction==Player) {
563 operation=pa_context_get_sink_info_by_index(context,dev,sink_volume_cb,&volume);
564 } else {
565 operation=pa_context_get_source_info_by_index(context,dev,source_volume_cb,&volume);
566 }
567 if (!lock.waitFor(operation)) return PFalse;
568 devVol=100*pa_cvolume_avg(&volume)/PA_VOLUME_NORM;
569 }
570 return PTrue;
571 }
572
573
574
575 // End of file
576
577