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, ¶m))) {
param.sched_priority = 7;
if (0 != (ret = pthread_setschedparam(d->feed_thread, SCHED_RR, ¶m))) {
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>