File:  [DVB] / libsoftmpeg / src / softmpeg.c
Revision 1.36: download - view: text, annotated - select for diffs
Mon Jul 12 16:48:05 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "internal.h"
#include "audio.h"
#include "video.h"

//#define SOFTMPEG_DEBUG_FEED

#ifdef SOFTMPEG_DEBUG_FEED
#define FEED_DEBUG(x...)  SOFTMPEG_DEBUG("Feed: "x)
#else
#define FEED_DEBUG(x...)  do {} while (0)
#endif

//#define SOFTMPEG_DEBUG_AVSYNC

#ifdef SOFTMPEG_DEBUG_AVSYNC
#define AVSYNC_DEBUG(x...)  SOFTMPEG_DEBUG("Sync: "x)
#else
#define AVSYNC_DEBUG(x...)  do {} while (0)
#endif

static struct timeval start_time = { 0, 0 };

long long softmpeg_get_micros()
{
	struct timeval tv;

	if (start_time.tv_sec == 0) {
		gettimeofday( &start_time, NULL );
		return 0;
	}

	gettimeofday( &tv, NULL );

	return(long long)(tv.tv_sec - start_time.tv_sec) * (long long) 1000000 +
	(long long)(tv.tv_usec - start_time.tv_usec);
}

static inline unsigned short ts_pid(const unsigned char *ts_packet)
{
	return((ts_packet[1] & 0x1f) << 8) + ts_packet[2];
}

void avdecoder_flush(AVCodec * codec, AVCodecContext * codec_ctx, int used)
{
	/* don't flush any buffers if the decoder hasn't been used at all
	   (is ff_mpeg_flush() in mpegvideo.c in libavcodec buggy? ) */
	if (0 == used)
		return;

	avcodec_flush_buffers(codec_ctx);
	avcodec_close(codec_ctx);

	if (avcodec_open(codec_ctx, codec) < 0) {
		SOFTMPEG_FATAL("could not re-open video/audio codec\n");
		/* doesn't come back */
	}
}

#define NUMBER_OF_SAMPLES 60
#define TIME_BETWEEN_SAMPLES 2000

static void *avsync_thread(void *data)
{
	struct softmpeg_decoder *d = (struct softmpeg_decoder *) data;
        sigset_t signals;

	int marks[TIME_BETWEEN_SAMPLES];
	int ptr = 0;
	int initial_samples = 0;
	struct timeval begin;

	int total;
	int playing;
	int ret;
	
        /* we don't want to catch any signals */
        sigfillset( &signals );
        if (pthread_sigmask( SIG_BLOCK, &signals, NULL )) {
                SOFTMPEG_WARN("warning: blocking signals for display_thread failed.\n");
        }

	AVSYNC_DEBUG("avsync_thread started\n");

	d->avsync_thread_init = 1;

	while (0 == d->cancel_threads) {
		softmpeg_sleep(TIME_BETWEEN_SAMPLES);

		pthread_mutex_lock(&d->avsync_mutex);
		
		if (d->avsync_signal == 0) {
			pthread_mutex_unlock(&d->avsync_mutex);
			AVSYNC_DEBUG("nothing is decoded currently\n");
			continue;
		}
		if (d->avsync_signal == 2) {
			if (gettimeofday(&begin, NULL) < 0) {
				pthread_mutex_unlock(&d->avsync_mutex);
				AVSYNC_DEBUG("gettimeofday() failed. resetting.\n");
				continue;
			}
			d->avsync_signal = 1;
			initial_samples = 0;
		}
		
		pthread_mutex_lock(&d->adec->stream_mutex);
		ret = d->adec->a_ops->get_status(d->adec,&marks[ptr], &total, &playing);
		pthread_mutex_unlock(&d->adec->stream_mutex);
		
		if (0 != ret || playing == 0) {
			d->avsync_signal = 2;
			pthread_mutex_unlock(&d->avsync_mutex);
			AVSYNC_DEBUG("no audio stream available. resetting.\n");
			continue;
		}
		
		pthread_mutex_unlock(&d->avsync_mutex);

		ptr++;	
		if (ptr >= NUMBER_OF_SAMPLES) {
			int i = 0;
			int median = 0;
			ptr = 0;
			for (i = 0; i < NUMBER_OF_SAMPLES; i++) {
				median += marks[i];
			}
			median /= NUMBER_OF_SAMPLES;
			AVSYNC_DEBUG("median with %d samples: %d\n", NUMBER_OF_SAMPLES, median);
		}

		initial_samples++;
		if (initial_samples > NUMBER_OF_SAMPLES) {
		}
	}

	return NULL;
}

int softmpeg_decoder_set_av_sync(struct softmpeg_decoder *d, int enable)
{
	if (NULL == d) {
		return EINVAL;
	}
	if (NULL == d->vdec) {
		return EINVAL;
	}

	d->vdec->av_sync_disable = !enable;
	
	return 0;	
}

static struct softmpeg_decoder *singleton = NULL;

struct softmpeg_decoder *softmpeg_get_current_softmpeg_context(void)
{
	return singleton;
}

struct softmpeg_decoder *softmpeg_decoder_create(void *priv, struct softmpeg_callbacks *cbs,
	enum softmpeg_video_output vo, void *v_init_data,
	enum softmpeg_audio_output ao, void *a_init_data)
{
	struct softmpeg_decoder *d = NULL;
	
	if (singleton != NULL) {
		SOFTMPEG_ERROR("cannot init softmpeg twice\n");
		return NULL;
	}
	
	singleton = calloc(1, sizeof(*d));
	if (NULL == singleton) {
		SOFTMPEG_ERROR("out of memory\n");
		return NULL;
	}
	d = singleton;

	d->feed_fd = -1;
	d->mode = SOFTMPEG_PLAY;
	d->cbs = cbs;
	
	d->vdec = video_decoder_create(priv, d, vo, v_init_data);
	if (NULL == d->vdec) {
		SOFTMPEG_ERROR("ocreating video decoder instance failed\n");
		free(d);
		return NULL;
	}
	d->vpid = 0x1fff;

	d->adec = audio_decoder_create(priv, d, ao, a_init_data);
	if (NULL == d->adec) {
		SOFTMPEG_ERROR("ocreating audio decoder instance failed\n");
		/* fixme: release video decoder instance */
		free(d);
		return NULL;
	}
	d->apid = 0x1fff;

	pthread_mutex_init(&d->avsync_mutex, NULL);
	pthread_mutex_init(&d->feed_mutex, NULL);

	pthread_mutex_lock(&d->avsync_mutex);
	d->avsync_signal = 0; /* stop */
	pthread_mutex_unlock(&d->avsync_mutex);

	/* create the long term avsync thread */
	pthread_attr_init(&d->avsync_thread_attr);
	pthread_attr_setdetachstate(&d->avsync_thread_attr, PTHREAD_CREATE_JOINABLE);
	pthread_attr_setinheritsched(&d->avsync_thread_attr, PTHREAD_EXPLICIT_SCHED);
	if (0 != pthread_create(&d->avsync_thread, &d->avsync_thread_attr, avsync_thread, d)) {
		/* fixme: cleanup properly */
		return NULL;
	}

	while (!d->avsync_thread_init)
		sched_yield();

	d->pcrpid = 0x1fff;

	d->priv = priv;
	return d;
}

int softmpeg_decoder_stop(struct softmpeg_decoder *d)
{
	d->vpid = 0x1fff;
	d->apid = 0x1fff;
	d->pcrpid = 0x1fff;

	audio_flush_queue(d->adec);
	video_flush_queue(d->vdec);
	
	/* for a better visual experience, clear the screen */
	d->vdec->v_ops->clear(d->vdec);

	return 0;
}

/* fd must be -1 if it should be ignored */
int softmpeg_decoder_set_pids(struct softmpeg_decoder *d, unsigned int vpid, unsigned int apid, unsigned int pcrpid, int fd)
{
	SOFTMPEG_DEBUG("%s(): vpid:0x%4x, apid:0x%4x, pcrpid:0x%4x\n", __FUNCTION__, vpid, apid, pcrpid);

	pthread_mutex_lock(&d->feed_mutex);

	softmpeg_decoder_set_mode(d, SOFTMPEG_PLAY, 0);
	softmpeg_decoder_stop(d);
	
	d->vpid = vpid;
	d->apid = apid;
	/* fixme: pcr pid handling */
	d->pcrpid = pcrpid;

	pthread_mutex_lock(&d->avsync_mutex);
	d->avsync_signal = 2; /* restart */
	pthread_mutex_unlock(&d->avsync_mutex);

	if (fd != -1) {
		fcntl(fd, F_SETFL, O_NONBLOCK);
		d->feed_fd = fd;
	}
	pthread_mutex_unlock(&d->feed_mutex);

	return 0;
}

int softmpeg_decoder_hard_resync(struct softmpeg_decoder *d)
{
	return softmpeg_decoder_set_pids(d, d->vpid, d->apid, d->pcrpid, -1);
}


int softmpeg_decoder_audio_pid_change(struct softmpeg_decoder *d, unsigned int apid)
{
	SOFTMPEG_DEBUG("%s(): new audio pid: 0x%4x\n",__FUNCTION__,apid);

	if (apid == 0x1fff) {
		/* fixme: disable audio decoder? */
	}
	
	/* fixme: avoid hard loss of sync? */

	d->apid = apid;

	pthread_mutex_lock(&d->avsync_mutex);
	d->avsync_signal = 2; /* restart */
	pthread_mutex_unlock(&d->avsync_mutex);

	return 0;
}

int softmpeg_decoder_destroy(struct softmpeg_decoder *d)
{
	softmpeg_decoder_stop(d);

	video_decoder_destroy(d->vdec);
	audio_decoder_destroy(d->adec);
	
	d->cancel_threads = 1;
        SOFTMPEG_DEBUG("waiting for threads to join in\n");
        pthread_join(d->feed_thread, NULL);
        pthread_join(d->avsync_thread, NULL);
        SOFTMPEG_DEBUG("threads joined in\n");
	
	return 0;
}

static inline int parse_pcr(const unsigned char *ts_packet, uint64_t * pcr_base, uint16_t * pcr_extension)
{
	unsigned char PCR_flag;

	PCR_flag = ts_packet[5] & 0x10;

	if (!PCR_flag) {
		return -1;
	}

	*pcr_base = (((uint64_t) ts_packet[6] & 0xff) << 25) | (((uint64_t) ts_packet[7] & 0xff) << 17) | (((uint64_t) ts_packet[8] & 0xff) << 9) | (((uint64_t) ts_packet[9] & 0xff) << 1) | (((uint64_t) ts_packet[10] & 0x80) >> 7);

	*pcr_extension = (((uint16_t) ts_packet[10] & 0x01) << 8) | (ts_packet[11] & 0xff);

	return 0;
}

static inline int has_adaption_field(const unsigned char *ts_packet)
{
	unsigned int adaption_field_control;
	unsigned char adaption_field_length;

	adaption_field_control = (ts_packet[3] >> 4) & 0x03;

	if (adaption_field_control != 0x02 && adaption_field_control != 0x03)
		return 0;

	adaption_field_length = ts_packet[4];

	if (adaption_field_length == 0)
		return 0;

	return 1;
}

/* returns the amount of time in milliseconds the calling thread should delay further processing of
data because the internal buffers are starting to overflow */
int softmpeg_decoder_process_pes_data(struct softmpeg_decoder *d, unsigned char *buf, ssize_t count)
{
	int ret_a = 0, ret_v = 0;
	int ret;
	uint64_t pts;
	uint64_t dts;
	unsigned int PTS_DTS_flags;
	int delay = 0;
	
	while (count > 0) {
		int header_length = 9;
		int pes_packet_len = 0;
		int ptsdts_updated = 0;
		
		/* fix: min size of pes header */
		if (count < 9) {
			return 0;
		}

		if (!(ret = is_pes_packet_start(buf))) {
			buf++;
			count--;
			continue;
		}

		pes_packet_len = ((buf[4] << 8) | buf[5])+6;
		
		PTS_DTS_flags = (buf[7] >> 6) & 0x03;

		if (PTS_DTS_flags == 0x02 || PTS_DTS_flags == 0x03) {
			decode_ptsdts(buf + 9, &pts);

			if (PTS_DTS_flags == 0x03)
				decode_ptsdts(buf + 14, &dts);
			
			ptsdts_updated = 1;
		}

		header_length += buf[8];

		if (0xe0 == (ret & 0xf0)) {
		 	ret_v = video_handle_pes_data(d->vdec, buf+header_length, pes_packet_len-header_length, ptsdts_updated, pts);
		} else if (0xc0 == (ret & 0xe0)) {
		 	ret_a = audio_handle_pes_data(d->adec, buf+header_length, pes_packet_len-header_length, ptsdts_updated, pts);
		} else {
		 	SOFTMPEG_WARN("unknown pes packet type: 0x%2x\n",ret);
			buf++;
			count--;
			continue;
		}
		
		if (ret_a > delay)
			delay = ret_a;
		if (ret_v > delay)
			delay = ret_v;

		buf += pes_packet_len;
		count -= pes_packet_len;
	}
	return delay;
}

/* we expect count to be a multiple of 188 */
int softmpeg_decoder_process_ts_data(struct softmpeg_decoder *d, unsigned char *buf, ssize_t count)
{
	int ret = 0, ret_a = 0, ret_v = 0;
	unsigned short pid;

	unsigned char *payload;
	int payload_len;
	int ptsdts_updated;

	while (count > 0) {
		uint64_t pts = 0;
		uint64_t dts = 0;

		if (buf[0] != 0x47) {
			buf++;
			count--;
			continue;
		}
		
		pid = ts_pid(buf);
		payload_len = payload_length(buf);
		payload = buf + 188 - payload_len;

		ptsdts_updated = update_ptsdts(buf, &pts, &dts);
		if (0 != ptsdts_updated) {
			int header_length = 9 + payload[8];
			payload += header_length;
			payload_len -= header_length;
		}

		if (payload_len <= 0 || payload_len > 184) {
			goto out;
		}

		if (pid == d->apid) {
			while(1) {
				ret = audio_handle_pes_data(d->adec, payload, payload_len, ptsdts_updated, pts);
				if (ret != -1) {
					break;
				}
				SOFTMPEG_DEBUG("delaying video data delivery\n");
				softmpeg_sleep(40);
			}
			if (ret > ret_a) {
				ret_a = ret;
			}
		} else if (pid == d->vpid) {
			while(1) {
				ret = video_handle_pes_data(d->vdec, payload, payload_len, ptsdts_updated, pts);
				if (ret != -1) {
					break;
				}
				SOFTMPEG_DEBUG("delaying video data delivery\n");
				softmpeg_sleep(40);
			}
			if (ret > ret_v) {
				ret_v = ret;
			}
		}
		if (pid == d->pcrpid) {
			/* pcr_handle_packet(buf); */
		}

out:
		buf += 188;
		count -= 188;
	}

	if (ret_a && ret_v)
		return(ret_a < ret_v) ? ret_a : ret_v;

	return(ret_a > ret_v) ? ret_a : ret_v;
}

static inline int pusi_is_set(const unsigned char *ts_packet)
{
	return(ts_packet[1] & 0x40);
}

int update_ptsdts(const unsigned char *ts_packet, uint64_t * pts, uint64_t * dts)
{
	const unsigned char *payload;
	unsigned int PTS_DTS_flags;
	unsigned int payload_len;

	if (!pusi_is_set(ts_packet)) {
		return 0;
	}

	payload_len = payload_length(ts_packet);
	payload = ts_packet + 188 - payload_len;

	if (!is_pes_packet_start(payload))
		return 0;

	PTS_DTS_flags = (payload[7] >> 6) & 0x03;

	if (PTS_DTS_flags == 0x02 || PTS_DTS_flags == 0x03) {
		decode_ptsdts(payload + 9, pts);

		if (PTS_DTS_flags == 0x03)
			decode_ptsdts(payload + 14, dts);

		return 1;
	}

	return 0;
}

void init_avcodec_stuff(void)
{
	static int initialized = 0;

	if (!initialized++) {
		avcodec_init();
		avcodec_register_all();
	}
}

static void *feed_thread(void *data)
{
	struct softmpeg_decoder *d = (struct softmpeg_decoder *) data;
	char buf[188 * 256];
	int delay = 40;
	ssize_t count;
	
        sigset_t signals;

        /* we don't want to catch any signals */
        sigfillset( &signals );
        if (pthread_sigmask( SIG_BLOCK, &signals, NULL )) {
                SOFTMPEG_WARN("warning: blocking signals for display_thread failed.\n");
        }

	SOFTMPEG_DEBUG("feed_thread started\n");

	d->feed_thread_init = 1;

	while (0 == d->cancel_threads) {
	
		pthread_mutex_lock(&d->feed_mutex);
		count = read(d->feed_fd, buf, sizeof(buf));
		pthread_mutex_unlock(&d->feed_mutex);

		switch (count) {
			case 0:
			case EAGAIN: 
			case -1: {
				delay = 100;
				break;
			}
			default: {
				FEED_DEBUG("got %d bytes\n", count);
				delay = softmpeg_decoder_process_ts_data(d, buf, count);
				if (0 != d->cbs && 0 != d->cbs->data) {
					d->cbs->data(d->priv, buf, count);
				}
				break;
			}
		}

		if (delay > 0) {
			FEED_DEBUG("sleeping %d ms...\n", delay);
			softmpeg_sleep( delay );
		}
	}

	d->feed_thread_init = 0;
	return NULL;
}

enum softmpeg_mode softmpeg_decoder_set_mode(struct softmpeg_decoder *d, enum softmpeg_mode mode, float arg)
{
	int ret1 = 0, ret2 = 0;
	int ret = 0;
	
	if (mode == d->mode) {
		return d->mode;
	}
	
	switch (mode) {
		case SOFTMPEG_PAUSE: {
			SOFTMPEG_DEBUG("SOFTMPEG_PAUSE.\n");
			ret1 = audio_set_pause(d->adec, 1);
			ret2 = video_set_pause(d->vdec, 1);
			break;
		}
		case SOFTMPEG_PLAY: {
			SOFTMPEG_DEBUG("SOFTMPEG_PLAY.\n");
			ret1 = audio_set_pause(d->adec, 0);
			ret2 = video_set_pause(d->vdec, 0);
			break;
		}
		case SOFTMPEG_SKIP: {
			struct stat buf;
			long long skip;
			SOFTMPEG_DEBUG("SOFTMPEG_SKIP: %f seconds\n", arg);
			mode = SOFTMPEG_PLAY;
			pthread_mutex_lock(&d->feed_mutex);
			if (-1 == fstat(d->feed_fd, &buf)) {
				break;
			}
			if (0 == S_ISREG(buf.st_mode)) {
				break;
			}
			if (arg > 60.0)
				arg = 60.0;
			if (arg < -60.0 )
				arg = -60.0;
			skip = (((long long)(arg * 524288.0)) / 188)*188;
			SOFTMPEG_DEBUG("seeking %lld bytes\n",skip);
			audio_flush_queue(d->adec);
			video_flush_queue(d->vdec);
			ret = lseek(d->feed_fd, (off_t)skip, SEEK_CUR);
			if (ret == -1 && errno == EINVAL) {
				SOFTMPEG_DEBUG("jumping to beginning of file\n");
				lseek(d->feed_fd, 0, SEEK_SET);
			}
			SOFTMPEG_DEBUG("ret: %d\n",ret);
			pthread_mutex_unlock(&d->feed_mutex);
			break;
		}
		case SOFTMPEG_BACKWARD: 
		case SOFTMPEG_REVERSE:
		case SOFTMPEG_FORWARD:
		default: {
			return d->mode;
		}
	}

	if (ret1 != ret2) {
		d->mode = SOFTMPEG_PLAY;
		SOFTMPEG_DEBUG("video and audio disagree about the state.\n");
		audio_set_pause(d->adec, d->mode);
		video_set_pause(d->vdec, d->mode);
	}

	d->mode = mode;
	return d->mode;
}

int softmpeg_decoder_polling_thread_create(struct softmpeg_decoder *d, int fd)
{
	struct sched_param param;
	int policy;
	int ret;

	if (0 != d->feed_thread_init) {
		SOFTMPEG_DEBUG("feed thread already running.\n");
		return 0;
	}

	if (fd != -1) {
		fcntl(fd, F_SETFL, O_NONBLOCK);
	}

	d->feed_fd = fd;

	/* create the decode thread */
	pthread_attr_init(&d->feed_thread_attr);
	pthread_attr_setdetachstate(&d->feed_thread_attr, PTHREAD_CREATE_JOINABLE);
	pthread_attr_setinheritsched(&d->feed_thread_attr, PTHREAD_EXPLICIT_SCHED);
	if (0 != pthread_create(&d->feed_thread, &d->feed_thread_attr, feed_thread, d)) {
		return -1;
	}

	while (!d->feed_thread_init)
		sched_yield();

	if (0 == (ret = pthread_getschedparam(d->feed_thread, &policy, &param))) {
		param.sched_priority = 7;
		if (0 != (ret = pthread_setschedparam(d->feed_thread, SCHED_RR, &param))) {
			SOFTMPEG_WARN("warning: could not set polling thread to realtime priority.\n");
		}
	} else {
		SOFTMPEG_WARN("warning: could not set polling thread to realtime priority.\n");
	}

	return 0;
}


LinuxTV legacy CVS <linuxtv.org/cvs>