From 4891d9a6bf96f3655a7df4b908f96cc036a8c51b Mon Sep 17 00:00:00 2001 From: Rintaro Kuroiwa Date: Mon, 13 Mar 2017 17:02:23 -0700 Subject: [PATCH] HLS with fragmented MP4 - Add EXT-X-MAP tag for init segment. - Do not set output field on stream descriptor if not specified on command line. If it's set (internally) then it gets copied to MediaInfo that gets passed to the manifest generators. b/36279481 Change-Id: I762c55b255699ec691817dc4806b0dee2f7504b8 --- packager/app/packager_main.cc | 2 +- packager/app/stream_descriptor.cc | 11 ++-- .../testdata/bear-640x360-a-enc-golden.m3u8 | 2 +- .../test/testdata/bear-640x360-a-golden.m3u8 | 2 +- .../testdata/bear-640x360-v-enc-golden.m3u8 | 2 +- .../test/testdata/bear-640x360-v-golden.m3u8 | 2 +- packager/hls/base/media_playlist.cc | 53 ++++++++++++------- packager/hls/base/media_playlist_unittest.cc | 44 ++++++++++++--- 8 files changed, 84 insertions(+), 34 deletions(-) diff --git a/packager/app/packager_main.cc b/packager/app/packager_main.cc index 05834a59470..b97fcfb45fa 100644 --- a/packager/app/packager_main.cc +++ b/packager/app/packager_main.cc @@ -296,7 +296,7 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors, remux_jobs->emplace_back(new RemuxJob(std::move(demuxer))); previous_input = stream_iter->input; // Skip setting up muxers if output is not needed. - if (stream_iter->output.empty()) + if (stream_iter->output.empty() && stream_iter->segment_template.empty()) continue; } DCHECK(!remux_jobs->empty()); diff --git a/packager/app/stream_descriptor.cc b/packager/app/stream_descriptor.cc index e314316302a..c08ed654ce4 100644 --- a/packager/app/stream_descriptor.cc +++ b/packager/app/stream_descriptor.cc @@ -187,12 +187,15 @@ bool InsertStreamDescriptor(const std::string& descriptor_string, << "' ignored. TS muxer does not support initialization " "segment generation."; } - // For convenience, set descriptor.output to descriptor.segment_template. It - // is only used for flag checking in variuos places. - descriptor.output = descriptor.segment_template; } - if (!FLAGS_dump_stream_info && descriptor.output.empty()) { + // For TS output, segment template is sufficient, and does not require an + // output entry. + const bool output_specified = + !descriptor.output.empty() || + (descriptor.output_format == CONTAINER_MPEG2TS && + !descriptor.segment_template.empty()); + if (!FLAGS_dump_stream_info && !output_specified) { LOG(ERROR) << "Stream output not specified."; return false; } 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 d352c7c2d30..c88c902b51d 100644 --- a/packager/app/test/testdata/bear-640x360-a-enc-golden.m3u8 +++ b/packager/app/test/testdata/bear-640x360-a-enc-golden.m3u8 @@ -1,5 +1,5 @@ #EXTM3U -#EXT-X-VERSION:5 +#EXT-X-VERSION:6 ## Generated with https://github.com/google/shaka-packager version -- #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD diff --git a/packager/app/test/testdata/bear-640x360-a-golden.m3u8 b/packager/app/test/testdata/bear-640x360-a-golden.m3u8 index 75059c47a7c..0209c9b3f2a 100644 --- a/packager/app/test/testdata/bear-640x360-a-golden.m3u8 +++ b/packager/app/test/testdata/bear-640x360-a-golden.m3u8 @@ -1,5 +1,5 @@ #EXTM3U -#EXT-X-VERSION:5 +#EXT-X-VERSION:6 ## Generated with https://github.com/google/shaka-packager version -- #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD diff --git a/packager/app/test/testdata/bear-640x360-v-enc-golden.m3u8 b/packager/app/test/testdata/bear-640x360-v-enc-golden.m3u8 index bc95bc1d1a2..c5b1330a309 100644 --- a/packager/app/test/testdata/bear-640x360-v-enc-golden.m3u8 +++ b/packager/app/test/testdata/bear-640x360-v-enc-golden.m3u8 @@ -1,5 +1,5 @@ #EXTM3U -#EXT-X-VERSION:5 +#EXT-X-VERSION:6 ## Generated with https://github.com/google/shaka-packager version -- #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD diff --git a/packager/app/test/testdata/bear-640x360-v-golden.m3u8 b/packager/app/test/testdata/bear-640x360-v-golden.m3u8 index 5389b34b763..14e87c02410 100644 --- a/packager/app/test/testdata/bear-640x360-v-golden.m3u8 +++ b/packager/app/test/testdata/bear-640x360-v-golden.m3u8 @@ -1,5 +1,5 @@ #EXTM3U -#EXT-X-VERSION:5 +#EXT-X-VERSION:6 ## Generated with https://github.com/google/shaka-packager version -- #EXT-X-TARGETDURATION:2 #EXT-X-PLAYLIST-TYPE:VOD diff --git a/packager/hls/base/media_playlist.cc b/packager/hls/base/media_playlist.cc index 640f474b6fa..3a97599c451 100644 --- a/packager/hls/base/media_playlist.cc +++ b/packager/hls/base/media_playlist.cc @@ -32,6 +32,39 @@ uint32_t GetTimeScale(const MediaInfo& media_info) { return 0u; } +std::string CreatePlaylistHeader( + const std::string& init_segment_name, + uint32_t target_duration, + MediaPlaylist::MediaPlaylistType type) { + const std::string version = GetPackagerVersion(); + std::string version_line; + if (!version.empty()) { + version_line = + base::StringPrintf("## Generated with %s version %s\n", + GetPackagerProjectUrl().c_str(), version.c_str()); + } + + // 6 is required for EXT-X-MAP without EXT-X-I-FRAMES-ONLY. + std::string header = base::StringPrintf( + "#EXTM3U\n" + "#EXT-X-VERSION:6\n" + "%s" + "#EXT-X-TARGETDURATION:%d\n", + version_line.c_str(), target_duration); + + if (type == MediaPlaylist::MediaPlaylistType::kVod) { + header += "#EXT-X-PLAYLIST-TYPE:VOD\n"; + } + + // Put EXT-X-MAP at the end since the rest of the playlist is about the + // segment and key info. + if (!init_segment_name.empty()) { + header += "#EXT-X-MAP:URI=\"" + init_segment_name + "\"\n"; + } + + return header; +} + class SegmentInfoEntry : public HlsEntry { public: SegmentInfoEntry(const std::string& file_name, double duration); @@ -266,24 +299,8 @@ bool MediaPlaylist::WriteToFile(media::File* file) { SetTargetDuration(ceil(GetLongestSegmentDuration())); } - const std::string version = GetPackagerVersion(); - std::string version_line; - if (!version.empty()) { - version_line = - base::StringPrintf("## Generated with %s version %s\n", - GetPackagerProjectUrl().c_str(), version.c_str()); - } - - // KEYFORMAT and KEYFORMATVERSIONS on EXT-X-KEY requires 5 or above. - std::string header = base::StringPrintf( - "#EXTM3U\n" - "#EXT-X-VERSION:5\n" - "%s" - "#EXT-X-TARGETDURATION:%d\n", - version_line.c_str(), target_duration_); - if (type_ == MediaPlaylistType::kVod) { - header += "#EXT-X-PLAYLIST-TYPE:VOD\n"; - } + std::string header = CreatePlaylistHeader(media_info_.init_segment_name(), + target_duration_, type_); std::string body; if (!entries_.empty()) { diff --git a/packager/hls/base/media_playlist_unittest.cc b/packager/hls/base/media_playlist_unittest.cc index ab7b321d173..950eb1f52da 100644 --- a/packager/hls/base/media_playlist_unittest.cc +++ b/packager/hls/base/media_playlist_unittest.cc @@ -119,7 +119,7 @@ TEST_F(MediaPlaylistTest, WriteToFile) { ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); const std::string kExpectedOutput = "#EXTM3U\n" - "#EXT-X-VERSION:5\n" + "#EXT-X-VERSION:6\n" "## Generated with https://github.com/google/shaka-packager version " "test\n" "#EXT-X-TARGETDURATION:0\n" @@ -176,7 +176,7 @@ TEST_F(MediaPlaylistTest, SetTargetDuration) { EXPECT_TRUE(media_playlist_.SetTargetDuration(20)); const std::string kExpectedOutput = "#EXTM3U\n" - "#EXT-X-VERSION:5\n" + "#EXT-X-VERSION:6\n" "## Generated with https://github.com/google/shaka-packager version " "test\n" "#EXT-X-TARGETDURATION:20\n" @@ -204,7 +204,7 @@ TEST_F(MediaPlaylistTest, WriteToFileWithSegments) { media_playlist_.AddSegment("file2.ts", 2700000, 5000000); const std::string kExpectedOutput = "#EXTM3U\n" - "#EXT-X-VERSION:5\n" + "#EXT-X-VERSION:6\n" "## Generated with https://github.com/google/shaka-packager version " "test\n" "#EXT-X-TARGETDURATION:30\n" @@ -235,7 +235,7 @@ TEST_F(MediaPlaylistTest, WriteToFileWithEncryptionInfo) { media_playlist_.AddSegment("file2.ts", 2700000, 5000000); const std::string kExpectedOutput = "#EXTM3U\n" - "#EXT-X-VERSION:5\n" + "#EXT-X-VERSION:6\n" "## Generated with https://github.com/google/shaka-packager version " "test\n" "#EXT-X-TARGETDURATION:30\n" @@ -269,7 +269,7 @@ TEST_F(MediaPlaylistTest, WriteToFileWithEncryptionInfoEmptyIv) { media_playlist_.AddSegment("file2.ts", 2700000, 5000000); const std::string kExpectedOutput = "#EXTM3U\n" - "#EXT-X-VERSION:5\n" + "#EXT-X-VERSION:6\n" "## Generated with https://github.com/google/shaka-packager version " "test\n" "#EXT-X-TARGETDURATION:30\n" @@ -302,7 +302,7 @@ TEST_F(MediaPlaylistTest, WriteToFileWithClearLead) { media_playlist_.AddSegment("file2.ts", 2700000, 5000000); const std::string kExpectedOutput = "#EXTM3U\n" - "#EXT-X-VERSION:5\n" + "#EXT-X-VERSION:6\n" "## Generated with https://github.com/google/shaka-packager version test\n" "#EXT-X-TARGETDURATION:30\n" "#EXT-X-PLAYLIST-TYPE:VOD\n" @@ -336,7 +336,7 @@ TEST_F(MediaPlaylistTest, RemoveOldestSegment) { const std::string kExpectedOutput = "#EXTM3U\n" - "#EXT-X-VERSION:5\n" + "#EXT-X-VERSION:6\n" "## Generated with https://github.com/google/shaka-packager version " "test\n" "#EXT-X-TARGETDURATION:30\n" @@ -370,5 +370,35 @@ TEST_F(MediaPlaylistTest, GetLanguage) { EXPECT_EQ("apa", media_playlist_.GetLanguage()); // no short form exists } +TEST_F(MediaPlaylistTest, InitSegment) { + valid_video_media_info_.set_reference_time_scale(90000); + valid_video_media_info_.set_init_segment_name("init_segment.mp4"); + ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); + + // 10 seconds. + media_playlist_.AddSegment("file1.mp4", 900000, 1000000); + // 30 seconds. + media_playlist_.AddSegment("file2.mp4", 2700000, 5000000); + + const std::string kExpectedOutput = + "#EXTM3U\n" + "#EXT-X-VERSION:6\n" + "## Generated with https://github.com/google/shaka-packager version test\n" + "#EXT-X-TARGETDURATION:30\n" + "#EXT-X-PLAYLIST-TYPE:VOD\n" + "#EXT-X-MAP:URI=\"init_segment.mp4\"\n" + "#EXTINF:10.000,\n" + "file1.mp4\n" + "#EXTINF:30.000,\n" + "file2.mp4\n" + "#EXT-X-ENDLIST\n"; + + MockFile file; + EXPECT_CALL(file, + Write(MatchesString(kExpectedOutput), kExpectedOutput.size())) + .WillOnce(ReturnArg<1>()); + EXPECT_TRUE(media_playlist_.WriteToFile(&file)); +} + } // namespace hls } // namespace shaka