From 83d26dba06a5e2519c5a575d97ff3bbcba5afabd Mon Sep 17 00:00:00 2001 From: Vicky Min Date: Thu, 29 Aug 2019 16:10:22 -0700 Subject: [PATCH] More configuration options for the pipeline. This allows the user to customize the availability window and the presentation delay for live packaging, as well as a time range extraction option for VOD. The presentation delay will only be set in the DASH manifest because there is currently no equivalent option for HLS. Closes #1 Closes #2 Closes #3 Change-Id: I20041c4697b675b62a6ec170fe3f9687abe3931a --- config_files/input_vod_config.yaml | 8 +- config_files/pipeline_live_config.yaml | 4 + streamer/default_config.py | 8 ++ streamer/input_configuration.py | 10 ++ streamer/metadata.py | 29 +++--- streamer/packager_node.py | 7 +- streamer/transcoder_node.py | 11 ++ tests/tests.js | 137 ++++++++++++++++++++++++- 8 files changed, 194 insertions(+), 20 deletions(-) diff --git a/config_files/input_vod_config.yaml b/config_files/input_vod_config.yaml index c6747d5..bd7ce80 100644 --- a/config_files/input_vod_config.yaml +++ b/config_files/input_vod_config.yaml @@ -29,11 +29,17 @@ inputs: resolution: 4k # Whether or not the video frames are interlaced. is_interlaced: False + # Start time of video to encode. + start_time: 00:00:00 + # End time of video to encode. + end_time: 00:20:00 # A second track (audio) from the same input file. - name: Sintel.2010.4k.mkv track_num: 1 media_type: audio + start_time: 00:00:00 + end_time: 00:20:00 # Several text tracks of different languages. # https://storage.googleapis.com/shaka-streamer-assets/sample-inputs/Sintel.2010.Arabic.vtt @@ -64,4 +70,4 @@ inputs: # https://storage.googleapis.com/shaka-streamer-assets/sample-inputs/Sintel.2010.Chinese.vtt - name: Sintel.2010.Chinese.vtt media_type: text - language: zh \ No newline at end of file + language: zh diff --git a/config_files/pipeline_live_config.yaml b/config_files/pipeline_live_config.yaml index f27f901..2fcc94b 100644 --- a/config_files/pipeline_live_config.yaml +++ b/config_files/pipeline_live_config.yaml @@ -40,3 +40,7 @@ packager: - hls # Length of each segment in seconds. segment_size: 4 + # Availability window for live packaging. + availability_window: 400 + # Set the presentation delay for live packaging. + presentation_delay: 50 diff --git a/streamer/default_config.py b/streamer/default_config.py index 9490369..f4c3e9d 100644 --- a/streamer/default_config.py +++ b/streamer/default_config.py @@ -42,6 +42,10 @@ # TODO: Add different default config input entries for audio, video and # text entries so that fields match up with each type of entry. 'language': 'und', + # Start time of VOD input to encode. + 'start_time': '', + # End time of VOD input to encode. + 'end_time': '', }, ], } @@ -81,6 +85,10 @@ 'segment_size': 10, # Forces the use of SegmentTemplate in DASH. 'segment_per_file': True, + # Availability window for live packaging. + 'availability_window': 300, + # Presentation delay for live packaging. + 'presentation_delay': 30, 'encryption': { # Enables encryption. # If disabled, the following settings are ignored. diff --git a/streamer/input_configuration.py b/streamer/input_configuration.py index fe0450c..9466400 100644 --- a/streamer/input_configuration.py +++ b/streamer/input_configuration.py @@ -65,6 +65,16 @@ def get_language(self): return self._input['language'] return None + def get_start_time(self): + if 'start_time' in self._input: + return self._input['start_time'] + return None + + def get_end_time(self): + if 'end_time' in self._input: + return self._input['end_time'] + return None + def has_video(self): if 'input_type' in self._input: if (self._input['input_type'] == 'webcam' or diff --git a/streamer/metadata.py b/streamer/metadata.py index cb0b571..1771986 100644 --- a/streamer/metadata.py +++ b/streamer/metadata.py @@ -23,7 +23,8 @@ def __init__(self, aac_bitrate, opus_bitrate): class ResolutionData(): - def __init__(self, height, h264_bitrate, vp9_bitrate, h264_profile): + def __init__(self, width, height, h264_bitrate, vp9_bitrate, h264_profile): + self.width = width self.height = height self.h264_bitrate = h264_bitrate self.vp9_bitrate = vp9_bitrate @@ -48,19 +49,19 @@ def __ge__(self, other): # A map of resolutions to ResolutionData objects which contain # the height and H264 bitrate of a given resolution. RESOLUTION_MAP = { - '144p': ResolutionData(144, '108k', '95k', 'baseline'), - '240p': ResolutionData(240, '242k', '150k', 'main'), - '360p': ResolutionData(360, '400k', '276k', 'main'), - '480p': ResolutionData(480, '2M', '750k', 'main'), - '576p': ResolutionData(576, '2.5M', '1M', 'main'), - '720p': ResolutionData(720, '3M', '2M', 'main'), - '720p-hfr': ResolutionData(720, '4M', '4M', 'main'), - '1080p': ResolutionData(1080, '5M', '4M', 'high'), - '1080p-hfr': ResolutionData(1080, '6M', '6M', 'high'), - '2k': ResolutionData(1440, '9M', '6M', 'high'), - '2k-hfr': ResolutionData(1440, '14M', '9M', 'high'), - '4k': ResolutionData(2160, '17M', '12M', 'uhd'), - '4k-hfr': ResolutionData(2160, '25M', '18M', 'uhd'), + '144p': ResolutionData(256, 144, '108k', '95k', 'baseline'), + '240p': ResolutionData(426, 240, '242k', '150k', 'main'), + '360p': ResolutionData(640, 360, '400k', '276k', 'main'), + '480p': ResolutionData(854, 480, '2M', '750k', 'main'), + '576p': ResolutionData(1024, 576, '2.5M', '1M', 'main'), + '720p': ResolutionData(1280, 720, '3M', '2M', 'main'), + '720p-hfr': ResolutionData(1280, 720, '4M', '4M', 'main'), + '1080p': ResolutionData(1920, 1080, '5M', '4M', 'high'), + '1080p-hfr': ResolutionData(1920, 1080, '6M', '6M', 'high'), + '2k': ResolutionData(2560, 1440, '9M', '6M', 'high'), + '2k-hfr': ResolutionData(2560, 1440, '14M', '9M', 'high'), + '4k': ResolutionData(3840, 2160, '17M', '12M', 'uhd'), + '4k-hfr': ResolutionData(3840, 2160, '25M', '18M', 'uhd'), } class Metadata(): diff --git a/streamer/packager_node.py b/streamer/packager_node.py index 6803821..33c1791 100644 --- a/streamer/packager_node.py +++ b/streamer/packager_node.py @@ -82,9 +82,14 @@ def start(self): if self._config.mode == 'live': args += [ # Number of seconds the user can rewind through backwards. - '--time_shift_buffer_depth', '300', + '--time_shift_buffer_depth', + str(self._config.packager['availability_window']), # Number of segments preserved outside the current live window. '--preserved_segments_outside_live_window', '1', + # Number of seconds of content encoded/packaged that is ahead of the + # live edge. + '--suggested_presentation_delay', + str(self._config.packager['presentation_delay']), ] args += self._setup_manifest_format() diff --git a/streamer/transcoder_node.py b/streamer/transcoder_node.py index af2a076..9b5f22b 100644 --- a/streamer/transcoder_node.py +++ b/streamer/transcoder_node.py @@ -64,6 +64,17 @@ def start(self): '-i', input.get_name(), ] + if input.get_start_time(): + args += [ + # Encode from intended starting time of the VOD input. + '-ss', input.get_start_time(), + ] + if input.get_end_time(): + args += [ + # Encode until intended ending time of the VOD input. + '-to', input.get_end_time(), + ] + # Check if the media type of the input is audio and if there are expected # outputs for the audio input. if input.get_media_type() == 'audio' and self._output_audios: diff --git a/tests/tests.js b/tests/tests.js index 4ca637b..8323799 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -17,6 +17,7 @@ const dashManifestUrl = flaskServerUrl + 'output_files/output.mpd'; const hlsManifestUrl = flaskServerUrl + 'output_files/master_playlist.m3u8'; const TEST_DIR = 'test_assets/'; let player; +let video; async function startStreamer(inputConfig, pipelineConfig) { // Send a request to flask server to start Shaka Streamer. @@ -45,8 +46,6 @@ async function stopStreamer() { } describe('Shaka Streamer', () => { - let video; - beforeAll(() => { shaka.polyfill.installAll(); jasmine.DEFAULT_TIMEOUT_INTERVAL = 400 * 1000; @@ -89,6 +88,14 @@ describe('Shaka Streamer', () => { // TODO: Redo this test with both 5.1 and stereo once 5.1 is supported. channelsTests(hlsManifestUrl, '(hls)'); channelsTests(dashManifestUrl, '(dash)'); + // The HLS manifest does not indicate the availability window, so only run a + // DASH test for this case. + availabilityTests(dashManifestUrl, '(dash)'); + // There is no option supported by Packager to set a presentation delay with + // HLS, so only run a dash test for this case. + delayTests(dashManifestUrl, '(dash)'); + durationTests(hlsManifestUrl, '(hls)'); + durationTests(dashManifestUrl, '(dash)'); }); function resolutionTests(manifestUrl, format) { @@ -386,7 +393,7 @@ function textTracksTests(manifestUrl, format) { } function vodTests(manifestUrl, format) { - it('has a vod streaming mode', async () => { + it('has a vod streaming mode ' + format, async () => { const inputConfigDict = { // List of inputs. Each one is a dictionary. 'inputs': [ @@ -425,7 +432,7 @@ function vodTests(manifestUrl, format) { } function channelsTests(manifestUrl, format) { - it('outputs the correct number of channels', async () => { + it('outputs the correct number of channels ' + format, async () => { const inputConfigDict = { // List of inputs. Each one is a dictionary. 'inputs': [ @@ -463,3 +470,125 @@ function channelsTests(manifestUrl, format) { expect(trackList[0].channelsCount).toBe(2); }); } + +function availabilityTests(manifestUrl, format) { + it('outputs the correct availability window ' + format, async() => { + const inputConfigDict = { + 'inputs': [ + { + 'name': TEST_DIR + 'Sintel.2010.720p.Small.mkv', + 'input_type': 'looped_file', + 'media_type': 'video', + 'resolution': '720p', + 'frame_rate': 24.0, + 'track_num': 0, + }, + ], + }; + const pipelineConfigDict = { + 'streaming_mode': 'live', + 'transcoder': { + 'resolutions': [ + '144p', + ], + 'video_codecs': [ + 'h264', + ], + }, + 'packager': { + 'manifest_format': [ + 'dash', + ], + 'segment_per_file': true, + 'availability_window': 500, + }, + }; + await startStreamer(inputConfigDict, pipelineConfigDict); + const response = await fetch(manifestUrl); + const bodyText = await response.text(); + const re = /timeShiftBufferDepth="([^"]*)"/; + const found = bodyText.match(re); + expect(found).not.toBe(null); + if (found) + expect(found[1]).toBe('PT500S'); + }); +} + +function delayTests(manifestUrl, format) { + it('outputs the correct presentation delay ' + format, async() => { + const inputConfigDict = { + 'inputs': [ + { + 'name': TEST_DIR + 'Sintel.2010.720p.Small.mkv', + 'input_type': 'looped_file', + 'media_type': 'video', + 'resolution': '720p', + 'frame_rate': 24.0, + 'track_num': 0, + }, + ], + }; + const pipelineConfigDict = { + 'streaming_mode': 'live', + 'transcoder': { + 'resolutions': [ + '144p', + ], + 'video_codecs': [ + 'h264', + ], + }, + 'packager': { + 'manifest_format': [ + 'dash', + ], + 'segment_per_file': true, + 'presentation_delay': 100, + }, + }; + await startStreamer(inputConfigDict, pipelineConfigDict); + await player.load(manifestUrl); + delay = player.getManifest().presentationTimeline.getDelay(); + expect(delay).toBe(100); + }); +} + +function durationTests(manifestUrl, format) { + it('outputs the correct duration of video ' + format, async() => { + const inputConfigDict = { + 'inputs': [ + { + 'name': TEST_DIR + 'Sintel.2010.720p.Small.mkv', + 'media_type': 'video', + 'resolution': '720p', + 'frame_rate': 24.0, + 'track_num': 0, + 'is_interlaced': false, + 'start_time': '00:00:00', + 'end_time': '00:00:10', + }, + ], + }; + const pipelineConfigDict = { + 'streaming_mode': 'vod', + 'transcoder': { + 'resolutions': [ + '144p', + ], + 'video_codecs': [ + 'h264', + ], + }, + 'packager': { + 'manifest_format': [ + 'dash', + 'hls', + ], + 'segment_per_file': false, + }, + }; + await startStreamer(inputConfigDict, pipelineConfigDict); + await player.load(manifestUrl); + expect(video.duration).toBeCloseTo(10); + }); +}