diff options
Diffstat (limited to 'src/demuxers/demux_flv.c')
-rw-r--r-- | src/demuxers/demux_flv.c | 1283 |
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 } }; + |