Adjust hls descriptor validation
- Try to determine container name from segment_template if output is not specified - segment_template descriptor is required for TS - output descriptor should not be specified for TS - Generate hls_playlist_name from stream number if not specified - Update packager_test.py to include playlist validation b/29551186 Change-Id: Ide7169f1c26c85a6e38272dda0e1af77135b4d90
This commit is contained in:
parent
1095b73a44
commit
5f0d138ec9
|
@ -238,13 +238,12 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
||||||
DCHECK(!(mpd_notifier && hls_notifier));
|
DCHECK(!(mpd_notifier && hls_notifier));
|
||||||
DCHECK(remux_jobs);
|
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;
|
std::string previous_input;
|
||||||
for (StreamDescriptorList::const_iterator stream_iter =
|
int stream_number = 0;
|
||||||
stream_descriptors.begin();
|
for (StreamDescriptorList::const_iterator
|
||||||
|
stream_iter = stream_descriptors.begin();
|
||||||
stream_iter != stream_descriptors.end();
|
stream_iter != stream_descriptors.end();
|
||||||
++stream_iter) {
|
++stream_iter, ++stream_number) {
|
||||||
// Process stream descriptor.
|
// Process stream descriptor.
|
||||||
MuxerOptions stream_muxer_options(muxer_options);
|
MuxerOptions stream_muxer_options(muxer_options);
|
||||||
stream_muxer_options.output_file_name = stream_iter->output;
|
stream_muxer_options.output_file_name = stream_iter->output;
|
||||||
|
@ -317,20 +316,8 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
||||||
}
|
}
|
||||||
DCHECK(!remux_jobs->empty());
|
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> muxer(
|
scoped_ptr<Muxer> 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 (FLAGS_use_fake_clock_for_muxer) muxer->set_clock(fake_clock);
|
||||||
|
|
||||||
if (key_source) {
|
if (key_source) {
|
||||||
|
@ -359,17 +346,19 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
||||||
|
|
||||||
if (hls_notifier) {
|
if (hls_notifier) {
|
||||||
// TODO(rkuroiwa): Do some smart stuff to group the audios, e.g. detect
|
// 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
|
// languages.
|
||||||
// audio%d is continuous.
|
|
||||||
std::string group_id = stream_iter->hls_group_id;
|
std::string group_id = stream_iter->hls_group_id;
|
||||||
std::string name = stream_iter->hls_name;
|
std::string name = stream_iter->hls_name;
|
||||||
|
std::string hls_playlist_name = stream_iter->hls_playlist_name;
|
||||||
if (group_id.empty())
|
if (group_id.empty())
|
||||||
group_id = "audio";
|
group_id = "audio";
|
||||||
if (name.empty())
|
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(
|
muxer_listener.reset(new HlsNotifyMuxerListener(hls_playlist_name, name,
|
||||||
stream_iter->hls_playlist_name, name, group_id, hls_notifier));
|
group_id, hls_notifier));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (muxer_listener)
|
if (muxer_listener)
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "packager/base/logging.h"
|
#include "packager/base/logging.h"
|
||||||
#include "packager/base/strings/string_number_conversions.h"
|
#include "packager/base/strings/string_number_conversions.h"
|
||||||
#include "packager/base/strings/string_split.h"
|
#include "packager/base/strings/string_split.h"
|
||||||
|
#include "packager/media/base/container_names.h"
|
||||||
#include "packager/mpd/base/language_utils.h"
|
#include "packager/mpd/base/language_utils.h"
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
|
@ -159,13 +160,39 @@ bool InsertStreamDescriptor(const std::string& descriptor_string,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// Note that MPEG2 TS doesn't need a separate initialization segment, so
|
||||||
// output field is ignored.
|
// output field is not needed.
|
||||||
const bool is_mpeg2ts_with_segment_template =
|
if (!descriptor.output.empty()) {
|
||||||
descriptor.output_format == MediaContainerName::CONTAINER_MPEG2TS &&
|
LOG(WARNING) << "TS output '" << descriptor.output
|
||||||
!descriptor.segment_template.empty();
|
<< "' ignored. TS muxer does not support initialization "
|
||||||
if (!FLAGS_dump_stream_info && descriptor.output.empty() &&
|
"segment generation.";
|
||||||
!is_mpeg2ts_with_segment_template) {
|
}
|
||||||
|
// 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.";
|
LOG(ERROR) << "Stream output not specified.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,8 @@
|
||||||
# Use of this source code is governed by a BSD-style
|
# Use of this source code is governed by a BSD-style
|
||||||
# license that can be found in the LICENSE file or at
|
# license that can be found in the LICENSE file or at
|
||||||
# https://developers.google.com/open-source/licenses/bsd
|
# https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
"""Test wrapper for the sample packager binary."""
|
"""Test wrapper for the sample packager binary."""
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ class PackagerAppTest(unittest.TestCase):
|
||||||
'test', 'testdata')
|
'test', 'testdata')
|
||||||
self.output_prefix = os.path.join(self.tmp_dir, 'output')
|
self.output_prefix = os.path.join(self.tmp_dir, 'output')
|
||||||
self.mpd_output = self.output_prefix + '.mpd'
|
self.mpd_output = self.output_prefix + '.mpd'
|
||||||
|
self.hls_master_playlist_output = self.output_prefix + '.m3u8'
|
||||||
self.output = None
|
self.output = None
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
@ -107,15 +108,19 @@ class PackagerAppTest(unittest.TestCase):
|
||||||
output_format='ts',
|
output_format='ts',
|
||||||
live=True,
|
live=True,
|
||||||
test_files=['bear-640x360.ts']),
|
test_files=['bear-640x360.ts']),
|
||||||
self._GetFlags(live=True))
|
self._GetFlags(live=True, output_hls=True))
|
||||||
self._DiffLiveGold(self.output[0],
|
self._DiffLiveGold(self.output[0],
|
||||||
'bear-640x360-a-golden',
|
'bear-640x360-a-golden',
|
||||||
has_init_segment=False,
|
output_format='ts')
|
||||||
segment_extension='ts')
|
|
||||||
self._DiffLiveGold(self.output[1],
|
self._DiffLiveGold(self.output[1],
|
||||||
'bear-640x360-v-golden',
|
'bear-640x360-v-golden',
|
||||||
has_init_segment=False,
|
output_format='ts')
|
||||||
segment_extension='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):
|
def testPackageVp8Webm(self):
|
||||||
self.packager.Package(
|
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[0], 'bear-640x360-a-golden.mp4')
|
||||||
self._VerifyDecryption(self.output[1], 'bear-640x360-v-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):
|
def testPackageWebmWithEncryption(self):
|
||||||
self.packager.Package(
|
self.packager.Package(
|
||||||
self._GetStreams(['video'],
|
self._GetStreams(['video'],
|
||||||
|
@ -392,10 +421,18 @@ class PackagerAppTest(unittest.TestCase):
|
||||||
output_prefix = '%s_%d_%s' % (self.output_prefix, test_file_index,
|
output_prefix = '%s_%d_%s' % (self.output_prefix, test_file_index,
|
||||||
stream_descriptor)
|
stream_descriptor)
|
||||||
if live:
|
if live:
|
||||||
|
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,'
|
stream = ('input=%s,stream=%s,format=%s,init_segment=%s-init.mp4,'
|
||||||
'segment_template=%s-$Number$.m4s')
|
'segment_template=%s-$Number$.m4s')
|
||||||
streams.append(stream % (test_file, stream_descriptor, output_format,
|
streams.append(stream % (test_file, stream_descriptor,
|
||||||
output_prefix, output_prefix))
|
output_format, output_prefix,
|
||||||
|
output_prefix))
|
||||||
self.output.append(output_prefix)
|
self.output.append(output_prefix)
|
||||||
else:
|
else:
|
||||||
output = '%s.%s' % (
|
output = '%s.%s' % (
|
||||||
|
@ -423,6 +460,7 @@ class PackagerAppTest(unittest.TestCase):
|
||||||
live=False,
|
live=False,
|
||||||
dash_if_iop=False,
|
dash_if_iop=False,
|
||||||
output_media_info=False,
|
output_media_info=False,
|
||||||
|
output_hls=False,
|
||||||
use_fake_clock=True):
|
use_fake_clock=True):
|
||||||
flags = []
|
flags = []
|
||||||
if widevine_encryption:
|
if widevine_encryption:
|
||||||
|
@ -432,14 +470,16 @@ class PackagerAppTest(unittest.TestCase):
|
||||||
'--key_server_url=' + widevine_server_url,
|
'--key_server_url=' + widevine_server_url,
|
||||||
'--content_id=3031323334353637', '--signer=widevine_test']
|
'--content_id=3031323334353637', '--signer=widevine_test']
|
||||||
elif encryption:
|
elif encryption:
|
||||||
|
flags += ['--enable_fixed_key_encryption',
|
||||||
|
'--key_id=31323334353637383930313233343536',
|
||||||
|
'--key=32333435363738393021323334353637', '--clear_lead=1']
|
||||||
|
if not output_hls:
|
||||||
pssh_box = ('000000307073736800000000' # PSSH header
|
pssh_box = ('000000307073736800000000' # PSSH header
|
||||||
'edef8ba979d64acea3c827dcd51d21ed' # Widevine system ID
|
'edef8ba979d64acea3c827dcd51d21ed' # Widevine system ID
|
||||||
'00000010' # Data size
|
'00000010' # Data size
|
||||||
'31323334353637383930313233343536') # Data
|
'31323334353637383930313233343536') # Data
|
||||||
flags += ['--enable_fixed_key_encryption',
|
flags.append('--pssh=' + pssh_box)
|
||||||
'--key_id=31323334353637383930313233343536',
|
|
||||||
'--key=32333435363738393021323334353637', '--pssh=' + pssh_box,
|
|
||||||
'--clear_lead=1']
|
|
||||||
if not random_iv:
|
if not random_iv:
|
||||||
flags.append('--iv=3334353637383930')
|
flags.append('--iv=3334353637383930')
|
||||||
if protection_scheme:
|
if protection_scheme:
|
||||||
|
@ -459,6 +499,8 @@ class PackagerAppTest(unittest.TestCase):
|
||||||
flags.append('--generate_dash_if_iop_compliant_mpd')
|
flags.append('--generate_dash_if_iop_compliant_mpd')
|
||||||
if output_media_info:
|
if output_media_info:
|
||||||
flags.append('--output_media_info')
|
flags.append('--output_media_info')
|
||||||
|
elif output_hls:
|
||||||
|
flags += ['--hls_master_playlist_output', self.hls_master_playlist_output]
|
||||||
else:
|
else:
|
||||||
flags += ['--mpd_output', self.mpd_output]
|
flags += ['--mpd_output', self.mpd_output]
|
||||||
|
|
||||||
|
@ -508,15 +550,18 @@ class PackagerAppTest(unittest.TestCase):
|
||||||
def _DiffLiveGold(self,
|
def _DiffLiveGold(self,
|
||||||
test_output_prefix,
|
test_output_prefix,
|
||||||
golden_file_name_prefix,
|
golden_file_name_prefix,
|
||||||
has_init_segment=True,
|
output_format='mp4'):
|
||||||
segment_extension='m4s'):
|
|
||||||
# Compare init and the first three segments.
|
# 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',
|
self._DiffGold(test_output_prefix + '-init.mp4',
|
||||||
golden_file_name_prefix + '-init.mp4')
|
golden_file_name_prefix + '-init.mp4')
|
||||||
for i in range(1, 4):
|
for i in range(1, 4):
|
||||||
self._DiffGold('%s-%d.m4s' % (test_output_prefix, i), '%s-%d.%s' %
|
self._DiffGold('%s-%d.m4s' % (test_output_prefix, i),
|
||||||
(golden_file_name_prefix, i, segment_extension))
|
'%s-%d.m4s' % (golden_file_name_prefix, i))
|
||||||
|
|
||||||
# Live mpd contains current availabilityStartTime and publishTime, which
|
# Live mpd contains current availabilityStartTime and publishTime, which
|
||||||
# needs to be replaced for comparison.
|
# needs to be replaced for comparison.
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue