summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--audio.c159
-rw-r--r--codec.c94
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 <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);
}
//----------------------------------------------------------------------------
@@ -584,27 +603,42 @@ 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.
*/
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;
}
/**
@@ -752,16 +817,39 @@ void AudioSetDevice(const char *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,7 +669,10 @@ void CodecAudioDecode(AudioDecoder * audio_decoder, AVPacket * avpkt)
index += n;
}
+
+#if 0
av_destruct_packet(spkt);
+#endif
}
//----------------------------------------------------------------------------
@@ -616,10 +680,24 @@ void CodecAudioDecode(AudioDecoder * audio_decoder, AVPacket * avpkt)
//----------------------------------------------------------------------------
/**
+** 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
}