From 300c23104e68477e0896229df0be1f977f34be32 Mon Sep 17 00:00:00 2001 From: Rintaro Kuroiwa Date: Mon, 28 Mar 2016 01:23:20 -0700 Subject: [PATCH] MuxerOptions and MuxerListener change for HLS - Add hls_name and hls_group_id fields to MuxerOptions. This is used to pass the NAME and GROUP-ID values for EXT-X-MEDIA tag to MuxerListener. - Change MuxerListener::OnEncryptionInfoReady() to take an initialization vector. - Change MuxerListener::OnNewSegment() to take segment name. - Reworded and formatted MuxerListener comments to Doxygen style. Issue #85 Change-Id: Iea06e68552a56ae180177ffd6ca315a7cf39456c --- packager/media/base/muxer_options.h | 11 +++ .../media/event/mpd_notify_muxer_listener.cc | 4 +- .../media/event/mpd_notify_muxer_listener.h | 4 +- .../mpd_notify_muxer_listener_unittest.cc | 33 ++++--- packager/media/event/muxer_listener.h | 92 +++++++++++-------- .../vod_media_info_dump_muxer_listener.cc | 7 +- .../vod_media_info_dump_muxer_listener.h | 4 +- ...media_info_dump_muxer_listener_unittest.cc | 8 +- .../formats/mp4/key_rotation_fragmenter.cc | 1 + .../formats/mp4/multi_segment_segmenter.cc | 14 +-- packager/media/formats/mp4/segmenter.cc | 4 +- .../formats/mp4/single_segment_segmenter.cc | 3 +- packager/media/formats/webm/encryptor.cc | 1 + .../formats/webm/multi_segment_segmenter.cc | 3 +- 14 files changed, 122 insertions(+), 67 deletions(-) diff --git a/packager/media/base/muxer_options.h b/packager/media/base/muxer_options.h index 093c8a6021..85f9af0fb4 100644 --- a/packager/media/base/muxer_options.h +++ b/packager/media/base/muxer_options.h @@ -60,6 +60,17 @@ struct MuxerOptions { /// Optional. std::string segment_template; + /// name of the output stream. This is not (necessarily) the same as @a + /// output_file_name. For HLS this is used as the NAME attribute for + /// EXT-X-MEDIA. + /// Required for audio when outputting HLS. + std::string hls_name; + + /// The group ID for the output stream. + /// For HLS this is used as the GROUP-ID attribute for EXT-X-MEDIA. + /// Required for audio when outputting HLS. + std::string hls_group_id; + /// Specify temporary directory for intermediate files. std::string temp_dir; diff --git a/packager/media/event/mpd_notify_muxer_listener.cc b/packager/media/event/mpd_notify_muxer_listener.cc index 705f2e2dd4..44d01c5db6 100644 --- a/packager/media/event/mpd_notify_muxer_listener.cc +++ b/packager/media/event/mpd_notify_muxer_listener.cc @@ -31,6 +31,7 @@ MpdNotifyMuxerListener::~MpdNotifyMuxerListener() {} void MpdNotifyMuxerListener::OnEncryptionInfoReady( bool is_initial_encryption_info, const std::vector& key_id, + const std::vector& iv, const std::vector& key_system_info) { if (is_initial_encryption_info) { LOG_IF(WARNING, is_encrypted_) @@ -142,7 +143,8 @@ void MpdNotifyMuxerListener::OnMediaEnd(bool has_init_range, mpd_notifier_->Flush(); } -void MpdNotifyMuxerListener::OnNewSegment(uint64_t start_time, +void MpdNotifyMuxerListener::OnNewSegment(const std::string& file_name, + uint64_t start_time, uint64_t duration, uint64_t segment_file_size) { if (mpd_notifier_->dash_profile() == kLiveProfile) { diff --git a/packager/media/event/mpd_notify_muxer_listener.h b/packager/media/event/mpd_notify_muxer_listener.h index 8e84cf372a..4fb6c9dcde 100644 --- a/packager/media/event/mpd_notify_muxer_listener.h +++ b/packager/media/event/mpd_notify_muxer_listener.h @@ -35,6 +35,7 @@ class MpdNotifyMuxerListener : public MuxerListener { /// @{ void OnEncryptionInfoReady(bool is_initial_encryption_info, const std::vector& key_id, + const std::vector& iv, const std::vector& key_system_info) override; void OnMediaStart(const MuxerOptions& muxer_options, @@ -50,7 +51,8 @@ class MpdNotifyMuxerListener : public MuxerListener { uint64_t index_range_end, float duration_seconds, uint64_t file_size) override; - void OnNewSegment(uint64_t start_time, + void OnNewSegment(const std::string& file_name, + uint64_t start_time, uint64_t duration, uint64_t segment_file_size) override; /// @} diff --git a/packager/media/event/mpd_notify_muxer_listener_unittest.cc b/packager/media/event/mpd_notify_muxer_listener_unittest.cc index 1eea88ea12..605b5a49d1 100644 --- a/packager/media/event/mpd_notify_muxer_listener_unittest.cc +++ b/packager/media/event/mpd_notify_muxer_listener_unittest.cc @@ -55,6 +55,11 @@ void SetDefaultLiveMuxerOptionsValues(media::MuxerOptions* muxer_options) { muxer_options->temp_dir.clear(); } +const uint8_t kBogusIv[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x67, 0x83, 0xC3, 0x66, 0xEE, 0xAB, 0xB2, 0xF1, +}; + } // namespace namespace media { @@ -161,8 +166,10 @@ TEST_F(MpdNotifyMuxerListenerTest, VodEncryptedContent) { "}\n"; EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); - listener_->OnEncryptionInfoReady(kInitialEncryptionInfo, - default_key_id, GetDefaultKeySystemInfo()); + + std::vector iv(kBogusIv, kBogusIv + arraysize(kBogusIv)); + listener_->OnEncryptionInfoReady(kInitialEncryptionInfo, default_key_id, iv, + GetDefaultKeySystemInfo()); listener_->OnMediaStart(muxer_options, *video_stream_info, kDefaultReferenceTimeScale, @@ -245,8 +252,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegment) { listener_->OnMediaStart(muxer_options, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment(kStartTime1, kDuration1, kSegmentFileSize1); - listener_->OnNewSegment(kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); InSequence s; @@ -312,13 +319,14 @@ TEST_F(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) { NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); EXPECT_CALL(*notifier_, Flush()); - listener_->OnEncryptionInfoReady(kInitialEncryptionInfo, - default_key_id, GetDefaultKeySystemInfo()); + std::vector iv(kBogusIv, kBogusIv + arraysize(kBogusIv)); + listener_->OnEncryptionInfoReady(kInitialEncryptionInfo, default_key_id, iv, + GetDefaultKeySystemInfo()); listener_->OnMediaStart(muxer_options, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment(kStartTime1, kDuration1, kSegmentFileSize1); - listener_->OnNewSegment(kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); EXPECT_CALL(*notifier_, Flush()).Times(0); @@ -374,16 +382,17 @@ TEST_F(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) { NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); EXPECT_CALL(*notifier_, Flush()); - listener_->OnEncryptionInfoReady(kInitialEncryptionInfo, default_key_id, + std::vector iv(kBogusIv, kBogusIv + arraysize(kBogusIv)); + listener_->OnEncryptionInfoReady(kInitialEncryptionInfo, default_key_id, iv, std::vector()); listener_->OnMediaStart(muxer_options, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); listener_->OnEncryptionInfoReady(kNonInitialEncryptionInfo, - std::vector(), + std::vector(), iv, GetDefaultKeySystemInfo()); - listener_->OnNewSegment(kStartTime1, kDuration1, kSegmentFileSize1); - listener_->OnNewSegment(kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); EXPECT_CALL(*notifier_, Flush()).Times(0); diff --git a/packager/media/event/muxer_listener.h b/packager/media/event/muxer_listener.h index abbae36004..aee537eb2b 100644 --- a/packager/media/event/muxer_listener.h +++ b/packager/media/event/muxer_listener.h @@ -6,8 +6,6 @@ // // Event handler for events fired by Muxer. -// TODO(rkuroiwa): Document using doxygen style comments. - #ifndef MEDIA_EVENT_MUXER_LISTENER_H_ #define MEDIA_EVENT_MUXER_LISTENER_H_ @@ -38,31 +36,37 @@ 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. - // |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. - // |key_id| is the key ID for the media. - // The format should be a vector of uint8_t, i.e. not (necessarily) human - // readable hex string. - // For ISO BMFF (MP4) media: - // If |is_initial_encryption_info| is true then |key_id| is the default_KID in - // 'tenc' box. - // If |is_initial_encryption_info| is false then |key_id| is the new key ID - // for the for the next crypto period. + /// 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. + /// @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. + /// @param key_id is the key ID for the media. The format should be a vector + /// of uint8_t, i.e. not (necessarily) human readable hex string. + /// @param iv is the initialization vector. For most cases this should be 16 + /// bytes, but whether the input is accepted is up to the + /// implementation. virtual void OnEncryptionInfoReady( bool is_initial_encryption_info, const std::vector& key_id, + const std::vector& iv, const std::vector& key_system_info) = 0; - // Called when muxing starts. - // For MPEG DASH Live profile, the initialization segment information is - // available from StreamInfo. - // |time_scale| is a reference time scale that overrides the time scale - // specified in |stream_info|. + /// Called when muxing starts. + /// For MPEG DASH Live profile, the initialization segment information is + /// available from StreamInfo. + /// @param muxer_options is the options for Muxer. + /// @param stream_info is the information of this media. + /// @param time_scale is a reference time scale that overrides the time scale + /// specified in @a stream_info. + /// @param container_type is the container of this media. virtual void OnMediaStart(const MuxerOptions& muxer_options, const StreamInfo& stream_info, uint32_t time_scale, @@ -72,16 +76,21 @@ class MuxerListener { /// @param sample_duration in timescale of the media. virtual void OnSampleDurationReady(uint32_t sample_duration) = 0; - // Called when all files are written out and the muxer object does not output - // any more files. - // Note: This event might not be very interesting to MPEG DASH Live profile. - // |init_range_{start,end}| is the byte range of initialization segment, in - // the media file. If |has_init_range| is false, these values are ignored. - // |index_range_{start,end}| is the byte range of segment index, in the media - // file. If |has_index_range| is false, these values are ignored. - // Both ranges are inclusive. - // Media length of |duration_seconds|. - // |file_size| of the media in bytes. + /// Called when all files are written out and the muxer object does not output + /// any more files. + /// Note: This event might not be very interesting to MPEG DASH Live profile. + /// @param has_init_range is true if @a init_range_start and @a init_range_end + /// actually define an initialization range of a segment. The range is + /// inclusive for both start and end. + /// @param init_range_start is the start of the initialization range. + /// @param init_range_end is the end of the initialization range. + /// @param has_index_range is true if @a index_range_start and @a + /// index_range_end actually define an index range of a segment. The + /// range is inclusive for both start and end. + /// @param index_range_start is the start of the index range. + /// @param index_range_end is the end of the index range. + /// @param duration_seconds is the length of the media in seconds. + /// @param file_size is the size of the file in bytes. virtual void OnMediaEnd(bool has_init_range, uint64_t init_range_start, uint64_t init_range_end, @@ -91,12 +100,19 @@ class MuxerListener { float duration_seconds, uint64_t file_size) = 0; - // Called when a segment has been muxed and the file has been written. - // Note: For video on demand (VOD), this would be for subsegments. - // |start_time| and |duration| are relative to time scale specified - // OnMediaStart(). - // |segment_file_size| in bytes. - virtual void OnNewSegment(uint64_t start_time, + /// Called when a segment has been muxed and the file has been written. + /// Note: For some implementations, this is used to signal new subsegments. + /// For example, for generating video on demand (VOD) MPD manifest, this is + /// called to signal subsegments. + /// @param segment_name is the name of the new segment. Note that some + /// implementations may not require this, e.g. if this is a subsegment. + /// @param start_time is the start time of the segment, relative to the + /// timescale specified by MediaInfo passed to OnMediaStart(). + /// @param duration is the duration of the segment, relative to the timescale + /// specified by MediaInfo passed to OnMediaStart(). + /// @param segment_file_size is the segment size in bytes. + virtual void OnNewSegment(const std::string& segment_name, + uint64_t start_time, uint64_t duration, uint64_t segment_file_size) = 0; diff --git a/packager/media/event/vod_media_info_dump_muxer_listener.cc b/packager/media/event/vod_media_info_dump_muxer_listener.cc index 286f26f537..675d2895cc 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener.cc +++ b/packager/media/event/vod_media_info_dump_muxer_listener.cc @@ -28,6 +28,7 @@ VodMediaInfoDumpMuxerListener::~VodMediaInfoDumpMuxerListener() {} void VodMediaInfoDumpMuxerListener::OnEncryptionInfoReady( bool is_initial_encryption_info, const std::vector& default_key_id, + const std::vector& iv, const std::vector& key_system_info) { LOG_IF(WARNING, !is_initial_encryption_info) << "Updating (non initial) encryption info is not supported by " @@ -91,10 +92,10 @@ void VodMediaInfoDumpMuxerListener::OnMediaEnd(bool has_init_range, WriteMediaInfoToFile(*media_info_, output_file_name_); } -void VodMediaInfoDumpMuxerListener::OnNewSegment(uint64_t start_time, +void VodMediaInfoDumpMuxerListener::OnNewSegment(const std::string& file_name, + uint64_t start_time, uint64_t duration, - uint64_t segment_file_size) { -} + uint64_t segment_file_size) {} // static bool VodMediaInfoDumpMuxerListener::WriteMediaInfoToFile( diff --git a/packager/media/event/vod_media_info_dump_muxer_listener.h b/packager/media/event/vod_media_info_dump_muxer_listener.h index 8c1c7b043f..c8fafec66f 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener.h +++ b/packager/media/event/vod_media_info_dump_muxer_listener.h @@ -34,6 +34,7 @@ class VodMediaInfoDumpMuxerListener : public MuxerListener { /// @{ void OnEncryptionInfoReady(bool is_initial_encryption_info, const std::vector& default_key_id, + const std::vector& iv, const std::vector& key_system_info) override; void OnMediaStart(const MuxerOptions& muxer_options, @@ -49,7 +50,8 @@ class VodMediaInfoDumpMuxerListener : public MuxerListener { uint64_t index_range_end, float duration_seconds, uint64_t file_size) override; - void OnNewSegment(uint64_t start_time, + void OnNewSegment(const std::string& file_name, + uint64_t start_time, uint64_t duration, uint64_t segment_file_size) override; /// @} diff --git a/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc b/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc index 0a550e8282..a65a2a3019 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc +++ b/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc @@ -25,6 +25,11 @@ const uint8_t kBogusDefaultKeyId[] = {0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x5f}; +const uint8_t kBogusIv[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x67, 0x83, 0xC3, 0x66, 0xEE, 0xAB, 0xB2, 0xF1, +}; + const bool kInitialEncryptionInfo = true; } // namespace @@ -76,9 +81,10 @@ class VodMediaInfoDumpMuxerListenerTest : public ::testing::Test { std::vector bogus_default_key_id( kBogusDefaultKeyId, kBogusDefaultKeyId + arraysize(kBogusDefaultKeyId)); + std::vector bogus_iv(kBogusIv, kBogusIv + arraysize(kBogusIv)); listener_->OnEncryptionInfoReady(kInitialEncryptionInfo, - bogus_default_key_id, + bogus_default_key_id, bogus_iv, GetDefaultKeySystemInfo()); } listener_->OnMediaStart(muxer_options, stream_info, kReferenceTimeScale, diff --git a/packager/media/formats/mp4/key_rotation_fragmenter.cc b/packager/media/formats/mp4/key_rotation_fragmenter.cc index db763586c2..221c1efbf0 100644 --- a/packager/media/formats/mp4/key_rotation_fragmenter.cc +++ b/packager/media/formats/mp4/key_rotation_fragmenter.cc @@ -81,6 +81,7 @@ Status KeyRotationFragmenter::PrepareFragmentForEncryption( if (muxer_listener_) { muxer_listener_->OnEncryptionInfoReady(!kInitialEncryptionInfo, encryption_key()->key_id, + encryption_key()->iv, encryption_key()->key_system_info); } diff --git a/packager/media/formats/mp4/multi_segment_segmenter.cc b/packager/media/formats/mp4/multi_segment_segmenter.cc index 93b4d4a351..d7a4db7473 100644 --- a/packager/media/formats/mp4/multi_segment_segmenter.cc +++ b/packager/media/formats/mp4/multi_segment_segmenter.cc @@ -148,11 +148,10 @@ Status MultiSegmentSegmenter::WriteSegment() { "Cannot open file for append " + options().output_file_name); } } else { - file = File::Open(GetSegmentName(options().segment_template, - sidx()->earliest_presentation_time, - num_segments_++, - options().bandwidth).c_str(), - "w"); + file_name = GetSegmentName(options().segment_template, + sidx()->earliest_presentation_time, + num_segments_++, options().bandwidth); + file = File::Open(file_name.c_str(), "w"); if (file == NULL) { return Status(error::FILE_FAILURE, "Cannot open file for write " + file_name); @@ -186,8 +185,9 @@ Status MultiSegmentSegmenter::WriteSegment() { UpdateProgress(segment_duration); if (muxer_listener()) { muxer_listener()->OnSampleDurationReady(sample_duration()); - muxer_listener()->OnNewSegment( - sidx()->earliest_presentation_time, segment_duration, segment_size); + muxer_listener()->OnNewSegment(file_name, + sidx()->earliest_presentation_time, + segment_duration, segment_size); } return Status::OK; diff --git a/packager/media/formats/mp4/segmenter.cc b/packager/media/formats/mp4/segmenter.cc index d4ff55c4ae..62b1339bc8 100644 --- a/packager/media/formats/mp4/segmenter.cc +++ b/packager/media/formats/mp4/segmenter.cc @@ -8,6 +8,7 @@ #include +#include "packager/base/logging.h" #include "packager/base/stl_util.h" #include "packager/media/base/aes_cryptor.h" #include "packager/media/base/buffer_writer.h" @@ -214,7 +215,7 @@ Status Segmenter::Initialize(const std::vector& streams, local_protection_scheme, &description); if (muxer_listener_) { muxer_listener_->OnEncryptionInfoReady( - kInitialEncryptionInfo, encryption_key.key_id, + kInitialEncryptionInfo, encryption_key.key_id, encryption_key.iv, encryption_key.key_system_info); } @@ -252,6 +253,7 @@ Status Segmenter::Initialize(const std::vector& streams, if (muxer_listener_) { muxer_listener_->OnEncryptionInfoReady(kInitialEncryptionInfo, encryption_key->key_id, + encryption_key->iv, encryption_key->key_system_info); } } diff --git a/packager/media/formats/mp4/single_segment_segmenter.cc b/packager/media/formats/mp4/single_segment_segmenter.cc index 27e6ce44cf..5b8483b19b 100644 --- a/packager/media/formats/mp4/single_segment_segmenter.cc +++ b/packager/media/formats/mp4/single_segment_segmenter.cc @@ -226,7 +226,8 @@ Status SingleSegmentSegmenter::DoFinalizeSegment() { UpdateProgress(vod_ref.subsegment_duration); if (muxer_listener()) { muxer_listener()->OnSampleDurationReady(sample_duration()); - muxer_listener()->OnNewSegment(vod_ref.earliest_presentation_time, + muxer_listener()->OnNewSegment(options().output_file_name, + vod_ref.earliest_presentation_time, vod_ref.subsegment_duration, segment_size); } return Status::OK; diff --git a/packager/media/formats/webm/encryptor.cc b/packager/media/formats/webm/encryptor.cc index d029fc0dfd..954b9cb5aa 100644 --- a/packager/media/formats/webm/encryptor.cc +++ b/packager/media/formats/webm/encryptor.cc @@ -120,6 +120,7 @@ Status Encryptor::CreateEncryptor(MuxerListener* muxer_listener, const bool kInitialEncryptionInfo = true; muxer_listener->OnEncryptionInfoReady(kInitialEncryptionInfo, encryption_key->key_id, + encryptor->iv(), encryption_key->key_system_info); } diff --git a/packager/media/formats/webm/multi_segment_segmenter.cc b/packager/media/formats/webm/multi_segment_segmenter.cc index 3d6dbba8c9..a011059e12 100644 --- a/packager/media/formats/webm/multi_segment_segmenter.cc +++ b/packager/media/formats/webm/multi_segment_segmenter.cc @@ -50,7 +50,8 @@ Status MultiSegmentSegmenter::FinalizeSegment() { const uint64_t start_timescale = FromWebMTimecode(start_webm_timecode); const uint64_t length = static_cast( cluster_length_sec() * info()->time_scale()); - muxer_listener()->OnNewSegment(start_timescale, length, size); + muxer_listener()->OnNewSegment(writer_->file()->file_name(), + start_timescale, length, size); } VLOG(1) << "WEBM file '" << writer_->file()->file_name() << "' finalized.";