00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include <QString>
00021
00022 #include "audiooutputpulse.h"
00023
00024 #define LOC QString("PulseAudio: ")
00025
00026 #define PULSE_MAX_CHANNELS 8
00027
00028 AudioOutputPulseAudio::AudioOutputPulseAudio(const AudioSettings &settings) :
00029 AudioOutputBase(settings),
00030 pcontext(NULL), pstream(NULL), mainloop(NULL),
00031 m_aosettings(NULL)
00032 {
00033 volume_control.channels = 0;
00034 for (unsigned int i = 0; i < PA_CHANNELS_MAX; ++i)
00035 volume_control.values[i] = PA_VOLUME_MUTED;
00036
00037 InitSettings(settings);
00038 if (settings.init)
00039 Reconfigure(settings);
00040 }
00041
00042 AudioOutputPulseAudio::~AudioOutputPulseAudio()
00043 {
00044 KillAudio();
00045 if (pcontext)
00046 {
00047 pa_context_unref(pcontext);
00048 pcontext = NULL;
00049 }
00050 }
00051
00052 AudioOutputSettings* AudioOutputPulseAudio::GetOutputSettings(bool )
00053 {
00054 AudioFormat fmt;
00055 m_aosettings = new AudioOutputSettings();
00056 QString fn_log_tag = "OpenDevice, ";
00057
00058
00059
00060 mainloop = pa_threaded_mainloop_new();
00061 if (!mainloop)
00062 {
00063 VBERROR(fn_log_tag + "Failed to get new threaded mainloop");
00064 delete m_aosettings;
00065 return NULL;
00066 }
00067
00068 pa_threaded_mainloop_start(mainloop);
00069 pa_threaded_mainloop_lock(mainloop);
00070
00071 if (!ContextConnect())
00072 {
00073 pa_threaded_mainloop_unlock(mainloop);
00074 pa_threaded_mainloop_stop(mainloop);
00075 delete m_aosettings;
00076 return NULL;
00077 }
00078
00079
00080
00081
00082
00083
00084 pa_operation *op = pa_context_get_sink_info_by_index(pcontext, 0,
00085 SinkInfoCallback,
00086 this);
00087 if (op)
00088 {
00089 pa_operation_unref(op);
00090 pa_threaded_mainloop_wait(mainloop);
00091 }
00092 else
00093 VBERROR("Failed to determine default sink samplerate");
00094
00095 pa_threaded_mainloop_unlock(mainloop);
00096
00097
00098 while ((fmt = m_aosettings->GetNextFormat()))
00099 {
00100 if (fmt == FORMAT_S24
00101
00102 #ifndef PA_MAJOR
00103 || fmt == FORMAT_S24LSB
00104 #endif
00105 )
00106 continue;
00107 m_aosettings->AddSupportedFormat(fmt);
00108 }
00109
00110 pa_context_disconnect(pcontext);
00111 pa_context_unref(pcontext);
00112 pcontext = NULL;
00113 pa_threaded_mainloop_stop(mainloop);
00114 mainloop = NULL;
00115
00116 return m_aosettings;
00117 }
00118
00119 bool AudioOutputPulseAudio::OpenDevice()
00120 {
00121 QString fn_log_tag = "OpenDevice, ";
00122 if (channels > PULSE_MAX_CHANNELS )
00123 {
00124 VBERROR(fn_log_tag + QString("audio channel limit %1, but %2 requested")
00125 .arg(PULSE_MAX_CHANNELS).arg(channels));
00126 return false;
00127 }
00128
00129 sample_spec.rate = samplerate;
00130 sample_spec.channels = volume_control.channels = channels;
00131 switch (output_format)
00132 {
00133 case FORMAT_U8: sample_spec.format = PA_SAMPLE_U8; break;
00134 case FORMAT_S16: sample_spec.format = PA_SAMPLE_S16NE; break;
00135
00136 #ifdef PA_MAJOR
00137 case FORMAT_S24LSB: sample_spec.format = PA_SAMPLE_S24_32NE; break;
00138 #endif
00139 case FORMAT_S32: sample_spec.format = PA_SAMPLE_S32NE; break;
00140 case FORMAT_FLT: sample_spec.format = PA_SAMPLE_FLOAT32NE; break;
00141 default:
00142 VBERROR(fn_log_tag + QString("unsupported sample format %1")
00143 .arg(output_format));
00144 return false;
00145 }
00146
00147 if (!pa_sample_spec_valid(&sample_spec))
00148 {
00149 VBERROR(fn_log_tag + "invalid sample spec");
00150 return false;
00151 }
00152 else
00153 {
00154 char spec[PA_SAMPLE_SPEC_SNPRINT_MAX];
00155 pa_sample_spec_snprint(spec, sizeof(spec), &sample_spec);
00156 VBAUDIO(fn_log_tag + QString("using sample spec %1").arg(spec));
00157 }
00158
00159 pa_channel_map *pmap = NULL;
00160
00161 if(!(pmap = pa_channel_map_init_auto(&channel_map, channels,
00162 PA_CHANNEL_MAP_WAVEEX)) < 0)
00163 {
00164 VBERROR(fn_log_tag + "failed to init channel map");
00165 return false;
00166 }
00167
00168 channel_map = *pmap;
00169
00170 mainloop = pa_threaded_mainloop_new();
00171 if (!mainloop)
00172 {
00173 VBERROR(fn_log_tag + "failed to get new threaded mainloop");
00174 return false;
00175 }
00176
00177 pa_threaded_mainloop_start(mainloop);
00178 pa_threaded_mainloop_lock(mainloop);
00179
00180 if (!ContextConnect())
00181 {
00182 pa_threaded_mainloop_unlock(mainloop);
00183 pa_threaded_mainloop_stop(mainloop);
00184 return false;
00185 }
00186
00187 if (!ConnectPlaybackStream())
00188 {
00189 pa_threaded_mainloop_unlock(mainloop);
00190 pa_threaded_mainloop_stop(mainloop);
00191 return false;
00192 }
00193
00194 pa_threaded_mainloop_unlock(mainloop);
00195 return true;
00196 }
00197
00198 void AudioOutputPulseAudio::CloseDevice()
00199 {
00200 if (mainloop)
00201 pa_threaded_mainloop_lock(mainloop);
00202
00203 if (pstream)
00204 {
00205 FlushStream("CloseDevice");
00206 pa_stream_disconnect(pstream);
00207 pa_stream_unref(pstream);
00208 pstream = NULL;
00209 }
00210
00211 if (pcontext)
00212 {
00213 pa_context_drain(pcontext, NULL, NULL);
00214 pa_context_disconnect(pcontext);
00215 pa_context_unref(pcontext);
00216 pcontext = NULL;
00217 }
00218
00219 if (mainloop)
00220 {
00221 pa_threaded_mainloop_unlock(mainloop);
00222 pa_threaded_mainloop_stop(mainloop);
00223 mainloop = NULL;
00224 }
00225 }
00226
00227 void AudioOutputPulseAudio::WriteAudio(uchar *aubuf, int size)
00228 {
00229 QString fn_log_tag = "WriteAudio, ";
00230 pa_stream_state_t sstate = pa_stream_get_state(pstream);
00231
00232 VBAUDIOTS(fn_log_tag + QString("writing %1 bytes").arg(size));
00233
00234
00235
00236
00237
00238 if (sstate == PA_STREAM_CREATING || sstate == PA_STREAM_READY)
00239 {
00240 int write_status = PA_ERR_INVALID;
00241 size_t to_write = size;
00242 unsigned char *buf_ptr = aubuf;
00243
00244 pa_threaded_mainloop_lock(mainloop);
00245 while (to_write > 0)
00246 {
00247 write_status = 0;
00248 size_t writable = pa_stream_writable_size(pstream);
00249 if (writable > 0)
00250 {
00251 size_t write = min(to_write, writable);
00252 write_status = pa_stream_write(pstream, buf_ptr, write,
00253 NULL, 0, PA_SEEK_RELATIVE);
00254
00255 if (0 != write_status)
00256 break;
00257
00258 buf_ptr += write;
00259 to_write -= write;
00260 }
00261 else
00262 {
00263 pa_threaded_mainloop_wait(mainloop);
00264 }
00265 }
00266 pa_threaded_mainloop_unlock(mainloop);
00267
00268 if (to_write > 0)
00269 {
00270 if (write_status != 0)
00271 VBERROR(fn_log_tag + QString("stream write failed: %1")
00272 .arg(write_status == PA_ERR_BADSTATE
00273 ? "PA_ERR_BADSTATE"
00274 : "PA_ERR_INVALID"));
00275
00276 VBERROR(fn_log_tag + QString("short write, %1 of %2")
00277 .arg(size - to_write).arg(size));
00278 }
00279 }
00280 else
00281 VBERROR(fn_log_tag + QString("stream state not good: %1")
00282 .arg(sstate,0,16));
00283 }
00284
00285 int AudioOutputPulseAudio::GetBufferedOnSoundcard(void) const
00286 {
00287 pa_usec_t latency = 0;
00288 size_t buffered = 0;
00289
00290 if (!pcontext || pa_context_get_state(pcontext) != PA_CONTEXT_READY)
00291 return 0;
00292
00293 if (!pstream || pa_stream_get_state(pstream) != PA_STREAM_READY)
00294 return 0;
00295
00296 const pa_buffer_attr *buf_attr = pa_stream_get_buffer_attr(pstream);
00297 size_t bfree = pa_stream_writable_size(pstream);
00298 buffered = buf_attr->tlength - bfree;
00299
00300 pa_threaded_mainloop_lock(mainloop);
00301
00302 while (pa_stream_get_latency(pstream, &latency, NULL) < 0)
00303 {
00304 if (pa_context_errno(pcontext) != PA_ERR_NODATA)
00305 {
00306 latency = 0;
00307 break;
00308 }
00309 pa_threaded_mainloop_wait(mainloop);
00310 }
00311
00312 pa_threaded_mainloop_unlock(mainloop);
00313
00314 return ((uint64_t)latency * samplerate *
00315 output_bytes_per_frame / 1000000) + buffered;
00316 }
00317
00318 int AudioOutputPulseAudio::GetVolumeChannel(int channel) const
00319 {
00320 return (float)volume_control.values[channel] /
00321 (float)PA_VOLUME_NORM * 100.0f;
00322 }
00323
00324 void AudioOutputPulseAudio::SetVolumeChannel(int channel, int volume)
00325 {
00326 QString fn_log_tag = "SetVolumeChannel, ";
00327
00328 if (channel < 0 || channel > PULSE_MAX_CHANNELS || volume < 0)
00329 {
00330 VBERROR(fn_log_tag + QString("bad volume params, channel %1, volume %2")
00331 .arg(channel).arg(volume));
00332 return;
00333 }
00334
00335 volume_control.values[channel] =
00336 (float)volume / 100.0f * (float)PA_VOLUME_NORM;
00337
00338 volume = min(100, volume);
00339 volume = max(0, volume);
00340
00341 if (gCoreContext->GetSetting("MixerControl", "PCM").toLower() == "pcm")
00342 {
00343 uint32_t stream_index = pa_stream_get_index(pstream);
00344 pa_threaded_mainloop_lock(mainloop);
00345 pa_operation *op =
00346 pa_context_set_sink_input_volume(pcontext, stream_index,
00347 &volume_control,
00348 OpCompletionCallback, this);
00349 pa_threaded_mainloop_unlock(mainloop);
00350 if (op)
00351 pa_operation_unref(op);
00352 else
00353 VBERROR(fn_log_tag +
00354 QString("set stream volume operation failed, stream %1, "
00355 "error %2 ")
00356 .arg(stream_index)
00357 .arg(pa_strerror(pa_context_errno(pcontext))));
00358 }
00359 else
00360 {
00361 uint32_t sink_index = pa_stream_get_device_index(pstream);
00362 pa_threaded_mainloop_lock(mainloop);
00363 pa_operation *op =
00364 pa_context_set_sink_volume_by_index(pcontext, sink_index,
00365 &volume_control,
00366 OpCompletionCallback, this);
00367 pa_threaded_mainloop_unlock(mainloop);
00368 if (op)
00369 pa_operation_unref(op);
00370 else
00371 VBERROR(fn_log_tag +
00372 QString("set sink volume operation failed, sink %1, "
00373 "error %2 ")
00374 .arg(sink_index)
00375 .arg(pa_strerror(pa_context_errno(pcontext))));
00376 }
00377 }
00378
00379 void AudioOutputPulseAudio::Drain(void)
00380 {
00381 AudioOutputBase::Drain();
00382 pa_threaded_mainloop_lock(mainloop);
00383 pa_operation *op = pa_stream_drain(pstream, NULL, this);
00384 pa_threaded_mainloop_unlock(mainloop);
00385
00386 if (op)
00387 pa_operation_unref(op);
00388 else
00389 VBERROR("Drain, stream drain failed");
00390 }
00391
00392 bool AudioOutputPulseAudio::ContextConnect(void)
00393 {
00394 QString fn_log_tag = "ContextConnect, ";
00395 if (pcontext)
00396 {
00397 VBERROR(fn_log_tag + "context appears to exist, but shouldn't (yet)");
00398 pa_context_unref(pcontext);
00399 pcontext = NULL;
00400 return false;
00401 }
00402 pa_proplist *proplist = pa_proplist_new();
00403 if (!proplist)
00404 {
00405 VBERROR(fn_log_tag + QString("failed to create new proplist"));
00406 return false;
00407 }
00408 pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "MythTV");
00409 pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "mythtv");
00410 pa_proplist_sets(proplist, PA_PROP_MEDIA_ROLE, "video");
00411 pcontext =
00412 pa_context_new_with_proplist(pa_threaded_mainloop_get_api(mainloop),
00413 "MythTV", proplist);
00414 if (!pcontext)
00415 {
00416 VBERROR(fn_log_tag + "failed to acquire new context");
00417 return false;
00418 }
00419 pa_context_set_state_callback(pcontext, ContextStateCallback, this);
00420
00421 char *pulse_host = ChooseHost();
00422 int chk = pa_context_connect(
00423 pcontext, pulse_host, (pa_context_flags_t)0, NULL);
00424
00425 delete[] pulse_host;
00426
00427 if (chk < 0)
00428 {
00429 VBERROR(fn_log_tag + QString("context connect failed: %1")
00430 .arg(pa_strerror(pa_context_errno(pcontext))));
00431 return false;
00432 }
00433 bool connected = false;
00434 pa_context_state_t state = pa_context_get_state(pcontext);
00435 for (; !connected; state = pa_context_get_state(pcontext))
00436 {
00437 switch(state)
00438 {
00439 case PA_CONTEXT_READY:
00440 VBAUDIO(fn_log_tag +"context connection ready");
00441 connected = true;
00442 continue;
00443
00444 case PA_CONTEXT_FAILED:
00445 case PA_CONTEXT_TERMINATED:
00446 VBERROR(fn_log_tag +
00447 QString("context connection failed or terminated: %1")
00448 .arg(pa_strerror(pa_context_errno(pcontext))));
00449 return false;
00450
00451 default:
00452 VBAUDIO(fn_log_tag + "waiting for context connection ready");
00453 pa_threaded_mainloop_wait(mainloop);
00454 break;
00455 }
00456 }
00457
00458 pa_operation *op =
00459 pa_context_get_server_info(pcontext, ServerInfoCallback, this);
00460
00461 if (op)
00462 pa_operation_unref(op);
00463 else
00464 VBERROR(fn_log_tag + "failed to get PulseAudio server info");
00465
00466 return true;
00467 }
00468
00469 char *AudioOutputPulseAudio::ChooseHost(void)
00470 {
00471 QString fn_log_tag = "ChooseHost, ";
00472 char *pulse_host = NULL;
00473 char *device = strdup(main_device.toAscii().constData());
00474 const char *host;
00475
00476 for (host=device; host && *host != ':' && *host != 0; host++);
00477
00478 if (host && *host != 0)
00479 host++;
00480
00481 if ( !(!host || *host == 0 || strcmp(host,"default") == 0))
00482 {
00483 if ((pulse_host = new char[strlen(host) + 1]))
00484 strcpy(pulse_host, host);
00485 else
00486 VBERROR(fn_log_tag +
00487 QString("allocation of pulse host '%1' char[%2] failed")
00488 .arg(host).arg(strlen(host) + 1));
00489 }
00490
00491 if (!pulse_host && strcmp(host,"default") != 0)
00492 {
00493 char *env_pulse_host = getenv("PULSE_SERVER");
00494 if (env_pulse_host && (*env_pulse_host != '\0'))
00495 {
00496 int host_len = strlen(env_pulse_host) + 1;
00497
00498 if ((pulse_host = new char[host_len]))
00499 strcpy(pulse_host, env_pulse_host);
00500 else
00501 {
00502 VBERROR(fn_log_tag +
00503 QString("allocation of pulse host '%1' char[%2] failed")
00504 .arg(env_pulse_host).arg(host_len));
00505 }
00506 }
00507 }
00508
00509 VBAUDIO(fn_log_tag + QString("chosen PulseAudio server: %1")
00510 .arg((pulse_host != NULL) ? pulse_host : "default"));
00511
00512 free(device);
00513
00514 return pulse_host;
00515 }
00516
00517 bool AudioOutputPulseAudio::ConnectPlaybackStream(void)
00518 {
00519 QString fn_log_tag = "ConnectPlaybackStream, ";
00520 pa_proplist *proplist = pa_proplist_new();
00521 if (!proplist)
00522 {
00523 VBERROR(fn_log_tag + QString("failed to create new proplist"));
00524 return false;
00525 }
00526 pa_proplist_sets(proplist, PA_PROP_MEDIA_ROLE, "video");
00527 pstream =
00528 pa_stream_new_with_proplist(pcontext, "MythTV playback", &sample_spec,
00529 &channel_map, proplist);
00530 if (!pstream)
00531 {
00532 VBERROR("failed to create new playback stream");
00533 return false;
00534 }
00535 pa_stream_set_state_callback(pstream, StreamStateCallback, this);
00536 pa_stream_set_write_callback(pstream, WriteCallback, this);
00537 pa_stream_set_overflow_callback(pstream, BufferFlowCallback, (char*)"over");
00538 pa_stream_set_underflow_callback(pstream, BufferFlowCallback,
00539 (char*)"under");
00540 if (set_initial_vol)
00541 {
00542 int volume = gCoreContext->GetNumSetting("MasterMixerVolume", 80);
00543 pa_cvolume_set(&volume_control, channels,
00544 (float)volume * (float)PA_VOLUME_NORM / 100.0f);
00545 }
00546 else
00547 pa_cvolume_reset(&volume_control, channels);
00548
00549 fragment_size = (samplerate * 25 * output_bytes_per_frame) / 1000;
00550
00551 buffer_settings.maxlength = (uint32_t)-1;
00552 buffer_settings.tlength = fragment_size * 4;
00553 buffer_settings.prebuf = (uint32_t)-1;
00554 buffer_settings.minreq = (uint32_t)-1;
00555
00556 int flags = PA_STREAM_INTERPOLATE_TIMING
00557 | PA_STREAM_ADJUST_LATENCY
00558 | PA_STREAM_AUTO_TIMING_UPDATE
00559 | PA_STREAM_NO_REMIX_CHANNELS;
00560
00561 pa_stream_connect_playback(pstream, NULL, &buffer_settings,
00562 (pa_stream_flags_t)flags, NULL, NULL);
00563
00564 pa_context_state_t cstate;
00565 pa_stream_state_t sstate;
00566 bool connected = false, failed = false;
00567
00568 while (!(connected || failed))
00569 {
00570 switch (cstate = pa_context_get_state(pcontext))
00571 {
00572 case PA_CONTEXT_FAILED:
00573 case PA_CONTEXT_TERMINATED:
00574 VBERROR(QString("context is stuffed, %1")
00575 .arg(pa_strerror(pa_context_errno(pcontext))));
00576 failed = true;
00577 break;
00578 default:
00579 switch (sstate = pa_stream_get_state(pstream))
00580 {
00581 case PA_STREAM_READY:
00582 connected = true;
00583 break;
00584 case PA_STREAM_FAILED:
00585 case PA_STREAM_TERMINATED:
00586 VBERROR(QString("stream failed or was terminated, "
00587 "context state %1, stream state %2")
00588 .arg(cstate).arg(sstate));
00589 failed = true;
00590 break;
00591 default:
00592 pa_threaded_mainloop_wait(mainloop);
00593 break;
00594 }
00595 }
00596 }
00597
00598 const pa_buffer_attr *buf_attr = pa_stream_get_buffer_attr(pstream);
00599 fragment_size = buf_attr->tlength >> 2;
00600 soundcard_buffer_size = buf_attr->maxlength;
00601
00602 VBAUDIO(QString("fragment size %1, soundcard buffer size %2")
00603 .arg(fragment_size).arg(soundcard_buffer_size));
00604
00605 return (connected && !failed);
00606 }
00607
00608 void AudioOutputPulseAudio::FlushStream(const char *caller)
00609 {
00610 QString fn_log_tag = QString("FlushStream (%1), ").arg(caller);
00611 pa_threaded_mainloop_lock(mainloop);
00612 pa_operation *op = pa_stream_flush(pstream, NULL, this);
00613 pa_threaded_mainloop_unlock(mainloop);
00614 if (op)
00615 pa_operation_unref(op);
00616 else
00617 VBERROR(fn_log_tag + "stream flush operation failed ");
00618 }
00619
00620 void AudioOutputPulseAudio::ContextStateCallback(pa_context *c, void *arg)
00621 {
00622 QString fn_log_tag = "_ContextStateCallback, ";
00623 AudioOutputPulseAudio *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
00624 switch (pa_context_get_state(c))
00625 {
00626 case PA_CONTEXT_READY:
00627 pa_threaded_mainloop_signal(audoutP->mainloop, 0);
00628 break;
00629 case PA_CONTEXT_TERMINATED:
00630 case PA_CONTEXT_FAILED:
00631 pa_threaded_mainloop_signal(audoutP->mainloop, 0);
00632 break;
00633 case PA_CONTEXT_CONNECTING:
00634 case PA_CONTEXT_UNCONNECTED:
00635 case PA_CONTEXT_AUTHORIZING:
00636 case PA_CONTEXT_SETTING_NAME:
00637 break;
00638 }
00639 }
00640
00641 void AudioOutputPulseAudio::StreamStateCallback(pa_stream *s, void *arg)
00642 {
00643 QString fn_log_tag = "StreamStateCallback, ";
00644 AudioOutputPulseAudio *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
00645 switch (pa_stream_get_state(s))
00646 {
00647 case PA_STREAM_READY:
00648 case PA_STREAM_TERMINATED:
00649 case PA_STREAM_FAILED:
00650 pa_threaded_mainloop_signal(audoutP->mainloop, 0);
00651 break;
00652 case PA_STREAM_UNCONNECTED:
00653 case PA_STREAM_CREATING:
00654 break;
00655 }
00656 }
00657
00658 void AudioOutputPulseAudio::WriteCallback(pa_stream *s, size_t size, void *arg)
00659 {
00660 AudioOutputPulseAudio *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
00661 pa_threaded_mainloop_signal(audoutP->mainloop, 0);
00662 }
00663
00664 void AudioOutputPulseAudio::BufferFlowCallback(pa_stream *s, void *tag)
00665 {
00666 VBERROR(QString("stream buffer %1 flow").arg((char*)tag));
00667 }
00668
00669 void AudioOutputPulseAudio::OpCompletionCallback(
00670 pa_context *c, int ok, void *arg)
00671 {
00672 QString fn_log_tag = "OpCompletionCallback, ";
00673 AudioOutputPulseAudio *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
00674 if (!ok)
00675 {
00676 VBERROR(fn_log_tag + QString("bummer, an operation failed: %1")
00677 .arg(pa_strerror(pa_context_errno(c))));
00678 }
00679 pa_threaded_mainloop_signal(audoutP->mainloop, 0);
00680 }
00681
00682 void AudioOutputPulseAudio::ServerInfoCallback(
00683 pa_context *context, const pa_server_info *inf, void *arg)
00684 {
00685 QString fn_log_tag = "ServerInfoCallback, ";
00686
00687 VBAUDIO(fn_log_tag +
00688 QString("PulseAudio server info - host name: %1, server version: "
00689 "%2, server name: %3, default sink: %4")
00690 .arg(inf->host_name).arg(inf->server_version)
00691 .arg(inf->server_name).arg(inf->default_sink_name));
00692 }
00693
00694 void AudioOutputPulseAudio::SinkInfoCallback(
00695 pa_context *c, const pa_sink_info *info, int eol, void *arg)
00696 {
00697 AudioOutputPulseAudio *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
00698
00699 if (!info)
00700 {
00701 pa_threaded_mainloop_signal(audoutP->mainloop, 0);
00702 return;
00703 }
00704
00705 audoutP->m_aosettings->AddSupportedRate(info->sample_spec.rate);
00706
00707 for (uint i = 2; i <= info->sample_spec.channels; i++)
00708 audoutP->m_aosettings->AddSupportedChannels(i);
00709
00710 pa_threaded_mainloop_signal(audoutP->mainloop, 0);
00711 }
00712
00713