Commit c6bee418 authored by Adrien Béraud's avatar Adrien Béraud Committed by Sébastien Blin

pulseaudio: start steams when ready

Follow API documentation recommendations: create streams corked,
start them when ready.

Change-Id: I9826f1f77bbe75351d863c29558db412e5dcbda0
Reviewed-by: Sébastien Blin's avatarSébastien Blin <sebastien.blin@savoirfairelinux.com>
parent 1d71c178
......@@ -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:
......
......@@ -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
......@@ -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();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment