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