Bug 1407653: Make the fake h264 encoder/decoder more realistic. draft
authorByron Campen [:bwc] <docfaraday@gmail.com>
Thu, 22 Mar 2018 10:26:54 -0500
changeset 778697 f4f1f0131e3631ff36a502d9ae1d07cf5f824391
parent 778675 0d661c592a164ca918ed12f87cbcf7f52c293359
push id105572
push userbcampen@mozilla.com
push dateFri, 06 Apr 2018 19:11:44 +0000
bugs1407653
milestone61.0a1
Bug 1407653: Make the fake h264 encoder/decoder more realistic. MozReview-Commit-ID: 7aC7fdf0RfS
dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp
--- a/dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp
+++ b/dom/media/gmp-plugin-openh264/gmp-fake-openh264.cpp
@@ -80,34 +80,38 @@ static int g_log_level = GL_CRIT;
         if ((l >= 0) && (l <= 3)) {                        \
         log_string = kLogStrings[l];                       \
         }                                                  \
         std::cerr << log_string << ": " << x << std::endl; \
         }                                                  \
     } while(0)
 
 
-GMPPlatformAPI* g_platform_api = nullptr;
-
 class FakeVideoEncoder;
 class FakeVideoDecoder;
 
 struct EncodedFrame {
   uint32_t length_;
-  uint8_t h264_compat_;
-  uint32_t magic_;
   uint32_t width_;
   uint32_t height_;
   uint8_t y_;
   uint8_t u_;
   uint8_t v_;
   uint32_t timestamp_;
 };
 
-#define ENCODED_FRAME_MAGIC 0x4652414d
+// Canned H264 stuff based on the real encoder's output.
+static const uint8_t cannedSps[] = {0x67, 0x42, 0xc0, 0x29,
+                                    0x8c, 0x8d, 0x40, 0xa0,
+                                    0xf9, 0x00, 0xf0, 0x88,
+                                    0x46, 0xa0};
+
+static const uint8_t cannedPps[] = {0x68, 0xce, 0x3c, 0x80};
+static const uint8_t cannedIFramePrefix[] = {0x65, 0xb8};
+static const uint8_t cannedPFramePrefix[] = {0x61, 0xe0};
 
 template <typename T> class SelfDestruct {
  public:
   explicit SelfDestruct (T* t) : t_ (t) {}
   ~SelfDestruct() {
     if (t_) {
       t_->Destroy();
     }
@@ -121,31 +125,16 @@ template <typename T> class SelfDestruct
     return t;
   }
 #endif
 
  private:
   T* t_;
 };
 
-class FakeEncoderTask : public GMPTask {
- public:
-  FakeEncoderTask(FakeVideoEncoder* encoder,
-                  GMPVideoi420Frame* frame,
-                  GMPVideoFrameType type)
-      : encoder_(encoder), frame_(frame), type_(type) {}
-
-  void Run() override;
-  void Destroy() override { delete this; }
-
-  FakeVideoEncoder* encoder_;
-  GMPVideoi420Frame* frame_;
-  GMPVideoFrameType type_;
-};
-
 class FakeVideoEncoder : public GMPVideoEncoder {
  public:
   explicit FakeVideoEncoder (GMPVideoHost* hostAPI) :
     host_ (hostAPI),
     callback_ (nullptr),
     frame_size_(BIG_FRAME),
     frames_encoded_(0) {}
 
@@ -169,75 +158,110 @@ class FakeVideoEncoder : public GMPVideo
     }
     GMPLOG (GL_INFO, "Initialized encoder");
   }
 
   void SendFrame(GMPVideoi420Frame* inputImage,
                  GMPVideoFrameType frame_type,
                  uint8_t nal_type)
   {
-    // Encode this in a frame that looks a little bit like H.264.
-    // Send SPS/PPS/IDR to avoid confusing people
-    // Copy the data. This really should convert this to network byte order.
-    EncodedFrame eframe;
-    eframe.length_ = sizeof(eframe) - sizeof(uint32_t);
-    eframe.h264_compat_ = nal_type; // 7 = SPS, 8 = PPS, 5 = IFrame/IDR slice, 1=PFrame/slice
-    eframe.magic_ = ENCODED_FRAME_MAGIC;
-    eframe.width_ = inputImage->Width();
-    eframe.height_ = inputImage->Height();
-    eframe.y_ = AveragePlane(inputImage->Buffer(kGMPYPlane),
-                             inputImage->AllocatedSize(kGMPYPlane));
-    eframe.u_ = AveragePlane(inputImage->Buffer(kGMPUPlane),
-                             inputImage->AllocatedSize(kGMPUPlane));
-    eframe.v_ = AveragePlane(inputImage->Buffer(kGMPVPlane),
-                             inputImage->AllocatedSize(kGMPVPlane));
-
-    eframe.timestamp_ = inputImage->Timestamp();
-
-    // Now return the encoded data back to the parent.
     GMPVideoFrame* ftmp;
     GMPErr err = host_->CreateFrame(kGMPEncodedVideoFrame, &ftmp);
     if (err != GMPNoErr) {
       GMPLOG (GL_ERROR, "Error creating encoded frame");
       return;
     }
 
     GMPVideoEncodedFrame* f = static_cast<GMPVideoEncodedFrame*> (ftmp);
 
-    err = f->CreateEmptyFrame (sizeof(eframe) +
-                               (nal_type == 5 ? sizeof(uint32_t) + frame_size_ : 0));
+    uint32_t length = 0;
+    switch (nal_type) {
+      case 8:
+        length = sizeof(cannedPps);
+        break;
+      case 7:
+        length = sizeof(cannedSps);
+        break;
+      case 5:
+        length = frame_size_;
+        break;
+      case 1:
+        length = sizeof(cannedPFramePrefix) + sizeof(EncodedFrame);
+        break;
+      default:
+        MOZ_CRASH();
+    }
+
+    err = f->CreateEmptyFrame (length + sizeof(uint32_t));
     if (err != GMPNoErr) {
       GMPLOG (GL_ERROR, "Error allocating frame data");
       f->Destroy();
       return;
     }
-    memcpy(f->Buffer(), &eframe, sizeof(eframe));
-    if (nal_type == 5) {
-      // set the size for the fake iframe
-      *((uint32_t*) (f->Buffer() + sizeof(eframe))) = frame_size_;
+
+    uint8_t* destBuf = f->Buffer();
+    *reinterpret_cast<uint32_t*>(destBuf) = length;
+    destBuf += sizeof(length);
+
+    switch (nal_type) {
+      case 8:
+        memcpy(destBuf, cannedPps, sizeof(cannedPps));
+        destBuf += sizeof(cannedPps);
+        break;
+      case 7:
+        memcpy(destBuf, cannedSps, sizeof(cannedSps));
+        destBuf += sizeof(cannedSps);
+        break;
+      case 5:
+        memcpy(destBuf, cannedIFramePrefix, sizeof(cannedIFramePrefix));
+        destBuf += sizeof(cannedIFramePrefix);
+        break;
+      case 1:
+        memcpy(destBuf, cannedPFramePrefix, sizeof(cannedPFramePrefix));
+        destBuf += sizeof(cannedPFramePrefix);
+        break;
+      default:
+        MOZ_CRASH();
     }
 
-    f->SetEncodedWidth(eframe.width_);
-    f->SetEncodedHeight(eframe.height_);
-    f->SetTimeStamp(eframe.timestamp_);
+    if (nal_type == 5 || nal_type == 1) {
+      EncodedFrame eframe;
+      eframe.length_ = length;
+      eframe.width_ = inputImage->Width();
+      eframe.height_ = inputImage->Height();
+      eframe.y_ = AveragePlane(inputImage->Buffer(kGMPYPlane),
+                               inputImage->AllocatedSize(kGMPYPlane));
+      eframe.u_ = AveragePlane(inputImage->Buffer(kGMPUPlane),
+                               inputImage->AllocatedSize(kGMPUPlane));
+      eframe.v_ = AveragePlane(inputImage->Buffer(kGMPVPlane),
+                               inputImage->AllocatedSize(kGMPVPlane));
+
+      eframe.timestamp_ = inputImage->Timestamp();
+      memcpy(destBuf, &eframe, sizeof(eframe));
+      destBuf += sizeof(eframe);
+    }
+
+    f->SetEncodedWidth(inputImage->Width());
+    f->SetEncodedHeight(inputImage->Height());
+    f->SetTimeStamp(inputImage->Timestamp());
     f->SetFrameType(frame_type);
     f->SetCompleteFrame(true);
     f->SetBufferType(GMP_BufferLength32);
 
     GMPLOG (GL_DEBUG, "Encoding complete. type= "
             << f->FrameType()
             << " NAL_type="
-            << (int) eframe.h264_compat_
+            << (int) nal_type
             << " length="
             << f->Size()
             << " timestamp="
             << f->TimeStamp()
             << " width/height="
-            << eframe.width_
-            << "x" << eframe.height_);
+            << inputImage->Width()
+            << "x" << inputImage->Height());
 
     // Return the encoded frame.
     GMPCodecSpecificInfo info;
     mozilla::PodZero(&info);
     info.mCodecType = kGMPVideoCodecH264;
     info.mBufferType = GMP_BufferLength32;
     info.mCodecSpecific.mH264.mSimulcastIdx = 0;
     GMPLOG (GL_DEBUG, "Calling callback");
@@ -245,48 +269,41 @@ class FakeVideoEncoder : public GMPVideo
     GMPLOG (GL_DEBUG, "Callback called");
   }
 
   void Encode (GMPVideoi420Frame* inputImage,
                const uint8_t* aCodecSpecificInfo,
                uint32_t aCodecSpecificInfoLength,
                const GMPVideoFrameType* aFrameTypes,
                uint32_t aFrameTypesLength) override {
+    SelfDestruct<GMPVideoi420Frame> ifd (inputImage);
+
     GMPLOG (GL_DEBUG,
             __FUNCTION__
             << " size="
             << inputImage->Width() << "x" << inputImage->Height());
 
     assert (aFrameTypesLength != 0);
 
-    g_platform_api->runonmainthread(new FakeEncoderTask(this,
-                                                        inputImage,
-                                                        aFrameTypes[0]));
-  }
-
-  void Encode_m (GMPVideoi420Frame* inputImage,
-                 GMPVideoFrameType frame_type) {
-    SelfDestruct<GMPVideoi420Frame> ifd (inputImage);
-
-    if (frame_type  == kGMPKeyFrame) {
+    if (aFrameTypes[0] == kGMPKeyFrame) {
       if (!inputImage)
         return;
     }
     if (!inputImage) {
       GMPLOG (GL_ERROR, "no input image");
       return;
     }
 
-    if (frame_type  == kGMPKeyFrame ||
+    if ((aFrameTypes[0] == kGMPKeyFrame) ||
         frames_encoded_++ % 10 == 0) { // periodically send iframes anyways
       SendFrame(inputImage, kGMPKeyFrame, 7); // 7 = SPS, 8 = PPS, 5 = IFrame/IDR slice, 1=PFrame/slice
       SendFrame(inputImage, kGMPKeyFrame, 8);
       SendFrame(inputImage, kGMPKeyFrame, 5);
     } else {
-      SendFrame(inputImage, frame_type, 1);
+      SendFrame(inputImage, aFrameTypes[0], 1);
     }
   }
 
   void SetChannelParameters (uint32_t aPacketLoss, uint32_t aRTT) override {
   }
 
   void SetRates (uint32_t aNewBitRate, uint32_t aFrameRate) override {
   }
@@ -310,36 +327,16 @@ class FakeVideoEncoder : public GMPVideo
   }
 
   GMPVideoHost* host_;
   GMPVideoEncoderCallback* callback_;
   uint32_t frame_size_;
   uint32_t frames_encoded_;
 };
 
-void FakeEncoderTask::Run() {
-  encoder_->Encode_m(frame_, type_);
-  frame_ = nullptr; // Encode_m() destroys the frame
-}
-
-class FakeDecoderTask : public GMPTask {
- public:
-  FakeDecoderTask(FakeVideoDecoder* decoder,
-                  GMPVideoEncodedFrame* frame,
-                  int64_t time)
-      : decoder_(decoder), frame_(frame), time_(time) {}
-
-  void Run() override;
-  void Destroy() override { delete this; }
-
-  FakeVideoDecoder* decoder_;
-  GMPVideoEncodedFrame* frame_;
-  int64_t time_;
-};
-
 class FakeVideoDecoder : public GMPVideoDecoder {
  public:
   explicit FakeVideoDecoder (GMPVideoHost* hostAPI) :
     host_ (hostAPI),
     callback_ (nullptr) {}
 
   ~FakeVideoDecoder() override = default;
 
@@ -357,120 +354,125 @@ class FakeVideoDecoder : public GMPVideo
     callback_ = callback;
   }
 
   void Decode (GMPVideoEncodedFrame* inputFrame,
                bool missingFrames,
                const uint8_t* aCodecSpecificInfo,
                uint32_t aCodecSpecificInfoLength,
                int64_t renderTimeMs = -1) override {
+    // Attach a self-destructor so that the input frame is destroyed on return.
+    SelfDestruct<GMPVideoEncodedFrame> ifd (inputFrame);
+
     GMPLOG (GL_DEBUG, __FUNCTION__
             << "Decoding frame size=" << inputFrame->Size()
             << " timestamp=" << inputFrame->TimeStamp());
-    g_platform_api->runonmainthread(new FakeDecoderTask(this, inputFrame, renderTimeMs));
+
+    const uint8_t* buf = inputFrame->Buffer();
+
+    if (!buf) {
+      MOZ_CRASH("Buffer is null");
+    }
+
+    if (inputFrame->Size() < sizeof(uint32_t)) {
+      MOZ_CRASH("Not enough buffer for length field");
+    }
+
+    const uint8_t* end = buf + inputFrame->Size();
+
+    size_t size = *reinterpret_cast<const uint32_t*>(buf);
+    if (size != inputFrame->Size()) {
+      MOZ_CRASH("Inconsistent length");
+    }
+    buf += sizeof(uint32_t);
+
+    if (buf >= end) {
+      MOZ_CRASH("Not enough buffer for nal type");
+    }
+
+    uint8_t nal_type = buf[0] & 0x1f;
+
+    if (nal_type == 5 || nal_type == 1) {
+      buf += (nal_type == 5) ? sizeof(cannedPFramePrefix) : sizeof(cannedIFramePrefix);
+      if (buf > end) {
+        MOZ_CRASH("Not enough buffer for frame prefix");
+      }
+
+      if (buf + sizeof(EncodedFrame) > end) {
+        MOZ_CRASH("Not enough buffer for EncodedFrame");
+      }
+
+      const EncodedFrame *eframe = reinterpret_cast<const EncodedFrame*>(buf);
+      int width = eframe->width_;
+      int height = eframe->height_;
+      int ystride = eframe->width_;
+      int uvstride = eframe->width_/2;
+
+      GMPLOG (GL_DEBUG, "Video frame ready for display "
+              << width
+              << "x"
+              << height
+              << " timestamp="
+              << inputFrame->TimeStamp()
+              << " y/u/v=" << (int) eframe->y_ << ":" << (int) eframe->u_ << ":" << (int) eframe->v_);
+
+      GMPVideoFrame* ftmp = nullptr;
+
+      // Translate the image.
+      GMPErr err = host_->CreateFrame (kGMPI420VideoFrame, &ftmp);
+      if (err != GMPNoErr) {
+        GMPLOG (GL_ERROR, "Couldn't allocate empty I420 frame");
+        return;
+      }
+
+      GMPVideoi420Frame* frame = static_cast<GMPVideoi420Frame*> (ftmp);
+      err = frame->CreateEmptyFrame (
+          width, height,
+          ystride, uvstride, uvstride);
+      if (err != GMPNoErr) {
+        GMPLOG (GL_ERROR, "Couldn't make decoded frame");
+        return;
+      }
+
+      memset(frame->Buffer(kGMPYPlane),
+             eframe->y_,
+             frame->AllocatedSize(kGMPYPlane));
+      memset(frame->Buffer(kGMPUPlane),
+             eframe->u_,
+             frame->AllocatedSize(kGMPUPlane));
+      memset(frame->Buffer(kGMPVPlane),
+             eframe->v_,
+             frame->AllocatedSize(kGMPVPlane));
+
+      GMPLOG (GL_DEBUG, "Allocated size = "
+              << frame->AllocatedSize (kGMPYPlane));
+      frame->SetTimestamp (inputFrame->TimeStamp());
+      frame->SetDuration (inputFrame->Duration());
+      callback_->Decoded (frame);
+    }
   }
 
   void Reset() override {
   }
 
   void Drain() override {
   }
 
   void DecodingComplete() override {
     delete this;
   }
 
-  // Return the decoded data back to the parent.
-  void Decode_m (GMPVideoEncodedFrame* inputFrame,
-                 int64_t renderTimeMs) {
-    // Attach a self-destructor so that the input frame is destroyed on return.
-    SelfDestruct<GMPVideoEncodedFrame> ifd (inputFrame);
-
-    EncodedFrame *eframe;
-    eframe = reinterpret_cast<EncodedFrame*>(inputFrame->Buffer());
-    GMPLOG(GL_DEBUG,"magic="  << eframe->magic_ << " h264_compat="  << (int) eframe->h264_compat_
-           << " width=" << eframe->width_ << " height=" << eframe->height_
-           << " timestamp=" << inputFrame->TimeStamp()
-           << " y/u/v=" << (int) eframe->y_ << ":" << (int) eframe->u_ << ":" << (int) eframe->v_);
-    if (inputFrame->Size() != (sizeof(*eframe))) {
-      GMPLOG (GL_ERROR, "Couldn't decode frame. Size=" << inputFrame->Size());
-      return;
-    }
-
-    if (eframe->magic_ != ENCODED_FRAME_MAGIC) {
-      GMPLOG (GL_ERROR, "Couldn't decode frame. Magic=" << eframe->magic_);
-      return;
-    }
-    if (eframe->h264_compat_ != 5 && eframe->h264_compat_ != 1) {
-      // only return video for iframes or pframes
-      GMPLOG (GL_DEBUG, "Not a video frame: NAL type " << (int) eframe->h264_compat_);
-      // Decode it anyways
-    }
-
-    int width = eframe->width_;
-    int height = eframe->height_;
-    int ystride = eframe->width_;
-    int uvstride = eframe->width_/2;
-
-    GMPLOG (GL_DEBUG, "Video frame ready for display "
-            << width
-            << "x"
-            << height
-            << " timestamp="
-            << inputFrame->TimeStamp());
-
-    GMPVideoFrame* ftmp = nullptr;
-
-    // Translate the image.
-    GMPErr err = host_->CreateFrame (kGMPI420VideoFrame, &ftmp);
-    if (err != GMPNoErr) {
-      GMPLOG (GL_ERROR, "Couldn't allocate empty I420 frame");
-      return;
-    }
-
-    GMPVideoi420Frame* frame = static_cast<GMPVideoi420Frame*> (ftmp);
-    err = frame->CreateEmptyFrame (
-        width, height,
-        ystride, uvstride, uvstride);
-    if (err != GMPNoErr) {
-      GMPLOG (GL_ERROR, "Couldn't make decoded frame");
-      return;
-    }
-
-    memset(frame->Buffer(kGMPYPlane),
-           eframe->y_,
-           frame->AllocatedSize(kGMPYPlane));
-    memset(frame->Buffer(kGMPUPlane),
-           eframe->u_,
-           frame->AllocatedSize(kGMPUPlane));
-    memset(frame->Buffer(kGMPVPlane),
-           eframe->v_,
-           frame->AllocatedSize(kGMPVPlane));
-
-    GMPLOG (GL_DEBUG, "Allocated size = "
-            << frame->AllocatedSize (kGMPYPlane));
-    frame->SetTimestamp (inputFrame->TimeStamp());
-    frame->SetDuration (inputFrame->Duration());
-    callback_->Decoded (frame);
-  }
-
   GMPVideoHost* host_;
   GMPVideoDecoderCallback* callback_;
 };
 
-void FakeDecoderTask::Run() {
-  decoder_->Decode_m(frame_, time_);
-  frame_ = nullptr; // Decode_m() destroys the frame
-}
-
 extern "C" {
 
   PUBLIC_FUNC GMPErr
   GMPInit (GMPPlatformAPI* aPlatformAPI) {
-    g_platform_api = aPlatformAPI;
     return GMPNoErr;
   }
 
   PUBLIC_FUNC GMPErr
   GMPGetAPI (const char* aApiName, void* aHostAPI, void** aPluginApi) {
     if (!strcmp (aApiName, GMP_API_VIDEO_DECODER)) {
       *aPluginApi = new FakeVideoDecoder (static_cast<GMPVideoHost*> (aHostAPI));
       return GMPNoErr;
@@ -479,12 +481,11 @@ extern "C" {
       *aPluginApi = new FakeVideoEncoder (static_cast<GMPVideoHost*> (aHostAPI));
       return GMPNoErr;
     }
     return GMPGenericErr;
   }
 
   PUBLIC_FUNC void
   GMPShutdown (void) {
-    g_platform_api = nullptr;
   }
 
 } // extern "C"