webmenc.cc (7807B)
1 /* 2 * Copyright (c) 2016, Alliance for Open Media. All rights reserved. 3 * 4 * This source code is subject to the terms of the BSD 2 Clause License and 5 * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License 6 * was not distributed with this source code in the LICENSE file, you can 7 * obtain it at www.aomedia.org/license/software. If the Alliance for Open 8 * Media Patent License 1.0 was not distributed with this source code in the 9 * PATENTS file, you can obtain it at www.aomedia.org/license/patent. 10 */ 11 12 #include "common/webmenc.h" 13 14 #include <stdio.h> 15 #include <string.h> 16 17 #include <memory> 18 #include <new> 19 #include <string> 20 21 #include "common/av1_config.h" 22 #include "third_party/libwebm/mkvmuxer/mkvmuxer.h" 23 #include "third_party/libwebm/mkvmuxer/mkvmuxerutil.h" 24 #include "third_party/libwebm/mkvmuxer/mkvwriter.h" 25 26 namespace { 27 const uint64_t kDebugTrackUid = 0xDEADBEEF; 28 const int kVideoTrackNumber = 1; 29 30 // Simplistic mechanism to detect if an argv parameter refers to 31 // an input or output file. Returns the total number of arguments that 32 // should be skipped. 33 int skip_input_output_arg(const char *arg, const char *input_fname) { 34 if (strcmp(arg, input_fname) == 0) { 35 return 1; 36 } 37 if (strcmp(arg, "-o") == 0 || strcmp(arg, "--output") == 0) { 38 return 2; 39 } 40 if (strncmp(arg, "--output=", strlen("--output=")) == 0) { 41 return 1; 42 } 43 return 0; 44 } 45 46 } // namespace 47 48 char *extract_encoder_settings(const char *version, const char **argv, int argc, 49 const char *input_fname) { 50 // + 9 for "version:" prefix and for null terminator. 51 size_t total_size = strlen(version) + 9; 52 int i = 1; 53 while (i < argc) { 54 int num_skip = skip_input_output_arg(argv[i], input_fname); 55 i += num_skip; 56 if (num_skip == 0) { 57 total_size += strlen(argv[i]) + 1; // + 1 is for space separator. 58 ++i; 59 } 60 } 61 char *result = static_cast<char *>(malloc(total_size)); 62 if (result == nullptr) { 63 return nullptr; 64 } 65 char *cur = result; 66 cur += snprintf(cur, total_size, "version:%s", version); 67 i = 1; 68 while (i < argc) { 69 int num_skip = skip_input_output_arg(argv[i], input_fname); 70 i += num_skip; 71 if (num_skip == 0) { 72 cur += snprintf(cur, total_size, " %s", argv[i]); 73 ++i; 74 } 75 } 76 *cur = '\0'; 77 return result; 78 } 79 80 int write_webm_file_header(struct WebmOutputContext *webm_ctx, 81 aom_codec_ctx_t *encoder_ctx, 82 const aom_codec_enc_cfg_t *cfg, 83 stereo_format_t stereo_fmt, unsigned int fourcc, 84 const struct AvxRational *par, 85 const char *encoder_settings) { 86 std::unique_ptr<mkvmuxer::MkvWriter> writer( 87 new (std::nothrow) mkvmuxer::MkvWriter(webm_ctx->stream)); 88 std::unique_ptr<mkvmuxer::Segment> segment(new (std::nothrow) 89 mkvmuxer::Segment()); 90 if (writer == nullptr || segment == nullptr) { 91 fprintf(stderr, "webmenc> mkvmuxer objects alloc failed, out of memory?\n"); 92 return -1; 93 } 94 95 bool ok = segment->Init(writer.get()); 96 if (!ok) { 97 fprintf(stderr, "webmenc> mkvmuxer Init failed.\n"); 98 return -1; 99 } 100 101 segment->set_mode(mkvmuxer::Segment::kFile); 102 segment->OutputCues(true); 103 104 mkvmuxer::SegmentInfo *const info = segment->GetSegmentInfo(); 105 if (!info) { 106 fprintf(stderr, "webmenc> Cannot retrieve Segment Info.\n"); 107 return -1; 108 } 109 110 const uint64_t kTimecodeScale = 1000000; 111 info->set_timecode_scale(kTimecodeScale); 112 std::string version = "aomenc"; 113 if (!webm_ctx->debug) { 114 version.append(std::string(" ") + aom_codec_version_str()); 115 } 116 info->set_writing_app(version.c_str()); 117 118 const uint64_t video_track_id = 119 segment->AddVideoTrack(static_cast<int>(cfg->g_w), 120 static_cast<int>(cfg->g_h), kVideoTrackNumber); 121 mkvmuxer::VideoTrack *const video_track = static_cast<mkvmuxer::VideoTrack *>( 122 segment->GetTrackByNumber(video_track_id)); 123 124 if (!video_track) { 125 fprintf(stderr, "webmenc> Video track creation failed.\n"); 126 return -1; 127 } 128 129 ok = false; 130 aom_fixed_buf_t *obu_sequence_header = 131 aom_codec_get_global_headers(encoder_ctx); 132 if (obu_sequence_header) { 133 Av1Config av1_config; 134 if (get_av1config_from_obu( 135 reinterpret_cast<const uint8_t *>(obu_sequence_header->buf), 136 obu_sequence_header->sz, false, &av1_config) == 0) { 137 uint8_t av1_config_buffer[4] = { 0 }; 138 size_t bytes_written = 0; 139 if (write_av1config(&av1_config, sizeof(av1_config_buffer), 140 &bytes_written, av1_config_buffer) == 0) { 141 ok = video_track->SetCodecPrivate(av1_config_buffer, 142 sizeof(av1_config_buffer)); 143 } 144 } 145 free(obu_sequence_header->buf); 146 free(obu_sequence_header); 147 } 148 if (!ok) { 149 fprintf(stderr, "webmenc> Unable to set AV1 config.\n"); 150 return -1; 151 } 152 153 ok = video_track->SetStereoMode(stereo_fmt); 154 if (!ok) { 155 fprintf(stderr, "webmenc> Unable to set stereo mode.\n"); 156 return -1; 157 } 158 159 if (fourcc != AV1_FOURCC) { 160 fprintf(stderr, "webmenc> Unsupported codec (unknown 4 CC).\n"); 161 return -1; 162 } 163 video_track->set_codec_id("V_AV1"); 164 165 if (par->numerator > 1 || par->denominator > 1) { 166 // TODO(fgalligan): Add support of DisplayUnit, Display Aspect Ratio type 167 // to WebM format. 168 const uint64_t display_width = static_cast<uint64_t>( 169 ((cfg->g_w * par->numerator * 1.0) / par->denominator) + .5); 170 video_track->set_display_width(display_width); 171 video_track->set_display_height(cfg->g_h); 172 } 173 174 if (encoder_settings != nullptr) { 175 mkvmuxer::Tag *tag = segment->AddTag(); 176 if (tag == nullptr) { 177 fprintf(stderr, 178 "webmenc> Unable to allocate memory for encoder settings tag.\n"); 179 return -1; 180 } 181 ok = tag->add_simple_tag("ENCODER_SETTINGS", encoder_settings); 182 if (!ok) { 183 fprintf(stderr, 184 "webmenc> Unable to allocate memory for encoder settings tag.\n"); 185 return -1; 186 } 187 } 188 189 if (webm_ctx->debug) { 190 video_track->set_uid(kDebugTrackUid); 191 } 192 193 webm_ctx->writer = writer.release(); 194 webm_ctx->segment = segment.release(); 195 return 0; 196 } 197 198 int write_webm_block(struct WebmOutputContext *webm_ctx, 199 const aom_codec_enc_cfg_t *cfg, 200 const aom_codec_cx_pkt_t *pkt) { 201 if (!webm_ctx->segment) { 202 fprintf(stderr, "webmenc> segment is NULL.\n"); 203 return -1; 204 } 205 mkvmuxer::Segment *const segment = 206 reinterpret_cast<mkvmuxer::Segment *>(webm_ctx->segment); 207 int64_t pts_ns = pkt->data.frame.pts * 1000000000ll * cfg->g_timebase.num / 208 cfg->g_timebase.den; 209 if (pts_ns <= webm_ctx->last_pts_ns) pts_ns = webm_ctx->last_pts_ns + 1000000; 210 webm_ctx->last_pts_ns = pts_ns; 211 212 if (!segment->AddFrame(static_cast<uint8_t *>(pkt->data.frame.buf), 213 pkt->data.frame.sz, kVideoTrackNumber, pts_ns, 214 pkt->data.frame.flags & AOM_FRAME_IS_KEY)) { 215 fprintf(stderr, "webmenc> AddFrame failed.\n"); 216 return -1; 217 } 218 return 0; 219 } 220 221 int write_webm_file_footer(struct WebmOutputContext *webm_ctx) { 222 if (!webm_ctx->writer || !webm_ctx->segment) { 223 fprintf(stderr, "webmenc> segment or writer NULL.\n"); 224 return -1; 225 } 226 mkvmuxer::MkvWriter *const writer = 227 reinterpret_cast<mkvmuxer::MkvWriter *>(webm_ctx->writer); 228 mkvmuxer::Segment *const segment = 229 reinterpret_cast<mkvmuxer::Segment *>(webm_ctx->segment); 230 const bool ok = segment->Finalize(); 231 delete segment; 232 delete writer; 233 webm_ctx->writer = NULL; 234 webm_ctx->segment = NULL; 235 236 if (!ok) { 237 fprintf(stderr, "webmenc> Segment::Finalize failed.\n"); 238 return -1; 239 } 240 241 return 0; 242 }