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