Merge branch 'main' into sbd
This commit is contained in:
commit
e8657721cd
|
@ -12,5 +12,5 @@ DASH specific stream descriptor fields
|
||||||
|
|
||||||
Optional semicolon separated list of values for DASH Role element. The
|
Optional semicolon separated list of values for DASH Role element. The
|
||||||
value should be one of: **caption**, **subtitle**, **main**, **alternate**,
|
value should be one of: **caption**, **subtitle**, **main**, **alternate**,
|
||||||
**supplementary**, **commentary**, **description** and **dub**. See
|
**supplementary**, **commentary**, **description**, **dub** and **forced-subtitle** .
|
||||||
DASH (ISO/IEC 23009-1) specification for details.
|
See DASH (ISO/IEC 23009-1) specification for details.
|
||||||
|
|
|
@ -76,6 +76,14 @@ HLS options
|
||||||
The EXT-X-MEDIA-SEQUENCE documentation can be read here:
|
The EXT-X-MEDIA-SEQUENCE documentation can be read here:
|
||||||
https://tools.ietf.org/html/rfc8216#section-4.3.3.2.
|
https://tools.ietf.org/html/rfc8216#section-4.3.3.2.
|
||||||
|
|
||||||
|
--hls_start_time_offset <seconds>
|
||||||
|
|
||||||
|
Sets EXT-X-START on the media playlists to specify the preferred point
|
||||||
|
at wich the player should start playing.
|
||||||
|
A positive number indicates a time offset from the beginning of the playlist.
|
||||||
|
A negative number indicates a negative time offset from the end of the
|
||||||
|
last media segment in the playlist.
|
||||||
|
|
||||||
--hls_only=0|1
|
--hls_only=0|1
|
||||||
|
|
||||||
Optional. Defaults to 0 if not specified. If it is set to 1, indicates the
|
Optional. Defaults to 0 if not specified. If it is set to 1, indicates the
|
||||||
|
|
|
@ -73,6 +73,15 @@ These are the available fields:
|
||||||
CEA allows specifying up to 4 streams within a single video stream. If not
|
CEA allows specifying up to 4 streams within a single video stream. If not
|
||||||
specified, all subtitles will be merged together.
|
specified, all subtitles will be merged together.
|
||||||
|
|
||||||
|
:forced_subtitle:
|
||||||
|
|
||||||
|
Optional boolean value (0|1). If set to 1 indicates that this stream is a
|
||||||
|
Forced Narrative subtitle that should be displayed when subtitles are otherwise
|
||||||
|
off, for example used to caption short portions of the audio that might be in
|
||||||
|
a foreign language. For DASH this will set role to **forced_subtitle**, for HLS
|
||||||
|
it will set FORCED=YES and AUTOSELECT=YES. Only valid for subtitles.
|
||||||
|
|
||||||
|
|
||||||
.. include:: /options/drm_stream_descriptors.rst
|
.. include:: /options/drm_stream_descriptors.rst
|
||||||
.. include:: /options/dash_stream_descriptors.rst
|
.. include:: /options/dash_stream_descriptors.rst
|
||||||
.. include:: /options/hls_stream_descriptors.rst
|
.. include:: /options/hls_stream_descriptors.rst
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#define PACKAGER_PUBLIC_HLS_PARAMS_H_
|
#define PACKAGER_PUBLIC_HLS_PARAMS_H_
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
|
@ -63,6 +64,12 @@ struct HlsParams {
|
||||||
/// Custom EXT-X-MEDIA-SEQUENCE value to allow continuous media playback
|
/// Custom EXT-X-MEDIA-SEQUENCE value to allow continuous media playback
|
||||||
/// across packager restarts. See #691 for details.
|
/// across packager restarts. See #691 for details.
|
||||||
uint32_t media_sequence_number = 0;
|
uint32_t media_sequence_number = 0;
|
||||||
|
/// Sets EXT-X-START on the media playlists to specify the preferred point
|
||||||
|
/// at wich the player should start playing.
|
||||||
|
/// A positive number indicates a time offset from the beginning of the
|
||||||
|
/// playlist. A negative number indicates a negative time offset from the end
|
||||||
|
/// of the last media segment in the playlist.
|
||||||
|
std::optional<double> start_time_offset;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -152,6 +152,9 @@ struct StreamDescriptor {
|
||||||
/// Set to true to indicate that the stream is for hls only.
|
/// Set to true to indicate that the stream is for hls only.
|
||||||
bool hls_only = false;
|
bool hls_only = false;
|
||||||
|
|
||||||
|
/// Optional, indicates if this is a Forced Narrative subtitle stream.
|
||||||
|
bool forced_subtitle = false;
|
||||||
|
|
||||||
/// Optional for DASH output. It defines the Label element in Adaptation Set.
|
/// Optional for DASH output. It defines the Label element in Adaptation Set.
|
||||||
std::string dash_label;
|
std::string dash_label;
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
|
|
||||||
#include <packager/app/hls_flags.h>
|
#include <packager/app/hls_flags.h>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
ABSL_FLAG(std::string,
|
ABSL_FLAG(std::string,
|
||||||
hls_master_playlist_output,
|
hls_master_playlist_output,
|
||||||
"",
|
"",
|
||||||
|
@ -35,3 +37,12 @@ ABSL_FLAG(int32_t,
|
||||||
"EXT-X-MEDIA-SEQUENCE value, which allows continuous media "
|
"EXT-X-MEDIA-SEQUENCE value, which allows continuous media "
|
||||||
"sequence across packager restarts. See #691 for more "
|
"sequence across packager restarts. See #691 for more "
|
||||||
"information about the reasoning of this and its use cases.");
|
"information about the reasoning of this and its use cases.");
|
||||||
|
ABSL_FLAG(std::optional<double>,
|
||||||
|
hls_start_time_offset,
|
||||||
|
std::nullopt,
|
||||||
|
"Floating-point number. Sets EXT-X-START on the media playlists "
|
||||||
|
"to specify the preferred point at wich the player should start "
|
||||||
|
"playing. A positive number indicates a time offset from the "
|
||||||
|
"beginning of the playlist. A negative number indicates a "
|
||||||
|
"negative time offset from the end of the last media segment "
|
||||||
|
"in the playlist.");
|
||||||
|
|
|
@ -15,5 +15,6 @@ ABSL_DECLARE_FLAG(std::string, hls_base_url);
|
||||||
ABSL_DECLARE_FLAG(std::string, hls_key_uri);
|
ABSL_DECLARE_FLAG(std::string, hls_key_uri);
|
||||||
ABSL_DECLARE_FLAG(std::string, hls_playlist_type);
|
ABSL_DECLARE_FLAG(std::string, hls_playlist_type);
|
||||||
ABSL_DECLARE_FLAG(int32_t, hls_media_sequence_number);
|
ABSL_DECLARE_FLAG(int32_t, hls_media_sequence_number);
|
||||||
|
ABSL_DECLARE_FLAG(std::optional<double>, hls_start_time_offset);
|
||||||
|
|
||||||
#endif // PACKAGER_APP_HLS_FLAGS_H_
|
#endif // PACKAGER_APP_HLS_FLAGS_H_
|
||||||
|
|
|
@ -120,8 +120,16 @@ const char kUsage[] =
|
||||||
" in the format: scheme_id_uri=value.\n"
|
" in the format: scheme_id_uri=value.\n"
|
||||||
" - dash_roles (roles): Optional semicolon separated list of values for\n"
|
" - dash_roles (roles): Optional semicolon separated list of values for\n"
|
||||||
" DASH Role elements. The value should be one of: caption, subtitle,\n"
|
" DASH Role elements. The value should be one of: caption, subtitle,\n"
|
||||||
" main, alternate, supplementary, commentary, description and dub. See\n"
|
" forced-subtitle, main, alternate, supplementary, commentary, \n"
|
||||||
" DASH (ISO/IEC 23009-1) specification for details.\n";
|
" description and dub. See DASH\n"
|
||||||
|
" (ISO/IEC 23009-1) specification for details.\n"
|
||||||
|
" - forced_subtitle: Optional boolean value (0|1). If set to 1 \n"
|
||||||
|
" indicates that this stream is a Forced Narrative subtitle that \n"
|
||||||
|
" should be displayed when subtitles are otherwise off, for example \n"
|
||||||
|
" used to caption short portions of the audio that might be in a \n"
|
||||||
|
" foreign language. For DASH this will set role to forced_subtitle, \n"
|
||||||
|
" for HLS it will set FORCED=YES and AUTOSELECT=YES. \n"
|
||||||
|
" Only valid for subtitles.";
|
||||||
|
|
||||||
// Labels for parameters in RawKey key info.
|
// Labels for parameters in RawKey key info.
|
||||||
const char kDrmLabelLabel[] = "label";
|
const char kDrmLabelLabel[] = "label";
|
||||||
|
@ -587,6 +595,7 @@ std::optional<PackagingParams> GetPackagingParams() {
|
||||||
hls_params.default_text_language = absl::GetFlag(FLAGS_default_text_language);
|
hls_params.default_text_language = absl::GetFlag(FLAGS_default_text_language);
|
||||||
hls_params.media_sequence_number =
|
hls_params.media_sequence_number =
|
||||||
absl::GetFlag(FLAGS_hls_media_sequence_number);
|
absl::GetFlag(FLAGS_hls_media_sequence_number);
|
||||||
|
hls_params.start_time_offset = absl::GetFlag(FLAGS_hls_start_time_offset);
|
||||||
|
|
||||||
TestParams& test_params = packaging_params.test_params;
|
TestParams& test_params = packaging_params.test_params;
|
||||||
test_params.dump_stream_info = absl::GetFlag(FLAGS_dump_stream_info);
|
test_params.dump_stream_info = absl::GetFlag(FLAGS_dump_stream_info);
|
||||||
|
|
|
@ -40,6 +40,7 @@ enum FieldType {
|
||||||
kDashOnlyField,
|
kDashOnlyField,
|
||||||
kHlsOnlyField,
|
kHlsOnlyField,
|
||||||
kDashLabelField,
|
kDashLabelField,
|
||||||
|
kForcedSubtitleField,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FieldNameToTypeMapping {
|
struct FieldNameToTypeMapping {
|
||||||
|
@ -88,6 +89,7 @@ const FieldNameToTypeMapping kFieldNameTypeMappings[] = {
|
||||||
{"dash_only", kDashOnlyField},
|
{"dash_only", kDashOnlyField},
|
||||||
{"hls_only", kHlsOnlyField},
|
{"hls_only", kHlsOnlyField},
|
||||||
{"dash_label", kDashLabelField},
|
{"dash_label", kDashLabelField},
|
||||||
|
{"forced_subtitle", kForcedSubtitleField},
|
||||||
};
|
};
|
||||||
|
|
||||||
FieldType GetFieldType(const std::string& field_name) {
|
FieldType GetFieldType(const std::string& field_name) {
|
||||||
|
@ -255,12 +257,35 @@ std::optional<StreamDescriptor> ParseStreamDescriptor(
|
||||||
case kDashLabelField:
|
case kDashLabelField:
|
||||||
descriptor.dash_label = pair.second;
|
descriptor.dash_label = pair.second;
|
||||||
break;
|
break;
|
||||||
|
case kForcedSubtitleField:
|
||||||
|
unsigned forced_subtitle_value;
|
||||||
|
if (!absl::SimpleAtoi(pair.second, &forced_subtitle_value)) {
|
||||||
|
LOG(ERROR) << "Non-numeric option for forced field "
|
||||||
|
"specified ("
|
||||||
|
<< pair.second << ").";
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
if (forced_subtitle_value > 1) {
|
||||||
|
LOG(ERROR) << "forced should be either 0 or 1.";
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
descriptor.forced_subtitle = forced_subtitle_value > 0;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
LOG(ERROR) << "Unknown field in stream descriptor (\"" << pair.first
|
LOG(ERROR) << "Unknown field in stream descriptor (\"" << pair.first
|
||||||
<< "\").";
|
<< "\").";
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (descriptor.forced_subtitle) {
|
||||||
|
auto itr = std::find(descriptor.dash_roles.begin(),
|
||||||
|
descriptor.dash_roles.end(), "forced-subtitle");
|
||||||
|
if (itr == descriptor.dash_roles.end()) {
|
||||||
|
descriptor.dash_roles.push_back("forced-subtitle");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return descriptor;
|
return descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -310,7 +310,8 @@ class PackagerAppTest(unittest.TestCase):
|
||||||
skip_encryption=None,
|
skip_encryption=None,
|
||||||
bandwidth=None,
|
bandwidth=None,
|
||||||
split_content_on_ad_cues=False,
|
split_content_on_ad_cues=False,
|
||||||
test_file=None):
|
test_file=None,
|
||||||
|
forced_subtitle=None):
|
||||||
"""Get a stream descriptor as a string.
|
"""Get a stream descriptor as a string.
|
||||||
|
|
||||||
|
|
||||||
|
@ -347,8 +348,9 @@ class PackagerAppTest(unittest.TestCase):
|
||||||
into multiple files, with a total of NumAdCues + 1 files.
|
into multiple files, with a total of NumAdCues + 1 files.
|
||||||
test_file: The input file to use. If the input file is not specified, a
|
test_file: The input file to use. If the input file is not specified, a
|
||||||
default file will be used.
|
default file will be used.
|
||||||
|
forced_subtitle: If set to true, it marks this as a Forced Narrative
|
||||||
|
subtitle, marked in DASH using forced-subtitle role and
|
||||||
|
in HLS using FORCED=YES.
|
||||||
Returns:
|
Returns:
|
||||||
A string that makes up a single stream descriptor for input to the
|
A string that makes up a single stream descriptor for input to the
|
||||||
packager.
|
packager.
|
||||||
|
@ -402,6 +404,9 @@ class PackagerAppTest(unittest.TestCase):
|
||||||
if dash_only:
|
if dash_only:
|
||||||
stream.Append('dash_only', 1)
|
stream.Append('dash_only', 1)
|
||||||
|
|
||||||
|
if forced_subtitle:
|
||||||
|
stream.Append('forced_subtitle', 1)
|
||||||
|
|
||||||
if dash_label:
|
if dash_label:
|
||||||
stream.Append('dash_label', dash_label)
|
stream.Append('dash_label', dash_label)
|
||||||
|
|
||||||
|
@ -830,7 +835,6 @@ class PackagerFunctionalTest(PackagerAppTest):
|
||||||
self._GetFlags(output_dash=True, output_hls=True))
|
self._GetFlags(output_dash=True, output_hls=True))
|
||||||
self._CheckTestResults('hls-only-dash-only')
|
self._CheckTestResults('hls-only-dash-only')
|
||||||
|
|
||||||
|
|
||||||
def testDashLabel(self):
|
def testDashLabel(self):
|
||||||
streams = [
|
streams = [
|
||||||
self._GetStream('video', dash_label='Main'),
|
self._GetStream('video', dash_label='Main'),
|
||||||
|
@ -858,6 +862,21 @@ class PackagerFunctionalTest(PackagerAppTest):
|
||||||
sbd_key_audio='k5=v5:k6=v6'))
|
sbd_key_audio='k5=v5:k6=v6'))
|
||||||
self._CheckTestResults('sbd_test')
|
self._CheckTestResults('sbd_test')
|
||||||
|
|
||||||
|
def testForcedSubtitle(self):
|
||||||
|
streams = [
|
||||||
|
self._GetStream('audio', hls=True),
|
||||||
|
self._GetStream('video', hls=True),
|
||||||
|
]
|
||||||
|
|
||||||
|
streams += self._GetStreams(
|
||||||
|
['text'],
|
||||||
|
test_files=['bear-english.vtt'],
|
||||||
|
forced_subtitle=True)
|
||||||
|
|
||||||
|
self.assertPackageSuccess(streams, self._GetFlags(output_dash=True,
|
||||||
|
output_hls=True))
|
||||||
|
self._CheckTestResults('forced-subtitle')
|
||||||
|
|
||||||
def testAudioVideoWithLanguageOverride(self):
|
def testAudioVideoWithLanguageOverride(self):
|
||||||
self.assertPackageSuccess(
|
self.assertPackageSuccess(
|
||||||
self._GetStreams(['audio', 'video'], language='por', hls=True),
|
self._GetStreams(['audio', 'video'], language='por', hls=True),
|
||||||
|
@ -966,6 +985,13 @@ class PackagerFunctionalTest(PackagerAppTest):
|
||||||
self._GetFlags(output_dash=True))
|
self._GetFlags(output_dash=True))
|
||||||
self._CheckTestResults('acc-he')
|
self._CheckTestResults('acc-he')
|
||||||
|
|
||||||
|
def testDtsx(self):
|
||||||
|
self.assertPackageSuccess(
|
||||||
|
self._GetStreams(
|
||||||
|
['audio'], test_files=['bear-dtsx.mp4']),
|
||||||
|
self._GetFlags(output_dash=True))
|
||||||
|
self._CheckTestResults('dtsx-dash')
|
||||||
|
|
||||||
def testVideoAudioWebVTT(self):
|
def testVideoAudioWebVTT(self):
|
||||||
audio_video_streams = self._GetStreams(['audio', 'video'])
|
audio_video_streams = self._GetStreams(['audio', 'video'])
|
||||||
text_stream = self._GetStreams(['text'], test_files=['bear-english.vtt'])
|
text_stream = self._GetStreams(['text'], test_files=['bear-english.vtt'])
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--Generated with https://github.com/shaka-project/shaka-packager version <tag>-<hash>-<test>-->
|
||||||
|
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" minBufferTime="PT2S" type="static" mediaPresentationDuration="PT3.114667S">
|
||||||
|
<Period id="0">
|
||||||
|
<AdaptationSet id="0" contentType="audio" subsegmentAlignment="true">
|
||||||
|
<Representation id="0" bandwidth="227665" codecs="dtsx" mimeType="audio/mp4" audioSamplingRate="48000">
|
||||||
|
<AudioChannelConfiguration schemeIdUri="tag:dts.com,2018:uhd:audio_channel_configuration" value="0000003F"/>
|
||||||
|
<BaseURL>bear-dtsx-audio.mp4</BaseURL>
|
||||||
|
<SegmentBase indexRange="742-821" timescale="48000">
|
||||||
|
<Initialization range="0-741"/>
|
||||||
|
</SegmentBase>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
</Period>
|
||||||
|
</MPD>
|
|
@ -0,0 +1,16 @@
|
||||||
|
#EXTM3U
|
||||||
|
#EXT-X-VERSION:6
|
||||||
|
## Generated with https://github.com/shaka-project/shaka-packager version <tag>-<hash>-<test>
|
||||||
|
#EXT-X-TARGETDURATION:5
|
||||||
|
#EXT-X-PLAYLIST-TYPE:VOD
|
||||||
|
#EXT-X-MAP:URI="bear-640x360-audio.mp4",BYTERANGE="804@0"
|
||||||
|
#EXTINF:1.022,
|
||||||
|
#EXT-X-BYTERANGE:17028@872
|
||||||
|
bear-640x360-audio.mp4
|
||||||
|
#EXTINF:0.998,
|
||||||
|
#EXT-X-BYTERANGE:16285
|
||||||
|
bear-640x360-audio.mp4
|
||||||
|
#EXTINF:0.720,
|
||||||
|
#EXT-X-BYTERANGE:9558
|
||||||
|
bear-640x360-audio.mp4
|
||||||
|
#EXT-X-ENDLIST
|
Binary file not shown.
|
@ -0,0 +1,17 @@
|
||||||
|
#EXTM3U
|
||||||
|
#EXT-X-VERSION:6
|
||||||
|
## Generated with https://github.com/shaka-project/shaka-packager version <tag>-<hash>-<test>
|
||||||
|
#EXT-X-TARGETDURATION:5
|
||||||
|
#EXT-X-PLAYLIST-TYPE:VOD
|
||||||
|
#EXT-X-I-FRAMES-ONLY
|
||||||
|
#EXT-X-MAP:URI="bear-640x360-video.mp4",BYTERANGE="870@0"
|
||||||
|
#EXTINF:1.001,
|
||||||
|
#EXT-X-BYTERANGE:15581@938
|
||||||
|
bear-640x360-video.mp4
|
||||||
|
#EXTINF:1.001,
|
||||||
|
#EXT-X-BYTERANGE:18221@100251
|
||||||
|
bear-640x360-video.mp4
|
||||||
|
#EXTINF:0.734,
|
||||||
|
#EXT-X-BYTERANGE:19663@222058
|
||||||
|
bear-640x360-video.mp4
|
||||||
|
#EXT-X-ENDLIST
|
|
@ -0,0 +1,16 @@
|
||||||
|
#EXTM3U
|
||||||
|
#EXT-X-VERSION:6
|
||||||
|
## Generated with https://github.com/shaka-project/shaka-packager version <tag>-<hash>-<test>
|
||||||
|
#EXT-X-TARGETDURATION:5
|
||||||
|
#EXT-X-PLAYLIST-TYPE:VOD
|
||||||
|
#EXT-X-MAP:URI="bear-640x360-video.mp4",BYTERANGE="870@0"
|
||||||
|
#EXTINF:1.001,
|
||||||
|
#EXT-X-BYTERANGE:99313@938
|
||||||
|
bear-640x360-video.mp4
|
||||||
|
#EXTINF:1.001,
|
||||||
|
#EXT-X-BYTERANGE:121807
|
||||||
|
bear-640x360-video.mp4
|
||||||
|
#EXTINF:0.734,
|
||||||
|
#EXT-X-BYTERANGE:79662
|
||||||
|
bear-640x360-video.mp4
|
||||||
|
#EXT-X-ENDLIST
|
Binary file not shown.
|
@ -0,0 +1,11 @@
|
||||||
|
WEBVTT
|
||||||
|
|
||||||
|
STYLE
|
||||||
|
::cue { color:lime }
|
||||||
|
|
||||||
|
00:00:00.000 --> 00:00:00.800 align:center
|
||||||
|
Yup, that's a bear, eh.
|
||||||
|
|
||||||
|
00:00:01.000 --> 00:00:04.700 align:center
|
||||||
|
He 's... um... doing bear-like stuff.
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
#EXTM3U
|
||||||
|
## Generated with https://github.com/shaka-project/shaka-packager version <tag>-<hash>-<test>
|
||||||
|
|
||||||
|
#EXT-X-INDEPENDENT-SEGMENTS
|
||||||
|
|
||||||
|
#EXT-X-MEDIA:TYPE=AUDIO,URI="bear-640x360-audio.m3u8",GROUP-ID="default-audio-group",NAME="stream_0",DEFAULT=NO,AUTOSELECT=YES,CHANNELS="2"
|
||||||
|
|
||||||
|
#EXT-X-MEDIA:TYPE=SUBTITLES,URI="stream_2.m3u8",GROUP-ID="default-text-group",NAME="stream_2",DEFAULT=NO,AUTOSELECT=YES,FORCED=YES
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=1106817,AVERAGE-BANDWIDTH=1004632,CODECS="avc1.64001e,mp4a.40.2",RESOLUTION=640x360,FRAME-RATE=29.970,AUDIO="default-audio-group",SUBTITLES="default-text-group",CLOSED-CAPTIONS=NONE
|
||||||
|
bear-640x360-video.m3u8
|
||||||
|
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=214292,AVERAGE-BANDWIDTH=156327,CODECS="avc1.64001e",RESOLUTION=640x360,CLOSED-CAPTIONS=NONE,URI="bear-640x360-video-iframe.m3u8"
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--Generated with https://github.com/shaka-project/shaka-packager version <tag>-<hash>-<test>-->
|
||||||
|
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" minBufferTime="PT2S" type="static" mediaPresentationDuration="PT2.736067S">
|
||||||
|
<Period id="0">
|
||||||
|
<AdaptationSet id="0" contentType="audio" subsegmentAlignment="true">
|
||||||
|
<Representation id="0" bandwidth="133334" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||||
|
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||||
|
<BaseURL>bear-640x360-audio.mp4</BaseURL>
|
||||||
|
<SegmentBase indexRange="804-871" timescale="44100">
|
||||||
|
<Initialization range="0-803"/>
|
||||||
|
</SegmentBase>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
<AdaptationSet id="1" contentType="video" width="640" height="360" frameRate="30000/1001" subsegmentAlignment="true" par="16:9">
|
||||||
|
<Representation id="1" bandwidth="973483" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1">
|
||||||
|
<BaseURL>bear-640x360-video.mp4</BaseURL>
|
||||||
|
<SegmentBase indexRange="870-937" timescale="30000">
|
||||||
|
<Initialization range="0-869"/>
|
||||||
|
</SegmentBase>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
<AdaptationSet id="2" contentType="text" subsegmentAlignment="true">
|
||||||
|
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="forced-subtitle"/>
|
||||||
|
<Representation id="2" bandwidth="317" mimeType="text/vtt">
|
||||||
|
<BaseURL>bear-english-text.vtt</BaseURL>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
</Period>
|
||||||
|
</MPD>
|
|
@ -0,0 +1,8 @@
|
||||||
|
#EXTM3U
|
||||||
|
#EXT-X-VERSION:6
|
||||||
|
## Generated with https://github.com/shaka-project/shaka-packager version <tag>-<hash>-<test>
|
||||||
|
#EXT-X-TARGETDURATION:5
|
||||||
|
#EXT-X-PLAYLIST-TYPE:VOD
|
||||||
|
#EXTINF:4.700,
|
||||||
|
bear-english-text.vtt
|
||||||
|
#EXT-X-ENDLIST
|
|
@ -318,11 +318,16 @@ void BuildMediaTag(const MediaPlaylist& playlist,
|
||||||
} else {
|
} else {
|
||||||
tag.AddString("DEFAULT", "NO");
|
tag.AddString("DEFAULT", "NO");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_autoselect) {
|
if (is_autoselect) {
|
||||||
tag.AddString("AUTOSELECT", "YES");
|
tag.AddString("AUTOSELECT", "YES");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (playlist.stream_type() ==
|
||||||
|
MediaPlaylist::MediaPlaylistStreamType::kSubtitle &&
|
||||||
|
playlist.forced_subtitle()) {
|
||||||
|
tag.AddString("FORCED", "YES");
|
||||||
|
}
|
||||||
|
|
||||||
const std::vector<std::string>& characteristics = playlist.characteristics();
|
const std::vector<std::string>& characteristics = playlist.characteristics();
|
||||||
if (!characteristics.empty()) {
|
if (!characteristics.empty()) {
|
||||||
tag.AddQuotedString("CHARACTERISTICS", absl::StrJoin(characteristics, ","));
|
tag.AddQuotedString("CHARACTERISTICS", absl::StrJoin(characteristics, ","));
|
||||||
|
@ -401,6 +406,12 @@ void BuildMediaTags(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (playlist->stream_type() ==
|
||||||
|
MediaPlaylist::MediaPlaylistStreamType::kSubtitle &&
|
||||||
|
playlist->forced_subtitle()) {
|
||||||
|
is_autoselect = true;
|
||||||
|
}
|
||||||
|
|
||||||
BuildMediaTag(*playlist, group_id, is_default, is_autoselect, base_url,
|
BuildMediaTag(*playlist, group_id, is_default, is_autoselect, base_url,
|
||||||
out);
|
out);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include <absl/log/check.h>
|
#include <absl/log/check.h>
|
||||||
#include <absl/log/log.h>
|
#include <absl/log/log.h>
|
||||||
|
@ -109,7 +110,8 @@ std::string CreatePlaylistHeader(
|
||||||
HlsPlaylistType type,
|
HlsPlaylistType type,
|
||||||
MediaPlaylist::MediaPlaylistStreamType stream_type,
|
MediaPlaylist::MediaPlaylistStreamType stream_type,
|
||||||
uint32_t media_sequence_number,
|
uint32_t media_sequence_number,
|
||||||
int discontinuity_sequence_number) {
|
int discontinuity_sequence_number,
|
||||||
|
std::optional<double> start_time_offset) {
|
||||||
const std::string version = GetPackagerVersion();
|
const std::string version = GetPackagerVersion();
|
||||||
std::string version_line;
|
std::string version_line;
|
||||||
if (!version.empty()) {
|
if (!version.empty()) {
|
||||||
|
@ -151,6 +153,10 @@ std::string CreatePlaylistHeader(
|
||||||
MediaPlaylist::MediaPlaylistStreamType::kVideoIFramesOnly) {
|
MediaPlaylist::MediaPlaylistStreamType::kVideoIFramesOnly) {
|
||||||
absl::StrAppendFormat(&header, "#EXT-X-I-FRAMES-ONLY\n");
|
absl::StrAppendFormat(&header, "#EXT-X-I-FRAMES-ONLY\n");
|
||||||
}
|
}
|
||||||
|
if (start_time_offset.has_value()) {
|
||||||
|
absl::StrAppendFormat(&header, "#EXT-X-START:TIME-OFFSET=%f\n",
|
||||||
|
start_time_offset.value());
|
||||||
|
}
|
||||||
|
|
||||||
// Put EXT-X-MAP at the end since the rest of the playlist is about the
|
// Put EXT-X-MAP at the end since the rest of the playlist is about the
|
||||||
// segment and key info.
|
// segment and key info.
|
||||||
|
@ -373,6 +379,10 @@ void MediaPlaylist::SetCharacteristicsForTesting(
|
||||||
characteristics_ = characteristics;
|
characteristics_ = characteristics;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MediaPlaylist::SetForcedSubtitleForTesting(const bool forced_subtitle) {
|
||||||
|
forced_subtitle_ = forced_subtitle;
|
||||||
|
}
|
||||||
|
|
||||||
bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) {
|
bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) {
|
||||||
const int32_t time_scale = GetTimeScale(media_info);
|
const int32_t time_scale = GetTimeScale(media_info);
|
||||||
if (time_scale == 0) {
|
if (time_scale == 0) {
|
||||||
|
@ -400,6 +410,8 @@ bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) {
|
||||||
std::vector<std::string>(media_info_.hls_characteristics().begin(),
|
std::vector<std::string>(media_info_.hls_characteristics().begin(),
|
||||||
media_info_.hls_characteristics().end());
|
media_info_.hls_characteristics().end());
|
||||||
|
|
||||||
|
forced_subtitle_ = media_info_.forced_subtitle();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -479,7 +491,8 @@ bool MediaPlaylist::WriteToFile(const std::filesystem::path& file_path) {
|
||||||
|
|
||||||
std::string content = CreatePlaylistHeader(
|
std::string content = CreatePlaylistHeader(
|
||||||
media_info_, target_duration_, hls_params_.playlist_type, stream_type_,
|
media_info_, target_duration_, hls_params_.playlist_type, stream_type_,
|
||||||
media_sequence_number_, discontinuity_sequence_number_);
|
media_sequence_number_, discontinuity_sequence_number_,
|
||||||
|
hls_params_.start_time_offset);
|
||||||
|
|
||||||
for (const auto& entry : entries_)
|
for (const auto& entry : entries_)
|
||||||
absl::StrAppendFormat(&content, "%s\n", entry->ToString().c_str());
|
absl::StrAppendFormat(&content, "%s\n", entry->ToString().c_str());
|
||||||
|
@ -513,8 +526,8 @@ void MediaPlaylist::SetTargetDuration(int32_t target_duration) {
|
||||||
if (target_duration_set_) {
|
if (target_duration_set_) {
|
||||||
if (target_duration_ == target_duration)
|
if (target_duration_ == target_duration)
|
||||||
return;
|
return;
|
||||||
VLOG(1) << "Updating target duration from " << target_duration << " to "
|
VLOG(1) << "Updating target duration from " << target_duration_ << " to "
|
||||||
<< target_duration_;
|
<< target_duration;
|
||||||
}
|
}
|
||||||
target_duration_ = target_duration;
|
target_duration_ = target_duration;
|
||||||
target_duration_set_ = true;
|
target_duration_set_ = true;
|
||||||
|
|
|
@ -90,6 +90,9 @@ class MediaPlaylist {
|
||||||
/// For testing only.
|
/// For testing only.
|
||||||
void SetLanguageForTesting(const std::string& language);
|
void SetLanguageForTesting(const std::string& language);
|
||||||
|
|
||||||
|
/// For testing only.
|
||||||
|
void SetForcedSubtitleForTesting(const bool forced_subtitle);
|
||||||
|
|
||||||
/// For testing only.
|
/// For testing only.
|
||||||
void SetCharacteristicsForTesting(
|
void SetCharacteristicsForTesting(
|
||||||
const std::vector<std::string>& characteristics);
|
const std::vector<std::string>& characteristics);
|
||||||
|
@ -223,6 +226,8 @@ class MediaPlaylist {
|
||||||
return characteristics_;
|
return characteristics_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool forced_subtitle() const { return forced_subtitle_; }
|
||||||
|
|
||||||
bool is_dvs() const {
|
bool is_dvs() const {
|
||||||
// HLS Authoring Specification for Apple Devices
|
// HLS Authoring Specification for Apple Devices
|
||||||
// https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_devices#overview
|
// https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_devices#overview
|
||||||
|
@ -262,6 +267,7 @@ class MediaPlaylist {
|
||||||
std::string codec_;
|
std::string codec_;
|
||||||
std::string language_;
|
std::string language_;
|
||||||
std::vector<std::string> characteristics_;
|
std::vector<std::string> characteristics_;
|
||||||
|
bool forced_subtitle_ = false;
|
||||||
uint32_t media_sequence_number_ = 0;
|
uint32_t media_sequence_number_ = 0;
|
||||||
bool inserted_discontinuity_tag_ = false;
|
bool inserted_discontinuity_tag_ = false;
|
||||||
int discontinuity_sequence_number_ = 0;
|
int discontinuity_sequence_number_ = 0;
|
||||||
|
|
|
@ -51,6 +51,10 @@ class MediaPlaylistTest : public ::testing::Test {
|
||||||
default_group_id_("default_group_id") {
|
default_group_id_("default_group_id") {
|
||||||
hls_params_.playlist_type = type;
|
hls_params_.playlist_type = type;
|
||||||
hls_params_.time_shift_buffer_depth = kTimeShiftBufferDepth;
|
hls_params_.time_shift_buffer_depth = kTimeShiftBufferDepth;
|
||||||
|
|
||||||
|
// NOTE: hls_params_ is passed by and stored by reference in MediaPlaylist,
|
||||||
|
// so changed made to it through mutable_hls_params() after this point
|
||||||
|
// still affect what the playlist see in its own hls_params_ later.
|
||||||
media_playlist_.reset(new MediaPlaylist(hls_params_, default_file_name_,
|
media_playlist_.reset(new MediaPlaylist(hls_params_, default_file_name_,
|
||||||
default_name_, default_group_id_));
|
default_name_, default_group_id_));
|
||||||
}
|
}
|
||||||
|
@ -658,6 +662,90 @@ TEST_F(MediaPlaylistMultiSegmentTest, MultipleEncryptionInfo) {
|
||||||
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(MediaPlaylistSingleSegmentTest, StartTimeEmpty) {
|
||||||
|
const std::string kExpectedOutput =
|
||||||
|
"#EXTM3U\n"
|
||||||
|
"#EXT-X-VERSION:6\n"
|
||||||
|
"## Generated with https://github.com/shaka-project/shaka-packager "
|
||||||
|
"version test\n"
|
||||||
|
"#EXT-X-TARGETDURATION:0\n"
|
||||||
|
"#EXT-X-PLAYLIST-TYPE:VOD\n"
|
||||||
|
"#EXT-X-ENDLIST\n";
|
||||||
|
|
||||||
|
// Because this is std::nullopt, the tag isn't in the playlist at all.
|
||||||
|
mutable_hls_params()->start_time_offset = std::nullopt;
|
||||||
|
|
||||||
|
ASSERT_TRUE(media_playlist_->SetMediaInfo(valid_video_media_info_));
|
||||||
|
|
||||||
|
const char kMemoryFilePath[] = "memory://media.m3u8";
|
||||||
|
EXPECT_TRUE(media_playlist_->WriteToFile(kMemoryFilePath));
|
||||||
|
|
||||||
|
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(MediaPlaylistSingleSegmentTest, StartTimeZero) {
|
||||||
|
const std::string kExpectedOutput =
|
||||||
|
"#EXTM3U\n"
|
||||||
|
"#EXT-X-VERSION:6\n"
|
||||||
|
"## Generated with https://github.com/shaka-project/shaka-packager "
|
||||||
|
"version test\n"
|
||||||
|
"#EXT-X-TARGETDURATION:0\n"
|
||||||
|
"#EXT-X-PLAYLIST-TYPE:VOD\n"
|
||||||
|
"#EXT-X-START:TIME-OFFSET=0.000000\n"
|
||||||
|
"#EXT-X-ENDLIST\n";
|
||||||
|
|
||||||
|
mutable_hls_params()->start_time_offset = 0;
|
||||||
|
|
||||||
|
ASSERT_TRUE(media_playlist_->SetMediaInfo(valid_video_media_info_));
|
||||||
|
|
||||||
|
const char kMemoryFilePath[] = "memory://media.m3u8";
|
||||||
|
EXPECT_TRUE(media_playlist_->WriteToFile(kMemoryFilePath));
|
||||||
|
|
||||||
|
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(MediaPlaylistSingleSegmentTest, StartTimePositive) {
|
||||||
|
const std::string kExpectedOutput =
|
||||||
|
"#EXTM3U\n"
|
||||||
|
"#EXT-X-VERSION:6\n"
|
||||||
|
"## Generated with https://github.com/shaka-project/shaka-packager "
|
||||||
|
"version test\n"
|
||||||
|
"#EXT-X-TARGETDURATION:0\n"
|
||||||
|
"#EXT-X-PLAYLIST-TYPE:VOD\n"
|
||||||
|
"#EXT-X-START:TIME-OFFSET=20.000000\n"
|
||||||
|
"#EXT-X-ENDLIST\n";
|
||||||
|
|
||||||
|
mutable_hls_params()->start_time_offset = 20;
|
||||||
|
|
||||||
|
ASSERT_TRUE(media_playlist_->SetMediaInfo(valid_video_media_info_));
|
||||||
|
|
||||||
|
const char kMemoryFilePath[] = "memory://media.m3u8";
|
||||||
|
EXPECT_TRUE(media_playlist_->WriteToFile(kMemoryFilePath));
|
||||||
|
|
||||||
|
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(MediaPlaylistSingleSegmentTest, StartTimeNegative) {
|
||||||
|
const std::string kExpectedOutput =
|
||||||
|
"#EXTM3U\n"
|
||||||
|
"#EXT-X-VERSION:6\n"
|
||||||
|
"## Generated with https://github.com/shaka-project/shaka-packager "
|
||||||
|
"version test\n"
|
||||||
|
"#EXT-X-TARGETDURATION:0\n"
|
||||||
|
"#EXT-X-PLAYLIST-TYPE:VOD\n"
|
||||||
|
"#EXT-X-START:TIME-OFFSET=-3.141590\n"
|
||||||
|
"#EXT-X-ENDLIST\n";
|
||||||
|
|
||||||
|
mutable_hls_params()->start_time_offset = -3.14159;
|
||||||
|
|
||||||
|
ASSERT_TRUE(media_playlist_->SetMediaInfo(valid_video_media_info_));
|
||||||
|
|
||||||
|
const char kMemoryFilePath[] = "memory://media.m3u8";
|
||||||
|
EXPECT_TRUE(media_playlist_->WriteToFile(kMemoryFilePath));
|
||||||
|
|
||||||
|
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||||
|
}
|
||||||
|
|
||||||
class LiveMediaPlaylistTest : public MediaPlaylistMultiSegmentTest {
|
class LiveMediaPlaylistTest : public MediaPlaylistMultiSegmentTest {
|
||||||
protected:
|
protected:
|
||||||
LiveMediaPlaylistTest()
|
LiveMediaPlaylistTest()
|
||||||
|
|
|
@ -150,6 +150,8 @@ std::string AudioStreamInfo::GetCodecString(Codec codec,
|
||||||
return "dts-";
|
return "dts-";
|
||||||
case kCodecDTSP:
|
case kCodecDTSP:
|
||||||
return "dts+";
|
return "dts+";
|
||||||
|
case kCodecDTSX:
|
||||||
|
return "dtsx";
|
||||||
case kCodecEAC3:
|
case kCodecEAC3:
|
||||||
return "ec-3";
|
return "ec-3";
|
||||||
case kCodecAC4:
|
case kCodecAC4:
|
||||||
|
|
|
@ -58,6 +58,7 @@ enum FourCC : uint32_t {
|
||||||
FOURCC_dtsl = 0x6474736c,
|
FOURCC_dtsl = 0x6474736c,
|
||||||
FOURCC_dtsm = 0x6474732d, // "dts-"
|
FOURCC_dtsm = 0x6474732d, // "dts-"
|
||||||
FOURCC_dtsp = 0x6474732b, // "dts+"
|
FOURCC_dtsp = 0x6474732b, // "dts+"
|
||||||
|
FOURCC_dtsx = 0x64747378, // "dtsx"
|
||||||
FOURCC_dvcC = 0x64766343,
|
FOURCC_dvcC = 0x64766343,
|
||||||
FOURCC_dvh1 = 0x64766831,
|
FOURCC_dvh1 = 0x64766831,
|
||||||
FOURCC_dvhe = 0x64766865,
|
FOURCC_dvhe = 0x64766865,
|
||||||
|
@ -151,6 +152,7 @@ enum FourCC : uint32_t {
|
||||||
FOURCC_trex = 0x74726578,
|
FOURCC_trex = 0x74726578,
|
||||||
FOURCC_trun = 0x7472756e,
|
FOURCC_trun = 0x7472756e,
|
||||||
FOURCC_udta = 0x75647461,
|
FOURCC_udta = 0x75647461,
|
||||||
|
FOURCC_udts = 0x75647473, // "udts"
|
||||||
FOURCC_url = 0x75726c20, // "url "
|
FOURCC_url = 0x75726c20, // "url "
|
||||||
FOURCC_urn = 0x75726e20, // "urn "
|
FOURCC_urn = 0x75726e20, // "urn "
|
||||||
FOURCC_uuid = 0x75756964,
|
FOURCC_uuid = 0x75756964,
|
||||||
|
|
|
@ -49,6 +49,7 @@ enum Codec {
|
||||||
kCodecDTSL,
|
kCodecDTSL,
|
||||||
kCodecDTSM,
|
kCodecDTSM,
|
||||||
kCodecDTSP,
|
kCodecDTSP,
|
||||||
|
kCodecDTSX,
|
||||||
kCodecEAC3,
|
kCodecEAC3,
|
||||||
kCodecFlac,
|
kCodecFlac,
|
||||||
kCodecOpus,
|
kCodecOpus,
|
||||||
|
|
|
@ -12,6 +12,7 @@ add_library(media_codecs STATIC
|
||||||
avc_decoder_configuration_record.cc
|
avc_decoder_configuration_record.cc
|
||||||
decoder_configuration_record.cc
|
decoder_configuration_record.cc
|
||||||
dovi_decoder_configuration_record.cc
|
dovi_decoder_configuration_record.cc
|
||||||
|
dts_audio_specific_config.cc
|
||||||
ec3_audio_util.cc
|
ec3_audio_util.cc
|
||||||
ac4_audio_util.cc
|
ac4_audio_util.cc
|
||||||
es_descriptor.cc
|
es_descriptor.cc
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright (c) 2023 Google Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#include <packager/media/codecs/dts_audio_specific_config.h>
|
||||||
|
|
||||||
|
#include <packager/media/base/bit_reader.h>
|
||||||
|
#include <packager/media/base/rcheck.h>
|
||||||
|
|
||||||
|
namespace shaka {
|
||||||
|
namespace media {
|
||||||
|
|
||||||
|
bool GetDTSXChannelMask(const std::vector<uint8_t>& udts, uint32_t& mask) {
|
||||||
|
// udts is the DTS-UHD Specific Box: ETSI TS 103 491 V1.2.1 Table B-2
|
||||||
|
// DecoderProfileCode(6 bits)
|
||||||
|
// FrameDurationCode(2 bits)
|
||||||
|
// MaxPayloadCode(3 bits)
|
||||||
|
// NumPresentationsCode(5 bits)
|
||||||
|
// ChannelMask (32 bits)
|
||||||
|
BitReader bit_reader(udts.data(), udts.size());
|
||||||
|
RCHECK(bit_reader.SkipBits(16));
|
||||||
|
RCHECK(bit_reader.ReadBits(32, &mask));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace media
|
||||||
|
} // namespace shaka
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright (c) 2023 Google Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#ifndef PACKAGER_MEDIA_CODECS_DTS_AUDIO_SPECIFIC_CONFIG_H_
|
||||||
|
#define PACKAGER_MEDIA_CODECS_DTS_AUDIO_SPECIFIC_CONFIG_H_
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace shaka {
|
||||||
|
namespace media {
|
||||||
|
|
||||||
|
class BitReader;
|
||||||
|
|
||||||
|
bool GetDTSXChannelMask(const std::vector<uint8_t>& udts, uint32_t& mask);
|
||||||
|
|
||||||
|
} // namespace media
|
||||||
|
} // namespace shaka
|
||||||
|
|
||||||
|
#endif // PACKAGER_MEDIA_CODECS_DTS_AUDIO_SPECIFIC_CONFIG_H_
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright 2023 Google Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "packager/media/codecs/dts_audio_specific_config.h"
|
||||||
|
|
||||||
|
namespace shaka {
|
||||||
|
namespace media {
|
||||||
|
|
||||||
|
TEST(DTSAudioSpecificConfigTest, BasicProfileTest) {
|
||||||
|
uint8_t buffer[] = {0x01, 0x20, 0x00, 0x00, 0x0, 0x3F, 0x80, 0x00};
|
||||||
|
std::vector<uint8_t> data(std::begin(buffer), std::end(buffer));
|
||||||
|
uint32_t mask;
|
||||||
|
EXPECT_TRUE(GetDTSXChannelMask(data, mask));
|
||||||
|
EXPECT_EQ(0x3F, mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DTSAudioSpecificConfigTest, ChannelMaskBytes) {
|
||||||
|
uint8_t buffer[] = {0x01, 0x20, 0x12, 0x34, 0x56, 0x78, 0x80, 0x00};
|
||||||
|
std::vector<uint8_t> data(std::begin(buffer), std::end(buffer));
|
||||||
|
uint32_t mask;
|
||||||
|
EXPECT_TRUE(GetDTSXChannelMask(data, mask));
|
||||||
|
EXPECT_EQ(0x12345678, mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DTSAudioSpecificConfigTest, Truncated) {
|
||||||
|
uint8_t buffer[] = {0x01, 0x20, 0x00, 0x00, 0x00};
|
||||||
|
std::vector<uint8_t> data(std::begin(buffer), std::end(buffer));
|
||||||
|
uint32_t mask;
|
||||||
|
EXPECT_FALSE(GetDTSXChannelMask(data, mask));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace media
|
||||||
|
} // namespace shaka
|
|
@ -74,21 +74,24 @@ bool H265ByteToUnitStreamConverter::GetDecoderConfigurationRecord(
|
||||||
buffer.AppendInt(static_cast<uint8_t>(kUnitStreamNaluLengthSize - 1));
|
buffer.AppendInt(static_cast<uint8_t>(kUnitStreamNaluLengthSize - 1));
|
||||||
buffer.AppendInt(static_cast<uint8_t>(3) /* numOfArrays */);
|
buffer.AppendInt(static_cast<uint8_t>(3) /* numOfArrays */);
|
||||||
|
|
||||||
|
// More parameter set NALUs may follow when strip_parameter_set_nalus is
|
||||||
|
// disabled.
|
||||||
|
const uint8_t array_completeness = strip_parameter_set_nalus() ? 0x80 : 0;
|
||||||
|
|
||||||
// VPS
|
// VPS
|
||||||
const uint8_t kArrayCompleteness = 0x80;
|
buffer.AppendInt(static_cast<uint8_t>(array_completeness | Nalu::H265_VPS));
|
||||||
buffer.AppendInt(static_cast<uint8_t>(kArrayCompleteness | Nalu::H265_VPS));
|
|
||||||
buffer.AppendInt(static_cast<uint16_t>(1) /* numNalus */);
|
buffer.AppendInt(static_cast<uint16_t>(1) /* numNalus */);
|
||||||
buffer.AppendInt(static_cast<uint16_t>(last_vps_.size()));
|
buffer.AppendInt(static_cast<uint16_t>(last_vps_.size()));
|
||||||
buffer.AppendVector(last_vps_);
|
buffer.AppendVector(last_vps_);
|
||||||
|
|
||||||
// SPS
|
// SPS
|
||||||
buffer.AppendInt(static_cast<uint8_t>(kArrayCompleteness | Nalu::H265_SPS));
|
buffer.AppendInt(static_cast<uint8_t>(array_completeness | Nalu::H265_SPS));
|
||||||
buffer.AppendInt(static_cast<uint16_t>(1) /* numNalus */);
|
buffer.AppendInt(static_cast<uint16_t>(1) /* numNalus */);
|
||||||
buffer.AppendInt(static_cast<uint16_t>(last_sps_.size()));
|
buffer.AppendInt(static_cast<uint16_t>(last_sps_.size()));
|
||||||
buffer.AppendVector(last_sps_);
|
buffer.AppendVector(last_sps_);
|
||||||
|
|
||||||
// PPS
|
// PPS
|
||||||
buffer.AppendInt(static_cast<uint8_t>(kArrayCompleteness | Nalu::H265_PPS));
|
buffer.AppendInt(static_cast<uint8_t>(array_completeness | Nalu::H265_PPS));
|
||||||
buffer.AppendInt(static_cast<uint16_t>(1) /* numNalus */);
|
buffer.AppendInt(static_cast<uint16_t>(1) /* numNalus */);
|
||||||
buffer.AppendInt(static_cast<uint16_t>(last_pps_.size()));
|
buffer.AppendInt(static_cast<uint16_t>(last_pps_.size()));
|
||||||
buffer.AppendVector(last_pps_);
|
buffer.AppendVector(last_pps_);
|
||||||
|
|
|
@ -26,6 +26,7 @@ HlsNotifyMuxerListener::HlsNotifyMuxerListener(
|
||||||
const std::string& ext_x_media_name,
|
const std::string& ext_x_media_name,
|
||||||
const std::string& ext_x_media_group_id,
|
const std::string& ext_x_media_group_id,
|
||||||
const std::vector<std::string>& characteristics,
|
const std::vector<std::string>& characteristics,
|
||||||
|
bool forced_subtitle,
|
||||||
hls::HlsNotifier* hls_notifier,
|
hls::HlsNotifier* hls_notifier,
|
||||||
std::optional<uint32_t> index)
|
std::optional<uint32_t> index)
|
||||||
: playlist_name_(playlist_name),
|
: playlist_name_(playlist_name),
|
||||||
|
@ -33,6 +34,7 @@ HlsNotifyMuxerListener::HlsNotifyMuxerListener(
|
||||||
ext_x_media_name_(ext_x_media_name),
|
ext_x_media_name_(ext_x_media_name),
|
||||||
ext_x_media_group_id_(ext_x_media_group_id),
|
ext_x_media_group_id_(ext_x_media_group_id),
|
||||||
characteristics_(characteristics),
|
characteristics_(characteristics),
|
||||||
|
forced_subtitle_(forced_subtitle),
|
||||||
hls_notifier_(hls_notifier),
|
hls_notifier_(hls_notifier),
|
||||||
index_(index) {
|
index_(index) {
|
||||||
DCHECK(hls_notifier);
|
DCHECK(hls_notifier);
|
||||||
|
@ -103,6 +105,9 @@ void HlsNotifyMuxerListener::OnMediaStart(const MuxerOptions& muxer_options,
|
||||||
for (const std::string& characteristic : characteristics_)
|
for (const std::string& characteristic : characteristics_)
|
||||||
media_info->add_hls_characteristics(characteristic);
|
media_info->add_hls_characteristics(characteristic);
|
||||||
}
|
}
|
||||||
|
if (forced_subtitle_) {
|
||||||
|
media_info->set_forced_subtitle(forced_subtitle_);
|
||||||
|
}
|
||||||
if (index_.has_value())
|
if (index_.has_value())
|
||||||
media_info->set_index(index_.value());
|
media_info->set_index(index_.value());
|
||||||
|
|
||||||
|
|
|
@ -39,12 +39,16 @@ class HlsNotifyMuxerListener : public MuxerListener {
|
||||||
/// @param characteristics is the characteristics for this playlist. This is
|
/// @param characteristics is the characteristics for this playlist. This is
|
||||||
/// the value of CHARACTERISTICS attribute for EXT-X-MEDIA. This may be
|
/// the value of CHARACTERISTICS attribute for EXT-X-MEDIA. This may be
|
||||||
/// empty.
|
/// empty.
|
||||||
|
/// @param forced is the HLS FORCED SUBTITLE setting for this playlist. This
|
||||||
|
/// is the value of FORCED attribute for EXT-X-MEDIA. This may be
|
||||||
|
/// empty.
|
||||||
/// @param hls_notifier used by this listener. Ownership does not transfer.
|
/// @param hls_notifier used by this listener. Ownership does not transfer.
|
||||||
HlsNotifyMuxerListener(const std::string& playlist_name,
|
HlsNotifyMuxerListener(const std::string& playlist_name,
|
||||||
bool iframes_only,
|
bool iframes_only,
|
||||||
const std::string& ext_x_media_name,
|
const std::string& ext_x_media_name,
|
||||||
const std::string& ext_x_media_group_id,
|
const std::string& ext_x_media_group_id,
|
||||||
const std::vector<std::string>& characteristics,
|
const std::vector<std::string>& characteristics,
|
||||||
|
bool forced,
|
||||||
hls::HlsNotifier* hls_notifier,
|
hls::HlsNotifier* hls_notifier,
|
||||||
std::optional<uint32_t> index);
|
std::optional<uint32_t> index);
|
||||||
~HlsNotifyMuxerListener() override;
|
~HlsNotifyMuxerListener() override;
|
||||||
|
@ -86,6 +90,7 @@ class HlsNotifyMuxerListener : public MuxerListener {
|
||||||
const std::string ext_x_media_name_;
|
const std::string ext_x_media_name_;
|
||||||
const std::string ext_x_media_group_id_;
|
const std::string ext_x_media_group_id_;
|
||||||
const std::vector<std::string> characteristics_;
|
const std::vector<std::string> characteristics_;
|
||||||
|
const bool forced_subtitle_;
|
||||||
hls::HlsNotifier* const hls_notifier_;
|
hls::HlsNotifier* const hls_notifier_;
|
||||||
std::optional<uint32_t> stream_id_;
|
std::optional<uint32_t> stream_id_;
|
||||||
std::optional<uint32_t> index_;
|
std::optional<uint32_t> index_;
|
||||||
|
|
|
@ -99,6 +99,7 @@ const char kDefaultName[] = "DEFAULTNAME";
|
||||||
const char kDefaultGroupId[] = "DEFAULTGROUPID";
|
const char kDefaultGroupId[] = "DEFAULTGROUPID";
|
||||||
const char kCharactersticA[] = "public.accessibility.transcribes-spoken-dialog";
|
const char kCharactersticA[] = "public.accessibility.transcribes-spoken-dialog";
|
||||||
const char kCharactersticB[] = "public.easy-to-read";
|
const char kCharactersticB[] = "public.easy-to-read";
|
||||||
|
const bool kForced = false;
|
||||||
|
|
||||||
MATCHER_P(HasEncryptionScheme, expected_scheme, "") {
|
MATCHER_P(HasEncryptionScheme, expected_scheme, "") {
|
||||||
*result_listener << "it has_protected_content: "
|
*result_listener << "it has_protected_content: "
|
||||||
|
@ -121,6 +122,7 @@ class HlsNotifyMuxerListenerTest : public ::testing::Test {
|
||||||
kDefaultName,
|
kDefaultName,
|
||||||
kDefaultGroupId,
|
kDefaultGroupId,
|
||||||
std::vector<std::string>{kCharactersticA, kCharactersticB},
|
std::vector<std::string>{kCharactersticA, kCharactersticB},
|
||||||
|
kForced,
|
||||||
&mock_notifier_,
|
&mock_notifier_,
|
||||||
0) {}
|
0) {}
|
||||||
|
|
||||||
|
@ -459,6 +461,7 @@ class HlsNotifyMuxerListenerKeyFrameTest : public TestWithParam<bool> {
|
||||||
kDefaultName,
|
kDefaultName,
|
||||||
kDefaultGroupId,
|
kDefaultGroupId,
|
||||||
std::vector<std::string>(), // no characteristics.
|
std::vector<std::string>(), // no characteristics.
|
||||||
|
kForced,
|
||||||
&mock_notifier_,
|
&mock_notifier_,
|
||||||
0) {}
|
0) {}
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,7 @@ std::list<std::unique_ptr<MuxerListener>> CreateHlsListenersInternal(
|
||||||
const std::string& group_id = stream.hls_group_id;
|
const std::string& group_id = stream.hls_group_id;
|
||||||
const std::string& iframe_playlist_name = stream.hls_iframe_playlist_name;
|
const std::string& iframe_playlist_name = stream.hls_iframe_playlist_name;
|
||||||
const std::vector<std::string>& characteristics = stream.hls_characteristics;
|
const std::vector<std::string>& characteristics = stream.hls_characteristics;
|
||||||
|
const bool forced_subtitle = stream.forced_subtitle;
|
||||||
|
|
||||||
if (name.empty()) {
|
if (name.empty()) {
|
||||||
name = absl::StrFormat("stream_%d", stream_index);
|
name = absl::StrFormat("stream_%d", stream_index);
|
||||||
|
@ -73,13 +74,13 @@ std::list<std::unique_ptr<MuxerListener>> CreateHlsListenersInternal(
|
||||||
|
|
||||||
const bool kIFramesOnly = true;
|
const bool kIFramesOnly = true;
|
||||||
std::list<std::unique_ptr<MuxerListener>> listeners;
|
std::list<std::unique_ptr<MuxerListener>> listeners;
|
||||||
listeners.emplace_back(
|
listeners.emplace_back(new HlsNotifyMuxerListener(
|
||||||
new HlsNotifyMuxerListener(playlist_name, !kIFramesOnly, name, group_id,
|
playlist_name, !kIFramesOnly, name, group_id, characteristics,
|
||||||
characteristics, notifier, stream.index));
|
forced_subtitle, notifier, stream.index));
|
||||||
if (!iframe_playlist_name.empty()) {
|
if (!iframe_playlist_name.empty()) {
|
||||||
listeners.emplace_back(new HlsNotifyMuxerListener(
|
listeners.emplace_back(new HlsNotifyMuxerListener(
|
||||||
iframe_playlist_name, kIFramesOnly, name, group_id,
|
iframe_playlist_name, kIFramesOnly, name, group_id,
|
||||||
std::vector<std::string>(), notifier, stream.index));
|
std::vector<std::string>(), forced_subtitle, notifier, stream.index));
|
||||||
}
|
}
|
||||||
return listeners;
|
return listeners;
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@ class MuxerListenerFactory {
|
||||||
std::string hls_playlist_name;
|
std::string hls_playlist_name;
|
||||||
std::string hls_iframe_playlist_name;
|
std::string hls_iframe_playlist_name;
|
||||||
std::vector<std::string> hls_characteristics;
|
std::vector<std::string> hls_characteristics;
|
||||||
|
bool forced_subtitle = false;
|
||||||
bool hls_only = false;
|
bool hls_only = false;
|
||||||
|
|
||||||
// DASH specific values needed to write DASH mpd. Will only be used if an
|
// DASH specific values needed to write DASH mpd. Will only be used if an
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include <packager/media/base/text_stream_info.h>
|
#include <packager/media/base/text_stream_info.h>
|
||||||
#include <packager/media/base/video_stream_info.h>
|
#include <packager/media/base/video_stream_info.h>
|
||||||
#include <packager/media/codecs/ac4_audio_util.h>
|
#include <packager/media/codecs/ac4_audio_util.h>
|
||||||
|
#include <packager/media/codecs/dts_audio_specific_config.h>
|
||||||
#include <packager/media/codecs/ec3_audio_util.h>
|
#include <packager/media/codecs/ec3_audio_util.h>
|
||||||
#include <packager/mpd/base/media_info.pb.h>
|
#include <packager/mpd/base/media_info.pb.h>
|
||||||
#include <packager/utils/bytes_to_string_view.h>
|
#include <packager/utils/bytes_to_string_view.h>
|
||||||
|
@ -165,6 +166,16 @@ void AddAudioInfo(const AudioStreamInfo* audio_stream_info,
|
||||||
codec_data->set_ac4_ims_flag(ac4_ims_flag);
|
codec_data->set_ac4_ims_flag(ac4_ims_flag);
|
||||||
codec_data->set_ac4_cbi_flag(ac4_cbi_flag);
|
codec_data->set_ac4_cbi_flag(ac4_cbi_flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (audio_stream_info->codec() == kCodecDTSX) {
|
||||||
|
auto* codec_data = audio_info->mutable_codec_specific_data();
|
||||||
|
uint32_t channel_mask;
|
||||||
|
if (!GetDTSXChannelMask(codec_config, channel_mask)) {
|
||||||
|
LOG(ERROR) << "Failed to parse DTSX channel mask.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
codec_data->set_channel_mask(channel_mask);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddTextInfo(const TextStreamInfo& text_stream_info,
|
void AddTextInfo(const TextStreamInfo& text_stream_info,
|
||||||
|
|
|
@ -72,6 +72,24 @@ TEST_F(MuxerListenerInternalVideoStreamTest, TransferCharacteristics) {
|
||||||
EXPECT_EQ(18u, media_info.video_info().transfer_characteristics());
|
EXPECT_EQ(18u, media_info.video_info().transfer_characteristics());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MuxerListenerInternalAudioStreamTest : public MuxerListenerInternalTest {
|
||||||
|
};
|
||||||
|
|
||||||
|
// AddAudioInfo function should parse the channel mask
|
||||||
|
TEST_F(MuxerListenerInternalAudioStreamTest, DTSX) {
|
||||||
|
MediaInfo media_info;
|
||||||
|
std::shared_ptr<AudioStreamInfo> audio_info = CreateAudioStreamInfo(
|
||||||
|
GetAudioStreamInfoParams(kCodecDTSX, "dtsx",
|
||||||
|
{0x01, 0x20, 0x00, 0x00, 0x0, 0x3F, 0x80,
|
||||||
|
0x00})); // Channel mask = 3F
|
||||||
|
ASSERT_TRUE(GenerateMediaInfo(MuxerOptions(), *audio_info,
|
||||||
|
kReferenceTimeScale,
|
||||||
|
MuxerListener::kContainerMp4, &media_info));
|
||||||
|
MediaInfo_AudioInfo* info = media_info.mutable_audio_info();
|
||||||
|
auto* codec_data = info->mutable_codec_specific_data();
|
||||||
|
EXPECT_EQ(0x3F, codec_data->channel_mask());
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
} // namespace media
|
} // namespace media
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -104,5 +104,53 @@ std::vector<ProtectionSystemSpecificInfo> GetDefaultKeySystemInfo() {
|
||||||
std::end(kExpectedDefaultPsshBox) - 1}}};
|
std::end(kExpectedDefaultPsshBox) - 1}}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AudioStreamInfoParameters::AudioStreamInfoParameters() {}
|
||||||
|
AudioStreamInfoParameters::~AudioStreamInfoParameters() {}
|
||||||
|
|
||||||
|
std::shared_ptr<AudioStreamInfo> CreateAudioStreamInfo(
|
||||||
|
const AudioStreamInfoParameters& param) {
|
||||||
|
return std::make_shared<AudioStreamInfo>(
|
||||||
|
param.track_id, param.time_scale, param.duration, param.codec,
|
||||||
|
param.codec_string, param.codec_config.data(), param.codec_config.size(),
|
||||||
|
param.sample_bits, param.num_channels, param.sampling_frequency,
|
||||||
|
param.seek_preroll_ns, param.codec_delay_ns, param.max_bitrate,
|
||||||
|
param.avg_bitrate, param.language, param.is_encrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioStreamInfoParameters GetAudioStreamInfoParams(
|
||||||
|
Codec codec,
|
||||||
|
const char* codec_string,
|
||||||
|
const std::vector<uint8_t>& codec_config) {
|
||||||
|
const int kTrackId = 0;
|
||||||
|
const int32_t kTimeScale = 10;
|
||||||
|
const int64_t kAudioStreamDuration = 200;
|
||||||
|
const char* kLanuageUndefined = "und";
|
||||||
|
const uint8_t kSampleBits = 16;
|
||||||
|
const uint8_t kNumChannels = 6;
|
||||||
|
const uint32_t kSamplingFrequency = 48000;
|
||||||
|
const uint64_t kSeekPrerollNs = 0;
|
||||||
|
const uint64_t kCodecDelayNs = 0;
|
||||||
|
const uint32_t kMaxBitrate = 0;
|
||||||
|
const uint32_t kAvgBitrate = 0;
|
||||||
|
const bool kEncryptedFlag = false;
|
||||||
|
AudioStreamInfoParameters params;
|
||||||
|
params.track_id = kTrackId;
|
||||||
|
params.time_scale = kTimeScale;
|
||||||
|
params.duration = kAudioStreamDuration;
|
||||||
|
params.codec = codec;
|
||||||
|
params.codec_string = codec_string;
|
||||||
|
params.language = kLanuageUndefined;
|
||||||
|
params.sample_bits = kSampleBits;
|
||||||
|
params.num_channels = kNumChannels;
|
||||||
|
params.sampling_frequency = kSamplingFrequency;
|
||||||
|
params.seek_preroll_ns = kSeekPrerollNs;
|
||||||
|
params.codec_delay_ns = kCodecDelayNs;
|
||||||
|
params.max_bitrate = kMaxBitrate;
|
||||||
|
params.avg_bitrate = kAvgBitrate;
|
||||||
|
params.codec_config = codec_config;
|
||||||
|
params.is_encrypted = kEncryptedFlag;
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <packager/media/base/audio_stream_info.h>
|
||||||
#include <packager/media/base/key_source.h>
|
#include <packager/media/base/key_source.h>
|
||||||
#include <packager/media/base/muxer_options.h>
|
#include <packager/media/base/muxer_options.h>
|
||||||
#include <packager/media/base/stream_info.h>
|
#include <packager/media/base/stream_info.h>
|
||||||
|
@ -95,6 +96,29 @@ struct VideoStreamInfoParameters {
|
||||||
bool is_encrypted;
|
bool is_encrypted;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Struct that gets passed for to CreateAudioStreamInfo() to create a
|
||||||
|
// StreamInfo instance. Useful for generating multiple AudioStreamInfo with
|
||||||
|
// slightly different parameters.
|
||||||
|
struct AudioStreamInfoParameters {
|
||||||
|
AudioStreamInfoParameters();
|
||||||
|
~AudioStreamInfoParameters();
|
||||||
|
int track_id;
|
||||||
|
int32_t time_scale;
|
||||||
|
int64_t duration;
|
||||||
|
Codec codec;
|
||||||
|
std::string codec_string;
|
||||||
|
std::vector<uint8_t> codec_config;
|
||||||
|
uint8_t sample_bits;
|
||||||
|
uint8_t num_channels;
|
||||||
|
uint32_t sampling_frequency;
|
||||||
|
uint64_t seek_preroll_ns;
|
||||||
|
uint64_t codec_delay_ns;
|
||||||
|
uint32_t max_bitrate;
|
||||||
|
uint32_t avg_bitrate;
|
||||||
|
std::string language;
|
||||||
|
bool is_encrypted;
|
||||||
|
};
|
||||||
|
|
||||||
struct OnNewSegmentParameters {
|
struct OnNewSegmentParameters {
|
||||||
std::string file_name;
|
std::string file_name;
|
||||||
int64_t start_time;
|
int64_t start_time;
|
||||||
|
@ -115,6 +139,16 @@ std::shared_ptr<VideoStreamInfo> CreateVideoStreamInfo(
|
||||||
// Returns the "default" VideoStreamInfoParameters for testing.
|
// Returns the "default" VideoStreamInfoParameters for testing.
|
||||||
VideoStreamInfoParameters GetDefaultVideoStreamInfoParams();
|
VideoStreamInfoParameters GetDefaultVideoStreamInfoParams();
|
||||||
|
|
||||||
|
// Creates StreamInfo instance from AudioStreamInfoParameters.
|
||||||
|
std::shared_ptr<AudioStreamInfo> CreateAudioStreamInfo(
|
||||||
|
const AudioStreamInfoParameters& param);
|
||||||
|
|
||||||
|
// Returns the "default" configuration for testing given codec and parameters.
|
||||||
|
AudioStreamInfoParameters GetAudioStreamInfoParams(
|
||||||
|
Codec codec,
|
||||||
|
const char* codec_string,
|
||||||
|
const std::vector<uint8_t>& codec_config);
|
||||||
|
|
||||||
// Returns the "default" values for OnMediaEnd().
|
// Returns the "default" values for OnMediaEnd().
|
||||||
OnMediaEndParameters GetDefaultOnMediaEndParams();
|
OnMediaEndParameters GetDefaultOnMediaEndParams();
|
||||||
|
|
||||||
|
|
|
@ -1811,6 +1811,27 @@ size_t DTSSpecific::ComputeSizeInternal() {
|
||||||
sizeof(kDdtsExtraData);
|
sizeof(kDdtsExtraData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UDTSSpecific::UDTSSpecific() = default;
|
||||||
|
UDTSSpecific::~UDTSSpecific() = default;
|
||||||
|
|
||||||
|
FourCC UDTSSpecific::BoxType() const {
|
||||||
|
return FOURCC_udts;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTSSpecific::ReadWriteInternal(BoxBuffer* buffer) {
|
||||||
|
RCHECK(ReadWriteHeaderInternal(buffer) &&
|
||||||
|
buffer->ReadWriteVector(
|
||||||
|
&data, buffer->Reading() ? buffer->BytesLeft() : data.size()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t UDTSSpecific::ComputeSizeInternal() {
|
||||||
|
// This box is optional. Skip it if not initialized.
|
||||||
|
if (data.empty())
|
||||||
|
return 0;
|
||||||
|
return HeaderSize() + data.size();
|
||||||
|
}
|
||||||
|
|
||||||
AC3Specific::AC3Specific() = default;
|
AC3Specific::AC3Specific() = default;
|
||||||
AC3Specific::~AC3Specific() = default;
|
AC3Specific::~AC3Specific() = default;
|
||||||
|
|
||||||
|
@ -1983,6 +2004,7 @@ bool AudioSampleEntry::ReadWriteInternal(BoxBuffer* buffer) {
|
||||||
|
|
||||||
RCHECK(buffer->TryReadWriteChild(&esds));
|
RCHECK(buffer->TryReadWriteChild(&esds));
|
||||||
RCHECK(buffer->TryReadWriteChild(&ddts));
|
RCHECK(buffer->TryReadWriteChild(&ddts));
|
||||||
|
RCHECK(buffer->TryReadWriteChild(&udts));
|
||||||
RCHECK(buffer->TryReadWriteChild(&dac3));
|
RCHECK(buffer->TryReadWriteChild(&dac3));
|
||||||
RCHECK(buffer->TryReadWriteChild(&dec3));
|
RCHECK(buffer->TryReadWriteChild(&dec3));
|
||||||
RCHECK(buffer->TryReadWriteChild(&dac4));
|
RCHECK(buffer->TryReadWriteChild(&dac4));
|
||||||
|
@ -2014,7 +2036,7 @@ size_t AudioSampleEntry::ComputeSizeInternal() {
|
||||||
sizeof(samplesize) + sizeof(samplerate) + sinf.ComputeSize() +
|
sizeof(samplesize) + sizeof(samplerate) + sinf.ComputeSize() +
|
||||||
esds.ComputeSize() + ddts.ComputeSize() + dac3.ComputeSize() +
|
esds.ComputeSize() + ddts.ComputeSize() + dac3.ComputeSize() +
|
||||||
dec3.ComputeSize() + dops.ComputeSize() + dfla.ComputeSize() +
|
dec3.ComputeSize() + dops.ComputeSize() + dfla.ComputeSize() +
|
||||||
dac4.ComputeSize() + mhac.ComputeSize() +
|
dac4.ComputeSize() + mhac.ComputeSize() + udts.ComputeSize() +
|
||||||
// Reserved and predefined bytes.
|
// Reserved and predefined bytes.
|
||||||
6 + 8 + // 6 + 8 bytes reserved.
|
6 + 8 + // 6 + 8 bytes reserved.
|
||||||
4; // 4 bytes predefined.
|
4; // 4 bytes predefined.
|
||||||
|
|
|
@ -334,6 +334,12 @@ struct DTSSpecific : Box {
|
||||||
std::vector<uint8_t> extra_data;
|
std::vector<uint8_t> extra_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct UDTSSpecific : Box {
|
||||||
|
DECLARE_BOX_METHODS(UDTSSpecific);
|
||||||
|
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
};
|
||||||
|
|
||||||
struct AC3Specific : Box {
|
struct AC3Specific : Box {
|
||||||
DECLARE_BOX_METHODS(AC3Specific);
|
DECLARE_BOX_METHODS(AC3Specific);
|
||||||
|
|
||||||
|
@ -396,6 +402,7 @@ struct AudioSampleEntry : Box {
|
||||||
|
|
||||||
ElementaryStreamDescriptor esds;
|
ElementaryStreamDescriptor esds;
|
||||||
DTSSpecific ddts;
|
DTSSpecific ddts;
|
||||||
|
UDTSSpecific udts;
|
||||||
AC3Specific dac3;
|
AC3Specific dac3;
|
||||||
EC3Specific dec3;
|
EC3Specific dec3;
|
||||||
AC4Specific dac4;
|
AC4Specific dac4;
|
||||||
|
|
|
@ -417,6 +417,16 @@ class BoxDefinitionsTestGeneral : public testing::Test {
|
||||||
ddts->pcm_sample_depth = 24;
|
ddts->pcm_sample_depth = 24;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Fill(UDTSSpecific* udts) {
|
||||||
|
const uint8_t kUdtsData[] = {0x01, 0x20, 0x00, 0x00, 0x0, 0x3F, 0x80, 0x00};
|
||||||
|
udts->data.assign(kUdtsData, kUdtsData + std::size(kUdtsData));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Modify(UDTSSpecific* udts) {
|
||||||
|
const uint8_t kUdtsData[] = {0x01, 0x20, 0x01, 0x80, 0xA, 0x3F, 0x80, 0x00};
|
||||||
|
udts->data.assign(kUdtsData, kUdtsData + std::size(kUdtsData));
|
||||||
|
}
|
||||||
|
|
||||||
void Fill(AC3Specific* dac3) {
|
void Fill(AC3Specific* dac3) {
|
||||||
const uint8_t kAc3Data[] = {0x50, 0x11, 0x60};
|
const uint8_t kAc3Data[] = {0x50, 0x11, 0x60};
|
||||||
dac3->data.assign(kAc3Data, kAc3Data + std::size(kAc3Data));
|
dac3->data.assign(kAc3Data, kAc3Data + std::size(kAc3Data));
|
||||||
|
@ -1217,6 +1227,20 @@ TEST_F(BoxDefinitionsTest, DTSSampleEntry) {
|
||||||
ASSERT_EQ(entry, entry_readback);
|
ASSERT_EQ(entry, entry_readback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(BoxDefinitionsTest, UDTSSampleEntry) {
|
||||||
|
AudioSampleEntry entry;
|
||||||
|
entry.format = FOURCC_dtsx;
|
||||||
|
entry.data_reference_index = 2;
|
||||||
|
entry.channelcount = 6;
|
||||||
|
entry.samplesize = 16;
|
||||||
|
entry.samplerate = 48000;
|
||||||
|
Fill(&entry.udts);
|
||||||
|
entry.Write(this->buffer_.get());
|
||||||
|
AudioSampleEntry entry_readback;
|
||||||
|
ASSERT_TRUE(ReadBack(&entry_readback));
|
||||||
|
ASSERT_EQ(entry, entry_readback);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(BoxDefinitionsTest, AC3SampleEntry) {
|
TEST_F(BoxDefinitionsTest, AC3SampleEntry) {
|
||||||
AudioSampleEntry entry;
|
AudioSampleEntry entry;
|
||||||
entry.format = FOURCC_ac_3;
|
entry.format = FOURCC_ac_3;
|
||||||
|
|
|
@ -90,6 +90,8 @@ Codec FourCCToCodec(FourCC fourcc) {
|
||||||
return kCodecDTSL;
|
return kCodecDTSL;
|
||||||
case FOURCC_dtse:
|
case FOURCC_dtse:
|
||||||
return kCodecDTSE;
|
return kCodecDTSE;
|
||||||
|
case FOURCC_dtsx:
|
||||||
|
return kCodecDTSX;
|
||||||
case FOURCC_dtsp:
|
case FOURCC_dtsp:
|
||||||
return kCodecDTSP;
|
return kCodecDTSP;
|
||||||
case FOURCC_dtsm:
|
case FOURCC_dtsm:
|
||||||
|
@ -491,6 +493,9 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
|
||||||
max_bitrate = entry.ddts.max_bitrate;
|
max_bitrate = entry.ddts.max_bitrate;
|
||||||
avg_bitrate = entry.ddts.avg_bitrate;
|
avg_bitrate = entry.ddts.avg_bitrate;
|
||||||
break;
|
break;
|
||||||
|
case FOURCC_dtsx:
|
||||||
|
codec_config = entry.udts.data;
|
||||||
|
break;
|
||||||
case FOURCC_ac_3:
|
case FOURCC_ac_3:
|
||||||
codec_config = entry.dac3.data;
|
codec_config = entry.dac3.data;
|
||||||
num_channels = static_cast<uint8_t>(GetAc3NumChannels(codec_config));
|
num_channels = static_cast<uint8_t>(GetAc3NumChannels(codec_config));
|
||||||
|
|
|
@ -86,6 +86,8 @@ FourCC CodecToFourCC(Codec codec, H26xStreamFormat h26x_stream_format) {
|
||||||
return FOURCC_dtse;
|
return FOURCC_dtse;
|
||||||
case kCodecDTSM:
|
case kCodecDTSM:
|
||||||
return FOURCC_dtsm;
|
return FOURCC_dtsm;
|
||||||
|
case kCodecDTSX:
|
||||||
|
return FOURCC_dtsx;
|
||||||
case kCodecEAC3:
|
case kCodecEAC3:
|
||||||
return FOURCC_ec_3;
|
return FOURCC_ec_3;
|
||||||
case kCodecAC4:
|
case kCodecAC4:
|
||||||
|
@ -498,6 +500,9 @@ bool MP4Muxer::GenerateAudioTrak(const AudioStreamInfo* audio_info,
|
||||||
audio.ddts.sampling_frequency = audio_info->sampling_frequency();
|
audio.ddts.sampling_frequency = audio_info->sampling_frequency();
|
||||||
audio.ddts.pcm_sample_depth = audio_info->sample_bits();
|
audio.ddts.pcm_sample_depth = audio_info->sample_bits();
|
||||||
break;
|
break;
|
||||||
|
case kCodecDTSX:
|
||||||
|
audio.udts.data = audio_info->codec_config();
|
||||||
|
break;
|
||||||
case kCodecAC3:
|
case kCodecAC3:
|
||||||
audio.dac3.data = audio_info->codec_config();
|
audio.dac3.data = audio_info->codec_config();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -118,3 +118,11 @@ sintel-1024x436.mp4 - First 6 seconds of Sintel stream.
|
||||||
// http://download.opencontent.netflix.com/?prefix=TechblogAssets/Sparks/Sparks_DolbyVision_P3D65_PQ_5994fps_4096x2160_LtRt_IMF_20170214/
|
// http://download.opencontent.netflix.com/?prefix=TechblogAssets/Sparks/Sparks_DolbyVision_P3D65_PQ_5994fps_4096x2160_LtRt_IMF_20170214/
|
||||||
sparks_dovi_5.mp4 - Dolby Vision profile 5
|
sparks_dovi_5.mp4 - Dolby Vision profile 5
|
||||||
sparks_dovi_8.mp4 - Dolby Vision profile 8
|
sparks_dovi_8.mp4 - Dolby Vision profile 8
|
||||||
|
|
||||||
|
// DTS:X Profile 2 5.1 channel audio
|
||||||
|
bear-dtsx.mp4:
|
||||||
|
Generated using ffmpeg and proprietary encoder:
|
||||||
|
ffmpeg -i bear.aiff -i bear.aiff -i bear.aiff \
|
||||||
|
-filter_complex "[0:a][1:a][2:a]concat=n=3:v=0:a=1[out]" \
|
||||||
|
-ar 48000 -map "[out]" -y bear.wav
|
||||||
|
dtsx-ott-enc -w -o bear-dtsx.mp4 -c 5.1 bear.wav bear.wav bear.wav
|
||||||
|
|
Binary file not shown.
|
@ -59,6 +59,8 @@ std::string RoleToText(AdaptationSet::Role role) {
|
||||||
return "commentary";
|
return "commentary";
|
||||||
case AdaptationSet::kRoleDub:
|
case AdaptationSet::kRoleDub:
|
||||||
return "dub";
|
return "dub";
|
||||||
|
case AdaptationSet::kRoleForcedSubtitle:
|
||||||
|
return "forced-subtitle";
|
||||||
case AdaptationSet::kRoleDescription:
|
case AdaptationSet::kRoleDescription:
|
||||||
return "description";
|
return "description";
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -43,6 +43,7 @@ class AdaptationSet {
|
||||||
kRoleSupplementary,
|
kRoleSupplementary,
|
||||||
kRoleCommentary,
|
kRoleCommentary,
|
||||||
kRoleDub,
|
kRoleDub,
|
||||||
|
kRoleForcedSubtitle,
|
||||||
kRoleDescription
|
kRoleDescription
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -213,6 +213,11 @@ message MediaInfo {
|
||||||
// Equal to the target segment duration times the reference time scale.
|
// Equal to the target segment duration times the reference time scale.
|
||||||
optional uint64 segment_duration = 25;
|
optional uint64 segment_duration = 25;
|
||||||
|
|
||||||
|
// Marks stream as a Forced Narrative subtitle stream, indicated using
|
||||||
|
// forced-subtitle role in DASH
|
||||||
|
// and FORCED=YES in HLS
|
||||||
|
optional bool forced_subtitle = 26 [default = false];
|
||||||
|
|
||||||
// stream index for consistent ordering of streams
|
// stream index for consistent ordering of streams
|
||||||
optional uint32 index = 28;
|
optional uint32 index = 28;
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,8 @@ AdaptationSet::Role RoleFromString(const std::string& role_str) {
|
||||||
return AdaptationSet::Role::kRoleCommentary;
|
return AdaptationSet::Role::kRoleCommentary;
|
||||||
if (role_str == "dub")
|
if (role_str == "dub")
|
||||||
return AdaptationSet::Role::kRoleDub;
|
return AdaptationSet::Role::kRoleDub;
|
||||||
|
if (role_str == "forced-subtitle")
|
||||||
|
return AdaptationSet::Role::kRoleForcedSubtitle;
|
||||||
if (role_str == "description")
|
if (role_str == "description")
|
||||||
return AdaptationSet::Role::kRoleDescription;
|
return AdaptationSet::Role::kRoleDescription;
|
||||||
return AdaptationSet::Role::kRoleUnknown;
|
return AdaptationSet::Role::kRoleUnknown;
|
||||||
|
|
|
@ -49,6 +49,9 @@ typedef MediaInfo::VideoInfo VideoInfo;
|
||||||
namespace {
|
namespace {
|
||||||
const char kEC3Codec[] = "ec-3";
|
const char kEC3Codec[] = "ec-3";
|
||||||
const char kAC4Codec[] = "ac-4";
|
const char kAC4Codec[] = "ac-4";
|
||||||
|
const char kDTSCCodec[] = "dtsc";
|
||||||
|
const char kDTSECodec[] = "dtse";
|
||||||
|
const char kDTSXCodec[] = "dtsx";
|
||||||
|
|
||||||
std::string RangeToString(const Range& range) {
|
std::string RangeToString(const Range& range) {
|
||||||
return absl::StrFormat("%u-%u", range.begin(), range.end());
|
return absl::StrFormat("%u-%u", range.begin(), range.end());
|
||||||
|
@ -680,6 +683,18 @@ bool RepresentationXmlNode::AddAudioChannelInfo(const AudioInfo& audio_info) {
|
||||||
"1");
|
"1");
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
} else if (audio_info.codec() == kDTSCCodec ||
|
||||||
|
audio_info.codec() == kDTSECodec) {
|
||||||
|
audio_channel_config_value =
|
||||||
|
absl::StrFormat("%u", audio_info.num_channels());
|
||||||
|
audio_channel_config_scheme =
|
||||||
|
"tag:dts.com,2014:dash:audio_channel_configuration:2012";
|
||||||
|
} else if (audio_info.codec() == kDTSXCodec) {
|
||||||
|
const auto& codec_data = audio_info.codec_specific_data();
|
||||||
|
audio_channel_config_value =
|
||||||
|
absl::StrFormat("%08X", codec_data.channel_mask());
|
||||||
|
audio_channel_config_scheme =
|
||||||
|
"tag:dts.com,2018:uhd:audio_channel_configuration";
|
||||||
} else {
|
} else {
|
||||||
audio_channel_config_value =
|
audio_channel_config_value =
|
||||||
absl::StrFormat("%u", audio_info.num_channels());
|
absl::StrFormat("%u", audio_info.num_channels());
|
||||||
|
|
|
@ -766,5 +766,61 @@ TEST_F(LowLatencySegmentTest, LowLatencySegmentTemplate) {
|
||||||
"</Representation>"));
|
"</Representation>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(XmlNodeTest, AddDTSCAudioInfo) {
|
||||||
|
MediaInfo::AudioInfo audio_info;
|
||||||
|
audio_info.set_codec("dtsc");
|
||||||
|
audio_info.set_sampling_frequency(48000);
|
||||||
|
audio_info.set_num_channels(6);
|
||||||
|
|
||||||
|
RepresentationXmlNode representation;
|
||||||
|
ASSERT_TRUE(representation.AddAudioInfo(audio_info));
|
||||||
|
EXPECT_THAT(
|
||||||
|
representation,
|
||||||
|
XmlNodeEqual(
|
||||||
|
"<Representation audioSamplingRate=\"48000\">\n"
|
||||||
|
" <AudioChannelConfiguration\n"
|
||||||
|
" schemeIdUri=\n"
|
||||||
|
" \"tag:dts.com,2014:dash:audio_channel_configuration:2012\"\n"
|
||||||
|
" value=\"6\"/>\n"
|
||||||
|
"</Representation>\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XmlNodeTest, AddDTSEAudioInfo) {
|
||||||
|
MediaInfo::AudioInfo audio_info;
|
||||||
|
audio_info.set_codec("dtse");
|
||||||
|
audio_info.set_sampling_frequency(48000);
|
||||||
|
audio_info.set_num_channels(6);
|
||||||
|
|
||||||
|
RepresentationXmlNode representation;
|
||||||
|
ASSERT_TRUE(representation.AddAudioInfo(audio_info));
|
||||||
|
EXPECT_THAT(
|
||||||
|
representation,
|
||||||
|
XmlNodeEqual(
|
||||||
|
"<Representation audioSamplingRate=\"48000\">\n"
|
||||||
|
" <AudioChannelConfiguration\n"
|
||||||
|
" schemeIdUri=\n"
|
||||||
|
" \"tag:dts.com,2014:dash:audio_channel_configuration:2012\"\n"
|
||||||
|
" value=\"6\"/>\n"
|
||||||
|
"</Representation>\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(XmlNodeTest, AddDTSXAudioInfo) {
|
||||||
|
MediaInfo::AudioInfo audio_info;
|
||||||
|
audio_info.set_codec("dtsx");
|
||||||
|
audio_info.set_sampling_frequency(48000);
|
||||||
|
audio_info.mutable_codec_specific_data()->set_channel_mask(0x3F);
|
||||||
|
|
||||||
|
RepresentationXmlNode representation;
|
||||||
|
ASSERT_TRUE(representation.AddAudioInfo(audio_info));
|
||||||
|
EXPECT_THAT(
|
||||||
|
representation,
|
||||||
|
XmlNodeEqual("<Representation audioSamplingRate=\"48000\">\n"
|
||||||
|
" <AudioChannelConfiguration\n"
|
||||||
|
" schemeIdUri=\n"
|
||||||
|
" \"tag:dts.com,2018:uhd:audio_channel_configuration\"\n"
|
||||||
|
" value=\"0000003F\"/>\n"
|
||||||
|
"</Representation>\n"));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace xml
|
} // namespace xml
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -69,6 +69,7 @@ MuxerListenerFactory::StreamData ToMuxerListenerData(
|
||||||
data.hls_playlist_name = stream.hls_playlist_name;
|
data.hls_playlist_name = stream.hls_playlist_name;
|
||||||
data.hls_iframe_playlist_name = stream.hls_iframe_playlist_name;
|
data.hls_iframe_playlist_name = stream.hls_iframe_playlist_name;
|
||||||
data.hls_characteristics = stream.hls_characteristics;
|
data.hls_characteristics = stream.hls_characteristics;
|
||||||
|
data.forced_subtitle = stream.forced_subtitle;
|
||||||
data.hls_only = stream.hls_only;
|
data.hls_only = stream.hls_only;
|
||||||
|
|
||||||
data.dash_accessiblities = stream.dash_accessiblities;
|
data.dash_accessiblities = stream.dash_accessiblities;
|
||||||
|
|
Loading…
Reference in New Issue