summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/demuxers/demux_flv.c431
1 files changed, 313 insertions, 118 deletions
diff --git a/src/demuxers/demux_flv.c b/src/demuxers/demux_flv.c
index 57f83e002..5bc09905f 100644
--- a/src/demuxers/demux_flv.c
+++ b/src/demuxers/demux_flv.c
@@ -20,11 +20,11 @@
/*
* Flash Video (.flv) File Demuxer
- * by Mike Melanson (melanson@pcisys.net)
+ * by Mike Melanson (melanson@pcisys.net) and Claudio Ciccani (klan@directfb.org)
* For more information on the FLV file format, visit:
* http://download.macromedia.com/pub/flash/flash_file_format_specification.pdf
*
- * $Id: demux_flv.c,v 1.9 2006/07/10 22:08:13 dgp85 Exp $
+ * $Id: demux_flv.c,v 1.10 2006/12/14 18:29:02 klan Exp $
*/
#ifdef HAVE_CONFIG_H
@@ -53,101 +53,232 @@
typedef struct {
demux_plugin_t demux_plugin;
+ xine_t *xine;
xine_stream_t *stream;
fifo_buffer_t *video_fifo;
fifo_buffer_t *audio_fifo;
input_plugin_t *input;
int status;
- unsigned int video_type;
- unsigned int audio_type;
-
- off_t data_start;
- off_t data_size;
-
- unsigned char bih[sizeof(xine_bmiheader)];
- xine_waveformatex wave;
-
+ unsigned char flags;
+ unsigned int movie_start;
+
+ unsigned char got_video;
+ unsigned char got_audio;
+
+ unsigned int cur_pts;
+
+ int64_t last_pts[2];
+ int send_newpts;
+ int buf_flag_seek;
} demux_flv_t ;
typedef struct {
demux_class_t demux_class;
} demux_flv_class_t;
+
+#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_NELLY8 0x05 /* Nellymoser 8KHz */
+#define FLV_SOUND_FORMAT_NELLY 0x06 /* Nellymoser */
+
+#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
+
+
+/* redefine abs as macro to handle 64-bit diffs.
+ i guess llabs may not be available everywhere */
+#define abs(x) ( ((x)<0) ? -(x) : (x) )
+
+#define WRAP_THRESHOLD 220000
+#define PTS_AUDIO 0
+#define PTS_VIDEO 1
+
+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 %lld\n", pts);
+
+ if (pts && (this->send_newpts || (this->last_pts[video] && abs(diff)>WRAP_THRESHOLD))) {
+ lprintf ("diff=%lld\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);
+ }
+ this->send_newpts = 0;
+ this->last_pts[1-video] = 0;
+ }
+
+ if (pts)
+ this->last_pts[video] = pts;
+}
+
/* returns 1 if the FLV file was opened successfully, 0 otherwise */
static int open_flv_file(demux_flv_t *this) {
+ unsigned char buffer[9];
- unsigned char buffer[4];
- off_t first_offset;
-
- if (_x_demux_read_header(this->input, buffer, 4) != 4)
+ if (_x_demux_read_header(this->input, buffer, 9) != 9)
return 0;
if ((buffer[0] != 'F') || (buffer[1] != 'L') || (buffer[2] != 'V'))
return 0;
+
+ if (buffer[3] != 0x01) {
+ xprintf(this->xine, XINE_VERBOSITY_LOG,
+ _("unsupported FLV version (%d).\n"), buffer[3]);
+ return 0;
+ }
- this->video_type = this->audio_type = 0;
- if (buffer[3] & 0x1)
- this->video_type = BUF_VIDEO_FLV1;
-/* buffer[3] * 0x4 indicates audio, possibly always MP3; deal with
- that later */
-
- /* file is qualified at this point; position to start of first packet */
- this->input->seek(this->input, 5, SEEK_SET);
- if (this->input->read(this->input, buffer, 4) != 4)
+ this->flags = buffer[4];
+ if ((this->flags & (FLV_FLAG_HAS_VIDEO | FLV_FLAG_HAS_AUDIO)) == 0) {
+ xprintf(this->xine, XINE_VERBOSITY_LOG,
+ _("neither video nor audio stream in this file.\n"));
return 0;
+ }
- first_offset = BE_32(buffer);
- this->input->seek(this->input, first_offset, SEEK_SET);
+ this->movie_start = BE_32(&buffer[5]);
+ this->input->seek(this->input, this->movie_start, SEEK_SET);
+
lprintf(" qualified FLV file, repositioned @ offset 0x%" PRIxMAX "\n",
- (intmax_t)first_offset);
+ (intmax_t)this->movie_start);
return 1;
}
-static int demux_flv_send_chunk(demux_plugin_t *this_gen) {
-
- demux_flv_t *this = (demux_flv_t *) this_gen;
- buf_element_t *buf = NULL;
- unsigned int remaining_bytes;
- unsigned char chunk_type, sub_type;
- int64_t pts;
-
- unsigned char buffer[12];
-
- lprintf (" sending FLV chunk...\n");
- this->input->seek(this->input, 4, SEEK_CUR);
- if (this->input->read(this->input, buffer, 12) != 12) {
- this->status = DEMUX_FINISHED;
- return this->status;
- }
+static int read_flv_packet(demux_flv_t *this) {
+ fifo_buffer_t *fifo = NULL;
+ buf_element_t *buf = NULL;
+ unsigned char buffer[12];
+ unsigned char tag_type;
+ unsigned int remaining_bytes;
+ unsigned int buf_type = 0;
+ int64_t pts;
+
+ while (1) {
+ lprintf (" reading FLV tag...\n");
+ this->input->seek(this->input, 4, SEEK_CUR);
+ if (this->input->read(this->input, buffer, 11) != 11) {
+ this->status = DEMUX_FINISHED;
+ return this->status;
+ }
- chunk_type = buffer[0];
- remaining_bytes = BE_32(&buffer[0]);
- remaining_bytes &= 0x00FFFFFF;
- pts = BE_32(&buffer[3]);
- pts &= 0x00FFFFFF;
- sub_type = buffer[11];
-
- /* Flash timestamps are in milliseconds; multiply by 90 to get xine pts */
- pts *= 90;
-
- lprintf (" chunk_type = %X, 0x%X -1 bytes, pts %lld, sub-type = %X\n",
- chunk_type, remaining_bytes, pts, sub_type);
-
- /* only handle the chunk right now if chunk type is 9 and lower nibble
- * of sub-type is 2 */
- if ((chunk_type != 9) || ((sub_type & 0x0F) != 2)) {
- this->input->seek(this->input, remaining_bytes - 1, SEEK_CUR);
- } else {
- /* send the chunk off to the video demuxer */
- remaining_bytes--; /* sub-type byte does not count */
+ tag_type = buffer[0];
+ remaining_bytes = BE_24(&buffer[1]);
+ pts = BE_24(&buffer[4]) | (buffer[7] << 24);
+
+ lprintf(" tag_type = 0x%02X, 0x%X bytes, pts %lld\n",
+ tag_type, remaining_bytes, pts/90);
+
+ switch (tag_type) {
+ case FLV_TAG_TYPE_AUDIO:
+ lprintf(" got audio tag..\n");
+ if (this->input->read(this->input, buffer, 1) != 1) {
+ this->status = DEMUX_FINISHED;
+ return this->status;
+ }
+ remaining_bytes--;
+
+ switch (buffer[0] >> 4) {
+ case FLV_SOUND_FORMAT_PCM_BE:
+ buf_type = BUF_AUDIO_LPCM_BE;
+ break;
+ case FLV_SOUND_FORMAT_MP3:
+ buf_type = BUF_AUDIO_MPEG;
+ break;
+ case FLV_SOUND_FORMAT_PCM_LE:
+ buf_type = BUF_AUDIO_LPCM_LE;
+ break;
+ default:
+ lprintf(" unsupported audio format (%d)...\n", buffer[0] >> 4);
+ buf_type = BUF_AUDIO_UNKNOWN;
+ break;
+ }
+
+ fifo = this->audio_fifo;
+ if (!this->got_audio) {
+ /* 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 - ((buffer[0] >> 2) & 3)); /* samplerate */
+ buf->decoder_info[2] = (buffer[0] & 2) ? 16 : 8; /* bits per sample */
+ buf->decoder_info[3] = (buffer[0] & 1) + 1; /* channels */
+ buf->size = 0; /* no extra data */
+ buf->type = buf_type;
+ fifo->put(fifo, buf);
+ this->got_audio = 1;
+ }
+ break;
+
+ case FLV_TAG_TYPE_VIDEO:
+ lprintf(" got video tag..\n");
+ if (this->input->read(this->input, buffer, 1) != 1) {
+ this->status = DEMUX_FINISHED;
+ return this->status;
+ }
+ remaining_bytes--;
+
+ switch (buffer[0] & 0x0F) {
+ case FLV_VIDEO_FORMAT_FLV1:
+ buf_type = BUF_VIDEO_FLV1;
+ break;
+ case FLV_VIDEO_FORMAT_VP6:
+ buf_type = BUF_VIDEO_VP6;
+ break;
+ default:
+ lprintf(" unsupported video format (%d)...\n", buffer[0] & 0x0F);
+ buf_type = BUF_VIDEO_UNKNOWN;
+ break;
+ }
+
+ fifo = this->video_fifo;
+ if (!this->got_video) {
+ /* 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_FRAME_END;
+ buf->decoder_info[0] = 7470; /* initial duration */
+ buf->size = 0;
+ buf->type = buf_type;
+ fifo->put(fifo, buf);
+ this->got_video = 1;
+ }
+ break;
+
+ default:
+ lprintf(" skipping packet...\n");
+ this->input->seek(this->input, remaining_bytes, SEEK_CUR);
+ continue;
+ }
+
while (remaining_bytes) {
- buf = this->video_fifo->buffer_pool_alloc (this->video_fifo);
- buf->type = BUF_VIDEO_FLV1;
- if( this->input->get_length (this->input) )
- buf->extra_info->input_normpos = (int)( (double) this->input->get_current_pos (this->input) *
- 65535 / this->input->get_length (this->input) );
+ buf = fifo->buffer_pool_alloc(fifo);
+ buf->type = buf_type;
+ buf->pts = (int64_t) pts * 90;
+ check_newpts(this, buf->pts, (tag_type == FLV_TAG_TYPE_VIDEO));
+
+ 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 / this->input->get_length(this->input) );
+ }
if (remaining_bytes > buf->max_size)
buf->size = buf->max_size;
@@ -158,25 +289,93 @@ static int demux_flv_send_chunk(demux_plugin_t *this_gen) {
if (!remaining_bytes)
buf->decoder_flags |= BUF_FLAG_FRAME_END;
- if (this->input->read(this->input, buf->content, buf->size) !=
- buf->size) {
+ if (this->input->read(this->input, buf->content, buf->size) != buf->size) {
buf->free_buffer(buf);
this->status = DEMUX_FINISHED;
break;
}
- buf->pts = pts;
- buf->extra_info->input_time = buf->pts / 90;
- this->video_fifo->put(this->video_fifo, buf);
+ fifo->put(fifo, buf);
}
+
+ this->cur_pts = pts;
+ break;
}
+
+ return this->status;
+}
+
+static int seek_flv_file(demux_flv_t *this, int seek_pts) {
+ unsigned char buffer[16];
+ int next_tag = 0;
+ int do_rewind = (seek_pts < this->cur_pts);
+
+ if (this->cur_pts == seek_pts)
+ return this->status;
+
+ if (seek_pts == 0) {
+ this->input->seek(this->input, this->movie_start, SEEK_SET);
+ this->cur_pts = 0;
+ return this->status;
+ }
+
+ lprintf(" seeking %s to %d...\n",
+ do_rewind ? "backward" : "forward", 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;
+ unsigned int pts;
+
+ if (next_tag)
+ this->input->seek(this->input, next_tag, SEEK_CUR);
+
+ if (this->input->read(this->input, buffer, 16) != 16) {
+ this->status = DEMUX_FINISHED;
+ return this->status;
+ }
+
+ ptag_size = BE_32(&buffer[0]);
+ tag_type = buffer[4];
+ data_size = BE_24(&buffer[5]);
+ pts = BE_24(&buffer[8]) | (buffer[11] << 24);
+
+ if (do_rewind) {
+ if (!ptag_size) break;
+ next_tag = -(ptag_size + 16 + 4);
+ }
+ else {
+ next_tag = data_size - 1;
+ }
+
+ 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;
+ }
+
+ /* seek back to the beginning of the tag */
+ this->input->seek(this->input, -16, SEEK_CUR);
+
+ lprintf( " seeked to %d.\n", this->cur_pts);
return this->status;
}
+
+static int demux_flv_send_chunk(demux_plugin_t *this_gen) {
+ demux_flv_t *this = (demux_flv_t *) this_gen;
+
+ return read_flv_packet(this);
+}
+
static void demux_flv_send_headers(demux_plugin_t *this_gen) {
demux_flv_t *this = (demux_flv_t *) this_gen;
- buf_element_t *buf;
+ int i;
this->video_fifo = this->stream->video_fifo;
this->audio_fifo = this->stream->audio_fifo;
@@ -185,24 +384,23 @@ static void demux_flv_send_headers(demux_plugin_t *this_gen) {
/* load stream information */
_x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_VIDEO,
- (this->video_type ? 1 : 0));
+ (this->flags & FLV_FLAG_HAS_VIDEO) ? 1 : 0);
_x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_AUDIO,
- (this->audio_type ? 1 : 0));
+ (this->flags & FLV_FLAG_HAS_AUDIO) ? 1 : 0);
/* send start buffers */
_x_demux_control_start(this->stream);
- /* send init info to decoders; send the bitmapinfo header to the decoder
- * primarily as a formality since there is no real data inside */
- 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] = 7470; /* initial duration */
- memcpy(buf->content, this->bih, sizeof(xine_bmiheader));
- buf->size = sizeof(xine_bmiheader);
- buf->type = BUF_VIDEO_FLV1;
- this->video_fifo->put (this->video_fifo, buf);
-
+ /* find first audio/video packets and send headers */
+ for (i = 0; i < 20; i++) {
+ if (read_flv_packet(this) != DEMUX_OK)
+ break;
+ if (((this->flags & FLV_FLAG_HAS_VIDEO) && this->got_video) &&
+ ((this->flags & FLV_FLAG_HAS_AUDIO) && this->got_audio)) {
+ lprintf(" headers sent...\n");
+ break;
+ }
+ }
}
static int demux_flv_seek (demux_plugin_t *this_gen,
@@ -210,10 +408,16 @@ static int demux_flv_seek (demux_plugin_t *this_gen,
demux_flv_t *this = (demux_flv_t *) this_gen;
- /* if thread is not running, initialize demuxer */
- if( !playing ) {
- this->status = DEMUX_OK;
- }
+ this->status = DEMUX_OK;
+
+ if (this->input->get_capabilities(this->input) & INPUT_CAP_SEEKABLE) {
+ seek_flv_file(this, start_time);
+
+ if (playing) {
+ this->buf_flag_seek = 1;
+ _x_demux_flush_engine(this->stream);
+ }
+ }
return this->status;
}
@@ -247,10 +451,10 @@ static int demux_flv_get_optional_data(demux_plugin_t *this_gen,
static demux_plugin_t *open_plugin (demux_class_t *class_gen, xine_stream_t *stream,
input_plugin_t *input) {
-
- demux_flv_t *this;
+ demux_flv_t *this;
this = xine_xmalloc (sizeof (demux_flv_t));
+ this->xine = stream->xine;
this->stream = stream;
this->input = input;
@@ -267,33 +471,24 @@ static demux_plugin_t *open_plugin (demux_class_t *class_gen, xine_stream_t *str
this->status = DEMUX_FINISHED;
switch (stream->content_detection_method) {
+ case METHOD_BY_EXTENSION:
+ if (!_x_demux_check_extension(input->get_mrl(input), "flv")) {
+ free (this);
+ return NULL;
+ }
+
+ /* falling through is intended */
+ case METHOD_BY_CONTENT:
+ case METHOD_EXPLICIT:
+ if (!open_flv_file(this)) {
+ free (this);
+ return NULL;
+ }
+ break;
- case METHOD_BY_EXTENSION: {
- char *extensions, *mrl;
-
- mrl = input->get_mrl (input);
- extensions = class_gen->get_extensions (class_gen);
-
- if (!_x_demux_check_extension (mrl, extensions)) {
- free (this);
- return NULL;
- }
- }
- /* falling through is intended */
-
- case METHOD_BY_CONTENT:
- case METHOD_EXPLICIT:
-
- if (!open_flv_file(this)) {
+ default:
free (this);
return NULL;
- }
-
- break;
-
- default:
- free (this);
- return NULL;
}
return &this->demux_plugin;
@@ -312,7 +507,7 @@ static char *get_extensions (demux_class_t *this_gen) {
}
static char *get_mimetypes (demux_class_t *this_gen) {
- return NULL;
+ return "video/x-flv";
}
static void class_dispose (demux_class_t *this_gen) {