Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow LIVE UDP WebVTT input #1349

Merged
merged 4 commits into from
Feb 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/source/options/stream_descriptors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions include/packager/packager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
4 changes: 4 additions & 0 deletions packager/app/packager_main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 6 additions & 0 deletions packager/app/stream_descriptor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ enum FieldType {
kHlsOnlyField,
kDashLabelField,
kForcedSubtitleField,
kInputFormatField,
};

struct FieldNameToTypeMapping {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -271,6 +273,10 @@ std::optional<StreamDescriptor> 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
<< "\").";
Expand Down
26 changes: 15 additions & 11 deletions packager/media/demuxer/demuxer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<size_t>(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<size_t>(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_) {
Expand Down
6 changes: 6 additions & 0 deletions packager/media/demuxer/demuxer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// @{
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions packager/media/event/muxer_listener_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 4 additions & 6 deletions packager/media/formats/webvtt/webvtt_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
11 changes: 7 additions & 4 deletions packager/media/formats/webvtt/webvtt_parser_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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());
Expand Down
2 changes: 2 additions & 0 deletions packager/packager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

Expand Down Expand Up @@ -463,6 +464,7 @@ Status CreateDemuxer(const StreamDescriptor& stream,
std::shared_ptr<Demuxer>* new_demuxer) {
std::shared_ptr<Demuxer> demuxer = std::make_shared<Demuxer>(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<KeySource> decryption_key_source(
Expand Down
Loading