File:  [DVB] / libsoftmpeg / src / video.c
Revision 1.35: 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 "video.h"

//#define VIDEO_PES_TO_FRAME_DEBUG

#ifdef VIDEO_PES_TO_FRAME_DEBUG
#define VIDEO_PES(x...) SOFTMPEG_DEBUG("Video PES: "x)
#else
#define VIDEO_PES(...) do {} while (0)
#endif

#define MAX_VIDEO_MPEG_FRAME_SIZE (1024 * 128)

int video_set_pause(struct video_decoder *d, int paused)
{
	VIDEO_DEBUG("pause:%d\n",paused);
	return paused;
#if 0
	if (d->display_thread_init == 0 && paused != 0) {
		VIDEO_DEBUG("display thread not available.\n");
		return 0;
	}
	if (paused != 0) {
		pthread_mutex_lock(&d->backbuffer_mutex);
		VIDEO_DEBUG("pause playback.\n");
		return 1;
	}

	pthread_mutex_unlock(&d->backbuffer_mutex);
	VIDEO_DEBUG("continue playback.\n");
#endif
	return 0;
}

static void *decode_thread(void *data);
static void *display_thread(void *data);

static void set_initial_sync(struct video_decoder *d, int is_synced);

int video_decoder_destroy(struct video_decoder *d)
{
	d->cancel_threads = 1;
	VIDEO_DEBUG("waiting for threads to join in\n");
	pthread_join(d->display_thread, NULL);
	pthread_join(d->decode_thread, NULL);
	VIDEO_DEBUG("threads joined in\n");
	return 0;
}

extern struct video_operations v_directfb_ops;
extern struct video_codec avcodec_ops;

struct video_decoder *video_decoder_create(void *priv, struct softmpeg_decoder *dec,
	enum softmpeg_video_output vo, void *v_init_data)
{
	struct sched_param param;
	int policy;
	struct video_decoder *d;
	int i = 0;
	int ret;

	d = calloc(1, sizeof(*d));
	if (NULL == d) {
		SOFTMPEG_ERROR("not enough memory\n");
		return NULL;
	}
	d->smd = dec;

	switch (vo) {
		case SOFTMPEG_DIRECTFB: {
			d->v_ops = &v_directfb_ops;
			break;
		}
		default: {
			SOFTMPEG_ERROR("unsupported video output device.\n");
			free(d);
			return NULL;
		}
	}
	
	if (0 != d->v_ops->init(d, v_init_data,&d->v_dura)) {
		SOFTMPEG_ERROR("initialization of video output device failed.\n");
		free(d);
		return NULL;
	}

	if (d->v_dura != 20) {
		SOFTMPEG_ERROR("********************************************************** \n");
		SOFTMPEG_ERROR("* your vertical refresh rate is %3d Hz, while 50 Hz is   * \n",(1000/d->v_dura));
		SOFTMPEG_ERROR("* the natural refresh rate of the video material. have a * \n");
		SOFTMPEG_ERROR("* look at the docs to learn to set a correct fb mode.    * \n");
		SOFTMPEG_ERROR("* libsoftmpeg does it's best to assure a/v sync anyway   * \n");
		SOFTMPEG_ERROR("* but don't complain about jerky video now! ;-)          * \n");
		SOFTMPEG_ERROR("********************************************************** \n");		
		softmpeg_sleep(5000);
	} else {
	}

	/* fixme: support different decoders here */
	d->v_codec = &avcodec_ops;

	if (0 != d->v_codec->init(d)) {
		goto error;
	}

	list_init(&d->free, offsetof(struct video_buffer, node));
	list_init(&d->filled, offsetof(struct video_buffer, node));

	for (i = 0; i < NUMBER_OF_VIDEO_BUFFERS; i++) {
		struct video_buffer *obuf = &d->output[i];
		obuf->buf = (unsigned char *) malloc(MAX_VIDEO_MPEG_FRAME_SIZE);
		if (NULL == obuf->buf) {
			/* fixme: release decoder + context, frames */
			goto error;
		}
		obuf->len = 0;
		list_add_tail(obuf, &d->free);
	}

	/* make first item current */
	d->current = (struct video_buffer *) list_del_head(&d->free);

	pthread_mutex_init(&d->mutex, NULL);
	pthread_mutex_init(&d->backbuffer_mutex, NULL);
	pthread_cond_init(&d->backbuffer_cond, NULL);

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

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

	if (0 == (ret = pthread_getschedparam(d->decode_thread, &policy, &param))) {
		param.sched_priority = 8;
		if (0 != (ret = pthread_setschedparam(d->decode_thread, SCHED_RR, &param))) {
			SOFTMPEG_WARN("could not set decoder thread to realtime priority. video might be jerky!\n");
		}
	} else {
		SOFTMPEG_WARN("could not set decoder thread to realtime priority. video might be jerky!\n");
	}

	/* create the display thread */
	pthread_attr_init(&d->display_thread_attr);
	pthread_attr_setdetachstate(&d->display_thread_attr, PTHREAD_CREATE_JOINABLE);
	pthread_attr_setinheritsched(&d->display_thread_attr, PTHREAD_EXPLICIT_SCHED);
	if (0 != pthread_create(&d->display_thread, &d->display_thread_attr, display_thread, d)) {
		return NULL;
	}

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

	if (0 == (ret = pthread_getschedparam(d->display_thread, &policy, &param))) {
		param.sched_priority = 9;
		if (0 != (ret = pthread_setschedparam(d->display_thread, SCHED_RR, &param))) {
			SOFTMPEG_WARN("could not set display thread to realtime priority. video might be jerky!\n");
		}
	} else {
		SOFTMPEG_WARN("could not set display thread to realtime priority. video might be jerky!\n");
	}

	d->priv = priv;
	return d;

	error:
	/* lot of error handling missing */
	return NULL;
}

/* return the 8 bit start code value and update the search
   state. Return -1 if no start code found */
static uint32_t find_start_code(const unsigned char* buf, int len, int *offset)
{
	int i;
	uint32_t val;
	if (len < 4) {
		return -1;
	}
	*offset = 0;
	val = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];

	for (i = 4; i < len; i++) {
		if (val == 0x00000100)
			break;
		val = (val << 8) | buf[i];
		(*offset)++;
	}
	if (i == len) {
		return -1;
	}

	return val;
}

int video_flush_queue(struct video_decoder *d)
{
	struct video_buffer *buf = NULL;

	pthread_mutex_lock(&d->mutex);

	pthread_mutex_lock(&d->backbuffer_mutex);
	d->backbuffer_filled = 0;
	pthread_mutex_unlock(&d->backbuffer_mutex);

	list_lock(&d->filled);
	list_lock(&d->free);

#if 0
	dump_list(&d->filled, "filled");
	dump_list(&d->free, "free");
#endif

	while (!list_empty(&d->filled)) {
		buf = (struct video_buffer *) list_del_head(&d->filled);
		list_add_tail(buf, &d->free);
	}
	if (d->current != NULL) {
		list_add_tail(d->current, &d->free);
	}
	d->current = (struct video_buffer *) list_del_head(&d->free);
#if 0
	dump_list(&d->filled, "filled");
	dump_list(&d->free, "free");
#endif
	list_unlock(&d->free);
	list_unlock(&d->filled);

	set_initial_sync(d, 0);

	d->v_codec->flush(d);

	pthread_mutex_unlock(&d->mutex);

	return 0;
}


#define VIDEO_THRESHOLD 5

int video_handle_pes_data(struct video_decoder *d, const unsigned char *payload, const unsigned int payload_len, int ptsdts_updated, uint64_t pts)
{
	int ret = 0;
	int frame_complete = -1;
	uint32_t sc;

	if (d->smd->mode == SOFTMPEG_PAUSE) {
		VIDEO_DEBUG("stop dispatching data. paused.\n");
		return -1;
	}

	if (0 != ptsdts_updated) {
		d->l_pts = pts;
	}

	if (d->filled.items > NUMBER_OF_VIDEO_BUFFERS-VIDEO_THRESHOLD) {
		int filled, total, playing;
		SOFTMPEG_WARN("video buffer queue rather full. delay furthing data delivery\n");

		pthread_mutex_lock(&d->smd->adec->stream_mutex);
		ret = d->smd->adec->a_ops->get_status(d->smd->adec,&filled, &total, &playing);
		pthread_mutex_unlock(&d->smd->adec->stream_mutex);
		SOFTMPEG_WARN("(v [%2d/%2d], a [%5d/%5d]), ", d->free.items, d->filled.items, filled, total);
		ret = 40;		
	}

	if (NULL == d->current) {
		list_lock(&d->free);
		if (!list_empty(&d->free)) {
			d->current = (struct video_buffer *) list_del_head(&d->free);
			d->current->len = 0;
			d->current->pts = 0;
			list_unlock(&d->free);
			d->synced_to_frame = 0;
		} else {
			list_unlock(&d->free);
			VIDEO_DEBUG("no v current! (fr:%d/fi:%d)\n", d->free.items, d->filled.items);
			/* fixme: replace 40 with time derived from frame rate! */
			return VIDEO_THRESHOLD*40;
		}
	}

	/* fixme: find new picture start code in payload */
	if (d->synced_to_frame != 0) {
		if (-1 != (sc = find_start_code(payload, payload_len, &frame_complete))) {
			int type = ((payload[frame_complete + 5] >> 3) & 0x3);
			/* need to keep the start code */
			VIDEO_PES("new psc 0x%08x found in payload @ %d, type: 0x%2x\n", sc, frame_complete, type);
			if (type == 0x1 && 0 != ptsdts_updated) {
				VIDEO_PES("syncing: set last pts to pts of this iframe\n");
				d->current->pts = d->l_pts;
			}
		} else {
			frame_complete = -1;
		}
	}

	/* if found, we have one complete frame we need to remember and send
	   to the decoder */
	// VIDEO_PES("writing %d bytes to video buf, %d bytes in buffer\n", payload_len, d->current->len);
	dfb_memcpy(d->current->buf + d->current->len, payload, payload_len);
	d->current->len += payload_len;

	if (d->current->len >= 4 && d->synced_to_frame != 0) {
		int offset = 0;
		if (-1 != find_start_code(d->current->buf, d->current->len, &offset)) {
			// VIDEO_PES("still in sync at beginning of buffer\n");
		} else {
			VIDEO_PES("ouch lost sync!\n");
			d->synced_to_frame = 0;
		}
	}

	if (d->synced_to_frame == 0) {
		int i = 0;
		if (-1 == find_start_code(d->current->buf, d->current->len, &i)) {
			VIDEO_PES("no sync found in this pes packet\n");
			d->current->len = 0;
			return ret;
		}
		VIDEO_PES("found new sync @ %d\n", i);
		d->current->len -= i;
		memmove(d->current->buf, d->current->buf + i, d->current->len);
		d->synced_to_frame = 1;
	}

	if (-1 != frame_complete) {
		struct video_buffer *buf = NULL;
		int chunk_len = (d->current->len - payload_len) + frame_complete;
		VIDEO_PES("buffer %p with %d bytes filled, adding to filled list, items:%d\n", d->current, chunk_len, d->filled.items);

		list_lock(&d->free);
		if (!list_empty(&d->free)) {
			buf = (struct video_buffer *) list_del_head(&d->free);
		}
		list_unlock(&d->free);

		if (NULL != buf) {
			buf->len = d->current->len - chunk_len;
			VIDEO_PES("moving %d bytes to front of new buffer\n", buf->len);
			dfb_memcpy(buf->buf, d->current->buf + chunk_len, buf->len);
			buf->pts = 0;
		} else {
			VIDEO_PES("no more empty buffers. \n");
		}

		d->current->len = chunk_len;
		list_lock(&d->filled);
		list_add_tail(d->current, &d->filled);
		list_unlock(&d->filled);

		d->current = buf;
	}
	return ret;
}

static uint64_t get_host_ticks90kHz(void)
{
	uint64_t clockval90kHz;
	struct timeval tv;

	if (gettimeofday(&tv, NULL) < 0) {
		perror("gettimeofday() failed");
		return 0;
	}

	clockval90kHz = tv.tv_sec * 90000;
	clockval90kHz += (tv.tv_usec * 9) / 100;

	return(clockval90kHz & 0x00000001ffffffffULL);
}

static void set_initial_sync(struct video_decoder *d, int is_synced)
{
	float volume = 0.0;

	if (is_synced == d->initial_sync) {
		return;
	}
	
	if (0 != is_synced) {
		volume = 1.0;
	}
	
	audio_set_volume(d->smd->adec, volume);
	d->initial_sync = is_synced;
}


static void *decode_thread(void *data)
{
	struct video_decoder *d = (struct video_decoder *) data;
	struct video_buffer *current = NULL;
	struct video_frame sm_frame;
	struct list *fi, *fr;
	int ret;
	int ready;
	
	uint64_t i_pts = 0;	/* pts of last iframe */
	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 decode_thread failed.\n");
	}

	VIDEO_DEBUG("decode_thread started, d:%p.\n", d);
	d->decode_thread_init = 1;

	d->initial_sync = 0;

	fi = &d->filled;
	fr = &d->free;

	while (0 == d->cancel_threads) {
		struct av_sync av;

		pthread_mutex_lock(&d->mutex);
		list_lock(fi);
		if (fi->items < 1 || (0 != d->av_sync_disable && fi->items < VIDEO_THRESHOLD)) {
			/* no buffers available */
			set_initial_sync(d, 0);

			list_unlock(fi);
			pthread_mutex_unlock(&d->mutex);

			softmpeg_sleep(40);

			continue;
		}

		current = (struct video_buffer *) list_del_head(fi);
		list_unlock(fi);

		again:
		if (current->pts != 0) {
			i_pts = current->pts;
		} else {
			i_pts += 90 * 40;
			current->pts = i_pts;
		}

		// VIDEO_DEBUG("putting %p to avdecoder, len:%d (fr:%d, fi:%d)\n", current, current->len, fr->items, fi->items);
		d->v_codec->decode(d, current, &sm_frame, &ready);
		if (0 == ready) {
			goto out;
		}
	
		if (0 != d->av_sync_disable) {
			d->sync_correction = 0;
			goto display;
		}

		ret = 0;
		while(1) {
			ret = audio_get_av_delay(d->smd->adec, &av);
			if (ret != 1)
				break;
			softmpeg_sleep(40);
		}
		
		if (ret == -1) {
			list_lock(fr);
			list_add_tail(current, fr);
			list_unlock(fr);
			pthread_mutex_unlock(&d->mutex);

			softmpeg_sleep(40);
			continue;
		}

		if (d->initial_sync != 0) {
			if (sm_frame.is_iframe != 0) {
				int diff_ms;
				int filled, total, playing;

				VIDEO_AVSYNC("\n\n");

				if (i_pts > av.stc)
					diff_ms = (i_pts - av.stc) / 90;
				else
					diff_ms	= -((av.stc - i_pts) / 90);

#ifdef SOFTMPEG_DEBUG_AVSYNC
				VIDEO_AVSYNC("syncing: pts == ");
				printpts(stderr, i_pts);
				VIDEO_AVSYNC(" @ stc: ");
				printpts(stderr, av.stc);
				VIDEO_AVSYNC(" (diff:%4d ms), ", diff_ms);
				VIDEO_AVSYNC("(v [%2d/%2d]), ", fi->items, fr->items);
	
				pthread_mutex_lock(&d->smd->adec->stream_mutex);
				ret = d->smd->adec->a_ops->get_status(d->smd->adec,&filled, &total, &playing);
				pthread_mutex_unlock(&d->smd->adec->stream_mutex);

				VIDEO_AVSYNC("(a [%5d/%5d])\n", filled, total);
#endif
				/* if we're ahead, simply wait */
				if (diff_ms > 200) {
					d->initial_sync = 0;
				}

				/* if we've fallen too far behind, skip frames hard */
				if (diff_ms < -200) {
					struct video_buffer *buf = NULL;

					list_lock(fr);
					list_add_tail(current, fr);
					list_lock(fi);
					while (!list_empty(&d->filled)) {
						buf = (struct video_buffer *) list_del_head(&d->filled);
						if (buf->pts != 0) {
							i_pts = buf->pts;
						} else {
							i_pts += 90 * 40;
							buf->pts = i_pts;
						}
						if (buf->pts >= av.stc) {
							list_unlock(fi);
							list_unlock(fr);
#ifdef SOFTMPEG_DEBUG_AVSYNC
							VIDEO_AVSYNC("syncing: found matching frame %p, pts ==", buf);
							printpts(stderr, buf->pts);
							VIDEO_AVSYNC(" @ stc: ");
							printpts(stderr, av.stc);
							VIDEO_AVSYNC("\n");
#endif
							current = buf;
							goto again;
						}
						list_add_tail(buf, &d->free);
					}
					list_unlock(fi);
					list_unlock(fr);

				}
				if (diff_ms > 30) {
					pthread_mutex_lock(&d->backbuffer_mutex);
					d->sync_correction = diff_ms / d->v_dura;
					pthread_mutex_unlock(&d->backbuffer_mutex);
				}
				if (diff_ms < -30) {
					pthread_mutex_lock(&d->backbuffer_mutex);
					d->sync_correction = diff_ms / d->v_dura;
					pthread_mutex_unlock(&d->backbuffer_mutex);
				}
			}
		} else {
			uint64_t diff;
			int diff_ms;

			if (sm_frame.is_iframe == 0) {
				/* no initial sync, no i frame? => bail out */
				goto out;
			}

			VIDEO_AVSYNC("\n\n");

			diff = (current->pts - av.stc);
			diff_ms = diff / 90;

#ifdef SOFTMPEG_DEBUG_VIDEO
			VIDEO_DEBUG("initial syncing: pts == ");
			printpts(stderr, current->pts);
			VIDEO_DEBUG(" @ stc: ");
			printpts(stderr, av.stc);
			VIDEO_DEBUG("\n");
#endif
			if (current->pts < av.stc) {
				VIDEO_DEBUG("syncing: %lld ms late...\n", (av.stc - current->pts) / 90);
				goto out;
			}
			
			VIDEO_DEBUG("%d ms early...\n", diff_ms);
			softmpeg_sleep(diff_ms);

			set_initial_sync(d, 1);
		}

display:
		pthread_mutex_lock(&d->backbuffer_mutex);

		if (d->sync_correction > 0)
			VIDEO_AVSYNC("+");
		else if (d->sync_correction < 0)
			VIDEO_AVSYNC("-");
		else
			VIDEO_AVSYNC("=");
#ifdef SOFTMPEG_DEBUG_VIDEO
			fprintf(stderr,"v: pts == ");
			printpts(stderr, current->pts);
			fprintf(stderr," @ stc: ");
			printpts(stderr, av.stc);
			fprintf(stderr," (diff:");
			printpts(stderr, current->pts-av.stc);
			fprintf(stderr,")\n");
#endif

		if (d->sync_correction >= 0) {
			while (0 != d->backbuffer_filled) {
				pthread_cond_wait(&d->backbuffer_cond, &d->backbuffer_mutex);
			}

			if (d->v_width != sm_frame.width || d->v_height != sm_frame.height) {
				d->v_ops->resize(d,sm_frame.width,sm_frame.height);
			}	

			/* ok, we're ready to present the last decoded frame */
			d->v_ops->prepare(d,&sm_frame);
			d->backbuffer_filled = 1;

			pthread_cond_broadcast(&d->backbuffer_cond);
			pthread_mutex_unlock(&d->backbuffer_mutex);
			
			if (d->v_width != sm_frame.width || d->v_height != sm_frame.height) {
				d->v_width = sm_frame.width;
				d->v_height = sm_frame.height;
				/* ugly */
				if (NULL != d->smd->cbs && NULL != d->smd->cbs->resize)
					d->smd->cbs->resize(d->priv, sm_frame.width, sm_frame.height, sm_frame.aspect_ratio);
			}	
		} else {
			d->sync_correction++;
			pthread_mutex_unlock(&d->backbuffer_mutex);
		}

		out:
		list_lock(fr);
		list_add_tail(current, fr);
		list_unlock(fr);
		pthread_mutex_unlock(&d->mutex);
	}
	return NULL;
}

static void *display_thread(void *data)
{
	struct video_decoder *d = (struct video_decoder *) data;
	struct list *fi, *fr;
	int ret;
	int64_t diff;
	int64_t vor = 0;
	int64_t nach = -1;

	int acc_diff = 0;
	int acc_count = 0;
	
	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");
	}

	VIDEO_DEBUG("display thread started\n");
	d->display_thread_init = 1;

	fi = &d->filled;
	fr = &d->free;

	while (0 == d->cancel_threads) {
	
		vor = get_host_ticks90kHz();

		pthread_mutex_lock(&d->backbuffer_mutex);
		while (0 == d->backbuffer_filled) {
			struct timeval tv;
			struct timespec ts;
			if (0 != d->cancel_threads) {
				pthread_mutex_unlock(&d->backbuffer_mutex);
				break;
			}
			gettimeofday(&tv, NULL);
			ts.tv_sec = tv.tv_sec;
			ts.tv_nsec = (tv.tv_usec + 80 * 1000) * 1000;
			if (ts.tv_nsec >= 1000000000) {
				ts.tv_nsec -= 1000000000;
				ts.tv_sec++;
			}
			ret = pthread_cond_timedwait(&d->backbuffer_cond, &d->backbuffer_mutex,&ts);
		}

		if (d->sync_correction >= 0) {
			d->v_ops->wait_for_sync(d);
		}

		d->v_ops->flip(d);

		d->backbuffer_filled = 0;
		pthread_cond_broadcast(&d->backbuffer_cond);
		pthread_mutex_unlock(&d->backbuffer_mutex);

		nach = get_host_ticks90kHz();
		diff = (nach-vor) / 90;
		VIDEO_AVSYNC("y %2lld ", diff);

		if (acc_count > 12) {
			/* don't accumulate little differences */
			acc_count = 0;
			acc_diff = 0;
		}

		acc_diff += 40 - diff;
		acc_count++;
		if (acc_diff > d->v_dura) {
			VIDEO_AVSYNC(" [zW] ");
			d->v_ops->wait_for_sync(d);
			acc_diff -= d->v_dura;
			acc_count = 0;
		} else {
			VIDEO_AVSYNC("      ");
		}

		if (d->sync_correction > 0) {
			long long t = softmpeg_get_millis();
			long long diff;		
			d->v_ops->wait_for_sync(d);
			diff = softmpeg_get_millis() - t;
			VIDEO_AVSYNC(" [as %2lld %2d] ", diff,d->sync_correction);
			d->sync_correction--;
		} else {
			VIDEO_AVSYNC("            ");
		}
	
	}
	return NULL;
}

LinuxTV legacy CVS <linuxtv.org/cvs>