diff --git a/src/media/audio/pulseaudio/audiostream.cpp b/src/media/audio/pulseaudio/audiostream.cpp index 4db4676b1519b2df513bbaa0067ed825e219f4ab..8b311ab5679acace9f19d02ceae7f60dfa724b6d 100644 --- a/src/media/audio/pulseaudio/audiostream.cpp +++ b/src/media/audio/pulseaudio/audiostream.cpp @@ -31,11 +31,12 @@ namespace ring { AudioStream::AudioStream(pa_context *c, pa_threaded_mainloop *m, const char *desc, - int type, + StreamType type, unsigned samplrate, const PaDeviceInfos* infos, - bool ec) - : audiostream_(0), mainloop_(m) + bool ec, + OnReady onReady) + : audiostream_(0), mainloop_(m), onReady_(std::move(onReady)) { const pa_channel_map channel_map = infos->channel_map; @@ -66,17 +67,24 @@ AudioStream::AudioStream(pa_context *c, attributes.fragsize = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec); attributes.minreq = (uint32_t) -1; + pa_stream_set_state_callback(audiostream_, [](pa_stream* s, void* user_data){ + static_cast<AudioStream*>(user_data)->stateChanged(s); + }, this); + pa_stream_set_moved_callback(audiostream_, [](pa_stream* s, void* user_data){ + static_cast<AudioStream*>(user_data)->moved(s); + }, this); + { PulseMainLoopLock lock(mainloop_); - const pa_stream_flags_t flags = static_cast<pa_stream_flags_t>(PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE); + const pa_stream_flags_t flags = static_cast<pa_stream_flags_t>(PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_START_CORKED); - if (type == PLAYBACK_STREAM || type == RINGTONE_STREAM) { + if (type == StreamType::Playback || type == StreamType::Ringtone) { pa_stream_connect_playback(audiostream_, infos->name.empty() ? nullptr : infos->name.c_str(), &attributes, flags, nullptr, nullptr); - } else if (type == CAPTURE_STREAM) { + } else if (type == StreamType::Capture) { pa_stream_connect_record(audiostream_, infos->name.empty() ? nullptr : infos->name.c_str(), &attributes, @@ -84,12 +92,6 @@ AudioStream::AudioStream(pa_context *c, } } - pa_stream_set_state_callback(audiostream_, [](pa_stream* s, void* user_data){ - static_cast<AudioStream*>(user_data)->stateChanged(s); - }, this); - pa_stream_set_moved_callback(audiostream_, [](pa_stream* s, void* user_data){ - static_cast<AudioStream*>(user_data)->moved(s); - }, this); } AudioStream::~AudioStream() @@ -99,16 +101,23 @@ AudioStream::~AudioStream() pa_stream_disconnect(audiostream_); // make sure we don't get any further callback - pa_stream_set_state_callback(audiostream_, NULL, NULL); - pa_stream_set_write_callback(audiostream_, NULL, NULL); - pa_stream_set_read_callback(audiostream_, NULL, NULL); - pa_stream_set_moved_callback(audiostream_, NULL, NULL); - pa_stream_set_underflow_callback(audiostream_, NULL, NULL); - pa_stream_set_overflow_callback(audiostream_, NULL, NULL); + pa_stream_set_state_callback(audiostream_, nullptr, nullptr); + pa_stream_set_write_callback(audiostream_, nullptr, nullptr); + pa_stream_set_read_callback(audiostream_, nullptr, nullptr); + pa_stream_set_moved_callback(audiostream_, nullptr, nullptr); + pa_stream_set_underflow_callback(audiostream_, nullptr, nullptr); + pa_stream_set_overflow_callback(audiostream_, nullptr, nullptr); + pa_stream_set_suspended_callback(audiostream_, nullptr, nullptr); + pa_stream_set_started_callback(audiostream_, nullptr, nullptr); pa_stream_unref(audiostream_); } +void +AudioStream::start() { + pa_stream_cork(audiostream_, 0, nullptr, nullptr); +} + void AudioStream::moved(pa_stream* s) { audiostream_ = s; @@ -137,6 +146,7 @@ AudioStream::stateChanged(pa_stream* s) //RING_DBG("minreq %u", pa_stream_get_buffer_attr(s)->minreq); //RING_DBG("fragsize %u", pa_stream_get_buffer_attr(s)->fragsize); //RING_DBG("samplespec %s", pa_sample_spec_snprint(str, sizeof(str), pa_stream_get_sample_spec(s))); + onReady_(); break; case PA_STREAM_UNCONNECTED: diff --git a/src/media/audio/pulseaudio/audiostream.h b/src/media/audio/pulseaudio/audiostream.h index a27c8b022b00c69b42dbb0ca6982c1cf683939dc..fb6fb421755cd5ec1eb87f9bef3d7774c58c76a5 100644 --- a/src/media/audio/pulseaudio/audiostream.h +++ b/src/media/audio/pulseaudio/audiostream.h @@ -18,8 +18,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef _AUDIO_STREAM_H -#define _AUDIO_STREAM_H +#pragma once #include "noncopyable.h" #include "pulselayer.h" @@ -32,85 +31,86 @@ namespace ring { /** * This data structure contains the different king of audio streams available */ -enum STREAM_TYPE { - PLAYBACK_STREAM, CAPTURE_STREAM, RINGTONE_STREAM -}; +enum class StreamType { Playback, Capture, Ringtone }; class AudioStream { - public: - - /** - * Constructor - * - * @param context pulseaudio's application context. - * @param mainloop pulseaudio's main loop - * @param description - * @param types - * @param audio sampling rate - * @param pointer to pa_source_info or pa_sink_info (depending on type). - * @param true if echo cancelling should be used with this stream - */ - AudioStream(pa_context *, pa_threaded_mainloop *, const char *, int, unsigned, const PaDeviceInfos*, bool); - - ~AudioStream(); - - /** - * Accessor: Get the pulseaudio stream object - * @return pa_stream* The stream - */ - pa_stream* stream() { - return audiostream_; - } - - const pa_sample_spec * sampleSpec() const { - return pa_stream_get_sample_spec(audiostream_); - } - - inline size_t sampleSize() const { - return pa_sample_size(sampleSpec()); - } - inline size_t frameSize() const { - return pa_frame_size(sampleSpec()); - } - - inline uint8_t channels() const { - return sampleSpec()->channels; - } - - inline AudioFormat format() const { - auto s = sampleSpec(); - return AudioFormat(s->rate, s->channels); - } - - inline std::string getDeviceName() const { - auto res = pa_stream_get_device_name(audiostream_); - if (res == reinterpret_cast<decltype(res)>(-PA_ERR_NOTSUPPORTED) or !res) - return {}; - return res; - } - - bool isReady(); - - private: - NON_COPYABLE(AudioStream); - - /** - * Mandatory asynchronous callback on the audio stream state - */ - void stateChanged(pa_stream* s); - void moved(pa_stream* s); - - /** - * The pulse audio object - */ - pa_stream* audiostream_; - - /** - * A pointer to the opaque threaded main loop object - */ - pa_threaded_mainloop * mainloop_; +public: + using OnReady = std::function<void()>; + + /** + * Constructor + * + * @param context pulseaudio's application context. + * @param mainloop pulseaudio's main loop + * @param description + * @param types + * @param audio sampling rate + * @param pointer to pa_source_info or pa_sink_info (depending on type). + * @param true if echo cancelling should be used with this stream + */ + AudioStream(pa_context *, pa_threaded_mainloop *, const char *, StreamType, unsigned, const PaDeviceInfos*, bool, OnReady onReady); + + ~AudioStream(); + + void start(); + + /** + * Accessor: Get the pulseaudio stream object + * @return pa_stream* The stream + */ + pa_stream* stream() { + return audiostream_; + } + + const pa_sample_spec * sampleSpec() const { + return pa_stream_get_sample_spec(audiostream_); + } + + inline size_t sampleSize() const { + return pa_sample_size(sampleSpec()); + } + inline size_t frameSize() const { + return pa_frame_size(sampleSpec()); + } + + inline uint8_t channels() const { + return sampleSpec()->channels; + } + + inline AudioFormat format() const { + auto s = sampleSpec(); + return AudioFormat(s->rate, s->channels); + } + + inline std::string getDeviceName() const { + auto res = pa_stream_get_device_name(audiostream_); + if (res == reinterpret_cast<decltype(res)>(-PA_ERR_NOTSUPPORTED) or !res) + return {}; + return res; + } + + bool isReady(); + +private: + NON_COPYABLE(AudioStream); + + OnReady onReady_; + + /** + * Mandatory asynchronous callback on the audio stream state + */ + void stateChanged(pa_stream* s); + void moved(pa_stream* s); + + /** + * The pulse audio object + */ + pa_stream* audiostream_; + + /** + * A pointer to the opaque threaded main loop object + */ + pa_threaded_mainloop * mainloop_; }; } - -#endif // _AUDIO_STREAM_H diff --git a/src/media/audio/pulseaudio/pulselayer.cpp b/src/media/audio/pulseaudio/pulselayer.cpp index 40aa7324562b5b707be8b716dae27fe3ec3aa366..7f7efe2d0624b8fae40b91e42417f584a0b646cc 100644 --- a/src/media/audio/pulseaudio/pulselayer.cpp +++ b/src/media/audio/pulseaudio/pulselayer.cpp @@ -323,9 +323,21 @@ void PulseLayer::createStreams(pa_context* c) { hardwareFormatAvailable(defaultAudioFormat_); + auto onReady = [this] { + bool playbackReady = not playback_ or playback_->isReady(); + bool ringtoneReady = not ringtone_ or ringtone_->isReady(); + bool recordReady = not record_ or record_->isReady(); + if (playbackReady and recordReady and ringtoneReady) { + RING_DBG("All streams ready, starting !"); + if (playback_) playback_->start(); + if (ringtone_) ringtone_->start(); + if (record_) record_->start(); + } + }; + // Create playback stream if (auto dev_infos = getDeviceInfos(sinkList_, getPreferredPlaybackDevice())) { - playback_.reset(new AudioStream(c, mainloop_.get(), "Playback", PLAYBACK_STREAM, audioFormat_.sample_rate, dev_infos, true)); + playback_.reset(new AudioStream(c, mainloop_.get(), "Playback", StreamType::Playback, audioFormat_.sample_rate, dev_infos, true, onReady)); pa_stream_set_write_callback(playback_->stream(), [](pa_stream * /*s*/, size_t /*bytes*/, void* userdata) { static_cast<PulseLayer*>(userdata)->writeToSpeaker(); }, this); @@ -334,7 +346,7 @@ void PulseLayer::createStreams(pa_context* c) // Create ringtone stream // Echo canceling is not enabled for ringtone, because PA can only cancel a single output source with an input source if (auto dev_infos = getDeviceInfos(sinkList_, getPreferredRingtoneDevice())) { - ringtone_.reset(new AudioStream(c, mainloop_.get(), "Ringtone", RINGTONE_STREAM, audioFormat_.sample_rate, dev_infos, false)); + ringtone_.reset(new AudioStream(c, mainloop_.get(), "Ringtone", StreamType::Ringtone, audioFormat_.sample_rate, dev_infos, false, onReady)); pa_stream_set_write_callback(ringtone_->stream(), [](pa_stream * /*s*/, size_t /*bytes*/, void* userdata) { static_cast<PulseLayer*>(userdata)->ringtoneToSpeaker(); }, this); @@ -342,7 +354,7 @@ void PulseLayer::createStreams(pa_context* c) // Create capture stream if (auto dev_infos = getDeviceInfos(sourceList_, getPreferredCaptureDevice())) { - record_.reset(new AudioStream(c, mainloop_.get(), "Capture", CAPTURE_STREAM, audioFormat_.sample_rate, dev_infos, true)); + record_.reset(new AudioStream(c, mainloop_.get(), "Capture", StreamType::Capture, audioFormat_.sample_rate, dev_infos, true, onReady)); pa_stream_set_read_callback(record_->stream() , [](pa_stream * /*s*/, size_t /*bytes*/, void* userdata) { static_cast<PulseLayer*>(userdata)->readFromMic(); }, this); @@ -483,10 +495,8 @@ void PulseLayer::ringtoneToSpeaker() } -std::string stripEchoSufix(std::string deviceName) { - +std::string stripEchoSufix(const std::string& deviceName) { return std::regex_replace(deviceName, PA_EC_SUFFIX, ""); - } void @@ -560,8 +570,15 @@ void PulseLayer::waitForDeviceList() return; // If a current device changed, restart streams - if (!playback_ || stripEchoSufix(playback_->getDeviceName()) != getDeviceInfos(sinkList_, getPreferredPlaybackDevice())->name - || !record_ || stripEchoSufix(record_->getDeviceName()) != getDeviceInfos(sourceList_, getPreferredCaptureDevice())->name) { + auto playbackInfo = getDeviceInfos(sinkList_, getPreferredPlaybackDevice()); + bool playbackDeviceChanged = !playback_ or (!playbackInfo->name.empty() and + playbackInfo->name != stripEchoSufix(playback_->getDeviceName())); + + auto recordInfo = getDeviceInfos(sourceList_, getPreferredCaptureDevice()); + bool recordDeviceChanged = !record_ or (!recordInfo->name.empty() and + recordInfo->name != stripEchoSufix(record_->getDeviceName())); + + if (playbackDeviceChanged or recordDeviceChanged) { RING_WARN("Audio devices changed, restarting streams."); stopStream(); startStream();