#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"
#include "internal.h"
#include "avio_internal.h"
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);
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;
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;
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);
AMFDataType amf_type;
char str_val[1024];
double num_val;
+ amf_date date;
num_val = 0;
ioc = s->pb;
}
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);
} 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;
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);
static int flv_get_extradata(AVFormatContext *s, AVStream *st, int size)
{
+ int ret;
if (!size)
return 0;
- av_freep(&st->codecpar->extradata);
- if (ff_get_extradata(s, st->codecpar, s->pb, size) < 0)
- return AVERROR(ENOMEM);
+ if ((ret = ff_get_extradata(s, st->codecpar, s->pb, size)) < 0)
+ return ret;
st->internal->need_context_update = 1;
return 0;
}
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)) {
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
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 ||
if (st->codecpar->codec_id == AV_CODEC_ID_AAC && t && !strcmp(t->value, "Omnia A/XE"))
st->codecpar->extradata_size = 2;
- if (st->codecpar->codec_id == AV_CODEC_ID_AAC && 0) {
- MPEG4AudioConfig cfg;
-
- if (avpriv_mpeg4audio_get_config(&cfg, st->codecpar->extradata,
- st->codecpar->extradata_size * 8, 1) >= 0) {
- st->codecpar->channels = cfg.channels;
- st->codecpar->channel_layout = 0;
- if (cfg.ext_sample_rate)
- st->codecpar->sample_rate = cfg.ext_sample_rate;
- else
- st->codecpar->sample_rate = cfg.sample_rate;
- av_log(s, AV_LOG_TRACE, "mp4a config channels %d sample rate %d\n",
- st->codecpar->channels, st->codecpar->sample_rate);
- }
- }
-
ret = FFERROR_REDO;
goto leave;
}
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;
}
}
}
+
+ if (ret >= 0)
+ flv->last_ts = pkt->dts;
+
return ret;
}
.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,
+};