#include "libavutil/spherical.h"
#include "libavutil/stereo3d.h"
#include "libavutil/timecode.h"
+#include "libavutil/dovi_meta.h"
#include "libavcodec/ac3tab.h"
#include "libavcodec/flac.h"
#include "libavcodec/mpegaudiodecheader.h"
+#include "libavcodec/mlp_parse.h"
#include "avformat.h"
#include "internal.h"
#include "avio_internal.h"
title_str[title_size] = 0;
if (title_str[0]) {
int off = (!c->isom && title_str[0] == title_size - 1);
- av_dict_set(&st->metadata, "handler_name", title_str + off, 0);
+ // flag added so as to not set stream handler name if already set from mdia->hdlr
+ av_dict_set(&st->metadata, "handler_name", title_str + off, AV_DICT_DONT_OVERWRITE);
}
av_freep(&title_str);
}
sha = av_sha_alloc();
if (!sha)
return AVERROR(ENOMEM);
+ av_free(c->aes_decrypt);
c->aes_decrypt = av_aes_alloc();
if (!c->aes_decrypt) {
ret = AVERROR(ENOMEM);
return ret;
}
comp_brands_str[comp_brand_size] = 0;
- av_dict_set(&c->fc->metadata, "compatible_brands", comp_brands_str, 0);
- av_freep(&comp_brands_str);
+ av_dict_set(&c->fc->metadata, "compatible_brands",
+ comp_brands_str, AV_DICT_DONT_STRDUP_VAL);
return 0;
}
static int64_t get_stream_info_time(MOVFragmentStreamInfo * frag_stream_info)
{
-
- if (frag_stream_info) {
- if (frag_stream_info->sidx_pts != AV_NOPTS_VALUE)
- return frag_stream_info->sidx_pts;
- if (frag_stream_info->first_tfra_pts != AV_NOPTS_VALUE)
- return frag_stream_info->first_tfra_pts;
- if (frag_stream_info->tfdt_dts != AV_NOPTS_VALUE)
- return frag_stream_info->tfdt_dts;
- }
- return AV_NOPTS_VALUE;
+ av_assert0(frag_stream_info);
+ if (frag_stream_info->sidx_pts != AV_NOPTS_VALUE)
+ return frag_stream_info->sidx_pts;
+ if (frag_stream_info->first_tfra_pts != AV_NOPTS_VALUE)
+ return frag_stream_info->first_tfra_pts;
+ return frag_stream_info->tfdt_dts;
}
static int64_t get_frag_time(MOVFragmentIndex *frag_index,
static int search_frag_timestamp(MOVFragmentIndex *frag_index,
AVStream *st, int64_t timestamp)
{
- int a, b, m;
+ int a, b, m, m0;
int64_t frag_time;
int id = -1;
b = frag_index->nb_items;
while (b - a > 1) {
- m = (a + b) >> 1;
- frag_time = get_frag_time(frag_index, m, id);
- if (frag_time != AV_NOPTS_VALUE) {
- if (frag_time >= timestamp)
- b = m;
- if (frag_time <= timestamp)
- a = m;
- }
+ m0 = m = (a + b) >> 1;
+
+ while (m < b &&
+ (frag_time = get_frag_time(frag_index, m, id)) == AV_NOPTS_VALUE)
+ m++;
+
+ if (m < b && frag_time <= timestamp)
+ a = m;
+ else
+ b = m0;
}
+
return a;
}
return -1;
for (i = 0; i < c->fc->nb_streams; i++) {
+ // Avoid building frag index if streams lack track id.
+ if (c->fc->streams[i]->id < 0) {
+ av_free(frag_stream_info);
+ return AVERROR_INVALIDDATA;
+ }
+
frag_stream_info[i].id = c->fc->streams[i]->id;
frag_stream_info[i].sidx_pts = AV_NOPTS_VALUE;
frag_stream_info[i].tfdt_dts = AV_NOPTS_VALUE;
+ frag_stream_info[i].next_trun_dts = AV_NOPTS_VALUE;
frag_stream_info[i].first_tfra_pts = AV_NOPTS_VALUE;
frag_stream_info[i].index_entry = -1;
frag_stream_info[i].encryption_index = NULL;
static int mov_read_moof(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
+ // Set by mov_read_tfhd(). mov_read_trun() will reject files missing tfhd.
+ c->fragment.found_tfhd = 0;
+
if (!c->has_looked_for_mfra && c->use_mfra_for > 0) {
c->has_looked_for_mfra = 1;
if (pb->seekable & AVIO_SEEKABLE_NORMAL) {
return mov_read_default(c, pb, atom);
}
-static void mov_metadata_creation_time(AVDictionary **metadata, int64_t time)
+static void mov_metadata_creation_time(AVDictionary **metadata, int64_t time, void *logctx)
{
if (time) {
if(time >= 2082844800)
time -= 2082844800; /* seconds between 1904-01-01 and Epoch */
if ((int64_t)(time * 1000000ULL) / 1000000 != time) {
- av_log(NULL, AV_LOG_DEBUG, "creation_time is not representable\n");
+ av_log(logctx, AV_LOG_DEBUG, "creation_time is not representable\n");
return;
}
creation_time = avio_rb32(pb);
avio_rb32(pb); /* modification time */
}
- mov_metadata_creation_time(&st->metadata, creation_time);
+ mov_metadata_creation_time(&st->metadata, creation_time, c->fc);
sc->time_scale = avio_rb32(pb);
if (sc->time_scale <= 0) {
creation_time = avio_rb32(pb);
avio_rb32(pb); /* modification time */
}
- mov_metadata_creation_time(&c->fc->metadata, creation_time);
+ mov_metadata_creation_time(&c->fc->metadata, creation_time, c->fc);
c->time_scale = avio_rb32(pb); /* time scale */
if (c->time_scale <= 0) {
av_log(c->fc, AV_LOG_ERROR, "Invalid mvhd time scale %d, defaulting to 1\n", c->time_scale);
static int mov_read_colr(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
AVStream *st;
+ uint8_t *icc_profile;
char color_parameter_type[5] = { 0 };
uint16_t color_primaries, color_trc, color_matrix;
int ret;
if (ret < 0)
return ret;
if (strncmp(color_parameter_type, "nclx", 4) &&
- strncmp(color_parameter_type, "nclc", 4)) {
+ strncmp(color_parameter_type, "nclc", 4) &&
+ strncmp(color_parameter_type, "prof", 4)) {
av_log(c->fc, AV_LOG_WARNING, "unsupported color_parameter_type %s\n",
color_parameter_type);
return 0;
}
- color_primaries = avio_rb16(pb);
- color_trc = avio_rb16(pb);
- color_matrix = avio_rb16(pb);
-
- av_log(c->fc, AV_LOG_TRACE,
- "%s: pri %d trc %d matrix %d",
- color_parameter_type, color_primaries, color_trc, color_matrix);
-
- if (!strncmp(color_parameter_type, "nclx", 4)) {
- uint8_t color_range = avio_r8(pb) >> 7;
- av_log(c->fc, AV_LOG_TRACE, " full %"PRIu8"", color_range);
- if (color_range)
- st->codecpar->color_range = AVCOL_RANGE_JPEG;
- else
- st->codecpar->color_range = AVCOL_RANGE_MPEG;
+ if (!strncmp(color_parameter_type, "prof", 4)) {
+ icc_profile = av_stream_new_side_data(st, AV_PKT_DATA_ICC_PROFILE, atom.size - 4);
+ if (!icc_profile)
+ return AVERROR(ENOMEM);
+ ret = ffio_read_size(pb, icc_profile, atom.size - 4);
+ if (ret < 0)
+ return ret;
}
+ else {
+ color_primaries = avio_rb16(pb);
+ color_trc = avio_rb16(pb);
+ color_matrix = avio_rb16(pb);
- if (!av_color_primaries_name(color_primaries))
- color_primaries = AVCOL_PRI_UNSPECIFIED;
- if (!av_color_transfer_name(color_trc))
- color_trc = AVCOL_TRC_UNSPECIFIED;
- if (!av_color_space_name(color_matrix))
- color_matrix = AVCOL_SPC_UNSPECIFIED;
+ av_log(c->fc, AV_LOG_TRACE,
+ "%s: pri %d trc %d matrix %d",
+ color_parameter_type, color_primaries, color_trc, color_matrix);
+
+ if (!strncmp(color_parameter_type, "nclx", 4)) {
+ uint8_t color_range = avio_r8(pb) >> 7;
+ av_log(c->fc, AV_LOG_TRACE, " full %"PRIu8"", color_range);
+ if (color_range)
+ st->codecpar->color_range = AVCOL_RANGE_JPEG;
+ else
+ st->codecpar->color_range = AVCOL_RANGE_MPEG;
+ }
- st->codecpar->color_primaries = color_primaries;
- st->codecpar->color_trc = color_trc;
- st->codecpar->color_space = color_matrix;
- av_log(c->fc, AV_LOG_TRACE, "\n");
+ if (!av_color_primaries_name(color_primaries))
+ color_primaries = AVCOL_PRI_UNSPECIFIED;
+ if (!av_color_transfer_name(color_trc))
+ color_trc = AVCOL_TRC_UNSPECIFIED;
+ if (!av_color_space_name(color_matrix))
+ color_matrix = AVCOL_SPC_UNSPECIFIED;
+ st->codecpar->color_primaries = color_primaries;
+ st->codecpar->color_trc = color_trc;
+ st->codecpar->color_space = color_matrix;
+ av_log(c->fc, AV_LOG_TRACE, "\n");
+ }
return 0;
}
}
}
if (decoded_field_order == AV_FIELD_UNKNOWN && mov_field_order) {
- av_log(NULL, AV_LOG_ERROR, "Unknown MOV field order 0x%04x\n", mov_field_order);
+ av_log(c->fc, AV_LOG_ERROR, "Unknown MOV field order 0x%04x\n", mov_field_order);
}
st->codecpar->field_order = decoded_field_order;
par->color_range = AVCOL_RANGE_JPEG;
break;
default:
- av_log(c, AV_LOG_WARNING, "ignored unknown aclr value (%d)\n", range_value);
+ av_log(c->fc, AV_LOG_WARNING, "ignored unknown aclr value (%d)\n", range_value);
break;
}
- ff_dlog(c, "color_range: %d\n", par->color_range);
+ ff_dlog(c->fc, "color_range: %d\n", par->color_range);
} else {
/* For some reason the whole atom was not added to the extradata */
- av_log(c, AV_LOG_ERROR, "aclr not decoded - incomplete atom\n");
+ av_log(c->fc, AV_LOG_ERROR, "aclr not decoded - incomplete atom\n");
}
} else {
- av_log(c, AV_LOG_ERROR, "aclr not decoded - unable to add atom to extradata\n");
+ av_log(c->fc, AV_LOG_ERROR, "aclr not decoded - unable to add atom to extradata\n");
}
} else {
- av_log(c, AV_LOG_WARNING, "aclr not decoded - unexpected size %"PRId64"\n", atom.size);
+ av_log(c->fc, AV_LOG_WARNING, "aclr not decoded - unexpected size %"PRId64"\n", atom.size);
}
}
st->codecpar->codec_id == AV_CODEC_ID_QDMC ||
st->codecpar->codec_id == AV_CODEC_ID_SPEEX) {
// pass all frma atom to codec, needed at least for QDMC and QDM2
- av_freep(&st->codecpar->extradata);
ret = ff_get_extradata(c->fc, st->codecpar, pb, atom.size);
if (ret < 0)
return ret;
return mov_read_default(c, pb, atom);
}
if (st->codecpar->extradata_size > 1 && st->codecpar->extradata) {
- av_log(c, AV_LOG_WARNING, "ignoring multiple glbl\n");
+ av_log(c->fc, AV_LOG_WARNING, "ignoring multiple glbl\n");
return 0;
}
- av_freep(&st->codecpar->extradata);
ret = ff_get_extradata(c->fc, st->codecpar, pb, atom.size);
if (ret < 0)
return ret;
+ if (atom.type == MKTAG('h','v','c','C') && st->codecpar->codec_tag == MKTAG('d','v','h','1'))
+ /* HEVC-based Dolby Vision derived from hvc1.
+ Happens to match with an identifier
+ previously utilized for DV. Thus, if we have
+ the hvcC extradata box available as specified,
+ set codec to HEVC */
+ st->codecpar->codec_id = AV_CODEC_ID_HEVC;
return 0;
}
return 0;
avio_seek(pb, 6, SEEK_CUR);
- av_freep(&st->codecpar->extradata);
ret = ff_get_extradata(c->fc, st->codecpar, pb, atom.size - 7);
if (ret < 0)
return ret;
return AVERROR_INVALIDDATA;
avio_skip(pb, 40);
- av_freep(&st->codecpar->extradata);
ret = ff_get_extradata(c->fc, st->codecpar, pb, atom.size - 40);
if (ret < 0)
return ret;
MOVStreamContext *sc;
unsigned int i, entries;
+ if (c->trak_index < 0) {
+ av_log(c->fc, AV_LOG_WARNING, "STCO outside TRAK\n");
+ return 0;
+ }
if (c->fc->nb_streams < 1)
return 0;
st = c->fc->streams[c->fc->nb_streams-1];
id = ff_codec_get_id(ff_codec_movsubtitle_tags, format);
if (id > 0)
st->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE;
+ else
+ id = ff_codec_get_id(ff_codec_movdata_tags, format);
}
}
// Read QT version 1 fields. In version 0 these do not exist.
av_log(c->fc, AV_LOG_TRACE, "version =%d, isom =%d\n", version, c->isom);
if (!c->isom ||
- (compatible_brands && strstr(compatible_brands->value, "qt "))) {
-
+ (compatible_brands && strstr(compatible_brands->value, "qt ")) ||
+ (sc->stsd_version == 0 && version > 0)) {
if (version == 1) {
sc->samples_per_frame = avio_rb32(pb);
avio_rb32(pb); /* bytes per packet */
{
char buf[256] = {0};
uint8_t *src = st->codecpar->extradata;
- int i;
+ int i, ret;
if (st->codecpar->extradata_size != 64)
return 0;
if (av_strlcat(buf, "\n", sizeof(buf)) >= sizeof(buf))
return 0;
- av_freep(&st->codecpar->extradata);
- st->codecpar->extradata_size = 0;
- st->codecpar->extradata = av_mallocz(strlen(buf) + AV_INPUT_BUFFER_PADDING_SIZE);
- if (!st->codecpar->extradata)
- return AVERROR(ENOMEM);
- st->codecpar->extradata_size = strlen(buf);
+ ret = ff_alloc_extradata(st->codecpar, strlen(buf));
+ if (ret < 0)
+ return ret;
memcpy(st->codecpar->extradata, buf, st->codecpar->extradata_size);
return 0;
int val;
val = AV_RB32(st->codecpar->extradata + 4);
tmcd_ctx->tmcd_flags = val;
- st->avg_frame_rate.num = st->codecpar->extradata[16]; /* number of frame */
- st->avg_frame_rate.den = 1;
+ st->avg_frame_rate.num = AV_RB32(st->codecpar->extradata + 8); /* timescale */
+ st->avg_frame_rate.den = AV_RB32(st->codecpar->extradata + 12); /* frameDuration */
#if FF_API_LAVF_AVCTX
FF_DISABLE_DEPRECATION_WARNINGS
st->codec->time_base = av_inv_q(st->avg_frame_rate);
"size=%"PRId64" 4CC=%s codec_type=%d\n", size,
av_fourcc2str(format), st->codecpar->codec_type);
+ st->codecpar->codec_id = id;
if (st->codecpar->codec_type==AVMEDIA_TYPE_VIDEO) {
- st->codecpar->codec_id = id;
mov_parse_stsd_video(c, pb, st, sc);
} else if (st->codecpar->codec_type==AVMEDIA_TYPE_AUDIO) {
- st->codecpar->codec_id = id;
mov_parse_stsd_audio(c, pb, st, sc);
if (st->codecpar->sample_rate < 0) {
av_log(c->fc, AV_LOG_ERROR, "Invalid sample rate %d\n", st->codecpar->sample_rate);
return AVERROR_INVALIDDATA;
}
} else if (st->codecpar->codec_type==AVMEDIA_TYPE_SUBTITLE){
- st->codecpar->codec_id = id;
mov_parse_stsd_subtitle(c, pb, st, sc,
size - (avio_tell(pb) - start_pos));
} else {
st = c->fc->streams[c->fc->nb_streams - 1];
sc = st->priv_data;
- avio_r8(pb); /* version */
+ sc->stsd_version = avio_r8(pb);
avio_rb24(pb); /* flags */
entries = avio_rb32(pb);
- if (entries <= 0) {
+ /* Each entry contains a size (4 bytes) and format (4 bytes). */
+ if (entries <= 0 || entries > atom.size / 8) {
av_log(c->fc, AV_LOG_ERROR, "invalid STSD entries %d\n", entries);
return AVERROR_INVALIDDATA;
}
sc->stsc_count = i;
for (i = sc->stsc_count - 1; i < UINT_MAX; i--) {
+ int64_t first_min = i + 1;
if ((i+1 < sc->stsc_count && sc->stsc_data[i].first >= sc->stsc_data[i+1].first) ||
(i > 0 && sc->stsc_data[i].first <= sc->stsc_data[i-1].first) ||
- sc->stsc_data[i].first < 1 ||
+ sc->stsc_data[i].first < first_min ||
sc->stsc_data[i].count < 1 ||
sc->stsc_data[i].id < 1) {
av_log(c->fc, AV_LOG_WARNING, "STSC entry %d is invalid (first=%d count=%d id=%d)\n", i, sc->stsc_data[i].first, sc->stsc_data[i].count, sc->stsc_data[i].id);
- if (i+1 >= sc->stsc_count || sc->stsc_data[i+1].first < 2)
- return AVERROR_INVALIDDATA;
+ if (i+1 >= sc->stsc_count) {
+ if (sc->stsc_data[i].count == 0 && i > 0) {
+ sc->stsc_count --;
+ continue;
+ }
+ sc->stsc_data[i].first = FFMAX(sc->stsc_data[i].first, first_min);
+ if (i > 0 && sc->stsc_data[i].first <= sc->stsc_data[i-1].first)
+ sc->stsc_data[i].first = FFMIN(sc->stsc_data[i-1].first + 1LL, INT_MAX);
+ sc->stsc_data[i].count = FFMAX(sc->stsc_data[i].count, 1);
+ sc->stsc_data[i].id = FFMAX(sc->stsc_data[i].id, 1);
+ continue;
+ }
+ av_assert0(sc->stsc_data[i+1].first >= 2);
// We replace this entry by the next valid
sc->stsc_data[i].first = sc->stsc_data[i+1].first - 1;
sc->stsc_data[i].count = sc->stsc_data[i+1].count;
if (mov_stsc_index_valid(index, sc->stsc_count))
chunk_count = sc->stsc_data[index + 1].first - sc->stsc_data[index].first;
- else
+ else {
+ // Validation for stsc / stco happens earlier in mov_read_stsc + mov_read_trak.
+ av_assert0(sc->stsc_data[index].first <= sc->chunk_count);
chunk_count = sc->chunk_count - (sc->stsc_data[index].first - 1);
+ }
return sc->stsc_data[index].count * (int64_t)chunk_count;
}
if (ret < 0) {
av_freep(&sc->sample_sizes);
av_free(buf);
- return ret;
+ av_log(c->fc, AV_LOG_WARNING, "STSZ atom truncated\n");
+ return 0;
}
init_get_bits(&gb, buf, 8*num_bytes);
av_log(c->fc, AV_LOG_TRACE, "sample_count=%d, sample_duration=%d\n",
sample_count, sample_duration);
- if ( i+1 == entries
- && i
- && sample_count == 1
- && total_sample_count > 100
- && sample_duration/10 > duration / total_sample_count)
- sample_duration = duration / total_sample_count;
duration+=(int64_t)sample_duration*(uint64_t)sample_count;
total_sample_count+=sample_count;
}
if (duration > 0 &&
duration <= INT64_MAX - sc->duration_for_fps &&
- total_sample_count <= INT64_MAX - sc->nb_frames_for_fps
+ total_sample_count <= INT_MAX - sc->nb_frames_for_fps
) {
sc->duration_for_fps += duration;
sc->nb_frames_for_fps += total_sample_count;
st->nb_frames= total_sample_count;
if (duration)
- st->duration= duration;
+ st->duration= FFMIN(st->duration, duration);
sc->track_end = duration;
return 0;
}
-static void mov_update_dts_shift(MOVStreamContext *sc, int duration)
+static int mov_read_sdtp(MOVContext *c, AVIOContext *pb, MOVAtom atom)
+{
+ AVStream *st;
+ MOVStreamContext *sc;
+ int64_t i, entries;
+
+ if (c->fc->nb_streams < 1)
+ return 0;
+ st = c->fc->streams[c->fc->nb_streams - 1];
+ sc = st->priv_data;
+
+ avio_r8(pb); /* version */
+ avio_rb24(pb); /* flags */
+ entries = atom.size - 4;
+
+ av_log(c->fc, AV_LOG_TRACE, "track[%u].sdtp.entries = %" PRId64 "\n",
+ c->fc->nb_streams - 1, entries);
+
+ if (sc->sdtp_data)
+ av_log(c->fc, AV_LOG_WARNING, "Duplicated SDTP atom\n");
+ av_freep(&sc->sdtp_data);
+ sc->sdtp_count = 0;
+
+ sc->sdtp_data = av_mallocz(entries);
+ if (!sc->sdtp_data)
+ return AVERROR(ENOMEM);
+
+ for (i = 0; i < entries && !pb->eof_reached; i++)
+ sc->sdtp_data[i] = avio_r8(pb);
+ sc->sdtp_count = i;
+
+ return 0;
+}
+
+static void mov_update_dts_shift(MOVStreamContext *sc, int duration, void *logctx)
{
if (duration < 0) {
if (duration == INT_MIN) {
- av_log(NULL, AV_LOG_WARNING, "mov_update_dts_shift(): dts_shift set to %d\n", INT_MAX);
+ av_log(logctx, AV_LOG_WARNING, "mov_update_dts_shift(): dts_shift set to %d\n", INT_MAX);
duration++;
}
sc->dts_shift = FFMAX(sc->dts_shift, -duration);
}
if (i+2<entries)
- mov_update_dts_shift(sc, duration);
+ mov_update_dts_shift(sc, duration, c->fc);
}
sc->ctts_count = ctts_count;
int ctts_sample = 0;
int64_t pts_buf[MAX_REORDER_DELAY + 1]; // Circular buffer to sort pts.
int buf_start = 0;
- int buf_size = 0;
int j, r, num_swaps;
+ for (j = 0; j < MAX_REORDER_DELAY + 1; j++)
+ pts_buf[j] = INT64_MIN;
+
if (st->codecpar->video_delay <= 0 && msc->ctts_data &&
st->codecpar->codec_id == AV_CODEC_ID_H264) {
st->codecpar->video_delay = 0;
for(ind = 0; ind < st->nb_index_entries && ctts_ind < msc->ctts_count; ++ind) {
- if (buf_size == (MAX_REORDER_DELAY + 1)) {
- // If circular buffer is full, then move the first element forward.
- buf_start = (buf_start + 1) % buf_size;
- } else {
- ++buf_size;
- }
-
// Point j to the last elem of the buffer and insert the current pts there.
- j = (buf_start + buf_size - 1) % buf_size;
+ j = buf_start;
+ buf_start = (buf_start + 1);
+ if (buf_start == MAX_REORDER_DELAY + 1)
+ buf_start = 0;
+
pts_buf[j] = st->index_entries[ind].timestamp + msc->ctts_data[ctts_ind].duration;
// The timestamps that are already in the sorted buffer, and are greater than the
// go through, to keep this buffer in sorted order.
num_swaps = 0;
while (j != buf_start) {
- r = (j - 1 + buf_size) % buf_size;
+ r = j - 1;
+ if (r < 0) r = MAX_REORDER_DELAY;
if (pts_buf[j] < pts_buf[r]) {
FFSWAP(int64_t, pts_buf[j], pts_buf[r]);
++num_swaps;
+ } else {
+ break;
}
j = r;
}
MOVIndexRange *current_index_range;
int i;
int found_keyframe_after_edit = 0;
+ int found_non_empty_edit = 0;
if (!msc->elst_data || msc->elst_count <= 0 || nb_old <= 0) {
return;
edit_list_dts_counter = edit_list_dts_entry_end;
edit_list_dts_entry_end += edit_list_duration;
num_discarded_begin = 0;
- if (edit_list_media_time == -1) {
+ if (!found_non_empty_edit && edit_list_media_time == -1) {
empty_edits_sum_duration += edit_list_duration;
continue;
}
+ found_non_empty_edit = 1;
// If we encounter a non-negative edit list reset the skip_samples/start_pad fields and set them
// according to the edit list below.
flags |= AVINDEX_DISCARD_FRAME;
av_log(mov->fc, AV_LOG_DEBUG, "drop a frame at curr_cts: %"PRId64" @ %"PRId64"\n", curr_cts, index);
- if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && edit_list_start_encountered == 0) {
+ if (edit_list_start_encountered == 0) {
num_discarded_begin++;
frame_duration_buffer = av_realloc(frame_duration_buffer,
num_discarded_begin * sizeof(int64_t));
frame_duration_buffer[num_discarded_begin - 1] = frame_duration;
// Increment skip_samples for the first non-zero audio edit list
- if (first_non_zero_audio_edit > 0 && st->codecpar->codec_id != AV_CODEC_ID_VORBIS) {
+ if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO &&
+ first_non_zero_audio_edit > 0 && st->codecpar->codec_id != AV_CODEC_ID_VORBIS) {
st->skip_samples += frame_duration;
}
}
}
if (edit_list_start_encountered == 0) {
edit_list_start_encountered = 1;
- // Make timestamps strictly monotonically increasing for audio, by rewriting timestamps for
+ // Make timestamps strictly monotonically increasing by rewriting timestamps for
// discarded packets.
- if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && frame_duration_buffer) {
+ if (frame_duration_buffer) {
fix_index_entry_timestamps(st, st->nb_index_entries, edit_list_dts_counter,
frame_duration_buffer, num_discarded_begin);
av_freep(&frame_duration_buffer);
// If the minimum pts turns out to be greater than zero after fixing the index, then we subtract the
// dts by that amount to make the first pts zero.
- if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && msc->min_corrected_pts > 0) {
- av_log(mov->fc, AV_LOG_DEBUG, "Offset DTS by %"PRId64" to make first pts zero.\n", msc->min_corrected_pts);
- for (i = 0; i < st->nb_index_entries; ++i) {
- st->index_entries[i].timestamp -= msc->min_corrected_pts;
+ if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
+ if (msc->min_corrected_pts > 0) {
+ av_log(mov->fc, AV_LOG_DEBUG, "Offset DTS by %"PRId64" to make first pts zero.\n", msc->min_corrected_pts);
+ for (i = 0; i < st->nb_index_entries; ++i) {
+ st->index_entries[i].timestamp -= msc->min_corrected_pts;
+ }
}
}
+ // Start time should be equal to zero or the duration of any empty edits.
+ st->start_time = empty_edits_sum_duration;
- // Update av stream length
- st->duration = edit_list_dts_entry_end - start_dts;
+ // Update av stream length, if it ends up shorter than the track's media duration
+ st->duration = FFMIN(st->duration, edit_list_dts_entry_end - start_dts);
msc->start_pad = st->skip_samples;
// Free the old index and the old CTTS structures
} else {
unsigned chunk_samples, total = 0;
+ if (!sc->chunk_count)
+ return;
+
// compute total chunk count
for (i = 0; i < sc->stsc_count; i++) {
unsigned count, chunk_count;
mov_fix_index(mov, st);
}
+ // Update start time of the stream.
+ if (st->start_time == AV_NOPTS_VALUE && st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && st->nb_index_entries > 0) {
+ st->start_time = st->index_entries[0].timestamp + sc->dts_shift;
+ if (sc->ctts_data) {
+ st->start_time += sc->ctts_data[0].duration;
+ }
+ }
+
mov_estimate_video_delay(mov, st);
}
st = avformat_new_stream(c->fc, NULL);
if (!st) return AVERROR(ENOMEM);
- st->id = c->fc->nb_streams;
+ st->id = -1;
sc = av_mallocz(sizeof(MOVStreamContext));
if (!sc) return AVERROR(ENOMEM);
c->trak_index = -1;
+ // Here stsc refers to a chunk not described in stco. This is technically invalid,
+ // but we can overlook it (clearing stsc) whenever stts_count == 0 (indicating no samples).
+ if (!sc->chunk_count && !sc->stts_count && sc->stsc_count) {
+ sc->stsc_count = 0;
+ av_freep(&sc->stsc_data);
+ }
+
/* sanity checks */
if ((sc->chunk_count && (!sc->stts_count || !sc->stsc_count ||
(!sc->sample_size && !sc->sample_count))) ||
static int mov_read_meta(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
while (atom.size > 8) {
- uint32_t tag = avio_rl32(pb);
+ uint32_t tag;
+ if (avio_feof(pb))
+ return AVERROR_EOF;
+ tag = avio_rl32(pb);
atom.size -= 4;
if (tag == MKTAG('h','d','l','r')) {
avio_seek(pb, -8, SEEK_CUR);
st = c->fc->streams[c->fc->nb_streams-1];
sc = st->priv_data;
+ // Each stream (trak) should have exactly 1 tkhd. This catches bad files and
+ // avoids corrupting AVStreams mapped to an earlier tkhd.
+ if (st->id != -1)
+ return AVERROR_INVALIDDATA;
+
version = avio_r8(pb);
flags = avio_rb24(pb);
st->disposition |= (flags & MOV_TKHD_FLAG_ENABLED) ? AV_DISPOSITION_DEFAULT : 0;
MOVFragment *frag = &c->fragment;
MOVTrackExt *trex = NULL;
int flags, track_id, i;
+ MOVFragmentStreamInfo * frag_stream_info;
avio_r8(pb); /* version */
flags = avio_rb24(pb);
track_id = avio_rb32(pb);
if (!track_id)
return AVERROR_INVALIDDATA;
- frag->track_id = track_id;
- set_frag_stream(&c->frag_index, track_id);
for (i = 0; i < c->trex_count; i++)
- if (c->trex_data[i].track_id == frag->track_id) {
+ if (c->trex_data[i].track_id == track_id) {
trex = &c->trex_data[i];
break;
}
if (!trex) {
- av_log(c->fc, AV_LOG_ERROR, "could not find corresponding trex\n");
- return AVERROR_INVALIDDATA;
+ av_log(c->fc, AV_LOG_WARNING, "could not find corresponding trex (id %u)\n", track_id);
+ return 0;
}
+ c->fragment.found_tfhd = 1;
+ frag->track_id = track_id;
+ set_frag_stream(&c->frag_index, track_id);
frag->base_data_offset = flags & MOV_TFHD_BASE_DATA_OFFSET ?
avio_rb64(pb) : flags & MOV_TFHD_DEFAULT_BASE_IS_MOOF ?
avio_rb32(pb) : trex->flags;
av_log(c->fc, AV_LOG_TRACE, "frag flags 0x%x\n", frag->flags);
+ frag_stream_info = get_current_frag_stream_info(&c->frag_index);
+ if (frag_stream_info)
+ frag_stream_info->next_trun_dts = AV_NOPTS_VALUE;
+
return 0;
}
}
}
if (!st) {
- av_log(c->fc, AV_LOG_ERROR, "could not find corresponding track id %u\n", frag->track_id);
- return AVERROR_INVALIDDATA;
+ av_log(c->fc, AV_LOG_WARNING, "could not find corresponding track id %u\n", frag->track_id);
+ return 0;
}
sc = st->priv_data;
if (sc->pseudo_stream_id + 1 != frag->stsd_id && sc->pseudo_stream_id != -1)
AVIndexEntry *new_entries;
MOVFragmentStreamInfo * frag_stream_info;
+ if (!frag->found_tfhd) {
+ av_log(c->fc, AV_LOG_ERROR, "trun track id unknown, no tfhd was found\n");
+ return AVERROR_INVALIDDATA;
+ }
+
for (i = 0; i < c->fc->nb_streams; i++) {
if (c->fc->streams[i]->id == frag->track_id) {
st = c->fc->streams[i];
}
}
if (!st) {
- av_log(c->fc, AV_LOG_ERROR, "could not find corresponding track id %u\n", frag->track_id);
- return AVERROR_INVALIDDATA;
+ av_log(c->fc, AV_LOG_WARNING, "could not find corresponding track id %u\n", frag->track_id);
+ return 0;
}
sc = st->priv_data;
if (sc->pseudo_stream_id+1 != frag->stsd_id && sc->pseudo_stream_id != -1)
break;
}
}
+ av_assert0(index_entry_pos <= st->nb_index_entries);
avio_r8(pb); /* version */
flags = avio_rb24(pb);
frag_stream_info = get_current_frag_stream_info(&c->frag_index);
if (frag_stream_info)
{
- if (frag_stream_info->first_tfra_pts != AV_NOPTS_VALUE &&
+ if (frag_stream_info->next_trun_dts != AV_NOPTS_VALUE) {
+ dts = frag_stream_info->next_trun_dts - sc->time_offset;
+ } else if (frag_stream_info->first_tfra_pts != AV_NOPTS_VALUE &&
c->use_mfra_for == FF_MOV_FLAG_MFRA_PTS) {
pts = frag_stream_info->first_tfra_pts;
av_log(c->fc, AV_LOG_DEBUG, "found mfra time %"PRId64
", using it for pts\n", pts);
+ } else if (frag_stream_info->first_tfra_pts != AV_NOPTS_VALUE &&
+ c->use_mfra_for == FF_MOV_FLAG_MFRA_DTS) {
+ dts = frag_stream_info->first_tfra_pts;
+ av_log(c->fc, AV_LOG_DEBUG, "found mfra time %"PRId64
+ ", using it for dts\n", pts);
} else if (frag_stream_info->sidx_pts != AV_NOPTS_VALUE) {
// FIXME: sidx earliest_presentation_time is *PTS*, s.b.
// pts = frag_stream_info->sidx_pts;
av_log(c->fc, AV_LOG_TRACE, "first sample flags 0x%x\n", first_sample_flags);
// realloc space for new index entries
- if((unsigned)st->nb_index_entries + entries >= UINT_MAX / sizeof(AVIndexEntry)) {
+ if((uint64_t)st->nb_index_entries + entries >= UINT_MAX / sizeof(AVIndexEntry)) {
entries = UINT_MAX / sizeof(AVIndexEntry) - st->nb_index_entries;
av_log(c->fc, AV_LOG_ERROR, "Failed to add index entry\n");
}
- if (entries <= 0)
- return -1;
+ if (entries == 0)
+ return 0;
requested_size = (st->nb_index_entries + entries) * sizeof(AVIndexEntry);
new_entries = av_fast_realloc(st->index_entries,
if (flags & MOV_TRUN_SAMPLE_FLAGS) sample_flags = avio_rb32(pb);
if (flags & MOV_TRUN_SAMPLE_CTS) ctts_duration = avio_rb32(pb);
- mov_update_dts_shift(sc, ctts_duration);
+ mov_update_dts_shift(sc, ctts_duration, c->fc);
if (pts != AV_NOPTS_VALUE) {
dts = pts - sc->dts_shift;
if (flags & MOV_TRUN_SAMPLE_CTS) {
sc->data_size += sample_size;
if (sample_duration <= INT64_MAX - sc->duration_for_fps &&
- 1 <= INT64_MAX - sc->nb_frames_for_fps
+ 1 <= INT_MAX - sc->nb_frames_for_fps
) {
sc->duration_for_fps += sample_duration;
sc->nb_frames_for_fps ++;
}
}
+ if (frag_stream_info)
+ frag_stream_info->next_trun_dts = dts + sc->time_offset;
if (i < entries) {
// EOF found before reading all entries. Fix the hole this would
// leave in index_entries and ctts_data
return AVERROR_PATCHWELCOME;
}
avio_rb32(pb); // sap_flags
- timestamp = av_rescale_q(pts, st->time_base, timescale);
+ timestamp = av_rescale_q(pts, timescale, st->time_base);
index = update_frag_index(c, offset);
frag_stream_info = get_frag_stream_info(&c->frag_index, index, track_id);
}
}
}
- for (i = 0; i < c->fc->nb_streams; i++) {
+ if (ref_st) for (i = 0; i < c->fc->nb_streams; i++) {
st = c->fc->streams[i];
sc = st->priv_data;
if (!sc->has_sidx) {
return 0;
}
+static int mov_read_av1c(MOVContext *c, AVIOContext *pb, MOVAtom atom)
+{
+ AVStream *st;
+ int ret;
+
+ if (c->fc->nb_streams < 1)
+ return 0;
+ st = c->fc->streams[c->fc->nb_streams - 1];
+
+ if (atom.size < 4) {
+ av_log(c->fc, AV_LOG_ERROR, "Empty AV1 Codec Configuration Box\n");
+ return AVERROR_INVALIDDATA;
+ }
+
+ /* For now, propagate only the OBUs, if any. Once libavcodec is
+ updated to handle isobmff style extradata this can be removed. */
+ avio_skip(pb, 4);
+
+ if (atom.size == 4)
+ return 0;
+
+ ret = ff_get_extradata(c->fc, st->codecpar, pb, atom.size - 4);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
static int mov_read_vpcc(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
AVStream *st;
static int mov_read_smdm(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
MOVStreamContext *sc;
- const int chroma_den = 50000;
- const int luma_den = 10000;
- int i, j, version;
+ int i, version;
if (c->fc->nb_streams < 1)
return AVERROR_INVALIDDATA;
if (!sc->mastering)
return AVERROR(ENOMEM);
- for (i = 0; i < 3; i++)
- for (j = 0; j < 2; j++)
- sc->mastering->display_primaries[i][j] =
- av_make_q(lrint(((double)avio_rb16(pb) / (1 << 16)) * chroma_den), chroma_den);
- for (i = 0; i < 2; i++)
- sc->mastering->white_point[i] =
- av_make_q(lrint(((double)avio_rb16(pb) / (1 << 16)) * chroma_den), chroma_den);
- sc->mastering->max_luminance =
- av_make_q(lrint(((double)avio_rb32(pb) / (1 << 8)) * luma_den), luma_den);
- sc->mastering->min_luminance =
- av_make_q(lrint(((double)avio_rb32(pb) / (1 << 14)) * luma_den), luma_den);
+ for (i = 0; i < 3; i++) {
+ sc->mastering->display_primaries[i][0] = av_make_q(avio_rb16(pb), 1 << 16);
+ sc->mastering->display_primaries[i][1] = av_make_q(avio_rb16(pb), 1 << 16);
+ }
+ sc->mastering->white_point[0] = av_make_q(avio_rb16(pb), 1 << 16);
+ sc->mastering->white_point[1] = av_make_q(avio_rb16(pb), 1 << 16);
+
+ sc->mastering->max_luminance = av_make_q(avio_rb32(pb), 1 << 8);
+ sc->mastering->min_luminance = av_make_q(avio_rb32(pb), 1 << 14);
sc->mastering->has_primaries = 1;
sc->mastering->has_luminance = 1;
return AVERROR_INVALIDDATA;
}
buffer[len] = '\0';
- av_dict_set(&c->fc->metadata, "xmp", buffer, 0);
- av_free(buffer);
+ av_dict_set(&c->fc->metadata, "xmp",
+ buffer, AV_DICT_DONT_STRDUP_VAL);
} else {
// skip all uuid atom, which makes it fast for long uuid-xmp file
ret = avio_skip(pb, len);
*sc = st->priv_data;
if (!frag_stream_info->encryption_index) {
+ // If this stream isn't encrypted, don't create the index.
+ if (!(*sc)->cenc.default_encrypted_sample)
+ return 0;
frag_stream_info->encryption_index = av_mallocz(sizeof(*frag_stream_info->encryption_index));
if (!frag_stream_info->encryption_index)
return AVERROR(ENOMEM);
*sc = st->priv_data;
if (!(*sc)->cenc.encryption_index) {
+ // If this stream isn't encrypted, don't create the index.
+ if (!(*sc)->cenc.default_encrypted_sample)
+ return 0;
(*sc)->cenc.encryption_index = av_mallocz(sizeof(*frag_stream_info->encryption_index));
if (!(*sc)->cenc.encryption_index)
return AVERROR(ENOMEM);
unsigned int subsample_count;
AVSubsampleEncryptionInfo *subsamples;
+ if (!sc->cenc.default_encrypted_sample) {
+ av_log(c->fc, AV_LOG_ERROR, "Missing schm or tenc\n");
+ return AVERROR_INVALIDDATA;
+ }
+
*sample = av_encryption_info_clone(sc->cenc.default_encrypted_sample);
if (!*sample)
return AVERROR(ENOMEM);
return AVERROR(ENOMEM);
for (i = 0; i < sample_count; i++) {
- unsigned int min_samples = FFMIN(FFMAX(i, 1024 * 1024), sample_count);
+ unsigned int min_samples = FFMIN(FFMAX(i + 1, 1024 * 1024), sample_count);
encrypted_samples = av_fast_realloc(encryption_index->encrypted_samples, &alloc_size,
min_samples * sizeof(*encrypted_samples));
if (encrypted_samples) {
return 0;
}
+static int mov_parse_auxiliary_info(MOVContext *c, MOVStreamContext *sc, AVIOContext *pb, MOVEncryptionIndex *encryption_index)
+{
+ AVEncryptionInfo **sample, **encrypted_samples;
+ int64_t prev_pos;
+ size_t sample_count, sample_info_size, i;
+ int ret = 0;
+ unsigned int alloc_size = 0;
+
+ if (encryption_index->nb_encrypted_samples)
+ return 0;
+ sample_count = encryption_index->auxiliary_info_sample_count;
+ if (encryption_index->auxiliary_offsets_count != 1) {
+ av_log(c->fc, AV_LOG_ERROR, "Multiple auxiliary info chunks are not supported\n");
+ return AVERROR_PATCHWELCOME;
+ }
+ if (sample_count >= INT_MAX / sizeof(*encrypted_samples))
+ return AVERROR(ENOMEM);
+
+ prev_pos = avio_tell(pb);
+ if (!(pb->seekable & AVIO_SEEKABLE_NORMAL) ||
+ avio_seek(pb, encryption_index->auxiliary_offsets[0], SEEK_SET) != encryption_index->auxiliary_offsets[0]) {
+ av_log(c->fc, AV_LOG_INFO, "Failed to seek for auxiliary info, will only parse senc atoms for encryption info\n");
+ goto finish;
+ }
+
+ for (i = 0; i < sample_count && !pb->eof_reached; i++) {
+ unsigned int min_samples = FFMIN(FFMAX(i + 1, 1024 * 1024), sample_count);
+ encrypted_samples = av_fast_realloc(encryption_index->encrypted_samples, &alloc_size,
+ min_samples * sizeof(*encrypted_samples));
+ if (!encrypted_samples) {
+ ret = AVERROR(ENOMEM);
+ goto finish;
+ }
+ encryption_index->encrypted_samples = encrypted_samples;
+
+ sample = &encryption_index->encrypted_samples[i];
+ sample_info_size = encryption_index->auxiliary_info_default_size
+ ? encryption_index->auxiliary_info_default_size
+ : encryption_index->auxiliary_info_sizes[i];
+
+ ret = mov_read_sample_encryption_info(c, pb, sc, sample, sample_info_size > sc->cenc.per_sample_iv_size);
+ if (ret < 0)
+ goto finish;
+ }
+ if (pb->eof_reached) {
+ av_log(c->fc, AV_LOG_ERROR, "Hit EOF while reading auxiliary info\n");
+ ret = AVERROR_INVALIDDATA;
+ } else {
+ encryption_index->nb_encrypted_samples = sample_count;
+ }
+
+finish:
+ avio_seek(pb, prev_pos, SEEK_SET);
+ if (ret < 0) {
+ for (; i > 0; i--) {
+ av_encryption_info_free(encryption_index->encrypted_samples[i - 1]);
+ }
+ av_freep(&encryption_index->encrypted_samples);
+ }
+ return ret;
+}
+
+/**
+ * Tries to read the given number of bytes from the stream and puts it in a
+ * newly allocated buffer. This reads in small chunks to avoid allocating large
+ * memory if the file contains an invalid/malicious size value.
+ */
+static int mov_try_read_block(AVIOContext *pb, size_t size, uint8_t **data)
+{
+ const unsigned int block_size = 1024 * 1024;
+ uint8_t *buffer = NULL;
+ unsigned int alloc_size = 0, offset = 0;
+ while (offset < size) {
+ unsigned int new_size =
+ alloc_size >= INT_MAX - block_size ? INT_MAX : alloc_size + block_size;
+ uint8_t *new_buffer = av_fast_realloc(buffer, &alloc_size, new_size);
+ unsigned int to_read = FFMIN(size, alloc_size) - offset;
+ if (!new_buffer) {
+ av_free(buffer);
+ return AVERROR(ENOMEM);
+ }
+ buffer = new_buffer;
+
+ if (avio_read(pb, buffer + offset, to_read) != to_read) {
+ av_free(buffer);
+ return AVERROR_INVALIDDATA;
+ }
+ offset += to_read;
+ }
+
+ *data = buffer;
+ return 0;
+}
+
+static int mov_read_saiz(MOVContext *c, AVIOContext *pb, MOVAtom atom)
+{
+ MOVEncryptionIndex *encryption_index;
+ MOVStreamContext *sc;
+ int ret;
+ unsigned int sample_count, aux_info_type, aux_info_param;
+
+ ret = get_current_encryption_info(c, &encryption_index, &sc);
+ if (ret != 1)
+ return ret;
+
+ if (encryption_index->nb_encrypted_samples) {
+ // This can happen if we have both saio/saiz and senc atoms.
+ av_log(c->fc, AV_LOG_DEBUG, "Ignoring duplicate encryption info in saiz\n");
+ return 0;
+ }
+
+ if (encryption_index->auxiliary_info_sample_count) {
+ av_log(c->fc, AV_LOG_ERROR, "Duplicate saiz atom\n");
+ return AVERROR_INVALIDDATA;
+ }
+
+ avio_r8(pb); /* version */
+ if (avio_rb24(pb) & 0x01) { /* flags */
+ aux_info_type = avio_rb32(pb);
+ aux_info_param = avio_rb32(pb);
+ if (sc->cenc.default_encrypted_sample) {
+ if (aux_info_type != sc->cenc.default_encrypted_sample->scheme) {
+ av_log(c->fc, AV_LOG_DEBUG, "Ignoring saiz box with non-zero aux_info_type\n");
+ return 0;
+ }
+ if (aux_info_param != 0) {
+ av_log(c->fc, AV_LOG_DEBUG, "Ignoring saiz box with non-zero aux_info_type_parameter\n");
+ return 0;
+ }
+ } else {
+ // Didn't see 'schm' or 'tenc', so this isn't encrypted.
+ if ((aux_info_type == MKBETAG('c','e','n','c') ||
+ aux_info_type == MKBETAG('c','e','n','s') ||
+ aux_info_type == MKBETAG('c','b','c','1') ||
+ aux_info_type == MKBETAG('c','b','c','s')) &&
+ aux_info_param == 0) {
+ av_log(c->fc, AV_LOG_ERROR, "Saw encrypted saiz without schm/tenc\n");
+ return AVERROR_INVALIDDATA;
+ } else {
+ return 0;
+ }
+ }
+ } else if (!sc->cenc.default_encrypted_sample) {
+ // Didn't see 'schm' or 'tenc', so this isn't encrypted.
+ return 0;
+ }
+
+ encryption_index->auxiliary_info_default_size = avio_r8(pb);
+ sample_count = avio_rb32(pb);
+ encryption_index->auxiliary_info_sample_count = sample_count;
+
+ if (encryption_index->auxiliary_info_default_size == 0) {
+ ret = mov_try_read_block(pb, sample_count, &encryption_index->auxiliary_info_sizes);
+ if (ret < 0) {
+ av_log(c->fc, AV_LOG_ERROR, "Failed to read the auxiliary info\n");
+ return ret;
+ }
+ }
+
+ if (encryption_index->auxiliary_offsets_count) {
+ return mov_parse_auxiliary_info(c, sc, pb, encryption_index);
+ }
+
+ return 0;
+}
+
+static int mov_read_saio(MOVContext *c, AVIOContext *pb, MOVAtom atom)
+{
+ uint64_t *auxiliary_offsets;
+ MOVEncryptionIndex *encryption_index;
+ MOVStreamContext *sc;
+ int i, ret;
+ unsigned int version, entry_count, aux_info_type, aux_info_param;
+ unsigned int alloc_size = 0;
+
+ ret = get_current_encryption_info(c, &encryption_index, &sc);
+ if (ret != 1)
+ return ret;
+
+ if (encryption_index->nb_encrypted_samples) {
+ // This can happen if we have both saio/saiz and senc atoms.
+ av_log(c->fc, AV_LOG_DEBUG, "Ignoring duplicate encryption info in saio\n");
+ return 0;
+ }
+
+ if (encryption_index->auxiliary_offsets_count) {
+ av_log(c->fc, AV_LOG_ERROR, "Duplicate saio atom\n");
+ return AVERROR_INVALIDDATA;
+ }
+
+ version = avio_r8(pb); /* version */
+ if (avio_rb24(pb) & 0x01) { /* flags */
+ aux_info_type = avio_rb32(pb);
+ aux_info_param = avio_rb32(pb);
+ if (sc->cenc.default_encrypted_sample) {
+ if (aux_info_type != sc->cenc.default_encrypted_sample->scheme) {
+ av_log(c->fc, AV_LOG_DEBUG, "Ignoring saio box with non-zero aux_info_type\n");
+ return 0;
+ }
+ if (aux_info_param != 0) {
+ av_log(c->fc, AV_LOG_DEBUG, "Ignoring saio box with non-zero aux_info_type_parameter\n");
+ return 0;
+ }
+ } else {
+ // Didn't see 'schm' or 'tenc', so this isn't encrypted.
+ if ((aux_info_type == MKBETAG('c','e','n','c') ||
+ aux_info_type == MKBETAG('c','e','n','s') ||
+ aux_info_type == MKBETAG('c','b','c','1') ||
+ aux_info_type == MKBETAG('c','b','c','s')) &&
+ aux_info_param == 0) {
+ av_log(c->fc, AV_LOG_ERROR, "Saw encrypted saio without schm/tenc\n");
+ return AVERROR_INVALIDDATA;
+ } else {
+ return 0;
+ }
+ }
+ } else if (!sc->cenc.default_encrypted_sample) {
+ // Didn't see 'schm' or 'tenc', so this isn't encrypted.
+ return 0;
+ }
+
+ entry_count = avio_rb32(pb);
+ if (entry_count >= INT_MAX / sizeof(*auxiliary_offsets))
+ return AVERROR(ENOMEM);
+
+ for (i = 0; i < entry_count && !pb->eof_reached; i++) {
+ unsigned int min_offsets = FFMIN(FFMAX(i + 1, 1024), entry_count);
+ auxiliary_offsets = av_fast_realloc(
+ encryption_index->auxiliary_offsets, &alloc_size,
+ min_offsets * sizeof(*auxiliary_offsets));
+ if (!auxiliary_offsets) {
+ av_freep(&encryption_index->auxiliary_offsets);
+ return AVERROR(ENOMEM);
+ }
+ encryption_index->auxiliary_offsets = auxiliary_offsets;
+
+ if (version == 0) {
+ encryption_index->auxiliary_offsets[i] = avio_rb32(pb);
+ } else {
+ encryption_index->auxiliary_offsets[i] = avio_rb64(pb);
+ }
+ if (c->frag_index.current >= 0) {
+ encryption_index->auxiliary_offsets[i] += c->fragment.base_data_offset;
+ }
+ }
+
+ if (pb->eof_reached) {
+ av_log(c->fc, AV_LOG_ERROR, "Hit EOF while reading saio\n");
+ av_freep(&encryption_index->auxiliary_offsets);
+ return AVERROR_INVALIDDATA;
+ }
+
+ encryption_index->auxiliary_offsets_count = entry_count;
+
+ if (encryption_index->auxiliary_info_sample_count) {
+ return mov_parse_auxiliary_info(c, sc, pb, encryption_index);
+ }
+
+ return 0;
+}
+
+static int mov_read_pssh(MOVContext *c, AVIOContext *pb, MOVAtom atom)
+{
+ AVEncryptionInitInfo *info, *old_init_info;
+ uint8_t **key_ids;
+ AVStream *st;
+ uint8_t *side_data, *extra_data, *old_side_data;
+ size_t side_data_size;
+ int ret = 0, old_side_data_size;
+ unsigned int version, kid_count, extra_data_size, alloc_size = 0;
+
+ if (c->fc->nb_streams < 1)
+ return 0;
+ st = c->fc->streams[c->fc->nb_streams-1];
+
+ version = avio_r8(pb); /* version */
+ avio_rb24(pb); /* flags */
+
+ info = av_encryption_init_info_alloc(/* system_id_size */ 16, /* num_key_ids */ 0,
+ /* key_id_size */ 16, /* data_size */ 0);
+ if (!info)
+ return AVERROR(ENOMEM);
+
+ if (avio_read(pb, info->system_id, 16) != 16) {
+ av_log(c->fc, AV_LOG_ERROR, "Failed to read the system id\n");
+ ret = AVERROR_INVALIDDATA;
+ goto finish;
+ }
+
+ if (version > 0) {
+ kid_count = avio_rb32(pb);
+ if (kid_count >= INT_MAX / sizeof(*key_ids)) {
+ ret = AVERROR(ENOMEM);
+ goto finish;
+ }
+
+ for (unsigned int i = 0; i < kid_count && !pb->eof_reached; i++) {
+ unsigned int min_kid_count = FFMIN(FFMAX(i + 1, 1024), kid_count);
+ key_ids = av_fast_realloc(info->key_ids, &alloc_size,
+ min_kid_count * sizeof(*key_ids));
+ if (!key_ids) {
+ ret = AVERROR(ENOMEM);
+ goto finish;
+ }
+ info->key_ids = key_ids;
+
+ info->key_ids[i] = av_mallocz(16);
+ if (!info->key_ids[i]) {
+ ret = AVERROR(ENOMEM);
+ goto finish;
+ }
+ info->num_key_ids = i + 1;
+
+ if (avio_read(pb, info->key_ids[i], 16) != 16) {
+ av_log(c->fc, AV_LOG_ERROR, "Failed to read the key id\n");
+ ret = AVERROR_INVALIDDATA;
+ goto finish;
+ }
+ }
+
+ if (pb->eof_reached) {
+ av_log(c->fc, AV_LOG_ERROR, "Hit EOF while reading pssh\n");
+ ret = AVERROR_INVALIDDATA;
+ goto finish;
+ }
+ }
+
+ extra_data_size = avio_rb32(pb);
+ ret = mov_try_read_block(pb, extra_data_size, &extra_data);
+ if (ret < 0)
+ goto finish;
+
+ av_freep(&info->data); // malloc(0) may still allocate something.
+ info->data = extra_data;
+ info->data_size = extra_data_size;
+
+ // If there is existing initialization data, append to the list.
+ old_side_data = av_stream_get_side_data(st, AV_PKT_DATA_ENCRYPTION_INIT_INFO, &old_side_data_size);
+ if (old_side_data) {
+ old_init_info = av_encryption_init_info_get_side_data(old_side_data, old_side_data_size);
+ if (old_init_info) {
+ // Append to the end of the list.
+ for (AVEncryptionInitInfo *cur = old_init_info;; cur = cur->next) {
+ if (!cur->next) {
+ cur->next = info;
+ break;
+ }
+ }
+ info = old_init_info;
+ } else {
+ // Assume existing side-data will be valid, so the only error we could get is OOM.
+ ret = AVERROR(ENOMEM);
+ goto finish;
+ }
+ }
+
+ side_data = av_encryption_init_info_add_side_data(info, &side_data_size);
+ if (!side_data) {
+ ret = AVERROR(ENOMEM);
+ goto finish;
+ }
+ ret = av_stream_add_side_data(st, AV_PKT_DATA_ENCRYPTION_INIT_INFO,
+ side_data, side_data_size);
+ if (ret < 0)
+ av_free(side_data);
+
+finish:
+ av_encryption_init_info_free(info);
+ return ret;
+}
+
static int mov_read_schm(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
AVStream *st;
return AVERROR(ENOMEM);
}
sc->cenc.per_sample_iv_size = avio_r8(pb);
+ if (sc->cenc.per_sample_iv_size != 0 && sc->cenc.per_sample_iv_size != 8 &&
+ sc->cenc.per_sample_iv_size != 16) {
+ av_log(c->fc, AV_LOG_ERROR, "invalid per-sample IV size value\n");
+ return AVERROR_INVALIDDATA;
+ }
if (avio_read(pb, sc->cenc.default_encrypted_sample->key_id, 16) != 16) {
- av_log(c->fc, AV_LOG_ERROR, "failed to read the default key ID");
+ av_log(c->fc, AV_LOG_ERROR, "failed to read the default key ID\n");
return AVERROR_INVALIDDATA;
}
}
if (avio_read(pb, sc->cenc.default_encrypted_sample->iv, iv_size) != iv_size) {
- av_log(c->fc, AV_LOG_ERROR, "failed to read the default IV");
+ av_log(c->fc, AV_LOG_ERROR, "failed to read the default IV\n");
return AVERROR_INVALIDDATA;
}
}
return 0;
}
-static int cenc_filter(MOVContext *mov, MOVStreamContext *sc, AVPacket *pkt, int current_index)
+static int cenc_filter(MOVContext *mov, AVStream* st, MOVStreamContext *sc, AVPacket *pkt, int current_index)
{
MOVFragmentStreamInfo *frag_stream_info;
MOVEncryptionIndex *encryption_index;
AVEncryptionInfo *encrypted_sample;
- int encrypted_index;
+ int encrypted_index, ret;
- frag_stream_info = get_current_frag_stream_info(&mov->frag_index);
+ frag_stream_info = get_frag_stream_info(&mov->frag_index, mov->frag_index.current, st->id);
encrypted_index = current_index;
encryption_index = NULL;
if (frag_stream_info) {
}
if (encryption_index) {
+ if (encryption_index->auxiliary_info_sample_count &&
+ !encryption_index->nb_encrypted_samples) {
+ av_log(mov->fc, AV_LOG_ERROR, "saiz atom found without saio\n");
+ return AVERROR_INVALIDDATA;
+ }
+ if (encryption_index->auxiliary_offsets_count &&
+ !encryption_index->nb_encrypted_samples) {
+ av_log(mov->fc, AV_LOG_ERROR, "saio atom found without saiz\n");
+ return AVERROR_INVALIDDATA;
+ }
+
if (!encryption_index->nb_encrypted_samples) {
// Full-sample encryption with default settings.
encrypted_sample = sc->cenc.default_encrypted_sample;
if (mov->decryption_key) {
return cenc_decrypt(mov, sc, encrypted_sample, pkt->data, pkt->size);
+ } else {
+ size_t size;
+ uint8_t *side_data = av_encryption_info_add_side_data(encrypted_sample, &size);
+ if (!side_data)
+ return AVERROR(ENOMEM);
+ ret = av_packet_add_side_data(pkt, AV_PKT_DATA_ENCRYPTION_INFO, side_data, size);
+ if (ret < 0)
+ av_free(side_data);
+ return ret;
}
}
static int mov_read_dops(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
const int OPUS_SEEK_PREROLL_MS = 80;
+ int ret;
AVStream *st;
size_t size;
- int16_t pre_skip;
+ uint16_t pre_skip;
if (c->fc->nb_streams < 1)
return 0;
/* OpusSpecificBox size plus magic for Ogg OpusHead header. */
size = atom.size + 8;
- if (ff_alloc_extradata(st->codecpar, size))
- return AVERROR(ENOMEM);
+ if ((ret = ff_alloc_extradata(st->codecpar, size)) < 0)
+ return ret;
AV_WL32(st->codecpar->extradata, MKTAG('O','p','u','s'));
AV_WL32(st->codecpar->extradata + 4, MKTAG('H','e','a','d'));
return 0;
}
+static int mov_read_dmlp(MOVContext *c, AVIOContext *pb, MOVAtom atom)
+{
+ AVStream *st;
+ unsigned format_info;
+ int channel_assignment, channel_assignment1, channel_assignment2;
+ int ratebits;
+
+ if (c->fc->nb_streams < 1)
+ return 0;
+ st = c->fc->streams[c->fc->nb_streams-1];
+
+ if (atom.size < 10)
+ return AVERROR_INVALIDDATA;
+
+ format_info = avio_rb32(pb);
+
+ ratebits = (format_info >> 28) & 0xF;
+ channel_assignment1 = (format_info >> 15) & 0x1F;
+ channel_assignment2 = format_info & 0x1FFF;
+ if (channel_assignment2)
+ channel_assignment = channel_assignment2;
+ else
+ channel_assignment = channel_assignment1;
+
+ st->codecpar->frame_size = 40 << (ratebits & 0x7);
+ st->codecpar->sample_rate = mlp_samplerate(ratebits);
+ st->codecpar->channels = truehd_channels(channel_assignment);
+ st->codecpar->channel_layout = truehd_layout(channel_assignment);
+
+ return 0;
+}
+
+static int mov_read_dvcc_dvvc(MOVContext *c, AVIOContext *pb, MOVAtom atom)
+{
+ AVStream *st;
+ uint32_t buf;
+ AVDOVIDecoderConfigurationRecord *dovi;
+ size_t dovi_size;
+ int ret;
+
+ if (c->fc->nb_streams < 1)
+ return 0;
+ st = c->fc->streams[c->fc->nb_streams-1];
+
+ if ((uint64_t)atom.size > (1<<30) || atom.size < 4)
+ return AVERROR_INVALIDDATA;
+
+ dovi = av_dovi_alloc(&dovi_size);
+ if (!dovi)
+ return AVERROR(ENOMEM);
+
+ dovi->dv_version_major = avio_r8(pb);
+ dovi->dv_version_minor = avio_r8(pb);
+
+ buf = avio_rb16(pb);
+ dovi->dv_profile = (buf >> 9) & 0x7f; // 7 bits
+ dovi->dv_level = (buf >> 3) & 0x3f; // 6 bits
+ dovi->rpu_present_flag = (buf >> 2) & 0x01; // 1 bit
+ dovi->el_present_flag = (buf >> 1) & 0x01; // 1 bit
+ dovi->bl_present_flag = buf & 0x01; // 1 bit
+ if (atom.size >= 24) { // 4 + 4 + 4 * 4
+ buf = avio_r8(pb);
+ dovi->dv_bl_signal_compatibility_id = (buf >> 4) & 0x0f; // 4 bits
+ } else {
+ // 0 stands for None
+ // Dolby Vision V1.2.93 profiles and levels
+ dovi->dv_bl_signal_compatibility_id = 0;
+ }
+
+ ret = av_stream_add_side_data(st, AV_PKT_DATA_DOVI_CONF,
+ (uint8_t *)dovi, dovi_size);
+ if (ret < 0) {
+ av_freep(dovi);
+ return ret;
+ }
+
+ av_log(c, AV_LOG_TRACE, "DOVI in dvcC/dvvC box, version: %d.%d, profile: %d, level: %d, "
+ "rpu flag: %d, el flag: %d, bl flag: %d, compatibility id: %d\n",
+ dovi->dv_version_major, dovi->dv_version_minor,
+ dovi->dv_profile, dovi->dv_level,
+ dovi->rpu_present_flag,
+ dovi->el_present_flag,
+ dovi->bl_present_flag,
+ dovi->dv_bl_signal_compatibility_id
+ );
+
+ return 0;
+}
+
static const MOVParseTableEntry mov_default_parse_table[] = {
{ MKTAG('A','C','L','R'), mov_read_aclr },
{ MKTAG('A','P','R','G'), mov_read_avid },
{ MKTAG('A','A','L','P'), mov_read_avid },
{ MKTAG('A','R','E','S'), mov_read_ares },
{ MKTAG('a','v','s','s'), mov_read_avss },
+{ MKTAG('a','v','1','C'), mov_read_av1c },
{ MKTAG('c','h','p','l'), mov_read_chpl },
{ MKTAG('c','o','6','4'), mov_read_stco },
{ MKTAG('c','o','l','r'), mov_read_colr },
{ MKTAG('s','t','s','z'), mov_read_stsz }, /* sample size */
{ MKTAG('s','t','t','s'), mov_read_stts },
{ MKTAG('s','t','z','2'), mov_read_stsz }, /* compact sample size */
+{ MKTAG('s','d','t','p'), mov_read_sdtp }, /* independent and disposable samples */
{ MKTAG('t','k','h','d'), mov_read_tkhd }, /* track header */
{ MKTAG('t','f','d','t'), mov_read_tfdt },
{ MKTAG('t','f','h','d'), mov_read_tfhd }, /* track fragment header */
{ MKTAG('s','i','n','f'), mov_read_default },
{ MKTAG('f','r','m','a'), mov_read_frma },
{ MKTAG('s','e','n','c'), mov_read_senc },
+{ MKTAG('s','a','i','z'), mov_read_saiz },
+{ MKTAG('s','a','i','o'), mov_read_saio },
+{ MKTAG('p','s','s','h'), mov_read_pssh },
{ MKTAG('s','c','h','m'), mov_read_schm },
{ MKTAG('s','c','h','i'), mov_read_default },
{ MKTAG('t','e','n','c'), mov_read_tenc },
{ MKTAG('s','t','3','d'), mov_read_st3d }, /* stereoscopic 3D video box */
{ MKTAG('s','v','3','d'), mov_read_sv3d }, /* spherical video box */
{ MKTAG('d','O','p','s'), mov_read_dops },
+{ MKTAG('d','m','l','p'), mov_read_dmlp },
{ MKTAG('S','m','D','m'), mov_read_smdm },
{ MKTAG('C','o','L','L'), mov_read_coll },
{ MKTAG('v','p','c','C'), mov_read_vpcc },
{ MKTAG('m','d','c','v'), mov_read_mdcv },
{ MKTAG('c','l','l','i'), mov_read_clli },
+{ MKTAG('d','v','c','C'), mov_read_dvcc_dvvc },
+{ MKTAG('d','v','v','C'), mov_read_dvcc_dvvc },
{ 0, NULL }
};
if (atom.size >= 8) {
a.size = avio_rb32(pb);
a.type = avio_rl32(pb);
- if (a.type == MKTAG('f','r','e','e') &&
+ if (((a.type == MKTAG('f','r','e','e') && c->moov_retry) ||
+ a.type == MKTAG('h','o','o','v')) &&
a.size >= 8 &&
- c->fc->strict_std_compliance < FF_COMPLIANCE_STRICT &&
- c->moov_retry) {
+ c->fc->strict_std_compliance < FF_COMPLIANCE_STRICT) {
uint8_t buf[8];
uint32_t *type = (uint32_t *)buf + 1;
if (avio_read(pb, buf, 8) != 8)
avio_seek(pb, -8, SEEK_CUR);
if (*type == MKTAG('m','v','h','d') ||
*type == MKTAG('c','m','o','v')) {
- av_log(c->fc, AV_LOG_ERROR, "Detected moov in a free atom.\n");
+ av_log(c->fc, AV_LOG_ERROR, "Detected moov in a free or hoov atom.\n");
a.type = MKTAG('m','o','o','v');
}
}
return 0;
}
-static int mov_probe(AVProbeData *p)
+static int mov_probe(const AVProbeData *p)
{
int64_t offset;
uint32_t tag;
av_encryption_info_free((*index)->encrypted_samples[i]);
}
av_freep(&(*index)->encrypted_samples);
+ av_freep(&(*index)->auxiliary_info_sizes);
+ av_freep(&(*index)->auxiliary_offsets);
av_freep(index);
}
av_freep(&sc->sample_sizes);
av_freep(&sc->keyframes);
av_freep(&sc->stts_data);
+ av_freep(&sc->sdtp_data);
av_freep(&sc->stps_data);
av_freep(&sc->elst_data);
av_freep(&sc->rap_group);
mov->next_root_atom = 0;
if (index < 0 || index >= mov->frag_index.nb_items)
index = search_frag_moof_offset(&mov->frag_index, target);
- if (index < mov->frag_index.nb_items) {
+ if (index < mov->frag_index.nb_items &&
+ mov->frag_index.item[index].moof_offset == target) {
if (index + 1 < mov->frag_index.nb_items)
mov->next_root_atom = mov->frag_index.item[index + 1].moof_offset;
if (mov->frag_index.item[index].headers_read)
} else {
int64_t next_dts = (sc->current_sample < st->nb_index_entries) ?
st->index_entries[sc->current_sample].timestamp : st->duration;
- pkt->duration = next_dts - pkt->dts;
+
+ if (next_dts >= pkt->dts)
+ pkt->duration = next_dts - pkt->dts;
pkt->pts = pkt->dts;
}
if (st->discard == AVDISCARD_ALL)
goto retry;
+ if (sc->sdtp_data && sc->current_sample <= sc->sdtp_count) {
+ uint8_t sample_flags = sc->sdtp_data[sc->current_sample - 1];
+ uint8_t sample_is_depended_on = (sample_flags >> 2) & 0x3;
+ pkt->flags |= sample_is_depended_on == MOV_SAMPLE_DEPENDENCY_NO ? AV_PKT_FLAG_DISPOSABLE : 0;
+ }
pkt->flags |= sample->flags & AVINDEX_KEYFRAME ? AV_PKT_FLAG_KEY : 0;
pkt->pos = sample->pos;
if (mov->aax_mode)
aax_filter(pkt->data, pkt->size, mov);
- ret = cenc_filter(mov, sc, pkt, current_index);
- if (ret < 0)
+ ret = cenc_filter(mov, st, sc, pkt, current_index);
+ if (ret < 0) {
return ret;
+ }
return 0;
}
}
/* adjust stsd index */
+ if (sc->chunk_count) {
time_sample = 0;
for (i = 0; i < sc->stsc_count; i++) {
int64_t next = time_sample + mov_get_stsc_samples(sc, i);
av_assert0(next == (int)next);
time_sample = next;
}
+ }
return sample;
}
OFFSET(use_absolute_path), AV_OPT_TYPE_BOOL, {.i64 = 0},
0, 1, FLAGS},
{"seek_streams_individually",
- "Seek each stream individually to the to the closest point",
+ "Seek each stream individually to the closest point",
OFFSET(seek_individually), AV_OPT_TYPE_BOOL, { .i64 = 1 },
0, 1, FLAGS},
{"ignore_editlist", "Ignore the edit list atom.", OFFSET(ignore_editlist), AV_OPT_TYPE_BOOL, {.i64 = 0},
.long_name = NULL_IF_CONFIG_SMALL("QuickTime / MOV"),
.priv_class = &mov_class,
.priv_data_size = sizeof(MOVContext),
- .extensions = "mov,mp4,m4a,3gp,3g2,mj2",
+ .extensions = "mov,mp4,m4a,3gp,3g2,mj2,psp,m4b,ism,ismv,isma,f4v",
.read_probe = mov_probe,
.read_header = mov_read_header,
.read_packet = mov_read_packet,
.read_close = mov_read_close,
.read_seek = mov_read_seek,
- .flags = AVFMT_NO_BYTE_SEEK,
+ .flags = AVFMT_NO_BYTE_SEEK | AVFMT_SEEK_TO_PTS,
};