X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Fflvdec.c;h=b531a39adc80da8051d2c87f5853e109f60a32a2;hb=5f64f6058e0c23641a68ce7dfe47b1f55efd401c;hp=a2dea464e3697e517415ce8e4a1a6da009c263fc;hpb=7167ac33a8f2c7d063384c267f984f23d2b73854;p=ffmpeg diff --git a/libavformat/flvdec.c b/libavformat/flvdec.c index a2dea464e36..b531a39adc8 100644 --- a/libavformat/flvdec.c +++ b/libavformat/flvdec.c @@ -30,6 +30,7 @@ #include "libavutil/opt.h" #include "libavutil/intfloat.h" #include "libavutil/mathematics.h" +#include "libavutil/time_internal.h" #include "libavcodec/bytestream.h" #include "libavcodec/mpeg4audio.h" #include "avformat.h" @@ -72,9 +73,18 @@ typedef struct FLVContext { int64_t *keyframe_filepositions; int missing_streams; AVRational framerate; + int64_t last_ts; + int64_t time_offset; + int64_t time_pos; } FLVContext; -static int probe(AVProbeData *p, int live) +/* AMF date type */ +typedef struct amf_date { + double milliseconds; + int16_t timezone; +} amf_date; + +static int probe(const AVProbeData *p, int live) { const uint8_t *d = p->buf; unsigned offset = AV_RB32(d + 5); @@ -93,16 +103,30 @@ static int probe(AVProbeData *p, int live) return 0; } -static int flv_probe(AVProbeData *p) +static int flv_probe(const AVProbeData *p) { return probe(p, 0); } -static int live_flv_probe(AVProbeData *p) +static int live_flv_probe(const AVProbeData *p) { return probe(p, 1); } +static int kux_probe(const AVProbeData *p) +{ + const uint8_t *d = p->buf; + + if (d[0] == 'K' && + d[1] == 'D' && + d[2] == 'K' && + d[3] == 0 && + d[4] == 0) { + return AVPROBE_SCORE_EXTENSION + 1; + } + return 0; +} + static void add_keyframes_index(AVFormatContext *s) { FLVContext *flv = s->priv_data; @@ -143,7 +167,9 @@ static AVStream *create_stream(AVFormatContext *s, int codec_type) st->codecpar->codec_type = codec_type; if (s->nb_streams>=3 ||( s->nb_streams==2 && s->streams[0]->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE - && s->streams[1]->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE)) + && s->streams[1]->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE + && s->streams[0]->codecpar->codec_type != AVMEDIA_TYPE_DATA + && s->streams[1]->codecpar->codec_type != AVMEDIA_TYPE_DATA)) s->ctx_flags &= ~AVFMTCTX_NOHEADER; if (codec_type == AVMEDIA_TYPE_AUDIO) { st->codecpar->bit_rate = flv->audio_bit_rate; @@ -381,7 +407,7 @@ static int parse_keyframes_index(AVFormatContext *s, AVIOContext *ioc, int64_t m int64_t initial_pos = avio_tell(ioc); if (flv->keyframe_count > 0) { - av_log(s, AV_LOG_DEBUG, "keyframes have been paresed\n"); + av_log(s, AV_LOG_DEBUG, "keyframes have been parsed\n"); return 0; } av_assert0(!flv->keyframe_times); @@ -466,6 +492,7 @@ static int amf_parse_object(AVFormatContext *s, AVStream *astream, AMFDataType amf_type; char str_val[1024]; double num_val; + amf_date date; num_val = 0; ioc = s->pb; @@ -537,7 +564,9 @@ static int amf_parse_object(AVFormatContext *s, AVStream *astream, } break; case AMF_DATA_TYPE_DATE: - avio_skip(ioc, 8 + 2); // timestamp (double) and UTC offset (int16) + // timestamp (double) and UTC offset (int16) + date.milliseconds = av_int2double(avio_rb64(ioc)); + date.timezone = avio_rb16(ioc); break; default: // unsupported type, we couldn't skip av_log(s, AV_LOG_ERROR, "unsupported amf type %d\n", amf_type); @@ -636,8 +665,18 @@ static int amf_parse_object(AVFormatContext *s, AVStream *astream, } else if (amf_type == AMF_DATA_TYPE_NUMBER) { snprintf(str_val, sizeof(str_val), "%.f", num_val); av_dict_set(&s->metadata, key, str_val, 0); - } else if (amf_type == AMF_DATA_TYPE_STRING) + } else if (amf_type == AMF_DATA_TYPE_STRING) { av_dict_set(&s->metadata, key, str_val, 0); + } else if (amf_type == AMF_DATA_TYPE_DATE) { + time_t time; + struct tm t; + char datestr[128]; + time = date.milliseconds / 1000; // to seconds + localtime_r(&time, &t); + strftime(datestr, sizeof(datestr), "%a, %d %b %Y %H:%M:%S %z", &t); + + av_dict_set(&s->metadata, key, datestr, 0); + } } return 0; @@ -713,6 +752,10 @@ static int flv_read_header(AVFormatContext *s) int offset; int pre_tag_size = 0; + /* Actual FLV data at 0xe40000 in KUX file */ + if(!strcmp(s->iformat->name, "kux")) + avio_skip(s->pb, 0xe40000); + avio_skip(s->pb, 4); flags = avio_r8(s->pb); @@ -915,6 +958,18 @@ static int resync(AVFormatContext *s) flv->resync_buffer[j ] = flv->resync_buffer[j1] = avio_r8(s->pb); + if (i >= 8 && pos) { + uint8_t *d = flv->resync_buffer + j1 - 8; + if (d[0] == 'F' && + d[1] == 'L' && + d[2] == 'V' && + d[3] < 5 && d[5] == 0) { + av_log(s, AV_LOG_WARNING, "Concatenated FLV detected, might fail to demux, decode and seek %"PRId64"\n", flv->last_ts); + flv->time_offset = flv->last_ts + 1; + flv->time_pos = avio_tell(s->pb); + } + } + if (i > 22) { unsigned lsize2 = AV_RB32(flv->resync_buffer + j1 - 4); if (lsize2 >= 11 && lsize2 + 8LL < FFMIN(i, RESYNC_BUFFER_SIZE)) { @@ -949,135 +1004,144 @@ static int flv_read_packet(AVFormatContext *s, AVPacket *pkt) retry: /* pkt size is repeated at end. skip it */ - pos = avio_tell(s->pb); - type = (avio_r8(s->pb) & 0x1F); - orig_size = - size = avio_rb24(s->pb); - flv->sum_flv_tag_size += size + 11; - dts = avio_rb24(s->pb); - dts |= (unsigned)avio_r8(s->pb) << 24; - av_log(s, AV_LOG_TRACE, "type:%d, size:%d, last:%d, dts:%"PRId64" pos:%"PRId64"\n", type, size, last, dts, avio_tell(s->pb)); - if (avio_feof(s->pb)) - return AVERROR_EOF; - avio_skip(s->pb, 3); /* stream id, always 0 */ - flags = 0; - - if (flv->validate_next < flv->validate_count) { - int64_t validate_pos = flv->validate_index[flv->validate_next].pos; - if (pos == validate_pos) { - if (FFABS(dts - flv->validate_index[flv->validate_next].dts) <= - VALIDATE_INDEX_TS_THRESH) { - flv->validate_next++; - } else { - clear_index_entries(s, validate_pos); - flv->validate_count = 0; - } - } else if (pos > validate_pos) { + pos = avio_tell(s->pb); + type = (avio_r8(s->pb) & 0x1F); + orig_size = + size = avio_rb24(s->pb); + flv->sum_flv_tag_size += size + 11; + dts = avio_rb24(s->pb); + dts |= (unsigned)avio_r8(s->pb) << 24; + av_log(s, AV_LOG_TRACE, "type:%d, size:%d, last:%d, dts:%"PRId64" pos:%"PRId64"\n", type, size, last, dts, avio_tell(s->pb)); + if (avio_feof(s->pb)) + return AVERROR_EOF; + avio_skip(s->pb, 3); /* stream id, always 0 */ + flags = 0; + + if (flv->validate_next < flv->validate_count) { + int64_t validate_pos = flv->validate_index[flv->validate_next].pos; + if (pos == validate_pos) { + if (FFABS(dts - flv->validate_index[flv->validate_next].dts) <= + VALIDATE_INDEX_TS_THRESH) { + flv->validate_next++; + } else { clear_index_entries(s, validate_pos); flv->validate_count = 0; } + } else if (pos > validate_pos) { + clear_index_entries(s, validate_pos); + flv->validate_count = 0; } + } - if (size == 0) { - ret = FFERROR_REDO; - goto leave; - } + if (size == 0) { + ret = FFERROR_REDO; + goto leave; + } - next = size + avio_tell(s->pb); - - if (type == FLV_TAG_TYPE_AUDIO) { - stream_type = FLV_STREAM_TYPE_AUDIO; - flags = avio_r8(s->pb); - size--; - } else if (type == FLV_TAG_TYPE_VIDEO) { - stream_type = FLV_STREAM_TYPE_VIDEO; - flags = avio_r8(s->pb); - size--; - if ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_VIDEO_INFO_CMD) - goto skip; - } else if (type == FLV_TAG_TYPE_META) { - stream_type=FLV_STREAM_TYPE_DATA; - if (size > 13 + 1 + 4) { // Header-type metadata stuff - int type; - meta_pos = avio_tell(s->pb); - type = flv_read_metabody(s, next); - if (type == 0 && dts == 0 || type < 0 || type == TYPE_UNKNOWN) { - if (type < 0 && flv->validate_count && - flv->validate_index[0].pos > next && - flv->validate_index[0].pos - 4 < next - ) { - av_log(s, AV_LOG_WARNING, "Adjusting next position due to index mismatch\n"); - next = flv->validate_index[0].pos - 4; - } - goto skip; - } else if (type == TYPE_ONTEXTDATA) { - avpriv_request_sample(s, "OnTextData packet"); - return flv_data_packet(s, pkt, dts, next); - } else if (type == TYPE_ONCAPTION) { - return flv_data_packet(s, pkt, dts, next); + next = size + avio_tell(s->pb); + + if (type == FLV_TAG_TYPE_AUDIO) { + stream_type = FLV_STREAM_TYPE_AUDIO; + flags = avio_r8(s->pb); + size--; + } else if (type == FLV_TAG_TYPE_VIDEO) { + stream_type = FLV_STREAM_TYPE_VIDEO; + flags = avio_r8(s->pb); + size--; + if ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_VIDEO_INFO_CMD) + goto skip; + } else if (type == FLV_TAG_TYPE_META) { + stream_type=FLV_STREAM_TYPE_SUBTITLE; + if (size > 13 + 1 + 4) { // Header-type metadata stuff + int type; + meta_pos = avio_tell(s->pb); + type = flv_read_metabody(s, next); + if (type == 0 && dts == 0 || type < 0) { + if (type < 0 && flv->validate_count && + flv->validate_index[0].pos > next && + flv->validate_index[0].pos - 4 < next + ) { + av_log(s, AV_LOG_WARNING, "Adjusting next position due to index mismatch\n"); + next = flv->validate_index[0].pos - 4; } - avio_seek(s->pb, meta_pos, SEEK_SET); + goto skip; + } else if (type == TYPE_ONTEXTDATA) { + avpriv_request_sample(s, "OnTextData packet"); + return flv_data_packet(s, pkt, dts, next); + } else if (type == TYPE_ONCAPTION) { + return flv_data_packet(s, pkt, dts, next); + } else if (type == TYPE_UNKNOWN) { + stream_type = FLV_STREAM_TYPE_DATA; } - } else { - av_log(s, AV_LOG_DEBUG, - "Skipping flv packet: type %d, size %d, flags %d.\n", - type, size, flags); + avio_seek(s->pb, meta_pos, SEEK_SET); + } + } else { + av_log(s, AV_LOG_DEBUG, + "Skipping flv packet: type %d, size %d, flags %d.\n", + type, size, flags); skip: - if (avio_seek(s->pb, next, SEEK_SET) != next) { - // This can happen if flv_read_metabody above read past - // next, on a non-seekable input, and the preceding data has - // been flushed out from the IO buffer. - av_log(s, AV_LOG_ERROR, "Unable to seek to the next packet\n"); - return AVERROR_INVALIDDATA; - } - ret = FFERROR_REDO; - goto leave; + if (avio_seek(s->pb, next, SEEK_SET) != next) { + // This can happen if flv_read_metabody above read past + // next, on a non-seekable input, and the preceding data has + // been flushed out from the IO buffer. + av_log(s, AV_LOG_ERROR, "Unable to seek to the next packet\n"); + return AVERROR_INVALIDDATA; } + ret = FFERROR_REDO; + goto leave; + } - /* skip empty data packets */ - if (!size) { - ret = FFERROR_REDO; - goto leave; - } + /* skip empty data packets */ + if (!size) { + ret = FFERROR_REDO; + goto leave; + } - /* now find stream */ - for (i = 0; i < s->nb_streams; i++) { - st = s->streams[i]; - if (stream_type == FLV_STREAM_TYPE_AUDIO) { - if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && - (s->audio_codec_id || flv_same_audio_codec(st->codecpar, flags))) - break; - } else if (stream_type == FLV_STREAM_TYPE_VIDEO) { - if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && - (s->video_codec_id || flv_same_video_codec(st->codecpar, flags))) - break; - } else if (stream_type == FLV_STREAM_TYPE_DATA) { - if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) - break; - } + /* now find stream */ + for (i = 0; i < s->nb_streams; i++) { + st = s->streams[i]; + if (stream_type == FLV_STREAM_TYPE_AUDIO) { + if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && + (s->audio_codec_id || flv_same_audio_codec(st->codecpar, flags))) + break; + } else if (stream_type == FLV_STREAM_TYPE_VIDEO) { + if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && + (s->video_codec_id || flv_same_video_codec(st->codecpar, flags))) + break; + } else if (stream_type == FLV_STREAM_TYPE_SUBTITLE) { + if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) + break; + } else if (stream_type == FLV_STREAM_TYPE_DATA) { + if (st->codecpar->codec_type == AVMEDIA_TYPE_DATA) + break; } - if (i == s->nb_streams) { - static const enum AVMediaType stream_types[] = {AVMEDIA_TYPE_VIDEO, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_SUBTITLE}; - st = create_stream(s, stream_types[stream_type]); - if (!st) - return AVERROR(ENOMEM); + } + if (i == s->nb_streams) { + static const enum AVMediaType stream_types[] = {AVMEDIA_TYPE_VIDEO, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_SUBTITLE, AVMEDIA_TYPE_DATA}; + st = create_stream(s, stream_types[stream_type]); + if (!st) + return AVERROR(ENOMEM); - } - av_log(s, AV_LOG_TRACE, "%d %X %d \n", stream_type, flags, st->discard); - - if ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) && - ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY || - stream_type == FLV_STREAM_TYPE_AUDIO)) - av_add_index_entry(st, pos, dts, size, 0, AVINDEX_KEYFRAME); - - if ( (st->discard >= AVDISCARD_NONKEY && !((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY || (stream_type == FLV_STREAM_TYPE_AUDIO))) - ||(st->discard >= AVDISCARD_BIDIR && ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_DISP_INTER && (stream_type == FLV_STREAM_TYPE_VIDEO))) - || st->discard >= AVDISCARD_ALL - ) { - avio_seek(s->pb, next, SEEK_SET); - ret = FFERROR_REDO; - goto leave; - } + } + av_log(s, AV_LOG_TRACE, "%d %X %d \n", stream_type, flags, st->discard); + + if (flv->time_pos <= pos) { + dts += flv->time_offset; + } + + if ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) && + ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY || + stream_type == FLV_STREAM_TYPE_AUDIO)) + av_add_index_entry(st, pos, dts, size, 0, AVINDEX_KEYFRAME); + + if ( (st->discard >= AVDISCARD_NONKEY && !((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY || (stream_type == FLV_STREAM_TYPE_AUDIO))) + ||(st->discard >= AVDISCARD_BIDIR && ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_DISP_INTER && (stream_type == FLV_STREAM_TYPE_VIDEO))) + || st->discard >= AVDISCARD_ALL + ) { + avio_seek(s->pb, next, SEEK_SET); + ret = FFERROR_REDO; + goto leave; + } // if not streamed and no duration from metadata then seek to end to find // the duration from the timestamps @@ -1151,8 +1215,10 @@ retry_duration: if (ret < 0) return ret; size -= ret; - } else if (stream_type == FLV_STREAM_TYPE_DATA) { + } else if (stream_type == FLV_STREAM_TYPE_SUBTITLE) { st->codecpar->codec_id = AV_CODEC_ID_TEXT; + } else if (stream_type == FLV_STREAM_TYPE_DATA) { + st->codecpar->codec_id = AV_CODEC_ID_NONE; // Opaque AMF data } if (st->codecpar->codec_id == AV_CODEC_ID_AAC || @@ -1253,6 +1319,7 @@ retry_duration: if ( stream_type == FLV_STREAM_TYPE_AUDIO || ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY) || + stream_type == FLV_STREAM_TYPE_SUBTITLE || stream_type == FLV_STREAM_TYPE_DATA) pkt->flags |= AV_PKT_FLAG_KEY; @@ -1272,6 +1339,10 @@ leave: } } } + + if (ret >= 0) + flv->last_ts = pkt->dts; + return ret; } @@ -1333,3 +1404,23 @@ AVInputFormat ff_live_flv_demuxer = { .priv_class = &live_flv_class, .flags = AVFMT_TS_DISCONT }; + +static const AVClass kux_class = { + .class_name = "kuxdec", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +AVInputFormat ff_kux_demuxer = { + .name = "kux", + .long_name = NULL_IF_CONFIG_SMALL("KUX (YouKu)"), + .priv_data_size = sizeof(FLVContext), + .read_probe = kux_probe, + .read_header = flv_read_header, + .read_packet = flv_read_packet, + .read_seek = flv_read_seek, + .read_close = flv_read_close, + .extensions = "kux", + .priv_class = &kux_class, +};