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