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,
436 pa_operation_unref(op);
439 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag +
440 QString(
"set stream volume operation failed, stream %1, "
443 .arg(pa_strerror(pa_context_errno(
m_pcontext))));
448 uint32_t sink_index = pa_stream_get_device_index(
m_pstream);
451 pa_context_set_sink_volume_by_index(
m_pcontext, sink_index,
456 pa_operation_unref(op);
459 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag +
460 QString(
"set sink volume operation failed, sink %1, "
463 .arg(pa_strerror(pa_context_errno(
m_pcontext))));
472 pa_operation *op = pa_stream_drain(
m_pstream,
nullptr,
this);
476 pa_operation_unref(op);
478 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Drain, stream drain failed");
483 QString fn_log_tag =
"ContextConnect, ";
486 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag +
"context appears to exist, but shouldn't (yet)");
494 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag + QString(
"failed to create new proplist"));
497 pa_proplist_sets(
m_ctproplist, PA_PROP_APPLICATION_NAME,
"MythTV");
498 pa_proplist_sets(
m_ctproplist, PA_PROP_APPLICATION_ICON_NAME,
"mythtv");
499 pa_proplist_sets(
m_ctproplist, PA_PROP_MEDIA_ROLE,
"video");
501 pa_context_new_with_proplist(pa_threaded_mainloop_get_api(
m_mainloop),
505 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag +
"failed to acquire new context");
514 !pulse_host.isEmpty() ? qPrintable(pulse_host) :
nullptr,
515 (pa_context_flags_t)0,
nullptr);
519 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag + QString(
"context connect failed: %1")
520 .arg(pa_strerror(pa_context_errno(
m_pcontext))));
525 bool connected =
false;
526 pa_context_state_t state = pa_context_get_state(
m_pcontext);
527 for (; !connected; state = pa_context_get_state(
m_pcontext))
531 case PA_CONTEXT_READY:
532 LOG(VB_AUDIO, LOG_INFO,
LOC + fn_log_tag +
"context connection ready");
536 case PA_CONTEXT_FAILED:
537 case PA_CONTEXT_TERMINATED:
538 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag +
539 QString(
"context connection failed or terminated: %1")
540 .arg(pa_strerror(pa_context_errno(
m_pcontext))));
546 LOG(VB_AUDIO, LOG_INFO,
LOC + fn_log_tag +
"waiting for context connection ready");
556 pa_operation_unref(op);
558 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag +
"failed to get PulseAudio server info");
565 QString fn_log_tag =
"ChooseHost, ";
567 QString host = parts.size() > 1 ? parts[1] : QString();
570 if (host !=
"default")
573 if (pulse_host.isEmpty() && host !=
"default")
575 QString env_pulse_host = qEnvironmentVariable(
"PULSE_SERVER");
576 if (!env_pulse_host.isEmpty())
577 pulse_host = env_pulse_host;
580 LOG(VB_AUDIO, LOG_INFO,
LOC + fn_log_tag + QString(
"chosen PulseAudio server: %1")
581 .arg((pulse_host !=
nullptr) ? pulse_host :
"default"));
588 QString fn_log_tag =
"ConnectPlaybackStream, ";
592 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag + QString(
"failed to create new proplist"));
595 pa_proplist_sets(
m_stproplist, PA_PROP_MEDIA_ROLE,
"video");
601 LOG(VB_GENERAL, LOG_ERR,
LOC +
"failed to create new playback stream");
615 (
float)volume * (
float)PA_VOLUME_NORM / 100.0F);
630 int flags = PA_STREAM_INTERPOLATE_TIMING
631 | PA_STREAM_ADJUST_LATENCY
632 | PA_STREAM_AUTO_TIMING_UPDATE
633 | PA_STREAM_NO_REMIX_CHANNELS;
636 (pa_stream_flags_t)flags,
nullptr,
nullptr);
638 pa_stream_state_t sstate = PA_STREAM_UNCONNECTED;
639 bool connected =
false;
642 while (!(connected || failed))
644 pa_context_state_t cstate = pa_context_get_state(
m_pcontext);
647 case PA_CONTEXT_FAILED:
648 case PA_CONTEXT_TERMINATED:
649 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"context is stuffed, %1")
650 .arg(pa_strerror(pa_context_errno(
m_pcontext))));
656 switch (sstate = pa_stream_get_state(
m_pstream))
658 case PA_STREAM_READY:
661 case PA_STREAM_FAILED:
662 case PA_STREAM_TERMINATED:
663 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"stream failed or was terminated, "
664 "context state %1, stream state %2")
665 .arg(cstate).arg(sstate));
677 const pa_buffer_attr *buf_attr = pa_stream_get_buffer_attr(
m_pstream);
681 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"fragment size %1, soundcard buffer size %2")
684 return (connected && !failed);
689 QString fn_log_tag = QString(
"FlushStream (%1), ").arg(caller);
691 pa_operation *op = pa_stream_flush(
m_pstream,
nullptr,
this);
694 pa_operation_unref(op);
696 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag +
"stream flush operation failed ");
701 auto *mloop = (pa_threaded_mainloop *)arg;
702 pa_threaded_mainloop_signal(mloop, 0);
708 switch (pa_context_get_state(c))
710 case PA_CONTEXT_READY:
711 case PA_CONTEXT_TERMINATED:
712 case PA_CONTEXT_FAILED:
713 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
715 case PA_CONTEXT_CONNECTING:
716 case PA_CONTEXT_UNCONNECTED:
717 case PA_CONTEXT_AUTHORIZING:
718 case PA_CONTEXT_SETTING_NAME:
726 switch (pa_stream_get_state(s))
728 case PA_STREAM_READY:
729 case PA_STREAM_TERMINATED:
730 case PA_STREAM_FAILED:
731 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
733 case PA_STREAM_UNCONNECTED:
734 case PA_STREAM_CREATING:
742 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
747 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"stream buffer %1 flow").arg((
char*)tag));
751 pa_context *c,
int ok,
void *arg)
753 QString fn_log_tag =
"OpCompletionCallback, ";
757 LOG(VB_GENERAL, LOG_ERR,
LOC + fn_log_tag + QString(
"bummer, an operation failed: %1")
758 .arg(pa_strerror(pa_context_errno(c))));
760 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
764 pa_context *,
const pa_server_info *inf,
void *)
766 QString fn_log_tag =
"ServerInfoCallback, ";
768 LOG(VB_AUDIO, LOG_INFO,
LOC + fn_log_tag +
769 QString(
"PulseAudio server info - host name: %1, server version: "
770 "%2, server name: %3, default sink: %4")
771 .arg(inf->host_name, inf->server_version,
772 inf->server_name, inf->default_sink_name));
776 pa_context *,
const pa_sink_info *
info,
int ,
void *arg)
782 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
786 audoutP->m_aoSettings->AddSupportedRate(
info->sample_spec.rate);
788 for (
uint i = 2; i <=
info->sample_spec.channels; i++)
789 audoutP->m_aoSettings->AddSupportedChannels(i);
791 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)