diff --git a/docs/source/options/stream_descriptors.rst b/docs/source/options/stream_descriptors.rst index 82e8037735..b7d041dfb5 100644 --- a/docs/source/options/stream_descriptors.rst +++ b/docs/source/options/stream_descriptors.rst @@ -60,6 +60,20 @@ These are the available fields: For subtitles in MP4, you can specify 'vtt+mp4' or 'ttml+mp4' to control which text format is used. +:input_format (format): + + Optional value which specifies the format of the input files or + streams. If not specified, it will be autodetected, which in some + cases may fail. + + For example, a live UDP WebVTT input stream may be up and streaming + long before a shaka packager instance consumes it, and therefore + shaka packager never gets the initial "WEBVTT" header string. In + such a case, shaka packager can't properly autodetect the stream + format as WebVTT, and thus doesn't process it. But stating + 'input_format=webvtt' as selector parameter will tell shaka packager + to omit autodetection and consider WebVTT format for that stream. + :trick_play_factor (tpf): Optional value which specifies the trick play, a.k.a. trick mode, stream diff --git a/include/packager/packager.h b/include/packager/packager.h index 76917a4120..6b835fe24f 100644 --- a/include/packager/packager.h +++ b/include/packager/packager.h @@ -152,6 +152,11 @@ struct StreamDescriptor { /// Set to true to indicate that the stream is for hls only. bool hls_only = false; + /// Optional value which specifies input container format. + /// Useful for live streaming situations, like auto-detecting webvtt without + /// its initial header. + std::string input_format; + /// Optional, indicates if this is a Forced Narrative subtitle stream. bool forced_subtitle = false; diff --git a/packager/app/packager_main.cc b/packager/app/packager_main.cc index 60937a1421..426a2352df 100644 --- a/packager/app/packager_main.cc +++ b/packager/app/packager_main.cc @@ -89,6 +89,10 @@ const char kUsage[] = " - output_format (format): Optional value which specifies the format\n" " of the output files (MP4 or WebM). If not specified, it will be\n" " derived from the file extension of the output file.\n" + " - input_format (format): Optional value which specifies the format\n" + " of the input files or streams. If not specified, it will be\n" + " autodetected, which in some cases (such as live UDP webvtt) may\n" + " fail.\n" " - skip_encryption=0|1: Optional. Defaults to 0 if not specified. If\n" " it is set to 1, no encryption of the stream will be made.\n" " - drm_label: Optional value for custom DRM label, which defines the\n" diff --git a/packager/app/stream_descriptor.cc b/packager/app/stream_descriptor.cc index b49d2dfc8f..afcc4fcb3d 100644 --- a/packager/app/stream_descriptor.cc +++ b/packager/app/stream_descriptor.cc @@ -41,6 +41,7 @@ enum FieldType { kHlsOnlyField, kDashLabelField, kForcedSubtitleField, + kInputFormatField, }; struct FieldNameToTypeMapping { @@ -90,6 +91,7 @@ const FieldNameToTypeMapping kFieldNameTypeMappings[] = { {"hls_only", kHlsOnlyField}, {"dash_label", kDashLabelField}, {"forced_subtitle", kForcedSubtitleField}, + {"input_format", kInputFormatField}, }; FieldType GetFieldType(const std::string& field_name) { @@ -271,6 +273,10 @@ std::optional ParseStreamDescriptor( } descriptor.forced_subtitle = forced_subtitle_value > 0; break; + case kInputFormatField: { + descriptor.input_format = pair.second; + break; + } default: LOG(ERROR) << "Unknown field in stream descriptor (\"" << pair.first << "\")."; diff --git a/packager/media/demuxer/demuxer.cc b/packager/media/demuxer/demuxer.cc index f3dd066b0d..beb2623b8a 100644 --- a/packager/media/demuxer/demuxer.cc +++ b/packager/media/demuxer/demuxer.cc @@ -165,21 +165,25 @@ Status Demuxer::InitializeParser() { "Cannot open file for reading " + file_name_); } - // Read enough bytes before detecting the container. int64_t bytes_read = 0; bool eof = false; - while (static_cast(bytes_read) < kInitBufSize) { - int64_t read_result = - media_file_->Read(buffer_.get() + bytes_read, kInitBufSize); - if (read_result < 0) - return Status(error::FILE_FAILURE, "Cannot read file " + file_name_); - if (read_result == 0) { - eof = true; - break; + if (input_format_.empty()) { + // Read enough bytes before detecting the container. + while (static_cast(bytes_read) < kInitBufSize) { + int64_t read_result = + media_file_->Read(buffer_.get() + bytes_read, kInitBufSize); + if (read_result < 0) + return Status(error::FILE_FAILURE, "Cannot read file " + file_name_); + if (read_result == 0) { + eof = true; + break; + } + bytes_read += read_result; } - bytes_read += read_result; + container_name_ = DetermineContainer(buffer_.get(), bytes_read); + } else { + container_name_ = DetermineContainerFromFormatName(input_format_); } - container_name_ = DetermineContainer(buffer_.get(), bytes_read); // Initialize media parser. switch (container_name_) { diff --git a/packager/media/demuxer/demuxer.h b/packager/media/demuxer/demuxer.h index e2a402f930..78617aae40 100644 --- a/packager/media/demuxer/demuxer.h +++ b/packager/media/demuxer/demuxer.h @@ -75,6 +75,10 @@ class Demuxer : public OriginHandler { dump_stream_info_ = dump_stream_info; } + void set_input_format(std::string input_format) { + input_format_ = input_format; + } + protected: /// @name MediaHandler implementation overrides. /// @{ @@ -148,6 +152,8 @@ class Demuxer : public OriginHandler { // Whether to dump stream info when it is received. bool dump_stream_info_ = false; Status init_event_status_; + // Explicitly defined input format, for avoiding autodetection. + std::string input_format_; }; } // namespace media diff --git a/packager/media/event/muxer_listener_factory.h b/packager/media/event/muxer_listener_factory.h index 9901373c48..375e7c12b7 100644 --- a/packager/media/event/muxer_listener_factory.h +++ b/packager/media/event/muxer_listener_factory.h @@ -40,6 +40,10 @@ class MuxerListenerFactory { // told to output media info. std::string media_info_output; + // Explicit input format, for avoiding autodetection when needed. + // This is useful for cases such as live WebVTT through UDP. + std::string input_format; + // HLS specific values needed to write to HLS manifests. Will only be used // if an HlsNotifier is given to the factory. std::string hls_group_id; diff --git a/packager/media/formats/webvtt/webvtt_parser.cc b/packager/media/formats/webvtt/webvtt_parser.cc index f31c07bcf2..268da6997a 100644 --- a/packager/media/formats/webvtt/webvtt_parser.cc +++ b/packager/media/formats/webvtt/webvtt_parser.cc @@ -225,14 +225,12 @@ bool WebVttParser::Parse() { // Check the header. It is possible for a 0xFEFF BOM to come before the // header text. if (block.size() != 1) { - LOG(ERROR) << "Failed to read WEBVTT header - " - << "block size should be 1 but was " << block.size() << "."; - return false; + LOG(WARNING) << "Failed to read WEBVTT header - " + << "block size should be 1 but was " << block.size() << "."; } if (block[0] != "WEBVTT" && block[0] != "\xEF\xBB\xBFWEBVTT") { - LOG(ERROR) << "Failed to read WEBVTT header - should be WEBVTT but was " - << block[0]; - return false; + LOG(WARNING) << "Failed to read WEBVTT header - should be WEBVTT but was " + << block[0]; } initialized_ = true; } diff --git a/packager/media/formats/webvtt/webvtt_parser_unittest.cc b/packager/media/formats/webvtt/webvtt_parser_unittest.cc index 9f0287e611..d4e6dc9da6 100644 --- a/packager/media/formats/webvtt/webvtt_parser_unittest.cc +++ b/packager/media/formats/webvtt/webvtt_parser_unittest.cc @@ -119,14 +119,17 @@ TEST_F(WebVttParserTest, ParseHeaderWithBOM) { ASSERT_TRUE(samples_.empty()); } -TEST_F(WebVttParserTest, FailToParseHeaderWrongWord) { +TEST_F(WebVttParserTest, ParseNoHeaderWithoutExiting) { + // A proper WebVTT file should have the "WEBVTT" string header. + // But UDP input (not file) may be ingested when the header already + // passed, and it will not be repeated later. const uint8_t text[] = - "NOT WEBVTT\n" + "00:00:01.000 --> 00:00:02.000\n" "\n"; ASSERT_NO_FATAL_FAILURE(SetUpAndInitialize()); - ASSERT_FALSE(parser_->Parse(text, sizeof(text) - 1)); + ASSERT_TRUE(parser_->Parse(text, sizeof(text) - 1)); ASSERT_TRUE(streams_.empty()); ASSERT_TRUE(samples_.empty()); @@ -140,7 +143,7 @@ TEST_F(WebVttParserTest, FailToParseHeaderNotOneLine) { ASSERT_NO_FATAL_FAILURE(SetUpAndInitialize()); - ASSERT_FALSE(parser_->Parse(text, sizeof(text) - 1)); + ASSERT_TRUE(parser_->Parse(text, sizeof(text) - 1)); ASSERT_TRUE(streams_.empty()); ASSERT_TRUE(samples_.empty()); diff --git a/packager/packager.cc b/packager/packager.cc index 8ebeb47ff4..557aef9334 100644 --- a/packager/packager.cc +++ b/packager/packager.cc @@ -77,6 +77,7 @@ MuxerListenerFactory::StreamData ToMuxerListenerData( data.dash_only = stream.dash_only; data.index = stream.index; data.dash_label = stream.dash_label; + data.input_format = stream.input_format; return data; }; @@ -447,6 +448,7 @@ Status CreateDemuxer(const StreamDescriptor& stream, std::shared_ptr* new_demuxer) { std::shared_ptr demuxer = std::make_shared(stream.input); demuxer->set_dump_stream_info(packaging_params.test_params.dump_stream_info); + demuxer->set_input_format(stream.input_format); if (packaging_params.decryption_params.key_provider != KeyProvider::kNone) { std::unique_ptr decryption_key_source(