1 /*******************************************************************************
2 *                         Goggles Audio Player Library                         *
3 ********************************************************************************
4 *           Copyright (C) 2010-2021 by Sander Jansen. All Rights Reserved      *
5 *                               ---                                            *
6 * This program is free software: you can redistribute it and/or modify         *
7 * it under the terms of the GNU General Public License as published by         *
8 * the Free Software Foundation, either version 3 of the License, or            *
9 * (at your option) any later version.                                          *
10 *                                                                              *
11 * This program is distributed in the hope that it will be useful,              *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of               *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                *
14 * GNU General Public License for more details.                                 *
15 *                                                                              *
16 * You should have received a copy of the GNU General Public License            *
17 * along with this program.  If not, see http://www.gnu.org/licenses.           *
18 ********************************************************************************/
19 #include "ap_defs.h"
20 #include "ap_output_plugin.h"
21 
22 extern "C" {
23 #include <pulse/pulseaudio.h>
24 }
25 
26 using namespace ap;
27 
pulse_quit(pa_mainloop_api *,int)28 void pulse_quit(pa_mainloop_api*,int){
29   GM_DEBUG_PRINT("[pulse] pulse_quit\n");
30   }
31 
32 
33 namespace ap {
34 
35 
36 class PulseOutput : public OutputPlugin {
37 protected:
38   static PulseOutput* instance;
39 protected:
40   friend struct ::pa_io_event;
41   friend struct ::pa_time_event;
42   friend struct ::pa_defer_event;
43 protected:
44   pa_mainloop_api  api;
45   pa_context     * pulse_context = nullptr;
46   pa_stream      * stream        = nullptr;
47   pa_volume_t      pulsevolume   = PA_VOLUME_MUTED;
48 protected:
49   static void sink_info_callback(pa_context*, const pa_sink_input_info *,int eol,void*);
50   static void context_subscribe_callback(pa_context *c,pa_subscription_event_type_t, uint32_t,void*);
51 protected:
52   FXbool open();
53 public:
54   PulseOutput(OutputContext*);
55 
56   /// Configure
57   FXbool configure(const AudioFormat &);
58 
59   /// Write frames to playback buffer
60   FXbool write(const void*, FXuint);
61 
62   /// Return delay in no. of frames
63   FXint delay();
64 
65   /// Empty Playback Buffer Immediately
66   void drop();
67 
68   /// Wait until playback buffer is emtpy.
69   void drain();
70 
71   /// Pause
72   void pause(FXbool);
73 
74   /// Change Volume
75   void volume(FXfloat);
76 
77   /// Close Output
78   void close();
79 
80   /// Get Device Type
type() const81   FXchar type() const { return DevicePulse; }
82 
83   /// Destructor
84   virtual ~PulseOutput();
85   };
86 
87 
88 }
89 
90 
91 
92 
93 
94 
95 struct pa_io_event : public Reactor::Input {
96 public:
97   static pa_io_event*      recycle;
98   pa_io_event_cb_t         callback         = nullptr;
99   pa_io_event_destroy_cb_t destroy_callback = nullptr;
100   void *                   userdata         = nullptr;
101 protected:
102 
103   /// Convert mode flags to pulseaudio flags
toPulsepa_io_event104   static pa_io_event_flags_t toPulse(FXuchar mode) {
105     int event=PA_IO_EVENT_NULL;
106     event=((mode&Reactor::Input::IsReadable)  ? PA_IO_EVENT_INPUT : 0) |
107           ((mode&Reactor::Input::IsWritable)  ? PA_IO_EVENT_OUTPUT : 0) |
108           ((mode&Reactor::Input::IsException) ? (PA_IO_EVENT_ERROR|PA_IO_EVENT_HANGUP) : 0);
109     return (pa_io_event_flags_t)event;
110     }
111 
112   /// Convert pulseaudio flags to mode flags
fromPulsepa_io_event113   static FXuchar fromPulse(pa_io_event_flags_t flags) {
114     return (((flags&PA_IO_EVENT_INPUT)  ? Reactor::Input::Readable  : 0) |
115             ((flags&PA_IO_EVENT_OUTPUT) ? Reactor::Input::Writable  : 0) |
116             ((flags&PA_IO_EVENT_ERROR)  ? Reactor::Input::Exception : 0) |
117             ((flags&PA_IO_EVENT_HANGUP) ? Reactor::Input::Exception : 0));
118     }
119 public:
pa_io_eventpa_io_event120   pa_io_event(FXInputHandle h,FXuchar m) : Reactor::Input(h,m),callback(nullptr),destroy_callback(nullptr),userdata(nullptr) {
121     }
122 
onSignalpa_io_event123   virtual void onSignal() {
124     callback(&(PulseOutput::instance->api),this,handle,toPulse(mode),userdata);
125     }
126 
createpa_io_event127   static pa_io_event * create(pa_mainloop_api*,int fd,pa_io_event_flags_t events, pa_io_event_cb_t cb, void *userdata) {
128     pa_io_event * event;
129     if (recycle) {
130       event         = recycle;
131       event->handle = fd;
132       event->mode   = fromPulse(events);
133       recycle       = nullptr;
134       }
135     else {
136       event = new pa_io_event(fd,fromPulse(events));
137       }
138     event->callback         = cb;
139     event->userdata         = userdata;
140     event->destroy_callback = nullptr;
141     PulseOutput::instance->context->getReactor().addInput(event);
142     return event;
143     }
144 
destroypa_io_event145   static void destroy(pa_io_event* event){
146     if (event->destroy_callback)
147       event->destroy_callback(&PulseOutput::instance->api,event,event->userdata);
148     PulseOutput::instance->context->getReactor().removeInput(event);
149     if (recycle==nullptr)
150       recycle=event;
151     else
152       delete event;
153     }
154 
enablepa_io_event155   static void enable(pa_io_event*event,pa_io_event_flags_t events) {
156     event->mode = fromPulse(events);
157     }
158 
set_destroypa_io_event159   static void set_destroy(pa_io_event *event, pa_io_event_destroy_cb_t cb){
160     event->destroy_callback=cb;
161     }
162   };
163 
164 
165 
166 
167 
168 struct pa_time_event : public Reactor::Timer {
169 public:
170   static pa_time_event*       recycle;
171   pa_time_event_cb_t          callback = nullptr;
172   pa_time_event_destroy_cb_t  destroy_callback  = nullptr;
173   void*                       userdata = nullptr;
174 public:
pa_time_eventpa_time_event175   pa_time_event() {}
176 
onExpiredpa_time_event177   virtual void onExpired() {
178     struct timeval tv;
179     tv.tv_usec = ( time / NANOSECONDS_PER_MICROSECOND ) % 1000000;
180     tv.tv_sec  = time / NANOSECONDS_PER_SECOND;
181     callback(&(PulseOutput::instance->api),this,&tv,userdata);
182     }
183 
createpa_time_event184   static pa_time_event * create(pa_mainloop_api*,const struct timeval *tv, pa_time_event_cb_t cb, void *userdata) {
185     FXTime time = (tv->tv_sec*NANOSECONDS_PER_SECOND) + (tv->tv_usec*NANOSECONDS_PER_MICROSECOND);
186     pa_time_event * event;
187     if (recycle) {
188       event   = recycle;
189       recycle = nullptr;
190       }
191     else {
192       event = new pa_time_event();
193       }
194     event->callback = cb;
195     event->userdata = userdata;
196     PulseOutput::instance->context->getReactor().addTimer(event,time);
197     return event;
198     }
199 
destroypa_time_event200   static void destroy(pa_time_event* event){
201     if (event->destroy_callback)
202       event->destroy_callback(&PulseOutput::instance->api,event,event->userdata);
203     PulseOutput::instance->context->getReactor().removeTimer(event);
204     if (recycle==nullptr)
205       recycle=event;
206     else
207       delete event;
208     }
209 
restartpa_time_event210   static void restart(pa_time_event* event, const struct timeval *tv){
211     FXTime time = (tv->tv_sec*NANOSECONDS_PER_SECOND) + (tv->tv_usec*NANOSECONDS_PER_MICROSECOND);
212     PulseOutput::instance->context->getReactor().removeTimer(event);
213     PulseOutput::instance->context->getReactor().addTimer(event,time);
214     }
215 
set_destroypa_time_event216   static void set_destroy(pa_time_event *event, pa_time_event_destroy_cb_t cb){
217     event->destroy_callback=cb;
218     }
219   };
220 
221 
222 
223 
224 
225 
226 
227 
228 struct pa_defer_event : public Reactor::Deferred {
229 public:
230   static pa_defer_event*      recycle;
231   pa_defer_event_cb_t         callback = nullptr;
232   pa_defer_event_destroy_cb_t destroy_callback = nullptr;
233   void*                       userdata = nullptr;
234 public:
pa_defer_eventpa_defer_event235   pa_defer_event() {}
236 
runpa_defer_event237   virtual void run() {
238     callback(&PulseOutput::instance->api,this,userdata);
239     }
240 
createpa_defer_event241   static pa_defer_event* create(pa_mainloop_api*, pa_defer_event_cb_t cb, void *userdata){
242     pa_defer_event * event;
243     if (recycle) {
244       event   = recycle;
245       recycle = nullptr;
246       }
247     else  {
248       event = new pa_defer_event();
249       }
250     event->callback         = cb;
251     event->userdata         = userdata;
252     event->destroy_callback = nullptr;
253 
254     PulseOutput::instance->context->getReactor().addDeferred(event);
255     return event;
256     };
257 
toggle_enabledpa_defer_event258   static void toggle_enabled(pa_defer_event*event,int b) {
259     if (b)
260       event->enable();
261     else
262       event->disable();
263     }
264 
destroypa_defer_event265   static void destroy(pa_defer_event* event){
266     if (event->destroy_callback)
267       event->destroy_callback(&PulseOutput::instance->api,event,event->userdata);
268 
269     PulseOutput::instance->context->getReactor().removeDeferred(event);
270 
271     if (recycle==nullptr)
272       recycle = event;
273     else
274       delete event;
275     }
276 
set_destroypa_defer_event277   static void set_destroy(pa_defer_event *event, pa_defer_event_destroy_cb_t cb){
278     event->destroy_callback=cb;
279     }
280   };
281 
282 
283 pa_io_event    * pa_io_event::recycle;
284 pa_time_event  * pa_time_event::recycle;
285 pa_defer_event * pa_defer_event::recycle;
286 
287 
288 
289 
290 
291 
292 
293 
294 
295 
296 
297 
298 
299 
300 namespace ap {
301 
302 
303 
304 
305 
306 
307 
308 
309 
310 
311 
312 
313 PulseOutput * PulseOutput::instance = nullptr;
314 
PulseOutput(OutputContext * ctx)315 PulseOutput::PulseOutput(OutputContext * ctx) : OutputPlugin(ctx) {
316   FXASSERT(instance==nullptr);
317   instance                = this;
318   api.userdata            = this;
319   api.io_new              = pa_io_event::create; //pulse_io_new;
320   api.io_free             = pa_io_event::destroy;// pulse_io_free;
321   api.io_enable           = pa_io_event::enable; //pulse_io_enable;
322   api.io_set_destroy      = pa_io_event::set_destroy;//pulse_io_set_destroy;
323   api.defer_new           = pa_defer_event::create; //pulse_defer_new;
324   api.defer_free          = pa_defer_event::destroy; //pulse_defer_free;
325   api.defer_enable        = pa_defer_event::toggle_enabled; //pulse_defer_enable;
326   api.defer_set_destroy   = pa_defer_event::set_destroy; //pulse_defer_set_destroy;
327   api.time_new            = pa_time_event::create;  //pulse_time_new;
328   api.time_restart        = pa_time_event::restart; //pulse_time_restart;
329   api.time_free           = pa_time_event::destroy; //pulse_time_free;
330   api.time_set_destroy    = pa_time_event::set_destroy; //pulse_time_set_destroy;
331   api.quit                = pulse_quit;
332   pa_io_event::recycle    = nullptr;
333   pa_time_event::recycle  = nullptr;
334   pa_defer_event::recycle = nullptr;
335   }
336 
~PulseOutput()337 PulseOutput::~PulseOutput() {
338   close();
339   instance=nullptr;
340   }
341 
to_pulse_format(const AudioFormat & af,pa_sample_format & pulse_format)342 static FXbool to_pulse_format(const AudioFormat & af,pa_sample_format & pulse_format){
343   switch(af.format) {
344     case AP_FORMAT_U8       : pulse_format=PA_SAMPLE_U8;    break;
345     case AP_FORMAT_S16_LE   : pulse_format=PA_SAMPLE_S16LE; break;
346     case AP_FORMAT_S16_BE   : pulse_format=PA_SAMPLE_S16BE; break;
347     case AP_FORMAT_S24_LE   : pulse_format=PA_SAMPLE_S24_32LE;  break;
348     case AP_FORMAT_S24_BE   : pulse_format=PA_SAMPLE_S24_32BE;  break;
349     case AP_FORMAT_S24_3LE  : pulse_format=PA_SAMPLE_S24LE;  break;
350     case AP_FORMAT_S24_3BE  : pulse_format=PA_SAMPLE_S24BE;  break;
351     case AP_FORMAT_S32_LE   : pulse_format=PA_SAMPLE_S32LE;  break;
352     case AP_FORMAT_S32_BE   : pulse_format=PA_SAMPLE_S32BE;  break;
353     case AP_FORMAT_FLOAT_LE : pulse_format=PA_SAMPLE_FLOAT32LE;  break;
354     case AP_FORMAT_FLOAT_BE : pulse_format=PA_SAMPLE_FLOAT32BE;  break;
355     default                 : return false; break;
356     }
357   return true;
358   }
359 
360 
to_gap_format(pa_sample_format pulse_format,AudioFormat & af)361 static FXbool to_gap_format(pa_sample_format pulse_format,AudioFormat & af){
362   switch(pulse_format) {
363     case PA_SAMPLE_U8       : af.format = AP_FORMAT_U8;       break;
364     case PA_SAMPLE_S16LE    : af.format = AP_FORMAT_S16_LE;   break;
365     case PA_SAMPLE_S16BE    : af.format = AP_FORMAT_S16_BE;   break;
366     case PA_SAMPLE_S24LE    : af.format = AP_FORMAT_S24_3LE;   break;
367     case PA_SAMPLE_S24BE    : af.format = AP_FORMAT_S24_3BE;   break;
368     case PA_SAMPLE_S24_32LE : af.format = AP_FORMAT_S24_LE;  break;
369     case PA_SAMPLE_S24_32BE : af.format = AP_FORMAT_S24_BE;  break;
370     case PA_SAMPLE_S32LE    : af.format = AP_FORMAT_S32_LE;   break;
371     case PA_SAMPLE_S32BE    : af.format = AP_FORMAT_S32_BE;   break;
372     case PA_SAMPLE_FLOAT32LE: af.format = AP_FORMAT_FLOAT_LE; break;
373     case PA_SAMPLE_FLOAT32BE: af.format = AP_FORMAT_FLOAT_BE; break;
374     default                 : return false;
375     }
376   return true;
377   }
378 
379 
380 
381 #ifdef DEBUG
context_state_callback(pa_context * c,void *)382 static void context_state_callback(pa_context *c,void*){
383   fxmessage("[pulse] context_state_callback:");
384   switch(pa_context_get_state(c)) {
385     case PA_CONTEXT_UNCONNECTED : fxmessage(" unconnected\n"); break;
386     case PA_CONTEXT_CONNECTING  : fxmessage(" connecting\n"); break;
387     case PA_CONTEXT_AUTHORIZING : fxmessage(" authorizing\n"); break;
388     case PA_CONTEXT_SETTING_NAME: fxmessage(" setting name\n"); break;
389     case PA_CONTEXT_READY       : fxmessage(" ready\n"); break;
390     case PA_CONTEXT_FAILED      : fxmessage(" failed\n"); break;
391     case PA_CONTEXT_TERMINATED  : fxmessage(" terminated\n"); break;
392     default                     : fxmessage(" unknown\n"); break;
393     }
394   }
395 
stream_state_callback(pa_stream * s,void *)396 static void stream_state_callback(pa_stream *s,void*){
397   fxmessage("[pulse] stream_state_callback:");
398   switch(pa_stream_get_state(s)) {
399     case PA_STREAM_UNCONNECTED : fxmessage(" unconnected\n"); break;
400     case PA_STREAM_CREATING    : fxmessage(" creating\n"); break;
401     case PA_STREAM_READY       : fxmessage(" ready\n"); break;
402     case PA_STREAM_FAILED      : fxmessage(" failed\n"); break;
403     case PA_STREAM_TERMINATED  : fxmessage(" terminated\n"); break;
404     default                     : fxmessage(" unknown\n"); break;
405     }
406   }
407 #endif
408 
409 
410 //static void stream_write_callback(pa_stream*s,size_t,void *){
411 //  GM_DEBUG_PRINT("stream_write_callback %d\n",pa_stream_get_state(s));
412 //  }
413 
sink_info_callback(pa_context *,const pa_sink_input_info * info,int,void * userdata)414 void PulseOutput::sink_info_callback(pa_context*, const pa_sink_input_info * info,int /*eol*/,void*userdata){
415   PulseOutput * out = static_cast<PulseOutput*>(userdata);
416   if (info) {
417     pa_volume_t value = pa_cvolume_avg(&info->volume);
418     if (out->pulsevolume!=value) {
419       out->context->notify_volume((float)value / (float)PA_VOLUME_NORM);
420       }
421     }
422   }
423 
context_subscribe_callback(pa_context * pulse_context,pa_subscription_event_type_t type,uint32_t index,void * userdata)424 void PulseOutput::context_subscribe_callback(pa_context * pulse_context, pa_subscription_event_type_t type, uint32_t index, void *userdata){
425   PulseOutput * out = static_cast<PulseOutput*>(userdata);
426 
427   if (out->stream==nullptr)
428     return;
429 
430   if (pa_stream_get_index(out->stream)!=index)
431     return;
432 
433   if ((type&PA_SUBSCRIPTION_EVENT_FACILITY_MASK)!=PA_SUBSCRIPTION_EVENT_SINK_INPUT)
434     return;
435 
436   if ((type&PA_SUBSCRIPTION_EVENT_TYPE_MASK)==PA_SUBSCRIPTION_EVENT_CHANGE ||
437       (type&PA_SUBSCRIPTION_EVENT_TYPE_MASK)==PA_SUBSCRIPTION_EVENT_NEW) {
438     pa_operation *operation = pa_context_get_sink_input_info(pulse_context,index,sink_info_callback,userdata);
439     if (operation) pa_operation_unref(operation);
440     }
441   }
442 
443 
444 
445 
open()446 FXbool PulseOutput::open() {
447 
448   /// Start the mainloop
449   //if (eventloop==nullptr) {
450    // eventloop = new PulseReactor();
451    // }
452 
453   /// Get a context
454   if (pulse_context==nullptr) {
455     pulse_context = pa_context_new(&api,"Goggles Music Manager");
456 #ifdef DEBUG
457     pa_context_set_state_callback(pulse_context,context_state_callback,this);
458 #endif
459     pa_context_set_subscribe_callback(pulse_context,context_subscribe_callback,this);
460     }
461 
462   /// Try connecting
463   GM_DEBUG_PRINT("[pulse] pa_context_connect()\n");
464   if (pa_context_get_state(pulse_context)==PA_CONTEXT_UNCONNECTED) {
465     if (pa_context_connect(pulse_context,nullptr,PA_CONTEXT_NOFLAGS,nullptr)<0) {
466       GM_DEBUG_PRINT("[pulse] pa_context_connect failed\n");
467       return false;
468       }
469     }
470 
471   /// Wait until we're connected to the pulse daemon
472   GM_DEBUG_PRINT("[pulse] wait for connection\n");
473   pa_context_state_t state;
474   while((state=pa_context_get_state(pulse_context))!=PA_CONTEXT_READY) {
475     if (state==PA_CONTEXT_FAILED || state==PA_CONTEXT_TERMINATED){
476       GM_DEBUG_PRINT("[pulse] Unable to connect to pulsedaemon\n");
477       return false;
478       }
479     context->wait_plugin_events();
480     }
481 
482   pa_operation* operation = pa_context_subscribe(pulse_context,PA_SUBSCRIPTION_MASK_SINK_INPUT,nullptr,this);
483   if (operation) pa_operation_unref(operation);
484 
485 
486   GM_DEBUG_PRINT("[pulse] ready()\n");
487   return true;
488   }
489 
close()490 void PulseOutput::close() {
491 #ifdef DEBUG
492   context->getReactor().debug();
493 #endif
494 
495   if (stream) {
496     GM_DEBUG_PRINT("[pulse] disconnecting stream\n");
497     pa_stream_disconnect(stream);
498     pa_stream_unref(stream);
499     stream=nullptr;
500     }
501 
502   if (pulse_context) {
503     GM_DEBUG_PRINT("[pulse] disconnecting context\n");
504     pa_context_disconnect(pulse_context);
505     pa_context_unref(pulse_context);
506     pulse_context=nullptr;
507     }
508 #ifdef DEBUG
509   context->getReactor().debug();
510 #endif
511 
512   delete pa_io_event::recycle;
513   delete pa_time_event::recycle;
514   delete pa_defer_event::recycle;
515   pa_io_event::recycle    = nullptr;
516   pa_time_event::recycle  = nullptr;
517   pa_defer_event::recycle = nullptr;
518   pulsevolume             = PA_VOLUME_MUTED;
519   af.reset();
520   }
521 
522 
523 
524 
volume(FXfloat v)525 void PulseOutput::volume(FXfloat v) {
526   if (pulse_context && stream) {
527     pulsevolume = (pa_volume_t)(v*PA_VOLUME_NORM);
528     pa_cvolume cvol;
529     pa_cvolume_set(&cvol,af.channels,pulsevolume);
530     pa_operation* operation = pa_context_set_sink_input_volume(pulse_context,pa_stream_get_index(stream),&cvol,nullptr,nullptr);
531     pa_operation_unref(operation);
532     }
533   }
534 
delay()535 FXint PulseOutput::delay() {
536   FXint value=0;
537   if (stream) {
538     pa_usec_t latency;
539     int negative;
540     if (pa_stream_get_latency(stream,&latency,&negative)>=0){
541       value = (latency*af.rate) / 1000000;
542       }
543     }
544   return value;
545   }
546 
drop()547 void PulseOutput::drop() {
548   if (stream) {
549     pa_operation* operation = pa_stream_flush(stream,nullptr,0);
550     pa_operation_unref(operation);
551     }
552   }
553 
drain()554 void PulseOutput::drain() {
555   if (stream) {
556     pa_operation * operation = pa_stream_drain(stream,nullptr,nullptr);
557     while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
558       context->wait_plugin_events();
559     pa_operation_unref(operation);
560     }
561   }
562 
pause(FXbool)563 void PulseOutput::pause(FXbool) {
564   }
565 
configure(const AudioFormat & fmt)566 FXbool PulseOutput::configure(const AudioFormat & fmt){
567   const pa_sample_spec * config=nullptr;
568   pa_operation *operation=nullptr;
569 
570   if (!open())
571     return false;
572 
573   if (stream && fmt==af)
574     return true;
575 
576   if (stream) {
577     pa_stream_disconnect(stream);
578     pa_stream_unref(stream);
579     stream=nullptr;
580     }
581 
582   pa_sample_spec spec;
583   pa_channel_map cmap;
584 
585   if (!to_pulse_format(fmt,spec.format))
586     goto failed;
587 
588   spec.rate     = fmt.rate;
589   spec.channels = fmt.channels;
590 
591 
592   // setup channel map
593   pa_channel_map_init(&cmap);
594   cmap.channels = fmt.channels;
595   for (FXint i=0;i<fmt.channels;i++) {
596     switch(fmt.channeltype(i)) {
597       case Channel::None        : cmap.map[i] = PA_CHANNEL_POSITION_INVALID;      break;
598       case Channel::Mono        : cmap.map[i] = PA_CHANNEL_POSITION_MONO;         break;
599       case Channel::FrontLeft   : cmap.map[i] = PA_CHANNEL_POSITION_FRONT_LEFT;   break;
600       case Channel::FrontRight  : cmap.map[i] = PA_CHANNEL_POSITION_FRONT_RIGHT;  break;
601       case Channel::FrontCenter : cmap.map[i] = PA_CHANNEL_POSITION_FRONT_CENTER; break;
602       case Channel::BackLeft    : cmap.map[i] = PA_CHANNEL_POSITION_REAR_LEFT;    break;
603       case Channel::BackRight   : cmap.map[i] = PA_CHANNEL_POSITION_REAR_RIGHT;   break;
604       case Channel::BackCenter  : cmap.map[i] = PA_CHANNEL_POSITION_REAR_CENTER;  break;
605       case Channel::SideLeft    : cmap.map[i] = PA_CHANNEL_POSITION_SIDE_LEFT;    break;
606       case Channel::SideRight   : cmap.map[i] = PA_CHANNEL_POSITION_SIDE_RIGHT;   break;
607       case Channel::LFE         : cmap.map[i] = PA_CHANNEL_POSITION_LFE;          break;
608       default: goto failed;
609       }
610     }
611 
612   stream = pa_stream_new(pulse_context,"Goggles Music Manager",&spec,&cmap);
613   if (stream == nullptr)
614     goto failed;
615 
616 #ifdef DEBUG
617   pa_stream_set_state_callback(stream,stream_state_callback,this);
618 #endif
619   //pa_stream_set_write_callback(stream,stream_write_callback,this);
620 
621   if (pa_stream_connect_playback(stream,nullptr,nullptr,PA_STREAM_NOFLAGS,nullptr,nullptr)<0)
622     goto failed;
623 
624   /// Wait until stream is ready
625   pa_stream_state_t state;
626   while((state=pa_stream_get_state(stream))!=PA_STREAM_READY) {
627     if (state==PA_STREAM_FAILED || state==PA_STREAM_TERMINATED){
628       goto failed;
629       }
630     context->wait_plugin_events();
631     }
632 
633   /// Get Actual Format
634   config = pa_stream_get_sample_spec(stream);
635   if (!to_gap_format(config->format,af))
636     goto failed;
637   af.channels=config->channels;
638   af.rate=config->rate;
639   af.channelmap=fmt.channelmap;
640 
641   /// Get Current Volume
642   operation = pa_context_get_sink_input_info(pulse_context,pa_stream_get_index(stream),sink_info_callback,this);
643   if (operation) pa_operation_unref(operation);
644 
645   return true;
646 failed:
647   GM_DEBUG_PRINT("[pulse] Unsupported pulse configuration:\n");
648   fmt.debug();
649   return false;
650   }
651 
write(const void * b,FXuint nframes)652 FXbool PulseOutput::write(const void * b,FXuint nframes){
653   FXASSERT(stream);
654   const FXchar * buffer = reinterpret_cast<const FXchar*>(b);
655   FXuint total = nframes*af.framesize();
656   while(total) {
657 
658     if (pa_stream_get_state(stream)!=PA_STREAM_READY)
659       return false;
660 
661     size_t nbytes = pa_stream_writable_size(stream);
662     size_t n = FXMIN(total,nbytes);
663     if (n==0) {
664       //fxmessage("size %ld\n",nbytes);
665       context->wait_plugin_events();
666       continue;
667       }
668     pa_stream_write(stream,buffer,n,nullptr,0,PA_SEEK_RELATIVE);
669     total-=n;
670     buffer+=n;
671     }
672   return true;
673   }
674 
675 
676 
677 
678 }
679 
680 
681 AP_IMPLEMENT_PLUGIN(PulseOutput);
682