| 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@
|
|---|
| 8 | diff --git a/configure b/configure
|
|---|
| 9 | index 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
|
|---|
| 78 | diff --git a/libs/libmyth/audiooutput.cpp b/libs/libmyth/audiooutput.cpp
|
|---|
| 79 | index 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,
|
|---|
| 111 | diff --git a/libs/libmyth/audiooutputpulse.cpp b/libs/libmyth/audiooutputpulse.cpp
|
|---|
| 112 | new file mode 100644
|
|---|
| 113 | index 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: */
|
|---|
| 763 | diff --git a/libs/libmyth/audiooutputpulse.h b/libs/libmyth/audiooutputpulse.h
|
|---|
| 764 | new file mode 100644
|
|---|
| 765 | index 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 | +
|
|---|
| 848 | diff --git a/libs/libmyth/libmyth.pro b/libs/libmyth/libmyth.pro
|
|---|
| 849 | index 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
|
|---|
| 866 | diff --git a/programs/mythfrontend/globalsettings.cpp b/programs/mythfrontend/globalsettings.cpp
|
|---|
| 867 | index 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 | }
|
|---|
| 890 | diff --git a/programs/mythfrontend/mythfrontend.pro b/programs/mythfrontend/mythfrontend.pro
|
|---|
| 891 | index 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
|
|---|
| 899 | diff --git a/settings.pro b/settings.pro
|
|---|
| 900 | index 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 |
|
|---|