1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "VPXDecoder.h"
8 
9 #include <algorithm>
10 
11 #include "BitReader.h"
12 #include "ByteWriter.h"
13 #include "ImageContainer.h"
14 #include "TimeUnits.h"
15 #include "gfx2DGlue.h"
16 #include "mozilla/PodOperations.h"
17 #include "mozilla/SyncRunnable.h"
18 #include "mozilla/TaskQueue.h"
19 #include "mozilla/Unused.h"
20 #include "nsError.h"
21 #include "prsystem.h"
22 #include "VideoUtils.h"
23 
24 #undef LOG
25 #define LOG(arg, ...)                                                  \
26   DDMOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, "::%s: " arg, __func__, \
27             ##__VA_ARGS__)
28 
29 namespace mozilla {
30 
31 using namespace gfx;
32 using namespace layers;
33 
MimeTypeToCodec(const nsACString & aMimeType)34 static VPXDecoder::Codec MimeTypeToCodec(const nsACString& aMimeType) {
35   if (aMimeType.EqualsLiteral("video/vp8")) {
36     return VPXDecoder::Codec::VP8;
37   } else if (aMimeType.EqualsLiteral("video/vp9")) {
38     return VPXDecoder::Codec::VP9;
39   }
40   return VPXDecoder::Codec::Unknown;
41 }
42 
InitContext(vpx_codec_ctx_t * aCtx,const VideoInfo & aInfo,const VPXDecoder::Codec aCodec,bool aLowLatency)43 static nsresult InitContext(vpx_codec_ctx_t* aCtx, const VideoInfo& aInfo,
44                             const VPXDecoder::Codec aCodec, bool aLowLatency) {
45   int decode_threads = 2;
46 
47   vpx_codec_iface_t* dx = nullptr;
48   if (aCodec == VPXDecoder::Codec::VP8) {
49     dx = vpx_codec_vp8_dx();
50   } else if (aCodec == VPXDecoder::Codec::VP9) {
51     dx = vpx_codec_vp9_dx();
52     if (aInfo.mDisplay.width >= 2048) {
53       decode_threads = 8;
54     } else if (aInfo.mDisplay.width >= 1024) {
55       decode_threads = 4;
56     }
57   }
58   decode_threads = std::min(decode_threads, PR_GetNumberOfProcessors());
59 
60   vpx_codec_dec_cfg_t config;
61   config.threads = aLowLatency ? 1 : decode_threads;
62   config.w = config.h = 0;  // set after decode
63 
64   if (!dx || vpx_codec_dec_init(aCtx, dx, &config, 0)) {
65     return NS_ERROR_FAILURE;
66   }
67   return NS_OK;
68 }
69 
VPXDecoder(const CreateDecoderParams & aParams)70 VPXDecoder::VPXDecoder(const CreateDecoderParams& aParams)
71     : mImageContainer(aParams.mImageContainer),
72       mImageAllocator(aParams.mKnowsCompositor),
73       mTaskQueue(new TaskQueue(
74           GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER), "VPXDecoder")),
75       mInfo(aParams.VideoConfig()),
76       mCodec(MimeTypeToCodec(aParams.VideoConfig().mMimeType)),
77       mLowLatency(
78           aParams.mOptions.contains(CreateDecoderParams::Option::LowLatency)) {
79   MOZ_COUNT_CTOR(VPXDecoder);
80   PodZero(&mVPX);
81   PodZero(&mVPXAlpha);
82 }
83 
~VPXDecoder()84 VPXDecoder::~VPXDecoder() { MOZ_COUNT_DTOR(VPXDecoder); }
85 
Shutdown()86 RefPtr<ShutdownPromise> VPXDecoder::Shutdown() {
87   RefPtr<VPXDecoder> self = this;
88   return InvokeAsync(mTaskQueue, __func__, [self]() {
89     vpx_codec_destroy(&self->mVPX);
90     vpx_codec_destroy(&self->mVPXAlpha);
91     return self->mTaskQueue->BeginShutdown();
92   });
93 }
94 
Init()95 RefPtr<MediaDataDecoder::InitPromise> VPXDecoder::Init() {
96   if (NS_FAILED(InitContext(&mVPX, mInfo, mCodec, mLowLatency))) {
97     return VPXDecoder::InitPromise::CreateAndReject(
98         NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
99   }
100   if (mInfo.HasAlpha()) {
101     if (NS_FAILED(InitContext(&mVPXAlpha, mInfo, mCodec, mLowLatency))) {
102       return VPXDecoder::InitPromise::CreateAndReject(
103           NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
104     }
105   }
106   return VPXDecoder::InitPromise::CreateAndResolve(TrackInfo::kVideoTrack,
107                                                    __func__);
108 }
109 
Flush()110 RefPtr<MediaDataDecoder::FlushPromise> VPXDecoder::Flush() {
111   return InvokeAsync(mTaskQueue, __func__, []() {
112     return FlushPromise::CreateAndResolve(true, __func__);
113   });
114 }
115 
ProcessDecode(MediaRawData * aSample)116 RefPtr<MediaDataDecoder::DecodePromise> VPXDecoder::ProcessDecode(
117     MediaRawData* aSample) {
118   MOZ_ASSERT(mTaskQueue->IsOnCurrentThread());
119 
120   if (vpx_codec_err_t r = vpx_codec_decode(&mVPX, aSample->Data(),
121                                            aSample->Size(), nullptr, 0)) {
122     LOG("VPX Decode error: %s", vpx_codec_err_to_string(r));
123     return DecodePromise::CreateAndReject(
124         MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
125                     RESULT_DETAIL("VPX error: %s", vpx_codec_err_to_string(r))),
126         __func__);
127   }
128 
129   vpx_codec_iter_t iter = nullptr;
130   vpx_image_t* img;
131   vpx_image_t* img_alpha = nullptr;
132   bool alpha_decoded = false;
133   DecodedData results;
134 
135   while ((img = vpx_codec_get_frame(&mVPX, &iter))) {
136     NS_ASSERTION(img->fmt == VPX_IMG_FMT_I420 || img->fmt == VPX_IMG_FMT_I444,
137                  "WebM image format not I420 or I444");
138     NS_ASSERTION(!alpha_decoded,
139                  "Multiple frames per packet that contains alpha");
140 
141     if (aSample->AlphaSize() > 0) {
142       if (!alpha_decoded) {
143         MediaResult rv = DecodeAlpha(&img_alpha, aSample);
144         if (NS_FAILED(rv)) {
145           return DecodePromise::CreateAndReject(rv, __func__);
146         }
147         alpha_decoded = true;
148       }
149     }
150     // Chroma shifts are rounded down as per the decoding examples in the SDK
151     VideoData::YCbCrBuffer b;
152     b.mPlanes[0].mData = img->planes[0];
153     b.mPlanes[0].mStride = img->stride[0];
154     b.mPlanes[0].mHeight = img->d_h;
155     b.mPlanes[0].mWidth = img->d_w;
156     b.mPlanes[0].mSkip = 0;
157 
158     b.mPlanes[1].mData = img->planes[1];
159     b.mPlanes[1].mStride = img->stride[1];
160     b.mPlanes[1].mSkip = 0;
161 
162     b.mPlanes[2].mData = img->planes[2];
163     b.mPlanes[2].mStride = img->stride[2];
164     b.mPlanes[2].mSkip = 0;
165 
166     if (img->fmt == VPX_IMG_FMT_I420) {
167       b.mPlanes[1].mHeight = (img->d_h + 1) >> img->y_chroma_shift;
168       b.mPlanes[1].mWidth = (img->d_w + 1) >> img->x_chroma_shift;
169 
170       b.mPlanes[2].mHeight = (img->d_h + 1) >> img->y_chroma_shift;
171       b.mPlanes[2].mWidth = (img->d_w + 1) >> img->x_chroma_shift;
172     } else if (img->fmt == VPX_IMG_FMT_I444) {
173       b.mPlanes[1].mHeight = img->d_h;
174       b.mPlanes[1].mWidth = img->d_w;
175 
176       b.mPlanes[2].mHeight = img->d_h;
177       b.mPlanes[2].mWidth = img->d_w;
178     } else {
179       LOG("VPX Unknown image format");
180       return DecodePromise::CreateAndReject(
181           MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
182                       RESULT_DETAIL("VPX Unknown image format")),
183           __func__);
184     }
185     b.mYUVColorSpace = [&]() {
186       switch (img->cs) {
187         case VPX_CS_BT_601:
188         case VPX_CS_SMPTE_170:
189         case VPX_CS_SMPTE_240:
190           return gfx::YUVColorSpace::BT601;
191         case VPX_CS_BT_709:
192           return gfx::YUVColorSpace::BT709;
193         case VPX_CS_BT_2020:
194           return gfx::YUVColorSpace::BT2020;
195         default:
196           return DefaultColorSpace({img->d_w, img->d_h});
197       }
198     }();
199     b.mColorRange = img->range == VPX_CR_FULL_RANGE ? gfx::ColorRange::FULL
200                                                     : gfx::ColorRange::LIMITED;
201 
202     RefPtr<VideoData> v;
203     if (!img_alpha) {
204       v = VideoData::CreateAndCopyData(
205           mInfo, mImageContainer, aSample->mOffset, aSample->mTime,
206           aSample->mDuration, b, aSample->mKeyframe, aSample->mTimecode,
207           mInfo.ScaledImageRect(img->d_w, img->d_h), mImageAllocator);
208     } else {
209       VideoData::YCbCrBuffer::Plane alpha_plane;
210       alpha_plane.mData = img_alpha->planes[0];
211       alpha_plane.mStride = img_alpha->stride[0];
212       alpha_plane.mHeight = img_alpha->d_h;
213       alpha_plane.mWidth = img_alpha->d_w;
214       alpha_plane.mSkip = 0;
215       v = VideoData::CreateAndCopyData(
216           mInfo, mImageContainer, aSample->mOffset, aSample->mTime,
217           aSample->mDuration, b, alpha_plane, aSample->mKeyframe,
218           aSample->mTimecode, mInfo.ScaledImageRect(img->d_w, img->d_h));
219     }
220 
221     if (!v) {
222       LOG("Image allocation error source %ux%u display %ux%u picture %ux%u",
223           img->d_w, img->d_h, mInfo.mDisplay.width, mInfo.mDisplay.height,
224           mInfo.mImage.width, mInfo.mImage.height);
225       return DecodePromise::CreateAndReject(
226           MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__), __func__);
227     }
228     results.AppendElement(std::move(v));
229   }
230   return DecodePromise::CreateAndResolve(std::move(results), __func__);
231 }
232 
Decode(MediaRawData * aSample)233 RefPtr<MediaDataDecoder::DecodePromise> VPXDecoder::Decode(
234     MediaRawData* aSample) {
235   return InvokeAsync<MediaRawData*>(mTaskQueue, this, __func__,
236                                     &VPXDecoder::ProcessDecode, aSample);
237 }
238 
Drain()239 RefPtr<MediaDataDecoder::DecodePromise> VPXDecoder::Drain() {
240   return InvokeAsync(mTaskQueue, __func__, [] {
241     return DecodePromise::CreateAndResolve(DecodedData(), __func__);
242   });
243 }
244 
DecodeAlpha(vpx_image_t ** aImgAlpha,const MediaRawData * aSample)245 MediaResult VPXDecoder::DecodeAlpha(vpx_image_t** aImgAlpha,
246                                     const MediaRawData* aSample) {
247   vpx_codec_err_t r = vpx_codec_decode(&mVPXAlpha, aSample->AlphaData(),
248                                        aSample->AlphaSize(), nullptr, 0);
249   if (r) {
250     LOG("VPX decode alpha error: %s", vpx_codec_err_to_string(r));
251     return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
252                        RESULT_DETAIL("VPX decode alpha error: %s",
253                                      vpx_codec_err_to_string(r)));
254   }
255 
256   vpx_codec_iter_t iter = nullptr;
257 
258   *aImgAlpha = vpx_codec_get_frame(&mVPXAlpha, &iter);
259   NS_ASSERTION((*aImgAlpha)->fmt == VPX_IMG_FMT_I420 ||
260                    (*aImgAlpha)->fmt == VPX_IMG_FMT_I444,
261                "WebM image format not I420 or I444");
262 
263   return NS_OK;
264 }
265 
266 /* static */
IsVPX(const nsACString & aMimeType,uint8_t aCodecMask)267 bool VPXDecoder::IsVPX(const nsACString& aMimeType, uint8_t aCodecMask) {
268   return ((aCodecMask & VPXDecoder::VP8) &&
269           aMimeType.EqualsLiteral("video/vp8")) ||
270          ((aCodecMask & VPXDecoder::VP9) &&
271           aMimeType.EqualsLiteral("video/vp9"));
272 }
273 
274 /* static */
IsVP8(const nsACString & aMimeType)275 bool VPXDecoder::IsVP8(const nsACString& aMimeType) {
276   return IsVPX(aMimeType, VPXDecoder::VP8);
277 }
278 
279 /* static */
IsVP9(const nsACString & aMimeType)280 bool VPXDecoder::IsVP9(const nsACString& aMimeType) {
281   return IsVPX(aMimeType, VPXDecoder::VP9);
282 }
283 
284 /* static */
IsKeyframe(Span<const uint8_t> aBuffer,Codec aCodec)285 bool VPXDecoder::IsKeyframe(Span<const uint8_t> aBuffer, Codec aCodec) {
286   VPXStreamInfo info;
287   return GetStreamInfo(aBuffer, info, aCodec) && info.mKeyFrame;
288 }
289 
290 /* static */
GetFrameSize(Span<const uint8_t> aBuffer,Codec aCodec)291 gfx::IntSize VPXDecoder::GetFrameSize(Span<const uint8_t> aBuffer,
292                                       Codec aCodec) {
293   VPXStreamInfo info;
294   if (!GetStreamInfo(aBuffer, info, aCodec)) {
295     return gfx::IntSize();
296   }
297   return info.mImage;
298 }
299 
300 /* static */
GetDisplaySize(Span<const uint8_t> aBuffer,Codec aCodec)301 gfx::IntSize VPXDecoder::GetDisplaySize(Span<const uint8_t> aBuffer,
302                                         Codec aCodec) {
303   VPXStreamInfo info;
304   if (!GetStreamInfo(aBuffer, info, aCodec)) {
305     return gfx::IntSize();
306   }
307   return info.mDisplay;
308 }
309 
310 /* static */
GetVP9Profile(Span<const uint8_t> aBuffer)311 int VPXDecoder::GetVP9Profile(Span<const uint8_t> aBuffer) {
312   VPXStreamInfo info;
313   if (!GetStreamInfo(aBuffer, info, Codec::VP9)) {
314     return -1;
315   }
316   return info.mProfile;
317 }
318 
319 /* static */
GetStreamInfo(Span<const uint8_t> aBuffer,VPXDecoder::VPXStreamInfo & aInfo,Codec aCodec)320 bool VPXDecoder::GetStreamInfo(Span<const uint8_t> aBuffer,
321                                VPXDecoder::VPXStreamInfo& aInfo, Codec aCodec) {
322   if (aBuffer.IsEmpty()) {
323     // Can't be good.
324     return false;
325   }
326 
327   aInfo = VPXStreamInfo();
328 
329   if (aCodec == Codec::VP8) {
330     aInfo.mKeyFrame = (aBuffer[0] & 1) ==
331                       0;  // frame type (0 for key frames, 1 for interframes)
332     if (!aInfo.mKeyFrame) {
333       // We can't retrieve the required information from interframes.
334       return true;
335     }
336     if (aBuffer.Length() < 10) {
337       return false;
338     }
339     uint8_t version = (aBuffer[0] >> 1) & 0x7;
340     if (version > 3) {
341       return false;
342     }
343     uint8_t start_code_byte_0 = aBuffer[3];
344     uint8_t start_code_byte_1 = aBuffer[4];
345     uint8_t start_code_byte_2 = aBuffer[5];
346     if (start_code_byte_0 != 0x9d || start_code_byte_1 != 0x01 ||
347         start_code_byte_2 != 0x2a) {
348       return false;
349     }
350     uint16_t width = (aBuffer[6] | aBuffer[7] << 8) & 0x3fff;
351     uint16_t height = (aBuffer[8] | aBuffer[9] << 8) & 0x3fff;
352 
353     // aspect ratio isn't found in the VP8 frame header.
354     aInfo.mImage = aInfo.mDisplay = gfx::IntSize(width, height);
355     aInfo.mDisplayAspectRatio =
356         (float)(aInfo.mDisplay.Width()) / (float)(aInfo.mDisplay.Height());
357     return true;
358   }
359 
360   BitReader br(aBuffer.Elements(), aBuffer.Length() * 8);
361   uint32_t frameMarker = br.ReadBits(2);  // frame_marker
362   if (frameMarker != 2) {
363     // That's not a valid vp9 header.
364     return false;
365   }
366   uint32_t profile = br.ReadBits(1);  // profile_low_bit
367   profile |= br.ReadBits(1) << 1;     // profile_high_bit
368   if (profile == 3) {
369     profile += br.ReadBits(1);  // reserved_zero
370     if (profile > 3) {
371       // reserved_zero wasn't zero.
372       return false;
373     }
374   }
375 
376   aInfo.mProfile = profile;
377 
378   bool show_existing_frame = br.ReadBits(1);
379   if (show_existing_frame) {
380     if (profile == 3 && aBuffer.Length() < 2) {
381       return false;
382     }
383     Unused << br.ReadBits(3);  // frame_to_show_map_idx
384     return true;
385   }
386 
387   if (aBuffer.Length() < 10) {
388     // Header too small;
389     return false;
390   }
391 
392   aInfo.mKeyFrame = !br.ReadBits(1);
393   bool show_frame = br.ReadBits(1);
394   bool error_resilient_mode = br.ReadBits(1);
395 
396   auto frame_sync_code = [&]() -> bool {
397     uint8_t frame_sync_byte_1 = br.ReadBits(8);
398     uint8_t frame_sync_byte_2 = br.ReadBits(8);
399     uint8_t frame_sync_byte_3 = br.ReadBits(8);
400     return frame_sync_byte_1 == 0x49 && frame_sync_byte_2 == 0x83 &&
401            frame_sync_byte_3 == 0x42;
402   };
403 
404   auto color_config = [&]() -> bool {
405     aInfo.mBitDepth = 8;
406     if (profile >= 2) {
407       bool ten_or_twelve_bit = br.ReadBits(1);
408       aInfo.mBitDepth = ten_or_twelve_bit ? 12 : 10;
409     }
410     aInfo.mColorSpace = br.ReadBits(3);
411     if (aInfo.mColorSpace != 7 /* CS_RGB */) {
412       aInfo.mFullRange = br.ReadBits(1);
413       if (profile == 1 || profile == 3) {
414         aInfo.mSubSampling_x = br.ReadBits(1);
415         aInfo.mSubSampling_y = br.ReadBits(1);
416         if (br.ReadBits(1)) {  // reserved_zero
417           return false;
418         };
419       } else {
420         aInfo.mSubSampling_x = true;
421         aInfo.mSubSampling_y = true;
422       }
423     } else {
424       aInfo.mFullRange = true;
425       if (profile == 1 || profile == 3) {
426         aInfo.mSubSampling_x = false;
427         aInfo.mSubSampling_y = false;
428         if (br.ReadBits(1)) {  // reserved_zero
429           return false;
430         };
431       } else {
432         // sRGB color space is only available with VP9 profile 1.
433         return false;
434       }
435     }
436     return true;
437   };
438 
439   auto frame_size = [&]() {
440     int32_t width = static_cast<int32_t>(br.ReadBits(16)) + 1;
441     int32_t height = static_cast<int32_t>(br.ReadBits(16)) + 1;
442     aInfo.mImage = gfx::IntSize(width, height);
443   };
444 
445   auto render_size = [&]() {
446     bool render_and_frame_size_different = br.ReadBits(1);
447     if (render_and_frame_size_different) {
448       int32_t width = static_cast<int32_t>(br.ReadBits(16)) + 1;
449       int32_t height = static_cast<int32_t>(br.ReadBits(16)) + 1;
450       aInfo.mDisplay = gfx::IntSize(width, height);
451     } else {
452       aInfo.mDisplay = aInfo.mImage;
453     }
454     aInfo.mDisplayAspectRatio =
455         (float)(aInfo.mDisplay.Width()) / (float)(aInfo.mDisplay.Height());
456   };
457 
458   if (aInfo.mKeyFrame) {
459     if (!frame_sync_code()) {
460       return false;
461     }
462     if (!color_config()) {
463       return false;
464     }
465     frame_size();
466     render_size();
467   } else {
468     bool intra_only = show_frame ? false : br.ReadBit();
469     if (!error_resilient_mode) {
470       Unused << br.ReadBits(2);  // reset_frame_context
471     }
472     if (intra_only) {
473       if (!frame_sync_code()) {
474         return false;
475       }
476       if (profile > 0) {
477         if (!color_config()) {
478           return false;
479         }
480       } else {
481         aInfo.mColorSpace = 1;  // CS_BT_601
482         aInfo.mSubSampling_x = true;
483         aInfo.mSubSampling_y = true;
484         aInfo.mBitDepth = 8;
485       }
486       Unused << br.ReadBits(8);  // refresh_frame_flags
487       frame_size();
488       render_size();
489     }
490   }
491   return true;
492 }
493 
494 // Ref: "VP Codec ISO Media File Format Binding, v1.0, 2017-03-31"
495 // <https://www.webmproject.org/vp9/mp4/>
496 //
497 // class VPCodecConfigurationBox extends FullBox('vpcC', version = 1, 0)
498 // {
499 //     VPCodecConfigurationRecord() vpcConfig;
500 // }
501 //
502 // aligned (8) class VPCodecConfigurationRecord {
503 //     unsigned int (8)     profile;
504 //     unsigned int (8)     level;
505 //     unsigned int (4)     bitDepth;
506 //     unsigned int (3)     chromaSubsampling;
507 //     unsigned int (1)     videoFullRangeFlag;
508 //     unsigned int (8)     colourPrimaries;
509 //     unsigned int (8)     transferCharacteristics;
510 //     unsigned int (8)     matrixCoefficients;
511 //     unsigned int (16)    codecIntializationDataSize;
512 //     unsigned int (8)[]   codecIntializationData;
513 // }
514 
515 /* static */
GetVPCCBox(MediaByteBuffer * aDestBox,const VPXStreamInfo & aInfo)516 void VPXDecoder::GetVPCCBox(MediaByteBuffer* aDestBox,
517                             const VPXStreamInfo& aInfo) {
518   ByteWriter<BigEndian> writer(*aDestBox);
519 
520   int chroma = [&]() {
521     if (aInfo.mSubSampling_x && aInfo.mSubSampling_y) {
522       return 1;  // 420 Colocated;
523     }
524     if (aInfo.mSubSampling_x && !aInfo.mSubSampling_y) {
525       return 2;  // 422
526     }
527     if (!aInfo.mSubSampling_x && !aInfo.mSubSampling_y) {
528       return 3;  // 444
529     }
530     // This indicates 4:4:0 subsampling, which is not expressable in the
531     // 'vpcC' box. Default to 4:2:0.
532     return 1;
533   }();
534 
535   MOZ_ALWAYS_TRUE(writer.WriteU32(1 << 24));        // version & flag
536   MOZ_ALWAYS_TRUE(writer.WriteU8(aInfo.mProfile));  // profile
537   MOZ_ALWAYS_TRUE(writer.WriteU8(10));              // level set it to 1.0
538   MOZ_ALWAYS_TRUE(writer.WriteU8(
539       (0xF & aInfo.mBitDepth) << 4 | (0x7 & chroma) << 1 |
540       (0x1 & aInfo.mFullRange)));      // bitdepth (4 bits), chroma (3 bits),
541                                        // video full/restrice range (1 bit)
542   MOZ_ALWAYS_TRUE(writer.WriteU8(2));  // color primaries: unknown
543   MOZ_ALWAYS_TRUE(writer.WriteU8(2));  // transfer characteristics: unknown
544   MOZ_ALWAYS_TRUE(writer.WriteU8(2));  // matrix coefficient: unknown
545   MOZ_ALWAYS_TRUE(
546       writer.WriteU16(0));  // codecIntializationDataSize (must be 0 for VP9)
547 }
548 
549 }  // namespace mozilla
550 #undef LOG
551