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>