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.
skip_input_output_arg(const char * arg,const char * input_fname)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
extract_encoder_settings(const char * version,const char ** argv,int argc,const char * input_fname)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
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 char * encoder_settings)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
write_webm_block(struct WebmOutputContext * webm_ctx,const aom_codec_enc_cfg_t * cfg,const aom_codec_cx_pkt_t * pkt)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
write_webm_file_footer(struct WebmOutputContext * webm_ctx)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 }
243