28 #define LOC QString("PulseAudio: ")
37 value = PA_VOLUME_MUTED;
57 QString fn_log_tag =
"OpenDevice, ";
64 VBERROR(fn_log_tag +
"Failed to get new threaded mainloop");
85 pa_operation *op = pa_context_get_sink_info_by_index(
m_pcontext, 0,
90 pa_operation_unref(op);
94 VBERROR(
"Failed to determine default sink samplerate");
123 QString fn_log_tag =
"OpenDevice, ";
126 VBERROR(fn_log_tag + QString(
"audio channel limit %1, but %2 requested")
144 VBERROR(fn_log_tag + QString(
"unsupported sample format %1")
151 VBERROR(fn_log_tag +
"invalid sample spec");
154 std::string spec(PA_SAMPLE_SPEC_SNPRINT_MAX,
'\0');
155 pa_sample_spec_snprint(spec.data(), spec.size(), &
m_sampleSpec);
156 VBAUDIO(fn_log_tag +
"using sample spec " + spec.data());
160 VBERROR(fn_log_tag +
"failed to init channel map");
167 VBERROR(fn_log_tag +
"failed to get new threaded mainloop");
207 pa_context_drain(
m_pcontext,
nullptr,
nullptr);
223 QString fn_log_tag =
"WriteAudio, ";
224 pa_stream_state_t sstate = pa_stream_get_state(
m_pstream);
226 VBAUDIOTS(fn_log_tag + QString(
"writing %1 bytes").arg(size));
232 if (sstate == PA_STREAM_CREATING || sstate == PA_STREAM_READY)
234 int write_status = PA_ERR_INVALID;
235 size_t to_write = size;
236 unsigned char *buf_ptr = aubuf;
242 size_t writable = pa_stream_writable_size(
m_pstream);
245 size_t write = std::min(to_write, writable);
247 nullptr, 0, PA_SEEK_RELATIVE);
249 if (0 != write_status)
264 if (write_status != 0)
266 VBERROR(fn_log_tag + QString(
"stream write failed: %1")
267 .arg(write_status == PA_ERR_BADSTATE
269 :
"PA_ERR_INVALID"));
272 VBERROR(fn_log_tag + QString(
"short write, %1 of %2")
273 .arg(size - to_write).arg(size));
277 VBERROR(fn_log_tag + QString(
"stream state not good: %1")
283 pa_usec_t latency = 0;
292 const pa_buffer_attr *buf_attr = pa_stream_get_buffer_attr(
m_pstream);
293 size_t bfree = pa_stream_writable_size(
m_pstream);
294 buffered = buf_attr->tlength - bfree;
298 while (pa_stream_get_latency(
m_pstream, &latency,
nullptr) < 0)
300 if (pa_context_errno(
m_pcontext) != PA_ERR_NODATA)
317 (float)PA_VOLUME_NORM * 100.0F;
322 QString fn_log_tag =
"SetVolumeChannel, ";
326 VBERROR(fn_log_tag + QString(
"bad volume params, channel %1, volume %2")
327 .arg(channel).arg(volume));
332 (float)volume / 100.0F * (
float)PA_VOLUME_NORM;
340 uint32_t stream_index = pa_stream_get_index(
m_pstream);
343 pa_context_set_sink_input_volume(
m_pcontext, stream_index,
348 pa_operation_unref(op);
352 QString(
"set stream volume operation failed, stream %1, "
355 .arg(pa_strerror(pa_context_errno(
m_pcontext))));
360 uint32_t sink_index = pa_stream_get_device_index(
m_pstream);
363 pa_context_set_sink_volume_by_index(
m_pcontext, sink_index,
368 pa_operation_unref(op);
372 QString(
"set sink volume operation failed, sink %1, "
375 .arg(pa_strerror(pa_context_errno(
m_pcontext))));
384 pa_operation *op = pa_stream_drain(
m_pstream,
nullptr,
this);
388 pa_operation_unref(op);
390 VBERROR(
"Drain, stream drain failed");
395 QString fn_log_tag =
"ContextConnect, ";
398 VBERROR(fn_log_tag +
"context appears to exist, but shouldn't (yet)");
403 pa_proplist *proplist = pa_proplist_new();
406 VBERROR(fn_log_tag + QString(
"failed to create new proplist"));
409 pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME,
"MythTV");
410 pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME,
"mythtv");
411 pa_proplist_sets(proplist, PA_PROP_MEDIA_ROLE,
"video");
413 pa_context_new_with_proplist(pa_threaded_mainloop_get_api(
m_mainloop),
417 VBERROR(fn_log_tag +
"failed to acquire new context");
424 !pulse_host.isEmpty() ? qPrintable(pulse_host) :
nullptr,
425 (pa_context_flags_t)0,
nullptr);
429 VBERROR(fn_log_tag + QString(
"context connect failed: %1")
430 .arg(pa_strerror(pa_context_errno(
m_pcontext))));
433 bool connected =
false;
434 pa_context_state_t state = pa_context_get_state(
m_pcontext);
435 for (; !connected; state = pa_context_get_state(
m_pcontext))
439 case PA_CONTEXT_READY:
440 VBAUDIO(fn_log_tag +
"context connection ready");
444 case PA_CONTEXT_FAILED:
445 case PA_CONTEXT_TERMINATED:
447 QString(
"context connection failed or terminated: %1")
448 .arg(pa_strerror(pa_context_errno(
m_pcontext))));
452 VBAUDIO(fn_log_tag +
"waiting for context connection ready");
462 pa_operation_unref(op);
464 VBERROR(fn_log_tag +
"failed to get PulseAudio server info");
471 QString fn_log_tag =
"ChooseHost, ";
473 QString host = parts.size() > 1 ? parts[1] : QString();
476 if (host !=
"default")
479 if (pulse_host.isEmpty() && host !=
"default")
481 QString env_pulse_host = qEnvironmentVariable(
"PULSE_SERVER");
482 if (!env_pulse_host.isEmpty())
483 pulse_host = env_pulse_host;
486 VBAUDIO(fn_log_tag + QString(
"chosen PulseAudio server: %1")
487 .arg((pulse_host !=
nullptr) ? pulse_host :
"default"));
494 QString fn_log_tag =
"ConnectPlaybackStream, ";
495 pa_proplist *proplist = pa_proplist_new();
498 VBERROR(fn_log_tag + QString(
"failed to create new proplist"));
501 pa_proplist_sets(proplist, PA_PROP_MEDIA_ROLE,
"video");
507 VBERROR(
"failed to create new playback stream");
519 (
float)volume * (
float)PA_VOLUME_NORM / 100.0F);
532 int flags = PA_STREAM_INTERPOLATE_TIMING
533 | PA_STREAM_ADJUST_LATENCY
534 | PA_STREAM_AUTO_TIMING_UPDATE
535 | PA_STREAM_NO_REMIX_CHANNELS;
538 (pa_stream_flags_t)flags,
nullptr,
nullptr);
540 pa_stream_state_t sstate = PA_STREAM_UNCONNECTED;
541 bool connected =
false;
544 while (!(connected || failed))
546 pa_context_state_t cstate = pa_context_get_state(
m_pcontext);
549 case PA_CONTEXT_FAILED:
550 case PA_CONTEXT_TERMINATED:
551 VBERROR(QString(
"context is stuffed, %1")
552 .arg(pa_strerror(pa_context_errno(
m_pcontext))));
556 switch (sstate = pa_stream_get_state(
m_pstream))
558 case PA_STREAM_READY:
561 case PA_STREAM_FAILED:
562 case PA_STREAM_TERMINATED:
563 VBERROR(QString(
"stream failed or was terminated, "
564 "context state %1, stream state %2")
565 .arg(cstate).arg(sstate));
575 const pa_buffer_attr *buf_attr = pa_stream_get_buffer_attr(
m_pstream);
579 VBAUDIO(QString(
"fragment size %1, soundcard buffer size %2")
582 return (connected && !failed);
587 QString fn_log_tag = QString(
"FlushStream (%1), ").arg(caller);
589 pa_operation *op = pa_stream_flush(
m_pstream,
nullptr,
this);
592 pa_operation_unref(op);
594 VBERROR(fn_log_tag +
"stream flush operation failed ");
600 switch (pa_context_get_state(c))
602 case PA_CONTEXT_READY:
603 case PA_CONTEXT_TERMINATED:
604 case PA_CONTEXT_FAILED:
605 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
607 case PA_CONTEXT_CONNECTING:
608 case PA_CONTEXT_UNCONNECTED:
609 case PA_CONTEXT_AUTHORIZING:
610 case PA_CONTEXT_SETTING_NAME:
618 switch (pa_stream_get_state(s))
620 case PA_STREAM_READY:
621 case PA_STREAM_TERMINATED:
622 case PA_STREAM_FAILED:
623 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
625 case PA_STREAM_UNCONNECTED:
626 case PA_STREAM_CREATING:
634 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
639 VBERROR(QString(
"stream buffer %1 flow").arg((
char*)tag));
643 pa_context *c,
int ok,
void *arg)
645 QString fn_log_tag =
"OpCompletionCallback, ";
649 VBERROR(fn_log_tag + QString(
"bummer, an operation failed: %1")
650 .arg(pa_strerror(pa_context_errno(c))));
652 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
656 pa_context *,
const pa_server_info *inf,
void *)
658 QString fn_log_tag =
"ServerInfoCallback, ";
661 QString(
"PulseAudio server info - host name: %1, server version: "
662 "%2, server name: %3, default sink: %4")
663 .arg(inf->host_name, inf->server_version,
664 inf->server_name, inf->default_sink_name));
668 pa_context *,
const pa_sink_info *info,
int ,
void *arg)
674 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
678 audoutP->m_aoSettings->AddSupportedRate(info->sample_spec.rate);
680 for (
uint i = 2; i <= info->sample_spec.channels; i++)
681 audoutP->m_aoSettings->AddSupportedChannels(i);
683 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);