31#define LOC QString("PulseAudio: ")
40 value = PA_VOLUME_MUTED;
70 QString fn_log_tag =
"OpenDevice, ";
77 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag +
"Failed to get new threaded mainloop");
110 pa_operation *op = pa_context_get_sink_info_by_index(
m_pcontext, 0,
115 pa_operation_unref(op);
120 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Failed to determine default sink samplerate");
161 QString fn_log_tag =
"OpenDevice, ";
164 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag + QString(
"audio channel limit %1, but %2 requested")
182 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag + QString(
"unsupported sample format %1")
189 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag +
"invalid sample spec");
192 std::string spec(PA_SAMPLE_SPEC_SNPRINT_MAX,
'\0');
193 pa_sample_spec_snprint(spec.data(), spec.size(), &
m_sampleSpec);
194 LOG(VB_AUDIO, LOG_INFO,
LOC + fn_log_tag +
"using sample spec " + spec.data());
198 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag +
"failed to init channel map");
205 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag +
"failed to get new threaded mainloop");
275 while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
279 pa_operation_unref(op);
309 QString fn_log_tag =
"WriteAudio, ";
310 pa_stream_state_t sstate = pa_stream_get_state(
m_pstream);
312 LOG(VB_AUDIO | VB_TIMESTAMP, LOG_INFO,
LOC + fn_log_tag + QString(
"writing %1 bytes").arg(size));
318 if (sstate == PA_STREAM_CREATING || sstate == PA_STREAM_READY)
320 int write_status = PA_ERR_INVALID;
321 size_t to_write = size;
322 unsigned char *buf_ptr = aubuf;
328 size_t writable = pa_stream_writable_size(
m_pstream);
331 size_t write = std::min(to_write, writable);
333 nullptr, 0, PA_SEEK_RELATIVE);
335 if (0 != write_status)
350 if (write_status != 0)
352 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag + QString(
"stream write failed: %1")
353 .arg(write_status == PA_ERR_BADSTATE
355 :
"PA_ERR_INVALID"));
358 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag + QString(
"short write, %1 of %2")
359 .arg(size - to_write).arg(size));
364 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag + QString(
"stream state not good: %1")
371 pa_usec_t latency = 0;
380 const pa_buffer_attr *buf_attr = pa_stream_get_buffer_attr(
m_pstream);
381 size_t bfree = pa_stream_writable_size(
m_pstream);
382 buffered = buf_attr->tlength - bfree;
386 while (pa_stream_get_latency(
m_pstream, &latency,
nullptr) < 0)
388 if (pa_context_errno(
m_pcontext) != PA_ERR_NODATA)
405 (float)PA_VOLUME_NORM * 100.0F;
410 QString fn_log_tag =
"SetVolumeChannel, ";
414 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag + QString(
"bad volume params, channel %1, volume %2")
415 .arg(channel).arg(volume));
420 (float)volume / 100.0F * (
float)PA_VOLUME_NORM;
428 uint32_t stream_index = pa_stream_get_index(
m_pstream);
431 pa_context_set_sink_input_volume(
m_pcontext, stream_index,
437 pa_operation_unref(op);
441 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag +
442 QString(
"set stream volume operation failed, stream %1, "
445 .arg(pa_strerror(pa_context_errno(
m_pcontext))));
450 uint32_t sink_index = pa_stream_get_device_index(
m_pstream);
453 pa_context_set_sink_volume_by_index(
m_pcontext, sink_index,
459 pa_operation_unref(op);
463 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag +
464 QString(
"set sink volume operation failed, sink %1, "
467 .arg(pa_strerror(pa_context_errno(
m_pcontext))));
476 pa_operation *op = pa_stream_drain(
m_pstream,
nullptr,
this);
480 pa_operation_unref(op);
482 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Drain, stream drain failed");
487 QString fn_log_tag =
"ContextConnect, ";
490 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag +
"context appears to exist, but shouldn't (yet)");
498 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag + QString(
"failed to create new proplist"));
501 pa_proplist_sets(
m_ctproplist, PA_PROP_APPLICATION_NAME,
"MythTV");
502 pa_proplist_sets(
m_ctproplist, PA_PROP_APPLICATION_ICON_NAME,
"mythtv");
503 pa_proplist_sets(
m_ctproplist, PA_PROP_MEDIA_ROLE,
"video");
505 pa_context_new_with_proplist(pa_threaded_mainloop_get_api(
m_mainloop),
509 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag +
"failed to acquire new context");
518 !pulse_host.isEmpty() ? qPrintable(pulse_host) :
nullptr,
519 (pa_context_flags_t)0,
nullptr);
523 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag + QString(
"context connect failed: %1")
524 .arg(pa_strerror(pa_context_errno(
m_pcontext))));
529 bool connected =
false;
530 pa_context_state_t state = pa_context_get_state(
m_pcontext);
531 for (; !connected; state = pa_context_get_state(
m_pcontext))
535 case PA_CONTEXT_READY:
536 LOG(VB_AUDIO, LOG_INFO,
LOC + fn_log_tag +
"context connection ready");
540 case PA_CONTEXT_FAILED:
541 case PA_CONTEXT_TERMINATED:
542 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag +
543 QString(
"context connection failed or terminated: %1")
544 .arg(pa_strerror(pa_context_errno(
m_pcontext))));
550 LOG(VB_AUDIO, LOG_INFO,
LOC + fn_log_tag +
"waiting for context connection ready");
560 pa_operation_unref(op);
562 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag +
"failed to get PulseAudio server info");
569 QString fn_log_tag =
"ChooseHost, ";
571 QString host = parts.size() > 1 ? parts[1] : QString();
574 if (host !=
"default")
577 if (pulse_host.isEmpty() && host !=
"default")
579 QString env_pulse_host = qEnvironmentVariable(
"PULSE_SERVER");
580 if (!env_pulse_host.isEmpty())
581 pulse_host = env_pulse_host;
584 LOG(VB_AUDIO, LOG_INFO,
LOC + fn_log_tag + QString(
"chosen PulseAudio server: %1")
585 .arg((pulse_host !=
nullptr) ? pulse_host :
"default"));
592 QString fn_log_tag =
"ConnectPlaybackStream, ";
596 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag + QString(
"failed to create new proplist"));
599 pa_proplist_sets(
m_stproplist, PA_PROP_MEDIA_ROLE,
"video");
605 LOG(VB_GENERAL, LOG_ERR,
LOC +
"failed to create new playback stream");
627 (
float)volume * (
float)PA_VOLUME_NORM / 100.0F);
642 int flags = PA_STREAM_INTERPOLATE_TIMING
643 | PA_STREAM_ADJUST_LATENCY
644 | PA_STREAM_AUTO_TIMING_UPDATE
645 | PA_STREAM_NO_REMIX_CHANNELS;
648 (pa_stream_flags_t)flags,
nullptr,
nullptr);
650 pa_stream_state_t sstate = PA_STREAM_UNCONNECTED;
651 bool connected =
false;
654 while (!(connected || failed))
656 pa_context_state_t cstate = pa_context_get_state(
m_pcontext);
659 case PA_CONTEXT_FAILED:
660 case PA_CONTEXT_TERMINATED:
661 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"context is stuffed, %1")
662 .arg(pa_strerror(pa_context_errno(
m_pcontext))));
668 switch (sstate = pa_stream_get_state(
m_pstream))
670 case PA_STREAM_READY:
673 case PA_STREAM_FAILED:
674 case PA_STREAM_TERMINATED:
675 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"stream failed or was terminated, "
676 "context state %1, stream state %2")
677 .arg(cstate).arg(sstate));
689 const pa_buffer_attr *buf_attr = pa_stream_get_buffer_attr(
m_pstream);
693 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"fragment size %1, soundcard buffer size %2")
696 return (connected && !failed);
701 QString fn_log_tag = QString(
"FlushStream (%1), ").arg(caller);
703 pa_operation *op = pa_stream_flush(
m_pstream,
nullptr,
this);
706 pa_operation_unref(op);
708 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag +
"stream flush operation failed ");
713 auto *mloop = (pa_threaded_mainloop *)arg;
714 pa_threaded_mainloop_signal(mloop, 0);
720 switch (pa_context_get_state(c))
722 case PA_CONTEXT_READY:
723 case PA_CONTEXT_TERMINATED:
724 case PA_CONTEXT_FAILED:
725 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
727 case PA_CONTEXT_CONNECTING:
728 case PA_CONTEXT_UNCONNECTED:
729 case PA_CONTEXT_AUTHORIZING:
730 case PA_CONTEXT_SETTING_NAME:
738 switch (pa_stream_get_state(s))
740 case PA_STREAM_READY:
741 case PA_STREAM_TERMINATED:
742 case PA_STREAM_FAILED:
743 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
745 case PA_STREAM_UNCONNECTED:
746 case PA_STREAM_CREATING:
754 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
759 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"stream buffer %1 flow").arg((
char*)tag));
763 pa_context *c,
int ok,
void *arg)
765 QString fn_log_tag =
"OpCompletionCallback, ";
769 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag + QString(
"bummer, an operation failed: %1")
770 .arg(pa_strerror(pa_context_errno(c))));
772 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
776 pa_context *,
const pa_server_info *inf,
void *)
778 QString fn_log_tag =
"ServerInfoCallback, ";
780 LOG(VB_AUDIO, LOG_INFO,
LOC + fn_log_tag +
781 QString(
"PulseAudio server info - host name: %1, server version: "
782 "%2, server name: %3, default sink: %4")
783 .arg(inf->host_name, inf->server_version,
784 inf->server_name, inf->default_sink_name));
788 pa_context *,
const pa_sink_info *
info,
int ,
void *arg)
794 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
798 audoutP->m_aoSettings->AddSupportedRate(
info->sample_spec.rate);
800 for (
uint i = 2; i <=
info->sample_spec.channels; i++)
801 audoutP->m_aoSettings->AddSupportedChannels(i);
803 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
static constexpr int8_t PULSE_MAX_CHANNELS
void KillAudio(void)
Kill the output thread and cleanup.
void Reconfigure(const AudioSettings &settings) override
(Re)Configure AudioOutputBase
AudioFormat m_outputFormat
int m_outputBytesPerFrame
long m_soundcardBufferSize
void InitSettings(const AudioSettings &settings)
void Drain(void) override
Block until all available frames have been written to the device.
int GetBufferedOnSoundcard(void) const override
Return the size in bytes of frames currently in the audio buffer adjusted with the audio playback lat...
void SetVolumeChannel(int channel, int volume) override
void WriteAudio(unsigned char *aubuf, int size) override
pa_proplist * m_ctproplist
pa_channel_map m_channelMap
pa_sample_spec m_sampleSpec
static void BufferFlowCallback(pa_stream *s, void *tag)
bool ConnectPlaybackStream(void)
void Drain(void) override
Block until all available frames have been written to the device.
static void SinkInfoCallback(pa_context *c, const pa_sink_info *info, int eol, void *arg)
static void ServerInfoCallback(pa_context *context, const pa_server_info *inf, void *arg)
static void ContextDrainCallback(pa_context *c, void *arg)
pa_cvolume m_volumeControl
void FlushStream(const char *caller)
AudioOutputPulseAudio(const AudioSettings &settings)
pa_proplist * m_stproplist
AudioOutputSettings * m_aoSettings
static void WriteCallback(pa_stream *s, size_t size, void *arg)
static void OpCompletionCallback(pa_context *c, int ok, void *arg)
static void StreamStateCallback(pa_stream *s, void *arg)
AudioOutputSettings * GetOutputSettings(bool digital) override
pa_threaded_mainloop * m_mainloop
int GetVolumeChannel(int channel) const override
static void ContextStateCallback(pa_context *c, void *arg)
bool OpenDevice(void) override
pa_buffer_attr m_bufferSettings
~AudioOutputPulseAudio() override
bool ContextConnect(void)
void CloseDevice(void) override
AudioFormat GetNextFormat()
void AddSupportedFormat(AudioFormat format)
bool m_init
If set to false, AudioOutput instance will not try to initially open the audio device.
QString GetSetting(const QString &key, const QString &defaultval="")
int GetNumSetting(const QString &key, int defaultval=0)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
def write(text, progress=True)