Bug 1482059 - P1. Implement VP8/VP9 frame header parser. r=TD-Linux
authorJean-Yves Avenard <jyavenard@mozilla.com>
Wed, 14 Nov 2018 09:33:10 +0000
changeset 446203 76b88f20b0824ae7e24b4de15c2bec404faa30e2
parent 446202 b8fba4d3bec32495e45e8e9c850d300d4bd9d842
child 446204 6b87d74e97c91155e6023d154aab1786f3518101
push id72957
push userjyavenard@mozilla.com
push dateWed, 14 Nov 2018 14:41:23 +0000
treeherderautoland@6b87d74e97c9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersTD-Linux
bugs1482059
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1482059 - P1. Implement VP8/VP9 frame header parser. r=TD-Linux And use it to determine if a frame is a keyframe, its size and display size. Differential Revision: https://phabricator.services.mozilla.com/D11588
dom/media/platforms/agnostic/VPXDecoder.cpp
dom/media/platforms/agnostic/VPXDecoder.h
--- a/dom/media/platforms/agnostic/VPXDecoder.cpp
+++ b/dom/media/platforms/agnostic/VPXDecoder.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "VPXDecoder.h"
 #include "BitReader.h"
 #include "TimeUnits.h"
 #include "gfx2DGlue.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/SyncRunnable.h"
+#include "mozilla/Unused.h"
 #include "ImageContainer.h"
 #include "nsError.h"
 #include "prsystem.h"
 
 #include <algorithm>
 
 #undef LOG
 #define LOG(arg, ...)                                                          \
@@ -305,69 +306,217 @@ VPXDecoder::IsVP9(const nsACString& aMim
 {
   return IsVPX(aMimeType, VPXDecoder::VP9);
 }
 
 /* static */
 bool
 VPXDecoder::IsKeyframe(Span<const uint8_t> aBuffer, Codec aCodec)
 {
-  vpx_codec_stream_info_t si;
-  PodZero(&si);
-  si.sz = sizeof(si);
-
-  if (aCodec == Codec::VP8) {
-    vpx_codec_peek_stream_info(vpx_codec_vp8_dx(), aBuffer.Elements(), aBuffer.Length(), &si);
-    return bool(si.is_kf);
-  } else if (aCodec == Codec::VP9) {
-    vpx_codec_peek_stream_info(vpx_codec_vp9_dx(), aBuffer.Elements(), aBuffer.Length(), &si);
-    return bool(si.is_kf);
-  }
-
-  return false;
+  VPXStreamInfo info;
+  return GetStreamInfo(aBuffer, info, aCodec) && info.mKeyFrame;
 }
 
 /* static */
 gfx::IntSize
 VPXDecoder::GetFrameSize(Span<const uint8_t> aBuffer, Codec aCodec)
 {
-  vpx_codec_stream_info_t si;
-  PodZero(&si);
-  si.sz = sizeof(si);
+  VPXStreamInfo info;
+  if (!GetStreamInfo(aBuffer, info, aCodec)) {
+    return gfx::IntSize();
+  }
+  return info.mImage;
+}
 
-  if (aCodec == Codec::VP8) {
-    vpx_codec_peek_stream_info(vpx_codec_vp8_dx(), aBuffer.Elements(), aBuffer.Length(), &si);
-  } else if (aCodec == Codec::VP9) {
-    vpx_codec_peek_stream_info(vpx_codec_vp9_dx(), aBuffer.Elements(), aBuffer.Length(), &si);
+/* static */
+gfx::IntSize
+VPXDecoder::GetDisplaySize(Span<const uint8_t> aBuffer, Codec aCodec)
+{
+  VPXStreamInfo info;
+  if (!GetStreamInfo(aBuffer, info, aCodec)) {
+    return gfx::IntSize();
   }
-
-  return gfx::IntSize(si.w, si.h);
+  return info.mDisplay;
 }
 
 /* static */
 int
 VPXDecoder::GetVP9Profile(Span<const uint8_t> aBuffer)
 {
-  if (aBuffer.Length() < 2) {
-    // Can't be good.
+  VPXStreamInfo info;
+  if (!GetStreamInfo(aBuffer, info, Codec::VP9)) {
     return -1;
   }
+  return info.mProfile;
+}
+
+/* static */
+bool
+VPXDecoder::GetStreamInfo(Span<const uint8_t> aBuffer,
+                          VPXDecoder::VPXStreamInfo& aInfo,
+                          Codec aCodec)
+{
+  if (aBuffer.IsEmpty()) {
+    // Can't be good.
+    return false;
+  }
+
+  aInfo = VPXStreamInfo();
+
+  if (aCodec == Codec::VP8) {
+    aInfo.mKeyFrame =
+      (aBuffer[0] & 1) == 0; // frame type (0 for key frames, 1 for interframes)
+    if (!aInfo.mKeyFrame) {
+      // We can't retrieve the required information from interframes.
+      return true;
+    }
+    if (aBuffer.Length() < 10) {
+      return false;
+    }
+    uint8_t version = (aBuffer[0] >> 1) & 0x7;
+    if (version > 3) {
+      return false;
+    }
+    uint8_t start_code_byte_0 = aBuffer[3];
+    uint8_t start_code_byte_1 = aBuffer[4];
+    uint8_t start_code_byte_2 = aBuffer[5];
+    if (start_code_byte_0 != 0x9d || start_code_byte_1 != 0x01 ||
+        start_code_byte_2 != 0x2a) {
+      return false;
+    }
+    uint16_t width = (aBuffer[6] | aBuffer[7] << 8) & 0x3fff;
+    uint16_t height = (aBuffer[8] | aBuffer[9] << 8) & 0x3fff;
+
+    // aspect ratio isn't found in the VP8 frame header.
+    aInfo.mImage = aInfo.mDisplay = gfx::IntSize(width, height);
+    return true;
+  }
+
   BitReader br(aBuffer.Elements(), aBuffer.Length() * 8);
-
   uint32_t frameMarker = br.ReadBits(2); // frame_marker
   if (frameMarker != 2) {
     // That's not a valid vp9 header.
-    return -1;
+    return false;
   }
   uint32_t profile = br.ReadBits(1); // profile_low_bit
-  profile |= br.ReadBits(1) << 1; // profile_high_bit
+  profile |= br.ReadBits(1) << 1;    // profile_high_bit
   if (profile == 3) {
     profile += br.ReadBits(1); // reserved_zero
     if (profile > 3) {
       // reserved_zero wasn't zero.
-      return -1;
+      return false;
     }
   }
-  return profile;
+
+  aInfo.mProfile = profile;
+
+  bool show_existing_frame = br.ReadBits(1);
+  if (show_existing_frame) {
+    if (profile == 3 && aBuffer.Length() < 2) {
+      return false;
+    }
+    Unused << br.ReadBits(3); // frame_to_show_map_idx
+    return true;
+  }
+
+  if (aBuffer.Length() < 10) {
+    // Header too small;
+    return false;
+  }
+
+  aInfo.mKeyFrame = !br.ReadBits(1);
+  bool show_frame = br.ReadBits(1);
+  bool error_resilient_mode = br.ReadBits(1);
+
+  auto frame_sync_code = [&]() -> bool {
+    uint8_t frame_sync_byte_1 = br.ReadBits(8);
+    uint8_t frame_sync_byte_2 = br.ReadBits(8);
+    uint8_t frame_sync_byte_3 = br.ReadBits(8);
+    return frame_sync_byte_1 == 0x49 && frame_sync_byte_2 == 0x83 &&
+           frame_sync_byte_3 == 0x42;
+  };
+
+  auto color_config = [&]() -> bool {
+    aInfo.mBitDepth = 8;
+    if (profile >= 2) {
+      bool ten_or_twelve_bit = br.ReadBits(1);
+      aInfo.mBitDepth = ten_or_twelve_bit ? 12 : 10;
+    }
+    aInfo.mColorSpace = br.ReadBits(3);
+    if (aInfo.mColorSpace != 7 /* CS_RGB */) {
+      aInfo.mFullRange = br.ReadBits(1);
+      if (profile == 1 || profile == 3) {
+        aInfo.mSubSampling_x = br.ReadBits(1);
+        aInfo.mSubSampling_y = br.ReadBits(1);
+        if (br.ReadBits(1)) { // reserved_zero
+          return false;
+        };
+      } else {
+        aInfo.mSubSampling_x = true;
+        aInfo.mSubSampling_y = true;
+      }
+    } else {
+      aInfo.mFullRange = true;
+      if (profile == 1 || profile == 3) {
+        aInfo.mSubSampling_x = false;
+        aInfo.mSubSampling_y = false;
+        if (br.ReadBits(1)) { // reserved_zero
+          return false;
+        };
+      } else {
+        // sRGB color space is only available with VP9 profile 1.
+        return false;
+      }
+    }
+    return true;
+  };
+
+  auto frame_size = [&]() {
+    aInfo.mImage = gfx::IntSize(br.ReadBits(16) + 1, br.ReadBits(16) + 1);
+  };
+
+  auto render_size = [&]() {
+    bool render_and_frame_size_different = br.ReadBits(1);
+    if (render_and_frame_size_different) {
+      aInfo.mDisplay = gfx::IntSize(br.ReadBits(16) + 1, br.ReadBits(16) + 1);
+    } else {
+      aInfo.mDisplay = aInfo.mImage;
+    }
+  };
+
+  if (aInfo.mKeyFrame) {
+    if (!frame_sync_code()) {
+      return false;
+    }
+    if (!color_config()) {
+      return false;
+    }
+    frame_size();
+    render_size();
+  } else {
+    bool intra_only = show_frame ? false : br.ReadBit();
+    if (!error_resilient_mode) {
+      Unused << br.ReadBits(2); // reset_frame_context
+    }
+    if (intra_only) {
+      if (!frame_sync_code()) {
+        return false;
+      }
+      if (profile > 0) {
+        if (!color_config()) {
+          return false;
+        }
+      } else {
+        aInfo.mColorSpace = 1; // CS_BT_601
+        aInfo.mSubSampling_x = 1;
+        aInfo.mSubSampling_y = 1;
+        aInfo.mBitDepth = 8;
+      }
+      Unused << br.ReadBits(8); // refresh_frame_flags
+      frame_size();
+      render_size();
+    }
+  }
+  return true;
 }
 
 } // namespace mozilla
 #undef LOG
--- a/dom/media/platforms/agnostic/VPXDecoder.h
+++ b/dom/media/platforms/agnostic/VPXDecoder.h
@@ -50,21 +50,83 @@ public:
   static bool IsVP8(const nsACString& aMimeType);
   static bool IsVP9(const nsACString& aMimeType);
 
   // Return true if a sample is a keyframe for the specified codec.
   static bool IsKeyframe(Span<const uint8_t> aBuffer, Codec aCodec);
 
   // Return the frame dimensions for a sample for the specified codec.
   static gfx::IntSize GetFrameSize(Span<const uint8_t> aBuffer, Codec aCodec);
+  // Return the display dimensions for a sample for the specified codec.
+  static gfx::IntSize GetDisplaySize(Span<const uint8_t> aBuffer, Codec aCodec);
 
   // Return the VP9 profile as per https://www.webmproject.org/vp9/profiles/
   // Return negative value if error.
   static int GetVP9Profile(Span<const uint8_t> aBuffer);
 
+  struct VPXStreamInfo
+  {
+    gfx::IntSize mImage;
+    gfx::IntSize mDisplay;
+    bool mKeyFrame = false;
+
+    uint8_t mProfile = 0;
+    uint8_t mBitDepth = 8;
+    /*
+    0 CS_UNKNOWN Unknown (in this case the color space must be signaled outside
+      the VP9 bitstream).
+    1 CS_BT_601 Rec. ITU-R BT.601-7
+    2 CS_BT_709 Rec. ITU-R BT.709-6
+    3 CS_SMPTE_170 SMPTE-170
+    4 CS_SMPTE_240 SMPTE-240
+    5 CS_BT_2020 Rec. ITU-R BT.2020-2
+    6 CS_RESERVED Reserved
+    7 CS_RGB sRGB (IEC 61966-2-1)
+    */
+    int mColorSpace = 1; // CS_BT_601
+
+    /*
+    mFullRange == false then:
+      For BitDepth equals 8:
+        Y is between 16 and 235 inclusive.
+        U and V are between 16 and 240 inclusive.
+      For BitDepth equals 10:
+        Y is between 64 and 940 inclusive.
+        U and V are between 64 and 960 inclusive.
+      For BitDepth equals 12:
+        Y is between 256 and 3760.
+        U and V are between 256 and 3840 inclusive.
+    mFullRange == true then:
+      No restriction on Y, U, V values.
+    */
+    bool mFullRange = false;
+
+    /*
+      Sub-sampling, used only for non sRGB colorspace.
+      subsampling_x subsampling_y Description
+          0             0         YUV 4:4:4
+          0             1         YUV 4:4:0
+          1             0         YUV 4:2:2
+          1             1         YUV 4:2:0
+    */
+    bool mSubSampling_x = true;
+    bool mSubSampling_y = true;
+
+    bool IsCompatible(const VPXStreamInfo& aOther) const
+    {
+      return mImage == aOther.mImage && mProfile == aOther.mProfile &&
+             mBitDepth == aOther.mBitDepth && mSubSampling_x &&
+             aOther.mSubSampling_x && mSubSampling_y == aOther.mSubSampling_y;
+    }
+  };
+
+  static bool GetStreamInfo(Span<const uint8_t> aBuffer,
+                            VPXStreamInfo& aInfo,
+                            Codec aCodec);
+
 private:
   ~VPXDecoder();
   RefPtr<DecodePromise> ProcessDecode(MediaRawData* aSample);
   MediaResult DecodeAlpha(vpx_image_t** aImgAlpha, const MediaRawData* aSample);
 
   const RefPtr<layers::ImageContainer> mImageContainer;
   RefPtr<layers::KnowsCompositor> mImageAllocator;
   const RefPtr<TaskQueue> mTaskQueue;