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