Make HLS with key rotation work
- Remove the TS key rotation not supported check as there is nothing to be done for key rotation in TS for SAMPLE-AES. - Fix IV updated problem in new segments even if crypto period does not change. - Avoid duplicate EXT-X-KEY tags if it does not change. - Make EXT-X-DISCONTINUITY-SEQUENCE match with number of removed EXT-X-DISCONTINUITY. - Added end to end test for HLS with key rotation. Change-Id: I73cb82e9f5575fcdf63ee643228efe78e6766302
This commit is contained in:
parent
5bad2fbd5c
commit
e56d1faaf0
|
@ -461,6 +461,34 @@ class PackagerFunctionalTest(PackagerAppTest):
|
|||
os.path.join(self.tmp_dir, 'video.m3u8'),
|
||||
'bear-640x360-v-live-golden.m3u8')
|
||||
|
||||
def testPackageAvcTsLivePlaylistWithKeyRotation(self):
|
||||
self.packager.Package(
|
||||
self._GetStreams(
|
||||
['audio', 'video'],
|
||||
output_format='ts',
|
||||
live=True,
|
||||
test_files=['bear-640x360.ts']),
|
||||
self._GetFlags(
|
||||
encryption=True,
|
||||
key_rotation=True,
|
||||
output_hls=True,
|
||||
hls_playlist_type='LIVE',
|
||||
time_shift_buffer_depth=0.5))
|
||||
self._DiffLiveGold(self.output[0],
|
||||
'bear-640x360-a-enc-rotation-golden',
|
||||
output_format='ts')
|
||||
self._DiffLiveGold(self.output[1],
|
||||
'bear-640x360-v-enc-rotation-golden',
|
||||
output_format='ts')
|
||||
self._DiffGold(self.hls_master_playlist_output,
|
||||
'bear-640x360-av-master-golden.m3u8')
|
||||
self._DiffGold(
|
||||
os.path.join(self.tmp_dir, 'audio.m3u8'),
|
||||
'bear-640x360-a-live-enc-rotation-golden.m3u8')
|
||||
self._DiffGold(
|
||||
os.path.join(self.tmp_dir, 'video.m3u8'),
|
||||
'bear-640x360-v-live-enc-rotation-golden.m3u8')
|
||||
|
||||
def testPackageAvcTsEventPlaylist(self):
|
||||
self.assertPackageSuccess(
|
||||
self._GetStreams(
|
||||
|
@ -664,7 +692,7 @@ class PackagerFunctionalTest(PackagerAppTest):
|
|||
'bear-640x360-v-enc-golden',
|
||||
output_format='ts')
|
||||
self._DiffGold(self.hls_master_playlist_output,
|
||||
'bear-640x360-av-enc-master-golden.m3u8')
|
||||
'bear-640x360-av-master-golden.m3u8')
|
||||
self._DiffGold(
|
||||
os.path.join(self.tmp_dir, 'audio.m3u8'),
|
||||
'bear-640x360-a-enc-golden.m3u8')
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,11 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:6
|
||||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||
#EXT-X-TARGETDURATION:2
|
||||
#EXT-X-MEDIA-SEQUENCE:1
|
||||
#EXTINF:0.998,
|
||||
output_audio-2.ts
|
||||
#EXT-X-DISCONTINUITY
|
||||
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MzQ1Njc4OTAxMjM0NTYxMg==",IV=0x3334353637383930,KEYFORMAT="identity"
|
||||
#EXTINF:0.789,
|
||||
output_audio-3.ts
|
|
@ -1,5 +0,0 @@
|
|||
#EXTM3U
|
||||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="stream_0",URI="audio.m3u8"
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=1217518,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,AUDIO="audio"
|
||||
video.m3u8
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,12 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:6
|
||||
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||
#EXT-X-TARGETDURATION:2
|
||||
#EXT-X-MEDIA-SEQUENCE:1
|
||||
#EXT-X-DISCONTINUITY-SEQUENCE:1
|
||||
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MjM0NTY3ODkwMTIzNDU2MQ==",IV=0x3334353637383930,KEYFORMAT="identity"
|
||||
#EXTINF:1.001,
|
||||
output_video-2.ts
|
||||
#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MzQ1Njc4OTAxMjM0NTYxMg==",IV=0x3334353637383930,KEYFORMAT="identity"
|
||||
#EXTINF:0.734,
|
||||
output_video-3.ts
|
|
@ -36,7 +36,7 @@ uint32_t GetTimeScale(const MediaInfo& media_info) {
|
|||
std::string CreatePlaylistHeader(const std::string& init_segment_name,
|
||||
uint32_t target_duration,
|
||||
MediaPlaylist::MediaPlaylistType type,
|
||||
int sequence_number,
|
||||
int media_sequence_number,
|
||||
int discontinuity_sequence_number) {
|
||||
const std::string version = GetPackagerVersion();
|
||||
std::string version_line;
|
||||
|
@ -62,9 +62,9 @@ std::string CreatePlaylistHeader(const std::string& init_segment_name,
|
|||
header += "#EXT-X-PLAYLIST-TYPE:EVENT\n";
|
||||
break;
|
||||
case MediaPlaylist::MediaPlaylistType::kLive:
|
||||
if (sequence_number > 0) {
|
||||
if (media_sequence_number > 0) {
|
||||
base::StringAppendF(&header, "#EXT-X-MEDIA-SEQUENCE:%d\n",
|
||||
sequence_number);
|
||||
media_sequence_number);
|
||||
}
|
||||
if (discontinuity_sequence_number > 0) {
|
||||
base::StringAppendF(&header, "#EXT-X-DISCONTINUITY-SEQUENCE:%d\n",
|
||||
|
@ -186,6 +186,27 @@ std::string EncryptionInfoEntry::ToString() {
|
|||
return ext_key + ",KEYFORMAT=\"" + key_format_ + "\"\n";
|
||||
}
|
||||
|
||||
class DiscontinuityEntry : public HlsEntry {
|
||||
public:
|
||||
DiscontinuityEntry();
|
||||
|
||||
~DiscontinuityEntry() override;
|
||||
|
||||
std::string ToString() override;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(DiscontinuityEntry);
|
||||
};
|
||||
|
||||
DiscontinuityEntry::DiscontinuityEntry()
|
||||
: HlsEntry(HlsEntry::EntryType::kExtDiscontinuity) {}
|
||||
|
||||
DiscontinuityEntry::~DiscontinuityEntry() {}
|
||||
|
||||
std::string DiscontinuityEntry::ToString() {
|
||||
return "#EXT-X-DISCONTINUITY\n";
|
||||
}
|
||||
|
||||
double LatestSegmentStartTime(
|
||||
const std::list<std::unique_ptr<HlsEntry>>& entries) {
|
||||
DCHECK(!entries.empty());
|
||||
|
@ -276,69 +297,19 @@ void MediaPlaylist::AddSegment(const std::string& file_name,
|
|||
SlideWindow();
|
||||
}
|
||||
|
||||
// TODO(rkuroiwa): This works for single key format but won't work for multiple
|
||||
// key formats (e.g. different DRM systems).
|
||||
// Candidate algorithm:
|
||||
// Assume entries_ is std::list (static_assert below).
|
||||
// Create a map from key_format to EncryptionInfoEntry (iterator actually).
|
||||
// Iterate over entries_ until it hits SegmentInfoEntry. While iterating over
|
||||
// entries_ if there are multiple EncryptionInfoEntry with the same key_format,
|
||||
// erase the older ones using the iterator.
|
||||
// Note that when erasing std::list iterators, only the deleted iterators are
|
||||
// invalidated.
|
||||
void MediaPlaylist::RemoveOldestSegment() {
|
||||
static_assert(std::is_same<decltype(entries_),
|
||||
std::list<std::unique_ptr<HlsEntry>>>::value,
|
||||
"This algorithm assumes std::list.");
|
||||
if (entries_.empty())
|
||||
return;
|
||||
if (entries_.front()->type() == HlsEntry::EntryType::kExtInf) {
|
||||
entries_.pop_front();
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure that the first EXT-X-KEY entry doesn't get popped out until the
|
||||
// next EXT-X-KEY entry because the first EXT-X-KEY applies to all the
|
||||
// segments following until the next one.
|
||||
|
||||
if (entries_.size() == 1) {
|
||||
// More segments might get added, leave the entry in.
|
||||
return;
|
||||
}
|
||||
|
||||
if (entries_.size() == 2) {
|
||||
auto entries_itr = entries_.begin();
|
||||
++entries_itr;
|
||||
if ((*entries_itr)->type() == HlsEntry::EntryType::kExtKey) {
|
||||
entries_.pop_front();
|
||||
} else {
|
||||
entries_.erase(entries_itr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto entries_itr = entries_.begin();
|
||||
++entries_itr;
|
||||
if ((*entries_itr)->type() == HlsEntry::EntryType::kExtInf) {
|
||||
DCHECK((*entries_itr)->type() == HlsEntry::EntryType::kExtInf);
|
||||
entries_.erase(entries_itr);
|
||||
return;
|
||||
}
|
||||
|
||||
++entries_itr;
|
||||
// This assumes that there is a segment between 2 EXT-X-KEY entries.
|
||||
// Which should be the case due to logic in AddEncryptionInfo().
|
||||
DCHECK((*entries_itr)->type() == HlsEntry::EntryType::kExtInf);
|
||||
entries_.erase(entries_itr);
|
||||
entries_.pop_front();
|
||||
}
|
||||
|
||||
void MediaPlaylist::AddEncryptionInfo(MediaPlaylist::EncryptionMethod method,
|
||||
const std::string& url,
|
||||
const std::string& key_id,
|
||||
const std::string& iv,
|
||||
const std::string& key_format,
|
||||
const std::string& key_format_versions) {
|
||||
if (!inserted_discontinuity_tag_) {
|
||||
// Insert discontinuity tag only for the first EXT-X-KEY, only if there
|
||||
// are non-encrypted media segments.
|
||||
if (!entries_.empty())
|
||||
entries_.emplace_back(new DiscontinuityEntry());
|
||||
inserted_discontinuity_tag_ = true;
|
||||
}
|
||||
entries_.emplace_back(new EncryptionInfoEntry(
|
||||
method, url, key_id, iv, key_format, key_format_versions));
|
||||
}
|
||||
|
@ -350,22 +321,11 @@ bool MediaPlaylist::WriteToFile(const std::string& file_path) {
|
|||
|
||||
std::string header = CreatePlaylistHeader(
|
||||
media_info_.init_segment_name(), target_duration_, type_,
|
||||
sequence_number_, discontinuity_sequence_number_);
|
||||
media_sequence_number_, discontinuity_sequence_number_);
|
||||
|
||||
std::string body;
|
||||
if (!entries_.empty()) {
|
||||
const bool first_is_ext_key =
|
||||
entries_.front()->type() == HlsEntry::EntryType::kExtKey;
|
||||
bool inserted_discontinuity_tag = false;
|
||||
for (const auto& entry : entries_) {
|
||||
if (!first_is_ext_key && !inserted_discontinuity_tag &&
|
||||
entry->type() == HlsEntry::EntryType::kExtKey) {
|
||||
body.append("#EXT-X-DISCONTINUITY\n");
|
||||
inserted_discontinuity_tag = true;
|
||||
}
|
||||
for (const auto& entry : entries_)
|
||||
body.append(entry->ToString());
|
||||
}
|
||||
}
|
||||
|
||||
std::string content = header + body;
|
||||
|
||||
|
@ -475,18 +435,11 @@ void MediaPlaylist::SlideWindow() {
|
|||
for (; last != entries_.end(); ++last) {
|
||||
HlsEntry::EntryType entry_type = last->get()->type();
|
||||
if (entry_type == HlsEntry::EntryType::kExtKey) {
|
||||
if (prev_entry_type != HlsEntry::EntryType::kExtKey) {
|
||||
if (!ext_x_keys.empty()) {
|
||||
// Increase discontinuity sequence every time key changes. Note that
|
||||
// it is inconsistent to how we insert EXT-X-DISCONTINUITY tag
|
||||
// currently as we only insert the tag for the first EXT-X-KEY.
|
||||
// TODO(kqyang): Find out if it is necessary to insert the
|
||||
// EXT-X-DISCONTINUITY tag when key changes.
|
||||
++discontinuity_sequence_number_;
|
||||
if (prev_entry_type != HlsEntry::EntryType::kExtKey)
|
||||
ext_x_keys.clear();
|
||||
}
|
||||
}
|
||||
ext_x_keys.push_back(std::move(*last));
|
||||
} else if (entry_type == HlsEntry::EntryType::kExtDiscontinuity) {
|
||||
++discontinuity_sequence_number_;
|
||||
} else {
|
||||
DCHECK_EQ(entry_type, HlsEntry::EntryType::kExtInf);
|
||||
const SegmentInfoEntry* segment_info =
|
||||
|
@ -503,7 +456,7 @@ void MediaPlaylist::SlideWindow() {
|
|||
// Add key entries back.
|
||||
entries_.insert(entries_.begin(), std::make_move_iterator(ext_x_keys.begin()),
|
||||
std::make_move_iterator(ext_x_keys.end()));
|
||||
sequence_number_ += num_segments_removed;
|
||||
media_sequence_number_ += num_segments_removed;
|
||||
}
|
||||
|
||||
} // namespace hls
|
||||
|
|
|
@ -27,6 +27,7 @@ class HlsEntry {
|
|||
enum class EntryType {
|
||||
kExtInf,
|
||||
kExtKey,
|
||||
kExtDiscontinuity,
|
||||
};
|
||||
virtual ~HlsEntry();
|
||||
|
||||
|
@ -105,10 +106,6 @@ class MediaPlaylist {
|
|||
uint64_t duration,
|
||||
uint64_t size);
|
||||
|
||||
/// Removes the oldest segment from the playlist. Useful for manually managing
|
||||
/// the length of the playlist.
|
||||
virtual void RemoveOldestSegment();
|
||||
|
||||
/// All segments added after calling this method must be decryptable with
|
||||
/// the key that can be fetched from |url|, until calling this again.
|
||||
/// @param method is the encryption method.
|
||||
|
@ -180,7 +177,8 @@ class MediaPlaylist {
|
|||
MediaPlaylistStreamType stream_type_ =
|
||||
MediaPlaylistStreamType::kPlaylistUnknown;
|
||||
std::string codec_;
|
||||
int sequence_number_ = 0;
|
||||
int media_sequence_number_ = 0;
|
||||
bool inserted_discontinuity_tag_ = false;
|
||||
int discontinuity_sequence_number_ = 0;
|
||||
|
||||
double longest_segment_duration_ = 0.0;
|
||||
|
|
|
@ -274,31 +274,6 @@ TEST_F(MediaPlaylistTest, WriteToFileWithClearLead) {
|
|||
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(MediaPlaylistTest, RemoveOldestSegment) {
|
||||
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
||||
|
||||
media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes);
|
||||
media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 30 * kTimeScale,
|
||||
5 * kMBytes);
|
||||
media_playlist_.RemoveOldestSegment();
|
||||
|
||||
const char kExpectedOutput[] =
|
||||
"#EXTM3U\n"
|
||||
"#EXT-X-VERSION:6\n"
|
||||
"## Generated with https://github.com/google/shaka-packager version "
|
||||
"test\n"
|
||||
"#EXT-X-TARGETDURATION:30\n"
|
||||
"#EXT-X-PLAYLIST-TYPE:VOD\n"
|
||||
"#EXTINF:30.000,\n"
|
||||
"file2.ts\n"
|
||||
"#EXT-X-ENDLIST\n";
|
||||
|
||||
const char kMemoryFilePath[] = "memory://media.m3u8";
|
||||
EXPECT_TRUE(media_playlist_.WriteToFile(kMemoryFilePath));
|
||||
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||
}
|
||||
|
||||
TEST_F(MediaPlaylistTest, GetLanguage) {
|
||||
MediaInfo media_info;
|
||||
media_info.set_reference_time_scale(kTimeScale);
|
||||
|
@ -510,6 +485,8 @@ TEST_F(LiveMediaPlaylistTest, TimeShiftedWithEncryptionInfo) {
|
|||
TEST_F(LiveMediaPlaylistTest, TimeShiftedWithEncryptionInfoShifted) {
|
||||
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
||||
|
||||
media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes);
|
||||
|
||||
media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes,
|
||||
"http://example.com", "", "0x12345678",
|
||||
"com.widevine", "1/2/4");
|
||||
|
@ -517,7 +494,8 @@ TEST_F(LiveMediaPlaylistTest, TimeShiftedWithEncryptionInfoShifted) {
|
|||
MediaPlaylist::EncryptionMethod::kSampleAes, "http://mydomain.com",
|
||||
"0xfedc", "0x12345678", "com.widevine.someother", "1");
|
||||
|
||||
media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes);
|
||||
media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 20 * kTimeScale,
|
||||
2 * kMBytes);
|
||||
|
||||
media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes,
|
||||
"http://example.com", "", "0x22345678",
|
||||
|
@ -526,7 +504,7 @@ TEST_F(LiveMediaPlaylistTest, TimeShiftedWithEncryptionInfoShifted) {
|
|||
MediaPlaylist::EncryptionMethod::kSampleAes, "http://mydomain.com",
|
||||
"0xfedd", "0x22345678", "com.widevine.someother", "1");
|
||||
|
||||
media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 20 * kTimeScale,
|
||||
media_playlist_.AddSegment("file3.ts", 30 * kTimeScale, 20 * kTimeScale,
|
||||
2 * kMBytes);
|
||||
|
||||
media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes,
|
||||
|
@ -536,7 +514,7 @@ TEST_F(LiveMediaPlaylistTest, TimeShiftedWithEncryptionInfoShifted) {
|
|||
MediaPlaylist::EncryptionMethod::kSampleAes, "http://mydomain.com",
|
||||
"0xfede", "0x32345678", "com.widevine.someother", "1");
|
||||
|
||||
media_playlist_.AddSegment("file3.ts", 30 * kTimeScale, 20 * kTimeScale,
|
||||
media_playlist_.AddSegment("file4.ts", 50 * kTimeScale, 20 * kTimeScale,
|
||||
2 * kMBytes);
|
||||
const char kExpectedOutput[] =
|
||||
"#EXTM3U\n"
|
||||
|
@ -544,7 +522,7 @@ TEST_F(LiveMediaPlaylistTest, TimeShiftedWithEncryptionInfoShifted) {
|
|||
"## Generated with https://github.com/google/shaka-packager version "
|
||||
"test\n"
|
||||
"#EXT-X-TARGETDURATION:20\n"
|
||||
"#EXT-X-MEDIA-SEQUENCE:1\n"
|
||||
"#EXT-X-MEDIA-SEQUENCE:2\n"
|
||||
"#EXT-X-DISCONTINUITY-SEQUENCE:1\n"
|
||||
"#EXT-X-KEY:METHOD=SAMPLE-AES,"
|
||||
"URI=\"http://example.com\",IV=0x22345678,KEYFORMATVERSIONS=\"1/2/4\","
|
||||
|
@ -554,7 +532,7 @@ TEST_F(LiveMediaPlaylistTest, TimeShiftedWithEncryptionInfoShifted) {
|
|||
"KEYFORMATVERSIONS=\"1\","
|
||||
"KEYFORMAT=\"com.widevine.someother\"\n"
|
||||
"#EXTINF:20.000,\n"
|
||||
"file2.ts\n"
|
||||
"file3.ts\n"
|
||||
"#EXT-X-KEY:METHOD=SAMPLE-AES,"
|
||||
"URI=\"http://example.com\",IV=0x32345678,KEYFORMATVERSIONS=\"1/2/4\","
|
||||
"KEYFORMAT=\"com.widevine\"\n"
|
||||
|
@ -563,7 +541,7 @@ TEST_F(LiveMediaPlaylistTest, TimeShiftedWithEncryptionInfoShifted) {
|
|||
"KEYFORMATVERSIONS=\"1\","
|
||||
"KEYFORMAT=\"com.widevine.someother\"\n"
|
||||
"#EXTINF:20.000,\n"
|
||||
"file3.ts\n";
|
||||
"file4.ts\n";
|
||||
|
||||
const char kMemoryFilePath[] = "memory://media.m3u8";
|
||||
EXPECT_TRUE(media_playlist_.WriteToFile(kMemoryFilePath));
|
||||
|
|
|
@ -46,20 +46,23 @@ Status Muxer::Process(std::unique_ptr<StreamData> stream_data) {
|
|||
kInitialEncryptionInfo, encryption_config.protection_scheme,
|
||||
encryption_config.key_id, encryption_config.constant_iv,
|
||||
encryption_config.key_system_info);
|
||||
current_key_id_ = encryption_config.key_id;
|
||||
}
|
||||
return InitializeMuxer();
|
||||
case StreamDataType::kSegmentInfo: {
|
||||
auto& segment_info = stream_data->segment_info;
|
||||
if (muxer_listener_) {
|
||||
if (muxer_listener_ && segment_info->is_encrypted) {
|
||||
const EncryptionConfig* encryption_config =
|
||||
segment_info->key_rotation_encryption_config.get();
|
||||
if (encryption_config) {
|
||||
// Only call OnEncryptionInfoReady again when key updates.
|
||||
if (encryption_config && encryption_config->key_id != current_key_id_) {
|
||||
muxer_listener_->OnEncryptionInfoReady(
|
||||
!kInitialEncryptionInfo, encryption_config->protection_scheme,
|
||||
encryption_config->key_id, encryption_config->constant_iv,
|
||||
encryption_config->key_system_info);
|
||||
current_key_id_ = encryption_config->key_id;
|
||||
}
|
||||
if (segment_info->is_encrypted && !encryption_started_) {
|
||||
if (!encryption_started_) {
|
||||
encryption_started_ = true;
|
||||
muxer_listener_->OnEncryptionStart();
|
||||
}
|
||||
|
|
|
@ -88,6 +88,7 @@ class Muxer : public MediaHandler {
|
|||
|
||||
MuxerOptions options_;
|
||||
std::vector<std::shared_ptr<StreamInfo>> streams_;
|
||||
std::vector<uint8_t> current_key_id_;
|
||||
bool encryption_started_ = false;
|
||||
bool cancelled_;
|
||||
|
||||
|
|
|
@ -222,6 +222,7 @@ Status EncryptionHandler::ProcessMediaSample(MediaSample* sample) {
|
|||
return status;
|
||||
if (!CreateEncryptor(encryption_key))
|
||||
return Status(error::ENCRYPTION_FAILURE, "Failed to create encryptor");
|
||||
prev_crypto_period_index_ = current_crypto_period_index;
|
||||
}
|
||||
check_new_crypto_period_ = false;
|
||||
}
|
||||
|
|
|
@ -304,7 +304,8 @@ class EncryptionHandlerEncryptionTest
|
|||
} else {
|
||||
EXPECT_CALL(*mock_header_parser, GetHeaderSize(_))
|
||||
.WillOnce(Return(kSliceHeaderSize1))
|
||||
.WillOnce(Return(kSliceHeaderSize2));
|
||||
.WillOnce(Return(kSliceHeaderSize2))
|
||||
.WillRepeatedly(Return(kSliceHeaderSize2));
|
||||
}
|
||||
InjectVideoSliceHeaderParserForTesting(std::move(mock_header_parser));
|
||||
break;
|
||||
|
@ -519,7 +520,9 @@ TEST_P(EncryptionHandlerEncryptionTest, ClearLeadWithNoKeyRotation) {
|
|||
|
||||
TEST_P(EncryptionHandlerEncryptionTest, ClearLeadWithKeyRotation) {
|
||||
const double kClearLeadInSeconds = 1.5 * kSegmentDuration / kTimeScale;
|
||||
const double kCryptoPeriodDurationInSeconds = kSegmentDuration / kTimeScale;
|
||||
const int kSegmentsPerCryptoPeriod = 2; // 2 segments.
|
||||
const double kCryptoPeriodDurationInSeconds =
|
||||
kSegmentsPerCryptoPeriod * kSegmentDuration / kTimeScale;
|
||||
EncryptionOptions encryption_options;
|
||||
encryption_options.protection_scheme = protection_scheme_;
|
||||
encryption_options.clear_lead_in_seconds = kClearLeadInSeconds;
|
||||
|
@ -546,19 +549,20 @@ TEST_P(EncryptionHandlerEncryptionTest, ClearLeadWithKeyRotation) {
|
|||
|
||||
InjectCodecParser();
|
||||
|
||||
// There are three segments. Only the third segment is encrypted.
|
||||
// Crypto period duration is the same as segment duration, so there are three
|
||||
// crypto periods, although only the last is encrypted.
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
EXPECT_CALL(mock_key_source_, GetCryptoPeriodKey(i, _, _))
|
||||
// There are five segments with the first two not encrypted.
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
if ((i % kSegmentsPerCryptoPeriod) == 0) {
|
||||
EXPECT_CALL(mock_key_source_,
|
||||
GetCryptoPeriodKey(i / kSegmentsPerCryptoPeriod, _, _))
|
||||
.WillOnce(DoAll(SetArgPointee<2>(GetMockEncryptionKey()),
|
||||
Return(Status::OK)));
|
||||
}
|
||||
// Use single-frame segment for testing.
|
||||
ASSERT_OK(Process(GetMediaSampleStreamData(
|
||||
kStreamIndex, i * kSegmentDuration, kSegmentDuration)));
|
||||
ASSERT_OK(Process(GetSegmentInfoStreamData(
|
||||
kStreamIndex, i * kSegmentDuration, kSegmentDuration, !kIsSubsegment)));
|
||||
const bool is_encrypted = i == 2;
|
||||
const bool is_encrypted = i >= 2;
|
||||
const auto& output_stream_data = GetOutputStreamDataVector();
|
||||
EXPECT_THAT(output_stream_data,
|
||||
ElementsAre(IsMediaSample(kStreamIndex, i * kSegmentDuration,
|
||||
|
|
|
@ -33,12 +33,9 @@ HlsNotifyMuxerListener::~HlsNotifyMuxerListener() {}
|
|||
// These methods work together to notify that the media is encrypted.
|
||||
// If OnEncryptionInfoReady() is called before the media has been started, then
|
||||
// the information is stored and handled when OnEncryptionStart() is called.
|
||||
// if OnEncryptionStart() is called before the media has been started then
|
||||
// If OnEncryptionStart() is called before the media has been started then
|
||||
// OnMediaStart() is responsible for notifying that the segments are encrypted
|
||||
// right away i.e. call OnEncryptionStart().
|
||||
// For now (because Live HLS is not implemented yet) this should be called once,
|
||||
// before media is started. So the logic after the first if statement should not
|
||||
// be taken.
|
||||
void HlsNotifyMuxerListener::OnEncryptionInfoReady(
|
||||
bool is_initial_encryption_info,
|
||||
FourCC protection_scheme,
|
||||
|
|
|
@ -38,15 +38,14 @@ class MuxerListener {
|
|||
|
||||
virtual ~MuxerListener() {};
|
||||
|
||||
/// Called when the media's encryption information is ready. This should be
|
||||
/// called before OnMediaStart(), if the media is encrypted.
|
||||
/// All the parameters may be empty just to notify that the media is
|
||||
/// encrypted.
|
||||
/// For ISO BMFF (MP4) media:
|
||||
/// If @a is_initial_encryption_info is true then @a key_id is the default_KID
|
||||
/// in 'tenc' box.
|
||||
/// If @a is_initial_encryption_info is false then @a key_id is the new key ID
|
||||
/// for the for the next crypto period.
|
||||
/// Called when the media's encryption information is ready.
|
||||
/// OnEncryptionInfoReady with @a initial_encryption_info being true should be
|
||||
/// called before OnMediaStart(), if the media is encrypted. All the
|
||||
/// parameters may be empty just to notify that the media is encrypted. For
|
||||
/// ISO BMFF (MP4) media: If @a is_initial_encryption_info is true then @a
|
||||
/// key_id is the default_KID in 'tenc' box. If @a is_initial_encryption_info
|
||||
/// is false then @a key_id is the new key ID for the for the next crypto
|
||||
/// period.
|
||||
/// @param is_initial_encryption_info is true if this is the first encryption
|
||||
/// info for the media. In general, this flag should always be true for
|
||||
/// non-key-rotated media and should be called only once.
|
||||
|
|
|
@ -41,11 +41,6 @@ Status TsMuxer::AddSample(size_t stream_id,
|
|||
Status TsMuxer::FinalizeSegment(size_t stream_id,
|
||||
std::shared_ptr<SegmentInfo> segment_info) {
|
||||
DCHECK_EQ(stream_id, 0u);
|
||||
if (segment_info->key_rotation_encryption_config) {
|
||||
NOTIMPLEMENTED() << "Key rotation is not implemented for TS.";
|
||||
return Status(error::UNIMPLEMENTED,
|
||||
"Key rotation is not implemented for TS");
|
||||
}
|
||||
return segment_info->is_subsegment
|
||||
? Status::OK
|
||||
: segmenter_->FinalizeSegment(segment_info->start_timestamp,
|
||||
|
|
Loading…
Reference in New Issue