diff options
-rw-r--r-- | src/demuxers/demux_flv.c | 431 |
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) { |