Audio improvement.

Made audio thread cancelable.
Calculate audio PTS.
Disable alsa message to stderr.
Better buffer flush with threaded play.
Prepared audio resample, for unsupported number of audio channels.
This commit is contained in:
Johns 2011-12-08 18:58:10 +01:00
parent fc2580dc2a
commit 3f8ff57e30
2 changed files with 210 additions and 43 deletions

159
audio.c
View File

@ -35,6 +35,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include <inttypes.h>
#include <libintl.h> #include <libintl.h>
#define _(str) gettext(str) ///< gettext shortcut #define _(str) gettext(str) ///< gettext shortcut
@ -63,7 +64,7 @@ static volatile char AudioRunning; ///< thread running / stopped
static int AudioPaused; ///< audio paused static int AudioPaused; ///< audio paused
static unsigned AudioSampleRate; ///< audio sample rate in hz static unsigned AudioSampleRate; ///< audio sample rate in hz
static unsigned AudioChannels; ///< number of audio channels static unsigned AudioChannels; ///< number of audio channels
static uint64_t AudioPTS; ///< audio pts clock static int64_t AudioPTS; ///< audio pts clock
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
// Alsa variables // Alsa variables
@ -75,6 +76,7 @@ static int AlsaUseMmap; ///< use mmap
static RingBuffer *AlsaRingBuffer; ///< audio ring buffer static RingBuffer *AlsaRingBuffer; ///< audio ring buffer
static unsigned AlsaStartThreshold; ///< start play, if filled static unsigned AlsaStartThreshold; ///< start play, if filled
static int AlsaFlushBuffer; ///< flag empty buffer
static snd_mixer_t *AlsaMixer; ///< alsa mixer handle static snd_mixer_t *AlsaMixer; ///< alsa mixer handle
static snd_mixer_elem_t *AlsaMixerElem; ///< alsa pcm mixer element static snd_mixer_elem_t *AlsaMixerElem; ///< alsa pcm mixer element
@ -102,7 +104,8 @@ static int AlsaAddToRingbuffer(const void *samples, int count)
// too many bytes are lost // too many bytes are lost
} }
// Update audio clock // Update audio clock
AudioPTS += (count * 90000) / (AudioSampleRate * AudioChannels * 2); AudioPTS +=
((int64_t) count * 90000) / (AudioSampleRate * AudioChannels * 2);
if (!AudioRunning) { if (!AudioRunning) {
if (AlsaStartThreshold < RingBufferUsedBytes(AlsaRingBuffer)) { if (AlsaStartThreshold < RingBufferUsedBytes(AlsaRingBuffer)) {
@ -149,7 +152,7 @@ static int AlsaPlayRingbuffer(void)
// happens with broken alsa drivers // happens with broken alsa drivers
Error(_("audio/alsa: broken driver %d\n"), avail); Error(_("audio/alsa: broken driver %d\n"), avail);
} }
break; //break;
} }
n = RingBufferGetReadPointer(AlsaRingBuffer, &p); n = RingBufferGetReadPointer(AlsaRingBuffer, &p);
@ -373,6 +376,18 @@ static void *AudioPlayHandlerThread(void *dummy)
usleep(100 * 1000); usleep(100 * 1000);
continue; continue;
} }
if (AlsaFlushBuffer) {
// we can flush too many, but wo cares
Debug(3, "audio/alsa: flushing buffers\n");
RingBufferReadAdvance(AlsaRingBuffer,
RingBufferUsedBytes(AlsaRingBuffer));
if ((err = snd_pcm_drain(AlsaPCMHandle))) {
Error(_("audio: snd_pcm_drain(): %s\n"),
snd_strerror(err));
}
AlsaFlushBuffer = 0;
break;
}
if ((err = AlsaPlayRingbuffer())) { // empty / error if ((err = AlsaPlayRingbuffer())) { // empty / error
snd_pcm_state_t state; snd_pcm_state_t state;
@ -400,6 +415,10 @@ static void *AudioPlayHandlerThread(void *dummy)
*/ */
void AudioEnqueue(const void *samples, int count) void AudioEnqueue(const void *samples, int count)
{ {
if (!AlsaRingBuffer || !AlsaPCMHandle) {
Debug(3, "audio/alsa: alsa not ready\n");
return;
}
if (AlsaAddToRingbuffer(samples, count)) { if (AlsaAddToRingbuffer(samples, count)) {
snd_pcm_state_t state; snd_pcm_state_t state;
@ -508,7 +527,7 @@ static void AlsaInitPCM(void)
pthread_mutex_init(&AudioMutex, NULL); pthread_mutex_init(&AudioMutex, NULL);
pthread_cond_init(&AudioStartCond, NULL); pthread_cond_init(&AudioStartCond, NULL);
pthread_create(&AudioThread, NULL, AudioPlayHandlerThread, NULL); pthread_create(&AudioThread, NULL, AudioPlayHandlerThread, NULL);
pthread_detach(AudioThread); //pthread_detach(AudioThread);
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@ -583,6 +602,21 @@ static void AlsaInitMixer(void)
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
/**
** Set audio clock base.
**
** @param pts audio presentation timestamp
*/
void AudioSetClock(int64_t pts)
{
if (AudioPTS != pts) {
Debug(3, "audio: set clock to %#012" PRIx64 " %#012" PRIx64 " pts\n",
AudioPTS, pts);
AudioPTS = pts;
}
}
/** /**
** Get audio delay in time stamps. ** Get audio delay in time stamps.
*/ */
@ -590,21 +624,21 @@ uint64_t AudioGetDelay(void)
{ {
int err; int err;
snd_pcm_sframes_t delay; snd_pcm_sframes_t delay;
uint64_t pts;
if ((err = snd_pcm_delay(AlsaPCMHandle, &delay)) < 0) { if ((err = snd_pcm_delay(AlsaPCMHandle, &delay)) < 0) {
//Debug(3, "audio/alsa: no hw delay\n"); //Debug(3, "audio/alsa: no hw delay\n");
delay = 0UL; delay = 0UL;
} else if (snd_pcm_state(AlsaPCMHandle) != SND_PCM_STATE_RUNNING) { } else if (snd_pcm_state(AlsaPCMHandle) != SND_PCM_STATE_RUNNING) {
//Debug(3, "audio/alsa: %lu delay ok, but not running\n", delay); //Debug(3, "audio/alsa: %ld delay ok, but not running\n", delay);
} }
delay = (delay * 90000) / AudioSampleRate;
//Debug(3, "audio/alsa: hw delay %lu\n", delay);
delay +=
(RingBufferUsedBytes(AlsaRingBuffer) * 90000) / (AudioSampleRate *
AudioChannels);
//Debug(3, "audio/alsa: hw+sw delay %lu ms\n", delay / 90);
return delay; pts = ((uint64_t) delay * 90000) / AudioSampleRate;
pts += ((uint64_t) RingBufferUsedBytes(AlsaRingBuffer) * 90000)
/ (AudioSampleRate * AudioChannels);
//Debug(3, "audio/alsa: hw+sw delay %"PRId64" ms\n", pts / 90);
return pts;
} }
/** /**
@ -613,40 +647,70 @@ uint64_t AudioGetDelay(void)
** @param freq sample frequency ** @param freq sample frequency
** @param channels number of channels ** @param channels number of channels
** **
** @retval 0 everything ok
** @retval 1 didn't support frequency/channels combination
** @retval -1 something gone wrong
**
** @todo audio changes must be queued and done when the buffer is empty ** @todo audio changes must be queued and done when the buffer is empty
*/ */
void AudioSetup(int freq, int channels) int AudioSetup(int *freq, int *channels)
{ {
snd_pcm_uframes_t buffer_size; snd_pcm_uframes_t buffer_size;
snd_pcm_uframes_t period_size; snd_pcm_uframes_t period_size;
int err; int err;
int ret;
#if 1 #if 1
Debug(3, "audio/alsa: channels %d frequency %d hz\n", channels, freq); Debug(3, "audio/alsa: channels %d frequency %d hz\n", *channels, *freq);
if (!freq || !channels) { // invalid parameter // invalid parameter
if (!freq || !channels || !*freq || !*channels) {
Debug(3, "audio: bad channels or frequency parameters\n");
// FIXME: set flag invalid setup // FIXME: set flag invalid setup
return; return -1;
} }
AudioChannels = channels; AudioChannels = *channels;
AudioSampleRate = freq; AudioSampleRate = *freq;
// FIXME: thread!!
RingBufferReadAdvance(AlsaRingBuffer, RingBufferUsedBytes(AlsaRingBuffer));
// flush any buffered data
#ifdef USE_AUDIO_THREAD
if (AudioRunning) {
AlsaFlushBuffer = 1;
} else
#endif
{
RingBufferReadAdvance(AlsaRingBuffer,
RingBufferUsedBytes(AlsaRingBuffer));
}
ret = 0;
try_again:
if ((err = if ((err =
snd_pcm_set_params(AlsaPCMHandle, SND_PCM_FORMAT_S16, snd_pcm_set_params(AlsaPCMHandle, SND_PCM_FORMAT_S16,
AlsaUseMmap ? SND_PCM_ACCESS_MMAP_INTERLEAVED : AlsaUseMmap ? SND_PCM_ACCESS_MMAP_INTERLEAVED :
SND_PCM_ACCESS_RW_INTERLEAVED, channels, freq, 1, SND_PCM_ACCESS_RW_INTERLEAVED, *channels, *freq, 1,
125 * 1000))) { 125 * 1000))) {
Error(_("audio/alsa: set params error: %s\n"), snd_strerror(err)); Error(_("audio/alsa: set params error: %s\n"), snd_strerror(err));
if (channels == 2) { switch (*channels) {
// FIXME: must stop sound case 1:
return; // FIXME: enable channel upmix
ret = 1;
*channels = 2;
goto try_again;
case 2:
return -1;
case 4:
case 6:
// FIXME: enable channel downmix
*channels = 2;
goto try_again;
default:
Error(_("audio/alsa: unsupported number of channels\n"));
// FIXME: must stop sound
return -1;
} }
// FIXME: enable channel downmix return -1;
// AudioChannels = downmix_channels;
return;
} }
#else #else
snd_pcm_hw_params_t *hw_params; snd_pcm_hw_params_t *hw_params;
@ -731,14 +795,15 @@ void AudioSetup(int freq, int channels)
Debug(3, "audio/alsa: state %s\n", Debug(3, "audio/alsa: state %s\n",
snd_pcm_state_name(snd_pcm_state(AlsaPCMHandle))); snd_pcm_state_name(snd_pcm_state(AlsaPCMHandle)));
AlsaStartThreshold = AlsaStartThreshold = snd_pcm_frames_to_bytes(AlsaPCMHandle, buffer_size);
snd_pcm_frames_to_bytes(AlsaPCMHandle, buffer_size + period_size);
// min 500ms // min 500ms
if (AlsaStartThreshold < (freq * channels * 2U) / 2) { if (AlsaStartThreshold < (*freq * *channels * 2U) / 2) {
AlsaStartThreshold = (freq * channels * 2U) / 2; AlsaStartThreshold = (*freq * *channels * 2U) / 2;
} }
Debug(3, "audio/alsa: delay %u ms\n", (AlsaStartThreshold * 1000) Debug(3, "audio/alsa: delay %u ms\n", (AlsaStartThreshold * 1000)
/ (AudioSampleRate * AudioChannels * 2)); / (AudioSampleRate * AudioChannels * 2));
return ret;
} }
/** /**
@ -751,17 +816,40 @@ void AudioSetDevice(const char *device)
AudioPCMDevice = device; AudioPCMDevice = device;
} }
/**
** Empty log callback
*/
static void AlsaNoopCallback( __attribute__ ((unused))
const char *file, __attribute__ ((unused))
int line, __attribute__ ((unused))
const char *function, __attribute__ ((unused))
int err, __attribute__ ((unused))
const char *fmt, ...)
{
}
/** /**
** Initialize audio output module. ** Initialize audio output module.
*/ */
void AudioInit(void) void AudioInit(void)
{ {
int freq;
int chan;
#ifndef DEBUG
// display alsa error messages
snd_lib_error_set_handler(AlsaNoopCallback);
#endif
AlsaRingBuffer = RingBufferNew(48000 * 8 * 2); // ~1s 8ch 16bit AlsaRingBuffer = RingBufferNew(48000 * 8 * 2); // ~1s 8ch 16bit
AlsaInitPCM(); AlsaInitPCM();
AlsaInitMixer(); AlsaInitMixer();
AudioSetup(48000, 2); // set default parameters freq = 48000;
chan = 2;
if (AudioSetup(&freq, &chan)) { // set default parameters
Error(_("audio: can't do initial setup\n"));
}
AudioPaused = 1; AudioPaused = 1;
} }
@ -773,9 +861,10 @@ void AudioExit(void)
{ {
void *retval; void *retval;
pthread_cancel(AudioThread); if (pthread_cancel(AudioThread)) {
pthread_join(AudioThread, &retval); Error(_("audio: can't queue cancel alsa play thread\n"));
if (retval != PTHREAD_CANCELED) { }
if (pthread_join(AudioThread, &retval) || retval != PTHREAD_CANCELED) {
Error(_("audio: can't cancel alsa play thread\n")); Error(_("audio: can't cancel alsa play thread\n"));
} }
pthread_cond_destroy(&AudioStartCond); pthread_cond_destroy(&AudioStartCond);

94
codec.c
View File

@ -346,7 +346,7 @@ void CodecVideoClose(VideoDecoder * video_decoder)
{ {
// FIXME: play buffered data // FIXME: play buffered data
av_freep(&video_decoder->Frame); av_freep(&video_decoder->Frame);
if ( video_decoder->VideoCtx ) { if (video_decoder->VideoCtx) {
avcodec_close(video_decoder->VideoCtx); avcodec_close(video_decoder->VideoCtx);
av_freep(&video_decoder->VideoCtx); av_freep(&video_decoder->VideoCtx);
} }
@ -455,8 +455,13 @@ struct _audio_decoder_
/// audio parser to support wired dvb streaks /// audio parser to support wired dvb streaks
AVCodecParserContext *AudioParser; AVCodecParserContext *AudioParser;
int SampleRate; ///< old sample rate int SampleRate; ///< current sample rate
int Channels; ///< old channels int Channels; ///< current channels
int HwSampleRate; ///< hw sample rate
int HwChannels; ///< hw channels
ReSampleContext *ReSample; ///< audio resampling context
}; };
/** /**
@ -525,6 +530,10 @@ void CodecAudioOpen(AudioDecoder * audio_decoder, const char *name,
void CodecAudioClose(AudioDecoder * audio_decoder) void CodecAudioClose(AudioDecoder * audio_decoder)
{ {
// FIXME: output any buffered data // FIXME: output any buffered data
if (audio_decoder->ReSample) {
audio_resample_close(audio_decoder->ReSample);
audio_decoder->ReSample = NULL;
}
if (audio_decoder->AudioParser) { if (audio_decoder->AudioParser) {
av_parser_close(audio_decoder->AudioParser); av_parser_close(audio_decoder->AudioParser);
audio_decoder->AudioParser = NULL; audio_decoder->AudioParser = NULL;
@ -549,11 +558,13 @@ void CodecAudioDecode(AudioDecoder * audio_decoder, AVPacket * avpkt)
FF_INPUT_BUFFER_PADDING_SIZE] __attribute__ ((aligned(16))); FF_INPUT_BUFFER_PADDING_SIZE] __attribute__ ((aligned(16)));
AVCodecContext *audio_ctx; AVCodecContext *audio_ctx;
int index; int index;
AVPacket spkt[1];
if (!audio_decoder->AudioParser) { if (!audio_decoder->AudioParser) {
Fatal(_("codec: internal error parser freeded while running\n")); Fatal(_("codec: internal error parser freeded while running\n"));
} }
#define spkt avpkt
#if 0
AVPacket spkt[1];
if (av_new_packet(spkt, avpkt->size + FF_INPUT_BUFFER_PADDING_SIZE)) { if (av_new_packet(spkt, avpkt->size + FF_INPUT_BUFFER_PADDING_SIZE)) {
Error(_("codec: out of memory\n")); Error(_("codec: out of memory\n"));
@ -561,6 +572,9 @@ void CodecAudioDecode(AudioDecoder * audio_decoder, AVPacket * avpkt)
} }
memcpy(spkt->data, avpkt->data, avpkt->size); memcpy(spkt->data, avpkt->data, avpkt->size);
memset(spkt->data + avpkt->size, 0, FF_INPUT_BUFFER_PADDING_SIZE); memset(spkt->data + avpkt->size, 0, FF_INPUT_BUFFER_PADDING_SIZE);
spkt->pts = avpkt->pts;
#endif
audio_ctx = audio_decoder->AudioCtx; audio_ctx = audio_decoder->AudioCtx;
index = 0; index = 0;
while (avpkt->size > index) { while (avpkt->size > index) {
@ -571,11 +585,13 @@ void CodecAudioDecode(AudioDecoder * audio_decoder, AVPacket * avpkt)
av_init_packet(dpkt); av_init_packet(dpkt);
n = av_parser_parse2(audio_decoder->AudioParser, audio_ctx, n = av_parser_parse2(audio_decoder->AudioParser, audio_ctx,
&dpkt->data, &dpkt->size, spkt->data + index, avpkt->size - index, &dpkt->data, &dpkt->size, spkt->data + index, avpkt->size - index,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE); !index ? (uint64_t) spkt->pts : AV_NOPTS_VALUE, AV_NOPTS_VALUE,
-1);
if (dpkt->size) { if (dpkt->size) {
int buf_sz; int buf_sz;
dpkt->pts = audio_decoder->AudioParser->pts;
buf_sz = sizeof(buf); buf_sz = sizeof(buf);
l = avcodec_decode_audio3(audio_ctx, buf, &buf_sz, dpkt); l = avcodec_decode_audio3(audio_ctx, buf, &buf_sz, dpkt);
if (l < 0) { // no audio frame could be decompressed if (l < 0) { // no audio frame could be decompressed
@ -589,17 +605,62 @@ void CodecAudioDecode(AudioDecoder * audio_decoder, AVPacket * avpkt)
avcodec_decode_audio4(audio_ctx, frame, &got_frame, dpkt); avcodec_decode_audio4(audio_ctx, frame, &got_frame, dpkt);
#else #else
#endif #endif
// Update audio clock
if ((uint64_t) dpkt->pts != AV_NOPTS_VALUE) {
AudioSetClock(dpkt->pts);
}
// FIXME: must first play remainings bytes, than change and play new. // FIXME: must first play remainings bytes, than change and play new.
if (audio_decoder->SampleRate != audio_ctx->sample_rate if (audio_decoder->SampleRate != audio_ctx->sample_rate
|| audio_decoder->Channels != audio_ctx->channels) { || audio_decoder->Channels != audio_ctx->channels) {
int err;
// FIXME: channels not support? if (audio_decoder->ReSample) {
AudioSetup(audio_ctx->sample_rate, audio_ctx->channels); audio_resample_close(audio_decoder->ReSample);
audio_decoder->ReSample = NULL;
}
audio_decoder->SampleRate = audio_ctx->sample_rate; audio_decoder->SampleRate = audio_ctx->sample_rate;
audio_decoder->HwSampleRate = audio_ctx->sample_rate;
audio_decoder->Channels = audio_ctx->channels; audio_decoder->Channels = audio_ctx->channels;
audio_decoder->HwChannels = audio_ctx->channels;
// channels not support?
if ((err =
AudioSetup(&audio_decoder->HwSampleRate,
&audio_decoder->HwChannels))) {
Debug(3, "codec/audio: resample %d -> %d\n",
audio_ctx->channels, audio_decoder->HwChannels);
if (err == 1) {
audio_decoder->ReSample =
av_audio_resample_init(audio_decoder->HwChannels,
audio_ctx->channels, audio_decoder->HwSampleRate,
audio_ctx->sample_rate, audio_ctx->sample_fmt,
audio_ctx->sample_fmt, 16, 10, 0, 0.8);
} else {
// FIXME: handle errors
audio_decoder->HwChannels = 0;
audio_decoder->HwSampleRate = 0;
}
}
}
if (audio_decoder->HwSampleRate && audio_decoder->HwChannels) {
// need to resample audio
if (audio_decoder->ReSample) {
int16_t outbuf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 4 +
FF_INPUT_BUFFER_PADDING_SIZE]
__attribute__ ((aligned(16)));
int outlen;
outlen =
audio_resample(audio_decoder->ReSample, outbuf, buf,
buf_sz);
AudioEnqueue(outbuf, outlen);
} else {
AudioEnqueue(buf, buf_sz);
}
} }
AudioEnqueue(buf, buf_sz);
if (dpkt->size > l) { if (dpkt->size > l) {
Error(_("codec: error more than one frame data\n")); Error(_("codec: error more than one frame data\n"));
@ -608,18 +669,35 @@ void CodecAudioDecode(AudioDecoder * audio_decoder, AVPacket * avpkt)
index += n; index += n;
} }
#if 0
av_destruct_packet(spkt); av_destruct_packet(spkt);
#endif
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
// Codec // Codec
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
/**
** Empty log callback
*/
static void CodecNoopCallback( __attribute__ ((unused))
void *ptr, __attribute__ ((unused))
int level, __attribute__ ((unused))
const char *fmt, __attribute__ ((unused)) va_list vl)
{
}
/** /**
** Codec init ** Codec init
*/ */
void CodecInit(void) void CodecInit(void)
{ {
#ifndef DEBUG
// display ffmpeg error messages
av_log_set_callback(CodecNoopCallback);
#endif
avcodec_register_all(); // register all formats and codecs avcodec_register_all(); // register all formats and codecs
} }