diff --git a/packager/app/packager_main.cc b/packager/app/packager_main.cc index 82f2c3a3b0..11a699656f 100644 --- a/packager/app/packager_main.cc +++ b/packager/app/packager_main.cc @@ -238,13 +238,12 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors, DCHECK(!(mpd_notifier && hls_notifier)); DCHECK(remux_jobs); - // This is the counter for audio that doesn't have a name set. - int hls_audio_name_counter = 0; std::string previous_input; - for (StreamDescriptorList::const_iterator stream_iter = - stream_descriptors.begin(); + int stream_number = 0; + for (StreamDescriptorList::const_iterator + stream_iter = stream_descriptors.begin(); stream_iter != stream_descriptors.end(); - ++stream_iter) { + ++stream_iter, ++stream_number) { // Process stream descriptor. MuxerOptions stream_muxer_options(muxer_options); stream_muxer_options.output_file_name = stream_iter->output; @@ -317,20 +316,8 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors, } DCHECK(!remux_jobs->empty()); - MediaContainerName output_format = stream_iter->output_format; - if (output_format == CONTAINER_UNKNOWN) { - output_format = - DetermineContainerFromFileName(stream_muxer_options.output_file_name); - - if (output_format == CONTAINER_UNKNOWN) { - LOG(ERROR) << "Unable to determine output format for file " - << stream_muxer_options.output_file_name; - return false; - } - } - scoped_ptr muxer( - CreateOutputMuxer(stream_muxer_options, output_format)); + CreateOutputMuxer(stream_muxer_options, stream_iter->output_format)); if (FLAGS_use_fake_clock_for_muxer) muxer->set_clock(fake_clock); if (key_source) { @@ -359,17 +346,19 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors, if (hls_notifier) { // TODO(rkuroiwa): Do some smart stuff to group the audios, e.g. detect - // languages. Also detect whether it is audio so that the counter for - // audio%d is continuous. + // languages. std::string group_id = stream_iter->hls_group_id; std::string name = stream_iter->hls_name; + std::string hls_playlist_name = stream_iter->hls_playlist_name; if (group_id.empty()) group_id = "audio"; if (name.empty()) - name = base::StringPrintf("audio%d", hls_audio_name_counter++); + name = base::StringPrintf("stream_%d", stream_number); + if (hls_playlist_name.empty()) + hls_playlist_name = base::StringPrintf("stream_%d.m3u8", stream_number); - muxer_listener.reset(new HlsNotifyMuxerListener( - stream_iter->hls_playlist_name, name, group_id, hls_notifier)); + muxer_listener.reset(new HlsNotifyMuxerListener(hls_playlist_name, name, + group_id, hls_notifier)); } if (muxer_listener) diff --git a/packager/app/stream_descriptor.cc b/packager/app/stream_descriptor.cc index 559f66db8e..bdf95546bf 100644 --- a/packager/app/stream_descriptor.cc +++ b/packager/app/stream_descriptor.cc @@ -10,6 +10,7 @@ #include "packager/base/logging.h" #include "packager/base/strings/string_number_conversions.h" #include "packager/base/strings/string_split.h" +#include "packager/media/base/container_names.h" #include "packager/mpd/base/language_utils.h" namespace shaka { @@ -159,13 +160,39 @@ bool InsertStreamDescriptor(const std::string& descriptor_string, return false; } - // Note that MPEG2 TS doesn't need a separate initialization segment, so - // output field is ignored. - const bool is_mpeg2ts_with_segment_template = - descriptor.output_format == MediaContainerName::CONTAINER_MPEG2TS && - !descriptor.segment_template.empty(); - if (!FLAGS_dump_stream_info && descriptor.output.empty() && - !is_mpeg2ts_with_segment_template) { + if (descriptor.output_format == CONTAINER_UNKNOWN) { + const std::string& output_name = descriptor.output.empty() + ? descriptor.segment_template + : descriptor.output; + if (!output_name.empty()) { + descriptor.output_format = DetermineContainerFromFileName(output_name); + if (descriptor.output_format == CONTAINER_UNKNOWN) { + LOG(ERROR) << "Unable to determine output format for file " + << output_name; + return false; + } + } + } + + if (descriptor.output_format == MediaContainerName::CONTAINER_MPEG2TS) { + if (descriptor.segment_template.empty()) { + LOG(ERROR) << "Please specify segment_template. Single file TS output is " + "not supported."; + return false; + } + // Note that MPEG2 TS doesn't need a separate initialization segment, so + // output field is not needed. + if (!descriptor.output.empty()) { + LOG(WARNING) << "TS output '" << descriptor.output + << "' 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()) { LOG(ERROR) << "Stream output not specified."; return false; } diff --git a/packager/app/test/packager_app.py b/packager/app/test/packager_app.py index 9502a246a0..f07d40ed04 100644 --- a/packager/app/test/packager_app.py +++ b/packager/app/test/packager_app.py @@ -5,10 +5,8 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file or at # https://developers.google.com/open-source/licenses/bsd - """Test wrapper for the sample packager binary.""" - import os import subprocess diff --git a/packager/app/test/packager_test.py b/packager/app/test/packager_test.py index 1f1cae2546..10e2c092b8 100755 --- a/packager/app/test/packager_test.py +++ b/packager/app/test/packager_test.py @@ -30,6 +30,7 @@ class PackagerAppTest(unittest.TestCase): 'test', 'testdata') self.output_prefix = os.path.join(self.tmp_dir, 'output') self.mpd_output = self.output_prefix + '.mpd' + self.hls_master_playlist_output = self.output_prefix + '.m3u8' self.output = None def tearDown(self): @@ -107,15 +108,19 @@ class PackagerAppTest(unittest.TestCase): output_format='ts', live=True, test_files=['bear-640x360.ts']), - self._GetFlags(live=True)) + self._GetFlags(live=True, output_hls=True)) self._DiffLiveGold(self.output[0], 'bear-640x360-a-golden', - has_init_segment=False, - segment_extension='ts') + output_format='ts') self._DiffLiveGold(self.output[1], 'bear-640x360-v-golden', - has_init_segment=False, - segment_extension='ts') + output_format='ts') + self._DiffGold(self.hls_master_playlist_output, + 'bear-640x360-av-master-golden.m3u8') + self._DiffGold( + os.path.join(self.tmp_dir, 'audio.m3u8'), 'bear-640x360-a-golden.m3u8') + self._DiffGold( + os.path.join(self.tmp_dir, 'video.m3u8'), 'bear-640x360-v-golden.m3u8') def testPackageVp8Webm(self): self.packager.Package( @@ -188,6 +193,30 @@ class PackagerAppTest(unittest.TestCase): self._VerifyDecryption(self.output[0], 'bear-640x360-a-golden.mp4') self._VerifyDecryption(self.output[1], 'bear-640x360-v-golden.mp4') + def testPackageAvcTsWithEncryption(self): + # Currently we only support live packaging for ts. + self.packager.Package( + self._GetStreams(['audio', 'video'], + output_format='ts', + live=True, + test_files=['bear-640x360.ts']), + self._GetFlags(encryption=True, + live=True, output_hls=True)) + self._DiffLiveGold(self.output[0], + 'bear-640x360-a-enc-golden', + output_format='ts') + self._DiffLiveGold(self.output[1], + 'bear-640x360-v-enc-golden', + output_format='ts') + self._DiffGold(self.hls_master_playlist_output, + 'bear-640x360-av-enc-master-golden.m3u8') + self._DiffGold( + os.path.join(self.tmp_dir, 'audio.m3u8'), + 'bear-640x360-a-enc-golden.m3u8') + self._DiffGold( + os.path.join(self.tmp_dir, 'video.m3u8'), + 'bear-640x360-v-enc-golden.m3u8') + def testPackageWebmWithEncryption(self): self.packager.Package( self._GetStreams(['video'], @@ -392,10 +421,18 @@ class PackagerAppTest(unittest.TestCase): output_prefix = '%s_%d_%s' % (self.output_prefix, test_file_index, stream_descriptor) if live: - stream = ('input=%s,stream=%s,format=%s,init_segment=%s-init.mp4,' - 'segment_template=%s-$Number$.m4s') - streams.append(stream % (test_file, stream_descriptor, output_format, - output_prefix, output_prefix)) + if output_format == 'ts': + stream = ('input=%s,stream=%s,format=%s,' + 'segment_template=%s-$Number$.ts,playlist_name=%s.m3u8') + streams.append(stream % (test_file, stream_descriptor, + output_format, output_prefix, + stream_descriptor)) + else: + stream = ('input=%s,stream=%s,format=%s,init_segment=%s-init.mp4,' + 'segment_template=%s-$Number$.m4s') + streams.append(stream % (test_file, stream_descriptor, + output_format, output_prefix, + output_prefix)) self.output.append(output_prefix) else: output = '%s.%s' % ( @@ -423,6 +460,7 @@ class PackagerAppTest(unittest.TestCase): live=False, dash_if_iop=False, output_media_info=False, + output_hls=False, use_fake_clock=True): flags = [] if widevine_encryption: @@ -432,14 +470,16 @@ class PackagerAppTest(unittest.TestCase): '--key_server_url=' + widevine_server_url, '--content_id=3031323334353637', '--signer=widevine_test'] elif encryption: - pssh_box = ('000000307073736800000000' # PSSH header - 'edef8ba979d64acea3c827dcd51d21ed' # Widevine system ID - '00000010' # Data size - '31323334353637383930313233343536') # Data flags += ['--enable_fixed_key_encryption', '--key_id=31323334353637383930313233343536', - '--key=32333435363738393021323334353637', '--pssh=' + pssh_box, - '--clear_lead=1'] + '--key=32333435363738393021323334353637', '--clear_lead=1'] + if not output_hls: + pssh_box = ('000000307073736800000000' # PSSH header + 'edef8ba979d64acea3c827dcd51d21ed' # Widevine system ID + '00000010' # Data size + '31323334353637383930313233343536') # Data + flags.append('--pssh=' + pssh_box) + if not random_iv: flags.append('--iv=3334353637383930') if protection_scheme: @@ -459,6 +499,8 @@ class PackagerAppTest(unittest.TestCase): flags.append('--generate_dash_if_iop_compliant_mpd') if output_media_info: flags.append('--output_media_info') + elif output_hls: + flags += ['--hls_master_playlist_output', self.hls_master_playlist_output] else: flags += ['--mpd_output', self.mpd_output] @@ -508,15 +550,18 @@ class PackagerAppTest(unittest.TestCase): def _DiffLiveGold(self, test_output_prefix, golden_file_name_prefix, - has_init_segment=True, - segment_extension='m4s'): + output_format='mp4'): # Compare init and the first three segments. - if has_init_segment: + if output_format == 'ts': + for i in range(1, 4): + self._DiffGold('%s-%d.ts' % (test_output_prefix, i), + '%s-%d.ts' % (golden_file_name_prefix, i)) + else: self._DiffGold(test_output_prefix + '-init.mp4', golden_file_name_prefix + '-init.mp4') - for i in range(1, 4): - self._DiffGold('%s-%d.m4s' % (test_output_prefix, i), '%s-%d.%s' % - (golden_file_name_prefix, i, segment_extension)) + for i in range(1, 4): + self._DiffGold('%s-%d.m4s' % (test_output_prefix, i), + '%s-%d.m4s' % (golden_file_name_prefix, i)) # Live mpd contains current availabilityStartTime and publishTime, which # needs to be replaced for comparison. 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 new file mode 100644 index 0000000000..50d83fd065 Binary files /dev/null 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 new file mode 100644 index 0000000000..1f115bcd44 Binary files /dev/null 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 new file mode 100644 index 0000000000..d00f931a80 Binary files /dev/null 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 new file mode 100644 index 0000000000..2f53f5c57d --- /dev/null +++ b/packager/app/test/testdata/bear-640x360-a-enc-golden.m3u8 @@ -0,0 +1,13 @@ +#EXTM3U +#EXT-X-VERSION:5 +#EXT-X-TARGETDURATION:2 +#EXT-X-PLAYLIST-TYPE:VOD +#EXTINF:1.021, +output_audio-1.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, +output_audio-3.ts +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/bear-640x360-a-golden.m3u8 b/packager/app/test/testdata/bear-640x360-a-golden.m3u8 new file mode 100644 index 0000000000..a64e3c7bf1 --- /dev/null +++ b/packager/app/test/testdata/bear-640x360-a-golden.m3u8 @@ -0,0 +1,11 @@ +#EXTM3U +#EXT-X-VERSION:5 +#EXT-X-TARGETDURATION:2 +#EXT-X-PLAYLIST-TYPE:VOD +#EXTINF:1.021, +output_audio-1.ts +#EXTINF:1.021, +output_audio-2.ts +#EXTINF:0.720, +output_audio-3.ts +#EXT-X-ENDLIST 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 new file mode 100644 index 0000000000..6b7ee89a16 --- /dev/null +++ b/packager/app/test/testdata/bear-640x360-av-enc-master-golden.m3u8 @@ -0,0 +1,4 @@ +#EXTM3U +#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 +video.m3u8 diff --git a/packager/app/test/testdata/bear-640x360-av-master-golden.m3u8 b/packager/app/test/testdata/bear-640x360-av-master-golden.m3u8 new file mode 100644 index 0000000000..6b7ee89a16 --- /dev/null +++ b/packager/app/test/testdata/bear-640x360-av-master-golden.m3u8 @@ -0,0 +1,4 @@ +#EXTM3U +#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 +video.m3u8 diff --git a/packager/app/test/testdata/bear-640x360-v-enc-golden-1.ts b/packager/app/test/testdata/bear-640x360-v-enc-golden-1.ts new file mode 100644 index 0000000000..c8600d8422 Binary files /dev/null and b/packager/app/test/testdata/bear-640x360-v-enc-golden-1.ts differ diff --git a/packager/app/test/testdata/bear-640x360-v-enc-golden-2.ts b/packager/app/test/testdata/bear-640x360-v-enc-golden-2.ts new file mode 100644 index 0000000000..80d082525f Binary files /dev/null and b/packager/app/test/testdata/bear-640x360-v-enc-golden-2.ts differ diff --git a/packager/app/test/testdata/bear-640x360-v-enc-golden-3.ts b/packager/app/test/testdata/bear-640x360-v-enc-golden-3.ts new file mode 100644 index 0000000000..679ea2f35b Binary files /dev/null and b/packager/app/test/testdata/bear-640x360-v-enc-golden-3.ts differ diff --git a/packager/app/test/testdata/bear-640x360-v-enc-golden.m3u8 b/packager/app/test/testdata/bear-640x360-v-enc-golden.m3u8 new file mode 100644 index 0000000000..ca686acd5e --- /dev/null +++ b/packager/app/test/testdata/bear-640x360-v-enc-golden.m3u8 @@ -0,0 +1,13 @@ +#EXTM3U +#EXT-X-VERSION:5 +#EXT-X-TARGETDURATION:2 +#EXT-X-PLAYLIST-TYPE:VOD +#EXTINF:1.001, +output_video-1.ts +#EXT-X-DISCONTINUITY +#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",IV=0x3334353637383930,KEYFORMAT="identity" +#EXTINF:1.001, +output_video-2.ts +#EXTINF:0.734, +output_video-3.ts +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/bear-640x360-v-golden.m3u8 b/packager/app/test/testdata/bear-640x360-v-golden.m3u8 new file mode 100644 index 0000000000..74edb5a1df --- /dev/null +++ b/packager/app/test/testdata/bear-640x360-v-golden.m3u8 @@ -0,0 +1,11 @@ +#EXTM3U +#EXT-X-VERSION:5 +#EXT-X-TARGETDURATION:2 +#EXT-X-PLAYLIST-TYPE:VOD +#EXTINF:1.001, +output_video-1.ts +#EXTINF:1.001, +output_video-2.ts +#EXTINF:0.734, +output_video-3.ts +#EXT-X-ENDLIST