File:
[DVB] /
libsoftmpeg /
src /
audio.c
Revision
1.33:
download - view:
text,
annotated -
select for diffs
Mon Jul 12 16:48:04 2004 UTC (19 years, 11 months ago) by
hunold
Branches:
MAIN
CVS tags:
HEAD
- remove individual xxx_handle_ts_packet() functions for audio and video
- handle ts packet filtering in softmpeg.c, do some sanity checks against payload_len
=> enhances stability on very broken reception
/*
(c) Copyright 2004 convergence GmbH
All rights reserved.
Written by Michael Hunold <hunold@convergence.de> and
Denis Oliver Kropp <dok@directfb.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
*/
#include "audio.h"
#include "internal.h"
//#define AUDIO_PES_TO_FRAME_DEBUG
#ifdef AUDIO_PES_TO_FRAME_DEBUG
#define AUDIO_PES(x...) SOFTMPEG_DEBUG("Audio PES: "x)
#else
#define AUDIO_PES(x...) do {} while (0)
#endif
#define AUDIO_BUFFER_SIZE 64000 /* audio ring buffer size */
/* all values in percent, xx.yy */
#define AUDIO_BUFFER_LOW_MARK_P 3333 /* print warning if this mark is underrun */
#define AUDIO_BUFFER_MID_MARK_P 4000 /* prebuffer amount */
#define AUDIO_BUFFER_HIGH_MARK_P 5500 /* limit to avoid blocking writes */
int audio_set_pause(struct audio_decoder *d, int paused)
{
int ret;
AUDIO_DEBUG("pause: %d\n",paused);
pthread_mutex_lock(&d->stream_mutex);
ret = d->a_ops->pause(d, paused);
pthread_mutex_unlock(&d->stream_mutex);
return ret;
}
void audio_set_volume(struct audio_decoder *d, float volume)
{
int ret;
AUDIO_DEBUG("volume: %f\n",volume);
pthread_mutex_lock(&d->stream_mutex);
ret = d->a_ops->volume(d, volume);
pthread_mutex_unlock(&d->stream_mutex);
//return ret;
}
int audio_get_av_delay(struct audio_decoder *d, struct av_sync *av)
{
int ret;
int delay;
memset(av, 0, sizeof(struct av_sync));
/* access to d->as should be protected by video mutex */
if (d->smd->mode == SOFTMPEG_PAUSE) {
AUDIO_DEBUG("audio is paused.\n");
return 1;
}
if (d->as.sync_pts == 0) {
AUDIO_DEBUG("sync pts not set, yet.\n");
return -1;
}
pthread_mutex_lock(&d->stream_mutex);
ret = d->a_ops->get_delay(d, &delay);
pthread_mutex_unlock(&d->stream_mutex);
if (0 != ret) {
AUDIO_DEBUG("not playing / stream playback unexpectedly ended!\n");
return -1;
}
av->stc = d->as.sync_pts - (delay * 90);
return 0;
}
int audio_decoder_destroy(struct audio_decoder *d)
{
if (d->as.buf != NULL)
free(d->as.buf);
if (d != NULL)
free(d);
return 0;
}
extern struct audio_operations a_fusionsound_ops;
struct audio_decoder *audio_decoder_create(void *priv, struct softmpeg_decoder *dec, enum softmpeg_audio_output ao, void *init_data)
{
struct audio_decoder *d;
int i = 0;
d = calloc(1, sizeof(*d));
if (NULL == d) {
SOFTMPEG_ERROR("not enough memory.\n");
return NULL;
}
d->smd = dec;
switch (ao) {
case SOFTMPEG_FUSIONSOUND: {
d->a_ops = &a_fusionsound_ops;
break;
}
default: {
SOFTMPEG_ERROR("unsupported audio output device.\n");
free(d);
return NULL;
}
}
if (0 != d->a_ops->init(d, init_data)) {
SOFTMPEG_ERROR("initialization of audio output device failed.\n");
free(d);
return NULL;
}
/* fixme: what's the maximum pes frame size for audio? */
d->as.buf = (unsigned char*)malloc(8192);
if (NULL == d->as.buf) {
SOFTMPEG_ERROR("not enough memory.\n");
free(d->as.buf);
return NULL;
}
init_avcodec_stuff();
d->codec = avcodec_find_decoder(CODEC_ID_MP2);
if (NULL == d->codec) {
SOFTMPEG_ERROR("a codec not found\n");
goto error;
}
d->codec_ctx = avcodec_alloc_context();
if (NULL == d->codec_ctx) {
SOFTMPEG_ERROR("cannot allocate a codec context\n");
/* fixme: release decoder */
goto error;
}
if (avcodec_open(d->codec_ctx, d->codec) < 0) {
SOFTMPEG_ERROR("could not open codec\n");
/* fixme: release decoder + contect */
goto error;
}
list_init(&d->free, offsetof(struct audio_buffer, node));
list_init(&d->filled, offsetof(struct audio_buffer, node));
for (i = 0; i < NUMBER_OF_AUDIO_BUFFERS; i++) {
struct audio_buffer *obuf = &d->output[i];
/* fixme: calculate bufsize correctly, currently one second @ 48kHz, 16 Bit, 2 Channels */
obuf->buf = malloc(48000 * 2 * 2);
if (NULL == obuf->buf) {
/* fixme: error handling */
SOFTMPEG_ERROR("out of memory\n");
goto error;
}
obuf->len = 0;
list_add_tail(obuf, &d->free);
}
/* make first item current */
d->current = (struct audio_buffer *) list_del_head(&d->free);
d->priv = priv;
d->as.scanning_for_first_frame = 1;
pthread_mutex_init(&d->stream_mutex, NULL);
pthread_mutex_init(&d->a_mutex, NULL);
pthread_cond_init(&d->a_cond, NULL);
return d;
error:
/* fixme: error handling broken */
if (d->as.buf != NULL)
free(d->as.buf);
if (d != NULL)
free(d);
return NULL;
}
/* return amount of ms we can wait */
int audio_playout(struct audio_decoder *d, struct audio_buffer *output)
{
int ret = 0;
int percent;
int filled;
int total;
int delay;
pthread_mutex_lock(&d->stream_mutex);
ret = d->a_ops->write(d, output, &filled, &total);
pthread_mutex_unlock(&d->stream_mutex);
percent = (filled * 10000)/ total;
if (!d->playing) {
if (percent < AUDIO_BUFFER_MID_MARK_P) {
AUDIO_DEBUG("prebuffering (%d/%d) ...\n", percent, AUDIO_BUFFER_MID_MARK_P);
}
else {
AUDIO_DEBUG("finished prebuffering (%d/%d), starting playback...\n",
percent, AUDIO_BUFFER_MID_MARK_P);
pthread_mutex_lock(&d->stream_mutex);
ret = d->a_ops->pause(d, 0);
pthread_mutex_unlock(&d->stream_mutex);
/* unmute audio here if there is no video playback (ie. radio) */
if (d->smd->vpid == 0x1fff) {
pthread_mutex_lock(&d->stream_mutex);
d->a_ops->volume(d, 1.0);
pthread_mutex_unlock(&d->stream_mutex);
}
d->playing = 1;
}
return 0;
}
if (0 == ret) {
SOFTMPEG_ERROR("stream playback unexpectedly ended! [%d/%d]\n",filled,total);
pthread_mutex_lock(&d->stream_mutex);
d->a_ops->pause(d, 0);
pthread_mutex_unlock(&d->stream_mutex);
return -1;
}
if (percent < AUDIO_BUFFER_LOW_MARK_P) {
SOFTMPEG_WARN("falling below low mark (%d/%d)\n", percent, AUDIO_BUFFER_LOW_MARK_P);
return 0;
}
pthread_mutex_lock(&d->stream_mutex);
ret = d->a_ops->get_delay(d, &delay);
pthread_mutex_unlock(&d->stream_mutex);
if (percent > AUDIO_BUFFER_HIGH_MARK_P) {
/* number of bytes we are above the mid mark */
ret = filled - ((AUDIO_BUFFER_SIZE * AUDIO_BUFFER_MID_MARK_P) / 10000);
/* ms */
ret = (1000 * ret) / d->config.sample_rate;
}
#if 0
AUDIO_DEBUG("playing [%d/%d], presentation delay: %d ms, delaying %d ms\n",
filled, total, delay, ret);
#endif
return ret;
}
int audio_flush_queue(struct audio_decoder *d)
{
struct audio_buffer *buf = NULL;
unsigned char *ptr = d->as.buf;
pthread_mutex_lock(&d->stream_mutex);
d->a_ops->flush(d);
pthread_mutex_unlock(&d->stream_mutex);
list_lock(&d->filled);
list_lock(&d->free);
while (!list_empty(&d->filled)) {
buf = (struct audio_buffer *) list_del_head(&d->filled);
buf->len = 0;
list_add_tail(buf, &d->free);
}
if (d->current != NULL) {
d->current->len = 0;
list_add_tail(d->current, &d->free);
}
d->current = (struct audio_buffer *) list_del_head(&d->free);
list_unlock(&d->free);
list_unlock(&d->filled);
/* this is horrible: erase our a/v sync informations, but make
sure nobody is currently using it */
pthread_mutex_lock(&d->smd->vdec->mutex);
memset(&d->as, 0, sizeof(struct audio_sync));
/* restore our buffer pointer */
d->as.buf = ptr;
pthread_mutex_unlock(&d->smd->vdec->mutex);
avdecoder_flush(d->codec, d->codec_ctx, 1);
d->playing = 0;
return 0;
}
int audio_manage(struct audio_decoder *d)
{
struct audio_buffer *buf = NULL;
struct list *fi, *fr;
int ret = 0;
fi = &d->filled;
fr = &d->free;
list_lock(fi);
if (list_empty(fi)) {
list_unlock(fi);
return 0;
}
while (fi->items > 1) {
buf = (struct audio_buffer *) list_del_head(fi);
list_unlock(fi);
ret = audio_playout(d, buf);
buf->len = 0;
list_lock(fr);
list_add_tail(buf, fr);
list_unlock(fr);
if (ret > 0) {
SOFTMPEG_WARN("providing audio data too fast, at least delay %d ms\n",ret);
break;
}
list_lock(fi);
}
buf = (struct audio_buffer *) list_get_head(fi);
#ifdef SOFTMPEG_DEBUG_AUDIO
fprintf(stderr,"a: pts == ");
printpts(stderr, buf->pts);
fprintf(stderr," @ sync pts: ");
printpts(stderr, d->as.sync_pts);
fprintf(stderr,"\n");
#endif
d->as.sync_pts = buf->pts;
list_unlock(fi);
return ret;
}
int check_and_write_audio_pes(struct audio_decoder *d)
{
int expect;
int ret = 0;
if (d->current == NULL) {
return 0;
}
d->as.pts_end = d->as.last_pts;
if (d->as.pts_end == d->as.pts_begin) {
/* this happens once after resynchronisation or when sync was lost */
AUDIO_PES("resync finished...\n");
goto out;
}
if (0 == d->as.pts_begin) {
SOFTMPEG_DEBUG("pts skew. no pts_begin\n");
goto out;
}
if (0 == d->as.pts_end) {
SOFTMPEG_DEBUG("pts skew. no pts_end\n");
goto out;
}
if (d->as.pts_end < d->as.pts_begin) {
SOFTMPEG_DEBUG("pts skew. pts_end is smaller than pts_begin: ");
printpts(stderr, d->as.pts_begin);
fprintf(stderr,"<=> ");
printpts(stderr, d->as.pts_end);
fprintf(stderr,"\n");
softmpeg_decoder_hard_resync(d->smd);
goto out;
}
if ((d->as.pts_end - d->as.pts_begin) > 90000) {
SOFTMPEG_DEBUG("pts skew. pts_begin and pts_end differ too much: ");
printpts(stderr, d->as.pts_begin);
fprintf(stderr, "<=> ");
printpts(stderr, d->as.pts_end);
fprintf(stderr, "\n");
softmpeg_decoder_hard_resync(d->smd);
goto out;
}
expect = (d->config.sample_rate * d->config.nb_channels * 2 * ((d->as.pts_end / 90) - (d->as.pts_begin / 90))) / 1000;
if (expect > d->current->len) {
AUDIO_DEBUG("broken audio data chunk. not enough data - inserting silence. (have: %d, expect:%d, diff:%d)\n", d->current->len, expect, d->current->len - expect);
memset(d->current->buf + d->current->len, 0x0, (expect - d->current->len));
d->current->len = expect;
}
if (expect < d->current->len) {
AUDIO_DEBUG("broken audio data chunk. too much data - deleting data (have: %d, expect:%d, diff:%d)\n", d->current->len, expect, d->current->len - expect);
d->current->len = expect;
}
#if 0
AUDIO_PES("as.pts_begin:");
printpts(stderr, d->as.pts_begin);
fprintf(stderr, ", as.pts_end: ");
printpts(stderr, d->as.pts_end);
fprintf(stderr, ", nas.last_pts: ");
printpts(stderr, d->as.last_pts);
fprintf(stderr, "\n");
#endif
d->current->pts = d->as.pts_end;
AUDIO_PES("writing %d bytes to audio device\n", d->current->len);
list_lock(&d->filled);
list_add_tail(d->current, &d->filled);
list_unlock(&d->filled);
if (!list_empty(&d->free)) {
d->current = (struct audio_buffer *) list_del_head(&d->free);
} else {
d->current = NULL;
}
ret = audio_manage(d);
out:
d->as.need_writeout = 0;
d->as.pts_begin = d->as.pts_end;
d->as.need_writeout = 0;
return ret;
}
int audio_handle_pes_data(struct audio_decoder *d, const unsigned char *payload, const unsigned int payload_len, int ptsdts_updated, uint64_t pts)
{
struct audio_config ac;
int new_frame_written = 0;
int delay = 0;
int ret = 0;
if (d->smd->mode == SOFTMPEG_PAUSE) {
AUDIO_DEBUG("stop dispatching data. paused\n");
return -1;
}
if (!d->current) {
SOFTMPEG_ERROR("no current! no decoding...\n");
return 0;
}
if (0 != ptsdts_updated) {
if (d->as.last_pts != pts) {
#ifdef AUDIO_PES_TO_FRAME_DEBUG
AUDIO_PES("found new pts: ");
printpts(stderr, pts);
fprintf(stderr,"\n");
#endif
d->as.last_pts = pts;
d->as.need_writeout = 1;
} else {
AUDIO_PES("pts already seen. skipping\n");
ptsdts_updated = 0;
}
if (0 == d->as.pts_begin) {
d->as.pts_begin = pts;
}
}
if (0 == d->as.pts_begin) {
AUDIO_PES("waiting for first audio pts...\n");
return 0;
}
AUDIO_PES("writing %d bytes to audio buf\n",payload_len);
memcpy(d->as.buf + d->as.len, payload, payload_len);
d->as.len += payload_len;
AUDIO_PES("now %d bytes in buffer\n", d->as.len);
if (d->as.len >= 4) {
if (0 == decode_header(&ac, d->as.buf[0] << 24 | d->as.buf[1] << 16 | d->as.buf[2] << 8 | d->as.buf[3])) {
AUDIO_PES("still in sync at beginning of buffer (%d,%d)\n",d->as.scanning_for_first_frame,d->as.synced_to_frame);
// AUDIO_DEBUG("sample_rate:%d, bit_rate:%d, channels:%d\n",ac.sample_rate, ac.bit_rate, ac.nb_channels);
} else {
AUDIO_PES("ouch lost sync!\n");
d->as.synced_to_frame = 0;
}
}
if (d->as.scanning_for_first_frame != 0 || d->as.synced_to_frame == 0) {
int i = 0;
for (i = 0; i < d->as.len - 3; i++) {
if (0 == decode_header(&ac, d->as.buf[i] << 24 | d->as.buf[i + 1] << 16 | d->as.buf[i + 2] << 8 | d->as.buf[i + 3])) {
break;
}
}
if (d->as.len - 3 == i) {
AUDIO_PES("no sync found\n");
d->as.len = 0;
return 0;
}
AUDIO_PES("found new sync @ %d\n", i);
if (d->as.scanning_for_first_frame != 0) {
d->as.scanning_for_first_frame = 0;
d->as.need_writeout = 0;
d->as.pts_begin = d->as.last_pts;
}
d->as.len -= i;
memmove(d->as.buf, d->as.buf + i, d->as.len);
d->as.synced_to_frame = 1;
/* if sync is lost, make sure that our pts' are properly synced */
d->as.pts_begin = d->as.pts_end = d->as.last_pts;
return 0;
}
if (ac.sample_rate != d->config.sample_rate || ac.nb_channels != d->config.nb_channels) {
int ret;
AUDIO_PES("audio configuration changed\n");
memcpy(&d->config, &ac, sizeof(ac));
audio_flush_queue(d);
pthread_mutex_lock(&d->stream_mutex);
ret = d->a_ops->reconfigure(d, AUDIO_BUFFER_SIZE, d->config.sample_rate, d->config.nb_channels);
pthread_mutex_unlock(&d->stream_mutex);
}
/* special case: new pts in this ts packet and a new mpeg frame is just beginning here */
if (d->as.len == payload_len && ptsdts_updated != 0) {
AUDIO_PES("special case: new mpeg audio frame at beginning of pes packet\n");
new_frame_written = 1;
}
if (0 != d->as.need_writeout && new_frame_written != 0) {
new_frame_written = 0;
delay = check_and_write_audio_pes(d);
}
if (d->as.len >= ac.frame_size) {
int chunk_len = 0;
int ret;
AUDIO_PES("writing %d bytes to avcodec\n", ac.frame_size);
ret = avcodec_decode_audio(d->codec_ctx, (int16_t *) (d->current->buf + d->current->len), &chunk_len, d->as.buf, ac.frame_size);
if (ac.frame_size == ret) {
if (0 != chunk_len) {
d->current->len += chunk_len;
} else {
SOFTMPEG_ERROR("mpeg audio frame decoding failed\n");
}
} else {
SOFTMPEG_ERROR("some error while mpeg audio frame decoding\n");
}
AUDIO_PES("current audio buf has %d samples\n", d->current->len);
d->as.len -= ac.frame_size;
AUDIO_PES("moving %d bytes to front \n", d->as.len);
if (0 != d->as.len) {
memmove(d->as.buf, d->as.buf + ac.frame_size, d->as.len);
}
new_frame_written = 1;
}
if (0 != d->as.need_writeout && new_frame_written != 0) {
new_frame_written = 0;
ret = check_and_write_audio_pes(d);
if (ret > delay) {
delay = ret;
}
}
if (d->as.len >= ac.frame_size) {
int ret;
int i = 0;
unsigned char *ptr = d->as.buf;
while (d->as.len > ac.frame_size) {
int chunk_len = 0;
AUDIO_PES("writing %d bytes to avcodec\n", ac.frame_size);
ret = avcodec_decode_audio(d->codec_ctx, (int16_t *) (d->current->buf + d->current->len), &chunk_len, ptr, ac.frame_size);
if (ac.frame_size == ret) {
if (0 != chunk_len) {
d->current->len += chunk_len;
} else {
SOFTMPEG_ERROR("mpeg audio frame decoding failed\n");
}
} else {
SOFTMPEG_ERROR("some error while mpeg audio frame decoding\n");
}
ptr += ac.frame_size;
d->as.len -= ac.frame_size;
AUDIO_PES("current audio buf has %d samples, pes store has %d bytes\n", d->current->len,d->as.len);
i++;
}
AUDIO_PES("moving %d bytes to front \n", d->as.len);
if (0 != d->as.len) {
memmove(d->as.buf, d->as.buf + (i*ac.frame_size), d->as.len);
}
}
return delay;
}
LinuxTV legacy CVS <linuxtv.org/cvs>