diff --git a/packager/app/packager_main.cc b/packager/app/packager_main.cc index 284dc95a8f..1104820d50 100644 --- a/packager/app/packager_main.cc +++ b/packager/app/packager_main.cc @@ -34,6 +34,7 @@ #include "packager/media/base/key_source.h" #include "packager/media/base/muxer_options.h" #include "packager/media/base/muxer_util.h" +#include "packager/media/chunking/chunking_handler.h" #include "packager/media/event/hls_notify_muxer_listener.h" #include "packager/media/event/mpd_notify_muxer_listener.h" #include "packager/media/event/vod_media_info_dump_muxer_listener.h" @@ -235,6 +236,7 @@ std::shared_ptr CreateOutputMuxer(const MuxerOptions& options, } bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors, + const ChunkingOptions& chunking_options, const MuxerOptions& muxer_options, FakeClock* fake_clock, KeySource* key_source, @@ -361,11 +363,15 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors, if (muxer_listener) muxer->SetMuxerListener(std::move(muxer_listener)); + auto chunking_handler = std::make_shared(chunking_options); + Status status = chunking_handler->SetHandler(0, std::move(muxer)); + auto* demuxer = remux_jobs->back()->demuxer(); const std::string& stream_selector = stream_iter->stream_selector; - Status status = demuxer->SetHandler(stream_selector, std::move(muxer)); + status.Update(demuxer->SetHandler(stream_selector, chunking_handler)); + if (!status.ok()) { - LOG(ERROR) << "Demuxer::SetHandler failed " << status; + LOG(ERROR) << "Failed to setup graph: " << status; return false; } if (!stream_iter->language.empty()) @@ -426,10 +432,8 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) { return false; } - // Get basic muxer options. - MuxerOptions muxer_options; - if (!GetMuxerOptions(&muxer_options)) - return false; + ChunkingOptions chunking_options = GetChunkingOptions(); + MuxerOptions muxer_options = GetMuxerOptions(); DCHECK(!stream_descriptors.empty()); // On demand profile generates single file segment while live profile @@ -451,9 +455,7 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) { return false; } - MpdOptions mpd_options; - if (!GetMpdOptions(on_demand_dash_profile, &mpd_options)) - return false; + MpdOptions mpd_options = GetMpdOptions(on_demand_dash_profile); // Create encryption key source if needed. std::unique_ptr encryption_key_source; @@ -495,9 +497,9 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) { std::vector> remux_jobs; FakeClock fake_clock; - if (!CreateRemuxJobs(stream_descriptors, muxer_options, &fake_clock, - encryption_key_source.get(), mpd_notifier.get(), - hls_notifier.get(), &remux_jobs)) { + if (!CreateRemuxJobs(stream_descriptors, chunking_options, muxer_options, + &fake_clock, encryption_key_source.get(), + mpd_notifier.get(), hls_notifier.get(), &remux_jobs)) { return false; } diff --git a/packager/app/packager_util.cc b/packager/app/packager_util.cc index 13158ebf2b..d7b02c7dfa 100644 --- a/packager/app/packager_util.cc +++ b/packager/app/packager_util.cc @@ -21,6 +21,7 @@ #include "packager/media/base/playready_key_source.h" #include "packager/media/base/request_signer.h" #include "packager/media/base/widevine_key_source.h" +#include "packager/media/chunking/chunking_handler.h" #include "packager/media/file/file.h" #include "packager/mpd/base/mpd_options.h" @@ -146,45 +147,45 @@ std::unique_ptr CreateDecryptionKeySource() { return decryption_key_source; } -bool GetMuxerOptions(MuxerOptions* muxer_options) { - DCHECK(muxer_options); +ChunkingOptions GetChunkingOptions() { + ChunkingOptions chunking_options; + chunking_options.segment_duration_in_seconds = FLAGS_segment_duration; + chunking_options.subsegment_duration_in_seconds = FLAGS_fragment_duration; + chunking_options.segment_sap_aligned = FLAGS_segment_sap_aligned; + chunking_options.subsegment_sap_aligned = FLAGS_fragment_sap_aligned; + return chunking_options; +} - muxer_options->segment_duration = FLAGS_segment_duration; - muxer_options->fragment_duration = FLAGS_fragment_duration; - muxer_options->segment_sap_aligned = FLAGS_segment_sap_aligned; - muxer_options->fragment_sap_aligned = FLAGS_fragment_sap_aligned; - muxer_options->num_subsegments_per_sidx = FLAGS_num_subsegments_per_sidx; - muxer_options->webm_subsample_encryption = FLAGS_webm_subsample_encryption; +MuxerOptions GetMuxerOptions() { + MuxerOptions muxer_options; + muxer_options.num_subsegments_per_sidx = FLAGS_num_subsegments_per_sidx; + muxer_options.webm_subsample_encryption = FLAGS_webm_subsample_encryption; if (FLAGS_mp4_use_decoding_timestamp_in_timeline) { LOG(WARNING) << "Flag --mp4_use_decoding_timestamp_in_timeline is set. " "Note that it is a temporary hack to workaround Chromium " "bug https://crbug.com/398130. The flag may be removed " "when the Chromium bug is fixed."; } - muxer_options->mp4_use_decoding_timestamp_in_timeline = + muxer_options.mp4_use_decoding_timestamp_in_timeline = FLAGS_mp4_use_decoding_timestamp_in_timeline; - - muxer_options->temp_dir = FLAGS_temp_dir; - return true; + muxer_options.temp_dir = FLAGS_temp_dir; + return muxer_options; } -bool GetMpdOptions(bool on_demand_profile, MpdOptions* mpd_options) { - DCHECK(mpd_options); - - mpd_options->dash_profile = +MpdOptions GetMpdOptions(bool on_demand_profile) { + MpdOptions mpd_options; + mpd_options.dash_profile = on_demand_profile ? DashProfile::kOnDemand : DashProfile::kLive; - mpd_options->mpd_type = - (on_demand_profile || FLAGS_generate_static_mpd) - ? MpdType::kStatic - : MpdType::kDynamic; - mpd_options->availability_time_offset = FLAGS_availability_time_offset; - mpd_options->minimum_update_period = FLAGS_minimum_update_period; - mpd_options->min_buffer_time = FLAGS_min_buffer_time; - mpd_options->time_shift_buffer_depth = FLAGS_time_shift_buffer_depth; - mpd_options->suggested_presentation_delay = - FLAGS_suggested_presentation_delay; - mpd_options->default_language = FLAGS_default_language; - return true; + mpd_options.mpd_type = (on_demand_profile || FLAGS_generate_static_mpd) + ? MpdType::kStatic + : MpdType::kDynamic; + mpd_options.availability_time_offset = FLAGS_availability_time_offset; + mpd_options.minimum_update_period = FLAGS_minimum_update_period; + mpd_options.min_buffer_time = FLAGS_min_buffer_time; + mpd_options.time_shift_buffer_depth = FLAGS_time_shift_buffer_depth; + mpd_options.suggested_presentation_delay = FLAGS_suggested_presentation_delay; + mpd_options.default_language = FLAGS_default_language; + return mpd_options; } } // namespace media diff --git a/packager/app/packager_util.h b/packager/app/packager_util.h index 684ced2ffd..efd3b53359 100644 --- a/packager/app/packager_util.h +++ b/packager/app/packager_util.h @@ -22,6 +22,7 @@ struct MpdOptions; namespace media { class KeySource; +struct ChunkingOptions; struct MuxerOptions; /// Create KeySource based on provided command line options for content @@ -36,11 +37,14 @@ std::unique_ptr CreateEncryptionKeySource(); /// decryption is not required. std::unique_ptr CreateDecryptionKeySource(); -/// Fill MuxerOptions members using provided command line options. -bool GetMuxerOptions(MuxerOptions* muxer_options); +/// @return ChunkingOptions from provided command line options. +ChunkingOptions GetChunkingOptions(); -/// Fill MpdOptions members using provided command line options. -bool GetMpdOptions(bool on_demand_profile, MpdOptions* mpd_options); +/// @return MuxerOptions from provided command line options. +MuxerOptions GetMuxerOptions(); + +/// @return MpdOptions from provided command line options. +MpdOptions GetMpdOptions(bool on_demand_profile); } // namespace media } // namespace shaka diff --git a/packager/app/test/testdata/bear-320x240-vorbis-golden.webm b/packager/app/test/testdata/bear-320x240-vorbis-golden.webm index b764a53aa8..dee18892f7 100644 Binary files a/packager/app/test/testdata/bear-320x240-vorbis-golden.webm and b/packager/app/test/testdata/bear-320x240-vorbis-golden.webm differ diff --git a/packager/app/test/testdata/bear-640x360-a-cbc1-golden.mp4 b/packager/app/test/testdata/bear-640x360-a-cbc1-golden.mp4 index 72a64d7631..626fab7cc9 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-cbc1-golden.mp4 and b/packager/app/test/testdata/bear-640x360-a-cbc1-golden.mp4 differ diff --git a/packager/app/test/testdata/bear-640x360-a-cbcs-golden.mp4 b/packager/app/test/testdata/bear-640x360-a-cbcs-golden.mp4 index 0e8a01563b..d155296c3b 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-cbcs-golden.mp4 and b/packager/app/test/testdata/bear-640x360-a-cbcs-golden.mp4 differ diff --git a/packager/app/test/testdata/bear-640x360-a-cenc-golden.mp4 b/packager/app/test/testdata/bear-640x360-a-cenc-golden.mp4 index e0b1c3c07e..a1192dce82 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-cenc-golden.mp4 and b/packager/app/test/testdata/bear-640x360-a-cenc-golden.mp4 differ diff --git a/packager/app/test/testdata/bear-640x360-a-cens-golden.mp4 b/packager/app/test/testdata/bear-640x360-a-cens-golden.mp4 index bf6c5b5908..897dbd4b5a 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-cens-golden.mp4 and b/packager/app/test/testdata/bear-640x360-a-cens-golden.mp4 differ diff --git a/packager/app/test/testdata/bear-640x360-a-enc-golden-1.ts b/packager/app/test/testdata/bear-640x360-a-enc-golden-1.ts index 50d83fd065..c492a868c3 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-enc-golden-1.ts and b/packager/app/test/testdata/bear-640x360-a-enc-golden-1.ts differ diff --git a/packager/app/test/testdata/bear-640x360-a-enc-golden-2.ts b/packager/app/test/testdata/bear-640x360-a-enc-golden-2.ts index 1f115bcd44..5f386328e3 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-enc-golden-2.ts and b/packager/app/test/testdata/bear-640x360-a-enc-golden-2.ts differ diff --git a/packager/app/test/testdata/bear-640x360-a-enc-golden-3.ts b/packager/app/test/testdata/bear-640x360-a-enc-golden-3.ts index d00f931a80..fafb892ed8 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-enc-golden-3.ts and b/packager/app/test/testdata/bear-640x360-a-enc-golden-3.ts differ diff --git a/packager/app/test/testdata/bear-640x360-a-enc-golden.m3u8 b/packager/app/test/testdata/bear-640x360-a-enc-golden.m3u8 index ad8b0a8560..d352c7c2d3 100644 --- a/packager/app/test/testdata/bear-640x360-a-enc-golden.m3u8 +++ b/packager/app/test/testdata/bear-640x360-a-enc-golden.m3u8 @@ -3,12 +3,12 @@ ## Generated with https://github.com/google/shaka-packager version -- #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD -#EXTINF:1.021, +#EXTINF:0.975, output_audio-1.ts +#EXTINF:0.998, +output_audio-2.ts #EXT-X-DISCONTINUITY #EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity" -#EXTINF:1.021, -output_audio-2.ts -#EXTINF:0.720, +#EXTINF:0.789, output_audio-3.ts #EXT-X-ENDLIST diff --git a/packager/app/test/testdata/bear-640x360-a-golden-1.ts b/packager/app/test/testdata/bear-640x360-a-golden-1.ts index 50d83fd065..c492a868c3 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-golden-1.ts and b/packager/app/test/testdata/bear-640x360-a-golden-1.ts differ diff --git a/packager/app/test/testdata/bear-640x360-a-golden-2.ts b/packager/app/test/testdata/bear-640x360-a-golden-2.ts index 6e8503c696..5f386328e3 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-golden-2.ts and b/packager/app/test/testdata/bear-640x360-a-golden-2.ts differ diff --git a/packager/app/test/testdata/bear-640x360-a-golden-3.ts b/packager/app/test/testdata/bear-640x360-a-golden-3.ts index b877ffdbf7..fcfe141d34 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-golden-3.ts and b/packager/app/test/testdata/bear-640x360-a-golden-3.ts differ diff --git a/packager/app/test/testdata/bear-640x360-a-golden.m3u8 b/packager/app/test/testdata/bear-640x360-a-golden.m3u8 index 4dfd5d3c6f..75059c47a7 100644 --- a/packager/app/test/testdata/bear-640x360-a-golden.m3u8 +++ b/packager/app/test/testdata/bear-640x360-a-golden.m3u8 @@ -3,10 +3,10 @@ ## Generated with https://github.com/google/shaka-packager version -- #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD -#EXTINF:1.021, +#EXTINF:0.975, output_audio-1.ts -#EXTINF:1.021, +#EXTINF:0.998, output_audio-2.ts -#EXTINF:0.720, +#EXTINF:0.789, output_audio-3.ts #EXT-X-ENDLIST diff --git a/packager/app/test/testdata/bear-640x360-a-golden.mp4 b/packager/app/test/testdata/bear-640x360-a-golden.mp4 index b1cfb2bc35..e1e27bde3a 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-golden.mp4 and b/packager/app/test/testdata/bear-640x360-a-golden.mp4 differ diff --git a/packager/app/test/testdata/bear-640x360-a-live-cenc-golden-2.m4s b/packager/app/test/testdata/bear-640x360-a-live-cenc-golden-2.m4s index 99823da348..ac2742b0cb 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-live-cenc-golden-2.m4s and b/packager/app/test/testdata/bear-640x360-a-live-cenc-golden-2.m4s differ diff --git a/packager/app/test/testdata/bear-640x360-a-live-cenc-golden-3.m4s b/packager/app/test/testdata/bear-640x360-a-live-cenc-golden-3.m4s index cfb7554998..e5295affc5 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-live-cenc-golden-3.m4s and b/packager/app/test/testdata/bear-640x360-a-live-cenc-golden-3.m4s differ diff --git a/packager/app/test/testdata/bear-640x360-a-live-cenc-rotation-golden-2.m4s b/packager/app/test/testdata/bear-640x360-a-live-cenc-rotation-golden-2.m4s index 23b514c520..057782c951 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-live-cenc-rotation-golden-2.m4s and b/packager/app/test/testdata/bear-640x360-a-live-cenc-rotation-golden-2.m4s differ diff --git a/packager/app/test/testdata/bear-640x360-a-live-cenc-rotation-golden-3.m4s b/packager/app/test/testdata/bear-640x360-a-live-cenc-rotation-golden-3.m4s index d924e4bcad..cc56e2ad51 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-live-cenc-rotation-golden-3.m4s and b/packager/app/test/testdata/bear-640x360-a-live-cenc-rotation-golden-3.m4s differ diff --git a/packager/app/test/testdata/bear-640x360-a-live-golden-2.m4s b/packager/app/test/testdata/bear-640x360-a-live-golden-2.m4s index 1ac70f0d41..8871a6a0d9 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-live-golden-2.m4s and b/packager/app/test/testdata/bear-640x360-a-live-golden-2.m4s differ diff --git a/packager/app/test/testdata/bear-640x360-a-live-golden-3.m4s b/packager/app/test/testdata/bear-640x360-a-live-golden-3.m4s index 8c4542ca9d..76937b2cb9 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-live-golden-3.m4s and b/packager/app/test/testdata/bear-640x360-a-live-golden-3.m4s differ diff --git a/packager/app/test/testdata/bear-640x360-a-por-BR-golden.mp4 b/packager/app/test/testdata/bear-640x360-a-por-BR-golden.mp4 index d50176a494..e2b16076c3 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-por-BR-golden.mp4 and b/packager/app/test/testdata/bear-640x360-a-por-BR-golden.mp4 differ diff --git a/packager/app/test/testdata/bear-640x360-a-por-golden.mp4 b/packager/app/test/testdata/bear-640x360-a-por-golden.mp4 index d50176a494..e2b16076c3 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-por-golden.mp4 and b/packager/app/test/testdata/bear-640x360-a-por-golden.mp4 differ 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 index 165ca15afe..f3be2834f1 100644 --- a/packager/app/test/testdata/bear-640x360-av-enc-master-golden.m3u8 +++ b/packager/app/test/testdata/bear-640x360-av-enc-master-golden.m3u8 @@ -1,5 +1,5 @@ #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:AUDIO="audio",CODECS="avc1.64001e,mp4a.40.2",BANDWIDTH=1217603 +#EXT-X-STREAM-INF:AUDIO="audio",CODECS="avc1.64001e,mp4a.40.2",BANDWIDTH=1217518 video.m3u8 diff --git a/packager/app/test/testdata/bear-640x360-av-live-cenc-golden.mpd b/packager/app/test/testdata/bear-640x360-av-live-cenc-golden.mpd index 74c7f215b7..7518715304 100644 --- a/packager/app/test/testdata/bear-640x360-av-live-cenc-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-live-cenc-golden.mpd @@ -21,12 +21,13 @@ AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - + - - + + + diff --git a/packager/app/test/testdata/bear-640x360-av-live-cenc-non-iop-golden.mpd b/packager/app/test/testdata/bear-640x360-av-live-cenc-non-iop-golden.mpd index d6c6df6147..e3768f912d 100644 --- a/packager/app/test/testdata/bear-640x360-av-live-cenc-non-iop-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-live-cenc-non-iop-golden.mpd @@ -17,7 +17,7 @@ - + @@ -25,8 +25,9 @@ - - + + + diff --git a/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-golden.mpd b/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-golden.mpd index ee85437b08..07b81749c6 100644 --- a/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-golden.mpd @@ -17,12 +17,13 @@ - + - - + + + diff --git a/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-non-iop-golden.mpd b/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-non-iop-golden.mpd index acac1aef23..4adc7ed781 100644 --- a/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-non-iop-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-live-cenc-rotation-non-iop-golden.mpd @@ -15,14 +15,15 @@ - + - - + + + diff --git a/packager/app/test/testdata/bear-640x360-av-live-golden.mpd b/packager/app/test/testdata/bear-640x360-av-live-golden.mpd index 3d2b5a017e..125a7785a5 100644 --- a/packager/app/test/testdata/bear-640x360-av-live-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-live-golden.mpd @@ -13,12 +13,13 @@ - + - - + + + diff --git a/packager/app/test/testdata/bear-640x360-av-live-static-golden.mpd b/packager/app/test/testdata/bear-640x360-av-live-static-golden.mpd index d455b643e6..dfe32584f7 100644 --- a/packager/app/test/testdata/bear-640x360-av-live-static-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-live-static-golden.mpd @@ -13,12 +13,13 @@ - + - - + + + diff --git a/packager/app/test/testdata/bear-640x360-av-master-golden.m3u8 b/packager/app/test/testdata/bear-640x360-av-master-golden.m3u8 index 165ca15afe..f3be2834f1 100644 --- a/packager/app/test/testdata/bear-640x360-av-master-golden.m3u8 +++ b/packager/app/test/testdata/bear-640x360-av-master-golden.m3u8 @@ -1,5 +1,5 @@ #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:AUDIO="audio",CODECS="avc1.64001e,mp4a.40.2",BANDWIDTH=1217603 +#EXT-X-STREAM-INF:AUDIO="audio",CODECS="avc1.64001e,mp4a.40.2",BANDWIDTH=1217518 video.m3u8 diff --git a/packager/media/base/media_handler.cc b/packager/media/base/media_handler.cc index 0b91c0f8ba..84758de4d3 100644 --- a/packager/media/base/media_handler.cc +++ b/packager/media/base/media_handler.cc @@ -11,7 +11,7 @@ namespace media { Status MediaHandler::SetHandler(int output_stream_index, std::shared_ptr handler) { - if (!ValidateOutputStreamIndex(output_stream_index)) + if (output_stream_index < 0) return Status(error::INVALID_ARGUMENT, "Invalid output stream index"); if (output_handlers_.find(output_stream_index) != output_handlers_.end()) { return Status(error::ALREADY_EXISTS, @@ -30,6 +30,8 @@ Status MediaHandler::Initialize() { if (!status.ok()) return status; for (auto& pair : output_handlers_) { + if (!ValidateOutputStreamIndex(pair.first)) + return Status(error::INVALID_ARGUMENT, "Invalid output stream index"); status = pair.second.first->Initialize(); if (!status.ok()) return status; diff --git a/packager/media/base/muxer.cc b/packager/media/base/muxer.cc index d3cda19f4a..87edbf79ee 100644 --- a/packager/media/base/muxer.cc +++ b/packager/media/base/muxer.cc @@ -64,8 +64,12 @@ Status Muxer::Process(std::unique_ptr stream_data) { case StreamDataType::kStreamInfo: streams_.push_back(std::move(stream_data->stream_info)); return InitializeMuxer(); + case StreamDataType::kSegmentInfo: + return FinalizeSegment(stream_data->stream_index, + std::move(stream_data->segment_info)); case StreamDataType::kMediaSample: - return DoAddSample(stream_data->media_sample); + return AddSample(stream_data->stream_index, + std::move(stream_data->media_sample)); default: VLOG(3) << "Stream data type " << static_cast(stream_data->stream_data_type) << " ignored."; diff --git a/packager/media/base/muxer.h b/packager/media/base/muxer.h index 0aa1dcd6a5..2c62cc08e4 100644 --- a/packager/media/base/muxer.h +++ b/packager/media/base/muxer.h @@ -123,8 +123,13 @@ class Muxer : public MediaHandler { // Final clean up. virtual Status Finalize() = 0; - // AddSample implementation. - virtual Status DoAddSample(std::shared_ptr sample) = 0; + // Add a new sample. + virtual Status AddSample(int stream_id, + std::shared_ptr sample) = 0; + + // Finalize the segment or subsegment. + virtual Status FinalizeSegment(int stream_id, + std::shared_ptr segment_info) = 0; MuxerOptions options_; std::vector> streams_; diff --git a/packager/media/base/muxer_options.h b/packager/media/base/muxer_options.h index 327c437c82..14dc73b6f2 100644 --- a/packager/media/base/muxer_options.h +++ b/packager/media/base/muxer_options.h @@ -19,25 +19,6 @@ struct MuxerOptions { MuxerOptions(); ~MuxerOptions(); - /// Segment duration in seconds. If single_segment is specified, this - /// parameter sets the duration of a subsegment; otherwise, this parameter - /// sets the duration of a segment. A segment can contain one or many - /// fragments. - double segment_duration = 0; - - /// Fragment duration in seconds. Should not be larger than the segment - /// duration. - double fragment_duration = 0; - - /// Force segments to begin with stream access points. Segment duration may - /// not be exactly what specified by segment_duration. - bool segment_sap_aligned = false; - - /// Force fragments to begin with stream access points. Fragment duration - /// may not be exactly what specified by segment_duration. Setting to true - /// implies that segment_sap_aligned is true as well. - bool fragment_sap_aligned = false; - /// For ISO BMFF only. /// Set the number of subsegments in each SIDX box. If 0, a single SIDX box /// is used per segment. If -1, no SIDX box is used. Otherwise, the Muxer diff --git a/packager/media/chunking/chunking_handler.cc b/packager/media/chunking/chunking_handler.cc index 247a52abb1..562edf164b 100644 --- a/packager/media/chunking/chunking_handler.cc +++ b/packager/media/chunking/chunking_handler.cc @@ -142,6 +142,8 @@ Status ChunkingHandler::ProcessMediaSample(const MediaSample* sample) { const int64_t segment_index = timestamp / segment_duration_; if (segment_index != current_segment_index_) { current_segment_index_ = segment_index; + // Reset subsegment index. + current_subsegment_index_ = 0; new_segment = true; } } diff --git a/packager/media/chunking/chunking_handler_unittest.cc b/packager/media/chunking/chunking_handler_unittest.cc index 608b546300..5e1072311f 100644 --- a/packager/media/chunking/chunking_handler_unittest.cc +++ b/packager/media/chunking/chunking_handler_unittest.cc @@ -86,6 +86,30 @@ TEST_F(ChunkingHandlerTest, AudioNoSubsegmentsThenFlush) { kDuration1 * 2, !kIsSubsegment))); } +TEST_F(ChunkingHandlerTest, AudioWithSubsegments) { + ChunkingOptions chunking_options; + chunking_options.segment_duration_in_seconds = 1; + chunking_options.subsegment_duration_in_seconds = 0.5; + SetUpChunkingHandler(1, chunking_options); + + ASSERT_OK(Process(GetAudioStreamInfoStreamData(kStreamIndex0, kTimeScale0))); + for (int i = 0; i < 5; ++i) { + ASSERT_OK(Process(GetMediaSampleStreamData(kStreamIndex0, i * kDuration1, + kDuration1, kKeyFrame))); + } + EXPECT_THAT( + GetOutputStreamDataVector(), + ElementsAre( + IsStreamInfo(kStreamIndex0, kTimeScale0, !kEncrypted), + IsMediaSample(kStreamIndex0, 0, kDuration1), + IsMediaSample(kStreamIndex0, kDuration1, kDuration1), + IsSegmentInfo(kStreamIndex0, 0, kDuration1 * 2, kIsSubsegment), + IsMediaSample(kStreamIndex0, 2 * kDuration1, kDuration1), + IsSegmentInfo(kStreamIndex0, 0, kDuration1 * 3, !kIsSubsegment), + IsMediaSample(kStreamIndex0, 3 * kDuration1, kDuration1), + IsMediaSample(kStreamIndex0, 4 * kDuration1, kDuration1))); +} + TEST_F(ChunkingHandlerTest, VideoAndSubsegmentAndNonzeroStart) { ChunkingOptions chunking_options; chunking_options.segment_duration_in_seconds = 1; diff --git a/packager/media/crypto/encryption_handler_unittest.cc b/packager/media/crypto/encryption_handler_unittest.cc index fcf3a64ef5..bbb27fce84 100644 --- a/packager/media/crypto/encryption_handler_unittest.cc +++ b/packager/media/crypto/encryption_handler_unittest.cc @@ -91,8 +91,9 @@ TEST_F(EncryptionHandlerTest, Initialize) { TEST_F(EncryptionHandlerTest, OnlyOneOutput) { // Connecting another handler will fail. + ASSERT_OK(encryption_handler_->AddHandler(some_handler())); ASSERT_EQ(error::INVALID_ARGUMENT, - encryption_handler_->AddHandler(some_handler()).error_code()); + encryption_handler_->Initialize().error_code()); } TEST_F(EncryptionHandlerTest, OnlyOneInput) { diff --git a/packager/media/event/mpd_notify_muxer_listener_unittest.cc b/packager/media/event/mpd_notify_muxer_listener_unittest.cc index 16feac4a2a..ec7819aca4 100644 --- a/packager/media/event/mpd_notify_muxer_listener_unittest.cc +++ b/packager/media/event/mpd_notify_muxer_listener_unittest.cc @@ -44,10 +44,6 @@ MediaInfo ConvertToMediaInfo(const std::string& media_info_string) { } void SetDefaultLiveMuxerOptionsValues(media::MuxerOptions* muxer_options) { - muxer_options->segment_duration = 10.0; - muxer_options->fragment_duration = 10.0; - muxer_options->segment_sap_aligned = true; - muxer_options->fragment_sap_aligned = true; muxer_options->num_subsegments_per_sidx = 0; muxer_options->output_file_name = "liveinit.mp4"; muxer_options->segment_template = "live-$NUMBER$.mp4"; diff --git a/packager/media/event/muxer_listener_test_helper.cc b/packager/media/event/muxer_listener_test_helper.cc index f309ad6602..ff23526a26 100644 --- a/packager/media/event/muxer_listener_test_helper.cc +++ b/packager/media/event/muxer_listener_test_helper.cc @@ -73,10 +73,6 @@ OnMediaEndParameters GetDefaultOnMediaEndParams() { } void SetDefaultMuxerOptionsValues(MuxerOptions* muxer_options) { - muxer_options->segment_duration = 10.0; - muxer_options->fragment_duration = 10.0; - muxer_options->segment_sap_aligned = true; - muxer_options->fragment_sap_aligned = true; muxer_options->num_subsegments_per_sidx = 0; muxer_options->output_file_name = "test_output_file_name.mp4"; muxer_options->segment_template.clear(); diff --git a/packager/media/formats/mp2t/ts_muxer.cc b/packager/media/formats/mp2t/ts_muxer.cc index db688c728c..611fbf4409 100644 --- a/packager/media/formats/mp2t/ts_muxer.cc +++ b/packager/media/formats/mp2t/ts_muxer.cc @@ -34,10 +34,20 @@ Status TsMuxer::Finalize() { return segmenter_->Finalize(); } -Status TsMuxer::DoAddSample(std::shared_ptr sample) { +Status TsMuxer::AddSample(int stream_id, std::shared_ptr sample) { + DCHECK_EQ(stream_id, 0); return segmenter_->AddSample(sample); } +Status TsMuxer::FinalizeSegment(int stream_id, + std::shared_ptr segment_info) { + DCHECK_EQ(stream_id, 0); + return segment_info->is_subsegment + ? Status::OK + : segmenter_->FinalizeSegment(segment_info->start_timestamp, + segment_info->duration); +} + void TsMuxer::FireOnMediaStartEvent() { if (!muxer_listener()) return; diff --git a/packager/media/formats/mp2t/ts_muxer.h b/packager/media/formats/mp2t/ts_muxer.h index 3308023e23..9271fd7910 100644 --- a/packager/media/formats/mp2t/ts_muxer.h +++ b/packager/media/formats/mp2t/ts_muxer.h @@ -26,7 +26,9 @@ class TsMuxer : public Muxer { // Muxer implementation. Status InitializeMuxer() override; Status Finalize() override; - Status DoAddSample(std::shared_ptr sample) override; + Status AddSample(int stream_id, std::shared_ptr sample) override; + Status FinalizeSegment(int stream_id, + std::shared_ptr sample) override; void FireOnMediaStartEvent(); void FireOnMediaEndEvent(); diff --git a/packager/media/formats/mp2t/ts_segmenter.cc b/packager/media/formats/mp2t/ts_segmenter.cc index e74bd21e2c..b6fe894080 100644 --- a/packager/media/formats/mp2t/ts_segmenter.cc +++ b/packager/media/formats/mp2t/ts_segmenter.cc @@ -82,21 +82,10 @@ Status TsSegmenter::Initialize(const StreamInfo& stream_info, } Status TsSegmenter::Finalize() { - return Flush(); + return Status::OK; } -// First checks whether the sample is a key frame. If so and the segment has -// passed the segment duration, then flush the generator and write all the data -// to file. Status TsSegmenter::AddSample(std::shared_ptr sample) { - const bool passed_segment_duration = - current_segment_total_sample_duration_ > muxer_options_.segment_duration; - if (sample->is_key_frame() && passed_segment_duration) { - Status status = Flush(); - if (!status.ok()) - return status; - } - if (!ts_writer_file_opened_ && !sample->is_key_frame()) LOG(WARNING) << "A segment will start with a non key frame."; @@ -104,11 +93,6 @@ Status TsSegmenter::AddSample(std::shared_ptr sample) { return Status(error::MUXER_FAILURE, "Failed to add sample to PesPacketGenerator."); } - - const double scaled_sample_duration = sample->duration() * timescale_scale_; - current_segment_total_sample_duration_ += - scaled_sample_duration / kTsTimescale; - return WritePesPacketsToFile(); } @@ -133,7 +117,6 @@ Status TsSegmenter::OpenNewSegmentIfClosed(uint32_t next_pts) { segment_number_++, muxer_options_.bandwidth); if (!ts_writer_->NewSegment(segment_name)) return Status(error::MUXER_FAILURE, "Failed to initilize TsPacketWriter."); - current_segment_start_time_ = next_pts; current_segment_path_ = segment_name; ts_writer_file_opened_ = true; return Status::OK; @@ -154,7 +137,8 @@ Status TsSegmenter::WritePesPacketsToFile() { return Status::OK; } -Status TsSegmenter::Flush() { +Status TsSegmenter::FinalizeSegment(uint64_t start_timestamp, + uint64_t duration) { if (!pes_packet_generator_->Flush()) { return Status(error::MUXER_FAILURE, "Failed to flush PesPacketGenerator."); @@ -172,15 +156,13 @@ Status TsSegmenter::Flush() { if (listener_) { const int64_t file_size = File::GetFileSize(current_segment_path_.c_str()); - listener_->OnNewSegment( - current_segment_path_, current_segment_start_time_, - current_segment_total_sample_duration_ * kTsTimescale, file_size); + listener_->OnNewSegment(current_segment_path_, + start_timestamp * timescale_scale_, + duration * timescale_scale_, file_size); } ts_writer_file_opened_ = false; - total_duration_in_seconds_ += current_segment_total_sample_duration_; + total_duration_in_seconds_ += duration * timescale_scale_ / kTsTimescale; } - current_segment_total_sample_duration_ = 0.0; - current_segment_start_time_ = 0; current_segment_path_.clear(); return NotifyEncrypted(); } diff --git a/packager/media/formats/mp2t/ts_segmenter.h b/packager/media/formats/mp2t/ts_segmenter.h index 47ff1e7e9f..347207585f 100644 --- a/packager/media/formats/mp2t/ts_segmenter.h +++ b/packager/media/formats/mp2t/ts_segmenter.h @@ -54,6 +54,18 @@ class TsSegmenter { /// @return OK on success. Status AddSample(std::shared_ptr sample); + /// Flush all the samples that are (possibly) buffered and write them to the + /// current segment, this will close the file. If a file is not already opened + /// before calling this, this will open one and write them to file. + /// @param start_timestamp is the segment's start timestamp in the input + /// stream's time scale. + /// @param duration is the segment's duration in the input stream's time + /// scale. + // TODO(kqyang): Remove the usage of segment start timestamp and duration in + // xx_segmenter, which could cause confusions on which is the source of truth + // as the segment start timestamp and duration could be tracked locally. + Status FinalizeSegment(uint64_t start_timestamp, uint64_t duration); + /// Only for testing. void InjectTsWriterForTesting(std::unique_ptr writer); @@ -71,11 +83,6 @@ class TsSegmenter { // it will open one. This will not close the file. Status WritePesPacketsToFile(); - // Flush all the samples that are (possibly) buffered and write them to the - // current segment, this will close the file. If a file is not already opened - // before calling this, this will open one and write them to file. - Status Flush(); - // If conditions are met, notify objects that the data is encrypted. Status NotifyEncrypted(); @@ -86,12 +93,6 @@ class TsSegmenter { // Used for calculating the duration in seconds fo the current segment. double timescale_scale_ = 1.0; - // This is the sum of the durations of the samples that were added to - // PesPacketGenerator for the current segment (in seconds). Note that this is - // not necessarily the same as the length of the PesPackets that have been - // written to the current segment in WritePesPacketsToFile(). - double current_segment_total_sample_duration_ = 0.0; - // Used for segment template. uint64_t segment_number_ = 0; @@ -102,7 +103,6 @@ class TsSegmenter { std::unique_ptr pes_packet_generator_; // For OnNewSegment(). - uint64_t current_segment_start_time_ = 0; // Path of the current segment so that File::GetFileSize() can be used after // the segment has been finalized. std::string current_segment_path_; diff --git a/packager/media/formats/mp2t/ts_segmenter_unittest.cc b/packager/media/formats/mp2t/ts_segmenter_unittest.cc index b6a59d95e4..3b5d3bf45a 100644 --- a/packager/media/formats/mp2t/ts_segmenter_unittest.cc +++ b/packager/media/formats/mp2t/ts_segmenter_unittest.cc @@ -133,7 +133,6 @@ TEST_F(TsSegmenterTest, AddSample) { arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; - options.segment_duration = 10.0; options.segment_template = "file$Number$.ts"; TsSegmenter segmenter(options, nullptr); @@ -175,10 +174,7 @@ TEST_F(TsSegmenterTest, AddSample) { EXPECT_OK(segmenter.AddSample(sample)); } -// Verify the case where the segment is long enough and the current segment -// should be closed. -// This will add 2 samples and verify that the first segment is closed when the -// second sample is added. +// This will add one sample then finalize segment then add another sample. TEST_F(TsSegmenterTest, PassedSegmentDuration) { // Use something significantly smaller than 90000 to check that the scaling is // done correctly in the segmenter. @@ -188,7 +184,6 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; - options.segment_duration = 10.0; options.segment_template = "file$Number$.ts"; MockMuxerListener mock_listener; @@ -202,21 +197,18 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { std::shared_ptr sample1 = MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame); + sample1->set_duration(kInputTimescale * 11); std::shared_ptr sample2 = MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame); - - // 11 seconds > 10 seconds (segment duration). - // Expect the segment to be finalized. - sample1->set_duration(kInputTimescale * 11); + // Doesn't really matter how long this is. + sample2->set_duration(kInputTimescale * 7); // (Finalize is not called at the end of this test so) Expect one segment // event. The length should be the same as the above sample that exceeds the // duration. EXPECT_CALL(mock_listener, - OnNewSegment("file1.ts", kFirstPts, kTimeScale * 11, _)); - - // Doesn't really matter how long this is. - sample2->set_duration(kInputTimescale * 7); + OnNewSegment("file1.ts", kFirstPts * kTimeScale / kInputTimescale, + kTimeScale * 11, _)); Sequence writer_sequence; EXPECT_CALL(*mock_ts_writer_, NewSegment(StrEq("file1.ts"))) @@ -263,11 +255,9 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { // The pointers are released inside the segmenter. Sequence pes_packet_sequence; - PesPacket* first_pes = new PesPacket(); - first_pes->set_pts(kFirstPts); EXPECT_CALL(*mock_pes_packet_generator_, GetNextPesPacketMock()) .InSequence(pes_packet_sequence) - .WillOnce(Return(first_pes)); + .WillOnce(Return(new PesPacket)); EXPECT_CALL(*mock_pes_packet_generator_, GetNextPesPacketMock()) .InSequence(pes_packet_sequence) .WillOnce(Return(new PesPacket())); @@ -277,6 +267,7 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { std::move(mock_pes_packet_generator_)); EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0)); EXPECT_OK(segmenter.AddSample(sample1)); + EXPECT_OK(segmenter.FinalizeSegment(kFirstPts, sample1->duration())); EXPECT_OK(segmenter.AddSample(sample2)); } @@ -287,7 +278,6 @@ TEST_F(TsSegmenterTest, InitializeThenFinalize) { arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; - options.segment_duration = 10.0; options.segment_template = "file$Number$.ts"; TsSegmenter segmenter(options, nullptr); @@ -295,7 +285,7 @@ TEST_F(TsSegmenterTest, InitializeThenFinalize) { EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_)) .WillOnce(Return(true)); - EXPECT_CALL(*mock_pes_packet_generator_, Flush()).WillOnce(Return(true)); + EXPECT_CALL(*mock_pes_packet_generator_, Flush()).Times(0); ON_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets()) .WillByDefault(Return(0)); @@ -310,13 +300,12 @@ TEST_F(TsSegmenterTest, InitializeThenFinalize) { // been initialized. // The test does not really add any samples but instead simulates an initialized // writer with a mock. -TEST_F(TsSegmenterTest, Finalize) { +TEST_F(TsSegmenterTest, FinalizeSegment) { std::shared_ptr stream_info(new VideoStreamInfo( kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData, arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; - options.segment_duration = 10.0; options.segment_template = "file$Number$.ts"; TsSegmenter segmenter(options, nullptr); @@ -335,114 +324,7 @@ TEST_F(TsSegmenterTest, Finalize) { std::move(mock_pes_packet_generator_)); EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0)); segmenter.SetTsWriterFileOpenedForTesting(true); - EXPECT_OK(segmenter.Finalize()); -} - -// Verify that it won't finish a segment if the sample is not a key frame. -TEST_F(TsSegmenterTest, SegmentOnlyBeforeKeyFrame) { - std::shared_ptr stream_info(new VideoStreamInfo( - kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData, - arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); - MuxerOptions options; - options.segment_duration = 10.0; - options.segment_template = "file$Number$.ts"; - TsSegmenter segmenter(options, nullptr); - - EXPECT_CALL(*mock_ts_writer_, Initialize(_)).WillOnce(Return(true)); - EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_)) - .WillOnce(Return(true)); - - const uint8_t kAnyData[] = { - 0x01, 0x0F, 0x3C, - }; - std::shared_ptr key_frame_sample1 = - MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame); - std::shared_ptr non_key_frame_sample = - MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), !kIsKeyFrame); - std::shared_ptr key_frame_sample2 = - MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame); - - // 11 seconds > 10 seconds (segment duration). - key_frame_sample1->set_duration(kTimeScale * 11); - - // But since the second sample is not a key frame, it shouldn't be segmented. - non_key_frame_sample->set_duration(kTimeScale * 7); - - // Since this is a key frame, it should be segmented when this is added. - key_frame_sample2->set_duration(kTimeScale * 3); - - EXPECT_CALL(*mock_pes_packet_generator_, PushSample(_)) - .Times(3) - .WillRepeatedly(Return(true)); - - Sequence writer_sequence; - EXPECT_CALL(*mock_ts_writer_, NewSegment(StrEq("file1.ts"))) - .InSequence(writer_sequence) - .WillOnce(Return(true)); - - Sequence ready_pes_sequence; - // First AddSample(). - EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets()) - .InSequence(ready_pes_sequence) - .WillOnce(Return(1u)); - EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets()) - .InSequence(ready_pes_sequence) - .WillOnce(Return(0u)); - // Second AddSample(). - EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets()) - .InSequence(ready_pes_sequence) - .WillOnce(Return(1u)); - EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets()) - .InSequence(ready_pes_sequence) - .WillOnce(Return(0u)); - // Third AddSample(), in Flush(). - EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets()) - .InSequence(ready_pes_sequence) - .WillOnce(Return(0u)); - // Third AddSample() after Flush(). - EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets()) - .InSequence(ready_pes_sequence) - .WillOnce(Return(1u)); - EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets()) - .InSequence(ready_pes_sequence) - .WillOnce(Return(0u)); - - EXPECT_CALL(*mock_pes_packet_generator_, Flush()) - .WillOnce(Return(true)); - - EXPECT_CALL(*mock_ts_writer_, FinalizeSegment()) - .InSequence(writer_sequence) - .WillOnce(Return(true)); - - // Expectations for second AddSample() for the second segment. - EXPECT_CALL(*mock_ts_writer_, NewSegment(StrEq("file2.ts"))) - .InSequence(writer_sequence) - .WillOnce(Return(true)); - - EXPECT_CALL(*mock_ts_writer_, AddPesPacketMock(_)) - .Times(3) - .WillRepeatedly(Return(true)); - - // The pointers are released inside the segmenter. - Sequence pes_packet_sequence; - EXPECT_CALL(*mock_pes_packet_generator_, GetNextPesPacketMock()) - .InSequence(pes_packet_sequence) - .WillOnce(Return(new PesPacket())); - EXPECT_CALL(*mock_pes_packet_generator_, GetNextPesPacketMock()) - .InSequence(pes_packet_sequence) - .WillOnce(Return(new PesPacket())); - EXPECT_CALL(*mock_pes_packet_generator_, GetNextPesPacketMock()) - .InSequence(pes_packet_sequence) - .WillOnce(Return(new PesPacket())); - - segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_)); - segmenter.InjectPesPacketGeneratorForTesting( - std::move(mock_pes_packet_generator_)); - EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0)); - EXPECT_OK(segmenter.AddSample(key_frame_sample1)); - EXPECT_OK(segmenter.AddSample(non_key_frame_sample)); - EXPECT_OK(segmenter.AddSample(key_frame_sample2)); + EXPECT_OK(segmenter.FinalizeSegment(0, 100 /* arbitrary duration */)); } TEST_F(TsSegmenterTest, WithEncryptionNoClearLead) { @@ -451,7 +333,6 @@ TEST_F(TsSegmenterTest, WithEncryptionNoClearLead) { arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; - options.segment_duration = 10.0; options.segment_template = "file$Number$.ts"; MockMuxerListener mock_listener; @@ -494,7 +375,7 @@ TEST_F(TsSegmenterTest, WithEncryptionNoClearLeadNoMuxerListener) { arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; - options.segment_duration = 10.0; + // options.segment_duration = 10.0; options.segment_template = "file$Number$.ts"; TsSegmenter segmenter(options, nullptr); @@ -535,7 +416,6 @@ TEST_F(TsSegmenterTest, WithEncryptionWithClearLead) { kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; - options.segment_duration = 1.0; const uint32_t k1080pPixels = 1920 * 1080; const uint32_t k2160pPixels = 4096 * 2160; const double kClearLeadSeconds = 1.0; @@ -557,12 +437,9 @@ TEST_F(TsSegmenterTest, WithEncryptionWithClearLead) { }; std::shared_ptr sample1 = MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame); + sample1->set_duration(kTimeScale * 2); std::shared_ptr sample2 = MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame); - - // Something longer than 1.0 (segment duration and clear lead). - sample1->set_duration(kTimeScale * 2); - // The length of the second sample doesn't really matter. sample2->set_duration(kTimeScale * 3); EXPECT_CALL(*mock_pes_packet_generator_, PushSample(_)) @@ -622,12 +499,13 @@ TEST_F(TsSegmenterTest, WithEncryptionWithClearLead) { kClearLeadSeconds)); EXPECT_OK(segmenter.AddSample(sample1)); - // These should be called AFTER the first AddSample(), before the second - // segment. + // Encryption should be setup after finalizing the first segment. + // These should be called when finalize segment is called. EXPECT_CALL(mock_listener, OnEncryptionStart()); EXPECT_CALL(*mock_pes_packet_generator_raw, SetEncryptionKeyMock(_)) .WillOnce(Return(true)); EXPECT_CALL(*mock_ts_writer_raw, SignalEncrypted()); + EXPECT_OK(segmenter.FinalizeSegment(0, sample1->duration())); EXPECT_OK(segmenter.AddSample(sample2)); } diff --git a/packager/media/formats/mp4/fragmenter.h b/packager/media/formats/mp4/fragmenter.h index 2150a1df87..7c38b67a63 100644 --- a/packager/media/formats/mp4/fragmenter.h +++ b/packager/media/formats/mp4/fragmenter.h @@ -52,6 +52,8 @@ class Fragmenter { /// Fill @a reference with current fragment information. void GenerateSegmentReference(SegmentReference* reference); + void ClearFragmentFinalized() { fragment_finalized_ = false; } + uint64_t fragment_duration() const { return fragment_duration_; } uint64_t first_sap_time() const { return first_sap_time_; } uint64_t earliest_presentation_time() const { diff --git a/packager/media/formats/mp4/mp4_muxer.cc b/packager/media/formats/mp4/mp4_muxer.cc index 67f08f9e3f..9e1b6add7a 100644 --- a/packager/media/formats/mp4/mp4_muxer.cc +++ b/packager/media/formats/mp4/mp4_muxer.cc @@ -161,9 +161,18 @@ Status MP4Muxer::Finalize() { return Status::OK; } -Status MP4Muxer::DoAddSample(std::shared_ptr sample) { +Status MP4Muxer::AddSample(int stream_id, std::shared_ptr sample) { DCHECK(segmenter_); - return segmenter_->AddSample(*streams()[0], sample); + return segmenter_->AddSample(stream_id, sample); +} + +Status MP4Muxer::FinalizeSegment(int stream_id, + std::shared_ptr segment_info) { + DCHECK(segmenter_); + VLOG(3) << "Finalize " << (segment_info->is_subsegment ? "sub" : "") + << "segment " << segment_info->start_timestamp << " duration " + << segment_info->duration; + return segmenter_->FinalizeSegment(stream_id, segment_info->is_subsegment); } void MP4Muxer::InitializeTrak(const StreamInfo* info, Track* trak) { diff --git a/packager/media/formats/mp4/mp4_muxer.h b/packager/media/formats/mp4/mp4_muxer.h index 5a16a2ce52..8832ce7182 100644 --- a/packager/media/formats/mp4/mp4_muxer.h +++ b/packager/media/formats/mp4/mp4_muxer.h @@ -37,7 +37,9 @@ class MP4Muxer : public Muxer { // Muxer implementation overrides. Status InitializeMuxer() override; Status Finalize() override; - Status DoAddSample(std::shared_ptr sample) override; + Status AddSample(int stream_id, std::shared_ptr sample) override; + Status FinalizeSegment(int stream_id, + std::shared_ptr segment_info) override; // Generate Audio/Video Track box. void InitializeTrak(const StreamInfo* info, Track* trak); diff --git a/packager/media/formats/mp4/segmenter.cc b/packager/media/formats/mp4/segmenter.cc index 2ce2f417a1..954672a378 100644 --- a/packager/media/formats/mp4/segmenter.cc +++ b/packager/media/formats/mp4/segmenter.cc @@ -178,7 +178,6 @@ Status Segmenter::Initialize( moof_->header.sequence_number = 0; moof_->tracks.resize(streams.size()); - segment_durations_.resize(streams.size()); fragmenters_.resize(streams.size()); const bool key_rotation_enabled = crypto_period_duration_in_seconds != 0; const bool kInitialEncryptionInfo = true; @@ -292,12 +291,6 @@ Status Segmenter::Initialize( } Status Segmenter::Finalize() { - for (const std::unique_ptr& fragmenter : fragmenters_) { - Status status = FinalizeFragment(true, fragmenter.get()); - if (!status.ok()) - return status; - } - // Set tracks and moov durations. // Note that the updated moov box will be written to output file for VOD case // only. @@ -315,111 +308,35 @@ Status Segmenter::Finalize() { return DoFinalize(); } -Status Segmenter::AddSample(const StreamInfo& stream_info, +Status Segmenter::AddSample(int stream_id, std::shared_ptr sample) { - // TODO(kqyang): Stream id should be passed in. - const uint32_t stream_id = 0; - Fragmenter* fragmenter = fragmenters_[stream_id].get(); - // Set default sample duration if it has not been set yet. if (moov_->extends.tracks[stream_id].default_sample_duration == 0) { moov_->extends.tracks[stream_id].default_sample_duration = sample->duration(); } + DCHECK_LT(stream_id, static_cast(fragmenters_.size())); + Fragmenter* fragmenter = fragmenters_[stream_id].get(); if (fragmenter->fragment_finalized()) { return Status(error::FRAGMENT_FINALIZED, "Current fragment is finalized already."); } - bool finalize_fragment = false; - if (fragmenter->fragment_duration() >= - options_.fragment_duration * stream_info.time_scale()) { - if (sample->is_key_frame() || !options_.fragment_sap_aligned) { - finalize_fragment = true; - } - } - bool finalize_segment = false; - if (segment_durations_[stream_id] >= - options_.segment_duration * stream_info.time_scale()) { - if (sample->is_key_frame() || !options_.segment_sap_aligned) { - finalize_segment = true; - finalize_fragment = true; - } - } - - Status status; - if (finalize_fragment) { - status = FinalizeFragment(finalize_segment, fragmenter); - if (!status.ok()) - return status; - } - - status = fragmenter->AddSample(sample); + Status status = fragmenter->AddSample(sample); if (!status.ok()) return status; if (sample_duration_ == 0) sample_duration_ = sample->duration(); moov_->tracks[stream_id].media.header.duration += sample->duration(); - segment_durations_[stream_id] += sample->duration(); - DCHECK_GE(segment_durations_[stream_id], fragmenter->fragment_duration()); return Status::OK; } -uint32_t Segmenter::GetReferenceTimeScale() const { - return moov_->header.timescale; -} - -double Segmenter::GetDuration() const { - if (moov_->header.timescale == 0) { - // Handling the case where this is not properly initialized. - return 0.0; - } - - return static_cast(moov_->header.duration) / moov_->header.timescale; -} - -void Segmenter::UpdateProgress(uint64_t progress) { - accumulated_progress_ += progress; - - if (!progress_listener_) return; - if (progress_target_ == 0) return; - // It might happen that accumulated progress exceeds progress_target due to - // computation errors, e.g. rounding error. Cap it so it never reports > 100% - // progress. - if (accumulated_progress_ >= progress_target_) { - progress_listener_->OnProgress(1.0); - } else { - progress_listener_->OnProgress(static_cast(accumulated_progress_) / - progress_target_); - } -} - -void Segmenter::SetComplete() { - if (!progress_listener_) return; - progress_listener_->OnProgress(1.0); -} - -Status Segmenter::FinalizeSegment() { - Status status = DoFinalizeSegment(); - - // Reset segment information to initial state. - sidx_->references.clear(); - std::vector::iterator it = segment_durations_.begin(); - for (; it != segment_durations_.end(); ++it) - *it = 0; - - return status; -} - -uint32_t Segmenter::GetReferenceStreamId() { - DCHECK(sidx_); - return sidx_->reference_id - 1; -} - -Status Segmenter::FinalizeFragment(bool finalize_segment, - Fragmenter* fragmenter) { +Status Segmenter::FinalizeSegment(int stream_id, bool is_subsegment) { + DCHECK_LT(stream_id, static_cast(fragmenters_.size())); + Fragmenter* fragmenter = fragmenters_[stream_id].get(); + DCHECK(fragmenter); fragmenter->FinalizeFragment(); // Check if all tracks are ready for fragmentation. @@ -468,12 +385,56 @@ Status Segmenter::FinalizeFragment(bool finalize_segment, // Increase sequence_number for next fragment. ++moof_->header.sequence_number; - if (finalize_segment) - return FinalizeSegment(); - + for (std::unique_ptr& fragmenter : fragmenters_) + fragmenter->ClearFragmentFinalized(); + if (!is_subsegment) { + Status status = DoFinalizeSegment(); + // Reset segment information to initial state. + sidx_->references.clear(); + return status; + } return Status::OK; } +uint32_t Segmenter::GetReferenceTimeScale() const { + return moov_->header.timescale; +} + +double Segmenter::GetDuration() const { + if (moov_->header.timescale == 0) { + // Handling the case where this is not properly initialized. + return 0.0; + } + + return static_cast(moov_->header.duration) / moov_->header.timescale; +} + +void Segmenter::UpdateProgress(uint64_t progress) { + accumulated_progress_ += progress; + + if (!progress_listener_) return; + if (progress_target_ == 0) return; + // It might happen that accumulated progress exceeds progress_target due to + // computation errors, e.g. rounding error. Cap it so it never reports > 100% + // progress. + if (accumulated_progress_ >= progress_target_) { + progress_listener_->OnProgress(1.0); + } else { + progress_listener_->OnProgress(static_cast(accumulated_progress_) / + progress_target_); + } +} + +void Segmenter::SetComplete() { + if (!progress_listener_) return; + progress_listener_->OnProgress(1.0); +} + +uint32_t Segmenter::GetReferenceStreamId() { + DCHECK(sidx_); + return sidx_->reference_id - 1; +} + } // namespace mp4 } // namespace media } // namespace shaka diff --git a/packager/media/formats/mp4/segmenter.h b/packager/media/formats/mp4/segmenter.h index 6279b32ad4..477ea0f9a3 100644 --- a/packager/media/formats/mp4/segmenter.h +++ b/packager/media/formats/mp4/segmenter.h @@ -85,10 +85,16 @@ class Segmenter { Status Finalize(); /// Add sample to the indicated stream. + /// @param stream_id is the zero-based stream index. /// @param sample points to the sample to be added. /// @return OK on success, an error status otherwise. - Status AddSample(const StreamInfo& stream_Info, - std::shared_ptr sample); + Status AddSample(int stream_id, std::shared_ptr sample); + + /// Finalize the segment / subsegment. + /// @param stream_id is the zero-based stream index. + /// @param is_subsegment indicates if it is a subsegment (fragment). + /// @return OK on success, an error status otherwise. + Status FinalizeSegment(int stream_id, bool is_subsegment); /// @return true if there is an initialization range, while setting @a offset /// and @a size; or false if initialization range does not apply. @@ -130,11 +136,8 @@ class Segmenter { virtual Status DoFinalize() = 0; virtual Status DoFinalizeSegment() = 0; - Status FinalizeSegment(); uint32_t GetReferenceStreamId(); - Status FinalizeFragment(bool finalize_segment, Fragmenter* fragment); - const MuxerOptions& options_; std::unique_ptr ftyp_; std::unique_ptr moov_; @@ -142,7 +145,6 @@ class Segmenter { std::unique_ptr fragment_buffer_; std::unique_ptr sidx_; std::vector> fragmenters_; - std::vector segment_durations_; MuxerListener* muxer_listener_; ProgressListener* progress_listener_; uint64_t progress_target_; diff --git a/packager/media/formats/webm/encrypted_segmenter_unittest.cc b/packager/media/formats/webm/encrypted_segmenter_unittest.cc index ac6a55ceaa..01b2bfaff4 100644 --- a/packager/media/formats/webm/encrypted_segmenter_unittest.cc +++ b/packager/media/formats/webm/encrypted_segmenter_unittest.cc @@ -14,6 +14,7 @@ namespace media { namespace { const uint64_t kDuration = 1000; +const bool kSubsegment = true; const std::string kKeyId = "4c6f72656d20697073756d20646f6c6f"; const std::string kIv = "0123456789012345"; const std::string kKey = "01234567890123456789012345678901"; @@ -210,17 +211,20 @@ class EncrypedSegmenterTest : public SegmentTestBase { TEST_F(EncrypedSegmenterTest, BasicSupport) { MuxerOptions options = CreateMuxerOptions(); - options.segment_duration = 3.0; ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options)); // Write the samples to the Segmenter. // There should be 2 segments with the first segment in clear and the second // segment encrypted. for (int i = 0; i < 5; i++) { + if (i == 3) + ASSERT_OK(segmenter_->FinalizeSegment(0, 3 * kDuration, !kSubsegment)); std::shared_ptr sample = CreateSample(kKeyFrame, kDuration, kNoSideData); ASSERT_OK(segmenter_->AddSample(sample)); } + ASSERT_OK( + segmenter_->FinalizeSegment(3 * kDuration, 2 * kDuration, !kSubsegment)); ASSERT_OK(segmenter_->Finalize()); ASSERT_FILE_ENDS_WITH(OutputFileName().c_str(), kBasicSupportData); diff --git a/packager/media/formats/webm/multi_segment_segmenter.cc b/packager/media/formats/webm/multi_segment_segmenter.cc index d085641f8b..7c4c8d2115 100644 --- a/packager/media/formats/webm/multi_segment_segmenter.cc +++ b/packager/media/formats/webm/multi_segment_segmenter.cc @@ -15,11 +15,33 @@ namespace shaka { namespace media { namespace webm { + MultiSegmentSegmenter::MultiSegmentSegmenter(const MuxerOptions& options) : Segmenter(options), num_segment_(0) {} MultiSegmentSegmenter::~MultiSegmentSegmenter() {} +Status MultiSegmentSegmenter::FinalizeSegment(uint64_t start_timescale, + uint64_t duration_timescale, + bool is_subsegment) { + CHECK(cluster()); + Status status = Segmenter::FinalizeSegment(start_timescale, + duration_timescale, is_subsegment); + if (!status.ok()) + return status; + if (!cluster()->Finalize()) + return Status(error::FILE_FAILURE, "Error finalizing segment."); + if (!is_subsegment) { + if (muxer_listener()) { + const uint64_t size = cluster()->Size(); + muxer_listener()->OnNewSegment(writer_->file()->file_name(), + start_timescale, duration_timescale, size); + } + VLOG(1) << "WEBM file '" << writer_->file()->file_name() << "' finalized."; + } + return Status::OK; +} + bool MultiSegmentSegmenter::GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) { return false; @@ -36,53 +58,23 @@ Status MultiSegmentSegmenter::DoInitialize(std::unique_ptr writer) { } Status MultiSegmentSegmenter::DoFinalize() { - Status status = FinalizeSegment(); - status.Update(writer_->Close()); - return status; + return writer_->Close(); } -Status MultiSegmentSegmenter::FinalizeSegment() { - if (!cluster()->Finalize()) - return Status(error::FILE_FAILURE, "Error finalizing segment."); - - if (muxer_listener()) { - const uint64_t size = cluster()->Size(); - const uint64_t start_webm_timecode = cluster()->timecode(); - const uint64_t start_timescale = FromWebMTimecode(start_webm_timecode); - muxer_listener()->OnNewSegment(writer_->file()->file_name(), - start_timescale, - cluster_length_in_time_scale(), size); +Status MultiSegmentSegmenter::NewSegment(uint64_t start_timescale, + bool is_subsegment) { + if (!is_subsegment) { + // Create a new file for the new segment. + std::string segment_name = + GetSegmentName(options().segment_template, start_timescale, + num_segment_, options().bandwidth); + writer_.reset(new MkvWriter); + Status status = writer_->Open(segment_name); + if (!status.ok()) + return status; + num_segment_++; } - VLOG(1) << "WEBM file '" << writer_->file()->file_name() << "' finalized."; - return Status::OK; -} - -Status MultiSegmentSegmenter::NewSubsegment(uint64_t start_timescale) { - if (cluster() && !cluster()->Finalize()) - return Status(error::FILE_FAILURE, "Error finalizing segment."); - - uint64_t start_webm_timecode = FromBMFFTimescale(start_timescale); - return SetCluster(start_webm_timecode, 0, writer_.get()); -} - -Status MultiSegmentSegmenter::NewSegment(uint64_t start_timescale) { - if (cluster()) { - Status temp = FinalizeSegment(); - if (!temp.ok()) - return temp; - } - - // Create a new file for the new segment. - std::string segment_name = - GetSegmentName(options().segment_template, start_timescale, num_segment_, - options().bandwidth); - writer_.reset(new MkvWriter); - Status status = writer_->Open(segment_name); - if (!status.ok()) - return status; - num_segment_++; - uint64_t start_webm_timecode = FromBMFFTimescale(start_timescale); return SetCluster(start_webm_timecode, 0, writer_.get()); } diff --git a/packager/media/formats/webm/multi_segment_segmenter.h b/packager/media/formats/webm/multi_segment_segmenter.h index 99da7b1f90..ca4d839fc0 100644 --- a/packager/media/formats/webm/multi_segment_segmenter.h +++ b/packager/media/formats/webm/multi_segment_segmenter.h @@ -28,6 +28,9 @@ class MultiSegmentSegmenter : public Segmenter { /// @name Segmenter implementation overrides. /// @{ + Status FinalizeSegment(uint64_t start_timescale, + uint64_t duration_timescale, + bool is_subsegment) override; bool GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) override; bool GetIndexRangeStartAndEnd(uint64_t* start, uint64_t* end) override; /// @} @@ -39,10 +42,7 @@ class MultiSegmentSegmenter : public Segmenter { private: // Segmenter implementation overrides. - Status NewSubsegment(uint64_t start_timescale) override; - Status NewSegment(uint64_t start_timescale) override; - - Status FinalizeSegment(); + Status NewSegment(uint64_t start_timescale, bool is_subsegment) override; std::unique_ptr writer_; uint32_t num_segment_; diff --git a/packager/media/formats/webm/multi_segment_segmenter_unittest.cc b/packager/media/formats/webm/multi_segment_segmenter_unittest.cc index 9140e975b1..eb93f2e983 100644 --- a/packager/media/formats/webm/multi_segment_segmenter_unittest.cc +++ b/packager/media/formats/webm/multi_segment_segmenter_unittest.cc @@ -14,6 +14,7 @@ namespace media { namespace { const uint64_t kDuration = 1000; +const bool kSubsegment = true; const uint8_t kBasicSupportDataInit[] = { // ID: EBML Header omitted. @@ -124,6 +125,7 @@ TEST_F(MultiSegmentSegmenterTest, BasicSupport) { CreateSample(kKeyFrame, kDuration, kNoSideData); ASSERT_OK(segmenter_->AddSample(sample)); } + ASSERT_OK(segmenter_->FinalizeSegment(0, 8 * kDuration, !kSubsegment)); ASSERT_OK(segmenter_->Finalize()); // Verify the resulting data. @@ -134,18 +136,21 @@ TEST_F(MultiSegmentSegmenterTest, BasicSupport) { EXPECT_FALSE(File::Open(TemplateFileName(1).c_str(), "r")); } -TEST_F(MultiSegmentSegmenterTest, SplitsFilesOnSegmentDuration) { +TEST_F(MultiSegmentSegmenterTest, SplitsFilesOnSegment) { MuxerOptions options = CreateMuxerOptions(); options.segment_template = segment_template_; - options.segment_duration = 5; // seconds ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options)); // Write the samples to the Segmenter. for (int i = 0; i < 8; i++) { + if (i == 5) + ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment)); std::shared_ptr sample = CreateSample(kKeyFrame, kDuration, kNoSideData); ASSERT_OK(segmenter_->AddSample(sample)); } + ASSERT_OK( + segmenter_->FinalizeSegment(5 * kDuration, 8 * kDuration, !kSubsegment)); ASSERT_OK(segmenter_->Finalize()); // Verify the resulting data. @@ -161,47 +166,20 @@ TEST_F(MultiSegmentSegmenterTest, SplitsFilesOnSegmentDuration) { EXPECT_FALSE(File::Open(TemplateFileName(2).c_str(), "r")); } -TEST_F(MultiSegmentSegmenterTest, RespectsSegmentSAPAlign) { +TEST_F(MultiSegmentSegmenterTest, SplitsClustersOnSubsegment) { MuxerOptions options = CreateMuxerOptions(); options.segment_template = segment_template_; - options.segment_duration = 3; // seconds - options.segment_sap_aligned = true; - ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options)); - - // Write the samples to the Segmenter. - for (int i = 0; i < 10; i++) { - const KeyFrameFlag key_frame_flag = i == 6 ? kKeyFrame : kNotKeyFrame; - std::shared_ptr sample = - CreateSample(key_frame_flag, kDuration, kNoSideData); - ASSERT_OK(segmenter_->AddSample(sample)); - } - ASSERT_OK(segmenter_->Finalize()); - - // Verify the resulting data. - ClusterParser parser; - ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(0))); - ASSERT_EQ(1u, parser.cluster_count()); - EXPECT_EQ(6, parser.GetFrameCountForCluster(0)); - - ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(1))); - ASSERT_EQ(1u, parser.cluster_count()); - EXPECT_EQ(4, parser.GetFrameCountForCluster(0)); - - EXPECT_FALSE(File::Open(TemplateFileName(2).c_str(), "r")); -} - -TEST_F(MultiSegmentSegmenterTest, SplitsClustersOnFragmentDuration) { - MuxerOptions options = CreateMuxerOptions(); - options.segment_template = segment_template_; - options.fragment_duration = 5; // seconds ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options)); // Write the samples to the Segmenter. for (int i = 0; i < 8; i++) { + if (i == 5) + ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, kSubsegment)); std::shared_ptr sample = CreateSample(kKeyFrame, kDuration, kNoSideData); ASSERT_OK(segmenter_->AddSample(sample)); } + ASSERT_OK(segmenter_->FinalizeSegment(0, 8 * kDuration, !kSubsegment)); ASSERT_OK(segmenter_->Finalize()); // Verify the resulting data. @@ -214,31 +192,5 @@ TEST_F(MultiSegmentSegmenterTest, SplitsClustersOnFragmentDuration) { EXPECT_FALSE(File::Open(TemplateFileName(1).c_str(), "r")); } -TEST_F(MultiSegmentSegmenterTest, RespectsFragmentSAPAlign) { - MuxerOptions options = CreateMuxerOptions(); - options.segment_template = segment_template_; - options.fragment_duration = 3; // seconds - options.fragment_sap_aligned = true; - ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options)); - - // Write the samples to the Segmenter. - for (int i = 0; i < 10; i++) { - const KeyFrameFlag key_frame_flag = i == 6 ? kKeyFrame : kNotKeyFrame; - std::shared_ptr sample = - CreateSample(key_frame_flag, kDuration, kNoSideData); - ASSERT_OK(segmenter_->AddSample(sample)); - } - ASSERT_OK(segmenter_->Finalize()); - - // Verify the resulting data. - ClusterParser parser; - ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(0))); - ASSERT_EQ(2u, parser.cluster_count()); - EXPECT_EQ(6, parser.GetFrameCountForCluster(0)); - EXPECT_EQ(4, parser.GetFrameCountForCluster(1)); - - EXPECT_FALSE(File::Open(TemplateFileName(1).c_str(), "r")); -} - } // namespace media } // namespace shaka diff --git a/packager/media/formats/webm/segmenter.cc b/packager/media/formats/webm/segmenter.cc index 354725fcc9..abec3ad18b 100644 --- a/packager/media/formats/webm/segmenter.cc +++ b/packager/media/formats/webm/segmenter.cc @@ -8,6 +8,7 @@ #include "packager/base/time/time.h" #include "packager/media/base/audio_stream_info.h" +#include "packager/media/base/media_handler.h" #include "packager/media/base/media_sample.h" #include "packager/media/base/muxer_options.h" #include "packager/media/base/muxer_util.h" @@ -28,22 +29,7 @@ int64_t kTimecodeScale = 1000000; int64_t kSecondsToNs = 1000000000L; } // namespace -Segmenter::Segmenter(const MuxerOptions& options) - : reference_frame_timestamp_(0), - options_(options), - clear_lead_(0), - enable_encryption_(false), - info_(NULL), - muxer_listener_(NULL), - progress_listener_(NULL), - progress_target_(0), - accumulated_progress_(0), - first_timestamp_(0), - sample_duration_(0), - segment_payload_pos_(0), - cluster_length_in_time_scale_(0), - segment_length_in_time_scale_(0), - track_id_(0) {} +Segmenter::Segmenter(const MuxerOptions& options) : options_(options) {} Segmenter::~Segmenter() {} @@ -110,10 +96,6 @@ Status Segmenter::Initialize(std::unique_ptr writer, } Status Segmenter::Finalize() { - Status status = WriteFrame(true /* write_duration */); - if (!status.ok()) - return status; - uint64_t duration = prev_sample_->pts() - first_timestamp_ + prev_sample_->duration(); segment_info_.set_duration(FromBMFFTimescale(duration)); @@ -137,33 +119,9 @@ Status Segmenter::AddSample(std::shared_ptr sample) { // previous frame first before creating the new Cluster. Status status; - bool wrote_frame = false; - bool new_segment = false; - if (!cluster_) { - status = NewSegment(sample->pts()); - new_segment = true; - // First frame, so no previous frame to write. - wrote_frame = true; - } else if (segment_length_in_time_scale_ >= - options_.segment_duration * info_->time_scale()) { - if (sample->is_key_frame() || !options_.segment_sap_aligned) { - status = WriteFrame(true /* write_duration */); - status.Update(NewSegment(sample->pts())); - new_segment = true; - segment_length_in_time_scale_ = 0; - cluster_length_in_time_scale_ = 0; - wrote_frame = true; - } - } else if (cluster_length_in_time_scale_ >= - options_.fragment_duration * info_->time_scale()) { - if (sample->is_key_frame() || !options_.fragment_sap_aligned) { - status = WriteFrame(true /* write_duration */); - status.Update(NewSubsegment(sample->pts())); - cluster_length_in_time_scale_ = 0; - wrote_frame = true; - } - } - if (!wrote_frame) { + if (new_segment_ || new_subsegment_) { + status = NewSegment(sample->pts(), new_subsegment_); + } else { status = WriteFrame(false /* write_duration */); } if (!status.ok()) @@ -173,7 +131,7 @@ Status Segmenter::AddSample(std::shared_ptr sample) { if (encryptor_) { // Don't enable encryption in the middle of a segment, i.e. only at the // first frame of a segment. - if (new_segment && !enable_encryption_) { + if (new_segment_ && !enable_encryption_) { if (sample->pts() - first_timestamp_ >= clear_lead_ * info_->time_scale()) { enable_encryption_ = true; @@ -189,16 +147,22 @@ Status Segmenter::AddSample(std::shared_ptr sample) { } } - // Add the sample to the durations even though we have not written the frame - // yet. This is needed to make sure we split Clusters at the correct point. - // These are only used in this method. - cluster_length_in_time_scale_ += sample->duration(); - segment_length_in_time_scale_ += sample->duration(); - + new_subsegment_ = false; + new_segment_ = false; prev_sample_ = sample; return Status::OK; } +Status Segmenter::FinalizeSegment(uint64_t start_timescale, + uint64_t duration_timescale, + bool is_subsegment) { + if (is_subsegment) + new_subsegment_ = true; + else + new_segment_ = true; + return WriteFrame(true /* write duration */); +} + float Segmenter::GetDuration() const { return static_cast(segment_info_.duration()) * segment_info_.timecode_scale() / kSecondsToNs; diff --git a/packager/media/formats/webm/segmenter.h b/packager/media/formats/webm/segmenter.h index 9095a4760e..3110cbba33 100644 --- a/packager/media/formats/webm/segmenter.h +++ b/packager/media/formats/webm/segmenter.h @@ -76,6 +76,11 @@ class Segmenter { /// @return OK on success, an error status otherwise. Status AddSample(std::shared_ptr sample); + /// Finalize the (sub)segment. + virtual Status FinalizeSegment(uint64_t start_timescale, + uint64_t duration_timescale, + bool is_subsegment) = 0; + /// @return true if there is an initialization range, while setting @a start /// and @a end; or false if initialization range does not apply. virtual bool GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) = 0; @@ -113,9 +118,6 @@ class Segmenter { int track_id() const { return track_id_; } uint64_t segment_payload_pos() const { return segment_payload_pos_; } - uint64_t cluster_length_in_time_scale() const { - return cluster_length_in_time_scale_; - } virtual Status DoInitialize(std::unique_ptr writer) = 0; virtual Status DoFinalize() = 0; @@ -129,25 +131,23 @@ class Segmenter { // Writes the previous frame to the file. Status WriteFrame(bool write_duration); - // This is called when there needs to be a new subsegment. This does nothing - // in single-segment mode. In multi-segment mode this creates a new Cluster - // element. - virtual Status NewSubsegment(uint64_t start_timescale) = 0; - // This is called when there needs to be a new segment. In single-segment - // mode, this creates a new Cluster element. In multi-segment mode this - // creates a new output file. - virtual Status NewSegment(uint64_t start_timescale) = 0; + // This is called when there needs to be a new (sub)segment. + // In single-segment mode, a Cluster is a segment and there is no subsegment. + // In multi-segment mode, a new file is a segment and the clusters in the file + // are subsegments. + virtual Status NewSegment(uint64_t start_timescale, bool is_subsegment) = 0; // Store the previous sample so we know which one is the last frame. std::shared_ptr prev_sample_; // The reference frame timestamp; used to populate the ReferenceBlock element // when writing non-keyframe BlockGroups. - uint64_t reference_frame_timestamp_; + uint64_t reference_frame_timestamp_ = 0; const MuxerOptions& options_; std::unique_ptr encryptor_; - double clear_lead_; - bool enable_encryption_; // Encryption is enabled only after clear_lead_. + double clear_lead_ = 0; + // Encryption is enabled only after clear_lead_. + bool enable_encryption_ = false; std::unique_ptr cluster_; mkvmuxer::Cues cues_; @@ -155,22 +155,23 @@ class Segmenter { mkvmuxer::SegmentInfo segment_info_; mkvmuxer::Tracks tracks_; - StreamInfo* info_; - MuxerListener* muxer_listener_; - ProgressListener* progress_listener_; - uint64_t progress_target_; - uint64_t accumulated_progress_; - uint64_t first_timestamp_; - int64_t sample_duration_; + StreamInfo* info_ = nullptr; + MuxerListener* muxer_listener_ = nullptr; + ProgressListener* progress_listener_ = nullptr; + uint64_t progress_target_ = 0; + uint64_t accumulated_progress_ = 0; + uint64_t first_timestamp_ = 0; + int64_t sample_duration_ = 0; // The position (in bytes) of the start of the Segment payload in the init // file. This is also the size of the header before the SeekHead. - uint64_t segment_payload_pos_; + uint64_t segment_payload_pos_ = 0; - // Durations in timescale. - uint64_t cluster_length_in_time_scale_; - uint64_t segment_length_in_time_scale_; - - int track_id_; + // Indicate whether a new segment needed to be created, which is always true + // in the beginning. + bool new_segment_ = true; + // Indicate whether a new subsegment needed to be created. + bool new_subsegment_ = false; + int track_id_ = 0; DISALLOW_COPY_AND_ASSIGN(Segmenter); }; diff --git a/packager/media/formats/webm/segmenter_test_base.cc b/packager/media/formats/webm/segmenter_test_base.cc index a1deed791a..3452ffc4a4 100644 --- a/packager/media/formats/webm/segmenter_test_base.cc +++ b/packager/media/formats/webm/segmenter_test_base.cc @@ -75,10 +75,6 @@ std::shared_ptr SegmentTestBase::CreateSample( MuxerOptions SegmentTestBase::CreateMuxerOptions() const { MuxerOptions ret; ret.output_file_name = output_file_name_; - ret.segment_duration = 30; // seconds - ret.fragment_duration = 30; // seconds - ret.segment_sap_aligned = false; - ret.fragment_sap_aligned = false; // Use memory files for temp storage. Normally this would be a bad idea // since it wouldn't support large files, but for tests the files are small. ret.temp_dir = std::string(kMemoryFilePrefix) + "temp/"; diff --git a/packager/media/formats/webm/single_segment_segmenter.cc b/packager/media/formats/webm/single_segment_segmenter.cc index 81688b1bbc..fe190884c9 100644 --- a/packager/media/formats/webm/single_segment_segmenter.cc +++ b/packager/media/formats/webm/single_segment_segmenter.cc @@ -18,55 +18,22 @@ SingleSegmentSegmenter::SingleSegmentSegmenter(const MuxerOptions& options) SingleSegmentSegmenter::~SingleSegmentSegmenter() {} -Status SingleSegmentSegmenter::DoInitialize(std::unique_ptr writer) { - writer_ = std::move(writer); - Status ret = WriteSegmentHeader(0, writer_.get()); - init_end_ = writer_->Position() - 1; - seek_head()->set_cluster_pos(init_end_ + 1 - segment_payload_pos()); - return ret; -} - -Status SingleSegmentSegmenter::DoFinalize() { +Status SingleSegmentSegmenter::FinalizeSegment(uint64_t start_timescale, + uint64_t duration_timescale, + bool is_subsegment) { + Status status = Segmenter::FinalizeSegment(start_timescale, + duration_timescale, is_subsegment); + if (!status.ok()) + return status; + // No-op for subsegment in single segment mode. + if (is_subsegment) + return Status::OK; + CHECK(cluster()); if (!cluster()->Finalize()) return Status(error::FILE_FAILURE, "Error finalizing cluster."); - - // Write the Cues to the end of the file. - index_start_ = writer_->Position(); - seek_head()->set_cues_pos(index_start_ - segment_payload_pos()); - if (!cues()->Write(writer_.get())) - return Status(error::FILE_FAILURE, "Error writing Cues data."); - - // The WebM index is at the end of the file. - index_end_ = writer_->Position() - 1; - writer_->Position(0); - - Status status = WriteSegmentHeader(index_end_ + 1, writer_.get()); - status.Update(writer_->Close()); - return status; -} - -Status SingleSegmentSegmenter::NewSubsegment(uint64_t start_timescale) { return Status::OK; } -Status SingleSegmentSegmenter::NewSegment(uint64_t start_timescale) { - if (cluster() && !cluster()->Finalize()) - return Status(error::FILE_FAILURE, "Error finalizing cluster."); - - // Create a new Cue point. - uint64_t position = writer_->Position(); - uint64_t start_webm_timecode = FromBMFFTimescale(start_timescale); - - mkvmuxer::CuePoint* cue_point = new mkvmuxer::CuePoint; - cue_point->set_time(start_webm_timecode); - cue_point->set_track(track_id()); - cue_point->set_cluster_pos(position - segment_payload_pos()); - if (!cues()->AddCue(cue_point)) - return Status(error::INTERNAL_ERROR, "Error adding CuePoint."); - - return SetCluster(start_webm_timecode, position, writer_.get()); -} - bool SingleSegmentSegmenter::GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) { // The init range is the header, from the start of the file to the size of @@ -85,6 +52,49 @@ bool SingleSegmentSegmenter::GetIndexRangeStartAndEnd(uint64_t* start, return true; } +Status SingleSegmentSegmenter::DoInitialize(std::unique_ptr writer) { + writer_ = std::move(writer); + Status ret = WriteSegmentHeader(0, writer_.get()); + init_end_ = writer_->Position() - 1; + seek_head()->set_cluster_pos(init_end_ + 1 - segment_payload_pos()); + return ret; +} + +Status SingleSegmentSegmenter::DoFinalize() { + // Write the Cues to the end of the file. + index_start_ = writer_->Position(); + seek_head()->set_cues_pos(index_start_ - segment_payload_pos()); + if (!cues()->Write(writer_.get())) + return Status(error::FILE_FAILURE, "Error writing Cues data."); + + // The WebM index is at the end of the file. + index_end_ = writer_->Position() - 1; + writer_->Position(0); + + Status status = WriteSegmentHeader(index_end_ + 1, writer_.get()); + status.Update(writer_->Close()); + return status; +} + +Status SingleSegmentSegmenter::NewSegment(uint64_t start_timescale, + bool is_subsegment) { + // No-op for subsegment in single segment mode. + if (is_subsegment) + return Status::OK; + // Create a new Cue point. + uint64_t position = writer_->Position(); + uint64_t start_webm_timecode = FromBMFFTimescale(start_timescale); + + mkvmuxer::CuePoint* cue_point = new mkvmuxer::CuePoint; + cue_point->set_time(start_webm_timecode); + cue_point->set_track(track_id()); + cue_point->set_cluster_pos(position - segment_payload_pos()); + if (!cues()->AddCue(cue_point)) + return Status(error::INTERNAL_ERROR, "Error adding CuePoint."); + + return SetCluster(start_webm_timecode, position, writer_.get()); +} + } // namespace webm } // namespace media } // namespace shaka diff --git a/packager/media/formats/webm/single_segment_segmenter.h b/packager/media/formats/webm/single_segment_segmenter.h index ff8efd54f2..fbbf56be00 100644 --- a/packager/media/formats/webm/single_segment_segmenter.h +++ b/packager/media/formats/webm/single_segment_segmenter.h @@ -30,6 +30,9 @@ class SingleSegmentSegmenter : public Segmenter { /// @name Segmenter implementation overrides. /// @{ + Status FinalizeSegment(uint64_t start_timescale, + uint64_t duration_timescale, + bool is_subsegment) override; bool GetInitRangeStartAndEnd(uint64_t* start, uint64_t* end) override; bool GetIndexRangeStartAndEnd(uint64_t* start, uint64_t* end) override; /// @} @@ -50,8 +53,7 @@ class SingleSegmentSegmenter : public Segmenter { private: // Segmenter implementation overrides. - Status NewSubsegment(uint64_t start_timescale) override; - Status NewSegment(uint64_t start_timescale) override; + Status NewSegment(uint64_t start_timescale, bool is_subsegment) override; std::unique_ptr writer_; uint64_t init_end_; diff --git a/packager/media/formats/webm/single_segment_segmenter_unittest.cc b/packager/media/formats/webm/single_segment_segmenter_unittest.cc index 6d5bf2d5fb..d8e2456bd7 100644 --- a/packager/media/formats/webm/single_segment_segmenter_unittest.cc +++ b/packager/media/formats/webm/single_segment_segmenter_unittest.cc @@ -13,6 +13,7 @@ namespace media { namespace { const uint64_t kDuration = 1000; +const bool kSubsegment = true; const uint8_t kBasicSupportData[] = { // ID: EBML Header omitted. @@ -159,22 +160,26 @@ TEST_F(SingleSegmentSegmenterTest, BasicSupport) { CreateSample(kKeyFrame, kDuration, side_data_flag); ASSERT_OK(segmenter_->AddSample(sample)); } + ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment)); ASSERT_OK(segmenter_->Finalize()); ASSERT_FILE_ENDS_WITH(OutputFileName().c_str(), kBasicSupportData); } -TEST_F(SingleSegmentSegmenterTest, SplitsClustersOnSegmentDuration) { +TEST_F(SingleSegmentSegmenterTest, SplitsClustersOnSegment) { MuxerOptions options = CreateMuxerOptions(); - options.segment_duration = 4.5; // seconds ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options)); // Write the samples to the Segmenter. for (int i = 0; i < 8; i++) { + if (i == 5) + ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, !kSubsegment)); std::shared_ptr sample = CreateSample(kKeyFrame, kDuration, kNoSideData); ASSERT_OK(segmenter_->AddSample(sample)); } + ASSERT_OK( + segmenter_->FinalizeSegment(5 * kDuration, 8 * kDuration, !kSubsegment)); ASSERT_OK(segmenter_->Finalize()); // Verify the resulting data. @@ -185,17 +190,19 @@ TEST_F(SingleSegmentSegmenterTest, SplitsClustersOnSegmentDuration) { EXPECT_EQ(3, parser.GetFrameCountForCluster(1)); } -TEST_F(SingleSegmentSegmenterTest, IgnoresFragmentDuration) { +TEST_F(SingleSegmentSegmenterTest, IgnoresSubsegment) { MuxerOptions options = CreateMuxerOptions(); - options.fragment_duration = 5; // seconds ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options)); // Write the samples to the Segmenter. for (int i = 0; i < 8; i++) { + if (i == 5) + ASSERT_OK(segmenter_->FinalizeSegment(0, 5 * kDuration, kSubsegment)); std::shared_ptr sample = CreateSample(kKeyFrame, kDuration, kNoSideData); ASSERT_OK(segmenter_->AddSample(sample)); } + ASSERT_OK(segmenter_->FinalizeSegment(0, 8 * kDuration, !kSubsegment)); ASSERT_OK(segmenter_->Finalize()); // Verify the resulting data. @@ -205,31 +212,5 @@ TEST_F(SingleSegmentSegmenterTest, IgnoresFragmentDuration) { EXPECT_EQ(8, parser.GetFrameCountForCluster(0)); } -TEST_F(SingleSegmentSegmenterTest, RespectsSAPAlign) { - MuxerOptions options = CreateMuxerOptions(); - options.segment_duration = 3; // seconds - options.segment_sap_aligned = true; - ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options)); - - // Write the samples to the Segmenter. - for (int i = 0; i < 10; i++) { - const KeyFrameFlag key_frame_flag = i == 6 ? kKeyFrame : kNotKeyFrame; - std::shared_ptr sample = - CreateSample(key_frame_flag, kDuration, kNoSideData); - ASSERT_OK(segmenter_->AddSample(sample)); - } - ASSERT_OK(segmenter_->Finalize()); - - // Verify the resulting data. - ClusterParser parser; - ASSERT_NO_FATAL_FAILURE(parser.PopulateFromSegment(OutputFileName())); - // Segments are 1 second, so there would normally be 3 frames per cluster, - // but since it's SAP aligned and only frame 7 is a key-frame, there are - // two clusters with 6 and 4 frames respectively. - ASSERT_EQ(2u, parser.cluster_count()); - EXPECT_EQ(6, parser.GetFrameCountForCluster(0)); - EXPECT_EQ(4, parser.GetFrameCountForCluster(1)); -} - } // namespace media } // namespace shaka diff --git a/packager/media/formats/webm/two_pass_single_segment_segmenter.cc b/packager/media/formats/webm/two_pass_single_segment_segmenter.cc index cc3e1ce885..450d180f9d 100644 --- a/packager/media/formats/webm/two_pass_single_segment_segmenter.cc +++ b/packager/media/formats/webm/two_pass_single_segment_segmenter.cc @@ -87,9 +87,6 @@ Status TwoPassSingleSegmentSegmenter::DoInitialize( } Status TwoPassSingleSegmentSegmenter::DoFinalize() { - if (!cluster()->Finalize()) - return Status(error::FILE_FAILURE, "Error finalizing cluster."); - const uint64_t header_size = init_end() + 1; const uint64_t cues_pos = header_size - segment_payload_pos(); const uint64_t cues_size = UpdateCues(cues()); diff --git a/packager/media/formats/webm/webm_muxer.cc b/packager/media/formats/webm/webm_muxer.cc index d4ac7bc043..c96d75004f 100644 --- a/packager/media/formats/webm/webm_muxer.cc +++ b/packager/media/formats/webm/webm_muxer.cc @@ -72,11 +72,22 @@ Status WebMMuxer::Finalize() { return Status::OK; } -Status WebMMuxer::DoAddSample(std::shared_ptr sample) { +Status WebMMuxer::AddSample(int stream_id, + std::shared_ptr sample) { DCHECK(segmenter_); + DCHECK_EQ(stream_id, 0); return segmenter_->AddSample(sample); } +Status WebMMuxer::FinalizeSegment(int stream_id, + std::shared_ptr segment_info) { + DCHECK(segmenter_); + DCHECK_EQ(stream_id, 0); + return segmenter_->FinalizeSegment(segment_info->start_timestamp, + segment_info->duration, + segment_info->is_subsegment); +} + void WebMMuxer::FireOnMediaStartEvent() { if (!muxer_listener()) return; diff --git a/packager/media/formats/webm/webm_muxer.h b/packager/media/formats/webm/webm_muxer.h index ff7e3b13bb..6aba30b565 100644 --- a/packager/media/formats/webm/webm_muxer.h +++ b/packager/media/formats/webm/webm_muxer.h @@ -26,7 +26,9 @@ class WebMMuxer : public Muxer { // Muxer implementation overrides. Status InitializeMuxer() override; Status Finalize() override; - Status DoAddSample(std::shared_ptr sample) override; + Status AddSample(int stream_id, std::shared_ptr sample) override; + Status FinalizeSegment(int stream_id, + std::shared_ptr segment_info) override; void FireOnMediaStartEvent(); void FireOnMediaEndEvent(); diff --git a/packager/media/test/packager_test.cc b/packager/media/test/packager_test.cc index a8ae1be2d9..1a6faa7de8 100644 --- a/packager/media/test/packager_test.cc +++ b/packager/media/test/packager_test.cc @@ -17,6 +17,7 @@ #include "packager/media/base/muxer_util.h" #include "packager/media/base/stream_info.h" #include "packager/media/base/test/status_test_util.h" +#include "packager/media/chunking/chunking_handler.h" #include "packager/media/formats/mp4/mp4_muxer.h" #include "packager/media/test/test_data_util.h" @@ -89,7 +90,9 @@ class PackagerTestBasic : public ::testing::TestWithParam { // Check if |file1| and |file2| are the same. bool ContentsEqual(const std::string& file1, const std::string file2); - MuxerOptions SetupOptions(const std::string& output, bool single_segment); + ChunkingOptions SetupChunkingOptions(); + MuxerOptions SetupMuxerOptions(const std::string& output, + bool single_segment); void Remux(const std::string& input, const std::string& video_output, const std::string& audio_output, @@ -116,15 +119,10 @@ bool PackagerTestBasic::ContentsEqual(const std::string& file1, test_directory_.AppendASCII(file2)); } -MuxerOptions PackagerTestBasic::SetupOptions(const std::string& output, - bool single_segment) { +MuxerOptions PackagerTestBasic::SetupMuxerOptions(const std::string& output, + bool single_segment) { MuxerOptions options; - options.segment_duration = kSegmentDurationInSeconds; - options.fragment_duration = kFragmentDurationInSecodns; - options.segment_sap_aligned = kSegmentSapAligned; - options.fragment_sap_aligned = kFragmentSapAligned; options.num_subsegments_per_sidx = kNumSubsegmentsPerSidx; - options.output_file_name = GetFullPath(output); if (!single_segment) options.segment_template = GetFullPath(kSegmentTemplate); @@ -132,6 +130,15 @@ MuxerOptions PackagerTestBasic::SetupOptions(const std::string& output, return options; } +ChunkingOptions PackagerTestBasic::SetupChunkingOptions() { + ChunkingOptions options; + options.segment_duration_in_seconds = kSegmentDurationInSeconds; + options.subsegment_duration_in_seconds = kFragmentDurationInSecodns; + options.segment_sap_aligned = kSegmentSapAligned; + options.subsegment_sap_aligned = kFragmentSapAligned; + return options; +} + void PackagerTestBasic::Remux(const std::string& input, const std::string& video_output, const std::string& audio_output, @@ -140,7 +147,6 @@ void PackagerTestBasic::Remux(const std::string& input, CHECK(!video_output.empty() || !audio_output.empty()); Demuxer demuxer(GetFullPath(input)); - std::unique_ptr encryption_key_source( FixedKeySource::CreateFromHexStrings(kKeyIdHex, kKeyHex, "", "")); DCHECK(encryption_key_source); @@ -148,7 +154,7 @@ void PackagerTestBasic::Remux(const std::string& input, std::shared_ptr muxer_video; if (!video_output.empty()) { muxer_video.reset( - new mp4::MP4Muxer(SetupOptions(video_output, single_segment))); + new mp4::MP4Muxer(SetupMuxerOptions(video_output, single_segment))); muxer_video->set_clock(&fake_clock_); if (enable_encryption) { @@ -157,13 +163,17 @@ void PackagerTestBasic::Remux(const std::string& input, kMaxUHD1Pixels, kClearLeadInSeconds, kCryptoDurationInSeconds, FOURCC_cenc); } - ASSERT_OK(demuxer.SetHandler("video", muxer_video)); + + auto chunking_handler = + std::make_shared(SetupChunkingOptions()); + ASSERT_OK(demuxer.SetHandler("video", chunking_handler)); + ASSERT_OK(chunking_handler->SetHandler(0, muxer_video)); } std::shared_ptr muxer_audio; if (!audio_output.empty()) { muxer_audio.reset( - new mp4::MP4Muxer(SetupOptions(audio_output, single_segment))); + new mp4::MP4Muxer(SetupMuxerOptions(audio_output, single_segment))); muxer_audio->set_clock(&fake_clock_); if (enable_encryption) { @@ -172,7 +182,11 @@ void PackagerTestBasic::Remux(const std::string& input, kMaxUHD1Pixels, kClearLeadInSeconds, kCryptoDurationInSeconds, FOURCC_cenc); } - ASSERT_OK(demuxer.SetHandler("audio", muxer_audio)); + + auto chunking_handler = + std::make_shared(SetupChunkingOptions()); + ASSERT_OK(demuxer.SetHandler("audio", chunking_handler)); + ASSERT_OK(chunking_handler->SetHandler(0, muxer_audio)); } ASSERT_OK(demuxer.Initialize()); @@ -193,18 +207,20 @@ void PackagerTestBasic::Decrypt(const std::string& input, std::shared_ptr muxer; if (!video_output.empty()) { - muxer.reset( - new mp4::MP4Muxer(SetupOptions(video_output, true))); + muxer.reset(new mp4::MP4Muxer(SetupMuxerOptions(video_output, true))); } if (!audio_output.empty()) { - muxer.reset( - new mp4::MP4Muxer(SetupOptions(audio_output, true))); + muxer.reset(new mp4::MP4Muxer(SetupMuxerOptions(audio_output, true))); } ASSERT_TRUE(muxer); muxer->set_clock(&fake_clock_); - ASSERT_OK(demuxer.SetHandler("0", muxer)); - ASSERT_OK(demuxer.Initialize()); + auto chunking_handler = + std::make_shared(SetupChunkingOptions()); + ASSERT_OK(demuxer.SetHandler("0", chunking_handler)); + ASSERT_OK(chunking_handler->SetHandler(0, muxer)); + + ASSERT_OK(demuxer.Initialize()); ASSERT_OK(demuxer.Run()); } diff --git a/packager/packager.gyp b/packager/packager.gyp index 859217bf57..2232251ad6 100644 --- a/packager/packager.gyp +++ b/packager/packager.gyp @@ -42,6 +42,7 @@ 'dependencies': [ 'hls/hls.gyp:hls_builder', 'media/codecs/codecs.gyp:codecs', + 'media/chunking/chunking.gyp:chunking', 'media/event/media_event.gyp:media_event', 'media/file/file.gyp:file', 'media/formats/mp2t/mp2t.gyp:mp2t', @@ -85,6 +86,7 @@ ], 'dependencies': [ 'media/codecs/codecs.gyp:codecs', + 'media/chunking/chunking.gyp:chunking', 'media/file/file.gyp:file', 'media/formats/mp2t/mp2t.gyp:mp2t', 'media/formats/mp4/mp4.gyp:mp4',