diff --git a/packager/app/test/packager_test.py b/packager/app/test/packager_test.py index 1279425f61..1c8b9bc010 100755 --- a/packager/app/test/packager_test.py +++ b/packager/app/test/packager_test.py @@ -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') diff --git a/packager/app/test/testdata/bear-640x360-a-enc-rotation-golden-1.ts b/packager/app/test/testdata/bear-640x360-a-enc-rotation-golden-1.ts new file mode 100644 index 0000000000..c492a868c3 Binary files /dev/null and b/packager/app/test/testdata/bear-640x360-a-enc-rotation-golden-1.ts differ diff --git a/packager/app/test/testdata/bear-640x360-a-enc-rotation-golden-2.ts b/packager/app/test/testdata/bear-640x360-a-enc-rotation-golden-2.ts new file mode 100644 index 0000000000..5f386328e3 Binary files /dev/null and b/packager/app/test/testdata/bear-640x360-a-enc-rotation-golden-2.ts differ diff --git a/packager/app/test/testdata/bear-640x360-a-enc-rotation-golden-3.ts b/packager/app/test/testdata/bear-640x360-a-enc-rotation-golden-3.ts new file mode 100644 index 0000000000..3f18799587 Binary files /dev/null and b/packager/app/test/testdata/bear-640x360-a-enc-rotation-golden-3.ts differ diff --git a/packager/app/test/testdata/bear-640x360-a-live-enc-rotation-golden.m3u8 b/packager/app/test/testdata/bear-640x360-a-live-enc-rotation-golden.m3u8 new file mode 100644 index 0000000000..40c3f77628 --- /dev/null +++ b/packager/app/test/testdata/bear-640x360-a-live-enc-rotation-golden.m3u8 @@ -0,0 +1,11 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/google/shaka-packager version -- +#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 diff --git a/packager/app/test/testdata/bear-640x360-av-enc-master-golden.m3u8 b/packager/app/test/testdata/bear-640x360-av-enc-master-golden.m3u8 deleted file mode 100644 index 19f6cf54c1..0000000000 --- a/packager/app/test/testdata/bear-640x360-av-enc-master-golden.m3u8 +++ /dev/null @@ -1,5 +0,0 @@ -#EXTM3U -## Generated with https://github.com/google/shaka-packager version -- -#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 diff --git a/packager/app/test/testdata/bear-640x360-v-enc-rotation-golden-1.ts b/packager/app/test/testdata/bear-640x360-v-enc-rotation-golden-1.ts new file mode 100644 index 0000000000..c8600d8422 Binary files /dev/null and b/packager/app/test/testdata/bear-640x360-v-enc-rotation-golden-1.ts differ diff --git a/packager/app/test/testdata/bear-640x360-v-enc-rotation-golden-2.ts b/packager/app/test/testdata/bear-640x360-v-enc-rotation-golden-2.ts new file mode 100644 index 0000000000..3eb4efe34b Binary files /dev/null and b/packager/app/test/testdata/bear-640x360-v-enc-rotation-golden-2.ts differ diff --git a/packager/app/test/testdata/bear-640x360-v-enc-rotation-golden-3.ts b/packager/app/test/testdata/bear-640x360-v-enc-rotation-golden-3.ts new file mode 100644 index 0000000000..c0522d752e Binary files /dev/null and b/packager/app/test/testdata/bear-640x360-v-enc-rotation-golden-3.ts differ diff --git a/packager/app/test/testdata/bear-640x360-v-live-enc-rotation-golden.m3u8 b/packager/app/test/testdata/bear-640x360-v-live-enc-rotation-golden.m3u8 new file mode 100644 index 0000000000..26c493d7f7 --- /dev/null +++ b/packager/app/test/testdata/bear-640x360-v-live-enc-rotation-golden.m3u8 @@ -0,0 +1,12 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/google/shaka-packager version -- +#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 diff --git a/packager/hls/base/media_playlist.cc b/packager/hls/base/media_playlist.cc index 03053b0b33..d91e15acb4 100644 --- a/packager/hls/base/media_playlist.cc +++ b/packager/hls/base/media_playlist.cc @@ -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>& 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>>::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; - } - body.append(entry->ToString()); - } - } + 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_; - ext_x_keys.clear(); - } - } + 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 diff --git a/packager/hls/base/media_playlist.h b/packager/hls/base/media_playlist.h index 6a2736e293..c10637fe0d 100644 --- a/packager/hls/base/media_playlist.h +++ b/packager/hls/base/media_playlist.h @@ -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; diff --git a/packager/hls/base/media_playlist_unittest.cc b/packager/hls/base/media_playlist_unittest.cc index cfe2a04851..11ede8390a 100644 --- a/packager/hls/base/media_playlist_unittest.cc +++ b/packager/hls/base/media_playlist_unittest.cc @@ -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)); diff --git a/packager/media/base/muxer.cc b/packager/media/base/muxer.cc index 1139bb0df7..c5a3331eed 100644 --- a/packager/media/base/muxer.cc +++ b/packager/media/base/muxer.cc @@ -46,20 +46,23 @@ Status Muxer::Process(std::unique_ptr 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(); } diff --git a/packager/media/base/muxer.h b/packager/media/base/muxer.h index feb1f90f0b..6adaa6f075 100644 --- a/packager/media/base/muxer.h +++ b/packager/media/base/muxer.h @@ -88,6 +88,7 @@ class Muxer : public MediaHandler { MuxerOptions options_; std::vector> streams_; + std::vector current_key_id_; bool encryption_started_ = false; bool cancelled_; diff --git a/packager/media/crypto/encryption_handler.cc b/packager/media/crypto/encryption_handler.cc index 96801635cc..750ed8764a 100644 --- a/packager/media/crypto/encryption_handler.cc +++ b/packager/media/crypto/encryption_handler.cc @@ -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; } diff --git a/packager/media/crypto/encryption_handler_unittest.cc b/packager/media/crypto/encryption_handler_unittest.cc index f5a58b310e..7196881632 100644 --- a/packager/media/crypto/encryption_handler_unittest.cc +++ b/packager/media/crypto/encryption_handler_unittest.cc @@ -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, _, _)) - .WillOnce(DoAll(SetArgPointee<2>(GetMockEncryptionKey()), - Return(Status::OK))); + // 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, diff --git a/packager/media/event/hls_notify_muxer_listener.cc b/packager/media/event/hls_notify_muxer_listener.cc index 30103640bc..cfadc6f6e1 100644 --- a/packager/media/event/hls_notify_muxer_listener.cc +++ b/packager/media/event/hls_notify_muxer_listener.cc @@ -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, diff --git a/packager/media/event/muxer_listener.h b/packager/media/event/muxer_listener.h index 393a9ab5f2..bb117ebf99 100644 --- a/packager/media/event/muxer_listener.h +++ b/packager/media/event/muxer_listener.h @@ -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. diff --git a/packager/media/formats/mp2t/ts_muxer.cc b/packager/media/formats/mp2t/ts_muxer.cc index cc1874c082..56c1c48262 100644 --- a/packager/media/formats/mp2t/ts_muxer.cc +++ b/packager/media/formats/mp2t/ts_muxer.cc @@ -41,11 +41,6 @@ Status TsMuxer::AddSample(size_t stream_id, Status TsMuxer::FinalizeSegment(size_t stream_id, std::shared_ptr 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,