summaryrefslogtreecommitdiffstats
path: root/media/mp4
diff options
context:
space:
mode:
authorstrobe@google.com <strobe@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-26 00:49:59 +0000
committerstrobe@google.com <strobe@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-26 00:49:59 +0000
commit9746f9132e55a91d2ec3d866711277b874574743 (patch)
treeb0f6d236afc3515855403363b6e9fe455a6c801d /media/mp4
parent7db8893ab741949612cebfed89e11d267daacbf9 (diff)
downloadchromium_src-9746f9132e55a91d2ec3d866711277b874574743.zip
chromium_src-9746f9132e55a91d2ec3d866711277b874574743.tar.gz
chromium_src-9746f9132e55a91d2ec3d866711277b874574743.tar.bz2
Add Common Encryption support to BMFF, including subsample decryption.
BUG=132351 TEST=AesDecryptorTest, plus manual playback in browser Review URL: https://chromiumcodereview.appspot.com/10651006 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@148453 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/mp4')
-rw-r--r--media/mp4/avc.cc27
-rw-r--r--media/mp4/avc.h4
-rw-r--r--media/mp4/avc_unittest.cc15
-rw-r--r--media/mp4/box_definitions.cc36
-rw-r--r--media/mp4/box_definitions.h4
-rw-r--r--media/mp4/cenc.cc27
-rw-r--r--media/mp4/cenc.h12
-rw-r--r--media/mp4/mp4_stream_parser.cc153
-rw-r--r--media/mp4/mp4_stream_parser.h7
-rw-r--r--media/mp4/mp4_stream_parser_unittest.cc29
-rw-r--r--media/mp4/track_run_iterator.cc221
-rw-r--r--media/mp4/track_run_iterator.h32
-rw-r--r--media/mp4/track_run_iterator_unittest.cc240
13 files changed, 628 insertions, 179 deletions
diff --git a/media/mp4/avc.cc b/media/mp4/avc.cc
index 2999466..ae28ffd 100644
--- a/media/mp4/avc.cc
+++ b/media/mp4/avc.cc
@@ -32,7 +32,7 @@ static bool ConvertAVCToAnnexBInPlaceForLengthSize4(std::vector<uint8>* buf) {
}
// static
-bool AVC::ConvertToAnnexB(int length_size, std::vector<uint8>* buffer) {
+bool AVC::ConvertFrameToAnnexB(int length_size, std::vector<uint8>* buffer) {
RCHECK(length_size == 1 || length_size == 2 || length_size == 4);
if (length_size == 4)
@@ -59,32 +59,31 @@ bool AVC::ConvertToAnnexB(int length_size, std::vector<uint8>* buffer) {
}
// static
-bool AVC::InsertParameterSets(const AVCDecoderConfigurationRecord& avc_config,
- std::vector<uint8>* buffer) {
+bool AVC::ConvertConfigToAnnexB(
+ const AVCDecoderConfigurationRecord& avc_config,
+ std::vector<uint8>* buffer) {
+ DCHECK(buffer->empty());
+ buffer->clear();
int total_size = 0;
for (size_t i = 0; i < avc_config.sps_list.size(); i++)
total_size += avc_config.sps_list[i].size() + kAnnexBStartCodeSize;
for (size_t i = 0; i < avc_config.pps_list.size(); i++)
total_size += avc_config.pps_list[i].size() + kAnnexBStartCodeSize;
-
- std::vector<uint8> temp;
- temp.reserve(total_size);
+ buffer->reserve(total_size);
for (size_t i = 0; i < avc_config.sps_list.size(); i++) {
- temp.insert(temp.end(), kAnnexBStartCode,
+ buffer->insert(buffer->end(), kAnnexBStartCode,
kAnnexBStartCode + kAnnexBStartCodeSize);
- temp.insert(temp.end(), avc_config.sps_list[i].begin(),
+ buffer->insert(buffer->end(), avc_config.sps_list[i].begin(),
avc_config.sps_list[i].end());
}
for (size_t i = 0; i < avc_config.pps_list.size(); i++) {
- temp.insert(temp.end(), kAnnexBStartCode,
- kAnnexBStartCode + kAnnexBStartCodeSize);
- temp.insert(temp.end(), avc_config.pps_list[i].begin(),
- avc_config.pps_list[i].end());
+ buffer->insert(buffer->end(), kAnnexBStartCode,
+ kAnnexBStartCode + kAnnexBStartCodeSize);
+ buffer->insert(buffer->end(), avc_config.pps_list[i].begin(),
+ avc_config.pps_list[i].end());
}
-
- buffer->insert(buffer->begin(), temp.begin(), temp.end());
return true;
}
diff --git a/media/mp4/avc.h b/media/mp4/avc.h
index 8e826b0..3d815a1 100644
--- a/media/mp4/avc.h
+++ b/media/mp4/avc.h
@@ -17,9 +17,9 @@ struct AVCDecoderConfigurationRecord;
class MEDIA_EXPORT AVC {
public:
- static bool ConvertToAnnexB(int length_size, std::vector<uint8>* buffer);
+ static bool ConvertFrameToAnnexB(int length_size, std::vector<uint8>* buffer);
- static bool InsertParameterSets(
+ static bool ConvertConfigToAnnexB(
const AVCDecoderConfigurationRecord& avc_config,
std::vector<uint8>* buffer);
};
diff --git a/media/mp4/avc_unittest.cc b/media/mp4/avc_unittest.cc
index cdcc413..766a979 100644
--- a/media/mp4/avc_unittest.cc
+++ b/media/mp4/avc_unittest.cc
@@ -23,7 +23,7 @@ static const uint8 kExpected[] = {
static const uint8 kExpectedParamSets[] = {
0x00, 0x00, 0x00, 0x01, 0x67, 0x12,
0x00, 0x00, 0x00, 0x01, 0x67, 0x34,
- 0x00, 0x00, 0x00, 0x01, 0x68, 0x56, 0x78, 0x9a};
+ 0x00, 0x00, 0x00, 0x01, 0x68, 0x56, 0x78};
class AVCConversionTest : public testing::TestWithParam<int> {
protected:
@@ -44,7 +44,7 @@ class AVCConversionTest : public testing::TestWithParam<int> {
TEST_P(AVCConversionTest, ParseCorrectly) {
std::vector<uint8> buf;
MakeInputForLength(GetParam(), &buf);
- EXPECT_TRUE(AVC::ConvertToAnnexB(GetParam(), &buf));
+ EXPECT_TRUE(AVC::ConvertFrameToAnnexB(GetParam(), &buf));
EXPECT_EQ(buf.size(), sizeof(kExpected));
EXPECT_EQ(0, memcmp(kExpected, &buf[0], sizeof(kExpected)));
}
@@ -53,19 +53,19 @@ TEST_P(AVCConversionTest, ParsePartial) {
std::vector<uint8> buf;
MakeInputForLength(GetParam(), &buf);
buf.pop_back();
- EXPECT_FALSE(AVC::ConvertToAnnexB(GetParam(), &buf));
+ EXPECT_FALSE(AVC::ConvertFrameToAnnexB(GetParam(), &buf));
// This tests a buffer ending in the middle of a NAL length. For length size
// of one, this can't happen, so we skip that case.
if (GetParam() != 1) {
MakeInputForLength(GetParam(), &buf);
buf.erase(buf.end() - (sizeof(kNALU2) + 1), buf.end());
- EXPECT_FALSE(AVC::ConvertToAnnexB(GetParam(), &buf));
+ EXPECT_FALSE(AVC::ConvertFrameToAnnexB(GetParam(), &buf));
}
}
TEST_P(AVCConversionTest, ParseEmpty) {
std::vector<uint8> buf;
- EXPECT_TRUE(AVC::ConvertToAnnexB(GetParam(), &buf));
+ EXPECT_TRUE(AVC::ConvertFrameToAnnexB(GetParam(), &buf));
EXPECT_EQ(0u, buf.size());
}
@@ -73,7 +73,7 @@ INSTANTIATE_TEST_CASE_P(AVCConversionTestValues,
AVCConversionTest,
::testing::Values(1, 2, 4));
-TEST(AVC, InsertParameterSetsTest) {
+TEST_F(AVCConversionTest, ConvertConfigToAnnexB) {
AVCDecoderConfigurationRecord avc_config;
avc_config.sps_list.resize(2);
avc_config.sps_list[0].push_back(0x67);
@@ -86,8 +86,7 @@ TEST(AVC, InsertParameterSetsTest) {
avc_config.pps_list[0].push_back(0x78);
std::vector<uint8> buf;
- buf.push_back(0x9a);
- EXPECT_TRUE(AVC::InsertParameterSets(avc_config, &buf));
+ EXPECT_TRUE(AVC::ConvertConfigToAnnexB(avc_config, &buf));
EXPECT_EQ(0, memcmp(kExpectedParamSets, &buf[0],
sizeof(kExpectedParamSets)));
}
diff --git a/media/mp4/box_definitions.cc b/media/mp4/box_definitions.cc
index c9245d8..d6ab0fc 100644
--- a/media/mp4/box_definitions.cc
+++ b/media/mp4/box_definitions.cc
@@ -27,7 +27,7 @@ FourCC ProtectionSystemSpecificHeader::BoxType() const { return FOURCC_PSSH; }
bool ProtectionSystemSpecificHeader::Parse(BoxReader* reader) {
uint32 size;
- return reader->SkipBytes(4) &&
+ return reader->ReadFullBoxHeader() &&
reader->ReadVec(&system_id, 16) &&
reader->Read4(&size) &&
reader->ReadVec(&data, size);
@@ -88,7 +88,7 @@ SchemeType::~SchemeType() {}
FourCC SchemeType::BoxType() const { return FOURCC_SCHM; }
bool SchemeType::Parse(BoxReader* reader) {
- RCHECK(reader->SkipBytes(4) &&
+ RCHECK(reader->ReadFullBoxHeader() &&
reader->ReadFourCC(&type) &&
reader->Read4(&version));
RCHECK(type == FOURCC_CENC);
@@ -103,7 +103,8 @@ FourCC TrackEncryption::BoxType() const { return FOURCC_TENC; }
bool TrackEncryption::Parse(BoxReader* reader) {
uint8 flag;
- RCHECK(reader->SkipBytes(2) &&
+ RCHECK(reader->ReadFullBoxHeader() &&
+ reader->SkipBytes(2) &&
reader->Read1(&flag) &&
reader->Read1(&default_iv_size) &&
reader->ReadVec(&default_kid, 16));
@@ -129,9 +130,11 @@ ProtectionSchemeInfo::~ProtectionSchemeInfo() {}
FourCC ProtectionSchemeInfo::BoxType() const { return FOURCC_SINF; }
bool ProtectionSchemeInfo::Parse(BoxReader* reader) {
- return reader->ScanChildren() &&
+ RCHECK(reader->ScanChildren() &&
+ reader->ReadChild(&format) &&
reader->ReadChild(&type) &&
- reader->ReadChild(&info);
+ reader->ReadChild(&info));
+ return true;
}
MovieHeader::MovieHeader()
@@ -375,20 +378,15 @@ bool VideoSampleEntry::Parse(BoxReader* reader) {
reader->Read2(&height) &&
reader->SkipBytes(50));
- RCHECK(reader->ScanChildren());
- RCHECK(reader->MaybeReadChild(&pixel_aspect));
- if (format == FOURCC_ENCV) {
- RCHECK(reader->ReadChild(&sinf));
- }
+ RCHECK(reader->ScanChildren() &&
+ reader->MaybeReadChild(&pixel_aspect));
- // TODO(strobe): finalize format signaling for encrypted media
- // (http://crbug.com/132351)
- //
- // if (format == FOURCC_AVC1 ||
- // (format == FOURCC_ENCV &&
- // sinf.format.format == FOURCC_AVC1)) {
+ if (format == FOURCC_ENCV)
+ RCHECK(reader->ReadChild(&sinf));
+ if (format == FOURCC_AVC1 ||
+ (format == FOURCC_ENCV && sinf.format.format == FOURCC_AVC1)) {
RCHECK(reader->ReadChild(&avcc));
- // }
+ }
return true;
}
@@ -444,9 +442,8 @@ bool AudioSampleEntry::Parse(BoxReader* reader) {
samplerate >>= 16;
RCHECK(reader->ScanChildren());
- if (format == FOURCC_ENCA) {
+ if (format == FOURCC_ENCA)
RCHECK(reader->ReadChild(&sinf));
- }
RCHECK(reader->ReadChild(&esds));
return true;
}
@@ -597,6 +594,7 @@ bool MovieFragmentHeader::Parse(BoxReader* reader) {
TrackFragmentHeader::TrackFragmentHeader()
: track_id(0),
+ sample_description_index(0),
default_sample_duration(0),
default_sample_size(0),
default_sample_flags(0),
diff --git a/media/mp4/box_definitions.h b/media/mp4/box_definitions.h
index 0b19a5c..b78ebdd 100644
--- a/media/mp4/box_definitions.h
+++ b/media/mp4/box_definitions.h
@@ -217,7 +217,9 @@ struct MEDIA_EXPORT SampleTable : Box {
DECLARE_BOX_METHODS(SampleTable);
// Media Source specific: we ignore many of the sub-boxes in this box,
- // including some that are required to be present in the BMFF spec.
+ // including some that are required to be present in the BMFF spec. This
+ // includes the 'stts', 'stsc', and 'stco' boxes, which must contain no
+ // samples in order to be compliant files.
SampleDescription description;
};
diff --git a/media/mp4/cenc.cc b/media/mp4/cenc.cc
index 996ebb1..104948d 100644
--- a/media/mp4/cenc.cc
+++ b/media/mp4/cenc.cc
@@ -4,6 +4,8 @@
#include "media/mp4/cenc.h"
+#include <cstring>
+
#include "media/mp4/box_reader.h"
#include "media/mp4/rcheck.h"
@@ -15,28 +17,35 @@ FrameCENCInfo::~FrameCENCInfo() {}
bool FrameCENCInfo::Parse(int iv_size, BufferReader* reader) {
const int kEntrySize = 6;
-
// Mandated by CENC spec
RCHECK(iv_size == 8 || iv_size == 16);
- iv.resize(iv_size);
+
+ memset(iv, 0, sizeof(iv));
+ for (int i = 0; i < iv_size; i++)
+ RCHECK(reader->Read1(&iv[i]));
+
+ if (!reader->HasBytes(1)) return true;
uint16 subsample_count;
- RCHECK(reader->ReadVec(&iv, iv_size) &&
- reader->Read2(&subsample_count) &&
+ RCHECK(reader->Read2(&subsample_count) &&
reader->HasBytes(subsample_count * kEntrySize));
- subsamples.resize(subsample_count);
+ subsamples.resize(subsample_count);
for (int i = 0; i < subsample_count; i++) {
- RCHECK(reader->Read2(&subsamples[i].clear_size) &&
- reader->Read4(&subsamples[i].encrypted_size));
+ uint16 clear_bytes;
+ uint32 cypher_bytes;
+ RCHECK(reader->Read2(&clear_bytes) &&
+ reader->Read4(&cypher_bytes));
+ subsamples[i].clear_bytes = clear_bytes;
+ subsamples[i].cypher_bytes = cypher_bytes;
}
return true;
}
-size_t FrameCENCInfo::GetTotalSize() const {
+size_t FrameCENCInfo::GetTotalSizeOfSubsamples() const {
size_t size = 0;
for (size_t i = 0; i < subsamples.size(); i++) {
- size += subsamples[i].clear_size + subsamples[i].encrypted_size;
+ size += subsamples[i].clear_bytes + subsamples[i].cypher_bytes;
}
return size;
}
diff --git a/media/mp4/cenc.h b/media/mp4/cenc.h
index ee23743..e558559 100644
--- a/media/mp4/cenc.h
+++ b/media/mp4/cenc.h
@@ -8,25 +8,21 @@
#include <vector>
#include "base/basictypes.h"
+#include "media/base/decrypt_config.h"
namespace media {
namespace mp4 {
class BufferReader;
-struct SubsampleSizes {
- uint16 clear_size;
- uint32 encrypted_size;
-};
-
struct FrameCENCInfo {
- std::vector<uint8> iv;
- std::vector<SubsampleSizes> subsamples;
+ uint8 iv[16];
+ std::vector<SubsampleEntry> subsamples;
FrameCENCInfo();
~FrameCENCInfo();
bool Parse(int iv_size, BufferReader* r);
- size_t GetTotalSize() const;
+ size_t GetTotalSizeOfSubsamples() const;
};
diff --git a/media/mp4/mp4_stream_parser.cc b/media/mp4/mp4_stream_parser.cc
index 05b18c1..757ea503 100644
--- a/media/mp4/mp4_stream_parser.cc
+++ b/media/mp4/mp4_stream_parser.cc
@@ -174,19 +174,24 @@ bool MP4StreamParser::ParseMoov(BoxReader* reader) {
// It is not uncommon to find otherwise-valid files with incorrect sample
// description indices, so we fail gracefully in that case.
- if (static_cast<uint32>(desc_idx) >= samp_descr.audio_entries.size())
+ if (desc_idx >= samp_descr.audio_entries.size())
desc_idx = 0;
const AudioSampleEntry& entry = samp_descr.audio_entries[desc_idx];
const AAC& aac = entry.esds.aac;
- // TODO(strobe): We accept all format values, pending clarification on
- // the formats used for encrypted media (http://crbug.com/132351).
- // RCHECK(entry.format == FOURCC_MP4A ||
- // (entry.format == FOURCC_ENCA &&
- // entry.sinf.format.format == FOURCC_MP4A));
-
+ if (!(entry.format == FOURCC_MP4A ||
+ (entry.format == FOURCC_ENCA &&
+ entry.sinf.format.format == FOURCC_MP4A))) {
+ LOG(ERROR) << "Unsupported audio format.";
+ return false;
+ }
// Check if it is MPEG4 AAC defined in ISO 14496 Part 3.
- RCHECK(entry.esds.object_type == kISO_14496_3);
+ if (entry.esds.object_type != kISO_14496_3) {
+ LOG(ERROR) << "Unsupported audio object type.";
+ return false;
+ }
+ RCHECK(EmitKeyNeeded(entry.sinf.info.track_encryption));
+
audio_config.Initialize(kCodecAAC, entry.samplesize,
aac.channel_layout(),
aac.GetOutputSamplesPerSecond(has_sbr_),
@@ -196,13 +201,17 @@ bool MP4StreamParser::ParseMoov(BoxReader* reader) {
}
if (track->media.handler.type == kVideo && !video_config.IsValidConfig()) {
RCHECK(!samp_descr.video_entries.empty());
- if (static_cast<uint32>(desc_idx) >= samp_descr.video_entries.size())
+ if (desc_idx >= samp_descr.video_entries.size())
desc_idx = 0;
const VideoSampleEntry& entry = samp_descr.video_entries[desc_idx];
- // RCHECK(entry.format == FOURCC_AVC1 ||
- // (entry.format == FOURCC_ENCV &&
- // entry.sinf.format.format == FOURCC_AVC1));
+ if (!(entry.format == FOURCC_AVC1 ||
+ (entry.format == FOURCC_ENCV &&
+ entry.sinf.format.format == FOURCC_AVC1))) {
+ LOG(ERROR) << "Unsupported video format.";
+ return false;
+ }
+ RCHECK(EmitKeyNeeded(entry.sinf.info.track_encryption));
// TODO(strobe): Recover correct crop box
video_config.Initialize(kCodecH264, H264PROFILE_MAIN, VideoFrame::YV12,
@@ -223,18 +232,20 @@ bool MP4StreamParser::ParseMoov(BoxReader* reader) {
// TODO(strobe): For now, we avoid sending new configs on a new
// reinitialization segment, and instead simply embed the updated parameter
- // sets into the video stream. The conditional should be removed when
- // http://crbug.com/122913 is fixed.
+ // sets into the video stream. The conditional should be removed when
+ // http://crbug.com/122913 is fixed. (We detect whether we've already sent
+ // configs by looking at init_cb_ instead of config_cb_, because init_cb_
+ // should only be fired once even after that bug is fixed.)
if (!init_cb_.is_null())
RCHECK(config_cb_.Run(audio_config, video_config));
base::TimeDelta duration;
if (moov_->extends.header.fragment_duration > 0) {
- duration = TimeDeltaFromFrac(moov_->extends.header.fragment_duration,
- moov_->header.timescale);
+ duration = TimeDeltaFromRational(moov_->extends.header.fragment_duration,
+ moov_->header.timescale);
} else if (moov_->header.duration > 0) {
- duration = TimeDeltaFromFrac(moov_->header.duration,
- moov_->header.timescale);
+ duration = TimeDeltaFromRational(moov_->header.duration,
+ moov_->header.timescale);
} else {
duration = kInfiniteDuration();
}
@@ -254,10 +265,54 @@ bool MP4StreamParser::ParseMoof(BoxReader* reader) {
return true;
}
+bool MP4StreamParser::EmitKeyNeeded(const TrackEncryption& track_encryption) {
+ // TODO(strobe): Send the correct value for initData. The format of initData
+ // has not yet been defined; see
+ // https://www.w3.org/Bugs/Public/show_bug.cgi?id=17673.
+ if (!track_encryption.is_encrypted) return true;
+ scoped_array<uint8> kid(new uint8[track_encryption.default_kid.size()]);
+ memcpy(kid.get(), &track_encryption.default_kid[0],
+ track_encryption.default_kid.size());
+ return need_key_cb_.Run(kid.Pass(), track_encryption.default_kid.size());
+}
+
+bool MP4StreamParser::PrepareAVCBuffer(
+ const AVCDecoderConfigurationRecord& avc_config,
+ std::vector<uint8>* frame_buf,
+ std::vector<SubsampleEntry>* subsamples) const {
+ // Convert the AVC NALU length fields to Annex B headers, as expected by
+ // decoding libraries. Since this may enlarge the size of the buffer, we also
+ // update the clear byte count for each subsample if encryption is used to
+ // account for the difference in size between the length prefix and Annex B
+ // start code.
+ RCHECK(AVC::ConvertFrameToAnnexB(avc_config.length_size, frame_buf));
+ if (!subsamples->empty()) {
+ const int nalu_size_diff = 4 - avc_config.length_size;
+ size_t expected_size = runs_->sample_size() +
+ subsamples->size() * nalu_size_diff;
+ RCHECK(frame_buf->size() == expected_size);
+ for (size_t i = 0; i < subsamples->size(); i++)
+ (*subsamples)[i].clear_bytes += nalu_size_diff;
+ }
+
+ if (runs_->is_keyframe()) {
+ // If this is a keyframe, we (re-)inject SPS and PPS headers at the start of
+ // a frame. If subsample info is present, we also update the clear byte
+ // count for that first subsample.
+ std::vector<uint8> param_sets;
+ RCHECK(AVC::ConvertConfigToAnnexB(avc_config, &param_sets));
+ frame_buf->insert(frame_buf->begin(),
+ param_sets.begin(), param_sets.end());
+ if (!subsamples->empty())
+ (*subsamples)[0].clear_bytes += param_sets.size();
+ }
+ return true;
+}
+
bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers,
BufferQueue* video_buffers,
bool* err) {
- if (!runs_->RunIsValid()) {
+ if (!runs_->IsRunValid()) {
// Flush any buffers we've gotten in this chunk so that buffers don't
// cross NewSegment() calls
*err = !SendAndFlushSamples(audio_buffers, video_buffers);
@@ -266,7 +321,7 @@ bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers,
return true;
}
- if (!runs_->SampleIsValid()) {
+ if (!runs_->IsSampleValid()) {
runs_->AdvanceRun();
return true;
}
@@ -274,9 +329,9 @@ bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers,
DCHECK(!(*err));
const uint8* buf;
- int size;
- queue_.Peek(&buf, &size);
- if (!size) return false;
+ int buf_size;
+ queue_.Peek(&buf, &buf_size);
+ if (!buf_size) return false;
bool audio = has_audio_ && audio_track_id_ == runs_->track_id();
bool video = has_video_ && video_track_id_ == runs_->track_id();
@@ -284,16 +339,42 @@ bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers,
// Skip this entire track if it's not one we're interested in
if (!audio && !video) runs_->AdvanceRun();
- queue_.PeekAt(runs_->sample_offset() + moof_head_, &buf, &size);
- if (size < runs_->sample_size()) return false;
+ // Attempt to cache the auxiliary information first. Aux info is usually
+ // placed in a contiguous block before the sample data, rather than being
+ // interleaved. If we didn't cache it, this would require that we retain the
+ // start of the segment buffer while reading samples. Aux info is typically
+ // quite small compared to sample data, so this pattern is useful on
+ // memory-constrained devices where the source buffer consumes a substantial
+ // portion of the total system memory.
+ if (runs_->AuxInfoNeedsToBeCached()) {
+ queue_.PeekAt(runs_->aux_info_offset() + moof_head_, &buf, &buf_size);
+ if (buf_size < runs_->aux_info_size()) return false;
+ *err = !runs_->CacheAuxInfo(buf, buf_size);
+ return !*err;
+ }
+
+ queue_.PeekAt(runs_->sample_offset() + moof_head_, &buf, &buf_size);
+ if (buf_size < runs_->sample_size()) return false;
+
+ scoped_ptr<DecryptConfig> decrypt_config;
+ if (runs_->is_encrypted())
+ decrypt_config = runs_->GetDecryptConfig();
std::vector<uint8> frame_buf(buf, buf + runs_->sample_size());
if (video) {
- const AVCDecoderConfigurationRecord& avc_config =
- runs_->video_description().avcc;
- RCHECK(AVC::ConvertToAnnexB(avc_config.length_size, &frame_buf));
- if (runs_->is_keyframe())
- RCHECK(AVC::InsertParameterSets(avc_config, &frame_buf));
+ std::vector<SubsampleEntry> subsamples;
+ if (decrypt_config.get())
+ subsamples = decrypt_config->subsamples();
+ RCHECK(PrepareAVCBuffer(runs_->video_description().avcc,
+ &frame_buf, &subsamples));
+ if (!subsamples.empty()) {
+ decrypt_config.reset(new DecryptConfig(
+ decrypt_config->key_id(),
+ decrypt_config->iv(),
+ decrypt_config->checksum(),
+ decrypt_config->data_offset(),
+ subsamples));
+ }
}
if (audio) {
@@ -305,6 +386,9 @@ bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers,
StreamParserBuffer::CopyFrom(&frame_buf[0], frame_buf.size(),
runs_->is_keyframe());
+ if (runs_->is_encrypted())
+ stream_buf->SetDecryptConfig(decrypt_config.Pass());
+
stream_buf->SetDuration(runs_->duration());
stream_buf->SetTimestamp(runs_->cts());
stream_buf->SetDecodeTimestamp(runs_->dts());
@@ -328,17 +412,16 @@ bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers,
bool MP4StreamParser::SendAndFlushSamples(BufferQueue* audio_buffers,
BufferQueue* video_buffers) {
+ bool err = false;
if (!audio_buffers->empty()) {
- if (audio_cb_.is_null() || !audio_cb_.Run(*audio_buffers))
- return false;
+ err |= (audio_cb_.is_null() || !audio_cb_.Run(*audio_buffers));
audio_buffers->clear();
}
if (!video_buffers->empty()) {
- if (video_cb_.is_null() || !video_cb_.Run(*video_buffers))
- return false;
+ err |= (video_cb_.is_null() || !video_cb_.Run(*video_buffers));
video_buffers->clear();
}
- return true;
+ return !err;
}
bool MP4StreamParser::ReadMDATsUntil(const int64 tgt_offset) {
diff --git a/media/mp4/mp4_stream_parser.h b/media/mp4/mp4_stream_parser.h
index a19bbcc..53100a95 100644
--- a/media/mp4/mp4_stream_parser.h
+++ b/media/mp4/mp4_stream_parser.h
@@ -5,6 +5,8 @@
#ifndef MEDIA_MP4_MP4_STREAM_PARSER_H_
#define MEDIA_MP4_MP4_STREAM_PARSER_H_
+#include <vector>
+
#include "base/basictypes.h"
#include "base/callback.h"
#include "base/compiler_specific.h"
@@ -45,11 +47,16 @@ class MEDIA_EXPORT MP4StreamParser : public StreamParser {
bool ParseMoov(mp4::BoxReader* reader);
bool ParseMoof(mp4::BoxReader* reader);
+ bool EmitKeyNeeded(const TrackEncryption& track_encryption);
+
bool ReadMDATsUntil(const int64 tgt_tail);
void ChangeState(State new_state);
bool EmitConfigs();
+ bool PrepareAVCBuffer(const AVCDecoderConfigurationRecord& avc_config,
+ std::vector<uint8>* frame_buf,
+ std::vector<SubsampleEntry>* subsamples) const;
bool EnqueueSample(BufferQueue* audio_buffers,
BufferQueue* video_buffers,
bool* err);
diff --git a/media/mp4/mp4_stream_parser_unittest.cc b/media/mp4/mp4_stream_parser_unittest.cc
index db2555f..63e1382 100644
--- a/media/mp4/mp4_stream_parser_unittest.cc
+++ b/media/mp4/mp4_stream_parser_unittest.cc
@@ -28,21 +28,16 @@ class MP4StreamParserTest : public testing::Test {
public:
MP4StreamParserTest()
: parser_(new MP4StreamParser(false)),
- got_configs_(false) {
+ configs_received_(false) {
}
protected:
scoped_ptr<MP4StreamParser> parser_;
base::TimeDelta segment_start_;
- bool got_configs_;
+ bool configs_received_;
bool AppendData(const uint8* data, size_t length) {
- parser_->Parse(data, length);
- return true;
- }
-
- bool AppendDataInPieces(const uint8* data, size_t length) {
- return AppendDataInPieces(data, length, 7);
+ return parser_->Parse(data, length);
}
bool AppendDataInPieces(const uint8* data, size_t length, size_t piece_size) {
@@ -71,8 +66,8 @@ class MP4StreamParserTest : public testing::Test {
// TODO(strobe): Until http://crbug.com/122913 is fixed, we want to make
// sure that this callback isn't called more than once per stream. Remove
// when that bug is fixed.
- EXPECT_FALSE(got_configs_);
- got_configs_ = true;
+ EXPECT_FALSE(configs_received_);
+ configs_received_ = true;
return true;
}
@@ -83,7 +78,7 @@ class MP4StreamParserTest : public testing::Test {
DVLOG(3) << " n=" << buf - bufs.begin()
<< ", size=" << (*buf)->GetDataSize()
<< ", dur=" << (*buf)->GetDuration().InMilliseconds();
- EXPECT_LE(segment_start_, (*buf)->GetTimestamp());
+ EXPECT_GE((*buf)->GetTimestamp(), segment_start_);
}
return true;
}
@@ -108,22 +103,28 @@ class MP4StreamParserTest : public testing::Test {
base::Bind(&MP4StreamParserTest::NewSegmentF, base::Unretained(this)));
}
- bool ParseMP4File(const std::string& filename, int append_size) {
+ bool ParseMP4File(const std::string& filename, int append_bytes) {
InitializeParser();
scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile(filename);
EXPECT_TRUE(AppendDataInPieces(buffer->GetData(),
buffer->GetDataSize(),
- append_size));
+ append_bytes));
return true;
}
};
-TEST_F(MP4StreamParserTest, TestParseBearDASH) {
+
+
+TEST_F(MP4StreamParserTest, TestUnalignedAppend) {
+ // Test small, non-segment-aligned appends (small enough to exercise
+ // incremental append system)
ParseMP4File("bear.1280x720_dash.mp4", 512);
}
TEST_F(MP4StreamParserTest, TestMultiFragmentAppend) {
+ // Large size ensures multiple fragments are appended in one call (size is
+ // larger than this particular test file)
ParseMP4File("bear.1280x720_dash.mp4", 768432);
}
diff --git a/media/mp4/track_run_iterator.cc b/media/mp4/track_run_iterator.cc
index 0bef86e..59bc229 100644
--- a/media/mp4/track_run_iterator.cc
+++ b/media/mp4/track_run_iterator.cc
@@ -9,6 +9,10 @@
#include "media/base/stream_parser_buffer.h"
#include "media/mp4/rcheck.h"
+namespace {
+static const uint32 kSampleIsDifferenceSampleFlagMask = 0x10000;
+}
+
namespace media {
namespace mp4 {
@@ -30,6 +34,11 @@ struct TrackRunInfo {
const AudioSampleEntry* audio_description;
const VideoSampleEntry* video_description;
+ int64 aux_info_start_offset; // Only valid if aux_info_total_size > 0.
+ int aux_info_default_size;
+ std::vector<uint8> aux_info_sizes; // Populated if default_size == 0.
+ int aux_info_total_size;
+
TrackRunInfo();
~TrackRunInfo();
};
@@ -39,20 +48,20 @@ TrackRunInfo::TrackRunInfo()
timescale(-1),
start_dts(-1),
sample_start_offset(-1),
- is_audio(false) {
+ is_audio(false),
+ aux_info_start_offset(-1),
+ aux_info_default_size(-1),
+ aux_info_total_size(-1) {
}
TrackRunInfo::~TrackRunInfo() {}
-TimeDelta TimeDeltaFromFrac(int64 numer, int64 denom) {
+TimeDelta TimeDeltaFromRational(int64 numer, int64 denom) {
DCHECK_LT((numer > 0 ? numer : -numer),
kint64max / base::Time::kMicrosecondsPerSecond);
return TimeDelta::FromMicroseconds(
base::Time::kMicrosecondsPerSecond * numer / denom);
}
-static const uint32 kSampleIsDifferenceSampleFlagMask = 0x10000;
-
-
TrackRunIterator::TrackRunIterator(const Movie* moov)
: moov_(moov), sample_offset_(0) {
CHECK(moov);
@@ -97,10 +106,30 @@ static void PopulateSampleInfo(const TrackExtends& trex,
sample_info->is_keyframe = !(flags & kSampleIsDifferenceSampleFlagMask);
}
+// In well-structured encrypted media, each track run will be immediately
+// preceded by its auxiliary information; this is the only optimal storage
+// pattern in terms of minimum number of bytes from a serial stream needed to
+// begin playback. It also allows us to optimize caching on memory-constrained
+// architectures, because we can cache the relatively small auxiliary
+// information for an entire run and then discard data from the input stream,
+// instead of retaining the entire 'mdat' box.
+//
+// We optimize for this situation (with no loss of generality) by sorting track
+// runs during iteration in order of their first data offset (either sample data
+// or auxiliary data).
class CompareMinTrackRunDataOffset {
public:
bool operator()(const TrackRunInfo& a, const TrackRunInfo& b) {
- return a.sample_start_offset < b.sample_start_offset;
+ int64 a_aux = a.aux_info_total_size ? a.aux_info_start_offset : kint64max;
+ int64 b_aux = b.aux_info_total_size ? b.aux_info_start_offset : kint64max;
+
+ int64 a_lesser = std::min(a_aux, a.sample_start_offset);
+ int64 a_greater = std::max(a_aux, a.sample_start_offset);
+ int64 b_lesser = std::min(b_aux, b.sample_start_offset);
+ int64 b_greater = std::max(b_aux, b.sample_start_offset);
+
+ if (a_lesser == b_lesser) return a_greater < b_greater;
+ return a_lesser < b_lesser;
}
};
@@ -130,11 +159,13 @@ bool TrackRunIterator::Init(const MovieFragment& moof) {
DVLOG(1) << "Skipping unhandled track type";
continue;
}
- int desc_idx = traf.header.sample_description_index;
+ size_t desc_idx = traf.header.sample_description_index;
if (!desc_idx) desc_idx = trex->default_sample_description_index;
- desc_idx--; // Descriptions are one-indexed in the file
+ RCHECK(desc_idx > 0); // Descriptions are one-indexed in the file
+ desc_idx -= 1;
int64 run_start_dts = traf.decode_time.decode_time;
+ int sample_count_sum = 0;
for (size_t j = 0; j < traf.runs.size(); j++) {
const TrackFragmentRun& trun = traf.runs[j];
@@ -146,17 +177,60 @@ bool TrackRunIterator::Init(const MovieFragment& moof) {
tri.is_audio = (stsd.type == kAudio);
if (tri.is_audio) {
+ RCHECK(!stsd.audio_entries.empty());
+ if (desc_idx > stsd.audio_entries.size())
+ desc_idx = 0;
tri.audio_description = &stsd.audio_entries[desc_idx];
} else {
+ RCHECK(!stsd.video_entries.empty());
+ if (desc_idx > stsd.video_entries.size())
+ desc_idx = 0;
tri.video_description = &stsd.video_entries[desc_idx];
}
+ // Collect information from the auxiliary_offset entry with the same index
+ // in the 'saiz' container as the current run's index in the 'trun'
+ // container, if it is present.
+ if (traf.auxiliary_offset.offsets.size() > j) {
+ // There should be an auxiliary info entry corresponding to each sample
+ // in the auxiliary offset entry's corresponding track run.
+ RCHECK(traf.auxiliary_size.sample_count >=
+ sample_count_sum + trun.sample_count);
+ tri.aux_info_start_offset = traf.auxiliary_offset.offsets[j];
+ tri.aux_info_default_size =
+ traf.auxiliary_size.default_sample_info_size;
+ if (tri.aux_info_default_size == 0) {
+ const std::vector<uint8>& sizes =
+ traf.auxiliary_size.sample_info_sizes;
+ tri.aux_info_sizes.insert(tri.aux_info_sizes.begin(),
+ sizes.begin() + sample_count_sum,
+ sizes.begin() + sample_count_sum + trun.sample_count);
+ }
+
+ // If the default info size is positive, find the total size of the aux
+ // info block from it, otherwise sum over the individual sizes of each
+ // aux info entry in the aux_offset entry.
+ if (tri.aux_info_default_size) {
+ tri.aux_info_total_size =
+ tri.aux_info_default_size * trun.sample_count;
+ } else {
+ tri.aux_info_total_size = 0;
+ for (size_t k = 0; k < trun.sample_count; k++) {
+ tri.aux_info_total_size += tri.aux_info_sizes[k];
+ }
+ }
+ } else {
+ tri.aux_info_start_offset = -1;
+ tri.aux_info_total_size = 0;
+ }
+
tri.samples.resize(trun.sample_count);
for (size_t k = 0; k < trun.sample_count; k++) {
PopulateSampleInfo(*trex, traf.header, trun, k, &tri.samples[k]);
run_start_dts += tri.samples[k].duration;
}
runs_.push_back(tri);
+ sample_count_sum += trun.sample_count;
}
}
@@ -172,61 +246,109 @@ void TrackRunIterator::AdvanceRun() {
}
void TrackRunIterator::ResetRun() {
- if (!RunIsValid()) return;
+ if (!IsRunValid()) return;
sample_dts_ = run_itr_->start_dts;
sample_offset_ = run_itr_->sample_start_offset;
sample_itr_ = run_itr_->samples.begin();
+ cenc_info_.clear();
}
void TrackRunIterator::AdvanceSample() {
- DCHECK(SampleIsValid());
+ DCHECK(IsSampleValid());
sample_dts_ += sample_itr_->duration;
sample_offset_ += sample_itr_->size;
++sample_itr_;
}
-bool TrackRunIterator::RunIsValid() const {
+// This implementation only indicates a need for caching if CENC auxiliary
+// info is available in the stream.
+bool TrackRunIterator::AuxInfoNeedsToBeCached() {
+ DCHECK(IsRunValid());
+ return is_encrypted() && aux_info_size() > 0 && cenc_info_.size() == 0;
+}
+
+// This implementation currently only caches CENC auxiliary info.
+bool TrackRunIterator::CacheAuxInfo(const uint8* buf, int buf_size) {
+ RCHECK(AuxInfoNeedsToBeCached() && buf_size >= aux_info_size());
+
+ cenc_info_.resize(run_itr_->samples.size());
+ int64 pos = 0;
+ for (size_t i = 0; i < run_itr_->samples.size(); i++) {
+ int info_size = run_itr_->aux_info_default_size;
+ if (!info_size)
+ info_size = run_itr_->aux_info_sizes[i];
+
+ BufferReader reader(buf + pos, info_size);
+ RCHECK(cenc_info_[i].Parse(track_encryption().default_iv_size, &reader));
+ pos += info_size;
+ }
+
+ return true;
+}
+
+bool TrackRunIterator::IsRunValid() const {
return run_itr_ != runs_.end();
}
-bool TrackRunIterator::SampleIsValid() const {
- return RunIsValid() && (sample_itr_ != run_itr_->samples.end());
+bool TrackRunIterator::IsSampleValid() const {
+ return IsRunValid() && (sample_itr_ != run_itr_->samples.end());
}
+// Because tracks are in sorted order and auxiliary information is cached when
+// returning samples, it is guaranteed that no data will be required before the
+// lesser of the minimum data offset of this track and the next in sequence.
+// (The stronger condition - that no data is required before the minimum data
+// offset of this track alone - is not guaranteed, because the BMFF spec does
+// not have any inter-run ordering restrictions.)
int64 TrackRunIterator::GetMaxClearOffset() {
int64 offset = kint64max;
- if (SampleIsValid())
+ if (IsSampleValid()) {
offset = std::min(offset, sample_offset_);
- if (run_itr_ == runs_.end())
- return offset;
- std::vector<TrackRunInfo>::const_iterator next_run = run_itr_ + 1;
- if (next_run != runs_.end())
- offset = std::min(offset, next_run->sample_start_offset);
+ if (AuxInfoNeedsToBeCached())
+ offset = std::min(offset, aux_info_offset());
+ }
+ if (run_itr_ != runs_.end()) {
+ std::vector<TrackRunInfo>::const_iterator next_run = run_itr_ + 1;
+ if (next_run != runs_.end()) {
+ offset = std::min(offset, next_run->sample_start_offset);
+ if (next_run->aux_info_total_size)
+ offset = std::min(offset, next_run->aux_info_start_offset);
+ }
+ }
+ if (offset == kint64max) return 0;
return offset;
}
TimeDelta TrackRunIterator::GetMinDecodeTimestamp() {
TimeDelta dts = kInfiniteDuration();
for (size_t i = 0; i < runs_.size(); i++) {
- dts = std::min(dts, TimeDeltaFromFrac(runs_[i].start_dts,
- runs_[i].timescale));
+ dts = std::min(dts, TimeDeltaFromRational(runs_[i].start_dts,
+ runs_[i].timescale));
}
return dts;
}
uint32 TrackRunIterator::track_id() const {
- DCHECK(RunIsValid());
+ DCHECK(IsRunValid());
return run_itr_->track_id;
}
bool TrackRunIterator::is_encrypted() const {
- DCHECK(RunIsValid());
- return false;
+ DCHECK(IsRunValid());
+ return track_encryption().is_encrypted;
+}
+
+int64 TrackRunIterator::aux_info_offset() const {
+ return run_itr_->aux_info_start_offset;
+}
+
+int TrackRunIterator::aux_info_size() const {
+ return run_itr_->aux_info_total_size;
}
bool TrackRunIterator::is_audio() const {
- DCHECK(RunIsValid());
+ DCHECK(IsRunValid());
return run_itr_->is_audio;
}
@@ -243,35 +365,64 @@ const VideoSampleEntry& TrackRunIterator::video_description() const {
}
int64 TrackRunIterator::sample_offset() const {
- DCHECK(SampleIsValid());
+ DCHECK(IsSampleValid());
return sample_offset_;
}
int TrackRunIterator::sample_size() const {
- DCHECK(SampleIsValid());
+ DCHECK(IsSampleValid());
return sample_itr_->size;
}
TimeDelta TrackRunIterator::dts() const {
- DCHECK(SampleIsValid());
- return TimeDeltaFromFrac(sample_dts_, run_itr_->timescale);
+ DCHECK(IsSampleValid());
+ return TimeDeltaFromRational(sample_dts_, run_itr_->timescale);
}
TimeDelta TrackRunIterator::cts() const {
- DCHECK(SampleIsValid());
- return TimeDeltaFromFrac(sample_dts_ + sample_itr_->cts_offset,
- run_itr_->timescale);
+ DCHECK(IsSampleValid());
+ return TimeDeltaFromRational(sample_dts_ + sample_itr_->cts_offset,
+ run_itr_->timescale);
}
TimeDelta TrackRunIterator::duration() const {
- DCHECK(SampleIsValid());
- return TimeDeltaFromFrac(sample_itr_->duration, run_itr_->timescale);
+ DCHECK(IsSampleValid());
+ return TimeDeltaFromRational(sample_itr_->duration, run_itr_->timescale);
}
bool TrackRunIterator::is_keyframe() const {
- DCHECK(SampleIsValid());
+ DCHECK(IsSampleValid());
return sample_itr_->is_keyframe;
}
+const TrackEncryption& TrackRunIterator::track_encryption() const {
+ if (is_audio())
+ return audio_description().sinf.info.track_encryption;
+ return video_description().sinf.info.track_encryption;
+}
+
+scoped_ptr<DecryptConfig> TrackRunIterator::GetDecryptConfig() {
+ size_t sample_idx = sample_itr_ - run_itr_->samples.begin();
+ DCHECK(sample_idx < cenc_info_.size());
+ const FrameCENCInfo& cenc_info = cenc_info_[sample_idx];
+ DCHECK(is_encrypted() && !AuxInfoNeedsToBeCached());
+
+ if (!cenc_info.subsamples.empty() &&
+ (cenc_info.GetTotalSizeOfSubsamples() !=
+ static_cast<size_t>(sample_size()))) {
+ DVLOG(1) << "Incorrect CENC subsample size.";
+ return scoped_ptr<DecryptConfig>();
+ }
+
+ const std::vector<uint8>& kid = track_encryption().default_kid;
+ return scoped_ptr<DecryptConfig>(new DecryptConfig(
+ std::string(reinterpret_cast<const char*>(&kid[0]), kid.size()),
+ std::string(reinterpret_cast<const char*>(cenc_info.iv),
+ arraysize(cenc_info.iv)),
+ std::string(), // No checksum in MP4 using CENC.
+ 0, // No offset to start of media data in MP4 using CENC.
+ cenc_info.subsamples));
+}
+
} // namespace mp4
} // namespace media
diff --git a/media/mp4/track_run_iterator.h b/media/mp4/track_run_iterator.h
index ddea39c..85cadce 100644
--- a/media/mp4/track_run_iterator.h
+++ b/media/mp4/track_run_iterator.h
@@ -11,12 +11,16 @@
#include "base/time.h"
#include "media/base/media_export.h"
#include "media/mp4/box_definitions.h"
+#include "media/mp4/cenc.h"
namespace media {
+
+class DecryptConfig;
+
namespace mp4 {
using base::TimeDelta;
-base::TimeDelta MEDIA_EXPORT TimeDeltaFromFrac(int64 numer, int64 denom);
+base::TimeDelta MEDIA_EXPORT TimeDeltaFromRational(int64 numer, int64 denom);
struct SampleInfo;
struct TrackRunInfo;
@@ -34,14 +38,23 @@ class MEDIA_EXPORT TrackRunIterator {
bool Init(const MovieFragment& moof);
// Returns true if the properties of the current run or sample are valid.
- bool RunIsValid() const;
- bool SampleIsValid() const;
+ bool IsRunValid() const;
+ bool IsSampleValid() const;
// Advance the properties to refer to the next run or sample. Requires that
// the current sample be valid.
void AdvanceRun();
void AdvanceSample();
+ // Returns true if this track run has auxiliary information and has not yet
+ // been cached. Only valid if IsRunValid().
+ bool AuxInfoNeedsToBeCached();
+
+ // Caches the CENC data from the given buffer. |buf| must be a buffer starting
+ // at the offset given by cenc_offset(), with a |size| of at least
+ // cenc_size(). Returns true on success, false on error.
+ bool CacheAuxInfo(const uint8* buf, int size);
+
// Returns the maximum buffer location at which no data earlier in the stream
// will be required in order to read the current or any subsequent sample. You
// may clear all data up to this offset before reading the current sample
@@ -52,15 +65,17 @@ class MEDIA_EXPORT TrackRunIterator {
// Returns the minimum timestamp (or kInfiniteDuration if no runs present).
TimeDelta GetMinDecodeTimestamp();
- // Property of the current run. Only valid if RunIsValid().
+ // Property of the current run. Only valid if IsRunValid().
uint32 track_id() const;
+ int64 aux_info_offset() const;
+ int aux_info_size() const;
bool is_encrypted() const;
bool is_audio() const;
// Only one is valid, based on the value of is_audio().
const AudioSampleEntry& audio_description() const;
const VideoSampleEntry& video_description() const;
- // Properties of the current sample. Only valid if SampleIsValid().
+ // Properties of the current sample. Only valid if IsSampleValid().
int64 sample_offset() const;
int sample_size() const;
TimeDelta dts() const;
@@ -68,8 +83,13 @@ class MEDIA_EXPORT TrackRunIterator {
TimeDelta duration() const;
bool is_keyframe() const;
+ // Only call when is_encrypted() is true and AuxInfoNeedsToBeCached() is
+ // false. Result is owned by caller.
+ scoped_ptr<DecryptConfig> GetDecryptConfig();
+
private:
void ResetRun();
+ const TrackEncryption& track_encryption() const;
const Movie* moov_;
@@ -77,6 +97,8 @@ class MEDIA_EXPORT TrackRunIterator {
std::vector<TrackRunInfo>::const_iterator run_itr_;
std::vector<SampleInfo>::const_iterator sample_itr_;
+ std::vector<FrameCENCInfo> cenc_info_;
+
int64 sample_dts_;
int64 sample_offset_;
diff --git a/media/mp4/track_run_iterator_unittest.cc b/media/mp4/track_run_iterator_unittest.cc
index 0b5a967..e1c1a03 100644
--- a/media/mp4/track_run_iterator_unittest.cc
+++ b/media/mp4/track_run_iterator_unittest.cc
@@ -19,6 +19,24 @@ static const int kVideoScale = 25;
static const uint32 kSampleIsDifferenceSampleFlagMask = 0x10000;
+static const uint8 kAuxInfo[] = {
+ 0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31,
+ 0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x32,
+ 0x00, 0x02,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x03, 0x00, 0x00, 0x00, 0x04
+};
+
+static const char kIv1[] = {
+ 0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+static const uint8 kKeyId[] = {
+ 0x41, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x54,
+ 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x44
+};
+
namespace media {
namespace mp4 {
@@ -40,19 +58,25 @@ class TrackRunIteratorTest : public testing::Test {
moov_.tracks[0].media.header.timescale = kAudioScale;
SampleDescription& desc1 =
moov_.tracks[0].media.information.sample_table.description;
+ AudioSampleEntry aud_desc;
+ aud_desc.format = FOURCC_MP4A;
+ aud_desc.sinf.info.track_encryption.is_encrypted = false;
desc1.type = kAudio;
- desc1.audio_entries.resize(1);
- desc1.audio_entries[0].format = FOURCC_MP4A;
+ desc1.audio_entries.push_back(aud_desc);
moov_.extends.tracks[0].track_id = 1;
+ moov_.extends.tracks[0].default_sample_description_index = 1;
moov_.tracks[1].header.track_id = 2;
moov_.tracks[1].media.header.timescale = kVideoScale;
SampleDescription& desc2 =
moov_.tracks[1].media.information.sample_table.description;
+ VideoSampleEntry vid_desc;
+ vid_desc.format = FOURCC_AVC1;
+ vid_desc.sinf.info.track_encryption.is_encrypted = false;
desc2.type = kVideo;
- desc2.video_entries.resize(1);
- desc2.video_entries[0].sinf.info.track_encryption.is_encrypted = false;
+ desc2.video_entries.push_back(vid_desc);
moov_.extends.tracks[1].track_id = 2;
+ moov_.extends.tracks[1].default_sample_description_index = 1;
moov_.tracks[2].header.track_id = 3;
moov_.tracks[2].media.information.sample_table.description.type = kHint;
@@ -66,7 +90,6 @@ class TrackRunIteratorTest : public testing::Test {
moof.tracks[0].header.has_default_sample_flags = true;
moof.tracks[0].header.default_sample_duration = 1024;
moof.tracks[0].header.default_sample_size = 4;
- moof.tracks[0].header.sample_description_index = 0;
moof.tracks[0].runs.resize(2);
moof.tracks[0].runs[0].sample_count = 10;
moof.tracks[0].runs[0].data_offset = 100;
@@ -77,7 +100,6 @@ class TrackRunIteratorTest : public testing::Test {
moof.tracks[1].header.track_id = 2;
moof.tracks[1].header.has_default_sample_flags = false;
- moof.tracks[1].header.sample_description_index = 0;
moof.tracks[1].decode_time.decode_time = 10;
moof.tracks[1].runs.resize(1);
moof.tracks[1].runs[0].sample_count = 10;
@@ -93,6 +115,36 @@ class TrackRunIteratorTest : public testing::Test {
return moof;
}
+ // Update the first sample description of a Track to indicate encryption
+ void AddEncryption(Track* track) {
+ SampleDescription* stsd =
+ &track->media.information.sample_table.description;
+ ProtectionSchemeInfo* sinf;
+ if (!stsd->video_entries.empty()) {
+ sinf = &stsd->video_entries[0].sinf;
+ } else {
+ sinf = &stsd->audio_entries[0].sinf;
+ }
+
+ sinf->type.type = FOURCC_CENC;
+ sinf->info.track_encryption.is_encrypted = true;
+ sinf->info.track_encryption.default_iv_size = 8;
+ sinf->info.track_encryption.default_kid.insert(
+ sinf->info.track_encryption.default_kid.begin(),
+ kKeyId, kKeyId + arraysize(kKeyId));
+ }
+
+ // Add aux info covering the first track run to a TrackFragment, and update
+ // the run to ensure it matches length and subsample information.
+ void AddAuxInfoHeaders(int offset, TrackFragment* frag) {
+ frag->auxiliary_offset.offsets.push_back(offset);
+ frag->auxiliary_size.sample_count = 2;
+ frag->auxiliary_size.sample_info_sizes.push_back(8);
+ frag->auxiliary_size.sample_info_sizes.push_back(22);
+ frag->runs[0].sample_count = 2;
+ frag->runs[0].sample_sizes[1] = 10;
+ }
+
void SetAscending(std::vector<uint32>* vec) {
vec->resize(10);
for (size_t i = 0; i < vec->size(); i++)
@@ -103,8 +155,8 @@ class TrackRunIteratorTest : public testing::Test {
TEST_F(TrackRunIteratorTest, NoRunsTest) {
iter_.reset(new TrackRunIterator(&moov_));
ASSERT_TRUE(iter_->Init(MovieFragment()));
- EXPECT_FALSE(iter_->RunIsValid());
- EXPECT_FALSE(iter_->SampleIsValid());
+ EXPECT_FALSE(iter_->IsRunValid());
+ EXPECT_FALSE(iter_->IsSampleValid());
}
TEST_F(TrackRunIteratorTest, BasicOperationTest) {
@@ -114,14 +166,14 @@ TEST_F(TrackRunIteratorTest, BasicOperationTest) {
// Test that runs are sorted correctly, and that properties of the initial
// sample of the first run are correct
ASSERT_TRUE(iter_->Init(moof));
- EXPECT_TRUE(iter_->RunIsValid());
+ EXPECT_TRUE(iter_->IsRunValid());
EXPECT_FALSE(iter_->is_encrypted());
EXPECT_EQ(iter_->track_id(), 1u);
EXPECT_EQ(iter_->sample_offset(), 100);
EXPECT_EQ(iter_->sample_size(), 1);
- EXPECT_EQ(iter_->dts(), TimeDeltaFromFrac(0, kAudioScale));
- EXPECT_EQ(iter_->cts(), TimeDeltaFromFrac(0, kAudioScale));
- EXPECT_EQ(iter_->duration(), TimeDeltaFromFrac(1024, kAudioScale));
+ EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(0, kAudioScale));
+ EXPECT_EQ(iter_->cts(), TimeDeltaFromRational(0, kAudioScale));
+ EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(1024, kAudioScale));
EXPECT_TRUE(iter_->is_keyframe());
// Advance to the last sample in the current run, and test its properties
@@ -129,13 +181,13 @@ TEST_F(TrackRunIteratorTest, BasicOperationTest) {
EXPECT_EQ(iter_->track_id(), 1u);
EXPECT_EQ(iter_->sample_offset(), 100 + kSumAscending1);
EXPECT_EQ(iter_->sample_size(), 10);
- EXPECT_EQ(iter_->dts(), TimeDeltaFromFrac(1024 * 9, kAudioScale));
- EXPECT_EQ(iter_->duration(), TimeDeltaFromFrac(1024, kAudioScale));
+ EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(1024 * 9, kAudioScale));
+ EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(1024, kAudioScale));
EXPECT_TRUE(iter_->is_keyframe());
// Test end-of-run
iter_->AdvanceSample();
- EXPECT_FALSE(iter_->SampleIsValid());
+ EXPECT_FALSE(iter_->IsSampleValid());
// Test last sample of next run
iter_->AdvanceRun();
@@ -145,20 +197,20 @@ TEST_F(TrackRunIteratorTest, BasicOperationTest) {
EXPECT_EQ(iter_->sample_offset(), 200 + kSumAscending1);
EXPECT_EQ(iter_->sample_size(), 10);
int64 base_dts = kSumAscending1 + moof.tracks[1].decode_time.decode_time;
- EXPECT_EQ(iter_->dts(), TimeDeltaFromFrac(base_dts, kVideoScale));
- EXPECT_EQ(iter_->duration(), TimeDeltaFromFrac(10, kVideoScale));
+ EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(base_dts, kVideoScale));
+ EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(10, kVideoScale));
EXPECT_FALSE(iter_->is_keyframe());
// Test final run
iter_->AdvanceRun();
EXPECT_EQ(iter_->track_id(), 1u);
- EXPECT_EQ(iter_->dts(), TimeDeltaFromFrac(1024 * 10, kAudioScale));
+ EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(1024 * 10, kAudioScale));
iter_->AdvanceSample();
EXPECT_EQ(moof.tracks[0].runs[1].data_offset +
moof.tracks[0].header.default_sample_size,
iter_->sample_offset());
iter_->AdvanceRun();
- EXPECT_FALSE(iter_->RunIsValid());
+ EXPECT_FALSE(iter_->IsRunValid());
}
TEST_F(TrackRunIteratorTest, TrackExtendsDefaultsTest) {
@@ -177,8 +229,8 @@ TEST_F(TrackRunIteratorTest, TrackExtendsDefaultsTest) {
EXPECT_FALSE(iter_->is_keyframe());
EXPECT_EQ(iter_->sample_size(), 3);
EXPECT_EQ(iter_->sample_offset(), moof.tracks[0].runs[0].data_offset + 3);
- EXPECT_EQ(iter_->duration(), TimeDeltaFromFrac(50, kAudioScale));
- EXPECT_EQ(iter_->dts(), TimeDeltaFromFrac(50, kAudioScale));
+ EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(50, kAudioScale));
+ EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(50, kAudioScale));
}
TEST_F(TrackRunIteratorTest, FirstSampleFlagTest) {
@@ -203,8 +255,8 @@ TEST_F(TrackRunIteratorTest, MinDecodeTest) {
MovieFragment moof = CreateFragment();
moof.tracks[0].decode_time.decode_time = kAudioScale;
ASSERT_TRUE(iter_->Init(moof));
- EXPECT_EQ(TimeDeltaFromFrac(moof.tracks[1].decode_time.decode_time,
- kVideoScale),
+ EXPECT_EQ(TimeDeltaFromRational(moof.tracks[1].decode_time.decode_time,
+ kVideoScale),
iter_->GetMinDecodeTimestamp());
}
@@ -219,13 +271,143 @@ TEST_F(TrackRunIteratorTest, ReorderingTest) {
moof.tracks[1].decode_time.decode_time = 0;
ASSERT_TRUE(iter_->Init(moof));
iter_->AdvanceRun();
- EXPECT_EQ(iter_->dts(), TimeDeltaFromFrac(0, kVideoScale));
- EXPECT_EQ(iter_->cts(), TimeDeltaFromFrac(2, kVideoScale));
- EXPECT_EQ(iter_->duration(), TimeDeltaFromFrac(1, kVideoScale));
+ EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(0, kVideoScale));
+ EXPECT_EQ(iter_->cts(), TimeDeltaFromRational(2, kVideoScale));
+ EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(1, kVideoScale));
iter_->AdvanceSample();
- EXPECT_EQ(iter_->dts(), TimeDeltaFromFrac(1, kVideoScale));
- EXPECT_EQ(iter_->cts(), TimeDeltaFromFrac(0, kVideoScale));
- EXPECT_EQ(iter_->duration(), TimeDeltaFromFrac(2, kVideoScale));
+ EXPECT_EQ(iter_->dts(), TimeDeltaFromRational(1, kVideoScale));
+ EXPECT_EQ(iter_->cts(), TimeDeltaFromRational(0, kVideoScale));
+ EXPECT_EQ(iter_->duration(), TimeDeltaFromRational(2, kVideoScale));
+}
+
+TEST_F(TrackRunIteratorTest, IgnoreUnknownAuxInfoTest) {
+ iter_.reset(new TrackRunIterator(&moov_));
+ MovieFragment moof = CreateFragment();
+ moof.tracks[1].auxiliary_offset.offsets.push_back(50);
+ moof.tracks[1].auxiliary_size.default_sample_info_size = 2;
+ moof.tracks[1].auxiliary_size.sample_count = 2;
+ moof.tracks[1].runs[0].sample_count = 2;
+ ASSERT_TRUE(iter_->Init(moof));
+ iter_->AdvanceRun();
+ EXPECT_FALSE(iter_->AuxInfoNeedsToBeCached());
+}
+
+TEST_F(TrackRunIteratorTest, DecryptConfigTest) {
+ AddEncryption(&moov_.tracks[1]);
+ iter_.reset(new TrackRunIterator(&moov_));
+
+ MovieFragment moof = CreateFragment();
+ AddAuxInfoHeaders(50, &moof.tracks[1]);
+
+ ASSERT_TRUE(iter_->Init(moof));
+
+ // The run for track 2 will be first, since its aux info offset is the first
+ // element in the file.
+ EXPECT_EQ(iter_->track_id(), 2u);
+ EXPECT_TRUE(iter_->is_encrypted());
+ EXPECT_TRUE(iter_->AuxInfoNeedsToBeCached());
+ EXPECT_EQ(static_cast<uint32>(iter_->aux_info_size()), arraysize(kAuxInfo));
+ EXPECT_EQ(iter_->aux_info_offset(), 50);
+ EXPECT_EQ(iter_->GetMaxClearOffset(), 50);
+ EXPECT_FALSE(iter_->CacheAuxInfo(NULL, 0));
+ EXPECT_FALSE(iter_->CacheAuxInfo(kAuxInfo, 3));
+ EXPECT_TRUE(iter_->AuxInfoNeedsToBeCached());
+ EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo)));
+ EXPECT_FALSE(iter_->AuxInfoNeedsToBeCached());
+ EXPECT_EQ(iter_->sample_offset(), 200);
+ EXPECT_EQ(iter_->GetMaxClearOffset(), moof.tracks[0].runs[0].data_offset);
+ scoped_ptr<DecryptConfig> config = iter_->GetDecryptConfig();
+ ASSERT_EQ(arraysize(kKeyId), config->key_id().size());
+ EXPECT_TRUE(!memcmp(kKeyId, config->key_id().data(),
+ config->key_id().size()));
+ ASSERT_EQ(arraysize(kIv1), config->iv().size());
+ EXPECT_TRUE(!memcmp(kIv1, config->iv().data(), config->iv().size()));
+ EXPECT_TRUE(config->subsamples().empty());
+ iter_->AdvanceSample();
+ config = iter_->GetDecryptConfig();
+ EXPECT_EQ(config->subsamples().size(), 2u);
+ EXPECT_EQ(config->subsamples()[0].clear_bytes, 1u);
+ EXPECT_EQ(config->subsamples()[1].cypher_bytes, 4u);
+}
+
+// It is legal for aux info blocks to be shared among multiple formats.
+TEST_F(TrackRunIteratorTest, SharedAuxInfoTest) {
+ AddEncryption(&moov_.tracks[0]);
+ AddEncryption(&moov_.tracks[1]);
+ iter_.reset(new TrackRunIterator(&moov_));
+
+ MovieFragment moof = CreateFragment();
+ moof.tracks[0].runs.resize(1);
+ AddAuxInfoHeaders(50, &moof.tracks[0]);
+ AddAuxInfoHeaders(50, &moof.tracks[1]);
+ moof.tracks[0].auxiliary_size.default_sample_info_size = 8;
+
+ ASSERT_TRUE(iter_->Init(moof));
+ EXPECT_EQ(iter_->track_id(), 1u);
+ EXPECT_EQ(iter_->aux_info_offset(), 50);
+ EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo)));
+ scoped_ptr<DecryptConfig> config = iter_->GetDecryptConfig();
+ ASSERT_EQ(arraysize(kIv1), config->iv().size());
+ EXPECT_TRUE(!memcmp(kIv1, config->iv().data(), config->iv().size()));
+ iter_->AdvanceSample();
+ EXPECT_EQ(iter_->GetMaxClearOffset(), 50);
+ iter_->AdvanceRun();
+ EXPECT_EQ(iter_->GetMaxClearOffset(), 50);
+ EXPECT_EQ(iter_->aux_info_offset(), 50);
+ EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo)));
+ EXPECT_EQ(iter_->GetMaxClearOffset(), 200);
+ ASSERT_EQ(arraysize(kIv1), config->iv().size());
+ EXPECT_TRUE(!memcmp(kIv1, config->iv().data(), config->iv().size()));
+ iter_->AdvanceSample();
+ EXPECT_EQ(iter_->GetMaxClearOffset(), 201);
+}
+
+// Sensible files are expected to place auxiliary information for a run
+// immediately before the main data for that run. Alternative schemes are
+// possible, however, including the somewhat reasonable behavior of placing all
+// aux info at the head of the 'mdat' box together, and the completely
+// unreasonable behavior demonstrated here:
+// byte 50: track 2, run 1 aux info
+// byte 100: track 1, run 1 data
+// byte 200: track 2, run 1 data
+// byte 201: track 1, run 2 aux info (*inside* track 2, run 1 data)
+// byte 10000: track 1, run 2 data
+// byte 20000: track 1, run 1 aux info
+TEST_F(TrackRunIteratorTest, UnexpectedOrderingTest) {
+ AddEncryption(&moov_.tracks[0]);
+ AddEncryption(&moov_.tracks[1]);
+ iter_.reset(new TrackRunIterator(&moov_));
+
+ MovieFragment moof = CreateFragment();
+ AddAuxInfoHeaders(20000, &moof.tracks[0]);
+ moof.tracks[0].auxiliary_offset.offsets.push_back(201);
+ moof.tracks[0].auxiliary_size.sample_count += 2;
+ moof.tracks[0].auxiliary_size.default_sample_info_size = 8;
+ moof.tracks[0].runs[1].sample_count = 2;
+ AddAuxInfoHeaders(50, &moof.tracks[1]);
+ moof.tracks[1].runs[0].sample_sizes[0] = 5;
+
+ ASSERT_TRUE(iter_->Init(moof));
+ EXPECT_EQ(iter_->track_id(), 2u);
+ EXPECT_EQ(iter_->aux_info_offset(), 50);
+ EXPECT_EQ(iter_->sample_offset(), 200);
+ EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo)));
+ EXPECT_EQ(iter_->GetMaxClearOffset(), 100);
+ iter_->AdvanceRun();
+ EXPECT_EQ(iter_->track_id(), 1u);
+ EXPECT_EQ(iter_->aux_info_offset(), 20000);
+ EXPECT_EQ(iter_->sample_offset(), 100);
+ EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo)));
+ EXPECT_EQ(iter_->GetMaxClearOffset(), 100);
+ iter_->AdvanceSample();
+ EXPECT_EQ(iter_->GetMaxClearOffset(), 101);
+ iter_->AdvanceRun();
+ EXPECT_EQ(iter_->track_id(), 1u);
+ EXPECT_EQ(iter_->aux_info_offset(), 201);
+ EXPECT_EQ(iter_->sample_offset(), 10000);
+ EXPECT_EQ(iter_->GetMaxClearOffset(), 201);
+ EXPECT_TRUE(iter_->CacheAuxInfo(kAuxInfo, arraysize(kAuxInfo)));
+ EXPECT_EQ(iter_->GetMaxClearOffset(), 10000);
}
} // namespace mp4