diff --git a/.github/workflows/build-docs.yaml b/.github/workflows/build-docs.yaml index 24a8a747c50..899f8da0f86 100644 --- a/.github/workflows/build-docs.yaml +++ b/.github/workflows/build-docs.yaml @@ -31,7 +31,7 @@ jobs: steps: - name: Install dependencies run: | - sudo apt install -y doxygen + sudo apt install -y doxygen graphviz python3 -m pip install \ sphinx==7.1.2 \ sphinxcontrib.plantuml \ diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 930cf0f40af..95a37e3ee86 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.0.3" + ".": "3.2.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f865ce08a6..355e8e8ea83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,37 @@ # Changelog +## [3.2.0](https://github.com/shaka-project/shaka-packager/compare/v3.1.0...v3.2.0) (2024-05-11) + + +### Features + +* support Dolby Vision profile 8.x (HEVC) and 10.x (AV1) in HLS and DASH ([#1396](https://github.com/shaka-project/shaka-packager/issues/1396)) ([a99cfe0](https://github.com/shaka-project/shaka-packager/commit/a99cfe036f09de51b488f87f4cb126a1bcd3a286)) + + +### Bug Fixes + +* adaptation set IDs were referenced by lowest representation ID ([#1394](https://github.com/shaka-project/shaka-packager/issues/1394)) ([94db9c9](https://github.com/shaka-project/shaka-packager/commit/94db9c9db3e73073925205355dd61a6dc9785065)), closes [#1393](https://github.com/shaka-project/shaka-packager/issues/1393) +* escape media URLs in MPD ([#1395](https://github.com/shaka-project/shaka-packager/issues/1395)) ([98b44d0](https://github.com/shaka-project/shaka-packager/commit/98b44d01df6a952466b5a1667818da877502da97)) +* set yuv full range flag to 1 for VP9 with sRGB ([#1398](https://github.com/shaka-project/shaka-packager/issues/1398)) ([f6f60e5](https://github.com/shaka-project/shaka-packager/commit/f6f60e5fff8d5c9b13fbf65f494eba651050ccb9)) + +## [3.1.0](https://github.com/shaka-project/shaka-packager/compare/v3.0.4...v3.1.0) (2024-05-03) + + +### Features + +* add missing DASH roles from ISO/IEC 23009-1 section 5.8.5.5 ([#1390](https://github.com/shaka-project/shaka-packager/issues/1390)) ([fe885b3](https://github.com/shaka-project/shaka-packager/commit/fe885b3ade020b197a04fc63ee41fd90e7e11a14)) +* get start number from muxer and specify initial sequence number ([#879](https://github.com/shaka-project/shaka-packager/issues/879)) ([bb104fe](https://github.com/shaka-project/shaka-packager/commit/bb104fef5d745ac3a0a8c1e6fb4f1b1a9b27d8ae)) +* teletext formatting ([#1384](https://github.com/shaka-project/shaka-packager/issues/1384)) ([4b5e80d](https://github.com/shaka-project/shaka-packager/commit/4b5e80d02c10fd1ddb8f7e0f2f1a8608782d8442)) + +## [3.0.4](https://github.com/shaka-project/shaka-packager/compare/v3.0.3...v3.0.4) (2024-03-27) + + +### Bug Fixes + +* BaseURL missing when MPD base path is empty ([#1380](https://github.com/shaka-project/shaka-packager/issues/1380)) ([90c3c3f](https://github.com/shaka-project/shaka-packager/commit/90c3c3f9b3e8e36706b9769e574aa316e8bbb351)), closes [#1378](https://github.com/shaka-project/shaka-packager/issues/1378) +* Fix NPM binary selection on ARM Macs ([#1376](https://github.com/shaka-project/shaka-packager/issues/1376)) ([733af91](https://github.com/shaka-project/shaka-packager/commit/733af9128dfa7c46dd7a9fe3f8361ab50a829afe)), closes [#1375](https://github.com/shaka-project/shaka-packager/issues/1375) + ## [3.0.3](https://github.com/shaka-project/shaka-packager/compare/v3.0.2...v3.0.3) (2024-03-12) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c1e8d132d9..5b96a2429e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,7 @@ option(FULLY_STATIC "Attempt fully static linking of all CLI apps" OFF) # Enable CMake's test infrastructure. enable_testing() -option(SKIP_INTEGRATION_TESTS "Skip the packager integration tests" ON) +option(SKIP_INTEGRATION_TESTS "Skip the packager integration tests" OFF) # Subdirectories with their own CMakeLists.txt add_subdirectory(packager) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index d7757949bb2..9ba03ca1b7e 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -54,6 +54,7 @@ Sanil Raut Sergio Ammirata Thomas Inskip Tim Lansen +Torbjörn Einarsson Vincent Nguyen Weiguo Shao diff --git a/docs/source/options/chunking_options.rst b/docs/source/options/chunking_options.rst index 560a2a7cc8c..4bb909e0437 100644 --- a/docs/source/options/chunking_options.rst +++ b/docs/source/options/chunking_options.rst @@ -21,3 +21,7 @@ Chunking options Force fragments to begin with stream access points. This flag implies *segment_sap_aligned*. Default enabled. + +--start_segment_number + + Indicates the startNumber in DASH SegmentTemplate and HLS segment name. diff --git a/docs/source/options/dash_stream_descriptors.rst b/docs/source/options/dash_stream_descriptors.rst index c5c646899b5..a50f7fadaa6 100644 --- a/docs/source/options/dash_stream_descriptors.rst +++ b/docs/source/options/dash_stream_descriptors.rst @@ -10,7 +10,10 @@ DASH specific stream descriptor fields :dash_roles (roles): - 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**, - **supplementary**, **commentary**, **description**, **dub** and **forced-subtitle** . + **supplementary**, **commentary**, **dub**, **description**, **sign**, + **metadata**, **enhanced-audio- intelligibility**, **emergency**, + **forced-subtitle**, **easyreader**, and **karaoke**. + See DASH (ISO/IEC 23009-1) specification for details. diff --git a/docs/source/options/widevine_encryption_options.rst b/docs/source/options/widevine_encryption_options.rst index 9d875a69755..597f0f9c480 100644 --- a/docs/source/options/widevine_encryption_options.rst +++ b/docs/source/options/widevine_encryption_options.rst @@ -9,6 +9,10 @@ Widevine encryption options --protection_systems is not specified. Use --protection_systems to generate multiple protection systems. +--enable_entitlement_license + + Enable entitlement license in the Widevine encryption request. + --enable_widevine_decryption Enable decryption with Widevine key server. User should provide either diff --git a/include/packager/chunking_params.h b/include/packager/chunking_params.h index e12baf0b9e0..ddc31675c93 100644 --- a/include/packager/chunking_params.h +++ b/include/packager/chunking_params.h @@ -31,6 +31,9 @@ struct ChunkingParams { /// and mdat atom. Each chunk is uploaded immediately upon creation, /// decoupling latency from segment duration. bool low_latency_dash_mode = false; + + /// Indicates the startNumber in DASH SegmentTemplate and HLS segment name. + int64_t start_segment_number = 1; }; } // namespace shaka diff --git a/link-test/CMakeLists.txt b/link-test/CMakeLists.txt index 3fc0d097d3b..4fce0f1b92c 100644 --- a/link-test/CMakeLists.txt +++ b/link-test/CMakeLists.txt @@ -13,7 +13,7 @@ if(BUILD_SHARED_LIBS) # Custom commands aren't targets, but have outputs. add_custom_command( - DEPENDS mpd_generator packager libpackager + DEPENDS mpd_generator packager libpackager pssh_box_py WORKING_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT ${TEST_INSTALL_DIR} COMMAND diff --git a/packager/CMakeLists.txt b/packager/CMakeLists.txt index a1f8bb25553..7238d243bb2 100644 --- a/packager/CMakeLists.txt +++ b/packager/CMakeLists.txt @@ -66,6 +66,11 @@ include("gtest.cmake") # Include our module for building protos. include("protobuf.cmake") +# Find Python3 used by integration tests, license notice and version string +if(NOT Python3_EXECUTABLE) + find_package(Python3 COMPONENTS Interpreter REQUIRED) +endif() + # Subdirectories with their own CMakeLists.txt, all of whose targets are built. add_subdirectory(file) add_subdirectory(kv_pairs) @@ -228,9 +233,22 @@ add_custom_target(packager_test_py_copy ALL if(NOT SKIP_INTEGRATION_TESTS) add_test (NAME packager_test_py - COMMAND ${PYTHON_EXECUTABLE} packager_test.py + COMMAND "${Python3_EXECUTABLE}" packager_test.py WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) + + set(test_environment_vars "PACKAGER_SRC_DIR=${CMAKE_SOURCE_DIR}") + list(APPEND test_environment_vars "PACKAGER_BIN=$") + list(APPEND test_environment_vars "MPD_GENERATOR_BIN=$") + if(BUILD_SHARED_LIBS) + list(APPEND test_environment_vars "BUILD_TYPE=shared") + else() + list(APPEND test_environment_vars "BUILD_TYPE=static") + endif() + + set_tests_properties(packager_test_py PROPERTIES + ENVIRONMENT "${test_environment_vars}" + ) endif() configure_file(packager.pc.in packager.pc @ONLY) @@ -238,12 +256,6 @@ configure_file(packager.pc.in packager.pc @ONLY) # Always install the binaries. install(TARGETS mpd_generator packager) -# Always install the python tools. -install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/pssh-box.py - DESTINATION ${CMAKE_INSTALL_BINDIR}) -install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/pssh-box-protos - DESTINATION ${CMAKE_INSTALL_BINDIR}) - # With shared libraries, also install the library, headers, and pkgconfig. # The static library isn't usable as a standalone because it doesn't include # its static dependencies (zlib, absl, etc). diff --git a/packager/app/muxer_flags.cc b/packager/app/muxer_flags.cc index 2a68b1ee91b..be6c3520b47 100644 --- a/packager/app/muxer_flags.cc +++ b/packager/app/muxer_flags.cc @@ -73,3 +73,9 @@ ABSL_FLAG( "If the first sample comes after default_text_zero_bias_ms then the start " "of the stream will not be padded as we cannot assume the start time of " "the stream."); + +ABSL_FLAG(int64_t, + start_segment_number, + 1, + "Indicates the startNumber in DASH SegmentTemplate and HLS " + "segment name."); \ No newline at end of file diff --git a/packager/app/muxer_flags.h b/packager/app/muxer_flags.h index a27aff8638a..527ca779396 100644 --- a/packager/app/muxer_flags.h +++ b/packager/app/muxer_flags.h @@ -22,5 +22,6 @@ ABSL_DECLARE_FLAG(std::string, temp_dir); ABSL_DECLARE_FLAG(bool, mp4_include_pssh_in_stream); ABSL_DECLARE_FLAG(int32_t, transport_stream_timestamp_offset_ms); ABSL_DECLARE_FLAG(int32_t, default_text_zero_bias_ms); +ABSL_DECLARE_FLAG(int64_t, start_segment_number); #endif // APP_MUXER_FLAGS_H_ diff --git a/packager/app/packager_main.cc b/packager/app/packager_main.cc index adf67fc58f5..8e542aeef6a 100644 --- a/packager/app/packager_main.cc +++ b/packager/app/packager_main.cc @@ -126,9 +126,10 @@ const char kUsage[] = " list of values for DASH Accessibility elements. The value should be\n" " in the format: scheme_id_uri=value.\n" " - dash_roles (roles): Optional semicolon separated list of values for\n" - " DASH Role elements. The value should be one of: caption, subtitle,\n" - " forced-subtitle, main, alternate, supplementary, commentary, \n" - " description and dub. See DASH\n" + " DASH Role elements. The value should be one of: caption, subtitle, \n" + " main, alternate, supplementary, commentary, dub, description, sign, \n" + " metadata, enhanced-audio- intelligibility, emergency, \n" + " forced-subtitle, easyreader, and karaoke. 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" @@ -359,6 +360,8 @@ std::optional GetPackagingParams() { absl::GetFlag(FLAGS_segment_sap_aligned); chunking_params.subsegment_sap_aligned = absl::GetFlag(FLAGS_fragment_sap_aligned); + chunking_params.start_segment_number = + absl::GetFlag(FLAGS_start_segment_number); int num_key_providers = 0; EncryptionParams& encryption_params = packaging_params.encryption_params; diff --git a/packager/app/test/packager_app.py b/packager/app/test/packager_app.py index a8c87521594..c736749c3d0 100644 --- a/packager/app/test/packager_app.py +++ b/packager/app/test/packager_app.py @@ -17,10 +17,8 @@ class PackagerApp(object): """Main integration class for testing the packager binaries.""" def __init__(self): - self.packager_binary = os.path.join(test_env.SCRIPT_DIR, - self._GetBinaryName('packager')) - self.mpd_generator_binary = os.path.join( - test_env.SCRIPT_DIR, self._GetBinaryName('mpd_generator')) + self.packager_binary = test_env.PACKAGER_BIN + self.mpd_generator_binary = test_env.MPD_GENERATOR_BIN # Set this to empty for now in case GetCommandLine() is called before # Package(). self.packaging_command_line = '' @@ -28,15 +26,10 @@ def __init__(self): 'Please run from output directory, e.g. out/Debug/packager_test.py\n' ' Missing: ' + self.packager_binary) - def _GetBinaryName(self, name): - if platform.system() == 'Windows': - name += '.exe' - return name - def GetEnv(self): env = os.environ.copy() if (platform.system() == 'Darwin' and - test_env.options.libpackager_type == 'shared_library'): + test_env.BUILD_TYPE == 'shared'): env['DYLD_FALLBACK_LIBRARY_PATH'] = test_env.SCRIPT_DIR return env diff --git a/packager/app/test/packager_test.py b/packager/app/test/packager_test.py index 7603e1fb6bb..79ee48f5afc 100755 --- a/packager/app/test/packager_test.py +++ b/packager/app/test/packager_test.py @@ -485,8 +485,9 @@ def _GetFlags(self, use_fake_clock=True, allow_codec_switching=False, dash_force_segment_list=False, - force_cl_index=False): - + force_cl_index=None, + start_segment_number=None, + use_dovi_supplemental_codecs=None): flags = ['--single_threaded'] if not strip_parameter_set_nalus: @@ -544,6 +545,9 @@ def _GetFlags(self, if not dash_if_iop: flags.append('--generate_dash_if_iop_compliant_mpd=false') + if use_dovi_supplemental_codecs: + flags.append('--use_dovi_supplemental_codecs') + if output_media_info: flags.append('--output_media_info') if output_dash: @@ -570,8 +574,13 @@ def _GetFlags(self, if allow_codec_switching: flags += ['--allow_codec_switching'] - if force_cl_index: + if force_cl_index is True: flags += ['--force_cl_index'] + elif force_cl_index is False: + flags += ['--noforce_cl_index'] + + if start_segment_number is not None: + flags += ['--start_segment_number', str(start_segment_number)] if ad_cues: flags += ['--ad_cues', ad_cues] @@ -754,7 +763,8 @@ def testAudioVideoWithTwoTrickPlay(self): self._GetStream('video', trick_play_factor=2), ] - self.assertPackageSuccess(streams, self._GetFlags(output_dash=True)) + self.assertPackageSuccess(streams, self._GetFlags(output_dash=True, + force_cl_index=False)) self._CheckTestResults('audio-video-with-two-trick-play') def testAudioVideoWithTwoTrickPlayDecreasingRate(self): @@ -765,7 +775,8 @@ def testAudioVideoWithTwoTrickPlayDecreasingRate(self): self._GetStream('video', trick_play_factor=1), ] - self.assertPackageSuccess(streams, self._GetFlags(output_dash=True)) + self.assertPackageSuccess(streams, self._GetFlags(output_dash=True, + force_cl_index=False)) # Since the stream descriptors are sorted in packager app, a different # order of trick play factors gets the same mpd. self._CheckTestResults('audio-video-with-two-trick-play') @@ -819,6 +830,13 @@ def testForcedSubtitle(self): output_hls=True)) self._CheckTestResults('forced-subtitle') + def testDashStartNumber(self): + audio_video_streams = self._GetStreams(['audio', 'video'], segmented=True) + streams = audio_video_streams + self.assertPackageSuccess(streams, self._GetFlags(output_dash=True, + start_segment_number=0)) + self._CheckTestResults('dash-start-number') + def testAudioVideoWithLanguageOverride(self): self.assertPackageSuccess( self._GetStreams(['audio', 'video'], language='por', hls=True), @@ -1442,6 +1460,42 @@ def testDolbyVisionProfile8WithEncryption(self): self.assertPackageSuccess(streams, flags) self._CheckTestResults('dolby-vision-profile-8-with-encryption') + # TODO(cosmin): shared_library build does not support + # use_dovi_supplemental_codecs + @unittest.skipIf( + test_env.BUILD_TYPE == 'shared', + 'libpackager shared_library does not support ' + '--use_dovi_supplemental_codecs flag.' + ) + def testDolbyVisionProfile8UsingSupplementalCodecs(self): + streams = [ + self._GetStream('video', test_file='sparks_dovi_8.mp4') + ] + flags = self._GetFlags(output_dash=True, + output_hls=True, + use_dovi_supplemental_codecs=True) + + self.assertPackageSuccess(streams, flags) + self._CheckTestResults('dolby-vision-profile-8-supplemental-codecs') + + # TODO(cosmin): shared_library build does not support + # use_dovi_supplemental_codecs + @unittest.skipIf( + test_env.BUILD_TYPE == 'shared', + 'libpackager shared_library does not support ' + '--use_dovi_supplemental_codecs flag.' + ) + def testDolbyVisionProfile10UsingSupplementalCodecs(self): + streams = [ + self._GetStream('video', test_file='sparks_dovi_10.mp4') + ] + flags = self._GetFlags(output_dash=True, + output_hls=True, + use_dovi_supplemental_codecs=True) + + self.assertPackageSuccess(streams, flags) + self._CheckTestResults('dolby-vision-profile-10-supplemental-codecs') + def testVp8Mp4WithEncryption(self): streams = [ self._GetStream('video', @@ -1510,8 +1564,8 @@ def testWvmInput(self): # TODO(kqyang): Fix shared_library not supporting strip_parameter_set_nalus # problem. - @unittest.skipUnless( - test_env.options.libpackager_type == 'static_library', + @unittest.skipIf( + test_env.BUILD_TYPE == 'shared', 'libpackager shared_library does not support ' '--strip_parameter_set_nalus flag.' ) @@ -1690,7 +1744,9 @@ def testLiveStaticProfileWithTimeInSegmentName(self): def testAllowCodecSwitching(self): streams = [ + self._GetStream('video', test_file='bear-1280x720-hevc.mp4'), self._GetStream('video', test_file='bear-640x360-hevc.mp4'), + self._GetStream('video', test_file='bear-640x360-vp9.mp4'), self._GetStream('video', test_file='bear-640x360.mp4'), self._GetStream('video', test_file='bear-1280x720.mp4'), self._GetStream('audio', test_file='bear-640x360.mp4'), diff --git a/packager/app/test/test_env.py b/packager/app/test/test_env.py index 602d9b9bb39..f26a46523f7 100644 --- a/packager/app/test/test_env.py +++ b/packager/app/test/test_env.py @@ -14,21 +14,38 @@ import argparse import os +import platform import sys +def GetBinaryName(name): + if platform.system() == 'Windows': + name += '.exe' + return name + # Define static global objects and attributes. SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) -SRC_DIR = os.path.join(SCRIPT_DIR, os.pardir, os.pardir) +SRC_DIR = os.environ.get('PACKAGER_SRC_DIR') +if not SRC_DIR: + # fallback to computing src dir from script dir + SRC_DIR = os.path.join(SCRIPT_DIR, os.pardir, os.pardir) + +PACKAGER_BIN = os.environ.get('PACKAGER_BIN') +if not PACKAGER_BIN: + PACKAGER_BIN = os.path.join(SCRIPT_DIR, + GetBinaryName('packager')) + +MPD_GENERATOR_BIN = os.environ.get('MPD_GENERATOR_BIN') +if not MPD_GENERATOR_BIN: + MPD_GENERATOR_BIN = os.path.join(SCRIPT_DIR, + GetBinaryName('mpd_generator')) + +BUILD_TYPE = os.environ.get('BUILD_TYPE', 'static') # Parse arguments and calculate dynamic global objects and attributes. parser = argparse.ArgumentParser() - parser.add_argument('--test_update_golden_files', action='store_true') -parser.add_argument('--libpackager_type', default='static_library', - choices=['static_library', 'shared_library']) - parser.add_argument('--v') parser.add_argument('--vmodule') # Overwrite the test to encryption key/iv specified in the command line. diff --git a/packager/app/test/testdata/audio-video-with-codec-switching-and-forced-commandline_order/output.mpd b/packager/app/test/testdata/audio-video-with-codec-switching-and-forced-commandline_order/output.mpd index 3ff2faa158d..95da685a0f2 100644 --- a/packager/app/test/testdata/audio-video-with-codec-switching-and-forced-commandline_order/output.mpd +++ b/packager/app/test/testdata/audio-video-with-codec-switching-and-forced-commandline_order/output.mpd @@ -12,7 +12,7 @@ - + bear-640x360-hevc-video.mp4 diff --git a/packager/app/test/testdata/audio-video-with-codec-switching-encryption-trick-play/output.mpd b/packager/app/test/testdata/audio-video-with-codec-switching-encryption-trick-play/output.mpd index b5b8344745c..0519cd9ef50 100644 --- a/packager/app/test/testdata/audio-video-with-codec-switching-encryption-trick-play/output.mpd +++ b/packager/app/test/testdata/audio-video-with-codec-switching-encryption-trick-play/output.mpd @@ -7,7 +7,7 @@ AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - + bear-640x360-hevc-video.mp4 @@ -21,7 +21,7 @@ AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - + bear-640x360-video.mp4 @@ -41,7 +41,7 @@ AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - + bear-1280x720-video-trick_play_factor_1.mp4 diff --git a/packager/app/test/testdata/audio-video-with-codec-switching/bear-1280x720-hevc-video.mp4 b/packager/app/test/testdata/audio-video-with-codec-switching/bear-1280x720-hevc-video.mp4 new file mode 100644 index 00000000000..5d9af3a83b2 Binary files /dev/null and b/packager/app/test/testdata/audio-video-with-codec-switching/bear-1280x720-hevc-video.mp4 differ diff --git a/packager/app/test/testdata/audio-video-with-codec-switching/bear-640x360-vp9-video.mp4 b/packager/app/test/testdata/audio-video-with-codec-switching/bear-640x360-vp9-video.mp4 new file mode 100644 index 00000000000..67ebd7e445f Binary files /dev/null and b/packager/app/test/testdata/audio-video-with-codec-switching/bear-640x360-vp9-video.mp4 differ diff --git a/packager/app/test/testdata/audio-video-with-codec-switching/output.mpd b/packager/app/test/testdata/audio-video-with-codec-switching/output.mpd index 5504466c188..d4db35ce576 100644 --- a/packager/app/test/testdata/audio-video-with-codec-switching/output.mpd +++ b/packager/app/test/testdata/audio-video-with-codec-switching/output.mpd @@ -2,34 +2,50 @@ - - + + - + + bear-1280x720-hevc-video.mp4 + + + + + bear-640x360-hevc-video.mp4 - - + + + + + bear-640x360-vp9-video.mp4 + + + + + + + - + bear-640x360-video.mp4 - + bear-1280x720-video.mp4 - - + + bear-640x360-audio.mp4 diff --git a/packager/app/test/testdata/audio-video-with-trick-play/output.mpd b/packager/app/test/testdata/audio-video-with-trick-play/output.mpd index 05e5387dac5..00f3fe96fee 100644 --- a/packager/app/test/testdata/audio-video-with-trick-play/output.mpd +++ b/packager/app/test/testdata/audio-video-with-trick-play/output.mpd @@ -20,7 +20,7 @@ - + bear-640x360-video-trick_play_factor_1.mp4 diff --git a/packager/app/test/testdata/audio-video-with-two-trick-play/output.mpd b/packager/app/test/testdata/audio-video-with-two-trick-play/output.mpd index e9c0ec69eb1..bee30c7943a 100644 --- a/packager/app/test/testdata/audio-video-with-two-trick-play/output.mpd +++ b/packager/app/test/testdata/audio-video-with-two-trick-play/output.mpd @@ -2,35 +2,35 @@ - - - - bear-640x360-audio.mp4 - - + + + bear-640x360-video.mp4 + + - - - bear-640x360-video.mp4 + + + + bear-640x360-video-trick_play_factor_1.mp4 - - - bear-640x360-video-trick_play_factor_2.mp4 - - bear-640x360-video-trick_play_factor_1.mp4 - - + + + + + bear-640x360-audio.mp4 + + diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-audio-0.m4s b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-0.m4s new file mode 100644 index 00000000000..6e56dd8555c Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-0.m4s differ diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-audio-1.m4s b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-1.m4s new file mode 100644 index 00000000000..5bf76aa0d1a Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-1.m4s differ diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-audio-2.m4s b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-2.m4s new file mode 100644 index 00000000000..d6bcbb5de8c Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-2.m4s differ diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-audio-init.mp4 b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-init.mp4 new file mode 100644 index 00000000000..3176a92d7ff Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-audio-init.mp4 differ diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-video-0.m4s b/packager/app/test/testdata/dash-start-number/bear-640x360-video-0.m4s new file mode 100644 index 00000000000..2c90c468835 Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-video-0.m4s differ diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-video-1.m4s b/packager/app/test/testdata/dash-start-number/bear-640x360-video-1.m4s new file mode 100644 index 00000000000..c6b356a8e91 Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-video-1.m4s differ diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-video-2.m4s b/packager/app/test/testdata/dash-start-number/bear-640x360-video-2.m4s new file mode 100644 index 00000000000..4edeba974ee Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-video-2.m4s differ diff --git a/packager/app/test/testdata/dash-start-number/bear-640x360-video-init.mp4 b/packager/app/test/testdata/dash-start-number/bear-640x360-video-init.mp4 new file mode 100644 index 00000000000..874b16341f7 Binary files /dev/null and b/packager/app/test/testdata/dash-start-number/bear-640x360-video-init.mp4 differ diff --git a/packager/app/test/testdata/dash-start-number/output.mpd b/packager/app/test/testdata/dash-start-number/output.mpd new file mode 100644 index 00000000000..81965d0568d --- /dev/null +++ b/packager/app/test/testdata/dash-start-number/output.mpd @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/output.m3u8 b/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/output.m3u8 new file mode 100644 index 00000000000..d9bf5154cca --- /dev/null +++ b/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/output.m3u8 @@ -0,0 +1,7 @@ +#EXTM3U +## Generated with https://github.com/shaka-project/shaka-packager version -- + +#EXT-X-INDEPENDENT-SEGMENTS + +#EXT-X-STREAM-INF:BANDWIDTH=550702,AVERAGE-BANDWIDTH=577484,CODECS="av01.0.04M.10.0.111.09.16.09.0",SUPPLEMENTAL-CODECS="dav1.10.01/db1p",RESOLUTION=640x360,FRAME-RATE=59.940,VIDEO-RANGE=PQ,CLOSED-CAPTIONS=NONE +stream_0.m3u8 diff --git a/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/output.mpd b/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/output.mpd new file mode 100644 index 00000000000..2a43ee4e413 --- /dev/null +++ b/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/output.mpd @@ -0,0 +1,17 @@ + + + + + + + + + + sparks_dovi_10-video.mp4 + + + + + + + diff --git a/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/sparks_dovi_10-video.mp4 b/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/sparks_dovi_10-video.mp4 new file mode 100644 index 00000000000..b4cd29e4ab8 Binary files /dev/null and b/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/sparks_dovi_10-video.mp4 differ diff --git a/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/stream_0.m3u8 b/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/stream_0.m3u8 new file mode 100644 index 00000000000..06ba5a56fc1 --- /dev/null +++ b/packager/app/test/testdata/dolby-vision-profile-10-supplemental-codecs/stream_0.m3u8 @@ -0,0 +1,13 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:6 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="sparks_dovi_10-video.mp4",BYTERANGE="871@0" +#EXTINF:5.355, +#EXT-X-BYTERANGE:368650@927 +sparks_dovi_10-video.mp4 +#EXTINF:0.667, +#EXT-X-BYTERANGE:66100 +sparks_dovi_10-video.mp4 +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/dolby-vision-profile-5-with-encryption/output.mpd b/packager/app/test/testdata/dolby-vision-profile-5-with-encryption/output.mpd index 300d51637a4..da522e3591e 100644 --- a/packager/app/test/testdata/dolby-vision-profile-5-with-encryption/output.mpd +++ b/packager/app/test/testdata/dolby-vision-profile-5-with-encryption/output.mpd @@ -3,6 +3,8 @@ + + diff --git a/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/output.m3u8 b/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/output.m3u8 new file mode 100644 index 00000000000..c18631c4c68 --- /dev/null +++ b/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/output.m3u8 @@ -0,0 +1,7 @@ +#EXTM3U +## Generated with https://github.com/shaka-project/shaka-packager version -- + +#EXT-X-INDEPENDENT-SEGMENTS + +#EXT-X-STREAM-INF:BANDWIDTH=807837,AVERAGE-BANDWIDTH=748074,CODECS="hvc1.2.4.L90.90",SUPPLEMENTAL-CODECS="dvh1.08.01/db2g",RESOLUTION=640x360,FRAME-RATE=59.940,VIDEO-RANGE=PQ,CLOSED-CAPTIONS=NONE +stream_0.m3u8 diff --git a/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/output.mpd b/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/output.mpd new file mode 100644 index 00000000000..5d06e41c784 --- /dev/null +++ b/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/output.mpd @@ -0,0 +1,17 @@ + + + + + + + + + + sparks_dovi_8-video.mp4 + + + + + + + diff --git a/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/sparks_dovi_8-video.mp4 b/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/sparks_dovi_8-video.mp4 new file mode 100644 index 00000000000..ce05290a78d Binary files /dev/null and b/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/sparks_dovi_8-video.mp4 differ diff --git a/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/stream_0.m3u8 b/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/stream_0.m3u8 new file mode 100644 index 00000000000..6b90ac6a7e6 --- /dev/null +++ b/packager/app/test/testdata/dolby-vision-profile-8-supplemental-codecs/stream_0.m3u8 @@ -0,0 +1,19 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/shaka-project/shaka-packager version -- +#EXT-X-TARGETDURATION:3 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="sparks_dovi_8-video.mp4",BYTERANGE="992@0" +#EXTINF:2.002, +#EXT-X-BYTERANGE:172013@1072 +sparks_dovi_8-video.mp4 +#EXTINF:2.002, +#EXT-X-BYTERANGE:186781 +sparks_dovi_8-video.mp4 +#EXTINF:2.002, +#EXT-X-BYTERANGE:202161 +sparks_dovi_8-video.mp4 +#EXTINF:0.017, +#EXT-X-BYTERANGE:2221 +sparks_dovi_8-video.mp4 +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/dolby-vision-profile-8-with-encryption/output.mpd b/packager/app/test/testdata/dolby-vision-profile-8-with-encryption/output.mpd index 0acbe5ca43c..7f5da8fad93 100644 --- a/packager/app/test/testdata/dolby-vision-profile-8-with-encryption/output.mpd +++ b/packager/app/test/testdata/dolby-vision-profile-8-with-encryption/output.mpd @@ -3,6 +3,8 @@ + + @@ -16,6 +18,8 @@ + + diff --git a/packager/app/test/testdata/encryption-and-output-media-info-and-mpd-from-media-info/bear-640x360-video.mp4.media_info b/packager/app/test/testdata/encryption-and-output-media-info-and-mpd-from-media-info/bear-640x360-video.mp4.media_info index f13a0e9fa89..e9054215f7d 100644 --- a/packager/app/test/testdata/encryption-and-output-media-info-and-mpd-from-media-info/bear-640x360-video.mp4.media_info +++ b/packager/app/test/testdata/encryption-and-output-media-info-and-mpd-from-media-info/bear-640x360-video.mp4.media_info @@ -8,6 +8,8 @@ video_info { decoder_config: "\001d\000\036\377\341\000\031gd\000\036\254\331@\240/\371p\021\000\000\003\003\351\000\000\352`\017\026-\226\001\000\006h\353\343\313\"\300" pixel_width: 1 pixel_height: 1 + supplemental_codec: "" + compatible_brand: 0 } init_range { begin: 0 diff --git a/packager/app/test/testdata/encryption-and-output-media-info/bear-640x360-video.mp4.media_info b/packager/app/test/testdata/encryption-and-output-media-info/bear-640x360-video.mp4.media_info index f13a0e9fa89..e9054215f7d 100644 --- a/packager/app/test/testdata/encryption-and-output-media-info/bear-640x360-video.mp4.media_info +++ b/packager/app/test/testdata/encryption-and-output-media-info/bear-640x360-video.mp4.media_info @@ -8,6 +8,8 @@ video_info { decoder_config: "\001d\000\036\377\341\000\031gd\000\036\254\331@\240/\371p\021\000\000\003\003\351\000\000\352`\017\026-\226\001\000\006h\353\343\313\"\300" pixel_width: 1 pixel_height: 1 + supplemental_codec: "" + compatible_brand: 0 } init_range { begin: 0 diff --git a/packager/app/test/testdata/encryption-and-trick-play/output.mpd b/packager/app/test/testdata/encryption-and-trick-play/output.mpd index 4d289f04421..5c00589ac23 100644 --- a/packager/app/test/testdata/encryption-and-trick-play/output.mpd +++ b/packager/app/test/testdata/encryption-and-trick-play/output.mpd @@ -32,7 +32,7 @@ AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - + bear-640x360-video-trick_play_factor_1.mp4 diff --git a/packager/app/test/testdata/encryption-and-two-trick-plays/output.mpd b/packager/app/test/testdata/encryption-and-two-trick-plays/output.mpd index 88e1a8ef0cf..910f1375c72 100644 --- a/packager/app/test/testdata/encryption-and-two-trick-plays/output.mpd +++ b/packager/app/test/testdata/encryption-and-two-trick-plays/output.mpd @@ -32,7 +32,7 @@ AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== - + bear-640x360-video-trick_play_factor_1.mp4 diff --git a/packager/app/test/testdata/hdr10-with-encryption/output.mpd b/packager/app/test/testdata/hdr10-with-encryption/output.mpd index af2733fe848..3eaf56c203d 100644 --- a/packager/app/test/testdata/hdr10-with-encryption/output.mpd +++ b/packager/app/test/testdata/hdr10-with-encryption/output.mpd @@ -3,6 +3,8 @@ + + diff --git a/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-1.m4s b/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-1.m4s index 1de1cd08d37..319e9a7cddc 100644 Binary files a/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-1.m4s and b/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-1.m4s differ diff --git a/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-2.m4s b/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-2.m4s index 29351536a0b..ac847dfc255 100644 Binary files a/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-2.m4s and b/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-2.m4s differ diff --git a/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-3.m4s b/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-3.m4s index 8b81e6d983d..db5c1dd6f6b 100644 Binary files a/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-3.m4s and b/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-3.m4s differ diff --git a/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-4.m4s b/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-4.m4s index 9c86b0e5e2e..32792d0cfcd 100644 Binary files a/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-4.m4s and b/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-4.m4s differ diff --git a/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-5.m4s b/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-5.m4s index e0dc6e3b8d4..224ccd2c68c 100644 Binary files a/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-5.m4s and b/packager/app/test/testdata/segmented-ttml-mp4/bear-english-text-5.m4s differ diff --git a/packager/app/test/testdata/segmented-ttml-mp4/output.mpd b/packager/app/test/testdata/segmented-ttml-mp4/output.mpd index 35ddebf4a19..bc8c0c7591c 100644 --- a/packager/app/test/testdata/segmented-ttml-mp4/output.mpd +++ b/packager/app/test/testdata/segmented-ttml-mp4/output.mpd @@ -4,7 +4,7 @@ - + diff --git a/packager/app/test/testdata/segmented-ttml-text/bear-english-text-1.ttml b/packager/app/test/testdata/segmented-ttml-text/bear-english-text-1.ttml index 94a5092c9d3..2bd77c0beda 100644 --- a/packager/app/test/testdata/segmented-ttml-text/bear-english-text-1.ttml +++ b/packager/app/test/testdata/segmented-ttml-text/bear-english-text-1.ttml @@ -1,6 +1,10 @@ - + + + + +

Yup, that's a bear, eh.

diff --git a/packager/app/test/testdata/segmented-ttml-text/bear-english-text-2.ttml b/packager/app/test/testdata/segmented-ttml-text/bear-english-text-2.ttml index 8048787ec79..10a7664c862 100644 --- a/packager/app/test/testdata/segmented-ttml-text/bear-english-text-2.ttml +++ b/packager/app/test/testdata/segmented-ttml-text/bear-english-text-2.ttml @@ -1,6 +1,10 @@ - + + + + +

He 's... um... doing bear-like stuff.

diff --git a/packager/app/test/testdata/segmented-ttml-text/bear-english-text-3.ttml b/packager/app/test/testdata/segmented-ttml-text/bear-english-text-3.ttml index 8048787ec79..10a7664c862 100644 --- a/packager/app/test/testdata/segmented-ttml-text/bear-english-text-3.ttml +++ b/packager/app/test/testdata/segmented-ttml-text/bear-english-text-3.ttml @@ -1,6 +1,10 @@ - + + + + +

He 's... um... doing bear-like stuff.

diff --git a/packager/app/test/testdata/segmented-ttml-text/bear-english-text-4.ttml b/packager/app/test/testdata/segmented-ttml-text/bear-english-text-4.ttml index 8048787ec79..10a7664c862 100644 --- a/packager/app/test/testdata/segmented-ttml-text/bear-english-text-4.ttml +++ b/packager/app/test/testdata/segmented-ttml-text/bear-english-text-4.ttml @@ -1,6 +1,10 @@ - + + + + +

He 's... um... doing bear-like stuff.

diff --git a/packager/app/test/testdata/segmented-ttml-text/bear-english-text-5.ttml b/packager/app/test/testdata/segmented-ttml-text/bear-english-text-5.ttml index 8048787ec79..10a7664c862 100644 --- a/packager/app/test/testdata/segmented-ttml-text/bear-english-text-5.ttml +++ b/packager/app/test/testdata/segmented-ttml-text/bear-english-text-5.ttml @@ -1,6 +1,10 @@ - + + + + +

He 's... um... doing bear-like stuff.

diff --git a/packager/app/test/testdata/segmented-ttml-text/output.mpd b/packager/app/test/testdata/segmented-ttml-text/output.mpd index 9b9e87e2ed3..398aeeca607 100644 --- a/packager/app/test/testdata/segmented-ttml-text/output.mpd +++ b/packager/app/test/testdata/segmented-ttml-text/output.mpd @@ -4,7 +4,7 @@ - + diff --git a/packager/hls/base/master_playlist.cc b/packager/hls/base/master_playlist.cc index 9a89ac8bdb3..f6680f6aa67 100644 --- a/packager/hls/base/master_playlist.cc +++ b/packager/hls/base/master_playlist.cc @@ -236,6 +236,15 @@ void BuildStreamInfTag(const MediaPlaylist& playlist, variant.text_codecs.end()); tag.AddQuotedString("CODECS", absl::StrJoin(all_codecs, ",")); + if (playlist.supplemental_codec() != "" && + playlist.compatible_brand() != media::FOURCC_NULL) { + std::vector supplemental_codecs; + supplemental_codecs.push_back(playlist.supplemental_codec()); + supplemental_codecs.push_back(FourCCToString(playlist.compatible_brand())); + tag.AddQuotedString("SUPPLEMENTAL-CODECS", + absl::StrJoin(supplemental_codecs, "/")); + } + uint32_t width; uint32_t height; if (playlist.GetDisplayResolution(&width, &height)) { diff --git a/packager/hls/base/media_playlist.cc b/packager/hls/base/media_playlist.cc index 3dd79e32657..b480c0e2ce6 100644 --- a/packager/hls/base/media_playlist.cc +++ b/packager/hls/base/media_playlist.cc @@ -393,6 +393,13 @@ bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) { if (media_info.has_video_info()) { stream_type_ = MediaPlaylistStreamType::kVideo; codec_ = AdjustVideoCodec(media_info.video_info().codec()); + if (media_info.video_info().has_supplemental_codec() && + media_info.video_info().has_compatible_brand()) { + supplemental_codec_ = + AdjustVideoCodec(media_info.video_info().supplemental_codec()); + compatible_brand_ = static_cast( + media_info.video_info().compatible_brand()); + } } else if (media_info.has_audio_info()) { stream_type_ = MediaPlaylistStreamType::kAudio; codec_ = media_info.audio_info().codec(); @@ -576,10 +583,23 @@ std::string MediaPlaylist::GetVideoRange() const { // https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-02#section-4.4.4.2 switch (media_info_.video_info().transfer_characteristics()) { case 1: + case 6: + case 13: + case 14: + // Dolby Vision profile 8.4 may have a transfer_characteristics 14, the + // actual value refers to preferred_transfer_characteristic value in SEI + // message, using compatible brand as a workaround + if (!supplemental_codec_.empty() && + compatible_brand_ == media::FOURCC_db4g) + return "HLG"; + else + return "SDR"; + case 15: return "SDR"; case 16: - case 18: return "PQ"; + case 18: + return "HLG"; default: // Leave it empty if we do not have the transfer characteristics // information. @@ -737,9 +757,9 @@ void MediaPlaylist::RemoveOldSegment(int64_t start_time) { if (stream_type_ == MediaPlaylistStreamType::kVideoIFramesOnly) return; - segments_to_be_removed_.push_back( - media::GetSegmentName(media_info_.segment_template(), start_time, - media_sequence_number_, media_info_.bandwidth())); + segments_to_be_removed_.push_back(media::GetSegmentName( + media_info_.segment_template(), start_time, media_sequence_number_ + 1, + media_info_.bandwidth())); while (segments_to_be_removed_.size() > hls_params_.preserved_segments_outside_live_window) { VLOG(2) << "Deleting " << segments_to_be_removed_.front(); diff --git a/packager/hls/base/media_playlist.h b/packager/hls/base/media_playlist.h index b2a97c10698..75127ccf7c1 100644 --- a/packager/hls/base/media_playlist.h +++ b/packager/hls/base/media_playlist.h @@ -17,6 +17,7 @@ #include #include #include +#include "packager/media/base/fourccs.h" namespace shaka { @@ -80,6 +81,8 @@ class MediaPlaylist { const std::string& group_id() const { return group_id_; } MediaPlaylistStreamType stream_type() const { return stream_type_; } const std::string& codec() const { return codec_; } + const std::string& supplemental_codec() const { return supplemental_codec_; } + const media::FourCC& compatible_brand() const { return compatible_brand_; } /// For testing only. void SetStreamTypeForTesting(MediaPlaylistStreamType stream_type); @@ -265,6 +268,8 @@ class MediaPlaylist { // Whether to use byte range for SegmentInfoEntry. bool use_byte_range_ = false; std::string codec_; + std::string supplemental_codec_; + media::FourCC compatible_brand_; std::string language_; std::vector characteristics_; bool forced_subtitle_ = false; diff --git a/packager/hls/base/media_playlist_unittest.cc b/packager/hls/base/media_playlist_unittest.cc index d92190d0ca4..7b17b412e2b 100644 --- a/packager/hls/base/media_playlist_unittest.cc +++ b/packager/hls/base/media_playlist_unittest.cc @@ -1227,7 +1227,7 @@ INSTANTIATE_TEST_CASE_P(VideoRanges, Values(VideoRangeTestData{"hvc1.2.4.L63.90", 0, ""}, VideoRangeTestData{"hvc1.2.4.L63.90", 1, "SDR"}, VideoRangeTestData{"hvc1.2.4.L63.90", 16, "PQ"}, - VideoRangeTestData{"hvc1.2.4.L63.90", 18, "PQ"}, + VideoRangeTestData{"hvc1.2.4.L63.90", 18, "HLG"}, VideoRangeTestData{"dvh1.05.08", 0, "PQ"})); } // namespace hls diff --git a/packager/media/base/fourccs.h b/packager/media/base/fourccs.h index 4c85c02c105..335ca3723f0 100644 --- a/packager/media/base/fourccs.h +++ b/packager/media/base/fourccs.h @@ -47,6 +47,11 @@ enum FourCC : uint32_t { FOURCC_dac3 = 0x64616333, FOURCC_dac4 = 0x64616334, FOURCC_dash = 0x64617368, + FOURCC_dav1 = 0x64617631, + FOURCC_db1p = 0x64623170, + FOURCC_db2g = 0x64623267, + FOURCC_db4g = 0x64623467, + FOURCC_db4h = 0x64623468, FOURCC_dby1 = 0x64627931, FOURCC_ddts = 0x64647473, FOURCC_dec3 = 0x64656333, diff --git a/packager/media/base/media_handler.h b/packager/media/base/media_handler.h index e2975ad52e5..9608145a2b8 100644 --- a/packager/media/base/media_handler.h +++ b/packager/media/base/media_handler.h @@ -59,6 +59,7 @@ struct SegmentInfo { bool is_encrypted = false; int64_t start_timestamp = -1; int64_t duration = 0; + int64_t segment_number = 1; // This is only available if key rotation is enabled. Note that we may have // a |key_rotation_encryption_config| even if the segment is not encrypted, // which is the case for clear lead. diff --git a/packager/media/base/media_handler_test_base.cc b/packager/media/base/media_handler_test_base.cc index 0a5b28e91c3..a9c78f91009 100644 --- a/packager/media/base/media_handler_test_base.cc +++ b/packager/media/base/media_handler_test_base.cc @@ -31,7 +31,10 @@ const uint32_t kWidth = 10u; const uint32_t kHeight = 20u; const uint32_t kPixelWidth = 2u; const uint32_t kPixelHeight = 3u; +const uint8_t kColorPrimaries = 0; +const uint8_t kMatrixCoefficients = 0; const uint8_t kTransferCharacteristics = 0; + const int16_t kTrickPlayFactor = 0; const uint8_t kNaluLengthSize = 1u; const bool kEncrypted = true; @@ -207,8 +210,9 @@ std::unique_ptr MediaHandlerTestBase::GetVideoStreamInfo( return std::unique_ptr(new VideoStreamInfo( kTrackId, time_scale, kDuration, codec, H26xStreamFormat::kUnSpecified, kCodecString, kCodecConfig, sizeof(kCodecConfig), width, height, - kPixelWidth, kPixelHeight, kTransferCharacteristics, kTrickPlayFactor, - kNaluLengthSize, kLanguage, !kEncrypted)); + kPixelWidth, kPixelHeight, kColorPrimaries, kMatrixCoefficients, + kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, + !kEncrypted)); } std::unique_ptr MediaHandlerTestBase::GetAudioStreamInfo( @@ -251,11 +255,13 @@ std::shared_ptr MediaHandlerTestBase::GetMediaSample( std::unique_ptr MediaHandlerTestBase::GetSegmentInfo( int64_t start_timestamp, int64_t duration, - bool is_subsegment) const { + bool is_subsegment, + int64_t segment_number) const { std::unique_ptr info(new SegmentInfo); info->start_timestamp = start_timestamp; info->duration = duration; info->is_subsegment = is_subsegment; + info->segment_number = segment_number; return info; } diff --git a/packager/media/base/media_handler_test_base.h b/packager/media/base/media_handler_test_base.h index 980948a5e4a..6b5cf3c2741 100644 --- a/packager/media/base/media_handler_test_base.h +++ b/packager/media/base/media_handler_test_base.h @@ -325,7 +325,8 @@ class MediaHandlerTestBase : public ::testing::Test { std::unique_ptr GetSegmentInfo(int64_t start_timestamp, int64_t duration, - bool is_subsegment) const; + bool is_subsegment, + int64_t segment_number) const; std::unique_ptr GetTextStreamInfo(int32_t timescale) const; diff --git a/packager/media/base/muxer.h b/packager/media/base/muxer.h index 6a622172ae1..9e997623834 100644 --- a/packager/media/base/muxer.h +++ b/packager/media/base/muxer.h @@ -117,7 +117,7 @@ class Muxer : public MediaHandler { // In VOD single segment case with Ad Cues, |output_file_name| is allowed to // be a template. In this case, there will be NumAdCues + 1 files generated. std::string output_file_template_; - size_t output_file_index_ = 0; + size_t output_file_index_ = 1; }; } // namespace media diff --git a/packager/media/base/muxer_util.cc b/packager/media/base/muxer_util.cc index bd6028da6f7..a928d868f9e 100644 --- a/packager/media/base/muxer_util.cc +++ b/packager/media/base/muxer_util.cc @@ -110,7 +110,7 @@ Status ValidateSegmentTemplate(const std::string& segment_template) { std::string GetSegmentName(const std::string& segment_template, int64_t segment_start_time, - uint32_t segment_index, + uint32_t segment_number, uint32_t bandwidth) { DCHECK_EQ(Status::OK, ValidateSegmentTemplate(segment_template)); @@ -154,7 +154,7 @@ std::string GetSegmentName(const std::string& segment_template, absl::UntypedFormatSpec format(format_tag); if (identifier == "Number") { // SegmentNumber starts from 1. - format_args.emplace_back(static_cast(segment_index + 1)); + format_args.emplace_back(static_cast(segment_number)); } else if (identifier == "Time") { format_args.emplace_back(static_cast(segment_start_time)); } else if (identifier == "Bandwidth") { diff --git a/packager/media/base/muxer_util.h b/packager/media/base/muxer_util.h index 02473ecb511..7cf797c0226 100644 --- a/packager/media/base/muxer_util.h +++ b/packager/media/base/muxer_util.h @@ -29,12 +29,12 @@ Status ValidateSegmentTemplate(const std::string& segment_template); /// @param segment_template is the segment template pattern, which should /// comply with ISO/IEC 23009-1:2012 5.3.9.4.4. /// @param segment_start_time specifies the segment start time. -/// @param segment_index specifies the segment index. +/// @param segment_number specifies the segment number. /// @param bandwidth represents the bit rate, in bits/sec, of the stream. /// @return The segment name with identifier substituted. std::string GetSegmentName(const std::string& segment_template, int64_t segment_start_time, - uint32_t segment_index, + uint32_t segment_number, uint32_t bandwidth); } // namespace media diff --git a/packager/media/base/muxer_util_unittest.cc b/packager/media/base/muxer_util_unittest.cc index 0f410ff8851..58e58122b49 100644 --- a/packager/media/base/muxer_util_unittest.cc +++ b/packager/media/base/muxer_util_unittest.cc @@ -62,95 +62,57 @@ TEST(MuxerUtilTest, ValidateSegmentTemplateWithFormatTag) { TEST(MuxerUtilTest, GetSegmentName) { const int64_t kSegmentStartTime = 180180; - const uint32_t kSegmentIndex = 11; + const uint32_t kSegmentNumber = 12; const uint32_t kBandwidth = 1234; - EXPECT_EQ("12", GetSegmentName("$Number$", - kSegmentStartTime, - kSegmentIndex, + EXPECT_EQ("12", GetSegmentName("$Number$", kSegmentStartTime, kSegmentNumber, kBandwidth)); - EXPECT_EQ("012", - GetSegmentName("$Number%03d$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); - EXPECT_EQ( - "12$foo$00012", - GetSegmentName( - "$Number%01d$$$foo$$$Number%05d$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); - - EXPECT_EQ("180180", - GetSegmentName("$Time$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + EXPECT_EQ("012", GetSegmentName("$Number%03d$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); + EXPECT_EQ("12$foo$00012", + GetSegmentName("$Number%01d$$$foo$$$Number%05d$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); + + EXPECT_EQ("180180", GetSegmentName("$Time$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); EXPECT_EQ("foo$_$18018000180180.m4s", GetSegmentName("foo$$_$$$Time%01d$$Time%08d$.m4s", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + kSegmentStartTime, kSegmentNumber, kBandwidth)); // Combo values. - EXPECT_EQ("12-1234", - GetSegmentName("$Number$-$Bandwidth$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + EXPECT_EQ("12-1234", GetSegmentName("$Number$-$Bandwidth$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); EXPECT_EQ("012-001234", - GetSegmentName("$Number%03d$-$Bandwidth%06d$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + GetSegmentName("$Number%03d$-$Bandwidth%06d$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); // Format specifier edge cases. - EXPECT_EQ("12", - GetSegmentName("$Number%00d$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); - EXPECT_EQ("00012", - GetSegmentName("$Number%005d$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + EXPECT_EQ("12", GetSegmentName("$Number%00d$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); + EXPECT_EQ("00012", GetSegmentName("$Number%005d$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); } TEST(MuxerUtilTest, GetSegmentNameWithIndexZero) { const int64_t kSegmentStartTime = 0; - const uint32_t kSegmentIndex = 0; + const uint32_t kSegmentNumber = 1; const uint32_t kBandwidth = 0; - EXPECT_EQ("1", GetSegmentName("$Number$", - kSegmentStartTime, - kSegmentIndex, + EXPECT_EQ("1", GetSegmentName("$Number$", kSegmentStartTime, kSegmentNumber, kBandwidth)); - EXPECT_EQ("001", - GetSegmentName("$Number%03d$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); - - EXPECT_EQ("0", GetSegmentName("$Time$", - kSegmentStartTime, - kSegmentIndex, + EXPECT_EQ("001", GetSegmentName("$Number%03d$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); + + EXPECT_EQ("0", GetSegmentName("$Time$", kSegmentStartTime, kSegmentNumber, kBandwidth)); - EXPECT_EQ("00000000.m4s", - GetSegmentName("$Time%08d$.m4s", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + EXPECT_EQ("00000000.m4s", GetSegmentName("$Time%08d$.m4s", kSegmentStartTime, + kSegmentNumber, kBandwidth)); } TEST(MuxerUtilTest, GetSegmentNameLargeTime) { const int64_t kSegmentStartTime = 1601599839840ULL; - const uint32_t kSegmentIndex = 8888888; + const uint32_t kSegmentNumber = 8888889; const uint32_t kBandwidth = 444444; - EXPECT_EQ("1601599839840", - GetSegmentName("$Time$", - kSegmentStartTime, - kSegmentIndex, - kBandwidth)); + EXPECT_EQ("1601599839840", GetSegmentName("$Time$", kSegmentStartTime, + kSegmentNumber, kBandwidth)); } } // namespace media diff --git a/packager/media/base/text_muxer.cc b/packager/media/base/text_muxer.cc index 70c967423c5..e6e07dcab1c 100644 --- a/packager/media/base/text_muxer.cc +++ b/packager/media/base/text_muxer.cc @@ -50,9 +50,13 @@ Status TextMuxer::Finalize() { // Insert a dummy value so the HLS generator will generate a segment list. ranges.subsegment_ranges.emplace_back(); + // The segment number does not matter for single segment output. + const uint32_t kArbitrarySegmentNumber = 0; + muxer_listener()->OnNewSegment( options().output_file_name, 0, - duration_seconds * streams()[0]->time_scale(), size); + duration_seconds * streams()[0]->time_scale(), size, + kArbitrarySegmentNumber); } muxer_listener()->OnMediaEnd(ranges, duration_seconds); @@ -82,17 +86,20 @@ Status TextMuxer::FinalizeSegment(size_t stream_id, const std::string& segment_template = options().segment_template; DCHECK(!segment_template.empty()); - const uint32_t index = segment_index_++; + const int64_t start = segment_info.start_timestamp; const int64_t duration = segment_info.duration; + const uint32_t segment_number = segment_info.segment_number; + const uint32_t bandwidth = options().bandwidth; const std::string filename = - GetSegmentName(segment_template, start, index, bandwidth); + GetSegmentName(segment_template, start, segment_number, bandwidth); uint64_t size; RETURN_IF_ERROR(WriteToFile(filename, &size)); - muxer_listener()->OnNewSegment(filename, start, duration, size); + muxer_listener()->OnNewSegment(filename, start, duration, size, + segment_number); return Status::OK; } diff --git a/packager/media/base/text_muxer.h b/packager/media/base/text_muxer.h index 0b4c9be58de..68ad1bd18f5 100644 --- a/packager/media/base/text_muxer.h +++ b/packager/media/base/text_muxer.h @@ -38,7 +38,6 @@ class TextMuxer : public Muxer { int64_t total_duration_ms_ = 0; int64_t last_cue_ms_ = 0; - uint32_t segment_index_ = 0; }; } // namespace media diff --git a/packager/media/base/text_sample.h b/packager/media/base/text_sample.h index 81f78eac676..22b46479842 100644 --- a/packager/media/base/text_sample.h +++ b/packager/media/base/text_sample.h @@ -80,6 +80,11 @@ struct TextFragmentStyle { std::optional underline; std::optional bold; std::optional italic; + // The colors could be any string that can be interpreted as + // a color in TTML (or WebVTT). As a start, the 8 teletext colors are used, + // i.e. black, red, green, yellow, blue, magenta, cyan, and white + std::string color; + std::string backgroundColor; }; /// Represents a recursive structure of styled blocks of text. Only one of diff --git a/packager/media/base/video_stream_info.cc b/packager/media/base/video_stream_info.cc index 1512ce83c2d..b557482bf2e 100644 --- a/packager/media/base/video_stream_info.cc +++ b/packager/media/base/video_stream_info.cc @@ -50,6 +50,8 @@ VideoStreamInfo::VideoStreamInfo(int track_id, uint32_t height, uint32_t pixel_width, uint32_t pixel_height, + uint8_t color_primaries, + uint8_t matrix_coefficients, uint8_t transfer_characteristics, uint32_t trick_play_factor, uint8_t nalu_length_size, @@ -71,6 +73,8 @@ VideoStreamInfo::VideoStreamInfo(int track_id, pixel_width_(pixel_width), pixel_height_(pixel_height), transfer_characteristics_(transfer_characteristics), + color_primaries_(color_primaries), + matrix_coefficients_(matrix_coefficients), trick_play_factor_(trick_play_factor), nalu_length_size_(nalu_length_size) {} diff --git a/packager/media/base/video_stream_info.h b/packager/media/base/video_stream_info.h index 5e6f24345d7..8f5e705ee4f 100644 --- a/packager/media/base/video_stream_info.h +++ b/packager/media/base/video_stream_info.h @@ -39,6 +39,8 @@ class VideoStreamInfo : public StreamInfo { uint32_t height, uint32_t pixel_width, uint32_t pixel_height, + uint8_t color_primaries, + uint8_t matrix_coefficients, uint8_t transfer_characteristics, uint32_t trick_play_factor, uint8_t nalu_length_size, @@ -54,6 +56,8 @@ class VideoStreamInfo : public StreamInfo { std::unique_ptr Clone() const override; /// @} + const std::string supplemental_codec() const { return supplemental_codec_; } + FourCC compatible_brand() const { return compatible_brand_; } const std::vector& extra_config() const { return extra_config_; } H26xStreamFormat h26x_stream_format() const { return h26x_stream_format_; } uint32_t width() const { return width_; } @@ -65,12 +69,22 @@ class VideoStreamInfo : public StreamInfo { /// @return 0 if unknown. uint32_t pixel_height() const { return pixel_height_; } uint8_t transfer_characteristics() const { return transfer_characteristics_; } + uint8_t color_primaries() const { return color_primaries_; } + uint8_t matrix_coefficients() const { return matrix_coefficients_; } uint8_t nalu_length_size() const { return nalu_length_size_; } uint32_t trick_play_factor() const { return trick_play_factor_; } uint32_t playback_rate() const { return playback_rate_; } const std::vector& eme_init_data() const { return eme_init_data_; } const std::vector& colr_data() const { return colr_data_; } + void set_supplemental_codec(const std::string supplemental_codec) { + supplemental_codec_ = supplemental_codec; + } + + void set_compatible_brand(const FourCC compatible_brand) { + compatible_brand_ = compatible_brand; + } + void set_extra_config(const std::vector& extra_config) { extra_config_ = extra_config; } @@ -81,6 +95,12 @@ class VideoStreamInfo : public StreamInfo { void set_transfer_characteristics(uint8_t transfer_characteristics) { transfer_characteristics_ = transfer_characteristics; } + void set_color_primaries(uint8_t color_primaries) { + color_primaries_ = color_primaries; + } + void set_matrix_coefficients(uint8_t matrix_coefficients) { + matrix_coefficients_ = matrix_coefficients; + } void set_trick_play_factor(uint32_t trick_play_factor) { trick_play_factor_ = trick_play_factor; } @@ -98,6 +118,8 @@ class VideoStreamInfo : public StreamInfo { private: // Extra codec configuration in a stream of mp4 boxes. It is only applicable // to mp4 container only. It is needed by some codecs, e.g. Dolby Vision. + std::string supplemental_codec_ = ""; + FourCC compatible_brand_ = FOURCC_NULL; std::vector extra_config_; H26xStreamFormat h26x_stream_format_; uint32_t width_; @@ -108,6 +130,8 @@ class VideoStreamInfo : public StreamInfo { uint32_t pixel_width_; uint32_t pixel_height_; uint8_t transfer_characteristics_ = 0; + uint8_t color_primaries_ = 0; + uint8_t matrix_coefficients_ = 0; uint32_t trick_play_factor_ = 0; // Non-zero for trick-play streams. // Playback rate is the attribute for trick play stream, which signals the diff --git a/packager/media/chunking/chunking_handler.cc b/packager/media/chunking/chunking_handler.cc index bcabf961274..388683a8678 100644 --- a/packager/media/chunking/chunking_handler.cc +++ b/packager/media/chunking/chunking_handler.cc @@ -34,6 +34,7 @@ bool IsNewSegmentIndex(int64_t new_index, int64_t current_index) { ChunkingHandler::ChunkingHandler(const ChunkingParams& chunking_params) : chunking_params_(chunking_params) { CHECK_NE(chunking_params.segment_duration_in_seconds, 0u); + segment_number_ = chunking_params.start_segment_number; } Status ChunkingHandler::InitializeInternal() { @@ -163,17 +164,20 @@ Status ChunkingHandler::OnMediaSample( return DispatchMediaSample(kStreamIndex, std::move(sample)); } -Status ChunkingHandler::EndSegmentIfStarted() const { +Status ChunkingHandler::EndSegmentIfStarted() { if (!segment_start_time_) return Status::OK; auto segment_info = std::make_shared(); segment_info->start_timestamp = segment_start_time_.value(); segment_info->duration = max_segment_time_ - segment_start_time_.value(); + segment_info->segment_number = segment_number_++; + if (chunking_params_.low_latency_dash_mode) { segment_info->is_chunk = true; segment_info->is_final_chunk_in_seg = true; } + return DispatchSegmentInfo(kStreamIndex, std::move(segment_info)); } diff --git a/packager/media/chunking/chunking_handler.h b/packager/media/chunking/chunking_handler.h index 717ccec3e10..8706e2bf128 100644 --- a/packager/media/chunking/chunking_handler.h +++ b/packager/media/chunking/chunking_handler.h @@ -60,7 +60,7 @@ class ChunkingHandler : public MediaHandler { Status OnCueEvent(std::shared_ptr event); Status OnMediaSample(std::shared_ptr sample); - Status EndSegmentIfStarted() const; + Status EndSegmentIfStarted(); Status EndSubsegmentIfStarted() const; bool IsSubsegmentEnabled() { @@ -74,8 +74,13 @@ class ChunkingHandler : public MediaHandler { int64_t segment_duration_ = 0; int64_t subsegment_duration_ = 0; + // Segment number that keeps monotically increasing. + // Set to start_segment_number in constructor. + int64_t segment_number_ = 1; + // Current segment index, useful to determine where to do chunking. int64_t current_segment_index_ = -1; + // Current subsegment index, useful to determine where to do chunking. int64_t current_subsegment_index_ = -1; diff --git a/packager/media/chunking/text_chunker.cc b/packager/media/chunking/text_chunker.cc index 6e4d5760a36..2f25ac7a288 100644 --- a/packager/media/chunking/text_chunker.cc +++ b/packager/media/chunking/text_chunker.cc @@ -16,8 +16,10 @@ namespace { const size_t kStreamIndex = 0; } // namespace -TextChunker::TextChunker(double segment_duration_in_seconds) - : segment_duration_in_seconds_(segment_duration_in_seconds){}; +TextChunker::TextChunker(double segment_duration_in_seconds, + int64_t start_segment_number) + : segment_duration_in_seconds_(segment_duration_in_seconds), + segment_number_(start_segment_number){}; Status TextChunker::Process(std::unique_ptr data) { switch (data->stream_data_type) { @@ -60,14 +62,12 @@ Status TextChunker::OnCueEvent(std::shared_ptr event) { // Convert the event's time to be scaled to the time of each sample. const int64_t event_time = ScaleTime(event->time_in_seconds); - // Output all full segments before the segment that the cue event interupts. while (segment_start_ + segment_duration_ < event_time) { RETURN_IF_ERROR(DispatchSegment(segment_duration_)); } const int64_t shorten_duration = event_time - segment_start_; - RETURN_IF_ERROR(DispatchSegment(shorten_duration)); return DispatchCueEvent(kStreamIndex, std::move(event)); } @@ -109,6 +109,8 @@ Status TextChunker::DispatchSegment(int64_t duration) { std::shared_ptr info = std::make_shared(); info->start_timestamp = segment_start_; info->duration = duration; + info->segment_number = segment_number_++; + RETURN_IF_ERROR(DispatchSegmentInfo(kStreamIndex, std::move(info))); // Move onto the next segment. diff --git a/packager/media/chunking/text_chunker.h b/packager/media/chunking/text_chunker.h index e6f75c4ca67..b4e836d09dd 100644 --- a/packager/media/chunking/text_chunker.h +++ b/packager/media/chunking/text_chunker.h @@ -20,7 +20,8 @@ namespace media { // is when a cue event is seen. class TextChunker : public MediaHandler { public: - explicit TextChunker(double segment_duration_in_seconds); + explicit TextChunker(double segment_duration_in_seconds, + int64_t start_segment_number); private: TextChunker(const TextChunker&) = delete; @@ -52,6 +53,10 @@ class TextChunker : public MediaHandler { int64_t segment_start_ = -1; // Set when the first sample comes in. int64_t segment_duration_ = -1; // Set in OnStreamInfo. + // Segment number that keeps monotically increasing. + // Set to start_segment_number in constructor. + int64_t segment_number_ = 1; + // All samples that make up the current segment. We must store the samples // until the segment ends because a cue event may end the segment sooner // than we expected. diff --git a/packager/media/chunking/text_chunker_unittest.cc b/packager/media/chunking/text_chunker_unittest.cc index 69372f9d0ad..d1f4a0f0bfa 100644 --- a/packager/media/chunking/text_chunker_unittest.cc +++ b/packager/media/chunking/text_chunker_unittest.cc @@ -30,6 +30,8 @@ const size_t kOutput = 0; const bool kEncrypted = true; const bool kSubSegment = true; +const int64_t kStartSegmentNumber = 1; + const char* kNoId = ""; const char* kNoPayload = ""; } // namespace @@ -38,7 +40,8 @@ class TextChunkerTest : public MediaHandlerTestBase { protected: Status Init(double segment_duration) { return SetUpAndInitializeGraph( - std::make_shared(segment_duration), kInputs, kOutputs); + std::make_shared(segment_duration, kStartSegmentNumber), + kInputs, kOutputs); } }; diff --git a/packager/media/codecs/decoder_configuration_record.h b/packager/media/codecs/decoder_configuration_record.h index b826484e719..45221cf5281 100644 --- a/packager/media/codecs/decoder_configuration_record.h +++ b/packager/media/codecs/decoder_configuration_record.h @@ -48,6 +48,12 @@ class DecoderConfigurationRecord { /// @return Transfer characteristics of the config. uint8_t transfer_characteristics() const { return transfer_characteristics_; } + /// @return Colour Primaries of the config. + uint8_t color_primaries() const { return color_primaries_; } + + /// @return Matrix Coeffs of the config. + uint8_t matrix_coefficients() const { return matrix_coefficients_; } + protected: DecoderConfigurationRecord(); @@ -71,6 +77,15 @@ class DecoderConfigurationRecord { transfer_characteristics_ = transfer_characteristics; } + /// Sets the colour primaries. + void set_color_primaries(uint8_t color_primaries) { + color_primaries_ = color_primaries; + } + /// Sets the matrix coeffs. + void set_matrix_coefficients(uint8_t matrix_coefficients) { + matrix_coefficients_ = matrix_coefficients; + } + private: // Performs the actual parsing of the data. virtual bool ParseInternal() = 0; @@ -86,6 +101,9 @@ class DecoderConfigurationRecord { // The parameter is extracted from SPS. uint8_t transfer_characteristics_ = 0; + uint8_t color_primaries_ = 0; + uint8_t matrix_coefficients_ = 0; + DISALLOW_COPY_AND_ASSIGN(DecoderConfigurationRecord); }; diff --git a/packager/media/codecs/dovi_decoder_configuration_record.cc b/packager/media/codecs/dovi_decoder_configuration_record.cc index 4814c70a919..44d5dbfc8b9 100644 --- a/packager/media/codecs/dovi_decoder_configuration_record.cc +++ b/packager/media/codecs/dovi_decoder_configuration_record.cc @@ -23,7 +23,9 @@ bool DOVIDecoderConfigurationRecord::Parse(const std::vector& data) { uint8_t minor_version = 0; RCHECK(reader.ReadBits(8, &major_version) && major_version == 1 && reader.ReadBits(8, &minor_version) && minor_version == 0 && - reader.ReadBits(7, &profile_) && reader.ReadBits(6, &level_)); + reader.ReadBits(7, &profile_) && reader.ReadBits(6, &level_) && + reader.SkipBits(3) && + reader.ReadBits(4, &bl_signal_compatibility_id_)); return true; } @@ -35,5 +37,24 @@ std::string DOVIDecoderConfigurationRecord::GetCodecString( profile_, level_); } +FourCC DOVIDecoderConfigurationRecord::GetDoViCompatibleBrand( + const uint8_t transfer_characteristics) const { + // Dolby Vision Streams within the ISO Base Media File Format Version 2.4: + switch (bl_signal_compatibility_id_) { + case 1: + return FOURCC_db1p; + case 2: + return FOURCC_db2g; + case 4: + if (transfer_characteristics == 14) { + return FOURCC_db4g; + } + // transfer_characteristics == 18 + return FOURCC_db4h; + default: + return FOURCC_NULL; + } +} + } // namespace media } // namespace shaka diff --git a/packager/media/codecs/dovi_decoder_configuration_record.h b/packager/media/codecs/dovi_decoder_configuration_record.h index 64e9445f281..da61e8adeda 100644 --- a/packager/media/codecs/dovi_decoder_configuration_record.h +++ b/packager/media/codecs/dovi_decoder_configuration_record.h @@ -35,6 +35,10 @@ class DOVIDecoderConfigurationRecord { /// DASH and HLS manifests. std::string GetCodecString(FourCC codec_fourcc) const; + /// @return The compatiable brand in the format defined by + /// https://mp4ra.org/#/brands. + FourCC GetDoViCompatibleBrand(const uint8_t transfer_characteristics) const; + private: DOVIDecoderConfigurationRecord(const DOVIDecoderConfigurationRecord&) = delete; @@ -42,6 +46,7 @@ class DOVIDecoderConfigurationRecord { const DOVIDecoderConfigurationRecord&) = delete; uint8_t profile_ = 0; + uint8_t bl_signal_compatibility_id_ = 0; uint8_t level_ = 0; }; diff --git a/packager/media/codecs/h264_parser.cc b/packager/media/codecs/h264_parser.cc index 8b86133b1de..c25dcbb0c9d 100644 --- a/packager/media/codecs/h264_parser.cc +++ b/packager/media/codecs/h264_parser.cc @@ -519,9 +519,9 @@ H264Parser::Result H264Parser::ParseVUIParameters(H26xBitReader* br, READ_BOOL_OR_RETURN(&data); // video_full_range_flag READ_BOOL_OR_RETURN(&data); // colour_description_present_flag if (data) { - READ_BITS_OR_RETURN(8, &data); // colour primaries + READ_BITS_OR_RETURN(8, &sps->color_primaries); // colour primaries READ_BITS_OR_RETURN(8, &sps->transfer_characteristics); - READ_BITS_OR_RETURN(8, &data); // matrix coeffs + READ_BITS_OR_RETURN(8, &sps->matrix_coefficients); // matrix coeffs } } diff --git a/packager/media/codecs/h264_parser.h b/packager/media/codecs/h264_parser.h index 423fe5a40d4..2752e31a388 100644 --- a/packager/media/codecs/h264_parser.h +++ b/packager/media/codecs/h264_parser.h @@ -81,6 +81,8 @@ struct H264Sps { int sar_width; // Set to 0 when not specified. int sar_height; // Set to 0 when not specified. int transfer_characteristics; + int color_primaries; + int matrix_coefficients; bool timing_info_present_flag; long num_units_in_tick; diff --git a/packager/media/codecs/h265_parser.cc b/packager/media/codecs/h265_parser.cc index bd7f2a411ce..7c3cab7bf51 100644 --- a/packager/media/codecs/h265_parser.cc +++ b/packager/media/codecs/h265_parser.cc @@ -680,9 +680,11 @@ H265Parser::Result H265Parser::ParseVuiParameters(int max_num_sub_layers_minus1, bool colour_description_present_flag; TRUE_OR_RETURN(br->ReadBool(&colour_description_present_flag)); if (colour_description_present_flag) { - TRUE_OR_RETURN(br->SkipBits(8)); // colour_primaries + TRUE_OR_RETURN( + br->ReadBits(8, &vui->color_primaries)); // color_primaries TRUE_OR_RETURN(br->ReadBits(8, &vui->transfer_characteristics)); - TRUE_OR_RETURN(br->SkipBits(8)); // matrix_coeffs + TRUE_OR_RETURN( + br->ReadBits(8, &vui->matrix_coefficients)); // matrix_coeffs } } diff --git a/packager/media/codecs/h265_parser.h b/packager/media/codecs/h265_parser.h index a36f82e8abc..febb2e0731d 100644 --- a/packager/media/codecs/h265_parser.h +++ b/packager/media/codecs/h265_parser.h @@ -52,6 +52,8 @@ struct H265VuiParameters { int sar_width = 0; int sar_height = 0; int transfer_characteristics = 0; + int color_primaries = 0; + int matrix_coefficients = 0; bool vui_timing_info_present_flag = false; long vui_num_units_in_tick = 0; diff --git a/packager/media/codecs/hevc_decoder_configuration_record.cc b/packager/media/codecs/hevc_decoder_configuration_record.cc index 9fef0fe06e1..f171fa56fce 100644 --- a/packager/media/codecs/hevc_decoder_configuration_record.cc +++ b/packager/media/codecs/hevc_decoder_configuration_record.cc @@ -120,6 +120,10 @@ bool HEVCDecoderConfigurationRecord::ParseInternal() { RCHECK(parser.ParseSps(nalu, &sps_id) == H265Parser::kOk); set_transfer_characteristics( parser.GetSps(sps_id)->vui_parameters.transfer_characteristics); + set_color_primaries( + parser.GetSps(sps_id)->vui_parameters.color_primaries); + set_matrix_coefficients( + parser.GetSps(sps_id)->vui_parameters.matrix_coefficients); } } } diff --git a/packager/media/codecs/vp9_parser.cc b/packager/media/codecs/vp9_parser.cc index 1e2499cc82b..12fc472a90b 100644 --- a/packager/media/codecs/vp9_parser.cc +++ b/packager/media/codecs/vp9_parser.cc @@ -285,6 +285,7 @@ bool ReadBitDepthAndColorSpace(BitReader* reader, } } else { // Assume 4:4:4 for colorspace SRGB. + yuv_full_range = true; chroma_subsampling = VPCodecConfigurationRecord::CHROMA_444; if (codec_config->profile() & 1) { bool reserved; diff --git a/packager/media/crypto/encryption_handler_unittest.cc b/packager/media/crypto/encryption_handler_unittest.cc index a45790a72c1..a1833def744 100644 --- a/packager/media/crypto/encryption_handler_unittest.cc +++ b/packager/media/crypto/encryption_handler_unittest.cc @@ -357,7 +357,7 @@ TEST_P(EncryptionHandlerEncryptionTest, ClearLeadWithNoKeyRotation) { kIsKeyFrame, kData, kDataSize)))); ASSERT_OK(Process(StreamData::FromSegmentInfo( kStreamIndex, GetSegmentInfo(i * kSegmentDuration, kSegmentDuration, - !kIsSubsegment)))); + !kIsSubsegment, i + 1)))); const bool is_encrypted = i == 2; const auto& output_stream_data = GetOutputStreamDataVector(); EXPECT_THAT(output_stream_data, @@ -436,7 +436,7 @@ TEST_P(EncryptionHandlerEncryptionTest, ClearLeadWithKeyRotation) { kIsKeyFrame, kData, kDataSize)))); ASSERT_OK(Process(StreamData::FromSegmentInfo( kStreamIndex, GetSegmentInfo(i * kSegmentDuration, kSegmentDuration, - !kIsSubsegment)))); + !kIsSubsegment, i)))); const bool is_encrypted = i >= 2; const auto& output_stream_data = GetOutputStreamDataVector(); EXPECT_THAT(output_stream_data, diff --git a/packager/media/crypto/subsample_generator_unittest.cc b/packager/media/crypto/subsample_generator_unittest.cc index c384f45adf6..e12dc721166 100644 --- a/packager/media/crypto/subsample_generator_unittest.cc +++ b/packager/media/crypto/subsample_generator_unittest.cc @@ -63,6 +63,8 @@ VideoStreamInfo GetVideoStreamInfo(Codec codec) { const uint16_t kHeight = 20u; const uint32_t kPixelWidth = 2u; const uint32_t kPixelHeight = 3u; + const uint8_t kColorPrimaries = 0; + const uint8_t kMatrixCoefficients = 0; const uint8_t kTransferCharacteristics = 0; const int16_t kTrickPlayFactor = 0; const uint8_t kNaluLengthSize = 1u; @@ -85,8 +87,9 @@ VideoStreamInfo GetVideoStreamInfo(Codec codec) { return VideoStreamInfo( kTrackId, kTimeScale, kDuration, codec, H26xStreamFormat::kUnSpecified, kCodecString, codec_config, codec_config_size, kWidth, kHeight, - kPixelWidth, kPixelHeight, kTransferCharacteristics, kTrickPlayFactor, - kNaluLengthSize, kLanguage, !kEncrypted); + kPixelWidth, kPixelHeight, kColorPrimaries, kMatrixCoefficients, + kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, + !kEncrypted); } AudioStreamInfo GetAudioStreamInfo(Codec codec) { diff --git a/packager/media/event/combined_muxer_listener.cc b/packager/media/event/combined_muxer_listener.cc index a1135655198..76381b7c36b 100644 --- a/packager/media/event/combined_muxer_listener.cc +++ b/packager/media/event/combined_muxer_listener.cc @@ -71,9 +71,11 @@ void CombinedMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, void CombinedMuxerListener::OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) { + uint64_t segment_file_size, + int64_t segment_number) { for (auto& listener : muxer_listeners_) { - listener->OnNewSegment(file_name, start_time, duration, segment_file_size); + listener->OnNewSegment(file_name, start_time, duration, segment_file_size, + segment_number); } } diff --git a/packager/media/event/combined_muxer_listener.h b/packager/media/event/combined_muxer_listener.h index fdeda6123c9..1f40e5c1e10 100644 --- a/packager/media/event/combined_muxer_listener.h +++ b/packager/media/event/combined_muxer_listener.h @@ -44,7 +44,8 @@ class CombinedMuxerListener : public MuxerListener { void OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) override; + uint64_t segment_file_size, + int64_t segment_number) override; void OnCompletedSegment(int64_t duration, uint64_t segment_file_size) override; void OnKeyFrame(int64_t timestamp, diff --git a/packager/media/event/event_info.h b/packager/media/event/event_info.h index ea1162a36e1..0b8a4a93023 100644 --- a/packager/media/event/event_info.h +++ b/packager/media/event/event_info.h @@ -20,6 +20,7 @@ struct SegmentEventInfo { // The below two fields are only useful for Segment. int64_t duration; uint64_t segment_file_size; + int64_t segment_number; }; struct KeyFrameEvent { diff --git a/packager/media/event/hls_notify_muxer_listener.cc b/packager/media/event/hls_notify_muxer_listener.cc index c0f71267a3c..3e619766519 100644 --- a/packager/media/event/hls_notify_muxer_listener.cc +++ b/packager/media/event/hls_notify_muxer_listener.cc @@ -244,11 +244,13 @@ void HlsNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, void HlsNotifyMuxerListener::OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) { + uint64_t segment_file_size, + int64_t segment_number) { if (!media_info_->has_segment_template()) { EventInfo event_info; event_info.type = EventInfoType::kSegment; - event_info.segment_info = {start_time, duration, segment_file_size}; + event_info.segment_info = {start_time, duration, segment_file_size, + segment_number}; event_info_.push_back(event_info); } else { // For multisegment, it always starts from the beginning of the file. diff --git a/packager/media/event/hls_notify_muxer_listener.h b/packager/media/event/hls_notify_muxer_listener.h index 9d0f58c0b3b..aa76fa8e84e 100644 --- a/packager/media/event/hls_notify_muxer_listener.h +++ b/packager/media/event/hls_notify_muxer_listener.h @@ -72,7 +72,8 @@ class HlsNotifyMuxerListener : public MuxerListener { void OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) override; + uint64_t segment_file_size, + int64_t segment_number) override; void OnKeyFrame(int64_t timestamp, uint64_t start_byte_offset, uint64_t size) override; diff --git a/packager/media/event/hls_notify_muxer_listener_unittest.cc b/packager/media/event/hls_notify_muxer_listener_unittest.cc index 4d9650a8e9e..1bf9e2e4f10 100644 --- a/packager/media/event/hls_notify_muxer_listener_unittest.cc +++ b/packager/media/event/hls_notify_muxer_listener_unittest.cc @@ -79,6 +79,7 @@ const uint64_t kSegmentStartOffset = 10000; const int64_t kSegmentStartTime = 19283; const int64_t kSegmentDuration = 98028; const uint64_t kSegmentSize = 756739; +const int64_t kAnySegmentNumber = 10; const int64_t kCueStartTime = kSegmentStartTime; @@ -349,7 +350,7 @@ TEST_F(HlsNotifyMuxerListenerTest, OnNewSegmentAndCueEvent) { kSegmentDuration, _, kSegmentSize)); listener_.OnCueEvent(kCueStartTime, "dummy cue data"); listener_.OnNewSegment("new_segment_name10.ts", kSegmentStartTime, - kSegmentDuration, kSegmentSize); + kSegmentDuration, kSegmentSize, kAnySegmentNumber); } // Verify that the notifier is called for every segment in OnMediaEnd if @@ -367,7 +368,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEnd) { listener_.OnCueEvent(kCueStartTime, "dummy cue data"); listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration, - kSegmentSize); + kSegmentSize, kAnySegmentNumber); EXPECT_CALL(mock_notifier_, NotifyCueEvent(_, kCueStartTime)); EXPECT_CALL( @@ -397,7 +398,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEndTwice) { listener_.OnMediaStart(muxer_options1, *video_stream_info, 90000, MuxerListener::kContainerMpeg2ts); listener_.OnNewSegment("filename1.mp4", kSegmentStartTime, kSegmentDuration, - kSegmentSize); + kSegmentSize, kAnySegmentNumber); listener_.OnCueEvent(kCueStartTime, "dummy cue data"); EXPECT_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) @@ -414,7 +415,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEndTwice) { listener_.OnMediaStart(muxer_options2, *video_stream_info, 90000, MuxerListener::kContainerMpeg2ts); listener_.OnNewSegment("filename2.mp4", kSegmentStartTime + kSegmentDuration, - kSegmentDuration, kSegmentSize); + kSegmentDuration, kSegmentSize, kAnySegmentNumber); EXPECT_CALL(mock_notifier_, NotifyNewSegment(_, StrEq("filename2.mp4"), kSegmentStartTime + kSegmentDuration, _, _, _)); @@ -440,7 +441,7 @@ TEST_F(HlsNotifyMuxerListenerTest, MuxerListener::kContainerMpeg2ts); listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration, - kSegmentSize); + kSegmentSize, kAnySegmentNumber); EXPECT_CALL( mock_notifier_, NotifyNewSegment(_, StrEq("filename.mp4"), kSegmentStartTime, @@ -504,7 +505,7 @@ TEST_P(HlsNotifyMuxerListenerKeyFrameTest, NoSegmentTemplate) { listener_.OnKeyFrame(kKeyFrameTimestamp, kKeyFrameStartByteOffset, kKeyFrameSize); listener_.OnNewSegment("filename.mp4", kSegmentStartTime, kSegmentDuration, - kSegmentSize); + kSegmentSize, kAnySegmentNumber); EXPECT_CALL(mock_notifier_, NotifyKeyFrame(_, kKeyFrameTimestamp, diff --git a/packager/media/event/mock_muxer_listener.h b/packager/media/event/mock_muxer_listener.h index 82713ae3f5d..970bc3b1186 100644 --- a/packager/media/event/mock_muxer_listener.h +++ b/packager/media/event/mock_muxer_listener.h @@ -56,11 +56,12 @@ class MockMuxerListener : public MuxerListener { void OnMediaEnd(const MediaRanges& range, float duration_seconds) override; - MOCK_METHOD4(OnNewSegment, + MOCK_METHOD5(OnNewSegment, void(const std::string& segment_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size)); + uint64_t segment_file_size, + int64_t segment_number)); MOCK_METHOD3(OnKeyFrame, void(int64_t timestamp, diff --git a/packager/media/event/mpd_notify_muxer_listener.cc b/packager/media/event/mpd_notify_muxer_listener.cc index 530c8434213..79b58d8cbfb 100644 --- a/packager/media/event/mpd_notify_muxer_listener.cc +++ b/packager/media/event/mpd_notify_muxer_listener.cc @@ -182,7 +182,8 @@ void MpdNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, mpd_notifier_->NotifyNewSegment( notification_id_.value(), event_info.segment_info.start_time, event_info.segment_info.duration, - event_info.segment_info.segment_file_size); + event_info.segment_info.segment_file_size, + event_info.segment_info.segment_number); break; case EventInfoType::kKeyFrame: // NO-OP for DASH. @@ -200,17 +201,20 @@ void MpdNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, void MpdNotifyMuxerListener::OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) { + uint64_t segment_file_size, + int64_t segment_number) { UNUSED(file_name); if (mpd_notifier_->dash_profile() == DashProfile::kLive) { mpd_notifier_->NotifyNewSegment(notification_id_.value(), start_time, - duration, segment_file_size); + duration, segment_file_size, + segment_number); if (mpd_notifier_->mpd_type() == MpdType::kDynamic) mpd_notifier_->Flush(); } else { EventInfo event_info; event_info.type = EventInfoType::kSegment; - event_info.segment_info = {start_time, duration, segment_file_size}; + event_info.segment_info = {start_time, duration, segment_file_size, + segment_number}; event_info_.push_back(event_info); } } diff --git a/packager/media/event/mpd_notify_muxer_listener.h b/packager/media/event/mpd_notify_muxer_listener.h index 94375fc41be..98697a01e79 100644 --- a/packager/media/event/mpd_notify_muxer_listener.h +++ b/packager/media/event/mpd_notify_muxer_listener.h @@ -52,7 +52,8 @@ class MpdNotifyMuxerListener : public MuxerListener { void OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) override; + uint64_t segment_file_size, + int64_t segment_number) override; void OnCompletedSegment(int64_t duration, uint64_t segment_file_size) override; void OnKeyFrame(int64_t timestamp, diff --git a/packager/media/event/mpd_notify_muxer_listener_unittest.cc b/packager/media/event/mpd_notify_muxer_listener_unittest.cc index 0cf6c3e10ca..d2d13aea5d8 100644 --- a/packager/media/event/mpd_notify_muxer_listener_unittest.cc +++ b/packager/media/event/mpd_notify_muxer_listener_unittest.cc @@ -301,6 +301,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnSampleDurationReady) { " time_scale: 10\n" " pixel_width: 1\n" " pixel_height: 1\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "init_range {\n" " begin: 0\n" @@ -349,6 +351,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnSampleDurationReadySegmentList) { " time_scale: 10\n" " pixel_width: 1\n" " pixel_height: 1\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "init_range {\n" " begin: 0\n" @@ -397,27 +401,31 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegment) { const uint64_t kSegmentFileSize1 = 29812u; const int64_t kStartTime2 = 1001; const int64_t kDuration2 = 3787; + const int64_t kSegmentNumber1 = 1; const uint64_t kSegmentFileSize2 = 83743u; + const int64_t kSegmentNumber2 = 2; EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); - EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _)).Times(0); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _, _)).Times(0); listener_->OnMediaStart(muxer_options, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, + kSegmentNumber1); listener_->OnCueEvent(kStartTime2, "dummy cue data"); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, + kSegmentNumber2); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); InSequence s; EXPECT_CALL(*notifier_, NotifyNewContainer( ExpectMediaInfoEq(kExpectedDefaultMediaInfo), _)) .WillOnce(Return(true)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, kSegmentNumber1)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, kSegmentNumber2)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); } @@ -433,29 +441,33 @@ TEST_F(MpdNotifyMuxerListenerTest, VodOnNewSegmentSegmentList) { const int64_t kStartTime1 = 0; const int64_t kDuration1 = 1000; const uint64_t kSegmentFileSize1 = 29812u; + const int64_t kSegmentNumber1 = 1; const int64_t kStartTime2 = 1001; const int64_t kDuration2 = 3787; const uint64_t kSegmentFileSize2 = 83743u; + const int64_t kSegmentNumber2 = 2; EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); - EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _)).Times(0); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _, _)).Times(0); listener_->OnMediaStart(muxer_options, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, + kSegmentNumber1); listener_->OnCueEvent(kStartTime2, "dummy cue data"); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, + kSegmentNumber2); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); InSequence s; EXPECT_CALL(*notifier_, NotifyNewContainer( ExpectMediaInfoEq(kExpectedDefaultMediaInfoSubsegmentRange), _)) .WillOnce(Return(true)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, kSegmentNumber1)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, kSegmentNumber2)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); } @@ -485,15 +497,18 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFiles) { const uint64_t kSegmentFileSize1 = 29812u; const int64_t kStartTime2 = 1001; const int64_t kDuration2 = 3787; + const int64_t kSegmentNumber1 = 1; const uint64_t kSegmentFileSize2 = 83743u; + const int64_t kSegmentNumber2 = 2; // Expectation for first file before OnMediaEnd. EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); - EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _)).Times(0); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _, _)).Times(0); listener_->OnMediaStart(muxer_options1, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, + kSegmentNumber1); listener_->OnCueEvent(kStartTime2, "dummy cue data"); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); @@ -502,8 +517,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFiles) { EXPECT_CALL(*notifier_, NotifyNewContainer(EqualsProto(expected_media_info1), _)) .WillOnce(Return(true)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, kSegmentNumber1)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); @@ -512,13 +527,14 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFiles) { listener_->OnMediaStart(muxer_options2, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, + kSegmentNumber2); // Expectation for second file OnMediaEnd. EXPECT_CALL(*notifier_, NotifyMediaInfoUpdate(_, EqualsProto(expected_media_info2))); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, kSegmentNumber2)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); } @@ -544,17 +560,20 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFilesSegmentList) { const int64_t kStartTime1 = 0; const int64_t kDuration1 = 1000; const uint64_t kSegmentFileSize1 = 29812u; + const int64_t kSegmentNumber1 = 1; const int64_t kStartTime2 = 1001; const int64_t kDuration2 = 3787; const uint64_t kSegmentFileSize2 = 83743u; + const int64_t kSegmentNumber2 = 2; // Expectation for first file before OnMediaEnd. EXPECT_CALL(*notifier_, NotifyNewContainer(_, _)).Times(0); - EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _)).Times(0); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, _, _, _, _)).Times(0); listener_->OnMediaStart(muxer_options1, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, + kSegmentNumber1); listener_->OnCueEvent(kStartTime2, "dummy cue data"); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); @@ -563,8 +582,8 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFilesSegmentList) { EXPECT_CALL(*notifier_, NotifyNewContainer(EqualsProto(expected_media_info1), _)) .WillOnce(Return(true)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, kSegmentNumber1)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); @@ -573,13 +592,14 @@ TEST_F(MpdNotifyMuxerListenerTest, VodMultipleFilesSegmentList) { listener_->OnMediaStart(muxer_options2, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, + kSegmentNumber2); // Expectation for second file OnMediaEnd. EXPECT_CALL(*notifier_, NotifyMediaInfoUpdate(_, EqualsProto(expected_media_info2))); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, kSegmentNumber2)); EXPECT_CALL(*notifier_, Flush()); FireOnMediaEndWithParams(GetDefaultOnMediaEndParams()); } @@ -600,6 +620,8 @@ TEST_F(MpdNotifyMuxerListenerTest, LowLatencyDash) { " time_scale: 10\n" " pixel_width: 1\n" " pixel_height: 1\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "media_duration_seconds: 20.0\n" "index: 0\n" @@ -613,6 +635,8 @@ TEST_F(MpdNotifyMuxerListenerTest, LowLatencyDash) { const uint64_t kDuration = 1000u; const uint64_t kSegmentSize1 = 29812u; const uint64_t kSegmentSize2 = 30128u; + const int64_t kSegmentNumber1 = 1; + const int64_t kSegmentNumber2 = 2; EXPECT_CALL(*notifier_, NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)) @@ -622,11 +646,11 @@ TEST_F(MpdNotifyMuxerListenerTest, LowLatencyDash) { EXPECT_CALL(*notifier_, NotifyAvailabilityTimeOffset(_)) .WillOnce(Return(true)); EXPECT_CALL(*notifier_, NotifySegmentDuration(_)).WillOnce(Return(true)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration, kSegmentSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration, + kSegmentSize1, kSegmentNumber1)); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration, kSegmentSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration, + kSegmentSize2, kSegmentNumber2)); EXPECT_CALL(*notifier_, Flush()).Times(2); listener_->OnMediaStart(muxer_options, *video_stream_info, @@ -635,9 +659,11 @@ TEST_F(MpdNotifyMuxerListenerTest, LowLatencyDash) { listener_->OnSampleDurationReady(kDuration); listener_->OnAvailabilityOffsetReady(); listener_->OnSegmentDurationReady(); - listener_->OnNewSegment("", kStartTime1, kDuration, kSegmentSize1); + listener_->OnNewSegment("", kStartTime1, kDuration, kSegmentSize1, + kSegmentNumber1); listener_->OnCueEvent(kStartTime2, "dummy cue data"); - listener_->OnNewSegment("", kStartTime2, kDuration, kSegmentSize2); + listener_->OnNewSegment("", kStartTime2, kDuration, kSegmentSize2, + kSegmentNumber2); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); EXPECT_CALL(*notifier_, Flush()).Times(0); @@ -662,6 +688,8 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) { " time_scale: 10\n" " pixel_width: 1\n" " pixel_height: 1\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "media_duration_seconds: 20.0\n" "index: 0\n" @@ -686,7 +714,10 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) { const uint64_t kSegmentFileSize1 = 29812u; const int64_t kStartTime2 = 1001; const int64_t kDuration2 = 3787; + const int64_t kSegmentNumber1 = 1; const uint64_t kSegmentFileSize2 = 83743u; + const int64_t kSegmentNumber2 = 2; + const std::vector default_key_id( kDefaultKeyId, kDefaultKeyId + std::size(kDefaultKeyId) - 1); @@ -695,14 +726,14 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) { EXPECT_CALL(*notifier_, NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)) .WillOnce(Return(true)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, kSegmentNumber1)); // Flush should only be called once in OnMediaEnd. if (GetParam() == MpdType::kDynamic) EXPECT_CALL(*notifier_, Flush()); EXPECT_CALL(*notifier_, NotifyCueEvent(_, kStartTime2)); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, kSegmentNumber2)); if (GetParam() == MpdType::kDynamic) EXPECT_CALL(*notifier_, Flush()); @@ -713,9 +744,11 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveNoKeyRotation) { listener_->OnMediaStart(muxer_options, *video_stream_info, kDefaultReferenceTimeScale, MuxerListener::kContainerMp4); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, + kSegmentNumber1); listener_->OnCueEvent(kStartTime2, "dummy cue data"); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, + kSegmentNumber2); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); EXPECT_CALL(*notifier_, Flush()) @@ -742,6 +775,8 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) { " time_scale: 10\n" " pixel_width: 1\n" " pixel_height: 1\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "media_duration_seconds: 20.0\n" "index: 0\n" @@ -760,7 +795,10 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) { const uint64_t kSegmentFileSize1 = 29812u; const int64_t kStartTime2 = 1001; const int64_t kDuration2 = 3787; + const int64_t kSegmentNumber1 = 1; const uint64_t kSegmentFileSize2 = 83743u; + const int64_t kSegmentNumber2 = 2; + const std::vector default_key_id( kDefaultKeyId, kDefaultKeyId + std::size(kDefaultKeyId) - 1); @@ -769,13 +807,13 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) { NotifyNewContainer(ExpectMediaInfoEq(kExpectedMediaInfo), _)) .WillOnce(Return(true)); EXPECT_CALL(*notifier_, NotifyEncryptionUpdate(_, _, _, _)).Times(1); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime1, kDuration1, kSegmentFileSize1)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime1, kDuration1, + kSegmentFileSize1, kSegmentNumber1)); // Flush should only be called once in OnMediaEnd. if (GetParam() == MpdType::kDynamic) EXPECT_CALL(*notifier_, Flush()); - EXPECT_CALL(*notifier_, - NotifyNewSegment(_, kStartTime2, kDuration2, kSegmentFileSize2)); + EXPECT_CALL(*notifier_, NotifyNewSegment(_, kStartTime2, kDuration2, + kSegmentFileSize2, kSegmentNumber2)); if (GetParam() == MpdType::kDynamic) EXPECT_CALL(*notifier_, Flush()); @@ -789,8 +827,10 @@ TEST_P(MpdNotifyMuxerListenerTest, LiveWithKeyRotation) { listener_->OnEncryptionInfoReady(kNonInitialEncryptionInfo, FOURCC_cbc1, std::vector(), iv, GetDefaultKeySystemInfo()); - listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1); - listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2); + listener_->OnNewSegment("", kStartTime1, kDuration1, kSegmentFileSize1, + kSegmentNumber1); + listener_->OnNewSegment("", kStartTime2, kDuration2, kSegmentFileSize2, + kSegmentNumber2); ::testing::Mock::VerifyAndClearExpectations(notifier_.get()); EXPECT_CALL(*notifier_, Flush()) diff --git a/packager/media/event/multi_codec_muxer_listener_unittest.cc b/packager/media/event/multi_codec_muxer_listener_unittest.cc index 6a75983d30a..64d79d848fb 100644 --- a/packager/media/event/multi_codec_muxer_listener_unittest.cc +++ b/packager/media/event/multi_codec_muxer_listener_unittest.cc @@ -27,6 +27,8 @@ const int64_t kSegmentStartTime = 19283; const int64_t kSegmentDuration = 98028; const uint64_t kSegmentSize = 756739; const int32_t kTimescale = 90000; +const int64_t kSegmentNumber = 10; + MuxerListener::ContainerType kContainer = MuxerListener::kContainerMpeg2ts; } // namespace @@ -79,10 +81,11 @@ TEST_F(MultiCodecMuxerListenerTest, OnNewSegmentAfterOnMediaStartSingleCodec) { EXPECT_CALL(*listener_for_first_codec_, OnNewSegment(StrEq("new_segment_name10.ts"), kSegmentStartTime, - kSegmentDuration, kSegmentSize)); + kSegmentDuration, kSegmentSize, kSegmentNumber)); multi_codec_listener_.OnNewSegment("new_segment_name10.ts", kSegmentStartTime, - kSegmentDuration, kSegmentSize); + kSegmentDuration, kSegmentSize, + kSegmentNumber); } TEST_F(MultiCodecMuxerListenerTest, OnMediaStartTwoCodecs) { @@ -114,13 +117,14 @@ TEST_F(MultiCodecMuxerListenerTest, OnNewSegmentAfterOnMediaStartTwoCodecs) { EXPECT_CALL(*listener_for_first_codec_, OnNewSegment(StrEq("new_segment_name10.ts"), kSegmentStartTime, - kSegmentDuration, kSegmentSize)); + kSegmentDuration, kSegmentSize, kSegmentNumber)); EXPECT_CALL(*listener_for_second_codec_, OnNewSegment(StrEq("new_segment_name10.ts"), kSegmentStartTime, - kSegmentDuration, kSegmentSize)); + kSegmentDuration, kSegmentSize, kSegmentNumber)); multi_codec_listener_.OnNewSegment("new_segment_name10.ts", kSegmentStartTime, - kSegmentDuration, kSegmentSize); + kSegmentDuration, kSegmentSize, + kSegmentNumber); } } // namespace media diff --git a/packager/media/event/muxer_listener.h b/packager/media/event/muxer_listener.h index b0a86e0fba5..2eb704154c9 100644 --- a/packager/media/event/muxer_listener.h +++ b/packager/media/event/muxer_listener.h @@ -133,10 +133,12 @@ class MuxerListener { /// @param duration is the duration of the segment, relative to the timescale /// specified by MediaInfo passed to OnMediaStart(). /// @param segment_file_size is the segment size in bytes. + /// @param segment_number is the segment number. virtual void OnNewSegment(const std::string& segment_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) = 0; + uint64_t segment_file_size, + int64_t segment_number) = 0; /// Called when a segment has been muxed and the entire file has been written. /// For Low Latency only. Note that it should be called after OnNewSegment. diff --git a/packager/media/event/muxer_listener_internal.cc b/packager/media/event/muxer_listener_internal.cc index 03f3dc6af17..0bc150fd0cf 100644 --- a/packager/media/event/muxer_listener_internal.cc +++ b/packager/media/event/muxer_listener_internal.cc @@ -73,6 +73,8 @@ void AddVideoInfo(const VideoStreamInfo* video_stream_info, DCHECK(video_stream_info); DCHECK(media_info); MediaInfo_VideoInfo* video_info = media_info->mutable_video_info(); + video_info->set_supplemental_codec(video_stream_info->supplemental_codec()); + video_info->set_compatible_brand(video_stream_info->compatible_brand()); video_info->set_codec(video_stream_info->codec_string()); video_info->set_width(video_stream_info->width()); video_info->set_height(video_stream_info->height()); @@ -96,6 +98,13 @@ void AddVideoInfo(const VideoStreamInfo* video_stream_info, video_info->set_transfer_characteristics( video_stream_info->transfer_characteristics()); } + if (video_stream_info->color_primaries() > 0) { + video_info->set_color_primaries(video_stream_info->color_primaries()); + } + if (video_stream_info->matrix_coefficients() > 0) { + video_info->set_matrix_coefficients( + video_stream_info->matrix_coefficients()); + } } void AddAudioInfo(const AudioStreamInfo* audio_stream_info, diff --git a/packager/media/event/muxer_listener_test_helper.cc b/packager/media/event/muxer_listener_test_helper.cc index 9225f71e880..f8029763446 100644 --- a/packager/media/event/muxer_listener_test_helper.cc +++ b/packager/media/event/muxer_listener_test_helper.cc @@ -23,6 +23,8 @@ std::shared_ptr CreateVideoStreamInfo( H26xStreamFormat::kUnSpecified, param.codec_string, param.codec_config.data(), param.codec_config.size(), param.width, param.height, param.pixel_width, param.pixel_height, + 0, // color_primaries + 0, // matrix_coefficients 0, // transfer_characteristics 0, // trick_play_factor param.nalu_length_size, param.language, param.is_encrypted); diff --git a/packager/media/event/muxer_listener_test_helper.h b/packager/media/event/muxer_listener_test_helper.h index 59ca8fbe1f9..6eff3ed9664 100644 --- a/packager/media/event/muxer_listener_test_helper.h +++ b/packager/media/event/muxer_listener_test_helper.h @@ -26,6 +26,8 @@ const char kExpectedDefaultPsshBox[] = "expected_pssh_box"; const char kExpectedDefaultMediaInfo[] = "video_info {\n" " codec: 'avc1.010101'\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" " width: 720\n" " height: 480\n" " time_scale: 10\n" @@ -54,6 +56,8 @@ const char kExpectedDefaultMediaInfoSubsegmentRange[] = " time_scale: 10\n" " pixel_width: 1\n" " pixel_height: 1\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "init_range {\n" " begin: 0\n" diff --git a/packager/media/event/vod_media_info_dump_muxer_listener.cc b/packager/media/event/vod_media_info_dump_muxer_listener.cc index e0108f6831b..148503c581d 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener.cc +++ b/packager/media/event/vod_media_info_dump_muxer_listener.cc @@ -95,7 +95,8 @@ void VodMediaInfoDumpMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, void VodMediaInfoDumpMuxerListener::OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) { + uint64_t segment_file_size, + int64_t segment_number) { UNUSED(file_name); UNUSED(start_time); const double segment_duration_seconds = diff --git a/packager/media/event/vod_media_info_dump_muxer_listener.h b/packager/media/event/vod_media_info_dump_muxer_listener.h index 4eacb4c6714..42dd57da22d 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener.h +++ b/packager/media/event/vod_media_info_dump_muxer_listener.h @@ -49,7 +49,8 @@ class VodMediaInfoDumpMuxerListener : public MuxerListener { void OnNewSegment(const std::string& file_name, int64_t start_time, int64_t duration, - uint64_t segment_file_size) override; + uint64_t segment_file_size, + int64_t segment_number) override; void OnKeyFrame(int64_t timestamp, uint64_t start_byte_offset, uint64_t size) override; diff --git a/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc b/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc index f3926cd3a14..157c36a43e1 100644 --- a/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc +++ b/packager/media/event/vod_media_info_dump_muxer_listener_unittest.cc @@ -102,8 +102,10 @@ class VodMediaInfoDumpMuxerListenerTest : public ::testing::Test { } void FireOnNewSegmentWithParams(const OnNewSegmentParameters& params) { + const int64_t kSegmentNumber = 1; listener_->OnNewSegment(params.file_name, params.start_time, - params.duration, params.segment_file_size); + params.duration, params.segment_file_size, + kSegmentNumber); } void FireOnMediaEndWithParams(const OnMediaEndParameters& params) { @@ -136,6 +138,8 @@ TEST_F(VodMediaInfoDumpMuxerListenerTest, UnencryptedStream_Normal) { " time_scale: 10\n" " pixel_width: 1\n" " pixel_height: 1\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "init_range {\n" " begin: 0\n" @@ -168,6 +172,8 @@ TEST_F(VodMediaInfoDumpMuxerListenerTest, EncryptedStream_Normal) { " time_scale: 10\n" " pixel_width: 1\n" " pixel_height: 1\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "init_range {\n" " begin: 0\n" @@ -216,6 +222,8 @@ TEST_F(VodMediaInfoDumpMuxerListenerTest, CheckPixelWidthAndHeightSet) { " time_scale: 10\n" " pixel_width: 8\n" " pixel_height: 9\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "init_range {\n" " begin: 0\n" @@ -258,6 +266,8 @@ TEST_F(VodMediaInfoDumpMuxerListenerTest, CheckBandwidth) { " time_scale: 10\n" " pixel_width: 1\n" " pixel_height: 1\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "init_range {\n" " begin: 0\n" @@ -295,6 +305,8 @@ TEST_F(VodMediaInfoDumpMuxerListenerTest, UnencryptedStream_Normal_SegmentList) " time_scale: 10\n" " pixel_width: 1\n" " pixel_height: 1\n" + " supplemental_codec: ''\n" + " compatible_brand: 0\n" "}\n" "init_range {\n" " begin: 0\n" diff --git a/packager/media/formats/mp2t/es_parser_h264.cc b/packager/media/formats/mp2t/es_parser_h264.cc index e450c81202c..eb4eec2d1e8 100644 --- a/packager/media/formats/mp2t/es_parser_h264.cc +++ b/packager/media/formats/mp2t/es_parser_h264.cc @@ -174,7 +174,8 @@ bool EsParserH264::UpdateVideoDecoderConfig(int pps_id) { codec_fourcc, decoder_config_record[1], decoder_config_record[2], decoder_config_record[3]), decoder_config_record.data(), decoder_config_record.size(), coded_width, - coded_height, pixel_width, pixel_height, sps->transfer_characteristics, 0, + coded_height, pixel_width, pixel_height, sps->color_primaries, + sps->matrix_coefficients, sps->transfer_characteristics, 0, nalu_length_size, std::string(), false); DVLOG(1) << "Profile IDC: " << sps->profile_idc; DVLOG(1) << "Level IDC: " << sps->level_idc; diff --git a/packager/media/formats/mp2t/es_parser_h265.cc b/packager/media/formats/mp2t/es_parser_h265.cc index 319ce47bcff..b89dd3f3f4b 100644 --- a/packager/media/formats/mp2t/es_parser_h265.cc +++ b/packager/media/formats/mp2t/es_parser_h265.cc @@ -177,8 +177,10 @@ bool EsParserH265::UpdateVideoDecoderConfig(int pps_id) { pid(), kMpeg2Timescale, kInfiniteDuration, kCodecH265, stream_format, decoder_config.GetCodecString(codec_fourcc), decoder_config_record.data(), decoder_config_record.size(), coded_width, coded_height, pixel_width, - pixel_height, sps->vui_parameters.transfer_characteristics, 0, - nalu_length_size, std::string(), false); + pixel_height, sps->vui_parameters.color_primaries, + sps->vui_parameters.matrix_coefficients, + sps->vui_parameters.transfer_characteristics, 0, nalu_length_size, + std::string(), false); // Video config notification. new_stream_info_cb_(last_video_decoder_config_); diff --git a/packager/media/formats/mp2t/es_parser_teletext.cc b/packager/media/formats/mp2t/es_parser_teletext.cc index 7aeb408bb37..e08c0a51569 100644 --- a/packager/media/formats/mp2t/es_parser_teletext.cc +++ b/packager/media/formats/mp2t/es_parser_teletext.cc @@ -7,10 +7,10 @@ #include #include -#include #include #include #include +#include namespace shaka { namespace media { @@ -18,6 +18,8 @@ namespace mp2t { namespace { +constexpr const char* kRegionTeletextPrefix = "ttx_"; + const uint8_t EBU_TELETEXT_WITH_SUBTITLING = 0x03; const int kPayloadSize = 40; const int kNumTriplets = 13; @@ -94,14 +96,6 @@ bool ParseSubtitlingDescriptor( return true; } -std::string RemoveTrailingSpaces(const std::string& input) { - const auto index = input.find_last_not_of(' '); - if (index == std::string::npos) { - return ""; - } - return input.substr(0, index + 1); -} - } // namespace EsParserTeletext::EsParserTeletext(const uint32_t pid, @@ -169,7 +163,7 @@ bool EsParserTeletext::ParseInternal(const uint8_t* data, const int64_t pts) { BitReader reader(data, size); RCHECK(reader.SkipBits(8)); - std::vector lines; + std::vector rows; while (reader.bits_available()) { uint8_t data_unit_id; @@ -178,15 +172,17 @@ bool EsParserTeletext::ParseInternal(const uint8_t* data, uint8_t data_unit_length; RCHECK(reader.ReadBits(8, &data_unit_length)); + if (data_unit_id != EBU_TELETEXT_WITH_SUBTITLING) { + RCHECK(reader.SkipBytes(data_unit_length)); + continue; + } + if (data_unit_length != 44) { + // Teletext data unit length is always 44 bytes LOG(ERROR) << "Bad Teletext data length"; break; } - if (data_unit_id != EBU_TELETEXT_WITH_SUBTITLING) { - RCHECK(reader.SkipBytes(44)); - continue; - } RCHECK(reader.SkipBits(16)); @@ -207,27 +203,26 @@ bool EsParserTeletext::ParseInternal(const uint8_t* data, const uint8_t* data_block = reader.current_byte_ptr(); RCHECK(reader.SkipBytes(40)); - std::string display_text; - if (ParseDataBlock(pts, data_block, packet_nr, magazine, display_text)) { - lines.emplace_back(std::move(display_text)); + TextRow row; + if (ParseDataBlock(pts, data_block, packet_nr, magazine, row)) { + rows.emplace_back(std::move(row)); } } - if (lines.empty()) { + if (rows.empty()) { return true; } - const uint16_t index = magazine_ * 100 + page_number_; auto page_state_itr = page_state_.find(index); if (page_state_itr == page_state_.end()) { - page_state_.emplace(index, TextBlock{std::move(lines), {}, last_pts_}); + page_state_.emplace(index, TextBlock{std::move(rows), {}, last_pts_}); } else { - for (auto& line : lines) { - auto& page_state_lines = page_state_itr->second.lines; - page_state_lines.emplace_back(std::move(line)); + for (auto& row : rows) { + auto& page_state_lines = page_state_itr->second.rows; + page_state_lines.emplace_back(std::move(row)); } - lines.clear(); + rows.clear(); } return true; @@ -237,13 +232,17 @@ bool EsParserTeletext::ParseDataBlock(const int64_t pts, const uint8_t* data_block, const uint8_t packet_nr, const uint8_t magazine, - std::string& display_text) { + TextRow& row) { if (packet_nr == 0) { last_pts_ = pts; BitReader reader(data_block, 32); const uint8_t page_number_units = ReadHamming(reader); const uint8_t page_number_tens = ReadHamming(reader); + if (page_number_units == 0xf || page_number_tens == 0xf) { + RCHECK(reader.SkipBits(40)); + return false; + } const uint8_t page_number = 10 * page_number_tens + page_number_units; const uint16_t index = magazine * 100 + page_number; @@ -251,9 +250,6 @@ bool EsParserTeletext::ParseDataBlock(const int64_t pts, page_number_ = page_number; magazine_ = magazine; - if (page_number == 0xFF) { - return false; - } RCHECK(reader.SkipBits(40)); const uint8_t subcode_c11_c14 = ReadHamming(reader); @@ -273,7 +269,7 @@ bool EsParserTeletext::ParseDataBlock(const int64_t pts, return false; } - display_text = BuildText(data_block, packet_nr); + row = BuildRow(data_block, packet_nr); return true; } @@ -318,45 +314,81 @@ void EsParserTeletext::SendPending(const uint16_t index, const int64_t pts) { auto page_state_itr = page_state_.find(index); if (page_state_itr == page_state_.end() || - page_state_itr->second.lines.empty()) { + page_state_itr->second.rows.empty()) { return; } - const auto& pending_lines = page_state_itr->second.lines; + const auto& pending_rows = page_state_itr->second.rows; const auto pending_pts = page_state_itr->second.pts; - TextFragmentStyle text_fragment_style; TextSettings text_settings; std::shared_ptr text_sample; - - if (pending_lines.size() == 1) { - TextFragment text_fragment(text_fragment_style, pending_lines[0].c_str()); - text_sample = std::make_shared("", pending_pts, pts, - text_settings, text_fragment); - + std::vector sub_fragments; + + if (pending_rows.size() == 1) { + // This is a single line of formatted text. + // Propagate row number/2 and alignment + const float line_nr = float(pending_rows[0].row_number) / 2.0; + text_settings.line = TextNumber(line_nr, TextUnitType::kLines); + text_settings.region = kRegionTeletextPrefix + std::to_string(int(line_nr)); + text_settings.text_alignment = pending_rows[0].alignment; + text_sample = std::make_shared( + "", pending_pts, pts, text_settings, pending_rows[0].fragment); + text_sample->set_sub_stream_index(index); + emit_sample_cb_(text_sample); + page_state_.erase(index); + return; } else { - std::vector sub_fragments; - for (const auto& line : pending_lines) { - sub_fragments.emplace_back(text_fragment_style, line.c_str()); - sub_fragments.emplace_back(text_fragment_style, true); + int32_t latest_row_nr = -1; + bool last_double_height = false; + bool new_sample = true; + for (const auto& row : pending_rows) { + int row_nr = row.row_number; + bool double_height = row.double_height; + int row_step = last_double_height ? 2 : 1; + if (latest_row_nr != -1) { // Not the first row + if (row_nr != latest_row_nr + row_step) { + // Send what has been collected since not adjacent + text_sample = + std::make_shared("", pending_pts, pts, text_settings, + TextFragment({}, sub_fragments)); + text_sample->set_sub_stream_index(index); + emit_sample_cb_(text_sample); + new_sample = true; + } else { + // Add a newline and the next row to the current sample + sub_fragments.push_back(TextFragment({}, true)); + sub_fragments.push_back(row.fragment); + new_sample = false; + } + } + if (new_sample) { + const float line_nr = float(row.row_number) / 2.0; + text_settings.line = TextNumber(line_nr, TextUnitType::kLines); + text_settings.region = + kRegionTeletextPrefix + std::to_string(int(line_nr)); + text_settings.text_alignment = row.alignment; + sub_fragments.clear(); + sub_fragments.push_back(row.fragment); + } + last_double_height = double_height; + latest_row_nr = row_nr; } - sub_fragments.pop_back(); - TextFragment text_fragment(text_fragment_style, sub_fragments); - text_sample = std::make_shared("", pending_pts, pts, - text_settings, text_fragment); } + text_sample = std::make_shared( + "", pending_pts, pts, text_settings, TextFragment({}, sub_fragments)); text_sample->set_sub_stream_index(index); emit_sample_cb_(text_sample); page_state_.erase(index); } -std::string EsParserTeletext::BuildText(const uint8_t* data_block, - const uint8_t row) const { +// BuildRow builds a row with alignment information. +EsParserTeletext::TextRow EsParserTeletext::BuildRow(const uint8_t* data_block, + const uint8_t row) const { std::string next_string; next_string.reserve(kPayloadSize * 2); - bool leading_spaces = true; const uint16_t index = magazine_ * 100 + page_number_; const auto page_state_itr = page_state_.find(index); @@ -371,12 +403,19 @@ std::string EsParserTeletext::BuildText(const uint8_t* data_block, } } + int32_t start_pos = 0; + int32_t end_pos = 0; + bool double_height = false; + TextFragmentStyle text_style = TextFragmentStyle(); + text_style.color = "white"; + text_style.backgroundColor = "black"; + // A typical 40 character line looks like: + // doubleHeight, [color] spaces, Start, Start, text, End End, spaces for (size_t i = 0; i < kPayloadSize; ++i) { if (column_replacement_map) { const auto column_itr = column_replacement_map->find(i); if (column_itr != column_replacement_map->cend()) { next_string.append(column_itr->second); - leading_spaces = false; continue; } } @@ -384,17 +423,68 @@ std::string EsParserTeletext::BuildText(const uint8_t* data_block, char next_char = static_cast(TELETEXT_BITREVERSE_8[data_block[i]] & 0x7f); - if (next_char < 32) { - next_char = 0x20; - } - - if (leading_spaces) { - if (next_char == 0x20) { - continue; + if (next_char < 0x20) { + // Here are control characters, which are not printable. + // These include colors, double-height, flashing, etc. + // We only handle one-foreground color and double-height. + switch (next_char) { + case 0x0: // Alpha Black (not included in Level 1.5) + // color = ColorBlack + break; + case 0x1: + text_style.color = "red"; + break; + case 0x2: + text_style.color = "green"; + break; + case 0x3: + text_style.color = "yellow"; + break; + case 0x4: + text_style.color = "blue"; + break; + case 0x5: + text_style.color = "magenta"; + break; + case 0x6: + text_style.color = "cyan"; + break; + case 0x7: + text_style.color = "white"; + break; + case 0x08: // Flash (not handled) + break; + case 0x09: // Steady (not handled) + break; + case 0xa: // End Box + end_pos = i - 1; + break; + case 0xb: // Start Box, typically twice due to double height + start_pos = i + 1; + continue; // Do not propagate as a space + break; + case 0xc: // Normal size + break; + case 0xd: // Double height, typically always used + double_height = true; + break; + case 0x1c: // Black background (not handled) + break; + case 0x1d: // Set background color from text color. + text_style.backgroundColor = text_style.color; + text_style.color = "black"; // Avoid having same as background + break; + default: + // Rest of codes below 0x20 are not part of Level 1.5 or related to + // mosaic graphics (non-text) + break; } - leading_spaces = false; + next_char = + 0x20; // These characters result in a space if between start and end + } + if (start_pos == 0 || end_pos != 0) { // Not between start and end + continue; } - switch (next_char) { case '&': next_string.append("&"); @@ -408,8 +498,25 @@ std::string EsParserTeletext::BuildText(const uint8_t* data_block, } break; } } + if (end_pos == 0) { + end_pos = kPayloadSize - 1; + } + + // Using start_pos and end_pos we approximated alignment of text + // depending on the number of spaces to the left and right of the text. + auto left_right_diff = start_pos - (kPayloadSize - 1 - end_pos); + TextAlignment alignment; + if (left_right_diff > 4) { + alignment = TextAlignment::kRight; + } else if (left_right_diff < -4) { + alignment = TextAlignment::kLeft; + } else { + alignment = TextAlignment::kCenter; + } + const auto text_row = TextRow( + {alignment, row, double_height, {TextFragment(text_style, next_string)}}); - return RemoveTrailingSpaces(next_string); + return text_row; } void EsParserTeletext::ParsePacket26(const uint8_t* data_block) { diff --git a/packager/media/formats/mp2t/es_parser_teletext.h b/packager/media/formats/mp2t/es_parser_teletext.h index dda578bf214..0efde38f3e8 100644 --- a/packager/media/formats/mp2t/es_parser_teletext.h +++ b/packager/media/formats/mp2t/es_parser_teletext.h @@ -12,6 +12,7 @@ #include #include +#include #include namespace shaka { @@ -37,8 +38,15 @@ class EsParserTeletext : public EsParser { using RowColReplacementMap = std::unordered_map>; + struct TextRow { + TextAlignment alignment; + int row_number; + bool double_height; + TextFragment fragment; + }; + struct TextBlock { - std::vector lines; + std::vector rows; RowColReplacementMap packet_26_replacements; int64_t pts; }; @@ -48,10 +56,10 @@ class EsParserTeletext : public EsParser { const uint8_t* data_block, const uint8_t packet_nr, const uint8_t magazine, - std::string& display_text); + TextRow& display_text); void UpdateCharset(); void SendPending(const uint16_t index, const int64_t pts); - std::string BuildText(const uint8_t* data_block, const uint8_t row) const; + TextRow BuildRow(const uint8_t* data_block, const uint8_t row) const; void ParsePacket26(const uint8_t* data_block); void UpdateNationalSubset(const uint8_t national_subset[13][3]); diff --git a/packager/media/formats/mp2t/es_parser_teletext_unittest.cc b/packager/media/formats/mp2t/es_parser_teletext_unittest.cc index 3cb9d59f482..64f52b97a84 100644 --- a/packager/media/formats/mp2t/es_parser_teletext_unittest.cc +++ b/packager/media/formats/mp2t/es_parser_teletext_unittest.cc @@ -166,6 +166,65 @@ const uint8_t PES_8937764[] = { 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x23, 0xc7, 0x75, 0x8c, 0x1c, 0x04, 0x04, 0x04, 0x86, 0x4f, 0xce, 0x75, 0x75, 0x75, 0x8c, 0x8c}; +// Start (packet0, page88) with packet26 and row 18 left (packet18) +const uint8_t PES_867681[] = { + 0x10, 0x03, 0x2c, 0xf6, 0xe4, 0xa8, 0xa8, 0x0b, 0x0b, 0xa8, 0x0b, 0xa8, + 0x0b, 0xf4, 0xa8, 0xb3, 0x83, 0x32, 0xa2, 0x73, 0x2a, 0xa2, 0x73, 0x23, + 0x83, 0x73, 0x2a, 0xcb, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, + 0x2c, 0xd4, 0xe4, 0xa8, 0x6d, 0xa8, 0x9e, 0xc9, 0x00, 0x4e, 0x93, 0xa7, + 0x90, 0x53, 0xa7, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, + 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, + 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x03, 0x2c, 0xd6, + 0xe4, 0xa8, 0xe3, 0xb0, 0x04, 0x04, 0x04, 0x04, 0xd0, 0xd0, 0xb5, 0x32, + 0xae, 0xd6, 0xa7, 0x04, 0x85, 0x51, 0x51, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}; + +// row 22 right (packet22) +const uint8_t PES_871281[] = { + 0x10, 0x03, 0x2c, 0xf4, 0xe4, 0xa8, 0xd9, 0xb0, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0xd0, 0xd0, 0xb5, 0x52, 0xa7, 0x04, 0x6e, 0x86, 0x97, 0xce, + 0x04, 0x86, 0xae, 0x1f, 0x04, 0xc7, 0xf7, 0xae, 0x4f, 0xce, 0x04, 0x26, + 0xe5, 0xa7, 0x2f, 0xa7, 0x75, 0x51, 0x51, 0x04, 0x04, 0x04, 0x04}; + +// End (packet 0, page 88) +const uint8_t PES_1011695[] = { + 0x10, 0x03, 0x2c, 0xf6, 0xe4, 0xa8, 0xa8, 0x0b, 0x0b, 0xa8, 0x0b, 0xa8, + 0x0b, 0xf4, 0xa8, 0xb3, 0x83, 0x32, 0xa2, 0x73, 0x2a, 0xa2, 0x73, 0x23, + 0x83, 0x73, 0x2a, 0xcb, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}; + +// Start (packet0, page 88) with packet26 and row 20 (centered yellow) +// (packet20) +const uint8_t PES_1033297[] = { + 0x10, 0x03, 0x2c, 0xf6, 0xe4, 0xa8, 0xa8, 0x0b, 0x0b, 0xa8, 0x0b, 0xa8, + 0x0b, 0xf4, 0xa8, 0xb3, 0x83, 0x32, 0xa2, 0x73, 0x2a, 0xa2, 0x73, 0x23, + 0x83, 0x73, 0x2a, 0xcb, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, + 0x2c, 0xd4, 0xe4, 0xa8, 0x6d, 0xa8, 0x06, 0xc9, 0x01, 0x62, 0x93, 0xa6, + 0x9e, 0xc9, 0x00, 0xf4, 0xa2, 0x86, 0x06, 0xa3, 0xa7, 0x2e, 0xfe, 0xff, + 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, + 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x2e, 0xfe, 0xff, 0x03, 0x2c, 0xd6, + 0xe4, 0xa8, 0x31, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0xb0, 0xc1, 0xd0, 0xd0, 0x52, 0xe5, 0x86, 0x97, 0x04, + 0x37, 0xf7, 0xae, 0x0e, 0xa7, 0x51, 0x51, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}; + +// row 22 centered blue on yellow background (packet22) +const uint8_t PES_1036900[] = { + 0x10, 0x03, 0x2c, 0xf4, 0xe4, 0xa8, 0xd9, 0x04, 0x04, 0xb0, 0xc1, 0xb9, + 0x20, 0xd0, 0xd0, 0x37, 0xe5, 0x97, 0x76, 0x97, 0x2f, 0x97, 0x86, 0x2f, + 0x97, 0xf7, 0x76, 0x04, 0x86, 0x04, 0x37, 0xe5, 0x86, 0x37, 0xe6, 0xa7, + 0x46, 0x4f, 0xa7, 0x75, 0x51, 0x51, 0x04, 0x04, 0x04, 0x04, 0x04}; + +// End (packet 0, page 88) +const uint8_t PES_1173713[] = { + 0x10, 0x03, 0x2c, 0xf6, 0xe4, 0xa8, 0xa8, 0x0b, 0x0b, 0xa8, 0x0b, 0xa8, + 0x0b, 0xf4, 0xa8, 0xb3, 0x83, 0x32, 0xa2, 0x73, 0x2a, 0xa2, 0x73, 0x23, + 0x83, 0x73, 0x2a, 0xcb, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}; + const uint32_t kPesPid = 123; } // namespace @@ -179,10 +238,12 @@ class EsParserTeletextTest : public ::testing::Test { void OnEmitTextSample(uint32_t pes_pid, std::shared_ptr text_sample) { text_sample_ = text_sample; + text_samples_.push_back(text_sample); } protected: std::shared_ptr stream_info_; + std::vector> text_samples_; std::shared_ptr text_sample_; }; @@ -230,6 +291,13 @@ TEST_F(EsParserTeletextTest, pes_283413_line_emitted_on_next_pes) { EXPECT_EQ(283413, text_sample_->start_time()); EXPECT_EQ(407876, text_sample_->EndTime()); EXPECT_EQ("Bon dia!", text_sample_->body().body); + EXPECT_EQ("black", text_sample_->body().style.backgroundColor); + EXPECT_EQ("white", text_sample_->body().style.color); + TextSettings settings = text_sample_->settings(); + EXPECT_EQ(TextAlignment::kCenter, settings.text_alignment); + EXPECT_TRUE(settings.line.has_value()); + EXPECT_EQ(11, settings.line.value().value); + EXPECT_EQ(TextUnitType::kLines, settings.line.value().type); } TEST_F(EsParserTeletextTest, multiple_lines_with_same_pts) { @@ -260,6 +328,98 @@ TEST_F(EsParserTeletextTest, multiple_lines_with_same_pts) { EXPECT_EQ("-Sí?", text_sample_->body().sub_fragments[0].body); EXPECT_TRUE(text_sample_->body().sub_fragments[1].newline); EXPECT_EQ("-Sí.", text_sample_->body().sub_fragments[2].body); + TextSettings settings = text_sample_->settings(); + EXPECT_EQ(10, settings.line.value().value); + EXPECT_EQ("ttx_10", settings.region); + EXPECT_EQ(1, text_samples_.size()); +} + +// separate_lines_with_slightly_different_pts has the original lines +// 18 and 22, with different alignment, which means that they should +// result in two parallel text samples. +TEST_F(EsParserTeletextTest, separate_lines_with_slightly_different_pts) { + auto on_new_stream = std::bind(&EsParserTeletextTest::OnNewStreamInfo, this, + kPesPid, std::placeholders::_1); + auto on_emit_text = std::bind(&EsParserTeletextTest::OnEmitTextSample, this, + kPesPid, std::placeholders::_1); + + std::unique_ptr es_parser_teletext(new EsParserTeletext( + kPesPid, on_new_stream, on_emit_text, DESCRIPTOR, 12)); + + auto parse_result = + es_parser_teletext->Parse(PES_867681, sizeof(PES_867681), 867681, 0); + EXPECT_TRUE(parse_result); + + parse_result = + es_parser_teletext->Parse(PES_871281, sizeof(PES_871281), 871281, 0); + EXPECT_TRUE(parse_result); + + parse_result = + es_parser_teletext->Parse(PES_1011695, sizeof(PES_1011695), 1011695, 0); + EXPECT_TRUE(parse_result); + + EXPECT_NE(nullptr, text_sample_.get()); + EXPECT_EQ(2, text_samples_.size()); + // The subtitles should get the same start and end time + EXPECT_EQ(867681, text_samples_[0]->start_time()); + EXPECT_EQ(867681, text_samples_[1]->start_time()); + EXPECT_EQ(1011695, text_samples_[0]->EndTime()); + EXPECT_EQ(1011695, text_samples_[0]->EndTime()); + EXPECT_EQ(1, text_samples_[0]->body().sub_fragments.size()); + EXPECT_EQ(1, text_samples_[1]->body().sub_fragments.size()); + EXPECT_EQ("-Luke !", text_samples_[0]->body().sub_fragments[0].body); + EXPECT_EQ("ttx_9", text_samples_[0]->settings().region); + EXPECT_EQ(TextAlignment::kLeft, text_samples_[0]->settings().text_alignment); + EXPECT_EQ("-Je vais aux cours d'été.", + text_samples_[1]->body().sub_fragments[0].body); + EXPECT_EQ(11, text_samples_[1]->settings().line.value().value); + EXPECT_EQ("ttx_11", text_samples_[1]->settings().region); + EXPECT_EQ(TextAlignment::kCenter, + text_samples_[1]->settings().text_alignment); +} + +// consecutive_lines_with_slightly_different_pts has the original lines +// 20 and 22 with same alignment, which means that they should +// result in one text sample with two lines. +TEST_F(EsParserTeletextTest, consecutive_lines_with_slightly_different_pts) { + auto on_new_stream = std::bind(&EsParserTeletextTest::OnNewStreamInfo, this, + kPesPid, std::placeholders::_1); + auto on_emit_text = std::bind(&EsParserTeletextTest::OnEmitTextSample, this, + kPesPid, std::placeholders::_1); + + std::unique_ptr es_parser_teletext(new EsParserTeletext( + kPesPid, on_new_stream, on_emit_text, DESCRIPTOR, 12)); + + auto parse_result = + es_parser_teletext->Parse(PES_1033297, sizeof(PES_1033297), 1033297, 0); + EXPECT_TRUE(parse_result); + + parse_result = + es_parser_teletext->Parse(PES_1036900, sizeof(PES_1036900), 1036900, 0); + EXPECT_TRUE(parse_result); + + parse_result = + es_parser_teletext->Parse(PES_1173713, sizeof(PES_1173713), 1173713, 0); + EXPECT_TRUE(parse_result); + + EXPECT_NE(nullptr, text_sample_.get()); + EXPECT_EQ(1, text_samples_.size()); + // The subtitles should get the same start and end time + EXPECT_EQ(1033297, text_sample_->start_time()); + EXPECT_EQ(1173713, text_sample_->EndTime()); + EXPECT_EQ(3, text_sample_->body().sub_fragments.size()); + TextSettings settings = text_sample_->settings(); + EXPECT_EQ(10, settings.line.value().value); + EXPECT_EQ("ttx_10", settings.region); + EXPECT_EQ(TextAlignment::kCenter, settings.text_alignment); + EXPECT_EQ("J'ai loupé", text_sample_->body().sub_fragments[0].body); + EXPECT_EQ("yellow", text_sample_->body().sub_fragments[0].style.color); + EXPECT_TRUE(text_sample_->body().sub_fragments[1].newline); + EXPECT_EQ("l'initiation à l'algèbre.", + text_sample_->body().sub_fragments[2].body); + EXPECT_EQ("yellow", + text_sample_->body().sub_fragments[2].style.backgroundColor); + EXPECT_EQ("blue", text_sample_->body().sub_fragments[2].style.color); } } // namespace mp2t diff --git a/packager/media/formats/mp2t/pes_packet_generator_unittest.cc b/packager/media/formats/mp2t/pes_packet_generator_unittest.cc index c47e8aa53f8..c3cb0021e08 100644 --- a/packager/media/formats/mp2t/pes_packet_generator_unittest.cc +++ b/packager/media/formats/mp2t/pes_packet_generator_unittest.cc @@ -84,6 +84,8 @@ const uint32_t kWidth = 1280; const uint32_t kHeight = 720; const uint32_t kPixelWidth = 1; const uint32_t kPixelHeight = 1; +const uint8_t kColorPrimaries = 0; +const uint8_t kMatrixCoefficients = 0; const uint8_t kTransferCharacteristics = 0; const uint16_t kTrickPlayFactor = 1; const uint8_t kNaluLengthSize = 1; @@ -125,8 +127,8 @@ std::shared_ptr CreateVideoStreamInfo(Codec codec) { kTrackId, kTimeScale, kDuration, codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kVideoExtraData, std::size(kVideoExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, - kIsEncrypted)); + kColorPrimaries, kMatrixCoefficients, kTransferCharacteristics, + kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); return stream_info; } @@ -358,8 +360,8 @@ TEST_F(PesPacketGeneratorTest, TimeStampScaling) { kTrackId, kTestTimescale, kDuration, kH264Codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kVideoExtraData, std::size(kVideoExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, - kIsEncrypted)); + kColorPrimaries, kMatrixCoefficients, kTransferCharacteristics, + kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); EXPECT_TRUE(generator_.Initialize(*stream_info)); EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets()); diff --git a/packager/media/formats/mp2t/ts_muxer.cc b/packager/media/formats/mp2t/ts_muxer.cc index c2a65f26531..636a1200f7d 100644 --- a/packager/media/formats/mp2t/ts_muxer.cc +++ b/packager/media/formats/mp2t/ts_muxer.cc @@ -82,7 +82,7 @@ Status TsMuxer::FinalizeSegment(size_t stream_id, options().segment_template.empty() ? options().output_file_name : GetSegmentName(options().segment_template, segment_start_timestamp, - segment_number_++, options().bandwidth); + segment_info.segment_number, options().bandwidth); const int64_t file_size = segmenter_->segment_buffer()->Size(); @@ -95,7 +95,8 @@ Status TsMuxer::FinalizeSegment(size_t stream_id, segment_path, segment_info.start_timestamp * segmenter_->timescale() + segmenter_->transport_stream_timestamp_offset(), - segment_info.duration * segmenter_->timescale(), file_size); + segment_info.duration * segmenter_->timescale(), file_size, + segment_info.segment_number); } segmenter_->set_segment_started(false); diff --git a/packager/media/formats/mp2t/ts_muxer.h b/packager/media/formats/mp2t/ts_muxer.h index d184c52587c..3a04efdbce8 100644 --- a/packager/media/formats/mp2t/ts_muxer.h +++ b/packager/media/formats/mp2t/ts_muxer.h @@ -41,9 +41,6 @@ class TsMuxer : public Muxer { int64_t sample_durations_[2] = {0, 0}; size_t num_samples_ = 0; - // Used in multi-segment mode for segment template. - uint64_t segment_number_ = 0; - // Used in single segment mode. std::unique_ptr output_file_; diff --git a/packager/media/formats/mp2t/ts_segmenter.h b/packager/media/formats/mp2t/ts_segmenter.h index e551feea877..8a4eb7bcba8 100644 --- a/packager/media/formats/mp2t/ts_segmenter.h +++ b/packager/media/formats/mp2t/ts_segmenter.h @@ -54,6 +54,7 @@ class TsSegmenter { /// stream's time scale. /// @param duration is the segment's duration in the input stream's time /// scale. + /// @param segment_number is the segment number. // TODO(kqyang): Remove the usage of segment start timestamp and duration in // xx_segmenter, which could cause confusions on which is the source of truth // as the segment start timestamp and duration could be tracked locally. diff --git a/packager/media/formats/mp2t/ts_segmenter_unittest.cc b/packager/media/formats/mp2t/ts_segmenter_unittest.cc index 8e3a45ba4ab..3f34856e226 100644 --- a/packager/media/formats/mp2t/ts_segmenter_unittest.cc +++ b/packager/media/formats/mp2t/ts_segmenter_unittest.cc @@ -45,6 +45,8 @@ const uint32_t kWidth = 1280; const uint32_t kHeight = 720; const uint32_t kPixelWidth = 1; const uint32_t kPixelHeight = 1; +const uint8_t kColorPrimaries = 0; +const uint8_t kMatrixCoefficients = 0; const uint8_t kTransferCharacteristics = 0; const uint16_t kTrickPlayFactor = 1; const uint8_t kNaluLengthSize = 1; @@ -84,6 +86,7 @@ class MockTsWriter : public TsWriter { MOCK_METHOD1(NewSegment, bool(BufferWriter* buffer_writer)); MOCK_METHOD0(SignalEncrypted, void()); + MOCK_METHOD0(FinalizeSegment, bool()); // Similar to the hack above but takes a std::unique_ptr. MOCK_METHOD2(AddPesPacketMock, bool(PesPacket* pes_packet, @@ -114,8 +117,8 @@ TEST_F(TsSegmenterTest, Initialize) { kTrackId, kTimeScale, kDuration, kH264Codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, std::size(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, - kIsEncrypted)); + kColorPrimaries, kMatrixCoefficients, kTransferCharacteristics, + kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; options.segment_template = "file$Number$.ts"; TsSegmenter segmenter(options, nullptr); @@ -134,8 +137,8 @@ TEST_F(TsSegmenterTest, AddSample) { kTrackId, kTimeScale, kDuration, kH264Codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, std::size(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, - kIsEncrypted)); + kColorPrimaries, kMatrixCoefficients, kTransferCharacteristics, + kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; options.segment_template = "file$Number$.ts"; TsSegmenter segmenter(options, nullptr); @@ -186,8 +189,8 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { kTrackId, kInputTimescale, kDuration, kH264Codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, std::size(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, - kIsEncrypted)); + kColorPrimaries, kMatrixCoefficients, kTransferCharacteristics, + kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; options.segment_template = "memory://file$Number$.ts"; @@ -232,7 +235,7 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets()) .InSequence(ready_pes_sequence) .WillOnce(Return(1u)); - + EXPECT_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets()) .InSequence(ready_pes_sequence) .WillOnce(Return(0u)); @@ -268,8 +271,8 @@ TEST_F(TsSegmenterTest, InitializeThenFinalize) { kTrackId, kTimeScale, kDuration, kH264Codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, std::size(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, - kIsEncrypted)); + kColorPrimaries, kMatrixCoefficients, kTransferCharacteristics, + kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; options.segment_template = "file$Number$.ts"; TsSegmenter segmenter(options, nullptr); @@ -296,8 +299,8 @@ TEST_F(TsSegmenterTest, FinalizeSegment) { kTrackId, kTimeScale, kDuration, kH264Codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, std::size(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, - kIsEncrypted)); + kColorPrimaries, kMatrixCoefficients, kTransferCharacteristics, + kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; options.segment_template = "file$Number$.ts"; TsSegmenter segmenter(options, nullptr); @@ -323,8 +326,8 @@ TEST_F(TsSegmenterTest, EncryptedSample) { kTrackId, kTimeScale, kDuration, kH264Codec, H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData, std::size(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight, - kTransferCharacteristics, kTrickPlayFactor, kNaluLengthSize, kLanguage, - kIsEncrypted)); + kColorPrimaries, kMatrixCoefficients, kTransferCharacteristics, + kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted)); MuxerOptions options; options.segment_template = "memory://file$Number$.ts"; diff --git a/packager/media/formats/mp2t/ts_writer.cc b/packager/media/formats/mp2t/ts_writer.cc index 78457c64fb8..1aeb3b9bbba 100644 --- a/packager/media/formats/mp2t/ts_writer.cc +++ b/packager/media/formats/mp2t/ts_writer.cc @@ -188,8 +188,7 @@ void TsWriter::SignalEncrypted() { } bool TsWriter::AddPesPacket(std::unique_ptr pes_packet, - BufferWriter* buffer) { - + BufferWriter* buffer) { if (!WritePesToBuffer(*pes_packet, &elementary_stream_continuity_counter_, buffer)) { LOG(ERROR) << "Failed to write pes to buffer."; diff --git a/packager/media/formats/mp4/box_definitions.cc b/packager/media/formats/mp4/box_definitions.cc index 81d175889ae..1f3cd260b4f 100644 --- a/packager/media/formats/mp4/box_definitions.cc +++ b/packager/media/formats/mp4/box_definitions.cc @@ -1633,6 +1633,16 @@ bool VideoSampleEntry::ReadWriteInternal(BoxBuffer* buffer) { extra_codec_configs.push_back(std::move(dv_box)); } } + const bool is_av1 = actual_format == FOURCC_av01; + if (is_av1) { + for (FourCC fourcc : {FOURCC_dvvC}) { + CodecConfiguration dv_box; + dv_box.box_type = fourcc; + RCHECK(buffer->TryReadWriteChild(&dv_box)); + if (!dv_box.data.empty()) + extra_codec_configs.push_back(std::move(dv_box)); + } + } } else { for (CodecConfiguration& extra_codec_config : extra_codec_configs) RCHECK(buffer->ReadWriteChild(&extra_codec_config)); diff --git a/packager/media/formats/mp4/low_latency_segment_segmenter.cc b/packager/media/formats/mp4/low_latency_segment_segmenter.cc index cbb1449acc3..22476461dc6 100644 --- a/packager/media/formats/mp4/low_latency_segment_segmenter.cc +++ b/packager/media/formats/mp4/low_latency_segment_segmenter.cc @@ -71,13 +71,13 @@ Status LowLatencySegmentSegmenter::DoFinalize() { return Status::OK; } -Status LowLatencySegmentSegmenter::DoFinalizeSegment() { +Status LowLatencySegmentSegmenter::DoFinalizeSegment(int64_t segment_number) { return FinalizeSegment(); } -Status LowLatencySegmentSegmenter::DoFinalizeChunk() { +Status LowLatencySegmentSegmenter::DoFinalizeChunk(int64_t segment_number) { if (is_initial_chunk_in_seg_) { - return WriteInitialChunk(); + return WriteInitialChunk(segment_number); } return WriteChunk(); } @@ -98,7 +98,7 @@ Status LowLatencySegmentSegmenter::WriteInitSegment() { return buffer->WriteToFile(file.get()); } -Status LowLatencySegmentSegmenter::WriteInitialChunk() { +Status LowLatencySegmentSegmenter::WriteInitialChunk(int64_t segment_number) { DCHECK(sidx()); DCHECK(fragment_buffer()); DCHECK(styp_); @@ -161,9 +161,9 @@ Status LowLatencySegmentSegmenter::WriteInitialChunk() { } // Add the current segment in the manifest. // Following chunks will be appended to the open segment file. - muxer_listener()->OnNewSegment(file_name_, - sidx()->earliest_presentation_time, - segment_duration, segment_size_); + muxer_listener()->OnNewSegment( + file_name_, sidx()->earliest_presentation_time, segment_duration, + segment_size_, segment_number); is_initial_chunk_in_seg_ = false; } diff --git a/packager/media/formats/mp4/low_latency_segment_segmenter.h b/packager/media/formats/mp4/low_latency_segment_segmenter.h index 0db73df8b03..5f2e577ddad 100644 --- a/packager/media/formats/mp4/low_latency_segment_segmenter.h +++ b/packager/media/formats/mp4/low_latency_segment_segmenter.h @@ -44,13 +44,13 @@ class LowLatencySegmentSegmenter : public Segmenter { // Segmenter implementation overrides. Status DoInitialize() override; Status DoFinalize() override; - Status DoFinalizeSegment() override; - Status DoFinalizeChunk() override; + Status DoFinalizeSegment(int64_t segment_number) override; + Status DoFinalizeChunk(int64_t segment_number) override; // Write segment to file. Status WriteInitSegment(); Status WriteChunk(); - Status WriteInitialChunk(); + Status WriteInitialChunk(int64_t segment_number); Status FinalizeSegment(); uint64_t GetSegmentDuration(); diff --git a/packager/media/formats/mp4/mp4_media_parser.cc b/packager/media/formats/mp4/mp4_media_parser.cc index b3be126385f..7aac01590f3 100644 --- a/packager/media/formats/mp4/mp4_media_parser.cc +++ b/packager/media/formats/mp4/mp4_media_parser.cc @@ -37,6 +37,13 @@ #include #include +ABSL_FLAG(bool, + use_dovi_supplemental_codecs, + false, + "Set to true to signal DolbyVision using the modern supplemental " + "codecs approach instead of the legacy " + "duplicate representations approach"); + namespace shaka { namespace media { namespace mp4 { @@ -156,6 +163,7 @@ bool UpdateCodecStringForDolbyVision( switch (actual_format) { case FOURCC_dvh1: case FOURCC_dvhe: + case FOURCC_dav1: // Non-Backward compatibility mode. Replace the code string with // Dolby Vision only. *codec_string = dovi_config.GetCodecString(actual_format); @@ -169,6 +177,9 @@ bool UpdateCodecStringForDolbyVision( // See above. *codec_string += ";" + dovi_config.GetCodecString(FOURCC_dvh1); break; + case FOURCC_av01: + *codec_string += ";" + dovi_config.GetCodecString(FOURCC_dav1); + break; default: LOG(ERROR) << "Unsupported format with extra codec " << FourCCToString(actual_format); @@ -177,6 +188,48 @@ bool UpdateCodecStringForDolbyVision( return true; } +bool UpdateDolbyVisionInfo(FourCC actual_format, + const std::vector& configs, + uint8_t transfer_characteristics, + std::string* codec_string, + std::string* dovi_supplemental_codec_string, + FourCC* dovi_compatible_brand) { + DOVIDecoderConfigurationRecord dovi_config; + if (!dovi_config.Parse(GetDOVIDecoderConfig(configs))) { + LOG(ERROR) << "Failed to parse Dolby Vision decoder " + "configuration record."; + return false; + } + switch (actual_format) { + case FOURCC_dvh1: + case FOURCC_dvhe: + case FOURCC_dav1: + // Non-Backward compatibility mode. Replace the code string with + // Dolby Vision only. + *codec_string = dovi_config.GetCodecString(actual_format); + break; + case FOURCC_hev1: + // Backward compatibility mode. Use supplemental codec indicating Dolby + // Dolby Vision content. + *dovi_supplemental_codec_string = dovi_config.GetCodecString(FOURCC_dvhe); + break; + case FOURCC_hvc1: + // See above. + *dovi_supplemental_codec_string = dovi_config.GetCodecString(FOURCC_dvh1); + break; + case FOURCC_av01: + *dovi_supplemental_codec_string = dovi_config.GetCodecString(FOURCC_dav1); + break; + default: + LOG(ERROR) << "Unsupported format with extra codec " + << FourCCToString(actual_format); + return false; + } + *dovi_compatible_brand = + dovi_config.GetDoViCompatibleBrand(transfer_characteristics); + return true; +} + const uint64_t kNanosecondsPerSecond = 1000000000ull; } // namespace @@ -391,6 +444,9 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { std::vector> streams; + bool use_dovi_supplemental = + absl::GetFlag(FLAGS_use_dovi_supplemental_codecs); + for (std::vector::const_iterator track = moov_->tracks.begin(); track != moov_->tracks.end(); ++track) { const int32_t timescale = track->media.header.timescale; @@ -604,8 +660,12 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { &pixel_height); } std::string codec_string; + std::string dovi_supplemental_codec_string(""); + FourCC dovi_compatible_brand = FOURCC_NULL; uint8_t nalu_length_size = 0; uint8_t transfer_characteristics = 0; + uint8_t color_primaries = 0; + uint8_t matrix_coefficients = 0; const FourCC actual_format = entry.GetActualFormat(); const Codec video_codec = FourCCToCodec(actual_format); @@ -618,13 +678,34 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { } // Generate the full codec string if the colr atom is present. if (entry.colr.color_parameter_type != FOURCC_NULL) { + transfer_characteristics = entry.colr.transfer_characteristics; + color_primaries = entry.colr.color_primaries; + matrix_coefficients = entry.colr.matrix_coefficients; codec_string = av1_config.GetCodecString( - entry.colr.color_primaries, entry.colr.transfer_characteristics, - entry.colr.matrix_coefficients, + color_primaries, transfer_characteristics, matrix_coefficients, entry.colr.video_full_range_flag); } else { codec_string = av1_config.GetCodecString(); } + + if (!entry.extra_codec_configs.empty()) { + // |extra_codec_configs| is present only for Dolby Vision. + if (use_dovi_supplemental) { + if (!UpdateDolbyVisionInfo( + actual_format, entry.extra_codec_configs, + transfer_characteristics, &codec_string, + &dovi_supplemental_codec_string, + &dovi_compatible_brand)) { + return false; + } + } else { + if (!UpdateCodecStringForDolbyVision(actual_format, + entry.extra_codec_configs, + &codec_string)) { + return false; + } + } + } break; } case FOURCC_avc1: @@ -637,6 +718,8 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { codec_string = avc_config.GetCodecString(actual_format); nalu_length_size = avc_config.nalu_length_size(); transfer_characteristics = avc_config.transfer_characteristics(); + color_primaries = avc_config.color_primaries(); + matrix_coefficients = avc_config.matrix_coefficients(); // Use configurations from |avc_config| if it is valid. if (avc_config.coded_width() != 0) { @@ -685,12 +768,25 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { codec_string = hevc_config.GetCodecString(actual_format); nalu_length_size = hevc_config.nalu_length_size(); transfer_characteristics = hevc_config.transfer_characteristics(); + color_primaries = hevc_config.color_primaries(); + matrix_coefficients = hevc_config.matrix_coefficients(); if (!entry.extra_codec_configs.empty()) { // |extra_codec_configs| is present only for Dolby Vision. - if (!UpdateCodecStringForDolbyVision( - actual_format, entry.extra_codec_configs, &codec_string)) { - return false; + if (use_dovi_supplemental) { + if (!UpdateDolbyVisionInfo( + actual_format, entry.extra_codec_configs, + transfer_characteristics, &codec_string, + &dovi_supplemental_codec_string, + &dovi_compatible_brand)) { + return false; + } + } else { + if (!UpdateCodecStringForDolbyVision(actual_format, + entry.extra_codec_configs, + &codec_string)) { + return false; + } } } break; @@ -733,10 +829,16 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { track->header.track_id, timescale, duration, video_codec, GetH26xStreamFormat(actual_format), codec_string, codec_configuration_data.data(), codec_configuration_data.size(), - coded_width, coded_height, pixel_width, pixel_height, - transfer_characteristics, + coded_width, coded_height, pixel_width, pixel_height, color_primaries, + matrix_coefficients, transfer_characteristics, 0, // trick_play_factor nalu_length_size, track->media.header.language.code, is_encrypted)); + + if (use_dovi_supplemental) { + video_stream_info->set_supplemental_codec( + dovi_supplemental_codec_string); + video_stream_info->set_compatible_brand(dovi_compatible_brand); + } video_stream_info->set_extra_config(entry.ExtraCodecConfigsAsVector()); video_stream_info->set_colr_data((entry.colr.raw_box).data(), (entry.colr.raw_box).size()); diff --git a/packager/media/formats/mp4/mp4_media_parser.h b/packager/media/formats/mp4/mp4_media_parser.h index ffa5687a679..4463aae95c1 100644 --- a/packager/media/formats/mp4/mp4_media_parser.h +++ b/packager/media/formats/mp4/mp4_media_parser.h @@ -12,11 +12,16 @@ #include #include +#include +#include + #include #include #include #include +ABSL_DECLARE_FLAG(bool, use_dovi_supplemental_codecs); + namespace shaka { namespace media { namespace mp4 { diff --git a/packager/media/formats/mp4/mp4_muxer.cc b/packager/media/formats/mp4/mp4_muxer.cc index a5992838395..b681dc448c8 100644 --- a/packager/media/formats/mp4/mp4_muxer.cc +++ b/packager/media/formats/mp4/mp4_muxer.cc @@ -209,7 +209,8 @@ Status MP4Muxer::FinalizeSegment(size_t stream_id, DCHECK(segmenter_); VLOG(3) << "Finalizing " << (segment_info.is_subsegment ? "sub" : "") << "segment " << segment_info.start_timestamp << " duration " - << segment_info.duration; + << segment_info.duration << " segment number " + << segment_info.segment_number; return segmenter_->FinalizeSegment(stream_id, segment_info); } @@ -236,8 +237,22 @@ Status MP4Muxer::DelayInitializeMuxer() { ftyp->compatible_brands.push_back(codec_fourcc); // https://professional.dolby.com/siteassets/content-creation/dolby-vision-for-content-creators/dolby_vision_bitstreams_within_the_iso_base_media_file_format_dec2017.pdf - if (streams()[0].get()->codec_string().find("dvh") != std::string::npos) + std::string codec_string = + static_cast(streams()[0].get()) + ->codec_string(); + std::string supplemental_codec_string = + static_cast(streams()[0].get()) + ->supplemental_codec(); + if (codec_string.find("dvh") != std::string::npos || + supplemental_codec_string.find("dvh") != std::string::npos || + codec_string.find("dav1") != std::string::npos || + supplemental_codec_string.find("dav1") != std::string::npos) ftyp->compatible_brands.push_back(FOURCC_dby1); + FourCC extra_brand = + static_cast(streams()[0].get()) + ->compatible_brand(); + if (extra_brand != FOURCC_NULL) + ftyp->compatible_brands.push_back(extra_brand); } // CMAF allows only one track/stream per file. diff --git a/packager/media/formats/mp4/multi_segment_segmenter.cc b/packager/media/formats/mp4/multi_segment_segmenter.cc index 5381a308e75..bab13ed39ea 100644 --- a/packager/media/formats/mp4/multi_segment_segmenter.cc +++ b/packager/media/formats/mp4/multi_segment_segmenter.cc @@ -31,8 +31,7 @@ MultiSegmentSegmenter::MultiSegmentSegmenter(const MuxerOptions& options, std::unique_ptr ftyp, std::unique_ptr moov) : Segmenter(options, std::move(ftyp), std::move(moov)), - styp_(new SegmentType), - num_segments_(0) { + styp_(new SegmentType) { // Use the same brands for styp as ftyp. styp_->major_brand = Segmenter::ftyp()->major_brand; styp_->compatible_brands = Segmenter::ftyp()->compatible_brands; @@ -70,8 +69,8 @@ Status MultiSegmentSegmenter::DoFinalize() { return Status::OK; } -Status MultiSegmentSegmenter::DoFinalizeSegment() { - return WriteSegment(); +Status MultiSegmentSegmenter::DoFinalizeSegment(int64_t segment_number) { + return WriteSegment(segment_number); } Status MultiSegmentSegmenter::WriteInitSegment() { @@ -90,7 +89,7 @@ Status MultiSegmentSegmenter::WriteInitSegment() { return buffer->WriteToFile(file.get()); } -Status MultiSegmentSegmenter::WriteSegment() { +Status MultiSegmentSegmenter::WriteSegment(int64_t segment_number) { DCHECK(sidx()); DCHECK(fragment_buffer()); DCHECK(styp_); @@ -115,7 +114,7 @@ Status MultiSegmentSegmenter::WriteSegment() { } else { file_name = GetSegmentName(options().segment_template, sidx()->earliest_presentation_time, - num_segments_++, options().bandwidth); + segment_number, options().bandwidth); file.reset(File::Open(file_name.c_str(), "w")); if (!file) { return Status(error::FILE_FAILURE, @@ -160,9 +159,9 @@ Status MultiSegmentSegmenter::WriteSegment() { UpdateProgress(segment_duration); if (muxer_listener()) { muxer_listener()->OnSampleDurationReady(sample_duration()); - muxer_listener()->OnNewSegment(file_name, - sidx()->earliest_presentation_time, - segment_duration, segment_size); + muxer_listener()->OnNewSegment( + file_name, sidx()->earliest_presentation_time, segment_duration, + segment_size, segment_number); } return Status::OK; diff --git a/packager/media/formats/mp4/multi_segment_segmenter.h b/packager/media/formats/mp4/multi_segment_segmenter.h index ab19465d7d8..3fd206dbbb9 100644 --- a/packager/media/formats/mp4/multi_segment_segmenter.h +++ b/packager/media/formats/mp4/multi_segment_segmenter.h @@ -39,14 +39,13 @@ class MultiSegmentSegmenter : public Segmenter { // Segmenter implementation overrides. Status DoInitialize() override; Status DoFinalize() override; - Status DoFinalizeSegment() override; + Status DoFinalizeSegment(int64_t segment_number) override; // Write segment to file. Status WriteInitSegment(); - Status WriteSegment(); + Status WriteSegment(int64_t segment_number); std::unique_ptr styp_; - uint32_t num_segments_; DISALLOW_COPY_AND_ASSIGN(MultiSegmentSegmenter); }; diff --git a/packager/media/formats/mp4/segmenter.cc b/packager/media/formats/mp4/segmenter.cc index 9313a0eb5e4..a9b180a258e 100644 --- a/packager/media/formats/mp4/segmenter.cc +++ b/packager/media/formats/mp4/segmenter.cc @@ -234,14 +234,15 @@ Status Segmenter::FinalizeSegment(size_t stream_id, if (segment_info.is_chunk) { // Finalize the completed chunk for the LL-DASH case. - status = DoFinalizeChunk(); + status = DoFinalizeChunk(segment_info.segment_number); if (!status.ok()) return status; } if (!segment_info.is_subsegment || segment_info.is_final_chunk_in_seg) { // Finalize the segment. - status = DoFinalizeSegment(); + status = DoFinalizeSegment(segment_info.segment_number); + // Reset segment information to initial state. sidx_->references.clear(); key_frame_infos_.clear(); diff --git a/packager/media/formats/mp4/segmenter.h b/packager/media/formats/mp4/segmenter.h index df488121222..51e659b35cb 100644 --- a/packager/media/formats/mp4/segmenter.h +++ b/packager/media/formats/mp4/segmenter.h @@ -127,9 +127,8 @@ class Segmenter { private: virtual Status DoInitialize() = 0; virtual Status DoFinalize() = 0; - virtual Status DoFinalizeSegment() = 0; - - virtual Status DoFinalizeChunk() { return Status::OK; } + virtual Status DoFinalizeSegment(int64_t segment_number) = 0; + virtual Status DoFinalizeChunk(int64_t segment_number) { return Status::OK; } uint32_t GetReferenceStreamId(); diff --git a/packager/media/formats/mp4/single_segment_segmenter.cc b/packager/media/formats/mp4/single_segment_segmenter.cc index 13527ce446d..afedf4ac7a1 100644 --- a/packager/media/formats/mp4/single_segment_segmenter.cc +++ b/packager/media/formats/mp4/single_segment_segmenter.cc @@ -166,7 +166,7 @@ Status SingleSegmentSegmenter::DoFinalize() { return Status::OK; } -Status SingleSegmentSegmenter::DoFinalizeSegment() { +Status SingleSegmentSegmenter::DoFinalizeSegment(int64_t segment_number) { DCHECK(sidx()); DCHECK(fragment_buffer()); // sidx() contains pre-generated segment references with one reference per @@ -224,9 +224,9 @@ Status SingleSegmentSegmenter::DoFinalizeSegment() { UpdateProgress(vod_ref.subsegment_duration); if (muxer_listener()) { muxer_listener()->OnSampleDurationReady(sample_duration()); - muxer_listener()->OnNewSegment(options().output_file_name, - vod_ref.earliest_presentation_time, - vod_ref.subsegment_duration, segment_size); + muxer_listener()->OnNewSegment( + options().output_file_name, vod_ref.earliest_presentation_time, + vod_ref.subsegment_duration, segment_size, segment_number); } return Status::OK; } diff --git a/packager/media/formats/mp4/single_segment_segmenter.h b/packager/media/formats/mp4/single_segment_segmenter.h index edfe618f8eb..02fe763088b 100644 --- a/packager/media/formats/mp4/single_segment_segmenter.h +++ b/packager/media/formats/mp4/single_segment_segmenter.h @@ -44,7 +44,7 @@ class SingleSegmentSegmenter : public Segmenter { // Segmenter implementation overrides. Status DoInitialize() override; Status DoFinalize() override; - Status DoFinalizeSegment() override; + Status DoFinalizeSegment(int64_t segment_number) override; std::unique_ptr vod_sidx_; std::string temp_file_name_; diff --git a/packager/media/formats/packed_audio/packed_audio_writer.cc b/packager/media/formats/packed_audio/packed_audio_writer.cc index f73e09faa0c..14a3d2febb1 100644 --- a/packager/media/formats/packed_audio/packed_audio_writer.cc +++ b/packager/media/formats/packed_audio/packed_audio_writer.cc @@ -81,7 +81,7 @@ Status PackedAudioWriter::FinalizeSegment(size_t stream_id, options().segment_template.empty() ? options().output_file_name : GetSegmentName(options().segment_template, segment_timestamp, - segment_number_++, options().bandwidth); + segment_info.segment_number, options().bandwidth); // Save |segment_size| as it will be cleared after writing. const size_t segment_size = segmenter_->segment_buffer()->Size(); @@ -92,7 +92,8 @@ Status PackedAudioWriter::FinalizeSegment(size_t stream_id, if (muxer_listener()) { muxer_listener()->OnNewSegment( segment_path, segment_timestamp + transport_stream_timestamp_offset_, - segment_info.duration * segmenter_->TimescaleScale(), segment_size); + segment_info.duration * segmenter_->TimescaleScale(), segment_size, + segment_info.segment_number); } return Status::OK; } diff --git a/packager/media/formats/packed_audio/packed_audio_writer_unittest.cc b/packager/media/formats/packed_audio/packed_audio_writer_unittest.cc index 910e8fa3a06..637b4983827 100644 --- a/packager/media/formats/packed_audio/packed_audio_writer_unittest.cc +++ b/packager/media/formats/packed_audio/packed_audio_writer_unittest.cc @@ -44,6 +44,8 @@ const char kOutputFile[] = "memory://test.aac"; const char kSegmentTemplate[] = "memory://test_$Number$.aac"; const char kSegment1Name[] = "memory://test_1.aac"; const char kSegment2Name[] = "memory://test_2.aac"; +const int64_t kSegmentNumber1 = 1; +const int64_t kSegmentNumber2 = 2; class MockPackedAudioSegmenter : public PackedAudioSegmenter { public: @@ -122,9 +124,10 @@ TEST_P(PackedAudioWriterTest, SubsegmentIgnored) { const int64_t kDuration = 100; const bool kSubsegment = true; auto subsegment_stream_data = StreamData::FromSegmentInfo( - kStreamIndex, GetSegmentInfo(kTimestamp, kDuration, kSubsegment)); + kStreamIndex, + GetSegmentInfo(kTimestamp, kDuration, kSubsegment, kSegmentNumber1)); - EXPECT_CALL(*mock_muxer_listener_ptr_, OnNewSegment(_, _, _, _)).Times(0); + EXPECT_CALL(*mock_muxer_listener_ptr_, OnNewSegment(_, _, _, _, _)).Times(0); EXPECT_CALL(*mock_segmenter_ptr_, FinalizeSegment()).Times(0); ASSERT_OK(Input(kInput)->Dispatch(std::move(subsegment_stream_data))); } @@ -137,7 +140,8 @@ TEST_P(PackedAudioWriterTest, OneSegment) { const int64_t kDuration = 100; const bool kSubsegment = true; auto segment_stream_data = StreamData::FromSegmentInfo( - kStreamIndex, GetSegmentInfo(kTimestamp, kDuration, !kSubsegment)); + kStreamIndex, + GetSegmentInfo(kTimestamp, kDuration, !kSubsegment, kSegmentNumber1)); const double kMockTimescaleScale = 10; const char kMockSegmentData[] = "hello segment 1"; @@ -147,7 +151,8 @@ TEST_P(PackedAudioWriterTest, OneSegment) { *mock_muxer_listener_ptr_, OnNewSegment(is_single_segment_mode_ ? kOutputFile : kSegment1Name, kTimestamp * kMockTimescaleScale, - kDuration * kMockTimescaleScale, kSegmentDataSize)); + kDuration * kMockTimescaleScale, kSegmentDataSize, + kSegmentNumber1)); EXPECT_CALL(*mock_segmenter_ptr_, TimescaleScale()) .WillRepeatedly(Return(kMockTimescaleScale)); @@ -190,10 +195,11 @@ TEST_P(PackedAudioWriterTest, TwoSegments) { const int64_t kDuration = 100; const bool kSubsegment = true; auto segment1_stream_data = StreamData::FromSegmentInfo( - kStreamIndex, GetSegmentInfo(kTimestamp, kDuration, !kSubsegment)); - auto segment2_stream_data = StreamData::FromSegmentInfo( kStreamIndex, - GetSegmentInfo(kTimestamp + kDuration, kDuration, !kSubsegment)); + GetSegmentInfo(kTimestamp, kDuration, !kSubsegment, kSegmentNumber1)); + auto segment2_stream_data = StreamData::FromSegmentInfo( + kStreamIndex, GetSegmentInfo(kTimestamp + kDuration, kDuration, + !kSubsegment, kSegmentNumber2)); const double kMockTimescaleScale = 10; const char kMockSegment1Data[] = "hello segment 1"; @@ -206,12 +212,13 @@ TEST_P(PackedAudioWriterTest, TwoSegments) { OnNewSegment(is_single_segment_mode_ ? kOutputFile : kSegment1Name, kTimestamp * kMockTimescaleScale, kDuration * kMockTimescaleScale, - sizeof(kMockSegment1Data) - 1)); + sizeof(kMockSegment1Data) - 1, kSegmentNumber1)); EXPECT_CALL( *mock_muxer_listener_ptr_, OnNewSegment(is_single_segment_mode_ ? kOutputFile : kSegment2Name, (kTimestamp + kDuration) * kMockTimescaleScale, - kDuration * kMockTimescaleScale, kSegment2DataSize)); + kDuration * kMockTimescaleScale, kSegment2DataSize, + kSegmentNumber2)); EXPECT_CALL(*mock_segmenter_ptr_, TimescaleScale()) .WillRepeatedly(Return(kMockTimescaleScale)); diff --git a/packager/media/formats/ttml/ttml_generator.cc b/packager/media/formats/ttml/ttml_generator.cc index e7fc596fc3a..230582cb4fa 100644 --- a/packager/media/formats/ttml/ttml_generator.cc +++ b/packager/media/formats/ttml/ttml_generator.cc @@ -18,6 +18,7 @@ namespace ttml { namespace { constexpr const char* kRegionIdPrefix = "_shaka_region_"; +constexpr const char* kRegionTeletextPrefix = "ttx_"; std::string ToTtmlTime(int64_t time, int32_t timescale) { int64_t remaining = time * 1000 / timescale; @@ -54,6 +55,18 @@ void TtmlGenerator::Initialize(const std::map& regions, regions_ = regions; language_ = language; time_scale_ = time_scale; + // Add ebu_tt_d_regions + float step = 74.1f / 11; + for (int i = 0; i < 12; i++) { + TextRegion region; + float verPos = 10.0 + int(float(step) * float(i)); + region.width = TextNumber(80, TextUnitType::kPercent); + region.height = TextNumber(15, TextUnitType::kPercent); + region.window_anchor_x = TextNumber(10, TextUnitType::kPercent); + region.window_anchor_y = TextNumber(verPos, TextUnitType::kPercent); + const std::string id = kRegionTeletextPrefix + std::to_string(i); + regions_.emplace(id, region); + } } void TtmlGenerator::AddSample(const TextSample& sample) { @@ -66,72 +79,92 @@ void TtmlGenerator::Reset() { bool TtmlGenerator::Dump(std::string* result) const { xml::XmlNode root("tt"); + bool ebuTTDFormat = isEbuTTTD(); RCHECK(root.SetStringAttribute("xmlns", kTtNamespace)); RCHECK(root.SetStringAttribute("xmlns:tts", "http://www.w3.org/ns/ttml#styling")); - - bool did_log = false; - xml::XmlNode head("head"); + RCHECK(root.SetStringAttribute("xmlns:tts", + "http://www.w3.org/ns/ttml#styling")); RCHECK(root.SetStringAttribute("xml:lang", language_)); - for (const auto& pair : regions_) { - if (!did_log && (pair.second.region_anchor_x.value != 0 && - pair.second.region_anchor_y.value != 0)) { - LOG(WARNING) << "TTML doesn't support non-0 region anchor"; - did_log = true; - } - xml::XmlNode region("region"); - const auto origin = - ToTtmlSize(pair.second.window_anchor_x, pair.second.window_anchor_y); - const auto extent = ToTtmlSize(pair.second.width, pair.second.height); - RCHECK(region.SetStringAttribute("xml:id", pair.first)); - RCHECK(region.SetStringAttribute("tts:origin", origin)); - RCHECK(region.SetStringAttribute("tts:extent", extent)); - RCHECK(head.AddChild(std::move(region))); + if (ebuTTDFormat) { + RCHECK(root.SetStringAttribute("xmlns:ttp", + "http://www.w3.org/ns/ttml#parameter")); + RCHECK(root.SetStringAttribute("xmlns:ttm", + "http://www.w3.org/ns/ttml#metadata")); + RCHECK(root.SetStringAttribute("xmlns:ebuttm", "urn:ebu:tt:metadata")); + RCHECK(root.SetStringAttribute("xmlns:ebutts", "urn:ebu:tt:style")); + RCHECK(root.SetStringAttribute("xml:space", "default")); + RCHECK(root.SetStringAttribute("ttp:timeBase", "media")); + RCHECK(root.SetStringAttribute("ttp:cellResolution", "32 15")); } - RCHECK(root.AddChild(std::move(head))); - size_t image_count = 0; + xml::XmlNode head("head"); + xml::XmlNode styling("styling"); xml::XmlNode metadata("metadata"); + xml::XmlNode layout("layout"); + RCHECK(addRegions(layout)); + xml::XmlNode body("body"); + if (ebuTTDFormat) { + RCHECK(body.SetStringAttribute("style", "default")); + } + size_t image_count = 0; + std::unordered_set fragmentStyles; xml::XmlNode div("div"); for (const auto& sample : samples_) { - RCHECK(AddSampleToXml(sample, &div, &metadata, &image_count)); + RCHECK( + AddSampleToXml(sample, &div, &metadata, fragmentStyles, &image_count)); } - RCHECK(body.AddChild(std::move(div))); if (image_count > 0) { RCHECK(root.SetStringAttribute( "xmlns:smpte", "http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt")); - RCHECK(root.AddChild(std::move(metadata))); } + RCHECK(body.AddChild(std::move(div))); + RCHECK(head.AddChild(std::move(metadata))); + RCHECK(addStyling(styling, fragmentStyles)); + RCHECK(head.AddChild(std::move(styling))); + RCHECK(head.AddChild(std::move(layout))); + RCHECK(root.AddChild(std::move(head))); + RCHECK(root.AddChild(std::move(body))); *result = root.ToString(/* comment= */ ""); return true; } -bool TtmlGenerator::AddSampleToXml(const TextSample& sample, - xml::XmlNode* body, - xml::XmlNode* metadata, - size_t* image_count) const { +bool TtmlGenerator::AddSampleToXml( + const TextSample& sample, + xml::XmlNode* body, + xml::XmlNode* metadata, + std::unordered_set& fragmentStyles, + size_t* image_count) const { xml::XmlNode p("p"); - RCHECK(p.SetStringAttribute("xml:space", "preserve")); + if (!isEbuTTTD()) { + RCHECK(p.SetStringAttribute("xml:space", "preserve")); + } RCHECK(p.SetStringAttribute("begin", ToTtmlTime(sample.start_time(), time_scale_))); RCHECK( p.SetStringAttribute("end", ToTtmlTime(sample.EndTime(), time_scale_))); - RCHECK(ConvertFragmentToXml(sample.body(), &p, metadata, image_count)); + RCHECK(ConvertFragmentToXml(sample.body(), &p, metadata, fragmentStyles, + image_count)); if (!sample.id().empty()) RCHECK(p.SetStringAttribute("xml:id", sample.id())); const auto& settings = sample.settings(); - if (settings.line || settings.position || settings.width || settings.height) { - // TTML positioning needs to be from a region. - if (!settings.region.empty()) { - LOG(WARNING) - << "Using both text regions and positioning isn't supported in TTML"; + bool regionFound = false; + if (!settings.region.empty()) { + auto reg = regions_.find(settings.region); + if (reg != regions_.end()) { + regionFound = true; + RCHECK(p.SetStringAttribute("region", settings.region)); } + } + if (!regionFound && (settings.line || settings.position || settings.width || + settings.height)) { + // TTML positioning needs to be from a region. const auto origin = ToTtmlSize( settings.position.value_or(TextNumber(0, TextUnitType::kPixels)), settings.line.value_or(TextNumber(0, TextUnitType::kPixels))); @@ -146,8 +179,6 @@ bool TtmlGenerator::AddSampleToXml(const TextSample& sample, RCHECK(region.SetStringAttribute("tts:extent", extent)); RCHECK(p.SetStringAttribute("region", id)); RCHECK(body->AddChild(std::move(region))); - } else if (!settings.region.empty()) { - RCHECK(p.SetStringAttribute("region", settings.region)); } if (settings.writing_direction != WritingDirection::kHorizontal) { @@ -179,19 +210,22 @@ bool TtmlGenerator::AddSampleToXml(const TextSample& sample, return true; } -bool TtmlGenerator::ConvertFragmentToXml(const TextFragment& body, - xml::XmlNode* parent, - xml::XmlNode* metadata, - size_t* image_count) const { +bool TtmlGenerator::ConvertFragmentToXml( + const TextFragment& body, + xml::XmlNode* parent, + xml::XmlNode* metadata, + std::unordered_set& fragmentStyles, + size_t* image_count) const { if (body.newline) { xml::XmlNode br("br"); return parent->AddChild(std::move(br)); } - - // If we have new styles, add a new . xml::XmlNode span("span"); xml::XmlNode* node = parent; - if (body.style.bold || body.style.italic || body.style.underline) { + bool useSpan = + (body.style.bold || body.style.italic || body.style.underline || + !body.style.color.empty() || !body.style.backgroundColor.empty()); + if (useSpan) { node = &span; if (body.style.bold) { RCHECK(span.SetStringAttribute("tts:fontWeight", @@ -206,6 +240,20 @@ bool TtmlGenerator::ConvertFragmentToXml(const TextFragment& body, "tts:textDecoration", *body.style.underline ? "underline" : "noUnderline")); } + std::string color = "white"; + std::string backgroundColor = "black"; + + if (!body.style.color.empty()) { + color = body.style.color; + } + + if (!body.style.backgroundColor.empty()) { + backgroundColor = body.style.backgroundColor; + } + + const std::string fragStyle = color + "_" + backgroundColor; + fragmentStyles.insert(fragStyle); + RCHECK(span.SetStringAttribute("style", fragStyle)); } if (!body.body.empty()) { @@ -226,16 +274,91 @@ bool TtmlGenerator::ConvertFragmentToXml(const TextFragment& body, RCHECK(node->SetStringAttribute("smpte:backgroundImage", "#" + id)); } else { for (const auto& frag : body.sub_fragments) { - if (!ConvertFragmentToXml(frag, node, metadata, image_count)) + if (!ConvertFragmentToXml(frag, node, metadata, fragmentStyles, + image_count)) return false; } } - if (body.style.bold || body.style.italic || body.style.underline) + if (useSpan) RCHECK(parent->AddChild(std::move(span))); return true; } +std::vector TtmlGenerator::usedRegions() const { + std::vector uRegions; + for (const auto& sample : samples_) { + if (!sample.settings().region.empty()) { + uRegions.push_back(sample.settings().region); + } + } + return uRegions; +} + +bool TtmlGenerator::addRegions(xml::XmlNode& layout) const { + auto regNames = usedRegions(); + for (const auto& r : regions_) { + bool used = false; + for (const auto& name : regNames) { + if (r.first == name) { + used = true; + } + } + if (used) { + xml::XmlNode region("region"); + const auto origin = + ToTtmlSize(r.second.window_anchor_x, r.second.window_anchor_y); + const auto extent = ToTtmlSize(r.second.width, r.second.height); + RCHECK(region.SetStringAttribute("xml:id", r.first)); + RCHECK(region.SetStringAttribute("tts:origin", origin)); + RCHECK(region.SetStringAttribute("tts:extent", extent)); + RCHECK(region.SetStringAttribute("tts:overflow", "visible")); + RCHECK(layout.AddChild(std::move(region))); + } + } + return true; +} + +bool TtmlGenerator::addStyling( + xml::XmlNode& styling, + const std::unordered_set& fragmentStyles) const { + if (fragmentStyles.empty()) { + return true; + } + // Add default style + xml::XmlNode defaultStyle("style"); + RCHECK(defaultStyle.SetStringAttribute("xml:id", "default")); + RCHECK(defaultStyle.SetStringAttribute("tts:fontStyle", "normal")); + RCHECK(defaultStyle.SetStringAttribute("tts:fontFamily", "sansSerif")); + RCHECK(defaultStyle.SetStringAttribute("tts:fontSize", "100%")); + RCHECK(defaultStyle.SetStringAttribute("tts:lineHeight", "normal")); + RCHECK(defaultStyle.SetStringAttribute("tts:textAlign", "center")); + RCHECK(defaultStyle.SetStringAttribute("ebutts:linePadding", "0.5c")); + RCHECK(styling.AddChild(std::move(defaultStyle))); + + for (const auto& name : fragmentStyles) { + auto pos = name.find('_'); + auto color = name.substr(0, pos); + auto backgroundColor = name.substr(pos + 1, name.size()); + xml::XmlNode fragStyle("style"); + RCHECK(fragStyle.SetStringAttribute("xml:id", name)); + RCHECK( + fragStyle.SetStringAttribute("tts:backgroundColor", backgroundColor)); + RCHECK(fragStyle.SetStringAttribute("tts:color", color)); + RCHECK(styling.AddChild(std::move(fragStyle))); + } + return true; +} + +bool TtmlGenerator::isEbuTTTD() const { + for (const auto& sample : samples_) { + if (sample.settings().region.rfind(kRegionTeletextPrefix, 0) == 0) { + return true; + } + } + return false; +} + } // namespace ttml } // namespace media } // namespace shaka diff --git a/packager/media/formats/ttml/ttml_generator.h b/packager/media/formats/ttml/ttml_generator.h index 303a3e4b33d..9c1afbdfaf4 100644 --- a/packager/media/formats/ttml/ttml_generator.h +++ b/packager/media/formats/ttml/ttml_generator.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -38,12 +39,20 @@ class TtmlGenerator { bool AddSampleToXml(const TextSample& sample, xml::XmlNode* body, xml::XmlNode* metadata, + std::unordered_set& fragmentStyles, size_t* image_count) const; bool ConvertFragmentToXml(const TextFragment& fragment, xml::XmlNode* parent, xml::XmlNode* metadata, + std::unordered_set& fragmentStyles, size_t* image_count) const; + bool addStyling(xml::XmlNode& styling, + const std::unordered_set& fragmentStyles) const; + bool addRegions(xml::XmlNode& layout) const; + std::vector usedRegions() const; + bool isEbuTTTD() const; + std::list samples_; std::map regions_; std::string language_; diff --git a/packager/media/formats/ttml/ttml_generator_unittest.cc b/packager/media/formats/ttml/ttml_generator_unittest.cc index 23728d223f7..0f0d04dcbd9 100644 --- a/packager/media/formats/ttml/ttml_generator_unittest.cc +++ b/packager/media/formats/ttml/ttml_generator_unittest.cc @@ -64,7 +64,11 @@ TEST_F(TtmlMuxerTest, WithOneSegmentAndWithOneSample) { "\n" "\n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" "
\n" "

\n" "\n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" "

\n" "

\n" "\n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" "

\n" "

\n" " \n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" " \n" "

\n" @@ -166,7 +182,11 @@ TEST_F(TtmlMuxerTest, HandlesLanguage) { "\n" "\n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" "
\n" "

\n" "\n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" "

\n" " \n" "\n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" "
\n" "

\n" "\n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" "

\n" "

\n" " \n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" " \n" "

\n" @@ -287,7 +323,11 @@ TEST_F(TtmlMuxerTest, HandlesReset) { "\n" "\n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" "
\n" "

\n" "\n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" "

\n" "

\n" - " \n" - " \n" - " " + " \n" + " \n" + " " "AQID\n" - " \n" + " \n" + " \n" + " \n" + " \n" " \n" "

\n" "

\n" "\n" - " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" "

\n" "

\n" + "\n" + " \n" + " \n" + " \n" + "