#include "avformat.h"
#include "riff.h"
#include "isom.h"
-#include "libavcodec/mpeg4audio.h"
-#include "libavcodec/mpegaudiodata.h"
#include "libavcodec/get_bits.h"
#if CONFIG_ZLIB
get_be16(pb); // unknown
snprintf(buf, sizeof(buf), "%d", get_be16(pb));
- av_metadata_set(&c->fc->metadata, "track", buf);
+ av_metadata_set2(&c->fc->metadata, "track", buf, 0);
get_be16(pb); // total tracks
switch (atom.type) {
case MKTAG(0xa9,'n','a','m'): key = "title"; break;
case MKTAG(0xa9,'a','u','t'):
- case MKTAG(0xa9,'A','R','T'): key = "author"; break;
+ case MKTAG(0xa9,'A','R','T'): key = "artist"; break;
case MKTAG(0xa9,'w','r','t'): key = "composer"; break;
case MKTAG( 'c','p','r','t'):
case MKTAG(0xa9,'c','p','y'): key = "copyright"; break;
case MKTAG(0xa9,'d','a','y'): key = "date"; break;
case MKTAG(0xa9,'g','e','n'): key = "genre"; break;
case MKTAG(0xa9,'t','o','o'):
+ case MKTAG(0xa9,'s','w','r'): key = "encoder"; break;
case MKTAG(0xa9,'e','n','c'): key = "encoder"; break;
case MKTAG( 'd','e','s','c'): key = "description";break;
case MKTAG( 'l','d','e','s'): key = "synopsis"; break;
get_buffer(pb, str, str_size);
str[str_size] = 0;
}
- av_metadata_set(&c->fc->metadata, key, str);
+ av_metadata_set2(&c->fc->metadata, key, str, 0);
if (*language && strcmp(language, "und")) {
snprintf(key2, sizeof(key2), "%s-%s", key, language);
- av_metadata_set(&c->fc->metadata, key2, str);
+ av_metadata_set2(&c->fc->metadata, key2, str, 0);
}
}
#ifdef DEBUG_METADATA
return 0;
}
+static int mov_read_chpl(MOVContext *c, ByteIOContext *pb, MOVAtom atom)
+{
+ int64_t start;
+ int i, nb_chapters, str_len, version;
+ char str[256+1];
+
+ if ((atom.size -= 5) < 0)
+ return 0;
+
+ version = get_byte(pb);
+ get_be24(pb);
+ if (version)
+ get_be32(pb); // ???
+ nb_chapters = get_byte(pb);
+
+ for (i = 0; i < nb_chapters; i++) {
+ if (atom.size < 9)
+ return 0;
+
+ start = get_be64(pb);
+ str_len = get_byte(pb);
+
+ if ((atom.size -= 9+str_len) < 0)
+ return 0;
+
+ get_buffer(pb, str, str_len);
+ str[str_len] = 0;
+ ff_new_chapter(c->fc, i, (AVRational){1,10000000}, start, AV_NOPTS_VALUE, str);
+ }
+ return 0;
+}
+
static int mov_read_default(MOVContext *c, ByteIOContext *pb, MOVAtom atom)
{
int64_t total_size = 0;
a.size = get_be32(pb);
a.type = get_le32(pb);
}
+ dprintf(c->fc, "type: %08x '%.4s' parent:'%.4s' sz: %"PRId64" %"PRId64" %"PRId64"\n",
+ a.type, (char*)&a.type, (char*)&atom.type, a.size, total_size, atom.size);
total_size += 8;
- dprintf(c->fc, "type: %08x %.4s sz: %"PRIx64" %"PRIx64" %"PRIx64"\n",
- a.type, (char*)&a.type, a.size, atom.size, total_size);
if (a.size == 1) { /* 64 bit extended size */
a.size = get_be64(pb) - 8;
total_size += 8;
uint32_t size = get_be32(pb);
int64_t next = url_ftell(pb) + size - 4;
+ if (size < 12)
+ return -1;
+
dref->type = get_le32(pb);
get_be32(pb); // version + flags
dprintf(c->fc, "type %.4s size %d\n", (char*)&dref->type, size);
return 0;
}
-int ff_mp4_read_descr_len(ByteIOContext *pb)
-{
- int len = 0;
- int count = 4;
- while (count--) {
- int c = get_byte(pb);
- len = (len << 7) | (c & 0x7f);
- if (!(c & 0x80))
- break;
- }
- return len;
-}
-
-static int mp4_read_descr(AVFormatContext *fc, ByteIOContext *pb, int *tag)
-{
- int len;
- *tag = get_byte(pb);
- len = ff_mp4_read_descr_len(pb);
- dprintf(fc, "MPEG4 description: tag=0x%02x len=%d\n", *tag, len);
- return len;
-}
-
-#define MP4ESDescrTag 0x03
-#define MP4DecConfigDescrTag 0x04
-#define MP4DecSpecificDescrTag 0x05
-
-static const AVCodecTag mp4_audio_types[] = {
- { CODEC_ID_MP3ON4, AOT_PS }, /* old mp3on4 draft */
- { CODEC_ID_MP3ON4, AOT_L1 }, /* layer 1 */
- { CODEC_ID_MP3ON4, AOT_L2 }, /* layer 2 */
- { CODEC_ID_MP3ON4, AOT_L3 }, /* layer 3 */
- { CODEC_ID_MP4ALS, AOT_ALS }, /* MPEG-4 ALS */
- { CODEC_ID_NONE, AOT_NULL },
-};
-
int ff_mov_read_esds(AVFormatContext *fc, ByteIOContext *pb, MOVAtom atom)
{
AVStream *st;
st = fc->streams[fc->nb_streams-1];
get_be32(pb); /* version + flags */
- len = mp4_read_descr(fc, pb, &tag);
+ len = ff_mp4_read_descr(fc, pb, &tag);
if (tag == MP4ESDescrTag) {
get_be16(pb); /* ID */
get_byte(pb); /* priority */
} else
get_be16(pb); /* ID */
- len = mp4_read_descr(fc, pb, &tag);
- if (tag == MP4DecConfigDescrTag) {
- int object_type_id = get_byte(pb);
- get_byte(pb); /* stream type */
- get_be24(pb); /* buffer size db */
- get_be32(pb); /* max bitrate */
- get_be32(pb); /* avg bitrate */
-
- st->codec->codec_id= ff_codec_get_id(ff_mp4_obj_type, object_type_id);
- dprintf(fc, "esds object type id 0x%02x\n", object_type_id);
- len = mp4_read_descr(fc, pb, &tag);
- if (tag == MP4DecSpecificDescrTag) {
- dprintf(fc, "Specific MPEG4 header len=%d\n", len);
- if((uint64_t)len > (1<<30))
- return -1;
- st->codec->extradata = av_mallocz(len + FF_INPUT_BUFFER_PADDING_SIZE);
- if (!st->codec->extradata)
- return AVERROR(ENOMEM);
- get_buffer(pb, st->codec->extradata, len);
- st->codec->extradata_size = len;
- if (st->codec->codec_id == CODEC_ID_AAC) {
- MPEG4AudioConfig cfg;
- ff_mpeg4audio_get_config(&cfg, st->codec->extradata,
- st->codec->extradata_size);
- st->codec->channels = cfg.channels;
- if (cfg.object_type == 29 && cfg.sampling_index < 3) // old mp3on4
- st->codec->sample_rate = ff_mpa_freq_tab[cfg.sampling_index];
- else
- st->codec->sample_rate = cfg.sample_rate; // ext sample rate ?
- dprintf(fc, "mp4a config channels %d obj %d ext obj %d "
- "sample rate %d ext sample rate %d\n", st->codec->channels,
- cfg.object_type, cfg.ext_object_type,
- cfg.sample_rate, cfg.ext_sample_rate);
- if (!(st->codec->codec_id = ff_codec_get_id(mp4_audio_types,
- cfg.object_type)))
- st->codec->codec_id = CODEC_ID_AAC;
- }
- }
- }
+ len = ff_mp4_read_descr(fc, pb, &tag);
+ if (tag == MP4DecConfigDescrTag)
+ ff_mp4_read_dec_config_descr(fc, st, pb);
return 0;
}
return ff_mov_read_esds(c->fc, pb, atom);
}
+static int mov_read_dac3(MOVContext *c, ByteIOContext *pb, MOVAtom atom)
+{
+ AVStream *st;
+ int ac3info, acmod, lfeon;
+
+ if (c->fc->nb_streams < 1)
+ return 0;
+ st = c->fc->streams[c->fc->nb_streams-1];
+
+ ac3info = get_be24(pb);
+ acmod = (ac3info >> 11) & 0x7;
+ lfeon = (ac3info >> 10) & 0x1;
+ st->codec->channels = ((int[]){2,1,2,3,3,4,4,5})[acmod] + lfeon;
+
+ return 0;
+}
+
static int mov_read_pasp(MOVContext *c, ByteIOContext *pb, MOVAtom atom)
{
const int num = get_be32(pb);
return 0;
st = c->fc->streams[c->fc->nb_streams-1];
- if (den != 0) {
- if ((st->sample_aspect_ratio.den != 1 || st->sample_aspect_ratio.num) && // default
- (den != st->sample_aspect_ratio.den || num != st->sample_aspect_ratio.num))
- av_log(c->fc, AV_LOG_WARNING,
- "sample aspect ratio already set to %d:%d, overriding by 'pasp' atom\n",
- st->sample_aspect_ratio.num, st->sample_aspect_ratio.den);
+ if ((st->sample_aspect_ratio.den != 1 || st->sample_aspect_ratio.num) && // default
+ (den != st->sample_aspect_ratio.den || num != st->sample_aspect_ratio.num)) {
+ av_log(c->fc, AV_LOG_WARNING,
+ "sample aspect ratio already set to %d:%d, ignoring 'pasp' atom (%d:%d)\n",
+ st->sample_aspect_ratio.num, st->sample_aspect_ratio.den,
+ num, den);
+ } else if (den != 0) {
st->sample_aspect_ratio.num = num;
st->sample_aspect_ratio.den = den;
}
if (strcmp(type, "qt "))
c->isom = 1;
av_log(c->fc, AV_LOG_DEBUG, "ISO: File Type Major Brand: %.4s\n",(char *)&type);
- av_metadata_set(&c->fc->metadata, "major_brand", type);
+ av_metadata_set2(&c->fc->metadata, "major_brand", type, 0);
minor_ver = get_be32(pb); /* minor version */
snprintf(minor_ver_str, sizeof(minor_ver_str), "%d", minor_ver);
- av_metadata_set(&c->fc->metadata, "minor_version", minor_ver_str);
+ av_metadata_set2(&c->fc->metadata, "minor_version", minor_ver_str, 0);
comp_brand_size = atom.size - 8;
if (comp_brand_size < 0)
return AVERROR(ENOMEM);
get_buffer(pb, comp_brands_str, comp_brand_size);
comp_brands_str[comp_brand_size] = 0;
- av_metadata_set(&c->fc->metadata, "compatible_brands", comp_brands_str);
+ av_metadata_set2(&c->fc->metadata, "compatible_brands", comp_brands_str, 0);
av_freep(&comp_brands_str);
return 0;
return mov_read_default(c, pb, atom);
}
+static void mov_metadata_creation_time(AVMetadata **metadata, time_t time)
+{
+ char buffer[32];
+ if (time) {
+ time -= 2082844800; /* seconds between 1904-01-01 and Epoch */
+ strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", gmtime(&time));
+ av_metadata_set2(metadata, "creation_time", buffer, 0);
+ }
+}
+
static int mov_read_mdhd(MOVContext *c, ByteIOContext *pb, MOVAtom atom)
{
AVStream *st;
int version;
char language[4] = {0};
unsigned lang;
+ time_t creation_time;
if (c->fc->nb_streams < 1)
return 0;
get_be24(pb); /* flags */
if (version == 1) {
- get_be64(pb);
+ creation_time = get_be64(pb);
get_be64(pb);
} else {
- get_be32(pb); /* creation time */
+ creation_time = get_be32(pb);
get_be32(pb); /* modification time */
}
+ mov_metadata_creation_time(&st->metadata, creation_time);
sc->time_scale = get_be32(pb);
st->duration = (version == 1) ? get_be64(pb) : get_be32(pb); /* duration */
lang = get_be16(pb); /* language */
if (ff_mov_lang_to_iso639(lang, language))
- av_metadata_set(&st->metadata, "language", language);
+ av_metadata_set2(&st->metadata, "language", language, 0);
get_be16(pb); /* quality */
return 0;
static int mov_read_mvhd(MOVContext *c, ByteIOContext *pb, MOVAtom atom)
{
+ time_t creation_time;
int version = get_byte(pb); /* version */
get_be24(pb); /* flags */
if (version == 1) {
- get_be64(pb);
+ creation_time = get_be64(pb);
get_be64(pb);
} else {
- get_be32(pb); /* creation time */
+ creation_time = get_be32(pb);
get_be32(pb); /* modification time */
}
+ mov_metadata_creation_time(&c->fc->metadata, creation_time);
c->time_scale = get_be32(pb); /* time scale */
dprintf(c->fc, "time scale = %i\n", c->time_scale);
return 0;
}
+/**
+ * An strf atom is a BITMAPINFOHEADER struct. This struct is 40 bytes itself,
+ * but can have extradata appended at the end after the 40 bytes belonging
+ * to the struct.
+ */
+static int mov_read_strf(MOVContext *c, ByteIOContext *pb, MOVAtom atom)
+{
+ AVStream *st;
+
+ if (c->fc->nb_streams < 1)
+ return 0;
+ if (atom.size <= 40)
+ return 0;
+ st = c->fc->streams[c->fc->nb_streams-1];
+
+ if((uint64_t)atom.size > (1<<30))
+ return -1;
+
+ av_free(st->codec->extradata);
+ st->codec->extradata = av_mallocz(atom.size - 40 + FF_INPUT_BUFFER_PADDING_SIZE);
+ if (!st->codec->extradata)
+ return AVERROR(ENOMEM);
+ st->codec->extradata_size = atom.size - 40;
+ url_fskip(pb, 40);
+ get_buffer(pb, st->codec->extradata, atom.size - 40);
+ return 0;
+}
+
static int mov_read_stco(MOVContext *c, ByteIOContext *pb, MOVAtom atom)
{
AVStream *st;
return CODEC_ID_NONE;
}
-static int mov_read_stsd(MOVContext *c, ByteIOContext *pb, MOVAtom atom)
+int ff_mov_read_stsd_entries(MOVContext *c, ByteIOContext *pb, int entries)
{
AVStream *st;
MOVStreamContext *sc;
- int j, entries, pseudo_stream_id;
+ int j, pseudo_stream_id;
if (c->fc->nb_streams < 1)
return 0;
st = c->fc->streams[c->fc->nb_streams-1];
sc = st->priv_data;
- get_byte(pb); /* version */
- get_be24(pb); /* flags */
-
- entries = get_be32(pb);
-
for(pseudo_stream_id=0; pseudo_stream_id<entries; pseudo_stream_id++) {
//Parsing Sample description table
enum CodecID id;
int dref_id = 1;
- MOVAtom a = { 0 };
+ MOVAtom a = { AV_RL32("stsd") };
int64_t start_pos = url_ftell(pb);
int size = get_be32(pb); /* size */
uint32_t format = get_le32(pb); /* data format */
/* Multiple fourcc, we skip JPEG. This is not correct, we should
* export it as a separate AVStream but this needs a few changes
* in the MOV demuxer, patch welcome. */
+ multiple_stsd:
av_log(c->fc, AV_LOG_WARNING, "multiple fourcc not supported\n");
url_fskip(pb, size - (url_ftell(pb) - start_pos));
continue;
}
+ /* we cannot demux concatenated h264 streams because of different extradata */
+ if (st->codec->codec_tag && st->codec->codec_tag == AV_RL32("avc1"))
+ goto multiple_stsd;
sc->pseudo_stream_id = st->codec->codec_tag ? -1 : pseudo_stream_id;
sc->dref_id= dref_id;
st->codec->codec_tag = format;
id = ff_codec_get_id(codec_movaudio_tags, format);
if (id<=0 && ((format&0xFFFF) == 'm'+('s'<<8) || (format&0xFFFF) == 'T'+('S'<<8)))
- id = ff_codec_get_id(ff_codec_wav_tags, bswap_32(format)&0xFFFF);
+ id = ff_codec_get_id(ff_codec_wav_tags, av_bswap32(format)&0xFFFF);
if (st->codec->codec_type != AVMEDIA_TYPE_VIDEO && id > 0) {
st->codec->codec_type = AVMEDIA_TYPE_AUDIO;
if (st->codec->extradata_size == 36) {
st->codec->frame_size = AV_RB32(st->codec->extradata+12);
st->codec->channels = AV_RB8 (st->codec->extradata+21);
+ st->codec->sample_rate = AV_RB32(st->codec->extradata+32);
}
break;
default:
return 0;
}
+static int mov_read_stsd(MOVContext *c, ByteIOContext *pb, MOVAtom atom)
+{
+ int entries;
+
+ get_byte(pb); /* version */
+ get_be24(pb); /* flags */
+ entries = get_be32(pb);
+
+ return ff_mov_read_stsd_entries(c, pb, entries);
+}
+
static int mov_read_stsc(MOVContext *c, ByteIOContext *pb, MOVAtom atom)
{
AVStream *st;
sc->pb = c->fc->pb;
if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
- if (st->codec->width != sc->width || st->codec->height != sc->height) {
- AVRational r = av_d2q(((double)st->codec->height * sc->width) /
- ((double)st->codec->width * sc->height), INT_MAX);
- if (st->sample_aspect_ratio.num)
- st->sample_aspect_ratio = av_mul_q(st->sample_aspect_ratio, r);
- else
- st->sample_aspect_ratio = r;
+ if (!st->sample_aspect_ratio.num &&
+ (st->codec->width != sc->width || st->codec->height != sc->height)) {
+ st->sample_aspect_ratio = av_d2q(((double)st->codec->height * sc->width) /
+ ((double)st->codec->width * sc->height), INT_MAX);
}
av_reduce(&st->avg_frame_rate.num, &st->avg_frame_rate.den,
return 0;
}
+static int mov_read_chap(MOVContext *c, ByteIOContext *pb, MOVAtom atom)
+{
+ c->chapter_track = get_be32(pb);
+ return 0;
+}
+
static int mov_read_trex(MOVContext *c, ByteIOContext *pb, MOVAtom atom)
{
MOVTrackExt *trex;
static const MOVParseTableEntry mov_default_parse_table[] = {
{ MKTAG('a','v','s','s'), mov_read_extradata },
+{ MKTAG('c','h','p','l'), mov_read_chpl },
{ MKTAG('c','o','6','4'), mov_read_stco },
{ MKTAG('c','t','t','s'), mov_read_ctts }, /* composition time to sample */
{ MKTAG('d','i','n','f'), mov_read_default },
{ MKTAG('s','t','b','l'), mov_read_default },
{ MKTAG('s','t','c','o'), mov_read_stco },
{ MKTAG('s','t','p','s'), mov_read_stps },
+{ MKTAG('s','t','r','f'), mov_read_strf },
{ MKTAG('s','t','s','c'), mov_read_stsc },
{ MKTAG('s','t','s','d'), mov_read_stsd }, /* sample description */
{ MKTAG('s','t','s','s'), mov_read_stss }, /* sync sample */
{ MKTAG('t','f','h','d'), mov_read_tfhd }, /* track fragment header */
{ MKTAG('t','r','a','k'), mov_read_trak },
{ MKTAG('t','r','a','f'), mov_read_default },
+{ MKTAG('t','r','e','f'), mov_read_default },
+{ MKTAG('c','h','a','p'), mov_read_chap },
{ MKTAG('t','r','e','x'), mov_read_trex },
{ MKTAG('t','r','u','n'), mov_read_trun },
{ MKTAG('u','d','t','a'), mov_read_default },
{ MKTAG('w','a','v','e'), mov_read_wave },
{ MKTAG('e','s','d','s'), mov_read_esds },
+{ MKTAG('d','a','c','3'), mov_read_dac3 }, /* AC-3 info */
{ MKTAG('w','i','d','e'), mov_read_wide }, /* place holder */
{ MKTAG('c','m','o','v'), mov_read_cmov },
{ 0, NULL }
return score;
}
+// must be done after parsing all trak because there's no order requirement
+static void mov_read_chapters(AVFormatContext *s)
+{
+ MOVContext *mov = s->priv_data;
+ AVStream *st = NULL;
+ MOVStreamContext *sc;
+ int64_t cur_pos;
+ uint8_t *title = NULL;
+ int i, len, i8, i16;
+
+ for (i = 0; i < s->nb_streams; i++)
+ if (s->streams[i]->id == mov->chapter_track) {
+ st = s->streams[i];
+ break;
+ }
+ if (!st) {
+ av_log(s, AV_LOG_ERROR, "Referenced QT chapter track not found\n");
+ return;
+ }
+
+ st->discard = AVDISCARD_ALL;
+ sc = st->priv_data;
+ cur_pos = url_ftell(sc->pb);
+
+ for (i = 0; i < st->nb_index_entries; i++) {
+ AVIndexEntry *sample = &st->index_entries[i];
+ int64_t end = i+1 < st->nb_index_entries ? st->index_entries[i+1].timestamp : st->duration;
+
+ if (url_fseek(sc->pb, sample->pos, SEEK_SET) != sample->pos) {
+ av_log(s, AV_LOG_ERROR, "Chapter %d not found in file\n", i);
+ goto finish;
+ }
+
+ title = av_malloc(sample->size+2);
+ get_buffer(sc->pb, title, sample->size);
+
+ // the first two bytes are the length of the title
+ len = AV_RB16(title);
+ if (len > sample->size-2)
+ continue;
+
+ // The samples could theoretically be in any encoding if there's an encd
+ // atom following, but in practice are only utf-8 or utf-16, distinguished
+ // instead by the presence of a BOM
+ if (AV_RB16(title+2) == 0xfeff) {
+ uint8_t *utf8 = av_malloc(2*len+3);
+
+ i8 = i16 = 0;
+ while (i16 < len) {
+ uint32_t ch;
+ uint8_t tmp;
+ GET_UTF16(ch, i16 < len ? AV_RB16(title + (i16+=2)) : 0, break;)
+ PUT_UTF8(ch, tmp, if (i8 < 2*len) utf8[2+i8++] = tmp;)
+ }
+ utf8[2+i8] = 0;
+ av_freep(&title);
+ title = utf8;
+ }
+
+ ff_new_chapter(s, i, st->time_base, sample->timestamp, end, title+2);
+ av_freep(&title);
+ }
+finish:
+ av_free(title);
+ url_fseek(sc->pb, cur_pos, SEEK_SET);
+}
+
static int mov_read_header(AVFormatContext *s, AVFormatParameters *ap)
{
MOVContext *mov = s->priv_data;
ByteIOContext *pb = s->pb;
int err;
- MOVAtom atom = { 0 };
+ MOVAtom atom = { AV_RL32("root") };
mov->fc = s;
/* .mov and .mp4 aren't streamable anyway (only progressive download if moov is before mdat) */
}
dprintf(mov->fc, "on_parse_exit_offset=%lld\n", url_ftell(pb));
+ if (!url_is_streamed(pb) && mov->chapter_track > 0)
+ mov_read_chapters(s);
+
return 0;
}
if (!sample) {
mov->found_mdat = 0;
if (!url_is_streamed(s->pb) ||
- mov_read_default(mov, s->pb, (MOVAtom){ 0, INT64_MAX }) < 0 ||
+ mov_read_default(mov, s->pb, (MOVAtom){ AV_RL32("root"), INT64_MAX }) < 0 ||
url_feof(s->pb))
return AVERROR_EOF;
dprintf(s, "read fragments, offset 0x%llx\n", url_ftell(s->pb));
sample = av_index_search_timestamp(st, timestamp, flags);
dprintf(s, "stream %d, timestamp %"PRId64", sample %d\n", st->index, timestamp, sample);
+ if (sample < 0 && st->nb_index_entries && timestamp < st->index_entries[0].timestamp)
+ sample = 0;
if (sample < 0) /* not sure what to do */
return -1;
sc->current_sample = sample;