Write encoder settings in WebM files If the output is webm, the version and command line (minus the binary name, anything matching the input filename, and output flags) will be written as an ENCODER_SETTINGS tag in the WebM container. STATS_CHANGED: WebM output size will increase slightly, but underlying bitstream is unchanged. BUG=aomedia:2593 Change-Id: I03e724d1eea9d6a82ef3068100b7ef96ffb3f87b
diff --git a/apps/aomenc.c b/apps/aomenc.c index 7f2b14a..a49bcb4 100644 --- a/apps/aomenc.c +++ b/apps/aomenc.c
@@ -1816,7 +1816,8 @@ static void open_output_file(struct stream_state *stream, struct AvxEncoderConfig *global, - const struct AvxRational *pixel_aspect_ratio) { + const struct AvxRational *pixel_aspect_ratio, + const char *encoder_settings) { const char *fn = stream->config.out_fn; const struct aom_codec_enc_cfg *const cfg = &stream->config.cfg; @@ -1835,12 +1836,13 @@ if (write_webm_file_header(&stream->webm_ctx, &stream->encoder, cfg, stream->config.stereo_fmt, get_fourcc_by_aom_encoder(global->codec), - pixel_aspect_ratio) != 0) { + pixel_aspect_ratio, encoder_settings) != 0) { fatal("WebM writer initialization failed."); } } #else (void)pixel_aspect_ratio; + (void)encoder_settings; #endif if (!stream->config.write_webm && stream->config.write_ivf) { @@ -2534,7 +2536,21 @@ FOREACH_STREAM(stream, streams) { setup_pass(stream, &global, pass); } FOREACH_STREAM(stream, streams) { initialize_encoder(stream, &global); } FOREACH_STREAM(stream, streams) { - open_output_file(stream, &global, &input.pixel_aspect_ratio); + char *encoder_settings = NULL; +#if CONFIG_WEBM_IO + if (stream->config.write_webm) { + encoder_settings = extract_encoder_settings( + aom_codec_version_str(), argv_, argc, input.filename); + if (encoder_settings == NULL) { + fprintf( + stderr, + "Warning: unable to extract encoder settings. Continuing...\n"); + } + } +#endif + open_output_file(stream, &global, &input.pixel_aspect_ratio, + encoder_settings); + free(encoder_settings); } if (strcmp(get_short_name_by_aom_encoder(global.codec), "av1") == 0) {
diff --git a/common/webmenc.cc b/common/webmenc.cc index 10a3eff..bb754e8 100644 --- a/common/webmenc.cc +++ b/common/webmenc.cc
@@ -12,6 +12,7 @@ #include "common/webmenc.h" #include <stdio.h> +#include <string.h> #include <memory> #include <new> @@ -25,13 +26,63 @@ namespace { const uint64_t kDebugTrackUid = 0xDEADBEEF; const int kVideoTrackNumber = 1; + +// Simplistic mechanism to detect if an argv parameter refers to +// an input or output file. Returns the total number of arguments that +// should be skipped. +int skip_input_output_arg(const char *arg, const char *input_fname) { + if (strcmp(arg, input_fname) == 0) { + return 1; + } + if (strcmp(arg, "-o") == 0 || strcmp(arg, "--output") == 0) { + return 2; + } + if (strncmp(arg, "--output=", strlen("--output=")) == 0) { + return 1; + } + return 0; +} + } // namespace +char *extract_encoder_settings(const char *version, const char **argv, int argc, + const char *input_fname) { + // + 9 for "version:" prefix and for null terminator. + size_t total_size = strlen(version) + 9; + int i = 1; + while (i < argc) { + int num_skip = skip_input_output_arg(argv[i], input_fname); + i += num_skip; + if (num_skip == 0) { + total_size += strlen(argv[i]) + 1; // + 1 is for space separator. + ++i; + } + } + char *result = static_cast<char *>(malloc(total_size)); + if (result == nullptr) { + return nullptr; + } + char *cur = result; + cur += snprintf(cur, total_size, "version:%s", version); + i = 1; + while (i < argc) { + int num_skip = skip_input_output_arg(argv[i], input_fname); + i += num_skip; + if (num_skip == 0) { + cur += snprintf(cur, total_size, " %s", argv[i]); + ++i; + } + } + *cur = '\0'; + return result; +} + int write_webm_file_header(struct WebmOutputContext *webm_ctx, aom_codec_ctx_t *encoder_ctx, const aom_codec_enc_cfg_t *cfg, stereo_format_t stereo_fmt, unsigned int fourcc, - const struct AvxRational *par) { + const struct AvxRational *par, + const char *encoder_settings) { std::unique_ptr<mkvmuxer::MkvWriter> writer( new (std::nothrow) mkvmuxer::MkvWriter(webm_ctx->stream)); std::unique_ptr<mkvmuxer::Segment> segment(new (std::nothrow) @@ -120,6 +171,21 @@ video_track->set_display_height(cfg->g_h); } + if (encoder_settings != nullptr) { + mkvmuxer::Tag *tag = segment->AddTag(); + if (tag == nullptr) { + fprintf(stderr, + "webmenc> Unable to allocate memory for encoder settings tag.\n"); + return -1; + } + ok = tag->add_simple_tag("ENCODER_SETTINGS", encoder_settings); + if (!ok) { + fprintf(stderr, + "webmenc> Unable to allocate memory for encoder settings tag.\n"); + return -1; + } + } + if (webm_ctx->debug) { video_track->set_uid(kDebugTrackUid); }
diff --git a/common/webmenc.h b/common/webmenc.h index a4aa992..c912208 100644 --- a/common/webmenc.h +++ b/common/webmenc.h
@@ -38,6 +38,16 @@ STEREO_FORMAT_RIGHT_LEFT = 11 } UENUM1BYTE(stereo_format_t); +// Simplistic mechanism to extract encoder settings, without having +// to re-invoke the entire flag-parsing logic. It lists the codec version +// and then copies the arguments as-is from argv, but skips the binary name, +// any arguments that match the input filename, and the output flags "-o" +// and "--output" (and the following argument for those flags). The caller +// is responsible for free-ing the returned string. If there is insufficient +// memory, it returns nullptr. +char *extract_encoder_settings(const char *version, const char **argv, int argc, + const char *input_fname); + // The following functions wrap libwebm's mkvmuxer. All functions return 0 upon // success, or -1 upon failure. @@ -45,7 +55,8 @@ aom_codec_ctx_t *encoder_ctx, const aom_codec_enc_cfg_t *cfg, stereo_format_t stereo_fmt, unsigned int fourcc, - const struct AvxRational *par); + const struct AvxRational *par, + const char *encoder_settings); int write_webm_block(struct WebmOutputContext *webm_ctx, const aom_codec_enc_cfg_t *cfg,
diff --git a/test/test.cmake b/test/test.cmake index 8da0b8d..1294f37 100644 --- a/test/test.cmake +++ b/test/test.cmake
@@ -223,7 +223,8 @@ "${AOM_ROOT}/test/frame_error_test.cc" "${AOM_ROOT}/test/warp_filter_test.cc" "${AOM_ROOT}/test/warp_filter_test_util.cc" - "${AOM_ROOT}/test/warp_filter_test_util.h") + "${AOM_ROOT}/test/warp_filter_test_util.h" + "${AOM_ROOT}/test/webmenc_test.cc") list(APPEND AOM_UNIT_TEST_ENCODER_INTRIN_SSE4_1 "${AOM_ROOT}/test/av1_highbd_iht_test.cc"
diff --git a/test/webmenc_test.cc b/test/webmenc_test.cc new file mode 100644 index 0000000..acd795f --- /dev/null +++ b/test/webmenc_test.cc
@@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020, Alliance for Open Media. All rights reserved + * + * This source code is subject to the terms of the BSD 2 Clause License and + * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License + * was not distributed with this source code in the LICENSE file, you can + * obtain it at www.aomedia.org/license/software. If the Alliance for Open + * Media Patent License 1.0 was not distributed with this source code in the + * PATENTS file, you can obtain it at www.aomedia.org/license/patent. + */ + +#include <string> +#include "common/webmenc.h" +#include "third_party/googletest/src/googletest/include/gtest/gtest.h" + +namespace { + +#if CONFIG_WEBM_IO + +class WebmencTest : public ::testing::Test {}; + +// All of these variations on output should be identical. +TEST(WebmencTest, ExtractEncoderSettingsOutput1) { + const char *argv[] = { "aomenc", "-o", "output", "input", + "--target-bitrate=300" }; + int argc = 5; + const std::string expected("version:1.2.3 --target-bitrate=300"); + char *result = extract_encoder_settings("1.2.3", argv, argc, "input"); + ASSERT_EQ(expected, std::string(result)); + free(result); +} + +TEST(WebmencTest, ExtractEncoderSettingsOutput2) { + const char *argv[] = { "aomenc", "--output", "bar", "foo", "--cpu-used=3" }; + int argc = 5; + const std::string expected("version:abc --cpu-used=3"); + char *result = extract_encoder_settings("abc", argv, argc, "foo"); + ASSERT_EQ(expected, std::string(result)); + free(result); +} + +TEST(WebmencTest, ExtractEncoderSettingsOutput3) { + const char *argv[] = { "aomenc", "--cq-level=63", "--end-usage=q", + "--output=foo", "baz" }; + int argc = 5; + const std::string expected("version:23 --cq-level=63 --end-usage=q"); + char *result = extract_encoder_settings("23", argv, argc, "baz"); + ASSERT_EQ(expected, std::string(result)); + free(result); +} + +TEST(WebmencTest, ExtractEncoderSettingsInput) { + // Check that input filename is filtered regardless of position. + const char *argv[] = { "aomenc", "-o", "out", "input", "-p", "2" }; + int argc = 6; + const char version[] = "1.0.0"; + const std::string expected("version:1.0.0 -p 2"); + char *result = extract_encoder_settings(version, argv, argc, "input"); + ASSERT_EQ(expected, std::string(result)); + free(result); + + const char *argv2[] = { "aomenc", "input", "-o", "out", "-p", "2" }; + result = extract_encoder_settings(version, argv2, argc, "input"); + ASSERT_EQ(expected, std::string(result)); + free(result); +} + +#endif // CONFIG_WEBM_IO +} // namespace