Ticket #5473: 30_pulseaudio_output.dpatch

File 30_pulseaudio_output.dpatch, 31.5 KB (added by IntuitiveNipple, 17 years ago)

0.21+fixes18722 (Ubuntu Intrepid) patch

Line 
1#! /bin/sh /usr/share/dpatch/dpatch-run
2## 30_pulseaudio_output.dpatch by TJ <ubuntu@tjworld.net>
3##
4## All lines beginning with `## DP:' are a description of the patch.
5## DP: Adopted from http://svn.mythtv.org/trac/ticket/5473
6
7@DPATCH@
8diff --git a/configure b/configure
9index 8c83453..3c00c0b 100755
10--- a/configure
11+++ b/configure
12@@ -1,5 +1,5 @@
13 #!/bin/sh
14-#
15+#jack
16 # MythTV configure script, based on the FFmpeg configure script
17 #
18 # Copyright (c) 2000, 2001, 2002 Fabrice Bellard
19@@ -144,6 +144,7 @@ BLOCK_QUOTE
20 echo " --disable-audio-alsa disable ALSA audio support"
21 echo " --disable-audio-arts disable aRts audio support"
22 echo " --disable-audio-jack disable JACK audio support"
23+ echo " --disable-audio-pulse disable PulseAudio audio support"
24 echo " --enable-valgrind disables timeouts for valgrind memory debugging"
25 # Don't print disable frontend/backend options, these
26 # cause breakage and people use them inappropriately. -- dtk
27@@ -915,6 +916,7 @@ MYTHTV_LIST='
28 audio_arts
29 audio_jack
30 audio_oss
31+ audio_pulse
32 ffmpeg_pthreads
33 mac_bundle
34 proc_opt
35@@ -1165,6 +1167,8 @@ audio_alsa_libs="-lasound"
36 audio_arts="default"
37 audio_jack="default"
38 audio_jack_libs="-ljack"
39+audio_pulse="default"
40+audio_pulse_libs="-lpulse"
41 enable audio_oss
42 bindings_perl="yes"
43 bindings_python="yes"
44@@ -2570,6 +2574,13 @@ check_header soundcard.h
45 enable audio_jack ||
46 disable audio_jack
47
48+# PulseAudio probe
49+! disabled audio_pulse &&
50+ check_lib pulse/pulseaudio.h pa_context_connect $audio_pulse_libs &&
51+ enable audio_pulse ||
52+ disable audio_pulse
53+
54+
55 # Deal with the x11 frame grabber
56 enabled x11grab &&
57 check_header X11/Xlib.h &&
58@@ -3013,6 +3024,7 @@ if enabled frontend; then
59 echo "ALSA support ${audio_alsa-no}"
60 echo "aRts support ${audio_arts-no}"
61 echo "JACK support ${audio_jack-no}"
62+ echo "PulseAudio support ${audio_pulse-no}"
63 if test x"$targetos" = x"mingw32" ; then
64 echo "Windows support yes"
65 fi
66@@ -3209,6 +3221,11 @@ if enabled mac_bundle; then
67 append CCONFIG "mac_bundle"
68 fi
69
70+if enabled audio_pulse; then
71+ append CCONFIG "using_pulseaudio"
72+ echo "CONFIG_AUDIO_PULSE_LIBS=$audio_pulse_libs" >> $MYTH_CONFIG_MAK
73+fi
74+
75 if enabled mac_corevideo; then
76 append CCONFIG "using_corevideo"
77 fi
78diff --git a/libs/libmyth/audiooutput.cpp b/libs/libmyth/audiooutput.cpp
79index 1eab657..8a07bc9 100644
80--- a/libs/libmyth/audiooutput.cpp
81+++ b/libs/libmyth/audiooutput.cpp
82@@ -30,6 +30,9 @@ using namespace std;
83 #ifdef USE_JACK
84 #include "audiooutputjack.h"
85 #endif
86+#ifdef USING_PULSEAUDIO
87+#include "audiooutputpulse.h"
88+#endif
89
90 AudioOutput *AudioOutput::OpenAudio(QString main_device,
91 QString passthru_device,
92@@ -110,6 +113,18 @@ AudioOutput *AudioOutput::OpenAudio(QString main_device,
93 return NULL;
94 #endif
95 }
96+ else if (main_device.startsWith("PulseAudio:"))
97+ {
98+#ifdef USING_PULSEAUDIO
99+ return new AudioOutputPulseAudio(main_device, passthru_device, audio_bits,
100+ audio_channels, audio_samplerate, source,
101+ set_initial_vol, audio_passthru);
102+#else
103+ VERBOSE(VB_IMPORTANT, "Audio output device is set to PulseAudio "
104+ "but PulseAudio support is not compiled in!");
105+ return NULL;
106+#endif
107+ }
108 #if defined(USING_OSS)
109 else
110 return new AudioOutputOSS(main_device, passthru_device, audio_bits,
111diff --git a/libs/libmyth/audiooutputpulse.cpp b/libs/libmyth/audiooutputpulse.cpp
112new file mode 100644
113index 0000000..0e2b628
114--- /dev/null
115+++ b/libs/libmyth/audiooutputpulse.cpp
116@@ -0,0 +1,646 @@
117+/*
118+ * Copyright (C) 2008 Alan Calvert
119+ * This program is free software; you can redistribute it and/or
120+ * modify it under the terms of the GNU General Public License
121+ * as published by the Free Software Foundation; either version 2
122+ * of the License, or (at your option) any later version.
123+ *
124+ * This program is distributed in the hope that it will be useful,
125+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
126+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
127+ * GNU General Public License for more details.
128+ *
129+ * You should have received a copy of the GNU General Public License
130+ * along with this program; if not, write to the Free Software
131+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
132+ * 02110-1301, USA.
133+ */
134+
135+#include "audiooutputpulse.h"
136+
137+#define LOC QString("PulseAudio: ")
138+#define LOC_ERR QString("PulseAudio Error: ")
139+
140+AudioOutputPulseAudio::AudioOutputPulseAudio(
141+ QString laudio_main_device, QString laudio_passthru_device,
142+ int laudio_bits,
143+ int laudio_channels, int laudio_samplerate,
144+ AudioOutputSource lsource,
145+ bool lset_initial_vol, bool laudio_passthru) :
146+ AudioOutputBase(laudio_main_device, laudio_passthru_device,
147+ laudio_bits, laudio_channels,
148+ laudio_samplerate, lsource,
149+ lset_initial_vol, laudio_passthru),
150+ pcontext(NULL), pstream(NULL), mainloop(NULL),
151+ sample_rate(laudio_samplerate)
152+{
153+ for (unsigned int i = 0; i < PA_CHANNELS_MAX; ++i)
154+ channel_map.map[i] = PA_CHANNEL_POSITION_INVALID;
155+ volume_control.channels = 0;
156+ for (unsigned int i = 0; i < PA_CHANNELS_MAX; ++i)
157+ volume_control.values[i] = PA_VOLUME_MUTED;
158+ ChooseHost();
159+ Reconfigure(laudio_bits, laudio_channels,
160+ laudio_samplerate, laudio_passthru);
161+}
162+
163+AudioOutputPulseAudio::~AudioOutputPulseAudio()
164+{
165+ KillAudio();
166+}
167+
168+bool AudioOutputPulseAudio::OpenDevice()
169+{
170+ QString fn_log_tag = "OpenDevice, ";
171+ if (audio_channels > MAX_CHANNELS )
172+ {
173+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
174+ + QString("audio channel limit %1, but %2 requested")
175+ .arg(MAX_CHANNELS).arg(audio_channels));
176+ return false;
177+ }
178+ sample_spec.rate = sample_rate;
179+ sample_spec.channels = volume_control.channels = audio_channels;
180+ switch (audio_bits)
181+ {
182+ case 8:
183+ sample_spec.format = PA_SAMPLE_U8;
184+ break;
185+ case 16:
186+ sample_spec.format = PA_SAMPLE_S16NE;
187+ break;
188+ case 32:
189+ sample_spec.format = PA_SAMPLE_FLOAT32RE;
190+ break;
191+ default:
192+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
193+ + QString("unsupported %1 bit sample format")
194+ .arg(audio_bits));
195+ return false;
196+ }
197+ if (!pa_sample_spec_valid(&sample_spec))
198+ {
199+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
200+ + QString("invalid sample spec"));
201+ return false;
202+ }
203+ else
204+ {
205+ char spec[PA_SAMPLE_SPEC_SNPRINT_MAX];
206+ pa_sample_spec_snprint(spec, sizeof(spec), &sample_spec);
207+ VERBOSE(VB_AUDIO, LOC + fn_log_tag
208+ + QString("using sample spec %1").arg(spec));
209+ }
210+ if (!MapChannels())
211+ return false;
212+ else
213+ {
214+ if (!pa_channel_map_valid(&channel_map))
215+ {
216+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
217+ + QString("channel map invalid"));
218+ return false;
219+ }
220+ }
221+ mainloop = pa_threaded_mainloop_new();
222+ if (!mainloop)
223+ {
224+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
225+ + QString("failed to get new threaded mainloop"));
226+ return false;
227+ }
228+ pa_threaded_mainloop_start(mainloop);
229+ pa_threaded_mainloop_lock(mainloop);
230+ if (!ContextConnect())
231+ {
232+ pa_threaded_mainloop_unlock(mainloop);
233+ pa_threaded_mainloop_stop(mainloop);
234+ return false;
235+ }
236+ if (!ConnectPlaybackStream())
237+ {
238+ pa_threaded_mainloop_unlock(mainloop);
239+ pa_threaded_mainloop_stop(mainloop);
240+ return false;
241+ }
242+ pa_threaded_mainloop_unlock(mainloop);
243+ pa_usec_t usec;
244+ int neg;
245+ pa_stream_get_latency(pstream, &usec, &neg);
246+ VERBOSE(VB_AUDIO, LOC + fn_log_tag
247+ + QString("total stream latency: %1%2 usecs")
248+ .arg((neg == 1) ? "-" : "")
249+ .arg(usec));
250+ return true;
251+}
252+
253+void AudioOutputPulseAudio::CloseDevice()
254+{
255+ if (mainloop)
256+ pa_threaded_mainloop_lock(mainloop);
257+ if (pstream)
258+ {
259+ FlushStream("CloseDevice");
260+ pa_stream_disconnect(pstream);
261+ pa_stream_unref(pstream);
262+ pstream = NULL;
263+ }
264+ if (pcontext)
265+ {
266+ pa_context_drain(pcontext, NULL, NULL);
267+ pa_context_disconnect(pcontext);
268+ pcontext = NULL;
269+ }
270+
271+ if (mainloop)
272+ {
273+ pa_threaded_mainloop_unlock(mainloop);
274+ pa_threaded_mainloop_stop(mainloop);
275+ mainloop = NULL;
276+ }
277+}
278+
279+void AudioOutputPulseAudio::WriteAudio(unsigned char* aubuf, int size)
280+{
281+ if (audio_actually_paused)
282+ return;
283+ QString fn_log_tag = "WriteAudio, ";
284+ pa_stream_state_t sstate = pa_stream_get_state(pstream);
285+ /* NB This "if" check can be replaced with PA_STREAM_IS_GOOD() in
286+ PulseAudio API from 0.9.11. As 0.9.10 is still widely used
287+ we use the more verbose version for now */
288+ if (sstate == PA_STREAM_CREATING || sstate == PA_STREAM_READY)
289+ {
290+ int retries = 0;
291+ int write_status = PA_ERR_INVALID;
292+ size_t write;
293+ size_t writable;
294+ size_t to_write = size;
295+ unsigned char* buf_ptr = aubuf;
296+ pa_context_state_t cstate;
297+
298+ pa_threaded_mainloop_lock(mainloop);
299+ while (to_write > 0 && retries < 3)
300+ {
301+ write_status = 0;
302+ writable = pa_stream_writable_size(pstream);
303+ if (writable > 0)
304+ {
305+ write = min(to_write, writable);
306+ write_status = pa_stream_write(pstream, buf_ptr, write,
307+ NULL, 0, PA_SEEK_RELATIVE);
308+ if (!write_status)
309+ {
310+ buf_ptr += write;
311+ to_write -= write;
312+ }
313+ else
314+ break;
315+ }
316+ else if (writable < 0)
317+ break;
318+ else // writable == 0
319+ pa_threaded_mainloop_wait(mainloop);
320+ ++retries;
321+ }
322+ pa_threaded_mainloop_unlock(mainloop);
323+ if (to_write > 0)
324+ {
325+ if (writable < 0)
326+ {
327+ cstate = pa_context_get_state(pcontext);
328+ sstate = pa_stream_get_state(pstream);
329+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
330+ + QString("stream unfit for writing (writable < 0), "
331+ "context state: %1, stream state: %2")
332+ .arg(cstate,0,16).arg(sstate,0,16));
333+ }
334+ if (write_status != 0)
335+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
336+ + QString("stream write failed: %1")
337+ .arg((write_status == PA_ERR_BADSTATE)
338+ ? "PA_ERR_BADSTATE"
339+ : "PA_ERR_INVALID"));
340+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
341+ + QString("short write, %1 of %2, tries %3")
342+ .arg(size - to_write).arg(size).arg(retries));
343+ }
344+ }
345+ else
346+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
347+ + QString("stream state not good: %1").arg(sstate,0,16));
348+}
349+
350+int AudioOutputPulseAudio::getBufferedOnSoundcard(void)
351+{
352+ pa_threaded_mainloop_lock(mainloop);
353+ int writable = pa_stream_writable_size(pstream);
354+ pa_threaded_mainloop_unlock(mainloop);
355+ return soundcard_buffer_size - writable;
356+}
357+
358+int AudioOutputPulseAudio::getSpaceOnSoundcard(void)
359+{
360+ pa_threaded_mainloop_lock(mainloop);
361+ int writable = pa_stream_writable_size(pstream);
362+ pa_threaded_mainloop_unlock(mainloop);
363+ return writable;
364+}
365+
366+int AudioOutputPulseAudio::GetVolumeChannel(int channel)
367+{
368+ return (float)volume_control.values[channel]
369+ / (float)PA_VOLUME_NORM * 100.0f;
370+}
371+
372+void AudioOutputPulseAudio::SetVolumeChannel(int channel, int volume)
373+{
374+ QString fn_log_tag = "SetVolumeChannel, ";
375+ if (channel < 0 || channel > MAX_CHANNELS || volume < 0)
376+ {
377+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
378+ + QString("bad volume params, channel %1, volume %2")
379+ .arg(channel).arg(volume));
380+ return;
381+ }
382+ volume_control.values[channel] =
383+ (float)volume / 100.0f * (float)PA_VOLUME_NORM;
384+ volume = min(100, volume);
385+ volume = max(0, volume);
386+ uint32_t sink_index = pa_stream_get_device_index(pstream);
387+ pa_threaded_mainloop_lock(mainloop);
388+ pa_operation* op =
389+ pa_context_set_sink_volume_by_index(pcontext, sink_index,
390+ &volume_control,
391+ OpCompletionCallback, this);
392+ pa_threaded_mainloop_unlock(mainloop);
393+ if (op)
394+ pa_operation_unref(op);
395+ else
396+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
397+ + QString("set sink volume operation failed, sink: %1, "
398+ "error: %2 ")
399+ .arg(sink_index)
400+ .arg(pa_strerror(pa_context_errno(pcontext))));
401+}
402+
403+void AudioOutputPulseAudio::Pause(bool paused)
404+{
405+ pa_operation* op;
406+ if (paused && !audio_actually_paused)
407+ {
408+ FlushStream("Pause");
409+ pa_threaded_mainloop_lock(mainloop);
410+ op = pa_stream_cork(pstream, 1, NULL, this);
411+ pa_threaded_mainloop_unlock(mainloop);
412+ if (op)
413+ {
414+ pa_operation_unref(op);
415+ audio_actually_paused = true;
416+ VERBOSE(VB_AUDIO, LOC + "Pause, audio actually paused");
417+ }
418+ else
419+ {
420+ VERBOSE(VB_IMPORTANT, LOC_ERR + "Pause, cork stream failed");
421+ audio_actually_paused = false;
422+ }
423+ pauseaudio = true;
424+ }
425+ else if(!paused && audio_actually_paused)
426+ {
427+ pa_threaded_mainloop_lock(mainloop);
428+ op = pa_stream_cork(pstream, 0, NULL, this);
429+ pa_threaded_mainloop_unlock(mainloop);
430+ if (op)
431+ {
432+ pa_operation_unref(op);
433+ VERBOSE(VB_AUDIO, LOC + "Pause, audio actually unpaused");
434+ }
435+ else
436+ VERBOSE(VB_IMPORTANT, LOC_ERR + "Pause, un-cork stream failed");
437+ FlushStream("(un)Pause");
438+ pauseaudio = false;
439+ audio_actually_paused = false;
440+ }
441+}
442+
443+void AudioOutputPulseAudio::Reset(void)
444+{
445+ AudioOutputBase::Reset();
446+ FlushStream("Reset");
447+}
448+
449+void AudioOutputPulseAudio::Drain(void)
450+{
451+ AudioOutputBase::Drain();
452+ pa_threaded_mainloop_lock(mainloop);
453+ pa_operation* op = pa_stream_drain(pstream, NULL, this);
454+ pa_threaded_mainloop_unlock(mainloop);
455+ if (op)
456+ pa_operation_unref(op);
457+ else
458+ VERBOSE(VB_IMPORTANT, LOC_ERR + "Drain, stream drain failed ");
459+}
460+
461+bool AudioOutputPulseAudio::MapChannels(void)
462+{
463+ QString fn_log_tag = "MapChannels, ";
464+ channel_map.channels = audio_channels;
465+ for (int ch = 0; ch < channel_map.channels; ++ch)
466+ switch (ch)
467+ {
468+ case 0:
469+ channel_map.map[0] =
470+ (audio_channels < 2) ? PA_CHANNEL_POSITION_MONO
471+ : PA_CHANNEL_POSITION_FRONT_LEFT;
472+ break;
473+ case 1:
474+ channel_map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
475+ break;
476+ case 2:
477+ channel_map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT;
478+ break;
479+ case 3:
480+ channel_map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT;
481+ break;
482+ case 4:
483+ channel_map.map[4] = PA_CHANNEL_POSITION_FRONT_CENTER;
484+ break;
485+ case 5:
486+ channel_map.map[5] = PA_CHANNEL_POSITION_LFE;
487+ break;
488+ default:
489+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
490+ + QString("invalid channel map count: %1 channels")
491+ .arg(audio_channels));
492+ return false;
493+ }
494+ return true;
495+}
496+
497+bool AudioOutputPulseAudio::ContextConnect(void)
498+{
499+ QString fn_log_tag = "ContextConnect, ";
500+ if (pcontext)
501+ {
502+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
503+ + QString("context appears to exist, but shouldn't (yet)"));
504+ pa_context_unref(pcontext);
505+ pcontext = NULL;
506+ return false;
507+ }
508+ pcontext = pa_context_new(pa_threaded_mainloop_get_api(mainloop), "MythTV");
509+ if (!pcontext)
510+ {
511+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
512+ + QString("failed to acquire new context"));
513+ return false;
514+ }
515+ pa_context_set_state_callback(pcontext, ContextStateCallback, this);
516+
517+ char* pulse_host = ChooseHost();
518+ int chk = pa_context_connect(pcontext, pulse_host, (pa_context_flags_t)0, NULL);
519+ if (pulse_host)
520+ delete(pulse_host);
521+ if (chk < 0)
522+ {
523+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
524+ + QString("context connect failed: %1")
525+ .arg(pa_strerror(pa_context_errno(pcontext))));
526+ return false;
527+ }
528+ bool connected = false;
529+ pa_context_state_t state = pa_context_get_state(pcontext);
530+ for (; !connected; state = pa_context_get_state(pcontext))
531+ {
532+ switch(state)
533+ {
534+ case PA_CONTEXT_READY:
535+ VERBOSE(VB_AUDIO, LOC + fn_log_tag
536+ + QString("context connection ready, move on"));
537+ connected = true;
538+ continue;
539+
540+ case PA_CONTEXT_FAILED:
541+ case PA_CONTEXT_TERMINATED:
542+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
543+ + QString("context connection failed or was "
544+ "terminated: %1")
545+ .arg(pa_strerror(pa_context_errno(pcontext))));
546+ return false;
547+
548+ default:
549+ VERBOSE(VB_AUDIO, LOC + fn_log_tag
550+ + QString("waiting for context connection ready"));
551+ pa_threaded_mainloop_wait(mainloop);
552+ break;
553+ }
554+ }
555+ pa_operation* op =
556+ pa_context_get_server_info(pcontext, ServerInfoCallback, this);
557+ if (op)
558+ pa_operation_unref(op);
559+ else
560+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
561+ + QString("failed to get PulseAudio server info"));
562+ return true;
563+}
564+
565+char* AudioOutputPulseAudio::ChooseHost(void)
566+{
567+ QString fn_log_tag = "ChooseHost, ";
568+ char* pulse_host = NULL;
569+ char *device = (char *)audio_main_device.ascii();
570+ char *host;
571+ for (host=device; host && *host != ':' && *host != 0; host++);
572+ if (host && *host != 0)
573+ host++;
574+ if ( !(!host || *host == 0 || strcmp(host,"default") == 0)) {
575+ if ((pulse_host = new char[strlen(host)]))
576+ strcpy(pulse_host, host);
577+ else
578+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
579+ + QString("allocation of pulse host '%1' char[%2] failed")
580+ .arg(host).arg(strlen(host) + 1));
581+ }
582+ if (!pulse_host && strcmp(host,"default") != 0)
583+ {
584+ char* env_pulse_host = getenv("PULSE_SERVER");
585+ if (env_pulse_host && strlen(env_pulse_host) > 0)
586+ {
587+ int host_len = strlen(env_pulse_host) + 1;
588+ if ((pulse_host = new char[host_len]))
589+ strcpy(pulse_host, env_pulse_host);
590+ else
591+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
592+ + QString("allocation of pulse host '%1' char[%2] failed")
593+ .arg(env_pulse_host).arg(host_len));
594+ }
595+ }
596+ VERBOSE(VB_AUDIO, LOC + fn_log_tag
597+ + QString("chosen PulseAudio server: %1")
598+ .arg((pulse_host != NULL) ? pulse_host : "default"));
599+ return pulse_host;
600+}
601+
602+bool AudioOutputPulseAudio::ConnectPlaybackStream(void)
603+{
604+ QString fn_log_tag = "ConnectPlaybackStream, ";
605+ pstream = pa_stream_new(pcontext, "MythTV playback", &sample_spec,
606+ &channel_map);
607+ if (!pstream)
608+ {
609+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
610+ + QString("failed to create new playback stream"));
611+ return false;
612+ }
613+ pa_stream_set_state_callback(pstream, StreamStateCallback, this);
614+ pa_stream_set_overflow_callback(pstream, BufferFlowCallback, (char*)"over");
615+ pa_stream_set_underflow_callback(pstream, BufferFlowCallback,
616+ (char*)"under");
617+ if (set_initial_vol)
618+ {
619+ int volume = gContext->GetNumSetting("MasterMixerVolume", 80);
620+ pa_cvolume_set(&volume_control, audio_channels,
621+ (float)volume * (float)PA_VOLUME_NORM / 100.0f);
622+ }
623+ else
624+ pa_cvolume_reset(&volume_control, audio_channels);
625+
626+ // set myth sizes and pa buffer metrics
627+ fragment_size = (float)sample_rate * 0.020f // 20msec
628+ * (float)(audio_bits / 8 * audio_channels);
629+ soundcard_buffer_size = 16 * fragment_size;
630+ buffer_settings.maxlength = soundcard_buffer_size;
631+ buffer_settings.tlength = fragment_size * 4;
632+ buffer_settings.prebuf = (uint32_t)-1;
633+ buffer_settings.minreq = (uint32_t)-1;
634+ VERBOSE(VB_AUDIO, LOC + fn_log_tag
635+ + QString("fragment size %1, soundcard buffer size %2")
636+ .arg(fragment_size).arg(soundcard_buffer_size));
637+
638+ int flags = PA_STREAM_INTERPOLATE_TIMING
639+ | PA_STREAM_AUTO_TIMING_UPDATE
640+ | PA_STREAM_NO_REMAP_CHANNELS
641+ | PA_STREAM_NO_REMIX_CHANNELS;
642+ pa_stream_connect_playback(pstream, NULL, &buffer_settings,
643+ (pa_stream_flags_t)flags, &volume_control, NULL);
644+ pa_context_state_t cstate;
645+ pa_stream_state_t sstate;
646+ bool connected = false, failed = false;
647+ while (!(connected || failed))
648+ {
649+ switch (cstate = pa_context_get_state(pcontext))
650+ {
651+ case PA_CONTEXT_FAILED:
652+ case PA_CONTEXT_TERMINATED:
653+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
654+ + QString("context is stuffed, %1")
655+ .arg(pa_strerror(pa_context_errno(
656+ pcontext))));
657+ failed = true;
658+ break;
659+ default:
660+ switch (sstate = pa_stream_get_state(pstream))
661+ {
662+ case PA_STREAM_READY:
663+ connected = true;
664+ break;
665+ case PA_STREAM_FAILED:
666+ case PA_STREAM_TERMINATED:
667+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
668+ + QString("stream failed or was terminated, "
669+ "context state %1, stream state %2")
670+ .arg(cstate).arg(sstate));
671+ failed = true;
672+ break;
673+ default:
674+ pa_threaded_mainloop_wait(mainloop);
675+ break;
676+ }
677+ }
678+ }
679+ return (connected && !failed);
680+}
681+
682+void AudioOutputPulseAudio::FlushStream(const char* caller)
683+{
684+ QString fn_log_tag = QString("FlushStream (%1), ").arg(caller);
685+ pa_threaded_mainloop_lock(mainloop);
686+ pa_operation* op = pa_stream_flush(pstream, NULL, this);
687+ pa_threaded_mainloop_unlock(mainloop);
688+ if (op)
689+ pa_operation_unref(op);
690+ else
691+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
692+ + "stream flush operation failed ");
693+}
694+
695+void AudioOutputPulseAudio::ContextStateCallback(pa_context* c, void* arg)
696+{
697+ QString fn_log_tag = "_ContextStateCallback, ";
698+ AudioOutputPulseAudio* audoutP = static_cast<AudioOutputPulseAudio*>(arg);
699+ switch (pa_context_get_state(c))
700+ {
701+ case PA_CONTEXT_READY:
702+ pa_threaded_mainloop_signal(audoutP->mainloop, 0);
703+ break;
704+ case PA_CONTEXT_TERMINATED:
705+ case PA_CONTEXT_FAILED:
706+ pa_threaded_mainloop_signal(audoutP->mainloop, 0);
707+ break;
708+ case PA_CONTEXT_CONNECTING:
709+ case PA_CONTEXT_UNCONNECTED:
710+ case PA_CONTEXT_AUTHORIZING:
711+ case PA_CONTEXT_SETTING_NAME:
712+ break;
713+ }
714+}
715+
716+void AudioOutputPulseAudio::StreamStateCallback(pa_stream* s, void* arg)
717+{
718+ QString fn_log_tag = "StreamStateCallback, ";
719+ AudioOutputPulseAudio* audoutP = static_cast<AudioOutputPulseAudio*>(arg);
720+ switch (pa_stream_get_state(s))
721+ {
722+ case PA_STREAM_READY:
723+ case PA_STREAM_TERMINATED:
724+ case PA_STREAM_FAILED:
725+ pa_threaded_mainloop_signal(audoutP->mainloop, 0);
726+ break;
727+ case PA_STREAM_UNCONNECTED:
728+ case PA_STREAM_CREATING:
729+ break;
730+ }
731+}
732+
733+void AudioOutputPulseAudio::BufferFlowCallback(pa_stream* s, void* tag)
734+{
735+ VERBOSE(VB_IMPORTANT, LOC_ERR + QString("stream buffer %1flow")
736+ .arg((char*)tag));
737+}
738+
739+void AudioOutputPulseAudio::OpCompletionCallback(pa_context* c, int ok,
740+ void* arg)
741+{
742+ QString fn_log_tag = "OpCompletionCallback, ";
743+ AudioOutputPulseAudio* audoutP = static_cast<AudioOutputPulseAudio*>(arg);
744+ if (!ok)
745+ VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag
746+ + QString("bummer, an operation failed: %1")
747+ .arg(pa_strerror(pa_context_errno(c))));
748+ pa_threaded_mainloop_signal(audoutP->mainloop, 0);
749+}
750+
751+void AudioOutputPulseAudio::ServerInfoCallback(pa_context* context,
752+ const pa_server_info* inf,
753+ void* arg)
754+{
755+ QString fn_log_tag = "ServerInfoCallback, ";
756+ VERBOSE(VB_AUDIO, LOC + fn_log_tag
757+ + QString("PulseAudio server info - host name: %1, server version: "
758+ "%2, server name: %3, default sink: %4")
759+ .arg(inf->host_name).arg(inf->server_version)
760+ .arg(inf->server_name).arg(inf->default_sink_name));
761+}
762+/* vim: set expandtab tabstop=4 shiftwidth=4: */
763diff --git a/libs/libmyth/audiooutputpulse.h b/libs/libmyth/audiooutputpulse.h
764new file mode 100644
765index 0000000..9668ee0
766--- /dev/null
767+++ b/libs/libmyth/audiooutputpulse.h
768@@ -0,0 +1,79 @@
769+/*
770+ * Copyright (C) 2008 Alan Calvert
771+ *
772+ * This program is free software; you can redistribute it and/or
773+ * modify it under the terms of the GNU General Public License
774+ * as published by the Free Software Foundation; either version 2
775+ * of the License, or (at your option) any later version.
776+ *
777+ * This program is distributed in the hope that it will be useful,
778+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
779+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
780+ * GNU General Public License for more details.
781+ *
782+ * You should have received a copy of the GNU General Public License
783+ * along with this program; if not, write to the Free Software
784+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
785+ * 02110-1301, USA.
786+ */
787+
788+#ifndef AUDIOOUTPUTPULSE
789+#define AUDIOOUTPUTPULSE
790+
791+#include <qstring.h>
792+#include <pulse/pulseaudio.h>
793+
794+using namespace std;
795+
796+#include "audiooutputbase.h"
797+
798+#define MAX_CHANNELS 6
799+
800+class AudioOutputPulseAudio : public AudioOutputBase
801+{
802+ public:
803+ AudioOutputPulseAudio(QString laudio_main_device, QString laudio_passthru_device,
804+ int laudio_bits,
805+ int laudio_channels, int laudio_samplerate,
806+ AudioOutputSource lsource,
807+ bool lset_initial_vol, bool laudio_passthru);
808+ ~AudioOutputPulseAudio();
809+ int GetVolumeChannel(int channel);
810+ void SetVolumeChannel(int channel, int volume);
811+ void Pause(bool paused);
812+ void Reset(void);
813+ void Drain(void);
814+
815+ protected:
816+ bool OpenDevice(void);
817+ void CloseDevice(void);
818+ void WriteAudio(unsigned char* aubuf, int size);
819+ int getSpaceOnSoundcard(void);
820+ int getBufferedOnSoundcard(void);
821+
822+ private:
823+ char* ChooseHost(void);
824+ bool MapChannels(void);
825+ bool ContextConnect(void);
826+ bool ConnectPlaybackStream(void);
827+ void FlushStream(const char* caller);
828+
829+ static void ContextStateCallback(pa_context* c, void* arg);
830+ static void StreamStateCallback(pa_stream* s, void* arg);
831+ static void OpCompletionCallback(pa_context* c, int ok, void* arg);
832+ static void BufferFlowCallback(pa_stream* s, void* tag);
833+ static void ServerInfoCallback(pa_context* context,
834+ const pa_server_info* inf, void* arg);
835+
836+ pa_context* pcontext;
837+ pa_stream* pstream;
838+ pa_threaded_mainloop* mainloop;
839+ size_t sample_rate;
840+ pa_sample_spec sample_spec;
841+ pa_channel_map channel_map;
842+ pa_cvolume volume_control;
843+ pa_buffer_attr buffer_settings;
844+};
845+#endif
846+/* vim: set expandtab tabstop=4 shiftwidth=4: */
847+
848diff --git a/libs/libmyth/libmyth.pro b/libs/libmyth/libmyth.pro
849index f865d5a..561068a 100644
850--- a/libs/libmyth/libmyth.pro
851+++ b/libs/libmyth/libmyth.pro
852@@ -208,6 +208,13 @@ using_jack {
853 LIBS += $$JACK_LIBS
854 }
855
856+using_pulseaudio {
857+ DEFINES += USING_PULSEAUDIO
858+ HEADERS += audiooutputpulse.h
859+ SOURCES += audiooutputpulse.cpp
860+ LIBS += $$PULSE_LIBS
861+}
862+
863 using_directx {
864 DEFINES += USING_DIRECTX
865 HEADERS += audiooutputdx.h
866diff --git a/programs/mythfrontend/globalsettings.cpp b/programs/mythfrontend/globalsettings.cpp
867index b1012d1..b8ff8cb 100644
868--- a/programs/mythfrontend/globalsettings.cpp
869+++ b/programs/mythfrontend/globalsettings.cpp
870@@ -81,6 +81,9 @@ static HostComboBox *AudioOutputDevice()
871 #ifdef USING_DIRECTX
872 gc->addSelection("DirectX:");
873 #endif
874+#ifdef USING_PULSEAUDIO
875+ gc->addSelection("PulseAudio:default", "PulseAudio:default");
876+#endif
877 gc->addSelection("NULL", "NULL");
878
879 return gc;
880@@ -168,6 +171,9 @@ static HostComboBox *MixerDevice()
881 #ifdef USING_WINAUDIO
882 gc->addSelection("Windows:", "Windows:");
883 #endif
884+#ifdef USING_PULSEAUDIO
885+ gc->addSelection("PulseAudio:default", "PulseAudio:default");
886+#endif
887
888 return gc;
889 }
890diff --git a/programs/mythfrontend/mythfrontend.pro b/programs/mythfrontend/mythfrontend.pro
891index f7e46b5..d8c4d29 100644
892--- a/programs/mythfrontend/mythfrontend.pro
893+++ b/programs/mythfrontend/mythfrontend.pro
894@@ -66,3 +66,4 @@ using_arts:DEFINES += USING_ARTS
895 using_jack:DEFINES += USING_JACK
896 using_oss: DEFINES += USING_OSS
897 macx: DEFINES += USING_COREAUDIO
898+using_pulseaudio: DEFINES += USING_PULSEAUDIO
899diff --git a/settings.pro b/settings.pro
900index 6ffef4f..f61d901 100644
901--- a/settings.pro
902+++ b/settings.pro
903@@ -116,6 +116,7 @@ EXTRA_LIBS += $$CONFIG_AUDIO_OSS_LIBS
904 EXTRA_LIBS += $$CONFIG_AUDIO_ALSA_LIBS
905 EXTRA_LIBS += $$CONFIG_AUDIO_ARTS_LIBS
906 EXTRA_LIBS += $$CONFIG_AUDIO_JACK_LIBS
907+EXTRA_LIBS += $$CONFIG_AUDIO_PULSE_LIBS
908 EXTRA_LIBS += $$CONFIG_FIREWIRE_LIBS
909 EXTRA_LIBS += $$CONFIG_DIRECTFB_LIBS
910