From 3f8ff57e30c82500511d40f718f6f80aafd37d20 Mon Sep 17 00:00:00 2001 From: Johns Date: Thu, 8 Dec 2011 18:58:10 +0100 Subject: [PATCH] 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. --- audio.c | 159 +++++++++++++++++++++++++++++++++++++++++++------------- codec.c | 94 ++++++++++++++++++++++++++++++--- 2 files changed, 210 insertions(+), 43 deletions(-) diff --git a/audio.c b/audio.c index 69a324e..f983787 100644 --- a/audio.c +++ b/audio.c @@ -35,6 +35,7 @@ #include #include +#include #include #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); diff --git a/codec.c b/codec.c index b8fda3e..c5269b4 100644 --- a/codec.c +++ b/codec.c @@ -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 }