Implement SampleEncryption box parsing and generation

Issue #41

Change-Id: Ic57436b6e6c8b58fc264037fd81c98627c525932
This commit is contained in:
KongQun Yang 2015-02-06 12:31:46 -08:00
parent c0df6b3239
commit 098e087459
32 changed files with 307 additions and 249 deletions

View File

@ -1,4 +1,4 @@
bandwidth: 128635 bandwidth: 128728
audio_info { audio_info {
codec: "mp4a.40.2" codec: "mp4a.40.2"
sampling_frequency: 44100 sampling_frequency: 44100

View File

@ -2,7 +2,7 @@
<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT2.763174533843994S"> <MPD xmlns="urn:mpeg:DASH:schema:MPD:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT2.763174533843994S">
<Period> <Period>
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" subSegmentAlignment="true" par="16:9"> <AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" subSegmentAlignment="true" par="16:9">
<Representation id="0" bandwidth="885058" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001"> <Representation id="0" bandwidth="885151" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/> <ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"> <ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2</cenc:pssh> <cenc:pssh>AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2</cenc:pssh>
@ -14,7 +14,7 @@
</Representation> </Representation>
</AdaptationSet> </AdaptationSet>
<AdaptationSet id="1" contentType="audio" subSegmentAlignment="true"> <AdaptationSet id="1" contentType="audio" subSegmentAlignment="true">
<Representation id="1" bandwidth="128635" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100"> <Representation id="1" bandwidth="128728" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/> <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/> <ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"> <ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">

View File

@ -6,7 +6,7 @@
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"> <ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2</cenc:pssh> <cenc:pssh>AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2</cenc:pssh>
</ContentProtection> </ContentProtection>
<Representation id="0" bandwidth="885058" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001"> <Representation id="0" bandwidth="885151" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001">
<BaseURL>output_video.mp4</BaseURL> <BaseURL>output_video.mp4</BaseURL>
<SegmentBase indexRange="941-1008" timescale="30000"> <SegmentBase indexRange="941-1008" timescale="30000">
<Initialization range="0-940"/> <Initialization range="0-940"/>
@ -18,7 +18,7 @@
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"> <ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2</cenc:pssh> <cenc:pssh>AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2</cenc:pssh>
</ContentProtection> </ContentProtection>
<Representation id="1" bandwidth="128635" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100"> <Representation id="1" bandwidth="128728" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/> <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<BaseURL>output_audio.mp4</BaseURL> <BaseURL>output_audio.mp4</BaseURL>
<SegmentBase indexRange="817-884" timescale="44100"> <SegmentBase indexRange="817-884" timescale="44100">

View File

@ -2,7 +2,7 @@
<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="dynamic" profiles="urn:mpeg:dash:profile:isoff-live:2011" availabilityStartTime="place_holder" minimumUpdatePeriod="PT5S" timeShiftBufferDepth="PT1800S"> <MPD xmlns="urn:mpeg:DASH:schema:MPD:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="dynamic" profiles="urn:mpeg:dash:profile:isoff-live:2011" availabilityStartTime="place_holder" minimumUpdatePeriod="PT5S" timeShiftBufferDepth="PT1800S">
<Period start="PT0S"> <Period start="PT0S">
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" segmentAlignment="true" par="16:9"> <AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" segmentAlignment="true" par="16:9">
<Representation id="0" bandwidth="875528" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001"> <Representation id="0" bandwidth="875620" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/> <ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"> <ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2</cenc:pssh> <cenc:pssh>AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2</cenc:pssh>
@ -16,7 +16,7 @@
</Representation> </Representation>
</AdaptationSet> </AdaptationSet>
<AdaptationSet id="1" contentType="audio" segmentAlignment="true"> <AdaptationSet id="1" contentType="audio" segmentAlignment="true">
<Representation id="1" bandwidth="124085" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100"> <Representation id="1" bandwidth="124195" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/> <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/> <ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"> <ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">

View File

@ -6,7 +6,7 @@
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"> <ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2</cenc:pssh> <cenc:pssh>AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2</cenc:pssh>
</ContentProtection> </ContentProtection>
<Representation id="0" bandwidth="875528" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001"> <Representation id="0" bandwidth="875620" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001">
<SegmentTemplate timescale="30000" initialization="output_video-init.mp4" media="output_video-$Number$.m4s" startNumber="1"> <SegmentTemplate timescale="30000" initialization="output_video-init.mp4" media="output_video-$Number$.m4s" startNumber="1">
<SegmentTimeline> <SegmentTimeline>
<S t="2002" d="30030" r="1"/> <S t="2002" d="30030" r="1"/>
@ -20,7 +20,7 @@
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"> <ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2</cenc:pssh> <cenc:pssh>AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2</cenc:pssh>
</ContentProtection> </ContentProtection>
<Representation id="1" bandwidth="124085" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100"> <Representation id="1" bandwidth="124195" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/> <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1"> <SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
<SegmentTimeline> <SegmentTimeline>

View File

@ -2,7 +2,7 @@
<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="dynamic" profiles="urn:mpeg:dash:profile:isoff-live:2011" availabilityStartTime="place_holder" minimumUpdatePeriod="PT5S" timeShiftBufferDepth="PT1800S"> <MPD xmlns="urn:mpeg:DASH:schema:MPD:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="dynamic" profiles="urn:mpeg:dash:profile:isoff-live:2011" availabilityStartTime="place_holder" minimumUpdatePeriod="PT5S" timeShiftBufferDepth="PT1800S">
<Period start="PT0S"> <Period start="PT0S">
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" segmentAlignment="true" par="16:9"> <AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" segmentAlignment="true" par="16:9">
<Representation id="0" bandwidth="876377" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001"> <Representation id="0" bandwidth="876470" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="00000000-0000-0000-0000-000000000000"/> <ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="00000000-0000-0000-0000-000000000000"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"/> <ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"/>
<SegmentTemplate timescale="30000" initialization="output_video-init.mp4" media="output_video-$Number$.m4s" startNumber="1"> <SegmentTemplate timescale="30000" initialization="output_video-init.mp4" media="output_video-$Number$.m4s" startNumber="1">
@ -14,7 +14,7 @@
</Representation> </Representation>
</AdaptationSet> </AdaptationSet>
<AdaptationSet id="1" contentType="audio" segmentAlignment="true"> <AdaptationSet id="1" contentType="audio" segmentAlignment="true">
<Representation id="1" bandwidth="125028" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100"> <Representation id="1" bandwidth="125138" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/> <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="00000000-0000-0000-0000-000000000000"/> <ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="00000000-0000-0000-0000-000000000000"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"/> <ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"/>

View File

@ -4,7 +4,7 @@
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" segmentAlignment="true" par="16:9"> <AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" segmentAlignment="true" par="16:9">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="00000000-0000-0000-0000-000000000000"/> <ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="00000000-0000-0000-0000-000000000000"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"/> <ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"/>
<Representation id="0" bandwidth="876377" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001"> <Representation id="0" bandwidth="876470" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001">
<SegmentTemplate timescale="30000" initialization="output_video-init.mp4" media="output_video-$Number$.m4s" startNumber="1"> <SegmentTemplate timescale="30000" initialization="output_video-init.mp4" media="output_video-$Number$.m4s" startNumber="1">
<SegmentTimeline> <SegmentTimeline>
<S t="2002" d="30030" r="1"/> <S t="2002" d="30030" r="1"/>
@ -16,7 +16,7 @@
<AdaptationSet id="1" contentType="audio" segmentAlignment="true"> <AdaptationSet id="1" contentType="audio" segmentAlignment="true">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="00000000-0000-0000-0000-000000000000"/> <ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="00000000-0000-0000-0000-000000000000"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"/> <ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"/>
<Representation id="1" bandwidth="125028" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100"> <Representation id="1" bandwidth="125138" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/> <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
<SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1"> <SegmentTemplate timescale="44100" initialization="output_audio-init.mp4" media="output_audio-$Number$.m4s" startNumber="1">
<SegmentTimeline> <SegmentTimeline>

View File

@ -1,4 +1,4 @@
bandwidth: 885058 bandwidth: 885151
video_info { video_info {
codec: "avc1.64001e" codec: "avc1.64001e"
width: 640 width: 640

View File

@ -1,93 +0,0 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "packager/media/formats/mp4/cenc.h"
#include <cstring>
#include "packager/media/base/buffer_reader.h"
#include "packager/media/base/buffer_writer.h"
#include "packager/media/formats/mp4/rcheck.h"
namespace {
// According to ISO/IEC FDIS 23001-7: CENC spec, IV should be either
// 64-bit (8-byte) or 128-bit (16-byte).
bool IsIvSizeValid(size_t iv_size) { return iv_size == 8 || iv_size == 16; }
// 16-bit |clear_bytes| and 32-bit |cipher_bytes|.
const size_t kSubsampleEntrySize = sizeof(uint16_t) + sizeof(uint32_t);
} // namespace
namespace edash_packager {
namespace media {
namespace mp4 {
FrameCENCInfo::FrameCENCInfo() {}
FrameCENCInfo::FrameCENCInfo(const std::vector<uint8_t>& iv) : iv_(iv) {
}
FrameCENCInfo::~FrameCENCInfo() {}
bool FrameCENCInfo::Parse(uint8_t iv_size, BufferReader* reader) {
DCHECK(reader);
// Mandated by CENC spec.
RCHECK(IsIvSizeValid(iv_size));
iv_.resize(iv_size);
RCHECK(reader->ReadToVector(&iv_, iv_size));
if (!reader->HasBytes(1))
return true;
uint16_t subsample_count;
RCHECK(reader->Read2(&subsample_count) &&
reader->HasBytes(subsample_count * kSubsampleEntrySize));
subsamples_.resize(subsample_count);
for (uint16_t i = 0; i < subsample_count; ++i) {
uint16_t clear_bytes;
uint32_t cipher_bytes;
RCHECK(reader->Read2(&clear_bytes) &&
reader->Read4(&cipher_bytes));
subsamples_[i].clear_bytes = clear_bytes;
subsamples_[i].cipher_bytes = cipher_bytes;
}
return true;
}
void FrameCENCInfo::Write(BufferWriter* writer) const {
DCHECK(writer);
DCHECK(IsIvSizeValid(iv_.size()));
writer->AppendVector(iv_);
uint16_t subsample_count = subsamples_.size();
if (subsample_count == 0)
return;
writer->AppendInt(subsample_count);
for (uint16_t i = 0; i < subsample_count; ++i) {
writer->AppendInt(subsamples_[i].clear_bytes);
writer->AppendInt(subsamples_[i].cipher_bytes);
}
}
size_t FrameCENCInfo::ComputeSize() const {
uint16_t subsample_count = subsamples_.size();
if (subsample_count == 0)
return iv_.size();
return iv_.size() + sizeof(subsample_count) +
subsample_count * kSubsampleEntrySize;
}
size_t FrameCENCInfo::GetTotalSizeOfSubsamples() const {
size_t size = 0;
for (size_t i = 0; i < subsamples_.size(); ++i) {
size += subsamples_[i].clear_bytes + subsamples_[i].cipher_bytes;
}
return size;
}
} // namespace mp4
} // namespace media
} // namespace edash_packager

View File

@ -1,52 +0,0 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MEDIA_FORMATS_MP4_CENC_H_
#define MEDIA_FORMATS_MP4_CENC_H_
#include <stdint.h>
#include <vector>
#include "packager/media/base/decrypt_config.h"
namespace edash_packager {
namespace media {
class BufferReader;
class BufferWriter;
namespace mp4 {
class FrameCENCInfo {
public:
FrameCENCInfo();
explicit FrameCENCInfo(const std::vector<uint8_t>& iv);
~FrameCENCInfo();
bool Parse(uint8_t iv_size, BufferReader* reader);
void Write(BufferWriter* writer) const;
size_t ComputeSize() const;
size_t GetTotalSizeOfSubsamples() const;
void AddSubsample(const SubsampleEntry& subsample) {
subsamples_.push_back(subsample);
}
const std::vector<uint8_t>& iv() const { return iv_; }
const std::vector<SubsampleEntry>& subsamples() const { return subsamples_; }
private:
std::vector<uint8_t> iv_;
std::vector<SubsampleEntry> subsamples_;
// Not using DISALLOW_COPY_AND_ASSIGN here intentionally to allow the compiler
// generated copy constructor and assignment operator.
};
} // namespace mp4
} // namespace media
} // namespace edash_packager
#endif // MEDIA_FORMATS_MP4_CENC_H_

View File

@ -13,7 +13,6 @@
#include "packager/media/filters/vp8_parser.h" #include "packager/media/filters/vp8_parser.h"
#include "packager/media/filters/vp9_parser.h" #include "packager/media/filters/vp9_parser.h"
#include "packager/media/formats/mp4/box_definitions.h" #include "packager/media/formats/mp4/box_definitions.h"
#include "packager/media/formats/mp4/cenc.h"
namespace { namespace {
// Generate 64bit IV by default. // Generate 64bit IV by default.
@ -67,6 +66,11 @@ Status EncryptingFragmenter::InitializeFragment(int64_t first_sample_dts) {
traf()->auxiliary_size.sample_info_sizes.clear(); traf()->auxiliary_size.sample_info_sizes.clear();
traf()->auxiliary_offset.offsets.clear(); traf()->auxiliary_offset.offsets.clear();
if (IsSubsampleEncryptionRequired()) {
traf()->sample_encryption.flags |=
SampleEncryption::kUseSubsampleEncryption;
}
traf()->sample_encryption.sample_encryption_entries.clear();
const bool enable_encryption = clear_time_ <= 0; const bool enable_encryption = clear_time_ <= 0;
if (!enable_encryption) { if (!enable_encryption) {
@ -116,6 +120,7 @@ void EncryptingFragmenter::FinalizeFragmentForEncryption() {
DCHECK(!IsSubsampleEncryptionRequired()); DCHECK(!IsSubsampleEncryptionRequired());
saiz.default_sample_info_size = encryptor_->iv().size(); saiz.default_sample_info_size = encryptor_->iv().size();
} }
traf()->sample_encryption.iv_size = encryptor_->iv().size();
} }
Status EncryptingFragmenter::CreateEncryptor() { Status EncryptingFragmenter::CreateEncryptor() {
@ -141,7 +146,8 @@ void EncryptingFragmenter::EncryptBytes(uint8_t* data, uint32_t size) {
Status EncryptingFragmenter::EncryptSample(scoped_refptr<MediaSample> sample) { Status EncryptingFragmenter::EncryptSample(scoped_refptr<MediaSample> sample) {
DCHECK(encryptor_); DCHECK(encryptor_);
FrameCENCInfo cenc_info(encryptor_->iv()); SampleEncryptionEntry sample_encryption_entry;
sample_encryption_entry.initialization_vector = encryptor_->iv();
uint8_t* data = sample->writable_data(); uint8_t* data = sample->writable_data();
if (IsSubsampleEncryptionRequired()) { if (IsSubsampleEncryptionRequired()) {
if (vpx_parser_) { if (vpx_parser_) {
@ -155,7 +161,7 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr<MediaSample> sample) {
subsample.clear_bytes = frame.uncompressed_header_size; subsample.clear_bytes = frame.uncompressed_header_size;
subsample.cipher_bytes = subsample.cipher_bytes =
frame.frame_size - frame.uncompressed_header_size; frame.frame_size - frame.uncompressed_header_size;
cenc_info.AddSubsample(subsample); sample_encryption_entry.subsamples.push_back(subsample);
if (subsample.cipher_bytes > 0) if (subsample.cipher_bytes > 0)
EncryptBytes(data + subsample.clear_bytes, subsample.cipher_bytes); EncryptBytes(data + subsample.clear_bytes, subsample.cipher_bytes);
data += frame.frame_size; data += frame.frame_size;
@ -167,27 +173,30 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr<MediaSample> sample) {
if (!reader.ReadNBytesInto8(&nalu_length, nalu_length_size_)) if (!reader.ReadNBytesInto8(&nalu_length, nalu_length_size_))
return Status(error::MUXER_FAILURE, "Fail to read nalu_length."); return Status(error::MUXER_FAILURE, "Fail to read nalu_length.");
SubsampleEntry subsample;
subsample.clear_bytes = nalu_length_size_ + 1;
subsample.cipher_bytes = nalu_length - 1;
if (!reader.SkipBytes(nalu_length)) { if (!reader.SkipBytes(nalu_length)) {
return Status(error::MUXER_FAILURE, return Status(error::MUXER_FAILURE,
"Sample size does not match nalu_length."); "Sample size does not match nalu_length.");
} }
SubsampleEntry subsample;
subsample.clear_bytes = nalu_length_size_ + 1;
subsample.cipher_bytes = nalu_length - 1;
sample_encryption_entry.subsamples.push_back(subsample);
EncryptBytes(data + subsample.clear_bytes, subsample.cipher_bytes); EncryptBytes(data + subsample.clear_bytes, subsample.cipher_bytes);
cenc_info.AddSubsample(subsample);
data += nalu_length_size_ + nalu_length; data += nalu_length_size_ + nalu_length;
} }
} }
// The length of per-sample auxiliary datum, defined in CENC ch. 7. // The length of per-sample auxiliary datum, defined in CENC ch. 7.
traf()->auxiliary_size.sample_info_sizes.push_back(cenc_info.ComputeSize()); traf()->auxiliary_size.sample_info_sizes.push_back(
sample_encryption_entry.ComputeSize());
} else { } else {
EncryptBytes(data, sample->data_size()); EncryptBytes(data, sample->data_size());
} }
cenc_info.Write(aux_data()); traf()->sample_encryption.sample_encryption_entries.push_back(
sample_encryption_entry);
encryptor_->UpdateIv(); encryptor_->UpdateIv();
return Status::OK; return Status::OK;
} }

View File

@ -84,7 +84,6 @@ Status Fragmenter::InitializeFragment(int64_t first_sample_dts) {
earliest_presentation_time_ = kInvalidTime; earliest_presentation_time_ = kInvalidTime;
first_sap_time_ = kInvalidTime; first_sap_time_ = kInvalidTime;
data_.reset(new BufferWriter()); data_.reset(new BufferWriter());
aux_data_.reset(new BufferWriter());
return Status::OK; return Status::OK;
} }

View File

@ -59,7 +59,6 @@ class Fragmenter {
bool fragment_initialized() const { return fragment_initialized_; } bool fragment_initialized() const { return fragment_initialized_; }
bool fragment_finalized() const { return fragment_finalized_; } bool fragment_finalized() const { return fragment_finalized_; }
BufferWriter* data() { return data_.get(); } BufferWriter* data() { return data_.get(); }
BufferWriter* aux_data() { return aux_data_.get(); }
protected: protected:
TrackFragment* traf() { return traf_; } TrackFragment* traf() { return traf_; }
@ -82,7 +81,6 @@ class Fragmenter {
int64_t earliest_presentation_time_; int64_t earliest_presentation_time_;
int64_t first_sap_time_; int64_t first_sap_time_;
scoped_ptr<BufferWriter> data_; scoped_ptr<BufferWriter> data_;
scoped_ptr<BufferWriter> aux_data_;
DISALLOW_COPY_AND_ASSIGN(Fragmenter); DISALLOW_COPY_AND_ASSIGN(Fragmenter);
}; };

View File

@ -22,8 +22,6 @@
'box_definitions.h', 'box_definitions.h',
'box_reader.cc', 'box_reader.cc',
'box_reader.h', 'box_reader.h',
'cenc.cc',
'cenc.h',
'chunk_info_iterator.cc', 'chunk_info_iterator.cc',
'chunk_info_iterator.h', 'chunk_info_iterator.h',
'composition_offset_iterator.cc', 'composition_offset_iterator.cc',

View File

@ -25,6 +25,10 @@ namespace edash_packager {
namespace media { namespace media {
namespace { namespace {
const char kKey[] =
"\xeb\xdd\x62\xf1\x68\x14\xd2\x7b\x68\xef\x12\x2a\xfc\xe4\xae\x3c";
const char kKeyId[] = "0123456789012345";
class MockKeySource : public KeySource { class MockKeySource : public KeySource {
public: public:
MOCK_METHOD1(FetchKeys, Status(const std::vector<uint8_t>& pssh_data)); MOCK_METHOD1(FetchKeys, Status(const std::vector<uint8_t>& pssh_data));
@ -225,7 +229,7 @@ TEST_F(MP4MediaParserTest, NON_FRAGMENTED_MP4) {
TEST_F(MP4MediaParserTest, CencWithoutDecryptionSource) { TEST_F(MP4MediaParserTest, CencWithoutDecryptionSource) {
// Parsing should fail but it will get the streams successfully. // Parsing should fail but it will get the streams successfully.
EXPECT_FALSE(ParseMP4File("bear-640x360-v_frag-cenc.mp4", 512)); EXPECT_FALSE(ParseMP4File("bear-640x360-v_frag-cenc-aux.mp4", 512));
EXPECT_EQ(1u, num_streams_); EXPECT_EQ(1u, num_streams_);
} }
@ -233,20 +237,16 @@ TEST_F(MP4MediaParserTest, CencInitWithoutDecryptionSource) {
InitializeParser(NULL); InitializeParser(NULL);
std::vector<uint8_t> buffer = std::vector<uint8_t> buffer =
ReadTestDataFile("bear-640x360-v_frag-cenc.mp4"); ReadTestDataFile("bear-640x360-v_frag-cenc-aux.mp4");
const int kFirstMoofOffset = 1646; const int kFirstMoofOffset = 1646;
EXPECT_TRUE(AppendDataInPieces(buffer.data(), kFirstMoofOffset, 512)); EXPECT_TRUE(AppendDataInPieces(buffer.data(), kFirstMoofOffset, 512));
EXPECT_EQ(1u, num_streams_); EXPECT_EQ(1u, num_streams_);
} }
TEST_F(MP4MediaParserTest, CencWithDecryptionSource) { TEST_F(MP4MediaParserTest, CencWithDecryptionSourceAndAuxInMdat) {
MockKeySource mock_key_source; MockKeySource mock_key_source;
EXPECT_CALL(mock_key_source, FetchKeys(_)).WillOnce(Return(Status::OK)); EXPECT_CALL(mock_key_source, FetchKeys(_)).WillOnce(Return(Status::OK));
const char kKey[] =
"\xeb\xdd\x62\xf1\x68\x14\xd2\x7b\x68\xef\x12\x2a\xfc\xe4\xae\x3c";
const char kKeyId[] = "0123456789012345";
EncryptionKey encryption_key; EncryptionKey encryption_key;
encryption_key.key.assign(kKey, kKey + strlen(kKey)); encryption_key.key.assign(kKey, kKey + strlen(kKey));
EXPECT_CALL(mock_key_source, EXPECT_CALL(mock_key_source,
@ -256,7 +256,26 @@ TEST_F(MP4MediaParserTest, CencWithDecryptionSource) {
InitializeParser(&mock_key_source); InitializeParser(&mock_key_source);
std::vector<uint8_t> buffer = std::vector<uint8_t> buffer =
ReadTestDataFile("bear-640x360-v_frag-cenc.mp4"); ReadTestDataFile("bear-640x360-v_frag-cenc-aux.mp4");
EXPECT_TRUE(AppendDataInPieces(buffer.data(), buffer.size(), 512));
EXPECT_EQ(1u, num_streams_);
EXPECT_EQ(82u, num_samples_);
}
TEST_F(MP4MediaParserTest, CencWithDecryptionSourceAndSenc) {
MockKeySource mock_key_source;
EXPECT_CALL(mock_key_source, FetchKeys(_)).WillOnce(Return(Status::OK));
EncryptionKey encryption_key;
encryption_key.key.assign(kKey, kKey + strlen(kKey));
EXPECT_CALL(mock_key_source,
GetKey(std::vector<uint8_t>(kKeyId, kKeyId + strlen(kKeyId)), _))
.WillOnce(DoAll(SetArgPointee<1>(encryption_key), Return(Status::OK)));
InitializeParser(&mock_key_source);
std::vector<uint8_t> buffer =
ReadTestDataFile("bear-640x360-v_frag-cenc-senc.mp4");
EXPECT_TRUE(AppendDataInPieces(buffer.data(), buffer.size(), 512)); EXPECT_TRUE(AppendDataInPieces(buffer.data(), buffer.size(), 512));
EXPECT_EQ(1u, num_streams_); EXPECT_EQ(1u, num_streams_);
EXPECT_EQ(82u, num_samples_); EXPECT_EQ(82u, num_samples_);

View File

@ -385,39 +385,41 @@ Status Segmenter::FinalizeFragment(bool finalize_segment,
} }
MediaData mdat; MediaData mdat;
// Fill in data offsets. Data offset base is moof size + mdat box size. // Data offset relative to 'moof': moof size + mdat header size.
// (mdat is still empty, mdat size is the same as mdat box size). // The code will also update box sizes for moof_ and its child boxes.
uint64_t base = moof_->ComputeSize() + mdat.ComputeSize(); uint64_t data_offset = moof_->ComputeSize() + mdat.HeaderSize();
// 'traf' should follow 'mfhd' moof header box.
uint64_t next_traf_position = moof_->HeaderSize() + moof_->header.box_size();
for (size_t i = 0; i < moof_->tracks.size(); ++i) { for (size_t i = 0; i < moof_->tracks.size(); ++i) {
TrackFragment& traf = moof_->tracks[i]; TrackFragment& traf = moof_->tracks[i];
Fragmenter* fragmenter = fragmenters_[i]; if (traf.auxiliary_offset.offsets.size() > 0) {
if (fragmenter->aux_data()->Size() > 0) { DCHECK_EQ(traf.auxiliary_offset.offsets.size(), 1u);
traf.auxiliary_offset.offsets[0] += base; DCHECK(!traf.sample_encryption.sample_encryption_entries.empty());
base += fragmenter->aux_data()->Size();
next_traf_position += traf.box_size();
// SampleEncryption 'senc' box should be the last box in 'traf'.
// |auxiliary_offset| should point to the data of SampleEncryption.
traf.auxiliary_offset.offsets[0] =
next_traf_position - traf.sample_encryption.box_size() +
traf.sample_encryption.HeaderSize() +
sizeof(uint32_t); // for sample count field in 'senc'
} }
traf.runs[0].data_offset += base; traf.runs[0].data_offset = data_offset + mdat.data_size;
base += fragmenter->data()->Size(); mdat.data_size += fragmenters_[i]->data()->Size();
} }
// Generate segment reference. // Generate segment reference.
sidx_->references.resize(sidx_->references.size() + 1); sidx_->references.resize(sidx_->references.size() + 1);
fragmenters_[GetReferenceStreamId()]->GenerateSegmentReference( fragmenters_[GetReferenceStreamId()]->GenerateSegmentReference(
&sidx_->references[sidx_->references.size() - 1]); &sidx_->references[sidx_->references.size() - 1]);
sidx_->references[sidx_->references.size() - 1].referenced_size = base; sidx_->references[sidx_->references.size() - 1].referenced_size =
data_offset + mdat.data_size;
// Write the fragment to buffer. // Write the fragment to buffer.
moof_->Write(fragment_buffer_.get()); moof_->Write(fragment_buffer_.get());
for (size_t i = 0; i < moof_->tracks.size(); ++i) {
Fragmenter* fragmenter = fragmenters_[i];
mdat.data_size =
fragmenter->aux_data()->Size() + fragmenter->data()->Size();
mdat.WriteHeader(fragment_buffer_.get()); mdat.WriteHeader(fragment_buffer_.get());
if (fragmenter->aux_data()->Size()) { for (Fragmenter* fragmenter : fragmenters_)
fragment_buffer_->AppendBuffer(*fragmenter->aux_data());
}
fragment_buffer_->AppendBuffer(*fragmenter->data()); fragment_buffer_->AppendBuffer(*fragmenter->data());
}
// Increase sequence_number for next fragment. // Increase sequence_number for next fragment.
++moof_->header.sequence_number; ++moof_->header.sequence_number;

View File

@ -40,6 +40,12 @@ struct TrackRunInfo {
const AudioSampleEntry* audio_description; const AudioSampleEntry* audio_description;
const VideoSampleEntry* video_description; const VideoSampleEntry* video_description;
// Stores sample encryption entries, which is populated from 'senc' box if it
// is available, otherwise will try to load from cenc auxiliary information.
std::vector<SampleEncryptionEntry> sample_encryption_entries;
// These variables are useful to load |sample_encryption_entries| from cenc
// auxiliary information when 'senc' box is not available.
int64_t aux_info_start_offset; // Only valid if aux_info_total_size > 0. int64_t aux_info_start_offset; // Only valid if aux_info_total_size > 0.
int aux_info_default_size; int aux_info_default_size;
std::vector<uint8_t> aux_info_sizes; // Populated if default_size == 0. std::vector<uint8_t> aux_info_sizes; // Populated if default_size == 0.
@ -300,6 +306,40 @@ bool TrackRunIterator::Init(const MovieFragment& moof) {
RCHECK(desc_idx > 0); // Descriptions are one-indexed in the file RCHECK(desc_idx > 0); // Descriptions are one-indexed in the file
desc_idx -= 1; desc_idx -= 1;
const AudioSampleEntry* audio_sample_entry = NULL;
const VideoSampleEntry* video_sample_entry = NULL;
switch (stsd.type) {
case kAudio:
RCHECK(!stsd.audio_entries.empty());
if (desc_idx > stsd.audio_entries.size())
desc_idx = 0;
audio_sample_entry = &stsd.audio_entries[desc_idx];
break;
case kVideo:
RCHECK(!stsd.video_entries.empty());
if (desc_idx > stsd.video_entries.size())
desc_idx = 0;
video_sample_entry = &stsd.video_entries[desc_idx];
break;
default:
NOTREACHED();
break;
}
// SampleEncryptionEntries should not have been parsed, without having
// iv_size. Parse the box now.
DCHECK(traf.sample_encryption.sample_encryption_entries.empty());
std::vector<SampleEncryptionEntry> sample_encryption_entries;
if (!traf.sample_encryption.sample_encryption_data.empty()) {
RCHECK(audio_sample_entry || video_sample_entry);
const uint8_t default_iv_size =
audio_sample_entry
? audio_sample_entry->sinf.info.track_encryption.default_iv_size
: video_sample_entry->sinf.info.track_encryption.default_iv_size;
RCHECK(traf.sample_encryption.ParseFromSampleEncryptionData(
default_iv_size, &sample_encryption_entries));
}
int64_t run_start_dts = traf.decode_time_absent int64_t run_start_dts = traf.decode_time_absent
? next_fragment_start_dts_[i] ? next_fragment_start_dts_[i]
: traf.decode_time.decode_time; : traf.decode_time.decode_time;
@ -314,27 +354,30 @@ bool TrackRunIterator::Init(const MovieFragment& moof) {
tri.sample_start_offset = trun.data_offset; tri.sample_start_offset = trun.data_offset;
tri.track_type = stsd.type; tri.track_type = stsd.type;
if (tri.track_type == kAudio) { tri.audio_description = audio_sample_entry;
RCHECK(!stsd.audio_entries.empty()); tri.video_description = video_sample_entry;
if (desc_idx > stsd.audio_entries.size())
desc_idx = 0;
tri.audio_description = &stsd.audio_entries[desc_idx];
} else if (tri.track_type == kVideo) {
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 tri.aux_info_start_offset = -1;
// in the 'saiz' container as the current run's index in the 'trun' tri.aux_info_total_size = 0;
// container, if it is present. // Populate sample encryption entries from SampleEncryption 'senc' box if
if (traf.auxiliary_offset.offsets.size() > j) { // it is available; otherwise initialize aux_info variables, which will
// be used to populate sample encryption entries later in CacheAuxInfo.
if (!sample_encryption_entries.empty()) {
RCHECK(sample_encryption_entries.size() >=
sample_count_sum + trun.sample_count);
for (size_t k = 0; k < trun.sample_count; ++k) {
tri.sample_encryption_entries.push_back(
sample_encryption_entries[sample_count_sum + k]);
}
} else if (traf.auxiliary_offset.offsets.size() > j) {
// 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.
tri.aux_info_start_offset = traf.auxiliary_offset.offsets[j];
// There should be an auxiliary info entry corresponding to each sample // There should be an auxiliary info entry corresponding to each sample
// in the auxiliary offset entry's corresponding track run. // in the auxiliary offset entry's corresponding track run.
RCHECK(traf.auxiliary_size.sample_count >= RCHECK(traf.auxiliary_size.sample_count >=
sample_count_sum + trun.sample_count); sample_count_sum + trun.sample_count);
tri.aux_info_start_offset = traf.auxiliary_offset.offsets[j];
tri.aux_info_default_size = tri.aux_info_default_size =
traf.auxiliary_size.default_sample_info_size; traf.auxiliary_size.default_sample_info_size;
if (tri.aux_info_default_size == 0) { if (tri.aux_info_default_size == 0) {
@ -358,9 +401,6 @@ bool TrackRunIterator::Init(const MovieFragment& moof) {
tri.aux_info_total_size += tri.aux_info_sizes[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); tri.samples.resize(trun.sample_count);
@ -391,7 +431,6 @@ void TrackRunIterator::ResetRun() {
sample_dts_ = run_itr_->start_dts; sample_dts_ = run_itr_->start_dts;
sample_offset_ = run_itr_->sample_start_offset; sample_offset_ = run_itr_->sample_start_offset;
sample_itr_ = run_itr_->samples.begin(); sample_itr_ = run_itr_->samples.begin();
cenc_info_.clear();
} }
void TrackRunIterator::AdvanceSample() { void TrackRunIterator::AdvanceSample() {
@ -405,14 +444,17 @@ void TrackRunIterator::AdvanceSample() {
// info is available in the stream. // info is available in the stream.
bool TrackRunIterator::AuxInfoNeedsToBeCached() { bool TrackRunIterator::AuxInfoNeedsToBeCached() {
DCHECK(IsRunValid()); DCHECK(IsRunValid());
return is_encrypted() && aux_info_size() > 0 && cenc_info_.size() == 0; return is_encrypted() && aux_info_size() > 0 &&
run_itr_->sample_encryption_entries.size() == 0;
} }
// This implementation currently only caches CENC auxiliary info. // This implementation currently only caches CENC auxiliary info.
bool TrackRunIterator::CacheAuxInfo(const uint8_t* buf, int buf_size) { bool TrackRunIterator::CacheAuxInfo(const uint8_t* buf, int buf_size) {
RCHECK(AuxInfoNeedsToBeCached() && buf_size >= aux_info_size()); RCHECK(AuxInfoNeedsToBeCached() && buf_size >= aux_info_size());
cenc_info_.resize(run_itr_->samples.size()); std::vector<SampleEncryptionEntry>& sample_encryption_entries =
runs_[run_itr_ - runs_.begin()].sample_encryption_entries;
sample_encryption_entries.resize(run_itr_->samples.size());
int64_t pos = 0; int64_t pos = 0;
for (size_t i = 0; i < run_itr_->samples.size(); i++) { for (size_t i = 0; i < run_itr_->samples.size(); i++) {
int info_size = run_itr_->aux_info_default_size; int info_size = run_itr_->aux_info_default_size;
@ -420,7 +462,9 @@ bool TrackRunIterator::CacheAuxInfo(const uint8_t* buf, int buf_size) {
info_size = run_itr_->aux_info_sizes[i]; info_size = run_itr_->aux_info_sizes[i];
BufferReader reader(buf + pos, info_size); BufferReader reader(buf + pos, info_size);
RCHECK(cenc_info_[i].Parse(track_encryption().default_iv_size, &reader)); const bool has_subsamples = info_size > track_encryption().default_iv_size;
RCHECK(sample_encryption_entries[i].ParseFromBuffer(
track_encryption().default_iv_size, has_subsamples, &reader));
pos += info_size; pos += info_size;
} }
@ -539,12 +583,14 @@ const TrackEncryption& TrackRunIterator::track_encryption() const {
scoped_ptr<DecryptConfig> TrackRunIterator::GetDecryptConfig() { scoped_ptr<DecryptConfig> TrackRunIterator::GetDecryptConfig() {
size_t sample_idx = sample_itr_ - run_itr_->samples.begin(); size_t sample_idx = sample_itr_ - run_itr_->samples.begin();
DCHECK_LT(sample_idx, cenc_info_.size()); DCHECK_LT(sample_idx, run_itr_->sample_encryption_entries.size());
const FrameCENCInfo& cenc_info = cenc_info_[sample_idx]; const SampleEncryptionEntry& sample_encryption_entry =
run_itr_->sample_encryption_entries[sample_idx];
DCHECK(is_encrypted()); DCHECK(is_encrypted());
DCHECK(!AuxInfoNeedsToBeCached()); DCHECK(!AuxInfoNeedsToBeCached());
const size_t total_size_of_subsamples = cenc_info.GetTotalSizeOfSubsamples(); const size_t total_size_of_subsamples =
sample_encryption_entry.GetTotalSizeOfSubsamples();
if (total_size_of_subsamples != 0 && if (total_size_of_subsamples != 0 &&
total_size_of_subsamples != static_cast<size_t>(sample_size())) { total_size_of_subsamples != static_cast<size_t>(sample_size())) {
LOG(ERROR) << "Incorrect CENC subsample size."; LOG(ERROR) << "Incorrect CENC subsample size.";
@ -553,9 +599,9 @@ scoped_ptr<DecryptConfig> TrackRunIterator::GetDecryptConfig() {
return scoped_ptr<DecryptConfig>(new DecryptConfig( return scoped_ptr<DecryptConfig>(new DecryptConfig(
track_encryption().default_kid, track_encryption().default_kid,
cenc_info.iv(), sample_encryption_entry.initialization_vector,
0, // No offset to start of media data in MP4 using CENC. 0, // No offset to start of media data in MP4 using CENC.
cenc_info.subsamples())); sample_encryption_entry.subsamples));
} }
} // namespace mp4 } // namespace mp4

View File

@ -9,7 +9,6 @@
#include "packager/base/memory/scoped_ptr.h" #include "packager/base/memory/scoped_ptr.h"
#include "packager/media/formats/mp4/box_definitions.h" #include "packager/media/formats/mp4/box_definitions.h"
#include "packager/media/formats/mp4/cenc.h"
namespace edash_packager { namespace edash_packager {
namespace media { namespace media {
@ -111,7 +110,6 @@ class TrackRunIterator {
std::vector<TrackRunInfo>::const_iterator run_itr_; std::vector<TrackRunInfo>::const_iterator run_itr_;
std::vector<SampleInfo>::const_iterator sample_itr_; std::vector<SampleInfo>::const_iterator sample_itr_;
std::vector<FrameCENCInfo> cenc_info_;
// Track the start dts of the next segment, only useful if decode_time box is // Track the start dts of the next segment, only useful if decode_time box is
// absent. // absent.
std::vector<int64_t> next_fragment_start_dts_; std::vector<int64_t> next_fragment_start_dts_;

View File

@ -11,14 +11,16 @@
#include "packager/media/formats/mp4/rcheck.h" #include "packager/media/formats/mp4/rcheck.h"
#include "packager/media/formats/mp4/track_run_iterator.h" #include "packager/media/formats/mp4/track_run_iterator.h"
namespace {
// The sum of the elements in a vector initialized with SumAscending, // The sum of the elements in a vector initialized with SumAscending,
// less the value of the last element. // less the value of the last element.
static const int kSumAscending1 = 45; const int kSumAscending1 = 45;
static const int kAudioScale = 48000; const int kAudioScale = 48000;
static const int kVideoScale = 25; const int kVideoScale = 25;
static const uint8_t kAuxInfo[] = { const uint8_t kAuxInfo[] = {
// Sample 1: IV (no subsumples). // Sample 1: IV (no subsumples).
0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31, 0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31,
// Sample 2: IV. // Sample 2: IV.
@ -30,11 +32,39 @@ static const uint8_t kAuxInfo[] = {
// Sample 2: Subsample 2. // Sample 2: Subsample 2.
0x00, 0x03, 0x00, 0x00, 0x00, 0x04}; 0x00, 0x03, 0x00, 0x00, 0x00, 0x04};
static const char kIv1[] = {0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31, }; const uint8_t kSampleEncryptionDataWithSubsamples[] = {
// Sample count.
0x00, 0x00, 0x00, 0x02,
// Sample 1: IV.
0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31,
// Sample 1: Subsample count.
0x00, 0x01,
// Sample 1: Subsample 1.
0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
// Sample 2: IV.
0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x32,
// Sample 2: Subsample count.
0x00, 0x02,
// Sample 2: Subsample 1.
0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
// Sample 2: Subsample 2.
0x00, 0x03, 0x00, 0x00, 0x00, 0x04};
static const uint8_t kKeyId[] = {0x41, 0x47, 0x6f, 0x6f, 0x67, 0x6c, const uint8_t kSampleEncryptionDataWithoutSubsamples[] = {
0x65, 0x54, 0x65, 0x73, 0x74, 0x4b, // Sample count.
0x65, 0x79, 0x49, 0x44}; 0x00, 0x00, 0x00, 0x02,
// Sample 1: IV.
0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31,
// Sample 2: IV.
0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x32};
const char kIv1[] = {0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x31};
const char kIv2[] = {0x41, 0x54, 0x65, 0x73, 0x74, 0x49, 0x76, 0x32};
const uint8_t kKeyId[] = {0x41, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x54,
0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x44};
} // namespace
namespace edash_packager { namespace edash_packager {
namespace media { namespace media {
@ -143,6 +173,38 @@ class TrackRunIteratorTest : public testing::Test {
frag->runs[0].sample_sizes[1] = 10; frag->runs[0].sample_sizes[1] = 10;
} }
void AddSampleEncryption(uint8_t use_subsample_flag, TrackFragment* frag) {
frag->sample_encryption.iv_size = 8;
frag->sample_encryption.flags = use_subsample_flag;
if (use_subsample_flag) {
frag->sample_encryption.sample_encryption_data.assign(
kSampleEncryptionDataWithSubsamples,
kSampleEncryptionDataWithSubsamples +
arraysize(kSampleEncryptionDataWithSubsamples));
} else {
frag->sample_encryption.sample_encryption_data.assign(
kSampleEncryptionDataWithoutSubsamples,
kSampleEncryptionDataWithoutSubsamples +
arraysize(kSampleEncryptionDataWithoutSubsamples));
}
// Update sample sizes and aux info header.
frag->runs.resize(1);
frag->runs[0].sample_count = 2;
frag->auxiliary_offset.offsets.push_back(0);
frag->auxiliary_size.sample_count = 2;
if (use_subsample_flag) {
// Update sample sizes to match with subsample entries above.
frag->runs[0].sample_sizes[0] = 3;
frag->runs[0].sample_sizes[1] = 10;
// Set aux info header.
frag->auxiliary_size.sample_info_sizes.push_back(16);
frag->auxiliary_size.sample_info_sizes.push_back(22);
} else {
frag->auxiliary_size.default_sample_info_size = 8;
}
}
void SetAscending(std::vector<uint32_t>* vec) { void SetAscending(std::vector<uint32_t>* vec) {
vec->resize(10); vec->resize(10);
for (size_t i = 0; i < vec->size(); i++) for (size_t i = 0; i < vec->size(); i++)
@ -303,7 +365,77 @@ TEST_F(TrackRunIteratorTest, IgnoreUnknownAuxInfoTest) {
EXPECT_FALSE(iter_->AuxInfoNeedsToBeCached()); EXPECT_FALSE(iter_->AuxInfoNeedsToBeCached());
} }
TEST_F(TrackRunIteratorTest, DecryptConfigTest) { TEST_F(TrackRunIteratorTest,
DecryptConfigTestWithSampleEncryptionAndSubsample) {
AddEncryption(&moov_.tracks[1]);
iter_.reset(new TrackRunIterator(&moov_));
MovieFragment moof = CreateFragment();
AddSampleEncryption(SampleEncryption::kUseSubsampleEncryption,
&moof.tracks[1]);
ASSERT_TRUE(iter_->Init(moof));
// The run for track 2 will be the second, which is parsed according to
// data_offset.
iter_->AdvanceRun();
EXPECT_EQ(iter_->track_id(), 2u);
EXPECT_TRUE(iter_->is_encrypted());
// No need to cache aux info as it is already available in SampleEncryption.
EXPECT_FALSE(iter_->AuxInfoNeedsToBeCached());
EXPECT_EQ(iter_->aux_info_size(), 0);
EXPECT_EQ(iter_->sample_offset(), 200);
EXPECT_EQ(iter_->GetMaxClearOffset(), moof.tracks[1].runs[0].data_offset);
scoped_ptr<DecryptConfig> config = iter_->GetDecryptConfig();
EXPECT_EQ(std::vector<uint8_t>(kKeyId, kKeyId + arraysize(kKeyId)),
config->key_id());
EXPECT_EQ(std::vector<uint8_t>(kIv1, kIv1 + arraysize(kIv1)), config->iv());
EXPECT_EQ(config->subsamples().size(), 1u);
EXPECT_EQ(config->subsamples()[0].clear_bytes, 1u);
EXPECT_EQ(config->subsamples()[0].cipher_bytes, 2u);
iter_->AdvanceSample();
config = iter_->GetDecryptConfig();
EXPECT_EQ(std::vector<uint8_t>(kIv2, kIv2 + arraysize(kIv2)), config->iv());
EXPECT_EQ(config->subsamples().size(), 2u);
EXPECT_EQ(config->subsamples()[0].clear_bytes, 1u);
EXPECT_EQ(config->subsamples()[0].cipher_bytes, 2u);
EXPECT_EQ(config->subsamples()[1].clear_bytes, 3u);
EXPECT_EQ(config->subsamples()[1].cipher_bytes, 4u);
}
TEST_F(TrackRunIteratorTest,
DecryptConfigTestWithSampleEncryptionAndNoSubsample) {
AddEncryption(&moov_.tracks[1]);
iter_.reset(new TrackRunIterator(&moov_));
MovieFragment moof = CreateFragment();
AddSampleEncryption(!SampleEncryption::kUseSubsampleEncryption,
&moof.tracks[1]);
ASSERT_TRUE(iter_->Init(moof));
// The run for track 2 will be the second, which is parsed according to
// data_offset.
iter_->AdvanceRun();
EXPECT_EQ(iter_->track_id(), 2u);
EXPECT_TRUE(iter_->is_encrypted());
// No need to cache aux info as it is already available in SampleEncryption.
EXPECT_FALSE(iter_->AuxInfoNeedsToBeCached());
EXPECT_EQ(iter_->aux_info_size(), 0);
EXPECT_EQ(iter_->sample_offset(), 200);
EXPECT_EQ(iter_->GetMaxClearOffset(), moof.tracks[1].runs[0].data_offset);
scoped_ptr<DecryptConfig> config = iter_->GetDecryptConfig();
EXPECT_EQ(std::vector<uint8_t>(kKeyId, kKeyId + arraysize(kKeyId)),
config->key_id());
EXPECT_EQ(std::vector<uint8_t>(kIv1, kIv1 + arraysize(kIv1)), config->iv());
EXPECT_EQ(config->subsamples().size(), 0u);
iter_->AdvanceSample();
config = iter_->GetDecryptConfig();
EXPECT_EQ(std::vector<uint8_t>(kIv2, kIv2 + arraysize(kIv2)), config->iv());
EXPECT_EQ(config->subsamples().size(), 0u);
}
TEST_F(TrackRunIteratorTest, DecryptConfigTestWithAuxInfo) {
AddEncryption(&moov_.tracks[1]); AddEncryption(&moov_.tracks[1]);
iter_.reset(new TrackRunIterator(&moov_)); iter_.reset(new TrackRunIterator(&moov_));
@ -316,7 +448,7 @@ TEST_F(TrackRunIteratorTest, DecryptConfigTest) {
// element in the file. // element in the file.
EXPECT_EQ(iter_->track_id(), 2u); EXPECT_EQ(iter_->track_id(), 2u);
EXPECT_TRUE(iter_->is_encrypted()); EXPECT_TRUE(iter_->is_encrypted());
EXPECT_TRUE(iter_->AuxInfoNeedsToBeCached()); ASSERT_TRUE(iter_->AuxInfoNeedsToBeCached());
EXPECT_EQ(static_cast<uint32_t>(iter_->aux_info_size()), arraysize(kAuxInfo)); EXPECT_EQ(static_cast<uint32_t>(iter_->aux_info_size()), arraysize(kAuxInfo));
EXPECT_EQ(iter_->aux_info_offset(), 50); EXPECT_EQ(iter_->aux_info_offset(), 50);
EXPECT_EQ(iter_->GetMaxClearOffset(), 50); EXPECT_EQ(iter_->GetMaxClearOffset(), 50);
@ -328,11 +460,9 @@ TEST_F(TrackRunIteratorTest, DecryptConfigTest) {
EXPECT_EQ(iter_->sample_offset(), 200); EXPECT_EQ(iter_->sample_offset(), 200);
EXPECT_EQ(iter_->GetMaxClearOffset(), moof.tracks[0].runs[0].data_offset); EXPECT_EQ(iter_->GetMaxClearOffset(), moof.tracks[0].runs[0].data_offset);
scoped_ptr<DecryptConfig> config = iter_->GetDecryptConfig(); scoped_ptr<DecryptConfig> config = iter_->GetDecryptConfig();
ASSERT_EQ(arraysize(kKeyId), config->key_id().size()); EXPECT_EQ(std::vector<uint8_t>(kKeyId, kKeyId + arraysize(kKeyId)),
EXPECT_TRUE( config->key_id());
!memcmp(kKeyId, config->key_id().data(), config->key_id().size())); EXPECT_EQ(std::vector<uint8_t>(kIv1, kIv1 + arraysize(kIv1)), config->iv());
ASSERT_EQ(arraysize(kIv1), config->iv().size());
EXPECT_TRUE(!memcmp(kIv1, config->iv().data(), config->iv().size()));
EXPECT_TRUE(config->subsamples().empty()); EXPECT_TRUE(config->subsamples().empty());
iter_->AdvanceSample(); iter_->AdvanceSample();
config = iter_->GetDecryptConfig(); config = iter_->GetDecryptConfig();

View File

@ -41,7 +41,11 @@ bear-640x360-non_square_pixel-with_pasp.mp4 - A non-square pixel version of the
bear-640x360-non_square_pixel-without_pasp.mp4 - A non-square pixel version of the video track of bear-640x360.mp4 without PixelAspectRatio box. bear-640x360-non_square_pixel-without_pasp.mp4 - A non-square pixel version of the video track of bear-640x360.mp4 without PixelAspectRatio box.
// Encrypted Files. // Encrypted Files.
bear-640x360-v_frag-cenc.mp4 - A fragmented MP4 version of the video track of bear-640x360.mp4 encrypted (ISO CENC) using key ID [1] and key [2]. bear-640x360-v_frag-cenc-aux.mp4 - A fragmented MP4 version of the video track of bear-640x360.mp4
encrypted (ISO CENC) using key ID [1] and key [2] and with sample
encryption auxiliary information in the beginning of mdat box.
bear-640x360-v_frag-cenc-senc.mp4 - Same as above, but with sample encryption information stored in
senc box.
[1] 30313233343536373839303132333435 [1] 30313233343536373839303132333435
[2] ebdd62f16814d27b68ef122afce4ae3c [2] ebdd62f16814d27b68ef122afce4ae3c