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 <stdint.h>
#include <inttypes.h>
#include <libintl.h>
#define _(str) gettext(str) ///< gettext shortcut
@ -63,7 +64,7 @@ static volatile char AudioRunning; ///< thread running / stopped
static int AudioPaused; ///< audio paused
static unsigned AudioSampleRate; ///< audio sample rate in hz
static unsigned AudioChannels; ///< number of audio channels
static uint64_t AudioPTS; ///< audio pts clock
static int64_t AudioPTS; ///< audio pts clock
//----------------------------------------------------------------------------
// Alsa variables
@ -75,6 +76,7 @@ static int AlsaUseMmap; ///< use mmap
static RingBuffer *AlsaRingBuffer; ///< audio ring buffer
static unsigned AlsaStartThreshold; ///< start play, if filled
static int AlsaFlushBuffer; ///< flag empty buffer
static snd_mixer_t *AlsaMixer; ///< alsa mixer handle
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
}
// Update audio clock
AudioPTS += (count * 90000) / (AudioSampleRate * AudioChannels * 2);
AudioPTS +=
((int64_t) count * 90000) / (AudioSampleRate * AudioChannels * 2);
if (!AudioRunning) {
if (AlsaStartThreshold < RingBufferUsedBytes(AlsaRingBuffer)) {
@ -149,7 +152,7 @@ static int AlsaPlayRingbuffer(void)
// happens with broken alsa drivers
Error(_("audio/alsa: broken driver %d\n"), avail);
}
break;
//break;
}
n = RingBufferGetReadPointer(AlsaRingBuffer, &p);
@ -373,6 +376,18 @@ static void *AudioPlayHandlerThread(void *dummy)
usleep(100 * 1000);
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
snd_pcm_state_t state;
@ -400,6 +415,10 @@ static void *AudioPlayHandlerThread(void *dummy)
*/
void AudioEnqueue(const void *samples, int count)
{
if (!AlsaRingBuffer || !AlsaPCMHandle) {
Debug(3, "audio/alsa: alsa not ready\n");
return;
}
if (AlsaAddToRingbuffer(samples, count)) {
snd_pcm_state_t state;
@ -508,7 +527,7 @@ static void AlsaInitPCM(void)
pthread_mutex_init(&AudioMutex, NULL);
pthread_cond_init(&AudioStartCond, 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.
*/
@ -590,21 +624,21 @@ uint64_t AudioGetDelay(void)
{
int err;
snd_pcm_sframes_t delay;
uint64_t pts;
if ((err = snd_pcm_delay(AlsaPCMHandle, &delay)) < 0) {
//Debug(3, "audio/alsa: no hw delay\n");
delay = 0UL;
} 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 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
*/
void AudioSetup(int freq, int channels)
int AudioSetup(int *freq, int *channels)
{
snd_pcm_uframes_t buffer_size;
snd_pcm_uframes_t period_size;
int err;
int ret;
#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
return;
return -1;
}
AudioChannels = channels;
AudioSampleRate = freq;
// FIXME: thread!!
RingBufferReadAdvance(AlsaRingBuffer, RingBufferUsedBytes(AlsaRingBuffer));
AudioChannels = *channels;
AudioSampleRate = *freq;
// flush any buffered data
#ifdef USE_AUDIO_THREAD
if (AudioRunning) {
AlsaFlushBuffer = 1;
} else
#endif
{
RingBufferReadAdvance(AlsaRingBuffer,
RingBufferUsedBytes(AlsaRingBuffer));
}
ret = 0;
try_again:
if ((err =
snd_pcm_set_params(AlsaPCMHandle, SND_PCM_FORMAT_S16,
AlsaUseMmap ? SND_PCM_ACCESS_MMAP_INTERLEAVED :
SND_PCM_ACCESS_RW_INTERLEAVED, channels, freq, 1,
SND_PCM_ACCESS_RW_INTERLEAVED, *channels, *freq, 1,
125 * 1000))) {
Error(_("audio/alsa: set params error: %s\n"), snd_strerror(err));
if (channels == 2) {
// FIXME: must stop sound
return;
switch (*channels) {
case 1:
// 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
// AudioChannels = downmix_channels;
return;
return -1;
}
#else
snd_pcm_hw_params_t *hw_params;
@ -731,14 +795,15 @@ void AudioSetup(int freq, int channels)
Debug(3, "audio/alsa: state %s\n",
snd_pcm_state_name(snd_pcm_state(AlsaPCMHandle)));
AlsaStartThreshold =
snd_pcm_frames_to_bytes(AlsaPCMHandle, buffer_size + period_size);
AlsaStartThreshold = snd_pcm_frames_to_bytes(AlsaPCMHandle, buffer_size);
// min 500ms
if (AlsaStartThreshold < (freq * channels * 2U) / 2) {
AlsaStartThreshold = (freq * channels * 2U) / 2;
if (AlsaStartThreshold < (*freq * *channels * 2U) / 2) {
AlsaStartThreshold = (*freq * *channels * 2U) / 2;
}
Debug(3, "audio/alsa: delay %u ms\n", (AlsaStartThreshold * 1000)
/ (AudioSampleRate * AudioChannels * 2));
return ret;
}
/**
@ -751,17 +816,40 @@ void AudioSetDevice(const char *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.
*/
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
AlsaInitPCM();
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;
}
@ -773,9 +861,10 @@ void AudioExit(void)
{
void *retval;
pthread_cancel(AudioThread);
pthread_join(AudioThread, &retval);
if (retval != PTHREAD_CANCELED) {
if (pthread_cancel(AudioThread)) {
Error(_("audio: can't queue cancel alsa play thread\n"));
}
if (pthread_join(AudioThread, &retval) || retval != PTHREAD_CANCELED) {
Error(_("audio: can't cancel alsa play thread\n"));
}
pthread_cond_destroy(&AudioStartCond);

94
codec.c
View File

@ -346,7 +346,7 @@ void CodecVideoClose(VideoDecoder * video_decoder)
{
// FIXME: play buffered data
av_freep(&video_decoder->Frame);
if ( video_decoder->VideoCtx ) {
if (video_decoder->VideoCtx) {
avcodec_close(video_decoder->VideoCtx);
av_freep(&video_decoder->VideoCtx);
}
@ -455,8 +455,13 @@ struct _audio_decoder_
/// audio parser to support wired dvb streaks
AVCodecParserContext *AudioParser;
int SampleRate; ///< old sample rate
int Channels; ///< old channels
int SampleRate; ///< current sample rate
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)
{
// FIXME: output any buffered data
if (audio_decoder->ReSample) {
audio_resample_close(audio_decoder->ReSample);
audio_decoder->ReSample = NULL;
}
if (audio_decoder->AudioParser) {
av_parser_close(audio_decoder->AudioParser);
audio_decoder->AudioParser = NULL;
@ -549,11 +558,13 @@ void CodecAudioDecode(AudioDecoder * audio_decoder, AVPacket * avpkt)
FF_INPUT_BUFFER_PADDING_SIZE] __attribute__ ((aligned(16)));
AVCodecContext *audio_ctx;
int index;
AVPacket spkt[1];
if (!audio_decoder->AudioParser) {
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)) {
Error(_("codec: out of memory\n"));
@ -561,6 +572,9 @@ void CodecAudioDecode(AudioDecoder * audio_decoder, AVPacket * avpkt)
}
memcpy(spkt->data, avpkt->data, avpkt->size);
memset(spkt->data + avpkt->size, 0, FF_INPUT_BUFFER_PADDING_SIZE);
spkt->pts = avpkt->pts;
#endif
audio_ctx = audio_decoder->AudioCtx;
index = 0;
while (avpkt->size > index) {
@ -571,11 +585,13 @@ void CodecAudioDecode(AudioDecoder * audio_decoder, AVPacket * avpkt)
av_init_packet(dpkt);
n = av_parser_parse2(audio_decoder->AudioParser, audio_ctx,
&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) {
int buf_sz;
dpkt->pts = audio_decoder->AudioParser->pts;
buf_sz = sizeof(buf);
l = avcodec_decode_audio3(audio_ctx, buf, &buf_sz, dpkt);
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);
#else
#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.
if (audio_decoder->SampleRate != audio_ctx->sample_rate
|| audio_decoder->Channels != audio_ctx->channels) {
int err;
// FIXME: channels not support?
AudioSetup(audio_ctx->sample_rate, audio_ctx->channels);
if (audio_decoder->ReSample) {
audio_resample_close(audio_decoder->ReSample);
audio_decoder->ReSample = NULL;
}
audio_decoder->SampleRate = audio_ctx->sample_rate;
audio_decoder->HwSampleRate = audio_ctx->sample_rate;
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) {
Error(_("codec: error more than one frame data\n"));
@ -608,18 +669,35 @@ void CodecAudioDecode(AudioDecoder * audio_decoder, AVPacket * avpkt)
index += n;
}
#if 0
av_destruct_packet(spkt);
#endif
}
//----------------------------------------------------------------------------
// 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
*/
void CodecInit(void)
{
#ifndef DEBUG
// display ffmpeg error messages
av_log_set_callback(CodecNoopCallback);
#endif
avcodec_register_all(); // register all formats and codecs
}