Fix cbcs/cens behavior when applied to non video tracks
- Tracks other than video are protected using whole-block full-sample encryption as specified in ISO/IEC 23001-7:2016(E) 9.7, which is equivalent to a pattern of 1:0. This is different to the non pattern encryption counterparts. - Also updated the code to allow the existence of other protection schemes in the original content, which will simply be ignored. - The internal cryptor used by AesPatternCryptor should not use constant iv, add a DCHECK for that. - Optimize AesPatternCryptor handling on the special pattern 1:0. Change-Id: Idc704e7bc6b347741336f38c6d3620fc19392960
This commit is contained in:
parent
84f3911985
commit
d08f6ae0cc
|
@ -171,7 +171,7 @@ class PackagerAppTest(unittest.TestCase):
|
||||||
self._GetStreams(['audio', 'video']),
|
self._GetStreams(['audio', 'video']),
|
||||||
self._GetFlags(encryption=True,
|
self._GetFlags(encryption=True,
|
||||||
protection_scheme='cens'))
|
protection_scheme='cens'))
|
||||||
self._DiffGold(self.output[0], 'bear-640x360-a-cenc-golden.mp4')
|
self._DiffGold(self.output[0], 'bear-640x360-a-cens-golden.mp4')
|
||||||
self._DiffGold(self.output[1], 'bear-640x360-v-cens-golden.mp4')
|
self._DiffGold(self.output[1], 'bear-640x360-v-cens-golden.mp4')
|
||||||
self._DiffGold(self.mpd_output, 'bear-640x360-av-cens-golden.mpd')
|
self._DiffGold(self.mpd_output, 'bear-640x360-av-cens-golden.mpd')
|
||||||
self._VerifyDecryption(self.output[0], 'bear-640x360-a-golden.mp4')
|
self._VerifyDecryption(self.output[0], 'bear-640x360-a-golden.mp4')
|
||||||
|
@ -182,7 +182,7 @@ class PackagerAppTest(unittest.TestCase):
|
||||||
self._GetStreams(['audio', 'video']),
|
self._GetStreams(['audio', 'video']),
|
||||||
self._GetFlags(encryption=True,
|
self._GetFlags(encryption=True,
|
||||||
protection_scheme='cbcs'))
|
protection_scheme='cbcs'))
|
||||||
self._DiffGold(self.output[0], 'bear-640x360-a-cbc1-golden.mp4')
|
self._DiffGold(self.output[0], 'bear-640x360-a-cbcs-golden.mp4')
|
||||||
self._DiffGold(self.output[1], 'bear-640x360-v-cbcs-golden.mp4')
|
self._DiffGold(self.output[1], 'bear-640x360-v-cbcs-golden.mp4')
|
||||||
self._DiffGold(self.mpd_output, 'bear-640x360-av-cbcs-golden.mpd')
|
self._DiffGold(self.mpd_output, 'bear-640x360-av-cbcs-golden.mpd')
|
||||||
self._VerifyDecryption(self.output[0], 'bear-640x360-a-golden.mp4')
|
self._VerifyDecryption(self.output[0], 'bear-640x360-a-golden.mp4')
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -15,15 +15,15 @@
|
||||||
</Representation>
|
</Representation>
|
||||||
</AdaptationSet>
|
</AdaptationSet>
|
||||||
<AdaptationSet id="1" contentType="audio" subsegmentAlignment="true">
|
<AdaptationSet id="1" contentType="audio" subsegmentAlignment="true">
|
||||||
<Representation id="1" bandwidth="129127" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
<Representation id="1" bandwidth="127202" 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="cbc1" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/>
|
<ContentProtection value="cbcs" 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>
|
||||||
</ContentProtection>
|
</ContentProtection>
|
||||||
<BaseURL>output_audio.mp4</BaseURL>
|
<BaseURL>output_audio.mp4</BaseURL>
|
||||||
<SegmentBase indexRange="955-1022" timescale="44100">
|
<SegmentBase indexRange="964-1031" timescale="44100">
|
||||||
<Initialization range="0-954"/>
|
<Initialization range="0-963"/>
|
||||||
</SegmentBase>
|
</SegmentBase>
|
||||||
</Representation>
|
</Representation>
|
||||||
</AdaptationSet>
|
</AdaptationSet>
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<AdaptationSet id="1" contentType="audio" subsegmentAlignment="true">
|
<AdaptationSet id="1" contentType="audio" subsegmentAlignment="true">
|
||||||
<Representation id="1" bandwidth="129127" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
<Representation id="1" bandwidth="129127" 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="cens" 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>
|
||||||
</ContentProtection>
|
</ContentProtection>
|
||||||
|
|
|
@ -58,11 +58,7 @@ DEFINE_int32(crypto_period_duration,
|
||||||
DEFINE_string(protection_scheme,
|
DEFINE_string(protection_scheme,
|
||||||
"cenc",
|
"cenc",
|
||||||
"Choose protection scheme, 'cenc' or 'cbc1' or pattern-based "
|
"Choose protection scheme, 'cenc' or 'cbc1' or pattern-based "
|
||||||
"protection schemes 'cens' or 'cbcs'. Note that if a "
|
"protection schemes 'cens' or 'cbcs'.");
|
||||||
"pattern-based protection scheme only applies to video stream; "
|
|
||||||
"audio stream will be encrypted using the corresponding "
|
|
||||||
"non-pattern-based protection schemes, i.e. 'cenc' for 'cens', "
|
|
||||||
"'cbc1' for 'cbcs'.");
|
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,9 @@ class AesCryptor {
|
||||||
/// @return The current iv.
|
/// @return The current iv.
|
||||||
const std::vector<uint8_t>& iv() const { return iv_; }
|
const std::vector<uint8_t>& iv() const { return iv_; }
|
||||||
|
|
||||||
|
/// @return true if constant iv is used, false otherwise.
|
||||||
|
bool use_constant_iv() const { return constant_iv_flag_ == kUseConstantIv; }
|
||||||
|
|
||||||
/// @param protection_scheme specifies the protection scheme: 'cenc', 'cens',
|
/// @param protection_scheme specifies the protection scheme: 'cenc', 'cens',
|
||||||
/// 'cbc1', 'cbcs', which is useful to determine the random iv size.
|
/// 'cbc1', 'cbcs', which is useful to determine the random iv size.
|
||||||
/// @param iv points to generated initialization vector.
|
/// @param iv points to generated initialization vector.
|
||||||
|
|
|
@ -23,7 +23,13 @@ AesPatternCryptor::AesPatternCryptor(uint8_t crypt_byte_block,
|
||||||
skip_byte_block_(skip_byte_block),
|
skip_byte_block_(skip_byte_block),
|
||||||
encryption_mode_(encryption_mode),
|
encryption_mode_(encryption_mode),
|
||||||
cryptor_(cryptor.Pass()) {
|
cryptor_(cryptor.Pass()) {
|
||||||
|
// |crypt_byte_block_| should never be 0. |skip_byte_block_| can be 0 to allow
|
||||||
|
// a special pattern of 1:0, which is the pattern for the case of pattern
|
||||||
|
// encryption when applied to non video tracks.
|
||||||
|
DCHECK_NE(crypt_byte_block_, 0u);
|
||||||
|
DCHECK(skip_byte_block_ != 0 || crypt_byte_block_ == 1);
|
||||||
DCHECK(cryptor_);
|
DCHECK(cryptor_);
|
||||||
|
DCHECK(!cryptor_->use_constant_iv());
|
||||||
}
|
}
|
||||||
|
|
||||||
AesPatternCryptor::~AesPatternCryptor() {}
|
AesPatternCryptor::~AesPatternCryptor() {}
|
||||||
|
@ -45,6 +51,17 @@ bool AesPatternCryptor::CryptInternal(const uint8_t* text,
|
||||||
}
|
}
|
||||||
*crypt_text_size = text_size;
|
*crypt_text_size = text_size;
|
||||||
|
|
||||||
|
// Handle the special pattern 1:0.
|
||||||
|
if (skip_byte_block_ == 0) {
|
||||||
|
DCHECK_EQ(crypt_byte_block_, 1u);
|
||||||
|
const size_t crypt_byte_size = text_size / AES_BLOCK_SIZE * AES_BLOCK_SIZE;
|
||||||
|
if (!cryptor_->Crypt(text, crypt_byte_size, crypt_text))
|
||||||
|
return false;
|
||||||
|
memcpy(crypt_text + crypt_byte_size, text + crypt_byte_size,
|
||||||
|
text_size - crypt_byte_size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
while (text_size > 0) {
|
while (text_size > 0) {
|
||||||
const size_t crypt_byte_size = crypt_byte_block_ * AES_BLOCK_SIZE;
|
const size_t crypt_byte_size = crypt_byte_block_ * AES_BLOCK_SIZE;
|
||||||
if (NeedEncrypt(text_size, crypt_byte_size)) {
|
if (NeedEncrypt(text_size, crypt_byte_size)) {
|
||||||
|
|
|
@ -47,7 +47,8 @@ class AesPatternCryptor : public AesCryptor {
|
||||||
/// internally inside Crypt call, i.e. iv will be updated across Crypt
|
/// internally inside Crypt call, i.e. iv will be updated across Crypt
|
||||||
/// calls.
|
/// calls.
|
||||||
/// @param cryptor points to an AesCryptor instance which performs the actual
|
/// @param cryptor points to an AesCryptor instance which performs the actual
|
||||||
/// encryption/decryption.
|
/// encryption/decryption. Note that @a cryptor shall not use constant
|
||||||
|
/// iv.
|
||||||
AesPatternCryptor(uint8_t crypt_byte_block,
|
AesPatternCryptor(uint8_t crypt_byte_block,
|
||||||
uint8_t skip_byte_block,
|
uint8_t skip_byte_block,
|
||||||
PatternEncryptionMode encryption_mode,
|
PatternEncryptionMode encryption_mode,
|
||||||
|
|
|
@ -219,5 +219,35 @@ TEST(SampleAesPatternCryptor, MoreThan16Bytes) {
|
||||||
ASSERT_TRUE(pattern_cryptor.Crypt("0123456789abcdef012", &crypt_text));
|
ASSERT_TRUE(pattern_cryptor.Crypt("0123456789abcdef012", &crypt_text));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(FullSampleSpecialPatternTest, Test) {
|
||||||
|
MockAesCryptor* mock_cryptor = new MockAesCryptor();
|
||||||
|
EXPECT_CALL(*mock_cryptor, CryptInternal(_, 96u, _, _))
|
||||||
|
.WillOnce(Invoke([](const uint8_t* text, size_t text_size,
|
||||||
|
uint8_t* crypt_text, size_t* crypt_text_size) {
|
||||||
|
*crypt_text_size = text_size;
|
||||||
|
for (size_t i = 0; i < text_size; ++i)
|
||||||
|
*crypt_text++ = 'e';
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
|
|
||||||
|
const uint8_t kFulLSampleCryptBlock = 1;
|
||||||
|
const uint8_t kFullSampleSkipBlock = 0;
|
||||||
|
AesPatternCryptor pattern_cryptor(
|
||||||
|
kFulLSampleCryptBlock, kFullSampleSkipBlock,
|
||||||
|
AesPatternCryptor::kSkipIfCryptByteBlockRemaining,
|
||||||
|
AesPatternCryptor::kUseConstantIv,
|
||||||
|
scoped_ptr<MockAesCryptor>(mock_cryptor));
|
||||||
|
|
||||||
|
std::vector<uint8_t> iv(8, 'i');
|
||||||
|
// SetIv will be called only once by AesPatternCryptor::SetIv.
|
||||||
|
EXPECT_TRUE(pattern_cryptor.SetIv(iv));
|
||||||
|
|
||||||
|
std::string input_text(100, 'c');
|
||||||
|
std::string crypt_text;
|
||||||
|
// More than 16 bytes so mock's CryptInternal should be called.
|
||||||
|
ASSERT_TRUE(pattern_cryptor.Crypt(input_text, &crypt_text));
|
||||||
|
EXPECT_EQ(std::string(96, 'e') + std::string(4, 'c'), crypt_text);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -126,6 +126,11 @@ FourCC TrackTypeToFourCC(TrackType track_type) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsProtectionSchemeSupported(FourCC scheme) {
|
||||||
|
return scheme == FOURCC_cenc || scheme == FOURCC_cens ||
|
||||||
|
scheme == FOURCC_cbc1 || scheme == FOURCC_cbcs;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
FileType::FileType() : major_brand(FOURCC_NULL), minor_version(0) {}
|
FileType::FileType() : major_brand(FOURCC_NULL), minor_version(0) {}
|
||||||
|
@ -483,9 +488,12 @@ bool ProtectionSchemeInfo::ReadWriteInternal(BoxBuffer* buffer) {
|
||||||
buffer->PrepareChildren() &&
|
buffer->PrepareChildren() &&
|
||||||
buffer->ReadWriteChild(&format) &&
|
buffer->ReadWriteChild(&format) &&
|
||||||
buffer->ReadWriteChild(&type));
|
buffer->ReadWriteChild(&type));
|
||||||
RCHECK(type.type == FOURCC_cenc || type.type == FOURCC_cbc1 ||
|
if (IsProtectionSchemeSupported(type.type)) {
|
||||||
type.type == FOURCC_cens || type.type == FOURCC_cbcs);
|
RCHECK(buffer->ReadWriteChild(&info));
|
||||||
RCHECK(buffer->ReadWriteChild(&info));
|
} else {
|
||||||
|
DLOG(WARNING) << "Ignore unsupported protection scheme: "
|
||||||
|
<< FourCCToString(type.type);
|
||||||
|
}
|
||||||
// Other protection schemes are silently ignored. Since the protection scheme
|
// Other protection schemes are silently ignored. Since the protection scheme
|
||||||
// type can't be determined until this box is opened, we return 'true' for
|
// type can't be determined until this box is opened, we return 'true' for
|
||||||
// non-CENC protection scheme types. It is the parent box's responsibility to
|
// non-CENC protection scheme types. It is the parent box's responsibility to
|
||||||
|
@ -1001,8 +1009,8 @@ bool SampleGroupDescription::ReadWriteInternal(BoxBuffer* buffer) {
|
||||||
return ReadWriteEntries(buffer, &audio_roll_recovery_entries);
|
return ReadWriteEntries(buffer, &audio_roll_recovery_entries);
|
||||||
default:
|
default:
|
||||||
DCHECK(buffer->Reading());
|
DCHECK(buffer->Reading());
|
||||||
DLOG(WARNING) << "Sample group '" << grouping_type
|
DLOG(WARNING) << "Ignore unsupported sample group: "
|
||||||
<< "' is not supported.";
|
<< FourCCToString(static_cast<FourCC>(grouping_type));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1077,9 +1085,8 @@ bool SampleToGroup::ReadWriteInternal(BoxBuffer* buffer) {
|
||||||
|
|
||||||
if (grouping_type != FOURCC_seig && grouping_type != FOURCC_roll) {
|
if (grouping_type != FOURCC_seig && grouping_type != FOURCC_roll) {
|
||||||
DCHECK(buffer->Reading());
|
DCHECK(buffer->Reading());
|
||||||
DLOG(WARNING) << "Sample group "
|
DLOG(WARNING) << "Ignore unsupported sample group: "
|
||||||
<< FourCCToString(static_cast<FourCC>(grouping_type))
|
<< FourCCToString(static_cast<FourCC>(grouping_type));
|
||||||
<< " is not supported.";
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1538,8 +1545,17 @@ bool VideoSampleEntry::ReadWriteInternal(BoxBuffer* buffer) {
|
||||||
|
|
||||||
RCHECK(buffer->PrepareChildren());
|
RCHECK(buffer->PrepareChildren());
|
||||||
|
|
||||||
if (format == FOURCC_encv)
|
if (format == FOURCC_encv) {
|
||||||
RCHECK(buffer->ReadWriteChild(&sinf));
|
if (buffer->Reading()) {
|
||||||
|
// Continue scanning until a supported protection scheme is found, or
|
||||||
|
// until we run out of protection schemes.
|
||||||
|
while (!IsProtectionSchemeSupported(sinf.type.type))
|
||||||
|
RCHECK(buffer->ReadWriteChild(&sinf));
|
||||||
|
} else {
|
||||||
|
DCHECK(IsProtectionSchemeSupported(sinf.type.type));
|
||||||
|
RCHECK(buffer->ReadWriteChild(&sinf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const FourCC actual_format = GetActualFormat();
|
const FourCC actual_format = GetActualFormat();
|
||||||
if (buffer->Reading()) {
|
if (buffer->Reading()) {
|
||||||
|
@ -1777,8 +1793,17 @@ bool AudioSampleEntry::ReadWriteInternal(BoxBuffer* buffer) {
|
||||||
samplerate >>= 16;
|
samplerate >>= 16;
|
||||||
|
|
||||||
RCHECK(buffer->PrepareChildren());
|
RCHECK(buffer->PrepareChildren());
|
||||||
if (format == FOURCC_enca)
|
if (format == FOURCC_enca) {
|
||||||
RCHECK(buffer->ReadWriteChild(&sinf));
|
if (buffer->Reading()) {
|
||||||
|
// Continue scanning until a supported protection scheme is found, or
|
||||||
|
// until we run out of protection schemes.
|
||||||
|
while (!IsProtectionSchemeSupported(sinf.type.type))
|
||||||
|
RCHECK(buffer->ReadWriteChild(&sinf));
|
||||||
|
} else {
|
||||||
|
DCHECK(IsProtectionSchemeSupported(sinf.type.type));
|
||||||
|
RCHECK(buffer->ReadWriteChild(&sinf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RCHECK(buffer->TryReadWriteChild(&esds));
|
RCHECK(buffer->TryReadWriteChild(&esds));
|
||||||
RCHECK(buffer->TryReadWriteChild(&ddts));
|
RCHECK(buffer->TryReadWriteChild(&ddts));
|
||||||
|
|
|
@ -186,9 +186,18 @@ void EncryptingFragmenter::FinalizeFragmentForEncryption() {
|
||||||
DCHECK(!IsSubsampleEncryptionRequired());
|
DCHECK(!IsSubsampleEncryptionRequired());
|
||||||
saiz.default_sample_info_size = per_sample_iv_size;
|
saiz.default_sample_info_size = per_sample_iv_size;
|
||||||
}
|
}
|
||||||
// It should only happen with full sample encryption + constant iv, which is
|
|
||||||
// not a legal combination.
|
// It should only happen with full sample encryption + constant iv, i.e.
|
||||||
CHECK(!saiz.sample_info_sizes.empty() || saiz.default_sample_info_size != 0);
|
// 'cbcs' applying to audio.
|
||||||
|
if (saiz.default_sample_info_size == 0 && saiz.sample_info_sizes.empty()) {
|
||||||
|
DCHECK_EQ(protection_scheme_, FOURCC_cbcs);
|
||||||
|
DCHECK(!IsSubsampleEncryptionRequired());
|
||||||
|
// ISO/IEC 23001-7:2016(E) The sample auxiliary information would then be
|
||||||
|
// empty and should be emitted. Clear saiz and saio boxes so they are not
|
||||||
|
// written.
|
||||||
|
saiz.sample_count = 0;
|
||||||
|
traf()->auxiliary_offset.offsets.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Status EncryptingFragmenter::CreateEncryptor() {
|
Status EncryptingFragmenter::CreateEncryptor() {
|
||||||
|
@ -337,6 +346,8 @@ Status EncryptingFragmenter::EncryptSample(scoped_refptr<MediaSample> sample) {
|
||||||
traf()->auxiliary_size.sample_info_sizes.push_back(
|
traf()->auxiliary_size.sample_info_sizes.push_back(
|
||||||
sample_encryption_entry.ComputeSize());
|
sample_encryption_entry.ComputeSize());
|
||||||
} else {
|
} else {
|
||||||
|
DCHECK_LE(crypt_byte_block(), 1u);
|
||||||
|
DCHECK_EQ(skip_byte_block(), 0u);
|
||||||
EncryptBytes(data, sample->data_size());
|
EncryptBytes(data, sample->data_size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,10 +28,6 @@ namespace media {
|
||||||
namespace mp4 {
|
namespace mp4 {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// For pattern-based encryption.
|
|
||||||
const uint8_t kCryptByteBlock = 1u;
|
|
||||||
const uint8_t kSkipByteBlock = 9u;
|
|
||||||
|
|
||||||
const size_t kCencKeyIdSize = 16u;
|
const size_t kCencKeyIdSize = 16u;
|
||||||
|
|
||||||
// The version of cenc implemented here. CENC 4.
|
// The version of cenc implemented here. CENC 4.
|
||||||
|
@ -43,6 +39,12 @@ const uint8_t kKeyRotationDefaultKeyId[] = {
|
||||||
0, 0, 0, 0, 0, 0, 0, 0
|
0, 0, 0, 0, 0, 0, 0, 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Defines protection pattern for pattern-based encryption.
|
||||||
|
struct ProtectionPattern {
|
||||||
|
uint8_t crypt_byte_block;
|
||||||
|
uint8_t skip_byte_block;
|
||||||
|
};
|
||||||
|
|
||||||
COMPILE_ASSERT(arraysize(kKeyRotationDefaultKeyId) == kCencKeyIdSize,
|
COMPILE_ASSERT(arraysize(kKeyRotationDefaultKeyId) == kCencKeyIdSize,
|
||||||
cenc_key_id_must_be_size_16);
|
cenc_key_id_must_be_size_16);
|
||||||
|
|
||||||
|
@ -52,21 +54,37 @@ uint64_t Rescale(uint64_t time_in_old_scale,
|
||||||
return static_cast<double>(time_in_old_scale) / old_scale * new_scale;
|
return static_cast<double>(time_in_old_scale) / old_scale * new_scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t GetCryptByteBlock(FourCC protection_scheme) {
|
ProtectionPattern GetProtectionPattern(FourCC protection_scheme,
|
||||||
return (protection_scheme == FOURCC_cbcs || protection_scheme == FOURCC_cens)
|
TrackType track_type) {
|
||||||
? kCryptByteBlock
|
ProtectionPattern pattern;
|
||||||
: 0;
|
if (protection_scheme != FOURCC_cbcs && protection_scheme != FOURCC_cens) {
|
||||||
}
|
// Not using pattern encryption.
|
||||||
|
pattern.crypt_byte_block = 0u;
|
||||||
uint8_t GetSkipByteBlock(FourCC protection_scheme) {
|
pattern.skip_byte_block = 0u;
|
||||||
return (protection_scheme == FOURCC_cbcs || protection_scheme == FOURCC_cens)
|
} else if (track_type != kVideo) {
|
||||||
? kSkipByteBlock
|
// Tracks other than video are protected using whole-block full-sample
|
||||||
: 0;
|
// encryption, which is essentially a pattern of 1:0. Note that this may not
|
||||||
|
// be the same as the non-pattern based encryption counterparts, e.g. in
|
||||||
|
// 'cens' for full sample encryption, the whole sample is encrypted up to
|
||||||
|
// the last 16-byte boundary, see 23001-7:2016(E) 9.7; while in 'cenc' for
|
||||||
|
// full sample encryption, the last partial 16-byte block is also encrypted,
|
||||||
|
// see 23001-7:2016(E) 9.4.2. Another difference is the use of constant iv.
|
||||||
|
pattern.crypt_byte_block = 1u;
|
||||||
|
pattern.skip_byte_block = 0u;
|
||||||
|
} else {
|
||||||
|
// Use 1:9 pattern for video.
|
||||||
|
const uint8_t kCryptByteBlock = 1u;
|
||||||
|
const uint8_t kSkipByteBlock = 9u;
|
||||||
|
pattern.crypt_byte_block = kCryptByteBlock;
|
||||||
|
pattern.skip_byte_block = kSkipByteBlock;
|
||||||
|
}
|
||||||
|
return pattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenerateSinf(const EncryptionKey& encryption_key,
|
void GenerateSinf(const EncryptionKey& encryption_key,
|
||||||
FourCC old_type,
|
FourCC old_type,
|
||||||
FourCC protection_scheme,
|
FourCC protection_scheme,
|
||||||
|
ProtectionPattern pattern,
|
||||||
ProtectionSchemeInfo* sinf) {
|
ProtectionSchemeInfo* sinf) {
|
||||||
sinf->format.format = old_type;
|
sinf->format.format = old_type;
|
||||||
|
|
||||||
|
@ -85,16 +103,15 @@ void GenerateSinf(const EncryptionKey& encryption_key,
|
||||||
} else {
|
} else {
|
||||||
track_encryption.default_per_sample_iv_size = encryption_key.iv.size();
|
track_encryption.default_per_sample_iv_size = encryption_key.iv.size();
|
||||||
}
|
}
|
||||||
track_encryption.default_crypt_byte_block =
|
track_encryption.default_crypt_byte_block = pattern.crypt_byte_block;
|
||||||
GetCryptByteBlock(protection_scheme);
|
track_encryption.default_skip_byte_block = pattern.skip_byte_block;
|
||||||
track_encryption.default_skip_byte_block =
|
|
||||||
GetSkipByteBlock(protection_scheme);
|
|
||||||
track_encryption.default_kid = encryption_key.key_id;
|
track_encryption.default_kid = encryption_key.key_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenerateEncryptedSampleEntry(const EncryptionKey& encryption_key,
|
void GenerateEncryptedSampleEntry(const EncryptionKey& encryption_key,
|
||||||
double clear_lead_in_seconds,
|
double clear_lead_in_seconds,
|
||||||
FourCC protection_scheme,
|
FourCC protection_scheme,
|
||||||
|
ProtectionPattern pattern,
|
||||||
SampleDescription* description) {
|
SampleDescription* description) {
|
||||||
DCHECK(description);
|
DCHECK(description);
|
||||||
if (description->type == kVideo) {
|
if (description->type == kVideo) {
|
||||||
|
@ -106,7 +123,8 @@ void GenerateEncryptedSampleEntry(const EncryptionKey& encryption_key,
|
||||||
|
|
||||||
// Convert the first entry to an encrypted entry.
|
// Convert the first entry to an encrypted entry.
|
||||||
VideoSampleEntry& entry = description->video_entries[0];
|
VideoSampleEntry& entry = description->video_entries[0];
|
||||||
GenerateSinf(encryption_key, entry.format, protection_scheme, &entry.sinf);
|
GenerateSinf(encryption_key, entry.format, protection_scheme, pattern,
|
||||||
|
&entry.sinf);
|
||||||
entry.format = FOURCC_encv;
|
entry.format = FOURCC_encv;
|
||||||
} else {
|
} else {
|
||||||
DCHECK_EQ(kAudio, description->type);
|
DCHECK_EQ(kAudio, description->type);
|
||||||
|
@ -118,7 +136,8 @@ void GenerateEncryptedSampleEntry(const EncryptionKey& encryption_key,
|
||||||
|
|
||||||
// Convert the first entry to an encrypted entry.
|
// Convert the first entry to an encrypted entry.
|
||||||
AudioSampleEntry& entry = description->audio_entries[0];
|
AudioSampleEntry& entry = description->audio_entries[0];
|
||||||
GenerateSinf(encryption_key, entry.format, protection_scheme, &entry.sinf);
|
GenerateSinf(encryption_key, entry.format, protection_scheme, pattern,
|
||||||
|
&entry.sinf);
|
||||||
entry.format = FOURCC_enca;
|
entry.format = FOURCC_enca;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,20 +193,12 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
FourCC local_protection_scheme = protection_scheme;
|
|
||||||
if (streams[i]->info()->stream_type() != kStreamVideo) {
|
|
||||||
// Pattern encryption should only be used with video. Replaces with the
|
|
||||||
// corresponding non-pattern encryption scheme.
|
|
||||||
if (protection_scheme == FOURCC_cbcs)
|
|
||||||
local_protection_scheme = FOURCC_cbc1;
|
|
||||||
else if (protection_scheme == FOURCC_cens)
|
|
||||||
local_protection_scheme = FOURCC_cenc;
|
|
||||||
}
|
|
||||||
|
|
||||||
KeySource::TrackType track_type =
|
KeySource::TrackType track_type =
|
||||||
GetTrackTypeForEncryption(*streams[i]->info(), max_sd_pixels);
|
GetTrackTypeForEncryption(*streams[i]->info(), max_sd_pixels);
|
||||||
SampleDescription& description =
|
SampleDescription& description =
|
||||||
moov_->tracks[i].media.information.sample_table.description;
|
moov_->tracks[i].media.information.sample_table.description;
|
||||||
|
ProtectionPattern pattern =
|
||||||
|
GetProtectionPattern(protection_scheme, description.type);
|
||||||
|
|
||||||
if (key_rotation_enabled) {
|
if (key_rotation_enabled) {
|
||||||
// Fill encrypted sample entry with default key.
|
// Fill encrypted sample entry with default key.
|
||||||
|
@ -195,17 +206,16 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
|
||||||
encryption_key.key_id.assign(
|
encryption_key.key_id.assign(
|
||||||
kKeyRotationDefaultKeyId,
|
kKeyRotationDefaultKeyId,
|
||||||
kKeyRotationDefaultKeyId + arraysize(kKeyRotationDefaultKeyId));
|
kKeyRotationDefaultKeyId + arraysize(kKeyRotationDefaultKeyId));
|
||||||
if (!AesCryptor::GenerateRandomIv(local_protection_scheme,
|
if (!AesCryptor::GenerateRandomIv(protection_scheme,
|
||||||
&encryption_key.iv)) {
|
&encryption_key.iv)) {
|
||||||
return Status(error::INTERNAL_ERROR, "Failed to generate random iv.");
|
return Status(error::INTERNAL_ERROR, "Failed to generate random iv.");
|
||||||
}
|
}
|
||||||
GenerateEncryptedSampleEntry(encryption_key, clear_lead_in_seconds,
|
GenerateEncryptedSampleEntry(encryption_key, clear_lead_in_seconds,
|
||||||
local_protection_scheme, &description);
|
protection_scheme, pattern, &description);
|
||||||
if (muxer_listener_) {
|
if (muxer_listener_) {
|
||||||
muxer_listener_->OnEncryptionInfoReady(
|
muxer_listener_->OnEncryptionInfoReady(
|
||||||
kInitialEncryptionInfo, local_protection_scheme,
|
kInitialEncryptionInfo, protection_scheme, encryption_key.key_id,
|
||||||
encryption_key.key_id, encryption_key.iv,
|
encryption_key.iv, encryption_key.key_system_info);
|
||||||
encryption_key.key_system_info);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fragmenters_[i] = new KeyRotationFragmenter(
|
fragmenters_[i] = new KeyRotationFragmenter(
|
||||||
|
@ -213,8 +223,8 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
|
||||||
encryption_key_source, track_type,
|
encryption_key_source, track_type,
|
||||||
crypto_period_duration_in_seconds * streams[i]->info()->time_scale(),
|
crypto_period_duration_in_seconds * streams[i]->info()->time_scale(),
|
||||||
clear_lead_in_seconds * streams[i]->info()->time_scale(),
|
clear_lead_in_seconds * streams[i]->info()->time_scale(),
|
||||||
local_protection_scheme, GetCryptByteBlock(local_protection_scheme),
|
protection_scheme, pattern.crypt_byte_block, pattern.skip_byte_block,
|
||||||
GetSkipByteBlock(local_protection_scheme), muxer_listener_);
|
muxer_listener_);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,14 +234,14 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
|
||||||
if (!status.ok())
|
if (!status.ok())
|
||||||
return status;
|
return status;
|
||||||
if (encryption_key->iv.empty()) {
|
if (encryption_key->iv.empty()) {
|
||||||
if (!AesCryptor::GenerateRandomIv(local_protection_scheme,
|
if (!AesCryptor::GenerateRandomIv(protection_scheme,
|
||||||
&encryption_key->iv)) {
|
&encryption_key->iv)) {
|
||||||
return Status(error::INTERNAL_ERROR, "Failed to generate random iv.");
|
return Status(error::INTERNAL_ERROR, "Failed to generate random iv.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GenerateEncryptedSampleEntry(*encryption_key, clear_lead_in_seconds,
|
GenerateEncryptedSampleEntry(*encryption_key, clear_lead_in_seconds,
|
||||||
local_protection_scheme, &description);
|
protection_scheme, pattern, &description);
|
||||||
|
|
||||||
if (moov_->pssh.empty()) {
|
if (moov_->pssh.empty()) {
|
||||||
moov_->pssh.resize(encryption_key->key_system_info.size());
|
moov_->pssh.resize(encryption_key->key_system_info.size());
|
||||||
|
@ -241,17 +251,15 @@ Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
|
||||||
|
|
||||||
if (muxer_listener_) {
|
if (muxer_listener_) {
|
||||||
muxer_listener_->OnEncryptionInfoReady(
|
muxer_listener_->OnEncryptionInfoReady(
|
||||||
kInitialEncryptionInfo, local_protection_scheme,
|
kInitialEncryptionInfo, protection_scheme, encryption_key->key_id,
|
||||||
encryption_key->key_id, encryption_key->iv,
|
encryption_key->iv, encryption_key->key_system_info);
|
||||||
encryption_key->key_system_info);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fragmenters_[i] = new EncryptingFragmenter(
|
fragmenters_[i] = new EncryptingFragmenter(
|
||||||
streams[i]->info(), &moof_->tracks[i], encryption_key.Pass(),
|
streams[i]->info(), &moof_->tracks[i], encryption_key.Pass(),
|
||||||
clear_lead_in_seconds * streams[i]->info()->time_scale(),
|
clear_lead_in_seconds * streams[i]->info()->time_scale(),
|
||||||
local_protection_scheme, GetCryptByteBlock(local_protection_scheme),
|
protection_scheme, pattern.crypt_byte_block, pattern.skip_byte_block);
|
||||||
GetSkipByteBlock(local_protection_scheme));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Choose the first stream if there is no VIDEO.
|
// Choose the first stream if there is no VIDEO.
|
||||||
|
|
Loading…
Reference in New Issue