improved alsa error handling

This commit is contained in:
badaix 2015-04-07 17:21:09 +00:00
parent a9d1ac387b
commit 74cdd5ea64
3 changed files with 82 additions and 41 deletions

View file

@ -25,15 +25,15 @@
using namespace std; using namespace std;
Player::Player(const PcmDevice& pcmDevice, Stream* stream) : pcm_handle_(NULL), buff_(NULL), active_(false), stream_(stream), pcmDevice_(pcmDevice) Player::Player(const PcmDevice& pcmDevice, Stream* stream) : buff_(NULL), active_(false), stream_(stream), pcmDevice_(pcmDevice)
{ {
} }
void Player::start() snd_pcm_t* Player::initAlsa()
{ {
unsigned int pcm, tmp, rate; unsigned int tmp, rate;
int channels; int pcm, channels;
snd_pcm_hw_params_t *params; snd_pcm_hw_params_t *params;
int buff_size; int buff_size;
@ -41,10 +41,14 @@ void Player::start()
rate = format.rate; rate = format.rate;
channels = format.channels; channels = format.channels;
/* Open the PCM device in playback mode */ snd_pcm_t* pcm_handle(NULL);
if ((pcm = snd_pcm_open(&pcm_handle_, pcmDevice_.name.c_str(), SND_PCM_STREAM_PLAYBACK, 0)) < 0)
logE << "ERROR: Can't open " << pcmDevice_.name << " PCM device. " << snd_strerror(pcm) << "\n";
/* Open the PCM device in playback mode */
if ((pcm = snd_pcm_open(&pcm_handle, pcmDevice_.name.c_str(), SND_PCM_STREAM_PLAYBACK, 0)) < 0)
{
logE << "ERROR: Can't open " << pcmDevice_.name << " PCM device. " << snd_strerror(pcm) << "\n";
return NULL;
}
/* struct snd_pcm_playback_info_t pinfo; /* struct snd_pcm_playback_info_t pinfo;
if ( (pcm = snd_pcm_playback_info( pcm_handle, &pinfo )) < 0 ) if ( (pcm = snd_pcm_playback_info( pcm_handle, &pinfo )) < 0 )
fprintf( stderr, "Error: playback info error: %s\n", snd_strerror( err ) ); fprintf( stderr, "Error: playback info error: %s\n", snd_strerror( err ) );
@ -53,20 +57,32 @@ void Player::start()
/* Allocate parameters object and fill it with default values*/ /* Allocate parameters object and fill it with default values*/
snd_pcm_hw_params_alloca(&params); snd_pcm_hw_params_alloca(&params);
snd_pcm_hw_params_any(pcm_handle_, params); snd_pcm_hw_params_any(pcm_handle, params);
/* Set parameters */ /* Set parameters */
if ((pcm = snd_pcm_hw_params_set_access(pcm_handle_, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) if ((pcm = snd_pcm_hw_params_set_access(pcm_handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
{
logE << "ERROR: Can't set interleaved mode. " << snd_strerror(pcm) << "\n"; logE << "ERROR: Can't set interleaved mode. " << snd_strerror(pcm) << "\n";
return NULL;
}
if ((pcm = snd_pcm_hw_params_set_format(pcm_handle_, params, SND_PCM_FORMAT_S16_LE)) < 0) if ((pcm = snd_pcm_hw_params_set_format(pcm_handle, params, SND_PCM_FORMAT_S16_LE)) < 0)
{
logE << "ERROR: Can't set format. " << snd_strerror(pcm) << "\n"; logE << "ERROR: Can't set format. " << snd_strerror(pcm) << "\n";
return NULL;
}
if ((pcm = snd_pcm_hw_params_set_channels(pcm_handle_, params, channels)) < 0) if ((pcm = snd_pcm_hw_params_set_channels(pcm_handle, params, channels)) < 0)
{
logE << "ERROR: Can't set channels number. " << snd_strerror(pcm) << "\n"; logE << "ERROR: Can't set channels number. " << snd_strerror(pcm) << "\n";
return NULL;
}
if ((pcm = snd_pcm_hw_params_set_rate_near(pcm_handle_, params, &rate, 0)) < 0) if ((pcm = snd_pcm_hw_params_set_rate_near(pcm_handle, params, &rate, 0)) < 0)
{
logE << "ERROR: Can't set rate. " << snd_strerror(pcm) << "\n"; logE << "ERROR: Can't set rate. " << snd_strerror(pcm) << "\n";
return NULL;
}
unsigned int buffer_time; unsigned int buffer_time;
snd_pcm_hw_params_get_buffer_time_max(params, &buffer_time, 0); snd_pcm_hw_params_get_buffer_time_max(params, &buffer_time, 0);
@ -75,20 +91,23 @@ void Player::start()
unsigned int period_time = buffer_time / 4; unsigned int period_time = buffer_time / 4;
snd_pcm_hw_params_set_period_time_near(pcm_handle_, params, &period_time, 0); snd_pcm_hw_params_set_period_time_near(pcm_handle, params, &period_time, 0);
snd_pcm_hw_params_set_buffer_time_near(pcm_handle_, params, &buffer_time, 0); snd_pcm_hw_params_set_buffer_time_near(pcm_handle, params, &buffer_time, 0);
// long unsigned int periodsize = stream_->format.msRate() * 50;//2*rate/50; // long unsigned int periodsize = stream_->format.msRate() * 50;//2*rate/50;
// if ((pcm = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, params, &periodsize)) < 0) // if ((pcm = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, params, &periodsize)) < 0)
// logE << "Unable to set buffer size " << (long int)periodsize << ": " << snd_strerror(pcm) << "\n"; // logE << "Unable to set buffer size " << (long int)periodsize << ": " << snd_strerror(pcm) << "\n";
/* Write parameters */ /* Write parameters */
if ((pcm = snd_pcm_hw_params(pcm_handle_, params)) < 0) if ((pcm = snd_pcm_hw_params(pcm_handle, params)) < 0)
{
logE << "ERROR: Can't set harware parameters. " << snd_strerror(pcm) << "\n"; logE << "ERROR: Can't set harware parameters. " << snd_strerror(pcm) << "\n";
return NULL;
}
/* Resume information */ /* Resume information */
logD << "PCM name: " << snd_pcm_name(pcm_handle_) << "\n"; logD << "PCM name: " << snd_pcm_name(pcm_handle) << "\n";
logD << "PCM state: " << snd_pcm_state_name(snd_pcm_state(pcm_handle_)) << "\n"; logD << "PCM state: " << snd_pcm_state_name(snd_pcm_state(pcm_handle)) << "\n";
snd_pcm_hw_params_get_channels(params, &tmp); snd_pcm_hw_params_get_channels(params, &tmp);
logD << "channels: " << tmp << "\n"; logD << "channels: " << tmp << "\n";
@ -107,13 +126,34 @@ void Player::start()
snd_pcm_sw_params_t *swparams; snd_pcm_sw_params_t *swparams;
snd_pcm_sw_params_alloca(&swparams); snd_pcm_sw_params_alloca(&swparams);
snd_pcm_sw_params_current(pcm_handle_, swparams); snd_pcm_sw_params_current(pcm_handle, swparams);
snd_pcm_sw_params_set_avail_min(pcm_handle_, swparams, frames_); snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, frames_);
snd_pcm_sw_params_set_start_threshold(pcm_handle_, swparams, frames_); snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams, frames_);
// snd_pcm_sw_params_set_stop_threshold(pcm_handle, swparams, frames_); // snd_pcm_sw_params_set_stop_threshold(pcm_handle, swparams, frames_);
snd_pcm_sw_params(pcm_handle_, swparams); snd_pcm_sw_params(pcm_handle, swparams);
return pcm_handle;
}
void Player::uninitAlsa(snd_pcm_t* handle)
{
if (handle != NULL)
{
snd_pcm_drain(handle);
snd_pcm_close(handle);
}
if (buff_ != NULL)
{
free(buff_);
buff_ = NULL;
}
}
void Player::start()
{
active_ = true; active_ = true;
playerThread_ = thread(&Player::worker, this); playerThread_ = thread(&Player::worker, this);
} }
@ -125,49 +165,49 @@ Player::~Player()
} }
void Player::stop() { void Player::stop()
{
if (active_) if (active_)
{ {
active_ = false; active_ = false;
playerThread_.join(); playerThread_.join();
} }
if (pcm_handle_ != NULL)
{
snd_pcm_drain(pcm_handle_);
snd_pcm_close(pcm_handle_);
pcm_handle_ = NULL;
}
if (buff_ != NULL)
{
free(buff_);
buff_ = NULL;
}
} }
void Player::worker() void Player::worker()
{ {
unsigned int pcm; snd_pcm_t* handle = NULL;
snd_pcm_sframes_t pcm;
snd_pcm_sframes_t framesAvail; snd_pcm_sframes_t framesAvail;
snd_pcm_sframes_t framesDelay; snd_pcm_sframes_t framesDelay;
while (active_) while (active_)
{ {
snd_pcm_avail_delay(pcm_handle_, &framesAvail, &framesDelay); if (handle == NULL)
{
if ((handle = initAlsa()) == NULL)
{
usleep(100*1000);
continue;
}
}
snd_pcm_avail_delay(handle, &framesAvail, &framesDelay);
chronos::usec delay((chronos::usec::rep) (1000 * (double) framesDelay / stream_->getFormat().msRate())); chronos::usec delay((chronos::usec::rep) (1000 * (double) framesDelay / stream_->getFormat().msRate()));
logD << "Avail: " << framesAvail << ", delay: " << framesDelay << ", delay[ms]: " << delay.count() / 1000 << "\n"; logD << "Avail: " << framesAvail << ", delay: " << framesDelay << ", delay[ms]: " << delay.count() / 1000 << "\n";
if (stream_->getPlayerChunk(buff_, delay, frames_)) if (stream_->getPlayerChunk(buff_, delay, frames_))
{ {
if ((pcm = snd_pcm_writei(pcm_handle_, buff_, frames_)) == -EPIPE) if ((pcm = snd_pcm_writei(handle, buff_, frames_)) == -EPIPE)
{ {
logE << "XRUN\n"; logE << "XRUN\n";
snd_pcm_prepare(pcm_handle_); snd_pcm_prepare(handle);
} }
else if (pcm < 0) else if (pcm < 0)
{ {
logE << "ERROR. Can't write to PCM device: " << snd_strerror(pcm) << "\n"; logE << "ERROR. Can't write to PCM device: " << snd_strerror(pcm) << "\n";
uninitAlsa(handle);
handle = NULL;
} }
} }
else else
@ -176,6 +216,7 @@ void Player::worker()
usleep(100*1000); usleep(100*1000);
} }
} }
uninitAlsa(handle);
} }

View file

@ -38,8 +38,9 @@ public:
static std::vector<PcmDevice> pcm_list(void); static std::vector<PcmDevice> pcm_list(void);
private: private:
snd_pcm_t* initAlsa();
void uninitAlsa(snd_pcm_t* handle);
void worker(); void worker();
snd_pcm_t* pcm_handle_;
snd_pcm_uframes_t frames_; snd_pcm_uframes_t frames_;
char *buff_; char *buff_;
std::atomic<bool> active_; std::atomic<bool> active_;

View file

@ -243,7 +243,7 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
{ {
logO << "sleep > bufferDuration/2: " << cs::duration<cs::msec>(sleep_) << " > " << cs::duration<cs::msec>(bufferDuration)/2 << "\n"; logO << "sleep > bufferDuration/2: " << cs::duration<cs::msec>(sleep_) << " > " << cs::duration<cs::msec>(bufferDuration)/2 << "\n";
// We're late: discard oldest chunks // We're late: discard oldest chunks
do while (sleep_ > chunk_->duration<cs::usec>())
{ {
logO << "sleep > chunkDuration: " << cs::duration<cs::msec>(sleep_) << " > " << chunk_->duration<cs::msec>().count() << ", chunks: " << chunks_.size() << ", out: " << cs::duration<cs::msec>(outputBufferDacTime) << ", needed: " << cs::duration<cs::msec>(bufferDuration) << "\n"; logO << "sleep > chunkDuration: " << cs::duration<cs::msec>(sleep_) << " > " << chunk_->duration<cs::msec>().count() << ", chunks: " << chunks_.size() << ", out: " << cs::duration<cs::msec>(outputBufferDacTime) << ", needed: " << cs::duration<cs::msec>(bufferDuration) << "\n";
sleep_ = std::chrono::duration_cast<cs::usec>(TimeProvider::serverNow() - chunk_->start() - bufferMs_ + outputBufferDacTime); sleep_ = std::chrono::duration_cast<cs::usec>(TimeProvider::serverNow() - chunk_->start() - bufferMs_ + outputBufferDacTime);
@ -255,7 +255,6 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT
return false; return false;
} }
} }
while (sleep_ > chunk_->duration<cs::usec>());
} }
// out of sync, can be corrected by playing faster/slower // out of sync, can be corrected by playing faster/slower