summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/demuxers/demux_flv.c1283
1 files changed, 687 insertions, 596 deletions
diff --git a/src/demuxers/demux_flv.c b/src/demuxers/demux_flv.c
index 090fe1097..cf1974659 100644
--- a/src/demuxers/demux_flv.c
+++ b/src/demuxers/demux_flv.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2004 the xine project
+ * Copyright (C) 2004-2013 the xine project
*
* This file is part of xine, a free video player.
*
@@ -22,9 +22,16 @@
* Flash Video (.flv) File Demuxer
* by Mike Melanson (melanson@pcisys.net) and
* Claudio Ciccani (klan@users.sf.net)
+ * rewritten by Torsten Jager (t.jager@gmx.de)
*
* For more information on the FLV file format, visit:
- * http://www.adobe.com/devnet/flv/pdf/video_file_format_spec_v9.pdf
+ * http://www.adobe.com/devnet/flv/pdf/video_file_format_spec_v10.pdf
+ *
+ * TJ. FLV actually is a persistent variant of Realtime Messaging Protocol
+ * (rtmp). Some features, most notably message interleaving and relative
+ * timestamps, have been removed. Official spec imposes further restrictions.
+ * We should nevertheless be prepared for more general stuff left by rtmp
+ * stream recorders.
*/
#ifdef HAVE_CONFIG_H
@@ -47,11 +54,10 @@
#include <xine/compat.h>
#include <xine/demux.h>
#include "bswap.h"
-#include "group_games.h"
typedef struct {
unsigned int pts;
- unsigned int offset;
+ off_t offset;
} flv_index_entry_t;
typedef struct {
@@ -68,8 +74,7 @@ typedef struct {
off_t start; /* in bytes */
off_t size; /* in bytes */
- unsigned char got_video_header;
- unsigned char got_audio_header;
+ unsigned char got_video_header, got_audio_header, got_info;
unsigned int length; /* in ms */
int width;
@@ -79,7 +84,7 @@ typedef struct {
int samplerate;
int samplesize;
- int stereo;
+ int audio_channels;
int audiocodec;
off_t filesize;
@@ -92,51 +97,63 @@ typedef struct {
int64_t last_pts[2];
int send_newpts;
int buf_flag_seek;
+
+ int audiodelay; /* fine tune a/v sync */
+
+ unsigned int zero_pts_count;
} demux_flv_t ;
typedef struct {
demux_class_t demux_class;
} demux_flv_class_t;
+/* an early FLV specification had 24bit bigendian timestamps. This
+ limited clip duration to 4:39:37.215. The backwards compatible solution:
+ hand over 1 byte from stream ID. Hence the weird byte order 2-1-0-3 */
+#define gettimestamp(p,o) (((((((uint32_t)p[o+3]<<8)|p[o])<<8)|p[o+1])<<8)|p[o+2])
#define FLV_FLAG_HAS_VIDEO 0x01
#define FLV_FLAG_HAS_AUDIO 0x04
#define FLV_TAG_TYPE_AUDIO 0x08
#define FLV_TAG_TYPE_VIDEO 0x09
-#define FLV_TAG_TYPE_SCRIPT 0x12
-
-#define FLV_SOUND_FORMAT_PCM_BE 0x00
-#define FLV_SOUND_FORMAT_ADPCM 0x01
-#define FLV_SOUND_FORMAT_MP3 0x02
-#define FLV_SOUND_FORMAT_PCM_LE 0x03
-#define FLV_SOUND_FORMAT_NELLY16 0x04 /* Nellymoser 16KHz */
-#define FLV_SOUND_FORMAT_NELLY8 0x05 /* Nellymoser 8KHz */
-#define FLV_SOUND_FORMAT_NELLY 0x06 /* Nellymoser */
-#define FLV_SOUND_FORMAT_ALAW 0x07 /* G.711 A-LAW */
-#define FLV_SOUND_FORMAT_MULAW 0x08 /* G.711 MU-LAW */
-#define FLV_SOUND_FORMAT_AAC 0x0a
-#define FLV_SOUND_FORMAT_MP38 0x0e /* MP3 8KHz */
-
-#define FLV_VIDEO_FORMAT_FLV1 0x02 /* Sorenson H.263 */
-#define FLV_VIDEO_FORMAT_SCREEN 0x03
-#define FLV_VIDEO_FORMAT_VP6 0x04 /* On2 VP6 */
-#define FLV_VIDEO_FORMAT_VP6A 0x05 /* On2 VP6 with alphachannel */
-#define FLV_VIDEO_FORMAT_SCREEN2 0x06
-#define FLV_VIDEO_FORMAT_H264 0x07
-
-#define FLV_DATA_TYPE_NUMBER 0x00
-#define FLV_DATA_TYPE_BOOL 0x01
-#define FLV_DATA_TYPE_STRING 0x02
-#define FLV_DATA_TYPE_OBJECT 0x03
-#define FLC_DATA_TYPE_CLIP 0x04
-#define FLV_DATA_TYPE_REFERENCE 0x07
-#define FLV_DATA_TYPE_ECMARRAY 0x08
-#define FLV_DATA_TYPE_ENDOBJECT 0x09
-#define FLV_DATA_TYPE_ARRAY 0x0a
-#define FLV_DATA_TYPE_DATE 0x0b
-#define FLV_DATA_TYPE_LONGSTRING 0x0c
-
+#define FLV_TAG_TYPE_NOTIFY 0x12
+
+typedef enum {
+ AF_PCM_BE, /* officially "native endian"?? */
+ AF_ADPCM,
+ AF_MP3,
+ AF_PCM_LE, /* little endian */
+ AF_NELLY16, /* Nellymoser 16KHz */
+ AF_NELLY8, /* Nellymoser 8KHz */
+ AF_NELLY, /* Nellymoser */
+ AF_ALAW, /* G.711 A-LAW */
+ AF_MULAW, /* G.711 MU-LAW */
+ AF_reserved9,
+ AF_AAC, /* mp4a with global header */
+ AF_SPEEX,
+ AF_reserved12,
+ AF_reserved13,
+ AF_MP38, /* MP3 8KHz */
+ AF_DS /* device specific sound */
+} af_t;
+
+/* audio types that support free samplerate from header */
+/* got the message ? ;-) */
+#define IS_PCM(id) ((((1<<AF_PCM_BE)|(1<<AF_ADPCM)|(1<<AF_PCM_LE)|(1<<AF_ALAW)|(1<<AF_MULAW))>>(id))&1)
+
+typedef enum {
+ VF_reserved0,
+ VF_JPEG,
+ VF_FLV1, /* modified Sorenson H.263 */
+ VF_SCREEN, /* Macromedia screen video v1 */
+ VF_VP6, /* On2 VP6 */
+ VF_VP6A, /* On2 VP6 with alphachannel */
+ VF_SCREEN2, /* v2 */
+ VF_H264, /* MPEG4 part 10, usually with global sequence parameter set */
+ VF_H263,
+ VF_MP4 /* MPEG4 part 2, usually with global sequence parameter set */
+} vf_t;
/* redefine abs as macro to handle 64-bit diffs.
i guess llabs may not be available everywhere */
@@ -146,27 +163,24 @@ typedef struct {
#define PTS_AUDIO 0
#define PTS_VIDEO 1
-static void check_newpts(demux_flv_t *this, int64_t pts, int video) {
+static void check_newpts (demux_flv_t *this, int64_t pts, int video) {
int64_t diff;
-
- diff = pts - this->last_pts[video];
lprintf ("check_newpts %"PRId64"\n", pts);
-
- if (pts && (this->send_newpts || (this->last_pts[video] && abs(diff)>WRAP_THRESHOLD))) {
- lprintf ("diff=%"PRId64"\n", diff);
-
- if (this->buf_flag_seek) {
- _x_demux_control_newpts(this->stream, pts, BUF_FLAG_SEEK);
- this->buf_flag_seek = 0;
- } else {
- _x_demux_control_newpts(this->stream, pts, 0);
+ if (this->buf_flag_seek) {
+ _x_demux_control_newpts (this->stream, pts, BUF_FLAG_SEEK);
+ this->buf_flag_seek = 0;
+ this->send_newpts = 0;
+ this->last_pts[1 - video] = 0;
+ } else {
+ diff = pts - this->last_pts[video];
+ if (pts && this->last_pts[video] && abs (diff) > WRAP_THRESHOLD) {
+ lprintf ("diff=%"PRId64"\n", diff);
+ _x_demux_control_newpts (this->stream, pts, 0);
+ this->send_newpts = 0;
+ this->last_pts[1-video] = 0;
}
- this->send_newpts = 0;
- this->last_pts[1-video] = 0;
}
-
- if (pts)
- this->last_pts[video] = pts;
+ this->last_pts[video] = pts;
}
/* returns 1 if the FLV file was opened successfully, 0 otherwise */
@@ -209,250 +223,349 @@ static int open_flv_file(demux_flv_t *this) {
_tmp.d;\
})\
-static int parse_flv_var(demux_flv_t *this,
- unsigned char *buf, int size, char *key, int keylen) {
- unsigned char *tmp = buf;
- unsigned char *end = buf + size;
- char *str;
- unsigned char type;
- unsigned int len, num;
-
- if (size < 1)
- return 0;
-
- type = *tmp++;
-
- switch (type) {
- case FLV_DATA_TYPE_NUMBER:
- lprintf(" got number (%f)\n", BE_F64(tmp));
- if (key) {
- double val = BE_F64(tmp);
- if (keylen == 8 && !strncmp(key, "duration", 8)) {
- this->length = val * 1000.0;
- }
- else if (keylen == 5 && !strncmp(key, "width", 5)) {
- this->width = val;
- _x_stream_info_set(this->stream, XINE_STREAM_INFO_VIDEO_WIDTH, this->width);
- }
- else if (keylen == 6 && !strncmp(key, "height", 6)) {
- this->height = val;
- _x_stream_info_set(this->stream, XINE_STREAM_INFO_VIDEO_HEIGHT, this->height);
- }
- else if (keylen == 9 && !strncmp(key, "framerate", 9)) {
- if (val > 0) {
- this->duration = 90000.0 / val;
- _x_stream_info_set(this->stream, XINE_STREAM_INFO_FRAME_DURATION, this->duration);
+/* Action Message Format data types */
+typedef enum {
+ AMF0_NUMBER = 0x00, /* double_be */
+ AMF0_BOOLEAN = 0x01, /* 1 byte TRUE or FALSE */
+ AMF0_STRING = 0x02, /* u16_be length, then utf8 string without end byte */
+ AMF0_OBJECT = 0x03, /* name/type/data triplets, then empty name plus
+ AMF0_OBJECT_END. name stored same way as AMF0_STRING */
+ AMF0_MOVIECLIP = 0x04, /* reserved */
+ AMF0_NULL_VALUE = 0x05, /* no data */
+ AMF0_UNDEFINED = 0x06, /* no data */
+ AMF0_REFERENCE = 0x07, /* u16be index into previous items table */
+ AMF0_ECMA_ARRAY = 0x08, /* u32_be number_of_entries, then same as AMF0_OBJECT */
+ AMF0_OBJECT_END = 0x09, /* end marker of AMF0_OBJECT */
+ AMF0_STRICT_ARRAY = 0x0a, /* u32_be n, then exactly n type/value pairs */
+ AMF0_DATE = 0x0b, /* double_be milliseconds since Jan 01, 1970, then
+ s16_be minutes off UTC */
+ AMF0_LONG_STRING = 0x0c, /* u32_be length, then utf8 string */
+ AMF0_UNSUPPORTED = 0x0d, /* no data */
+ AMF0_RECORD_SET = 0x0e, /* reserved */
+ AMF0_XML_OBJECT = 0x0f, /* physically same as AMF0_LONG_STRING */
+ AMF0_TYPED_OBJECT = 0x10, /* very complex, should not appear in FLV */
+ AMF0_AMF3 = 0x11, /* switch to AMF3 from here */
+} amf_type_t;
+
+#define MAX_AMF_LEVELS 10
+#define SPC (space + 2 * (MAX_AMF_LEVELS - level))
+#define NEEDBYTES(n) if ((unsigned long int)(end - p) < n) return 0
+
+static int parse_amf (demux_flv_t *this, unsigned char *buf, int size) {
+ unsigned char *p = buf, *end = buf + size, *name, space[2 * MAX_AMF_LEVELS + 3];
+ int level = 0, i, type, count[MAX_AMF_LEVELS], info = 0;
+ unsigned int u, c;
+ time_t tsecs;
+ struct tm *tstruct;
+ double val;
+
+ /* init prettyprinter */
+ memset (space, ' ', 2 * MAX_AMF_LEVELS + 2);
+ space[2 * MAX_AMF_LEVELS + 2] = 0x00;
+ /* top level has nameless vars */
+ count[0] = 10000;
+ while (1) {
+ if (count[level] > 0) {
+ /* next strict array item */
+ if (--count[level] == 0) {
+ /* one level up */
+ if (--level < 0) return 0;
+ xprintf (this->xine, XINE_VERBOSITY_DEBUG, "%s}\n", SPC);
+ continue;
+ }
+ if (p >= end) break;
+ type = *p++;
+ name = NULL;
+ xprintf (this->xine, XINE_VERBOSITY_DEBUG, "%s", SPC);
+ } else {
+ /* get current name */
+ NEEDBYTES (2);
+ u = _X_BE_16 (p);
+ p += 2;
+ NEEDBYTES (u);
+ name = p;
+ p += u;
+ if (u == 0) {
+ /* object end, 1 level up */
+ if (--level < 0) return 0;
+ if ((p < end) && (*p == AMF0_OBJECT_END)) p++;
+ xprintf (this->xine, XINE_VERBOSITY_DEBUG, "%s}\n", SPC);
+ continue;
+ }
+ NEEDBYTES (1);
+ type = *p;
+ *p++ = 0x00;
+ xprintf (this->xine, XINE_VERBOSITY_DEBUG, "%s%s = ", SPC, name);
+ }
+ switch (type) {
+ case AMF0_NUMBER:
+ NEEDBYTES (8);
+ val = BE_F64 (p);
+ i = val;
+ if (i == val) xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "%d\n", i);
+ else xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "%.03lf\n", val);
+ p += 8;
+ if (name && info) {
+ if (!strcmp (name, "duration")) {
+ this->length = val * (double)1000;
+ } else if (!strcmp (name, "width")) {
+ this->width = i;
+ _x_stream_info_set (this->stream, XINE_STREAM_INFO_VIDEO_WIDTH, i);
+ } else if (!strcmp (name, "height")) {
+ this->height = i;
+ _x_stream_info_set (this->stream, XINE_STREAM_INFO_VIDEO_HEIGHT, i);
+ } else if (!strcmp (name, "framerate") || !strcmp (name, "videoframerate")) {
+ if ((i > 0) && (i < 1000)) {
+ this->duration = (double)90000 / val;
+ _x_stream_info_set (this->stream, XINE_STREAM_INFO_FRAME_DURATION,
+ this->duration);
+ }
+ } else if (!strcmp (name, "videodatarate")) {
+ _x_stream_info_set (this->stream, XINE_STREAM_INFO_VIDEO_BITRATE,
+ val * (double)1000);
+ } else if (!strcmp (name, "videocodecid")) {
+ this->videocodec = i;
+ } else if (!strcmp (name, "audiosamplerate")) {
+ this->samplerate = i;
+ _x_stream_info_set (this->stream, XINE_STREAM_INFO_AUDIO_SAMPLERATE, i);
+ } else if (!strcmp (name, "audiosamplesize")) {
+ this->samplesize = i;
+ _x_stream_info_set (this->stream, XINE_STREAM_INFO_AUDIO_BITS, i);
+ } else if (!strcmp (name, "stereo")) {
+ this->audio_channels = i ? 2 : 1;
+ _x_stream_info_set (this->stream, XINE_STREAM_INFO_AUDIO_CHANNELS, i);
+ } else if (!strcmp (name, "audiodatarate")) {
+ _x_stream_info_set (this->stream, XINE_STREAM_INFO_AUDIO_BITRATE,
+ val * (double)1000);
+ } else if (!strcmp (name, "audiocodecid")) {
+ this->audiocodec = i;
+ } else if (!strcmp (name, "filesize")) {
+ this->filesize = val;
+ } else if (!strcmp (name, "audiodelay")) {
+ this->audiodelay = val * (double)-1000;
}
}
- else if (keylen == 13 && !strncmp(key, "videodatarate", 13)) {
- _x_stream_info_set(this->stream, XINE_STREAM_INFO_VIDEO_BITRATE, val*1000.0);
- }
- else if (keylen == 12 && !strncmp(key, "videocodecid", 12)) {
- this->videocodec = val;
- }
- else if (keylen == 15 && !strncmp(key, "audiosamplerate", 15)) {
- this->samplerate = val;
- _x_stream_info_set(this->stream, XINE_STREAM_INFO_AUDIO_SAMPLERATE, this->samplerate);
- }
- else if (keylen == 15 && !strncmp(key, "audiosamplesize", 15)) {
- this->samplesize = val;
- _x_stream_info_set(this->stream, XINE_STREAM_INFO_AUDIO_BITS, this->samplesize);
- }
- else if (keylen == 5 && !strncmp(key, "stereo", 5)) {
- this->stereo = val;
- _x_stream_info_set(this->stream, XINE_STREAM_INFO_AUDIO_CHANNELS, this->stereo ? 2 : 1);
- }
- else if (keylen == 13 && !strncmp(key, "audiodatarate", 13)) {
- _x_stream_info_set(this->stream, XINE_STREAM_INFO_AUDIO_BITRATE, val*1000.0);
- }
- else if (keylen == 12 && !strncmp(key, "audiocodecid", 12)) {
- this->audiocodec = val;
- }
- else if (keylen == 8 && !strncmp(key, "filesize", 8)) {
- this->filesize = val;
- }
- }
- tmp += 8;
break;
- case FLV_DATA_TYPE_BOOL:
- lprintf(" got bool (%d)\n", *tmp);
- tmp++;
- break;
- case FLV_DATA_TYPE_STRING:
- lprintf(" got string (%s)\n", tmp+2);
- len = _X_BE_16(tmp);
- tmp += len + 2;
+ case AMF0_BOOLEAN:
+ NEEDBYTES (1);
+ i = !!(*p++);
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "%s\n", i ? "yes" : "no");
+ if (name && info) {
+ if (!strcmp (name, "stereo"))
+ this->audio_channels = i ? 2 : 1;
+ }
break;
- case FLV_DATA_TYPE_OBJECT:
- while ((len = _X_BE_16(tmp)) && tmp < end) {
- lprintf(" got object var (%s)\n", tmp+2);
- str = tmp + 2;
- tmp += len + 2;
- len = parse_flv_var(this, tmp, end-tmp, str, len);
- if (!len)
- return 0;
- tmp += len;
- }
- if (*tmp++ != FLV_DATA_TYPE_ENDOBJECT)
- return 0;
+ case AMF0_STRING:
+ NEEDBYTES (2);
+ u = _X_BE_16 (p);
+ p += 2;
+ NEEDBYTES (u);
+ c = p[u];
+ p[u] = 0x00;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "\"%s\"\n", p);
+ if (!level && !strcmp (p, "onMetaData")) info = this->got_info = 1;
+ if (name && info) {
+ if ((!strcmp (name, "audiocodecid")) && (!strcmp (p, "mp4a")))
+ this->audiocodec = AF_AAC;
+ else if ((!strcmp (name, "videocodecid")) && (!strcmp (p, "avc1")))
+ this->videocodec = VF_H264;
+ else if (!strcmp (name, "stereo")) {
+ if (!strcmp (p, "true") || !strcmp (p, "yes"))
+ this->audio_channels = 2;
+ }
+ }
+ p[u] = c;
+ p += u;
break;
- case FLV_DATA_TYPE_ECMARRAY:
- lprintf(" got EMCA array (%d indices)\n", _X_BE_32(tmp));
- num = _X_BE_32(tmp);
- tmp += 4;
- while (num-- && tmp < end) {
- lprintf(" got array key (%s)\n", tmp+2);
- len = _X_BE_16(tmp);
- str = tmp + 2;
- tmp += len + 2;
- len = parse_flv_var(this, tmp, end-tmp, str, len);
- if (!len)
- return 0;
- tmp += len;
- }
+ case AMF0_LONG_STRING:
+ case AMF0_XML_OBJECT:
+ NEEDBYTES (4);
+ u = _X_BE_32 (p);
+ p += 4;
+ NEEDBYTES (u);
+ /* avoid printf() overload */
+ if (u > 4000) p[4000] = 0x00;
+ c = p[u];
+ p[u] = 0x00;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "%s\n", p);
+ p[u] = c;
+ p += u;
break;
- case FLV_DATA_TYPE_ARRAY:
- lprintf(" got array (%d indices)\n", _X_BE_32(tmp));
- num = _X_BE_32(tmp);
- tmp += 4;
- if (key && keylen == 5 && !strncmp(key, "times", 5)) {
- if (!this->index || this->num_indices != num) {
- if (this->index)
- free(this->index);
- this->index = calloc(num, sizeof(flv_index_entry_t));
- if (!this->index)
- return 0;
- this->num_indices = num;
+ case AMF0_ECMA_ARRAY:
+ NEEDBYTES (4);
+ u = _X_BE_32 (p); /* this value is unreliable */
+ p += 4;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "[%d] ", u);
+ /* fall through */
+ case AMF0_OBJECT:
+ if (++level >= MAX_AMF_LEVELS) return 0;
+ count[level] = -1;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "{\n");
+ continue;
+ case AMF0_STRICT_ARRAY:
+ NEEDBYTES (4);
+ u = _X_BE_32 (p);
+ p += 4;
+ c = 0;
+ if (name) {
+ if (!strcmp (name, "times")) c = 1;
+ else if (!strcmp (name, "filepositions")) c = 2;
}
- for (num = 0; num < this->num_indices && tmp < end; num++) {
- if (*tmp++ == FLV_DATA_TYPE_NUMBER) {
- lprintf(" got number (%f)\n", BE_F64(tmp));
- this->index[num].pts = BE_F64(tmp) * 1000.0;
- tmp += 8;
+ if (c) {
+ NEEDBYTES (u * 9);
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "[%d] {..}\n", u);
+ if (!this->index || (this->num_indices != u)) {
+ if (this->index) free (this->index);
+ this->index = calloc (u, sizeof (flv_index_entry_t));
+ if (!this->index) return 0;
+ this->num_indices = u;
}
- }
- break;
- }
- if (key && keylen == 13 && !strncmp(key, "filepositions", 13)) {
- if (!this->index || this->num_indices != num) {
- if (this->index)
- free(this->index);
- this->index = calloc(num, sizeof(flv_index_entry_t));
- if (!this->index)
- return 0;
- this->num_indices = num;
- }
- for (num = 0; num < this->num_indices && tmp < end; num++) {
- if (*tmp++ == FLV_DATA_TYPE_NUMBER) {
- lprintf(" got number (%f)\n", BE_F64(tmp));
- this->index[num].offset = BE_F64(tmp);
- tmp += 8;
+ if (c == 1) for (i = 0; i < (int)u; i++) {
+ if (*p++ != AMF0_NUMBER) return 0;
+ this->index[i].pts = BE_F64 (p) * (double)1000;
+ p += 8;
+ } else for (i = 0; i < (int)u; i++) {
+ if (*p++ != AMF0_NUMBER) return 0;
+ this->index[i].offset = BE_F64 (p);
+ p += 8;
}
+ } else {
+ if (++level >= MAX_AMF_LEVELS) return 0;
+ count[level] = u + 1;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "[%d] {\n", u);
}
- break;
- }
- while (num-- && tmp < end) {
- len = parse_flv_var(this, tmp, end-tmp, NULL, 0);
- if (!len)
- return 0;
- tmp += len;
- }
break;
- case FLV_DATA_TYPE_DATE:
- lprintf(" got date (%"PRId64", %d)\n", _X_BE_64(tmp), _X_BE_16(tmp+8));
- tmp += 10;
+ case AMF0_DATE:
+ NEEDBYTES (10);
+ val = BE_F64 (p) / (double)1000;
+ tsecs = val;
+ p += 8;
+ i = _X_BE_16 (p);
+ p += 2;
+ if (i & 0x8000) i |= ~0x7fff;
+ tsecs += i * 60;
+ tstruct = gmtime (&tsecs);
+ if (tstruct) {
+ char ts[200];
+ if (strftime (ts, 200, "%x %X", tstruct) >= 0)
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "%s\n", ts);
+ }
break;
- default:
- lprintf(" got type %d\n", type);
+ case AMF0_NULL_VALUE:
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "(null)\n");
break;
- }
-
- return (tmp - buf);
-}
-
-static void parse_flv_script(demux_flv_t *this, int size) {
- unsigned char *buf = malloc(size);
- unsigned char *tmp = buf;
- unsigned char *end = buf + size;
- int len;
-
- if (!buf || this->input->read(this->input, buf, size ) != size) {
- this->status = DEMUX_FINISHED;
- free(buf);
- return;
- }
-
- while (tmp < end) {
- len = parse_flv_var(this, tmp, end-tmp, NULL, 0);
- if (len < 1)
+ case AMF0_UNDEFINED:
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "(undefined)\n");
+ break;
+ case AMF0_REFERENCE:
+ NEEDBYTES (2);
+ u = _X_BE_16 (p);
+ p += 2;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "(see #%u)\n", u);
+ break;
+ default:
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "(unhandled type %d)\n", type);
+ return 0;
break;
- tmp += len;
+ }
}
-
- free(buf);
+ return level == 0;
}
+#define GETBYTES(n) \
+ if (remaining_bytes < n) \
+ continue; \
+ if (this->input->read (this->input, (char *)buffer + 16, n) != n) \
+ goto fail; \
+ remaining_bytes -= n;
+
static int read_flv_packet(demux_flv_t *this, int preview) {
fifo_buffer_t *fifo = NULL;
buf_element_t *buf = NULL;
+ unsigned int tag_type, mp4header, buf_type = 0;
+ unsigned int buf_flags = BUF_FLAG_FRAME_START;
+ int remaining_bytes = 0;
+
+ unsigned int pts; /* ms */
+ int ptsoffs = 0; /* pts ticks */
+ int64_t buf_pts;
+ off_t size, normpos = 0;
+
+ this->status = DEMUX_OK;
while (1) {
- unsigned char buffer[12], extrabuffer[4];
- unsigned char tag_type, avinfo;
- unsigned int remaining_bytes;
- unsigned int buf_type = 0;
- unsigned int buf_flags = 0;
- unsigned int pts;
-
- lprintf (" reading FLV tag...\n");
- this->input->seek(this->input, 4, SEEK_CUR);
- if (this->input->read(this->input, buffer, 11) != 11) {
+ /* define this here to prevent compiler caching it across multiple this->input->read ()'s.
+ layout:
+ 0 .. 3 previous tags footer (not needed here)
+ 4 tag type
+ 5 .. 7 payload size
+ 8 .. 11 dts (= pts unless H.264 or MPEG4)
+ 12 .. 14 stream id (usually 0)
+ 15 codec id / frame type / audio params (A/V only)
+ 16 right / bottom crop (VP6(F))
+ frame subtype (AAC, H264, MPEG4)
+ 17 .. 19 ?? (VP6F)
+ pts - dts (H264, MPEG4) */
+ unsigned char buffer[20];
+ /* skip rest, if any */
+ if (remaining_bytes)
+ this->input->seek (this->input, remaining_bytes, SEEK_CUR);
+ /* we have a/v tags mostly, optimize for them */
+ if (this->input->read (this->input, (char *)buffer, 16) != 16) {
+ fail:
this->status = DEMUX_FINISHED;
return this->status;
}
-
- tag_type = buffer[0];
- remaining_bytes = _X_BE_24(&buffer[1]);
- pts = _X_BE_24(&buffer[4]) | (buffer[7] << 24);
-
- lprintf(" tag_type = 0x%02X, 0x%X bytes, pts %u\n",
- tag_type, remaining_bytes, pts/90);
+ remaining_bytes = _X_BE_24(&buffer[5]);
+ /* skip empty tags */
+ if (--remaining_bytes < 0)
+ continue;
+ tag_type = buffer[4];
+ pts = gettimestamp (buffer, 8);
+ mp4header = 0;
switch (tag_type) {
case FLV_TAG_TYPE_AUDIO:
- lprintf(" got audio tag..\n");
- if (this->input->read(this->input, &avinfo, 1) != 1) {
- this->status = DEMUX_FINISHED;
- return this->status;
- }
- remaining_bytes--;
+ if (!pts)
+ this->zero_pts_count++;
+ else if (this->audiodelay > 0)
+ pts += this->audiodelay;
+ this->audiocodec = buffer[15] >> 4;
- this->audiocodec = avinfo >> 4; /* override */
switch (this->audiocodec) {
- case FLV_SOUND_FORMAT_PCM_BE:
+ case AF_PCM_BE:
buf_type = BUF_AUDIO_LPCM_BE;
- break;
- case FLV_SOUND_FORMAT_ADPCM:
+ break;
+ case AF_ADPCM:
buf_type = BUF_AUDIO_FLVADPCM;
- break;
- case FLV_SOUND_FORMAT_MP3:
- case FLV_SOUND_FORMAT_MP38:
+ break;
+ case AF_MP3:
+ case AF_MP38:
buf_type = BUF_AUDIO_MPEG;
- break;
- case FLV_SOUND_FORMAT_PCM_LE:
+ break;
+ case AF_PCM_LE:
buf_type = BUF_AUDIO_LPCM_LE;
- break;
- case FLV_SOUND_FORMAT_ALAW:
+ break;
+ case AF_ALAW:
buf_type = BUF_AUDIO_ALAW;
- break;
- case FLV_SOUND_FORMAT_MULAW:
+ break;
+ case AF_MULAW:
buf_type = BUF_AUDIO_MULAW;
- break;
- case FLV_SOUND_FORMAT_AAC:
+ break;
+#ifdef BUF_AUDIO_NELLYMOSER
+ case AF_NELLY:
+ case AF_NELLY8:
+ case AF_NELLY16:
+ buf_type = BUF_AUDIO_NELLYMOSER;
+ break;
+#endif
+ case AF_AAC:
buf_type = BUF_AUDIO_AAC;
- /* AAC extra header */
- this->input->read(this->input, extrabuffer, 1 );
- remaining_bytes--;
+ GETBYTES (1);
+ if (!buffer[16]) mp4header = 1;
+ break;
+ case AF_SPEEX:
+ buf_type = BUF_AUDIO_SPEEX;
break;
default:
lprintf(" unsupported audio format (%d)...\n", this->audiocodec);
@@ -461,272 +574,205 @@ static int read_flv_packet(demux_flv_t *this, int preview) {
}
fifo = this->audio_fifo;
- if (preview && !this->got_audio_header) {
+ if (!this->got_audio_header) {
+ /* prefer tag header settings, unless we hit some unofficial libavformat extension */
+ if (!IS_PCM (this->audiocodec) || (buffer[15] & 0x0c) || (this->samplerate < 4000)) {
+ this->samplerate =
+ this->audiocodec == AF_NELLY8 || this->audiocodec == AF_MP38 ? 8000 :
+ this->audiocodec == AF_NELLY16 ? 16000 :
+ 44100 >> (3 - ((buffer[15] >> 2) & 3));
+ this->audio_channels = (buffer[15] & 1) + 1;
+ }
+ if (!this->audio_channels)
+ this->audio_channels = (buffer[15] & 1) + 1;
/* send init info to audio decoder */
buf = fifo->buffer_pool_alloc(fifo);
buf->decoder_flags = BUF_FLAG_HEADER | BUF_FLAG_STDHEADER | BUF_FLAG_FRAME_END;
buf->decoder_info[0] = 0;
- buf->decoder_info[1] = 44100 >> (3 - ((avinfo >> 2) & 3)); /* samplerate */
- buf->decoder_info[2] = (avinfo & 2) ? 16 : 8; /* bits per sample */
- buf->decoder_info[3] = (avinfo & 1) + 1; /* channels */
+ buf->decoder_info[1] = this->samplerate;
+ buf->decoder_info[2] = (buffer[15] & 2) ? 16 : 8; /* bits per sample */
+ buf->decoder_info[3] = this->audio_channels;
buf->size = 0; /* no extra data */
buf->type = buf_type;
fifo->put(fifo, buf);
this->got_audio_header = 1;
- if (!INPUT_IS_SEEKABLE(this->input)) {
- /* stop preview processing immediately, this enables libfaad to
- * initialize even without INPUT_CAP_SEEKABLE of input stream.
- */
- preview = 0;
- }
}
- break;
+ break;
case FLV_TAG_TYPE_VIDEO:
- lprintf(" got video tag..\n");
- if (this->input->read(this->input, &avinfo, 1) != 1) {
- this->status = DEMUX_FINISHED;
- return this->status;
- }
- remaining_bytes--;
-
- switch ((avinfo >> 4)) {
- case 0x01:
- buf_flags = BUF_FLAG_KEYFRAME;
- break;
- case 0x05:
- /* skip server command */
- this->input->seek(this->input, remaining_bytes, SEEK_CUR);
- continue;
- default:
- break;
+ if (!pts)
+ this->zero_pts_count++;
+ else if (this->audiodelay < 0)
+ pts -= this->audiodelay;
+ /* check frame type */
+ switch (buffer[15] >> 4) {
+ case 1: /* Key or seekable frame */
+ case 4: /* server generated keyframe */
+ buf_flags |= BUF_FLAG_KEYFRAME;
+ break;
+ case 5:
+ /* This is a rtmp server command.
+ One known use:
+ When doing a rtmp time seek between key frames, server may send:
+ 1. a type 5 frame of one 0x00 byte
+ 2. the nearest keyframe before the seek time
+ 3. the following frames before the seek time, if any
+ 4. a type 5 frame of one 0x01 byte */
+ continue;
+ default: ;
}
-
- this->videocodec = avinfo & 0x0F; /* override */
+ this->videocodec = buffer[15] & 0x0F;
switch (this->videocodec) {
- case FLV_VIDEO_FORMAT_FLV1:
+ case VF_FLV1:
buf_type = BUF_VIDEO_FLV1;
- break;
- case FLV_VIDEO_FORMAT_VP6:
- buf_type = BUF_VIDEO_VP6F;
- /* VP6 extra header */
- this->input->read(this->input, extrabuffer, 1 );
- remaining_bytes--;
- break;
- case FLV_VIDEO_FORMAT_VP6A:
- buf_type = BUF_VIDEO_VP6F;
- /* VP6A extra header */
- this->input->read(this->input, extrabuffer, 4);
- remaining_bytes -= 4;
- break;
- case FLV_VIDEO_FORMAT_H264:
+ break;
+ case VF_H263:
+ buf_type = BUF_VIDEO_H263;
+ break;
+ case VF_MP4:
+ buf_type = BUF_VIDEO_MPEG4;
+ goto comm_mpeg4;
+ case VF_H264:
buf_type = BUF_VIDEO_H264;
/* AVC extra header */
- this->input->read(this->input, extrabuffer, 4);
- remaining_bytes -= 4;
- break;
+ comm_mpeg4:
+ GETBYTES (4);
+ if (buffer[16] == 2)
+ continue; /* skip sequence footer */
+ if (!buffer[16])
+ mp4header = 1;
+ /* pts really is dts here, buffer[17..19] has (pts - dts) signed big endian. */
+ ptsoffs = _X_BE_24 (buffer + 17);
+ if (ptsoffs & 0x800000)
+ ptsoffs |= ~0xffffff;
+ /* better: +/- 16 frames, but we cannot trust header framerate */
+ if ((ptsoffs < -1000) || (ptsoffs > 1000))
+ ptsoffs = 0;
+ ptsoffs *= 90;
+ break;
+ case VF_VP6:
+ buf_type = BUF_VIDEO_VP6F;
+ GETBYTES (1);
+ break;
+ case VF_VP6A:
+ buf_type = BUF_VIDEO_VP6F;
+ GETBYTES (4);
+ break;
+ case VF_JPEG:
+ buf_type = BUF_VIDEO_JPEG;
+ break;
default:
lprintf(" unsupported video format (%d)...\n", this->videocodec);
buf_type = BUF_VIDEO_UNKNOWN;
- break;
+ break;
}
fifo = this->video_fifo;
- if (preview && !this->got_video_header) {
+ if (!this->got_video_header) {
xine_bmiheader *bih;
/* send init info to video decoder; send the bitmapinfo header to the decoder
* primarily as a formality since there is no real data inside */
buf = fifo->buffer_pool_alloc(fifo);
- buf->decoder_flags = BUF_FLAG_HEADER | BUF_FLAG_STDHEADER |
- BUF_FLAG_FRAMERATE | BUF_FLAG_FRAME_END;
- buf->decoder_info[0] = this->duration;
+ buf->decoder_flags = BUF_FLAG_HEADER | BUF_FLAG_STDHEADER | BUF_FLAG_FRAME_END;
+ if (this->duration) {
+ buf->decoder_flags |= BUF_FLAG_FRAMERATE;
+ buf->decoder_info[0] = this->duration;
+ }
bih = (xine_bmiheader *) buf->content;
- memset(bih, 0, sizeof(xine_bmiheader));
- bih->biSize = sizeof(xine_bmiheader);
- bih->biWidth = this->width;
+ memset (bih, 0, sizeof(xine_bmiheader));
+ bih->biSize = sizeof(xine_bmiheader);
+ bih->biWidth = this->width;
bih->biHeight = this->height;
- buf->size = sizeof(xine_bmiheader);
- buf->type = buf_type;
+ buf->size = sizeof(xine_bmiheader);
+ buf->type = buf_type;
if (buf_type == BUF_VIDEO_VP6F) {
- *((unsigned char *)buf->content+buf->size) = extrabuffer[0];
+ *((unsigned char *)buf->content+buf->size) = buffer[16];
bih->biSize++;
buf->size++;
}
- else if (buf_type == BUF_VIDEO_H264 && extrabuffer[0] == 0) {
- /* AVC sequence header */
- if (remaining_bytes > buf->max_size-buf->size) {
- xprintf(this->xine, XINE_VERBOSITY_LOG,
- _("sequence header too big (%u bytes)!\n"), remaining_bytes);
- this->input->read(this->input, buf->content+buf->size, buf->max_size-buf->size);
- this->input->seek(this->input, remaining_bytes-buf->max_size-buf->size, SEEK_CUR);
- bih->biSize = buf->max_size;
- buf->size = buf->max_size;
- }
- else {
- this->input->read(this->input, buf->content+buf->size, remaining_bytes);
- bih->biSize += remaining_bytes;
- buf->size += remaining_bytes;
- }
- remaining_bytes = 0;
- }
fifo->put(fifo, buf);
this->got_video_header = 1;
}
- break;
-
- case FLV_TAG_TYPE_SCRIPT:
- lprintf(" got script tag...\n");
- if (preview) {
- parse_flv_script(this, remaining_bytes);
-
- /* send init info to decoders using script information as reference */
- if (!this->got_audio_header && this->audiocodec) {
- buf = this->audio_fifo->buffer_pool_alloc(this->audio_fifo);
- buf->decoder_flags = BUF_FLAG_HEADER | BUF_FLAG_STDHEADER | BUF_FLAG_FRAME_END;
- buf->decoder_info[0] = 0;
- buf->decoder_info[1] = this->samplerate;
- buf->decoder_info[2] = this->samplesize;
- buf->decoder_info[3] = this->stereo ? 2 : 1;
- switch (this->audiocodec) {
- case FLV_SOUND_FORMAT_PCM_BE:
- buf->type = BUF_AUDIO_LPCM_BE;
- break;
- case FLV_SOUND_FORMAT_ADPCM:
- buf->type = BUF_AUDIO_FLVADPCM;
- break;
- case FLV_SOUND_FORMAT_MP3:
- case FLV_SOUND_FORMAT_MP38:
- buf->type = BUF_AUDIO_MPEG;
- break;
- case FLV_SOUND_FORMAT_PCM_LE:
- buf->type = BUF_AUDIO_LPCM_LE;
- break;
- case FLV_SOUND_FORMAT_ALAW:
- buf->type = BUF_AUDIO_ALAW;
- break;
- case FLV_SOUND_FORMAT_MULAW:
- buf->type = BUF_AUDIO_MULAW;
- break;
- case FLV_SOUND_FORMAT_AAC:
- buf->type = BUF_AUDIO_AAC;
- break;
- default:
- buf->type = BUF_AUDIO_UNKNOWN;
- break;
- }
- buf->size = 0;
- this->audio_fifo->put(this->audio_fifo, buf);
- this->got_audio_header = 1;
- lprintf(" got audio header from metadata...\n");
- }
+ break;
- if (!this->got_video_header && this->videocodec && this->videocodec != FLV_VIDEO_FORMAT_H264) {
- xine_bmiheader *bih;
- buf = this->video_fifo->buffer_pool_alloc(this->video_fifo);
- buf->decoder_flags = BUF_FLAG_HEADER | BUF_FLAG_STDHEADER |
- BUF_FLAG_FRAMERATE | BUF_FLAG_FRAME_END;
- buf->decoder_info[0] = this->duration;
- switch (this->videocodec) {
- case FLV_VIDEO_FORMAT_FLV1:
- buf->type = BUF_VIDEO_FLV1;
- break;
- case FLV_VIDEO_FORMAT_VP6:
- case FLV_VIDEO_FORMAT_VP6A:
- buf->type = BUF_VIDEO_VP6F;
- break;
- default:
- buf->type = BUF_VIDEO_UNKNOWN;
- break;
- }
- buf->size = sizeof(xine_bmiheader);
- bih = (xine_bmiheader *) buf->content;
- memset(bih, 0, sizeof(xine_bmiheader));
- bih->biSize = sizeof(xine_bmiheader);
- bih->biWidth = this->width;
- bih->biHeight = this->height;
- if (buf->type == BUF_VIDEO_VP6F) {
- *((uint8_t *)buf->content+buf->size) = ((16-(this->width&15)) << 4) |
- ((16-(this->height&15)) & 0xf);
- bih->biSize++;
- buf->size++;
- }
- this->video_fifo->put(this->video_fifo, buf);
- this->got_video_header = 1;
- lprintf(" got video header from metadata...\n");
+ case FLV_TAG_TYPE_NOTIFY:
+ if (!this->got_info) {
+ unsigned char *text;
+ this->input->seek (this->input, -1, SEEK_CUR);
+ remaining_bytes++;
+ text = malloc (remaining_bytes + 1); /* 1 more byte for possible string end */
+ if (!text || this->input->read (this->input, (char *)text, remaining_bytes) != remaining_bytes) {
+ free (text);
+ goto fail;
}
-
- return this->status;
+ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "demux_flv: stream info:\n");
+ parse_amf (this, text, remaining_bytes);
+ free (text);
+ return (this->status);
}
- /* no preview */
- this->input->seek(this->input, remaining_bytes, SEEK_CUR);
- continue;
-
+ /* fall through */
default:
lprintf(" skipping packet...\n");
- this->input->seek(this->input, remaining_bytes, SEEK_CUR);
- continue;
+ continue;
}
- while (remaining_bytes) {
- buf = fifo->buffer_pool_alloc(fifo);
+ /* send mpeg4 style headers in both normal and preview mode. This makes sure that
+ they get through before they are needed. And it supports multiple sequences per
+ stream (unless we seek too far). */
+ if (mp4header) {
+ buf = fifo->buffer_pool_alloc (fifo);
buf->type = buf_type;
-
- buf->extra_info->input_time = pts;
- if (this->input->get_length(this->input)) {
- buf->extra_info->input_normpos =
- (int)((double)this->input->get_current_pos(this->input) * 65535.0 / this->size);
+ buf->size = 0;
+ buf->decoder_flags = BUF_FLAG_SPECIAL|BUF_FLAG_HEADER;
+ buf->decoder_info[1] = BUF_SPECIAL_DECODER_CONFIG;
+ buf->decoder_info[2] = remaining_bytes > buf->max_size ?
+ buf->max_size : remaining_bytes;
+ buf->decoder_info_ptr[2] = buf->mem;
+ if ((this->input->read (this->input, (char *)buf->mem, buf->decoder_info[2]))
+ != buf->decoder_info[2]) {
+ buf->free_buffer (buf);
+ goto fail;
}
+ remaining_bytes -= buf->decoder_info[2];
+ if (remaining_bytes)
+ this->input->seek (this->input, remaining_bytes, SEEK_CUR);
+ remaining_bytes = 0;
+ fifo->put (fifo, buf);
+ break;
+ }
- if ((buf_type == BUF_VIDEO_H264 || buf_type == BUF_AUDIO_AAC) && extrabuffer[0] == 0) {
- /* AVC/AAC sequence header */
- buf->pts = 0;
- buf->size = 0;
-
- buf->decoder_flags = BUF_FLAG_SPECIAL | BUF_FLAG_HEADER;
- if (preview)
- buf->decoder_flags |= BUF_FLAG_PREVIEW;
-
- buf->decoder_info[1] = BUF_SPECIAL_DECODER_CONFIG;
- buf->decoder_info[2] = MIN(remaining_bytes, buf->max_size);
- buf->decoder_info_ptr[2] = buf->mem;
-
- if (this->input->read(this->input, buf->mem, buf->decoder_info[2]) != buf->decoder_info[2]) {
- buf->free_buffer(buf);
- this->status = DEMUX_FINISHED;
- break;
- }
+ /* fkip frame contents in preview mode */
+ if (preview) {
+ if (remaining_bytes)
+ this->input->seek (this->input, remaining_bytes, SEEK_CUR);
+ return this->status;
+ }
- if (remaining_bytes > buf->max_size) {
- xprintf(this->xine, XINE_VERBOSITY_LOG,
- _("sequence header too big (%u bytes)!\n"), remaining_bytes);
- this->input->seek(this->input, remaining_bytes-buf->max_size, SEEK_CUR);
- }
- remaining_bytes = 0;
- }
- else {
- buf->pts = (int64_t) pts * 90;
- if (!preview)
- check_newpts(this, buf->pts, (tag_type == FLV_TAG_TYPE_VIDEO));
-
- if (remaining_bytes > buf->max_size)
- buf->size = buf->max_size;
- else
- buf->size = remaining_bytes;
- remaining_bytes -= buf->size;
-
- buf->decoder_flags = buf_flags;
- if (preview)
- buf->decoder_flags |= BUF_FLAG_PREVIEW;
- if (!remaining_bytes)
- buf->decoder_flags |= BUF_FLAG_FRAME_END;
-
- if (this->input->read(this->input, buf->content, buf->size) != buf->size) {
- buf->free_buffer(buf);
- this->status = DEMUX_FINISHED;
- break;
- }
+ /* send frame contents */
+ buf_pts = (int64_t)pts * 90;
+ check_newpts (this, buf_pts, (tag_type == FLV_TAG_TYPE_VIDEO));
+ size = this->input->get_length (this->input);
+ if (size > 0) {
+ this->size = size;
+ normpos = (int64_t)this->input->get_current_pos (this->input) * 65535 / size;
+ }
+ while (remaining_bytes) {
+ buf = fifo->buffer_pool_alloc (fifo);
+ buf->type = buf_type;
+ buf->pts = buf_pts + ptsoffs;
+ buf->extra_info->input_time = pts;
+ if (size > 0)
+ buf->extra_info->input_normpos = normpos;
+ buf->size = remaining_bytes > buf->max_size ? buf->max_size : remaining_bytes;
+ remaining_bytes -= buf->size;
+ if (!remaining_bytes)
+ buf_flags |= BUF_FLAG_FRAME_END;
+ buf->decoder_flags = buf_flags;
+ buf_flags &= ~BUF_FLAG_FRAME_START;
+ if (this->input->read (this->input, (char *)buf->content, buf->size) != buf->size) {
+ buf->free_buffer (buf);
+ goto fail;
}
-
fifo->put(fifo, buf);
}
@@ -737,123 +783,162 @@ static int read_flv_packet(demux_flv_t *this, int preview) {
return this->status;
}
-static void seek_flv_file(demux_flv_t *this, off_t seek_pos, int seek_pts) {
- unsigned char buffer[16];
- unsigned int pts = this->cur_pts;
- int len = 0;
- int next_tag = 0;
- int do_rewind = (seek_pts < this->cur_pts);
- int i;
-
- lprintf(" seeking %s to %d...\n",
- do_rewind ? "backward" : "forward", seek_pts);
+static void seek_flv_file (demux_flv_t *this, off_t seek_pos, int seek_pts) {
+ int i;
+ /* we start where we are */
+ off_t pos1, pos2, size, used, found;
+ unsigned char buf[4096], *p1, *p2;
+ unsigned int now = 0, fpts = this->cur_pts, try;
+
+ size = this->input->get_length (this->input);
+ if (size > 0)
+ this->size = size;
+ found = this->input->get_current_pos (this->input) + 4;
+ if (!seek_pos && this->length)
+ pos2 = (uint64_t)size * seek_pts / this->length;
+ else
+ pos2 = (uint64_t)size * seek_pos / 65535;
+
+ xprintf (this->xine, XINE_VERBOSITY_DEBUG, "demux_flv: seek (%u.%03u, %"PRId64")\n",
+ seek_pts / 1000, seek_pts % 1000, (int64_t)pos2);
+
+ /* force send newpts */
+ this->buf_flag_seek = 1;
+ /* neither fileposition nor time given, restart at beginning */
if (seek_pos == 0 && seek_pts == 0) {
this->input->seek(this->input, this->start, SEEK_SET);
this->cur_pts = 0;
+ this->zero_pts_count = 0;
return;
}
-
- if (this->index) {
- if (do_rewind) {
- for (i = this->num_indices-1; i > 0; i--) {
- if (this->index[i-1].pts < seek_pts)
- break;
- }
+
+ /* use file index for time based seek (if we got 1) */
+ if (seek_pts && this->index) {
+ flv_index_entry_t *x;
+ uint32_t a = 0, b, c = this->num_indices;
+ while (a + 1 < c) {
+ b = (a + c) >> 1;
+ if (this->index[b].pts <= seek_pts) a = b; else c = b;
}
- else {
- for (i = 0; i < (this->num_indices-1); i++) {
- if (this->index[i+1].pts > seek_pts)
- break;
+ x = &this->index[a];
+ if ((x->offset >= this->start + 4) && (x->offset + 15 < size)) {
+ this->input->seek (this->input, x->offset, SEEK_SET);
+ this->input->read (this->input, (char *)buf, 15);
+ if (!buf[8] && !buf[9] && !buf[10] && (
+ ((buf[0] == FLV_TAG_TYPE_VIDEO) && ((buf[11] >> 4) == 1)) ||
+ (buf[0] == FLV_TAG_TYPE_AUDIO)
+ )) {
+ xprintf (this->xine, XINE_VERBOSITY_DEBUG,
+ "demux_flv: seek_index (%u.%03u, %"PRId64")\n",
+ x->pts / 1000, x->pts % 1000, (int64_t)x->offset);
+ this->input->seek (this->input, x->offset - 4, SEEK_SET);
+ this->cur_pts = x->pts;
+ return;
}
}
-
- if (this->index[i].offset >= this->start+4) {
- lprintf(" seeking to index entry %d (pts:%u, offset:%u).\n",
- i, this->index[i].pts, this->index[i].offset);
-
- this->input->seek(this->input, this->index[i].offset-4, SEEK_SET);
- this->cur_pts = this->index[i].pts;
- }
+ xprintf (this->xine, XINE_VERBOSITY_LOG, _("demux_flv: Not using broken seek index.\n"));
+ }
+ /* Up to 4 zero pts are OK (2 AAC/AVC sequence headers, 2 av tags).
+ Otherwise, the file is non seekable. Try a size based seek. */
+ if (this->zero_pts_count > 8) {
+ xprintf (this->xine, XINE_VERBOSITY_LOG,
+ _("demux_flv: This file is non seekable. A/V lag may occur.\n"
+ " Recommend fixing the file with some flvtool.\n"));
+ seek_pts = 0;
}
- else if (seek_pos && this->videocodec && abs(seek_pts-this->cur_pts) > 300000) {
- off_t pos, size;
-
- pos = this->input->get_current_pos(this->input);
- size = this->filesize ? : this->input->get_length(this->input);
- this->input->seek(this->input, (uint64_t)size * seek_pos / 65535, SEEK_SET);
- lprintf(" resyncing...\n");
-
- /* resync */
- for (i = 0; i < 200000; i++) {
- uint8_t buf[4];
- if (this->input->read(this->input, buf, 1) < 1) {
- this->status = DEMUX_FINISHED;
- return;
- }
- if (buf[0] == FLV_TAG_TYPE_VIDEO) {
- this->input->seek(this->input, 7, SEEK_CUR);
- if (this->input->read(this->input, buf, 4) < 4) {
- this->status = DEMUX_FINISHED;
- return;
- }
- /* check StreamID and CodecID */
- if ( _X_ME_32(buf) == ME_FOURCC(0, 0, 0, (this->videocodec | 0x10)) ) {
- this->input->seek(this->input, -16, SEEK_CUR);
- lprintf(" ...resynced after %d bytes\n", i);
- return;
- }
- this->input->seek(this->input, -11, SEEK_CUR);
+ /* step 1: phonebook search. Estimate file position, find next tag header,
+ check time, make better estimation, repeat */
+ for (try = 4; try && (!seek_pts || abs ((int)seek_pts - (int)fpts) > 800); try--) {
+ pos1 = found;
+ found = 0;
+ this->input->seek (this->input, pos2, SEEK_SET);
+ used = this->input->read (this->input, (char *)buf + 4096 - 12, 12);
+ for (i = 0; !found && (i < 50); i++) {
+ memcpy (buf, buf + 4096 - 12, 12);
+ used = this->input->read (this->input, (char *)buf + 12, 4096 - 12);
+ if (used <= 0) break;
+ p1 = buf;
+ p2 = buf + used + 12;
+ while (!found && (p1 + 11 < p2)) switch (*p1++) {
+ case FLV_TAG_TYPE_AUDIO:
+ if (p1[7] || p1[8] || p1[9] || ((p1[10] >> 4) != this->audiocodec)) continue;
+ found = pos2 + (p1 - 1 - buf);
+ break;
+ case FLV_TAG_TYPE_VIDEO:
+ if (p1[7] || p1[8] || p1[9] || ((p1[10] & 0x0f) != this->videocodec)) continue;
+ found = pos2 + (p1 - 1 - buf);
+ break;
}
+ pos2 += 4096 - 12;
}
-
- lprintf(" ...resync failed!\n");
- this->input->seek(this->input, pos, SEEK_SET);
+ if (found) {
+ fpts = gettimestamp (p1, 3);
+ if (seek_pts && fpts) pos2 = (uint64_t)found * seek_pts / fpts;
+ else try = 1;
+ xprintf (this->xine, XINE_VERBOSITY_DEBUG,
+ "demux_flv: seek_quick (%u.%03u, %"PRId64")\n",
+ fpts / 1000, fpts % 1000, (int64_t)found);
+ } else found = pos1;
}
- else if (seek_pts) {
- while (do_rewind ? (seek_pts < this->cur_pts) : (seek_pts > this->cur_pts)) {
- unsigned char tag_type;
- int data_size;
- int ptag_size;
-
- if (next_tag)
- this->input->seek(this->input, next_tag, SEEK_CUR);
-
- len = this->input->read(this->input, buffer, 16);
- if (len != 16) {
- len = (len < 0) ? 0 : len;
- break;
- }
- ptag_size = _X_BE_32(&buffer[0]);
- tag_type = buffer[4];
- data_size = _X_BE_24(&buffer[5]);
- pts = _X_BE_24(&buffer[8]) | (buffer[11] << 24);
-
- if (do_rewind) {
- if (!ptag_size)
- break; /* beginning of movie */
- next_tag = -(ptag_size + 16 + 4);
- }
- else {
- next_tag = data_size - 1;
+ /* step 2: Traverse towards the desired time */
+ if (seek_pts) {
+ pos1 = 0;
+ pos2 = found;
+ i = 0;
+ while (1) {
+ if (pos2 < this->start + 4) break;
+ this->input->seek (this->input, pos2 - 4, SEEK_SET);
+ if (this->input->read (this->input, (char *)buf, 16) != 16) break;
+ if ((buf[4] == FLV_TAG_TYPE_VIDEO) && ((buf[15] >> 4) == 1)) pos1 = pos2;
+ if ((now = gettimestamp (buf, 8)) == 0) break;
+ if (now >= seek_pts) {
+ if (i > 0) break;
+ if ((i = _X_BE_32 (buf)) == 0) break;
+ found = pos2;
+ pos2 -= i + 4;
+ i = -1;
+ } else {
+ if (i < 0) break;
+ pos2 += _X_BE_24 (&buf[5]) + 15;
+ i = 1;
}
+ }
+ if (pos1) found = pos1;
+ xprintf (this->xine, XINE_VERBOSITY_DEBUG,
+ "demux_flv: seek_traverse (%u.%03u, %"PRId64")\n",
+ now / 1000, now % 1000, (int64_t)(pos2));
+ }
- if (this->flags & FLV_FLAG_HAS_VIDEO) {
- /* sync to video key frame */
- if (tag_type != FLV_TAG_TYPE_VIDEO || (buffer[15] >> 4) != 0x01)
- continue;
- lprintf(" video keyframe found at %d...\n", pts);
- }
- this->cur_pts = pts;
+ /* Go back to previous keyframe */
+ if (this->videocodec) {
+ pos1 = pos2 = found;
+ found = 0;
+ while (1) {
+ if (pos2 < this->start + 4) break;
+ this->input->seek (this->input, pos2 - 4, SEEK_SET);
+ if (this->input->read (this->input, (char *)buf, 16) != 16) break;
+ if ((buf[4] == FLV_TAG_TYPE_VIDEO) && ((buf[15] >> 4) == 1)) {found = pos2; break;}
+ if ((i = _X_BE_32 (buf)) == 0) break;
+ pos2 -= i + 4;
}
+ if (found) {
+ now = gettimestamp (buf, 8);
+ xprintf (this->xine, XINE_VERBOSITY_DEBUG,
+ "demux_flv: seek_keyframe (%u.%03u, %"PRId64")\n",
+ now / 1000, now % 1000, (int64_t)found);
+ } else found = pos1;
+ }
- /* seek back to the beginning of the tag */
- this->input->seek(this->input, -len, SEEK_CUR);
+ /* we are there!! */
+ this->input->seek (this->input, found + 4, SEEK_SET);
+ this->input->read (this->input, (char *)buf, 4);
+ this->cur_pts = gettimestamp (buf, 0);
+ this->input->seek (this->input, found - 4, SEEK_SET);
- lprintf( " seeked to %d.\n", pts);
- }
+ return;
}
@@ -887,8 +972,8 @@ static void demux_flv_send_headers(demux_plugin_t *this_gen) {
for (i = 0; i < 20; i++) {
if (read_flv_packet(this, 1) != DEMUX_OK)
break;
- if (((this->flags & FLV_FLAG_HAS_VIDEO) && this->got_video_header) &&
- ((this->flags & FLV_FLAG_HAS_AUDIO) && this->got_audio_header)) {
+ if (((!(this->flags & FLV_FLAG_HAS_VIDEO)) || this->got_video_header) &&
+ ((!(this->flags & FLV_FLAG_HAS_AUDIO)) || this->got_audio_header)) {
lprintf(" headers sent...\n");
break;
}
@@ -902,21 +987,26 @@ static int demux_flv_seek (demux_plugin_t *this_gen,
this->status = DEMUX_OK;
- if (INPUT_IS_SEEKABLE(this->input)) {
- if (start_pos && !start_time) {
- if (this->length)
- start_time = (int64_t) this->length * start_pos / 65535;
- else if (this->index)
- start_time = this->index[(int)(start_pos * (this->num_indices-1) / 65535)].pts;
- }
+ /* if demux thread is not running, do some init stuff */
+ if (!playing) {
+ this->last_pts[0] = 0;
+ this->last_pts[1] = 0;
+ _x_demux_flush_engine(this->stream);
+ seek_flv_file(this, start_pos, start_time);
+ _x_demux_control_newpts (this->stream, 0, 0);
+ return (this->status);
+ }
+
+ if (start_pos && !start_time)
+ start_time = (int64_t) this->length * start_pos / 65535;
+ /* always allow initial seek (this, 0, 0, 1) after send_headers ().
+ It usually works at least due to xine input cache.
+ Even if not, no problem there. */
+ if ((!start_time && !start_pos) || INPUT_IS_SEEKABLE (this->input)) {
if (!this->length || start_time < this->length) {
+ _x_demux_flush_engine(this->stream);
seek_flv_file(this, start_pos, start_time);
-
- if (playing) {
- this->buf_flag_seek = 1;
- _x_demux_flush_engine(this->stream);
- }
}
}
@@ -956,7 +1046,7 @@ static demux_plugin_t *open_plugin (demux_class_t *class_gen, xine_stream_t *str
input_plugin_t *input) {
demux_flv_t *this;
- this = calloc(1, sizeof(demux_flv_t));
+ this = calloc(1, sizeof (demux_flv_t));
this->xine = stream->xine;
this->stream = stream;
this->input = input;
@@ -994,7 +1084,7 @@ static demux_plugin_t *open_plugin (demux_class_t *class_gen, xine_stream_t *str
static void *init_plugin (xine_t *xine, void *data) {
demux_flv_class_t *this;
- this = calloc(1, sizeof(demux_flv_class_t));
+ this = calloc(1, sizeof (demux_flv_class_t));
this->demux_class.open_plugin = open_plugin;
this->demux_class.description = N_("Flash Video file demux plugin");
@@ -1020,3 +1110,4 @@ const plugin_info_t xine_plugin_info[] EXPORTED = {
{ PLUGIN_DEMUX, 27, "flashvideo", XINE_VERSION_CODE, &demux_info_flv, init_plugin },
{ PLUGIN_NONE, 0, "", 0, NULL, NULL }
};
+