Commit 84ec8623 authored by Rafaël Carré's avatar Rafaël Carré

* #6629

simplify alsalayer xrun
split audio mainloop into smaller specific functions
simplify pulse audiostream
remove unused pulse functions
parent 0a6e00b6
......@@ -89,7 +89,6 @@ AlsaLayer::AlsaLayer ()
, is_capture_running_ (false)
, is_playback_open_ (false)
, is_capture_open_ (false)
, trigger_request_ (false)
, audioThread_ (NULL)
{
}
......@@ -427,40 +426,44 @@ bool AlsaLayer::alsa_set_params (snd_pcm_t *pcm_handle)
}
//TODO first frame causes broken pipe (underrun) because not enough data are send --> make the handle wait to be ready
int
void
AlsaLayer::write (void* buffer, int length, snd_pcm_t * handle)
{
if (trigger_request_) {
trigger_request_ = false;
startPlaybackStream ();
}
snd_pcm_uframes_t frames = snd_pcm_bytes_to_frames (handle, length);
int err;
if ((err = snd_pcm_writei (handle, buffer , frames)) < 0) {
switch (err) {
case -EPIPE:
case -ESTRPIPE:
case -EIO:
handle_xrun_playback (handle);
if (snd_pcm_writei (handle, buffer , frames) < 0)
_debug ("Audio: XRUN handling failed");
trigger_request_ = true;
int err = snd_pcm_writei (handle, buffer , frames);
if (err >= 0)
return;
break;
switch (err) {
case -EPIPE:
case -ESTRPIPE:
case -EIO:
{
snd_pcm_status_t* status;
snd_pcm_status_alloca (&status);
err = snd_pcm_status (handle, status);
if (err < 0)
_error("ALSA: Cannot get playback handle status (%s)" , snd_strerror (err));
else if (snd_pcm_status_get_state (status) == SND_PCM_STATE_XRUN) {
stopPlaybackStream();
preparePlaybackStream();
startPlaybackStream();
}
default:
_debug ("Audio: Write error unknown - dropping frames: %s", snd_strerror (err));
stopPlaybackStream ();
break;
}
}
err = snd_pcm_writei (handle, buffer , frames);
if (err < 0)
_error("ALSA: XRUN handling failed : %s", snd_strerror(err));
break;
}
return (err > 0) ? err : 0 ;
default:
_error("ALSA: unknown write error, dropping frames: %s", snd_strerror (err));
stopPlaybackStream ();
break;
}
}
int
......@@ -473,68 +476,43 @@ AlsaLayer::read (void* buffer, int toCopy)
snd_pcm_uframes_t frames = snd_pcm_bytes_to_frames (captureHandle_, toCopy);
int err;
if ((err = snd_pcm_readi (captureHandle_, buffer, frames)) < 0) {
switch (err) {
case -EPIPE:
case -ESTRPIPE:
case -EIO:
_debug ("Audio: XRUN capture ignored (%s)", snd_strerror (err));
handle_xrun_capture();
break;
case EPERM:
_debug ("Audio: Capture EPERM (%s)", snd_strerror (err));
prepareCaptureStream ();
startCaptureStream ();
break;
default:
break;
}
return 0;
}
return toCopy;
}
void
AlsaLayer::handle_xrun_capture (void)
{
snd_pcm_status_t* status;
snd_pcm_status_alloca (&status);
if (snd_pcm_status(captureHandle_, status) <= 0) {
if (snd_pcm_status_get_state (status) == SND_PCM_STATE_XRUN) {
stopCaptureStream ();
prepareCaptureStream ();
startCaptureStream ();
}
} else
_debug ("Audio: Get status failed");
}
int err = snd_pcm_readi (captureHandle_, buffer, frames);
if (err >= 0)
return snd_pcm_frames_to_bytes(captureHandle_, frames);
switch (err) {
case -EPIPE:
case -ESTRPIPE:
case -EIO:
{
snd_pcm_status_t* status;
snd_pcm_status_alloca (&status);
err = snd_pcm_status(captureHandle_, status);
if (err < 0)
_error("ALSA: Get status failed: %s", snd_strerror(err));
else if (snd_pcm_status_get_state (status) == SND_PCM_STATE_XRUN) {
stopCaptureStream ();
prepareCaptureStream ();
startCaptureStream ();
}
void
AlsaLayer::handle_xrun_playback (snd_pcm_t *handle)
{
snd_pcm_status_t* status;
snd_pcm_status_alloca (&status);
_error("ALSA: XRUN capture ignored (%s)", snd_strerror (err));
break;
}
int state;
if ((state = snd_pcm_status (handle, status)) < 0)
_debug ("Audio: Error: Cannot get playback handle status (%s)" , snd_strerror (state));
else {
int state = snd_pcm_status_get_state (status);
case EPERM:
_error("ALSA: Can't capture, EPERM (%s)", snd_strerror (err));
prepareCaptureStream ();
startCaptureStream ();
break;
if (state == SND_PCM_STATE_XRUN) {
stopPlaybackStream ();
preparePlaybackStream ();
default:
break;
}
trigger_request_ = true;
}
}
return 0;
}
std::string
......@@ -645,33 +623,122 @@ namespace
}
}
void AlsaLayer::audioCallback (void)
void AlsaLayer::capture(void)
{
unsigned int mainBufferSampleRate = getMainBuffer()->getInternalSamplingRate();
bool resample = audioSampleRate_ != mainBufferSampleRate;
notifyincomingCall();
int toGetSamples = snd_pcm_avail_update (captureHandle_);
if (toGetSamples < 0)
_error ("Audio: Mic error: %s", snd_strerror (toGetSamples));
if (toGetSamples <= 0)
return;
const int framesPerBufferAlsa = 2048;
if (toGetSamples > framesPerBufferAlsa)
toGetSamples = framesPerBufferAlsa;
int toGetBytes = toGetSamples * sizeof(SFLDataFormat);
SFLDataFormat* in = (SFLDataFormat*) malloc (toGetBytes);
if (read(in, toGetBytes) != toGetBytes) {
_error("ALSA MIC : Couldn't read!");
goto end;
}
adjustVolume (in, toGetSamples, Manager::instance().getSpkrVolume());
if (resample) {
int outSamples = toGetSamples * ((double) audioSampleRate_ / mainBufferSampleRate);
int outBytes = outSamples * sizeof (SFLDataFormat);
SFLDataFormat* rsmpl_out = (SFLDataFormat*) malloc (outBytes);
converter_->resample ( (SFLDataFormat*) in, rsmpl_out, mainBufferSampleRate, audioSampleRate_, toGetSamples);
dcblocker_.process(rsmpl_out, outBytes);
getMainBuffer()->putData (rsmpl_out, outBytes);
free (rsmpl_out);
} else {
SFLDataFormat* filter_out = (SFLDataFormat*) malloc (toGetBytes);
dcblocker_.process(in, filter_out, toGetBytes);
getMainBuffer()->putData (filter_out, toGetBytes);
free (filter_out);
}
end:
free (in);
}
void AlsaLayer::playback(int maxSamples)
{
unsigned short spkrVolume = Manager::instance().getSpkrVolume();
AudioLoop *tone = Manager::instance().getTelephoneTone();
AudioLoop *file_tone = Manager::instance().getTelephoneFile();
unsigned int mainBufferSampleRate = getMainBuffer()->getInternalSamplingRate();
bool resample = audioSampleRate_ != mainBufferSampleRate;
int toGet = getMainBuffer()->availForGet();
int toPut = maxSamples * sizeof(SFLDataFormat);
if (toGet <= 0) { // no audio available, play tone or silence
AudioLoop *tone = Manager::instance().getTelephoneTone();
AudioLoop *file_tone = Manager::instance().getTelephoneFile();
// AvailForGet tell the number of chars inside the buffer
// framePerBuffer are the number of data for one channel (left)
int urgentAvailBytes = urgentRingBuffer_.AvailForGet();
SFLDataFormat *out = (SFLDataFormat *) malloc (toPut);
if (tone)
tone->getNext (out, maxSamples, spkrVolume);
else if (file_tone && !ringtoneHandle_)
file_tone->getNext (out, maxSamples, spkrVolume);
else
memset(out, 0, toPut);
write (out, toPut, playbackHandle_);
free (out);
return;
}
// play the regular sound samples
int maxNbBytesToGet = toPut;
// Compute maximal value to get from the ring buffer
double resampleFactor = 1.0;
if (resample) {
resampleFactor = (double) audioSampleRate_ / mainBufferSampleRate;
maxNbBytesToGet = (double) toGet / resampleFactor;
}
if (toGet > maxNbBytesToGet)
toGet = maxNbBytesToGet;
SFLDataFormat *out = (SFLDataFormat*) malloc (toGet);
getMainBuffer()->getData (out, toGet);
adjustVolume(out, toGet / sizeof(SFLDataFormat), spkrVolume);
if (resample) {
int inSamples = toGet / sizeof(SFLDataFormat);
int outSamples = inSamples * resampleFactor;
SFLDataFormat *rsmpl_out = (SFLDataFormat*) malloc (outSamples * sizeof(SFLDataFormat));
converter_->resample (out, rsmpl_out, mainBufferSampleRate, audioSampleRate_, inSamples);
write (rsmpl_out, outSamples * sizeof(SFLDataFormat), playbackHandle_);
free (rsmpl_out);
} else {
write (out, toGet, playbackHandle_);
}
free (out);
}
void AlsaLayer::audioCallback (void)
{
if (!playbackHandle_ or !captureHandle_)
return;
notifyincomingCall();
unsigned short spkrVolume = Manager::instance().getSpkrVolume();
snd_pcm_wait (playbackHandle_, 20);
int playbackAvailSmpl = snd_pcm_avail_update (playbackHandle_);
int playbackAvailBytes = playbackAvailSmpl * sizeof (SFLDataFormat);
if (urgentAvailBytes > 0) {
int toGet = urgentRingBuffer_.AvailForGet();
if (toGet > 0) {
// Urgent data (dtmf, incoming call signal) come first.
int toGet = urgentAvailBytes;
if (toGet > playbackAvailBytes)
toGet = playbackAvailBytes;
SFLDataFormat *out = (SFLDataFormat*) malloc (toGet);
......@@ -684,109 +751,26 @@ void AlsaLayer::audioCallback (void)
getMainBuffer()->discard (toGet);
} else {
// regular audio data
int toGet = getMainBuffer()->availForGet();
if (toGet <= 0) {
// no audio available, play tone or silence
SFLDataFormat *out = (SFLDataFormat *) malloc (playbackAvailBytes);
if (tone)
tone->getNext (out, playbackAvailSmpl, spkrVolume);
else if (file_tone && !ringtoneHandle_)
file_tone->getNext (out, playbackAvailSmpl, spkrVolume);
else
memset(out, 0, playbackAvailBytes);
write (out, playbackAvailBytes, playbackHandle_);
free (out);
} else {
// play the regular sound samples
int maxNbBytesToGet = playbackAvailBytes;
// Compute maximal value to get from the ring buffer
double resampleFactor = 1.0;
if (resample) {
resampleFactor = (double) audioSampleRate_ / mainBufferSampleRate;
maxNbBytesToGet = (double) toGet / resampleFactor;
}
if (toGet > maxNbBytesToGet)
toGet = maxNbBytesToGet;
SFLDataFormat *out = (SFLDataFormat*) malloc (toGet);
getMainBuffer()->getData (out, toGet);
adjustVolume(out, toGet / sizeof(SFLDataFormat), spkrVolume);
if (resample) {
int inSamples = toGet / sizeof(SFLDataFormat);
int outSamples = inSamples * resampleFactor;
SFLDataFormat *rsmpl_out = (SFLDataFormat*) malloc (outSamples * sizeof(SFLDataFormat));
converter_->resample (out, rsmpl_out, mainBufferSampleRate, audioSampleRate_, inSamples);
write (rsmpl_out, outSamples * sizeof(SFLDataFormat), playbackHandle_);
free (rsmpl_out);
} else {
write (out, toGet, playbackHandle_);
}
free (out);
}
playback(playbackAvailSmpl);
}
if (file_tone and ringtoneHandle_) {
int ringtoneAvailSmpl = snd_pcm_avail_update (ringtoneHandle_);
int ringtoneAvailBytes = ringtoneAvailSmpl*sizeof (SFLDataFormat);
SFLDataFormat *out = (SFLDataFormat *) malloc (ringtoneAvailBytes);
file_tone->getNext (out, ringtoneAvailSmpl, spkrVolume);
write (out, ringtoneAvailBytes, ringtoneHandle_);
free (out);
} else if (ringtoneHandle_) {
int ringtoneAvailSmpl = snd_pcm_avail_update (ringtoneHandle_);
int ringtoneAvailBytes = ringtoneAvailSmpl*sizeof (SFLDataFormat);
if (ringtoneHandle_) {
AudioLoop *file_tone = Manager::instance().getTelephoneFile();
int ringtoneAvailSmpl = snd_pcm_avail_update (ringtoneHandle_);
int ringtoneAvailBytes = ringtoneAvailSmpl*sizeof (SFLDataFormat);
SFLDataFormat *out = (SFLDataFormat *) malloc (ringtoneAvailBytes);
memset (out, 0, ringtoneAvailBytes);
write (out, ringtoneAvailBytes, ringtoneHandle_);
free (out);
}
SFLDataFormat *out = (SFLDataFormat *) malloc (ringtoneAvailBytes);
// Additionally handle the mic's audio stream
if (!is_capture_running_)
return;
if (file_tone)
file_tone->getNext (out, ringtoneAvailSmpl, spkrVolume);
else
memset (out, 0, ringtoneAvailBytes);
int toPutSamples = snd_pcm_avail_update (captureHandle_);
if (toPutSamples <= 0) {
if (toPutSamples < 0)
_error ("Audio: Mic error: %s", snd_strerror (toPutSamples));
return;
}
const int framesPerBufferAlsa = 2048;
if (toPutSamples > framesPerBufferAlsa)
toPutSamples = framesPerBufferAlsa;
int toPutBytes = toPutSamples * sizeof(SFLDataFormat);
SFLDataFormat* in = (SFLDataFormat*) malloc (toPutBytes);
int bytes = read (in, toPutBytes);
if (toPutBytes != bytes) {
_error("ALSA MIC : Couldn't read!");
free(in);
return;
write(out, ringtoneAvailBytes, ringtoneHandle_);
free(out);
}
adjustVolume (in, toPutSamples, spkrVolume);
if (resample) {
int outSamples = toPutSamples * ((double) audioSampleRate_ / mainBufferSampleRate);
int outBytes = outSamples * sizeof (SFLDataFormat);
SFLDataFormat* rsmpl_out = (SFLDataFormat*) malloc (outBytes);
converter_->resample ( (SFLDataFormat*) in, rsmpl_out, mainBufferSampleRate, audioSampleRate_, toPutSamples);
dcblocker_.process(rsmpl_out, outBytes);
getMainBuffer()->putData (rsmpl_out, outBytes);
free (rsmpl_out);
} else {
SFLDataFormat* filter_out = (SFLDataFormat*) malloc (toPutBytes);
dcblocker_.process(in, filter_out, toPutBytes);
getMainBuffer()->putData (filter_out, toPutBytes);
free (filter_out);
}
free (in);
// Additionally handle the mic's audio stream
if (is_capture_running_)
capture();
}
......@@ -124,6 +124,9 @@ class AlsaLayer : public AudioLayer
return audioPlugin_;
}
void playback(int maxSamples);
void capture(void);
void audioCallback (void);
/**
......@@ -204,9 +207,8 @@ class AlsaLayer : public AudioLayer
* ALSA Library API
* @param buffer The data to be copied
* @param length The size of the buffer
* @return int The number of frames actually copied
*/
int write (void* buffer, int length, snd_pcm_t *handle);
void write (void* buffer, int length, snd_pcm_t *handle);
/**
* Read data from the internal ring buffer
......@@ -217,18 +219,6 @@ class AlsaLayer : public AudioLayer
*/
int read (void* buffer, int toCopy);
/**
* Recover from XRUN state for capture
* ALSA Library API
*/
void handle_xrun_capture (void);
/**
* Recover from XRUN state for playback
* ALSA Library API
*/
void handle_xrun_playback (snd_pcm_t *handle);
/**
* Handles to manipulate playback stream
* ALSA Library API
......@@ -266,7 +256,6 @@ class AlsaLayer : public AudioLayer
bool is_capture_running_;
bool is_playback_open_;
bool is_capture_open_;
bool trigger_request_;
AlsaThread* audioThread_;
};
......
......@@ -31,247 +31,104 @@
#include <audiostream.h>
#include "pulselayer.h"
static pa_channel_map channel_map;
AudioStream::AudioStream (pa_context *c, pa_threaded_mainloop *m, std::string desc, int type, int smplrate)
: _audiostream (NULL),
_context (c),
_streamType (type),
_streamDescription (desc),
_volume(),
_flag (PA_STREAM_AUTO_TIMING_UPDATE),
_sample_spec(),
_mainloop (m)
{
_sample_spec.format = PA_SAMPLE_S16LE; // PA_SAMPLE_FLOAT32LE;
_sample_spec.rate = smplrate;
_sample_spec.channels = 1;
channel_map.channels = 1;
pa_cvolume_set (&_volume , 1 , PA_VOLUME_NORM) ; // * vol / 100 ;
}
AudioStream::~AudioStream()
{
disconnectStream();
}
bool
AudioStream::connectStream (std::string* deviceName)
{
ost::MutexLock guard (_mutex);
if (!_audiostream)
_audiostream = createStream (_context, deviceName);
return true;
}
static void success_cb (pa_stream *s, int success UNUSED, void *userdata)
AudioStream::AudioStream (pa_context *c, pa_threaded_mainloop *m, const char *desc, int type, int smplrate, std::string *deviceName)
: _mainloop (m)
{
static const pa_channel_map channel_map = {
1,
{ PA_CHANNEL_POSITION_MONO },
};
pa_sample_spec sample_spec = {
PA_SAMPLE_S16LE, // PA_SAMPLE_FLOAT32LE,
smplrate,
1
};
assert (pa_sample_spec_valid (&sample_spec));
assert (pa_channel_map_valid (&channel_map));
assert (s);
pa_threaded_mainloop * mainloop = (pa_threaded_mainloop *) userdata;
_audiostream = pa_stream_new (c, desc, &sample_spec, &channel_map);
if (!_audiostream) {
_error("Pulse: %s: pa_stream_new() failed : %s" , desc, pa_strerror (pa_context_errno (c)));
throw std::runtime_error("Pulse : could not create stream\n");
}
pa_threaded_mainloop_signal (mainloop, 0);
}
pa_buffer_attr attributes;
attributes.maxlength = pa_usec_to_bytes (160 * PA_USEC_PER_MSEC, &sample_spec);
attributes.tlength = pa_usec_to_bytes (80 * PA_USEC_PER_MSEC, &sample_spec);
attributes.prebuf = 0;
attributes.fragsize = pa_usec_to_bytes (80 * PA_USEC_PER_MSEC, &sample_spec);
attributes.minreq = (uint32_t) -1;
bool
AudioStream::drainStream (void)
{
if (_audiostream) {
_info ("Audio: Draining stream");
pa_operation * operation;
const char *name = deviceName ? deviceName->c_str() : NULL;
pa_threaded_mainloop_lock (_mainloop);
pa_threaded_mainloop_lock (_mainloop);
if ( (operation = pa_stream_drain (_audiostream, success_cb, _mainloop))) {
while (pa_operation_get_state (operation) != PA_OPERATION_DONE) {
if (!_context || pa_context_get_state (_context) != PA_CONTEXT_READY || !_audiostream || pa_stream_get_state (_audiostream) != PA_STREAM_READY) {
_warn ("Audio: Connection died: %s", _context ? pa_strerror (pa_context_errno (_context)) : "NULL");
pa_operation_unref (operation);
break;
} else {
pa_threaded_mainloop_wait (_mainloop);
}
}
}
if (type == PLAYBACK_STREAM || type == RINGTONE_STREAM)
pa_stream_connect_playback (_audiostream , name, &attributes, (pa_stream_flags_t) (PA_STREAM_ADJUST_LATENCY|PA_STREAM_AUTO_TIMING_UPDATE), NULL, NULL);
else if (type == CAPTURE_STREAM)
pa_stream_connect_record (_audiostream, name, &attributes, (pa_stream_flags_t) (PA_STREAM_ADJUST_LATENCY|PA_STREAM_AUTO_TIMING_UPDATE));
pa_threaded_mainloop_unlock (_mainloop);
}
pa_threaded_mainloop_unlock (_mainloop);
return true;
pa_stream_set_state_callback (_audiostream , stream_state_callback, NULL);
}
bool
AudioStream::disconnectStream (void)
AudioStream::~AudioStream()
{
_info ("Audio: Destroy audio streams");
pa_threaded_mainloop_lock (_mainloop);
if (_audiostream) {
pa_stream_disconnect (_audiostream);