Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / media / cast / video_receiver / video_decoder.cc
index 230b6e1..9f16749 100644 (file)
 #include "media/cast/video_receiver/video_decoder.h"
 
 #include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/json/json_reader.h"
+#include "base/location.h"
 #include "base/logging.h"
-#include "base/message_loop/message_loop.h"
-#include "media/cast/video_receiver/codecs/vp8/vp8_decoder.h"
+#include "base/stl_util.h"
+#include "base/values.h"
+#include "media/base/video_util.h"
+#include "media/cast/cast_defines.h"
+#include "media/cast/cast_environment.h"
+// VPX_CODEC_DISABLE_COMPAT excludes parts of the libvpx API that provide
+// backwards compatibility for legacy applications using the library.
+#define VPX_CODEC_DISABLE_COMPAT 1
+#include "third_party/libvpx/source/libvpx/vpx/vp8dx.h"
+#include "third_party/libvpx/source/libvpx/vpx/vpx_decoder.h"
+#include "ui/gfx/size.h"
 
 namespace media {
 namespace cast {
 
-VideoDecoder::VideoDecoder(const VideoReceiverConfig& video_config,
-                           scoped_refptr<CastEnvironment> cast_environment)
-    : codec_(video_config.codec), vp8_decoder_() {
+// Base class that handles the common problem of detecting dropped frames, and
+// then invoking the Decode() method implemented by the subclasses to convert
+// the encoded payload data into a usable video frame.
+class VideoDecoder::ImplBase
+    : public base::RefCountedThreadSafe<VideoDecoder::ImplBase> {
+ public:
+  ImplBase(const scoped_refptr<CastEnvironment>& cast_environment,
+           transport::VideoCodec codec)
+      : cast_environment_(cast_environment),
+        codec_(codec),
+        cast_initialization_status_(STATUS_VIDEO_UNINITIALIZED),
+        seen_first_frame_(false) {}
+
+  CastInitializationStatus InitializationResult() const {
+    return cast_initialization_status_;
+  }
+
+  void DecodeFrame(scoped_ptr<transport::EncodedVideoFrame> encoded_frame,
+                   const DecodeFrameCallback& callback) {
+    DCHECK_EQ(cast_initialization_status_, STATUS_VIDEO_INITIALIZED);
+
+    if (encoded_frame->codec != codec_) {
+      NOTREACHED();
+      cast_environment_->PostTask(
+          CastEnvironment::MAIN,
+          FROM_HERE,
+          base::Bind(callback, scoped_refptr<VideoFrame>(NULL), false));
+    }
+
+    COMPILE_ASSERT(sizeof(encoded_frame->frame_id) == sizeof(last_frame_id_),
+                   size_of_frame_id_types_do_not_match);
+    bool is_continuous = true;
+    if (seen_first_frame_) {
+      const uint32 frames_ahead = encoded_frame->frame_id - last_frame_id_;
+      if (frames_ahead > 1) {
+        RecoverBecauseFramesWereDropped();
+        is_continuous = false;
+      }
+    } else {
+      seen_first_frame_ = true;
+    }
+    last_frame_id_ = encoded_frame->frame_id;
+
+    const scoped_refptr<VideoFrame> decoded_frame = Decode(
+        reinterpret_cast<uint8*>(string_as_array(&encoded_frame->data)),
+        static_cast<int>(encoded_frame->data.size()));
+    cast_environment_->PostTask(
+        CastEnvironment::MAIN,
+        FROM_HERE,
+        base::Bind(callback, decoded_frame, is_continuous));
+  }
+
+ protected:
+  friend class base::RefCountedThreadSafe<ImplBase>;
+  virtual ~ImplBase() {}
+
+  virtual void RecoverBecauseFramesWereDropped() {}
+
+  // Note: Implementation of Decode() is allowed to mutate |data|.
+  virtual scoped_refptr<VideoFrame> Decode(uint8* data, int len) = 0;
+
+  const scoped_refptr<CastEnvironment> cast_environment_;
+  const transport::VideoCodec codec_;
+
+  // Subclass' ctor is expected to set this to STATUS_VIDEO_INITIALIZED.
+  CastInitializationStatus cast_initialization_status_;
+
+ private:
+  bool seen_first_frame_;
+  uint32 last_frame_id_;
+
+  DISALLOW_COPY_AND_ASSIGN(ImplBase);
+};
+
+class VideoDecoder::Vp8Impl : public VideoDecoder::ImplBase {
+ public:
+  explicit Vp8Impl(const scoped_refptr<CastEnvironment>& cast_environment)
+      : ImplBase(cast_environment, transport::kVp8) {
+    if (ImplBase::cast_initialization_status_ != STATUS_VIDEO_UNINITIALIZED)
+      return;
+
+    vpx_codec_dec_cfg_t cfg = {0};
+    // TODO(miu): Revisit this for typical multi-core desktop use case.  This
+    // feels like it should be 4 or 8.
+    cfg.threads = 1;
+
+    DCHECK(vpx_codec_get_caps(vpx_codec_vp8_dx()) & VPX_CODEC_CAP_POSTPROC);
+    if (vpx_codec_dec_init(&context_,
+                           vpx_codec_vp8_dx(),
+                           &cfg,
+                           VPX_CODEC_USE_POSTPROC) != VPX_CODEC_OK) {
+      ImplBase::cast_initialization_status_ =
+          STATUS_INVALID_VIDEO_CONFIGURATION;
+      return;
+    }
+    ImplBase::cast_initialization_status_ = STATUS_VIDEO_INITIALIZED;
+  }
+
+ private:
+  virtual ~Vp8Impl() {
+    if (ImplBase::cast_initialization_status_ == STATUS_VIDEO_INITIALIZED)
+      CHECK_EQ(VPX_CODEC_OK, vpx_codec_destroy(&context_));
+  }
+
+  virtual scoped_refptr<VideoFrame> Decode(uint8* data, int len) OVERRIDE {
+    if (len <= 0 || vpx_codec_decode(&context_,
+                                     data,
+                                     static_cast<unsigned int>(len),
+                                     NULL,
+                                     0) != VPX_CODEC_OK) {
+      return NULL;
+    }
+
+    vpx_codec_iter_t iter = NULL;
+    vpx_image_t* const image = vpx_codec_get_frame(&context_, &iter);
+    if (!image)
+      return NULL;
+    if (image->fmt != VPX_IMG_FMT_I420 && image->fmt != VPX_IMG_FMT_YV12) {
+      NOTREACHED();
+      return NULL;
+    }
+    DCHECK(vpx_codec_get_frame(&context_, &iter) == NULL)
+        << "Should have only decoded exactly one frame.";
+
+    const gfx::Size frame_size(image->d_w, image->d_h);
+    // Note: Timestamp for the VideoFrame will be set in VideoReceiver.
+    const scoped_refptr<VideoFrame> decoded_frame =
+        VideoFrame::CreateFrame(VideoFrame::YV12,
+                                frame_size,
+                                gfx::Rect(frame_size),
+                                frame_size,
+                                base::TimeDelta());
+    CopyYPlane(image->planes[VPX_PLANE_Y],
+               image->stride[VPX_PLANE_Y],
+               image->d_h,
+               decoded_frame);
+    CopyUPlane(image->planes[VPX_PLANE_U],
+               image->stride[VPX_PLANE_U],
+               (image->d_h + 1) / 2,
+               decoded_frame);
+    CopyVPlane(image->planes[VPX_PLANE_V],
+               image->stride[VPX_PLANE_V],
+               (image->d_h + 1) / 2,
+               decoded_frame);
+    return decoded_frame;
+  }
+
+  // VPX decoder context (i.e., an instantiation).
+  vpx_codec_ctx_t context_;
+
+  DISALLOW_COPY_AND_ASSIGN(Vp8Impl);
+};
+
+#ifndef OFFICIAL_BUILD
+// A fake video decoder that always output 2x2 black frames.
+class VideoDecoder::FakeImpl : public VideoDecoder::ImplBase {
+ public:
+  explicit FakeImpl(const scoped_refptr<CastEnvironment>& cast_environment)
+      : ImplBase(cast_environment, transport::kFakeSoftwareVideo),
+        last_decoded_id_(-1) {
+    if (ImplBase::cast_initialization_status_ != STATUS_VIDEO_UNINITIALIZED)
+      return;
+    ImplBase::cast_initialization_status_ = STATUS_VIDEO_INITIALIZED;
+  }
+
+ private:
+  virtual ~FakeImpl() {}
+
+  virtual scoped_refptr<VideoFrame> Decode(uint8* data, int len) OVERRIDE {
+    base::JSONReader reader;
+    scoped_ptr<base::Value> values(reader.Read(
+        base::StringPiece(reinterpret_cast<char*>(data), len)));
+    base::DictionaryValue* dict = NULL;
+    values->GetAsDictionary(&dict);
+
+    bool key = false;
+    int id = 0;
+    int ref = 0;
+    dict->GetBoolean("key", &key);
+    dict->GetInteger("id", &id);
+    dict->GetInteger("ref", &ref);
+    DCHECK(id == last_decoded_id_ + 1);
+    last_decoded_id_ = id;
+    return media::VideoFrame::CreateBlackFrame(gfx::Size(2, 2));
+  }
+
+  int last_decoded_id_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeImpl);
+};
+#endif
+
+VideoDecoder::VideoDecoder(
+    const scoped_refptr<CastEnvironment>& cast_environment,
+    const VideoReceiverConfig& video_config)
+    : cast_environment_(cast_environment) {
   switch (video_config.codec) {
+#ifndef OFFICIAL_BUILD
+    case transport::kFakeSoftwareVideo:
+      impl_ = new FakeImpl(cast_environment);
+      break;
+#endif
     case transport::kVp8:
-      vp8_decoder_.reset(new Vp8Decoder(cast_environment));
+      impl_ = new Vp8Impl(cast_environment);
       break;
     case transport::kH264:
+      // TODO(miu): Need implementation.
       NOTIMPLEMENTED();
       break;
+    default:
+      NOTREACHED() << "Unknown or unspecified codec.";
+      break;
   }
 }
 
 VideoDecoder::~VideoDecoder() {}
 
-bool VideoDecoder::DecodeVideoFrame(
-    const transport::EncodedVideoFrame* encoded_frame,
-    const base::TimeTicks render_time,
-    const VideoFrameDecodedCallback& frame_decoded_cb) {
-  DCHECK(encoded_frame->codec == codec_) << "Invalid codec";
-  DCHECK_GT(encoded_frame->data.size(), GG_UINT64_C(0)) << "Empty video frame";
-  return vp8_decoder_->Decode(encoded_frame, render_time, frame_decoded_cb);
+CastInitializationStatus VideoDecoder::InitializationResult() const {
+  if (impl_)
+    return impl_->InitializationResult();
+  return STATUS_UNSUPPORTED_VIDEO_CODEC;
+}
+
+void VideoDecoder::DecodeFrame(
+    scoped_ptr<transport::EncodedVideoFrame> encoded_frame,
+    const DecodeFrameCallback& callback) {
+  DCHECK(encoded_frame.get());
+  DCHECK(!callback.is_null());
+  if (!impl_ || impl_->InitializationResult() != STATUS_VIDEO_INITIALIZED) {
+    callback.Run(make_scoped_refptr<VideoFrame>(NULL), false);
+    return;
+  }
+  cast_environment_->PostTask(CastEnvironment::VIDEO,
+                              FROM_HERE,
+                              base::Bind(&VideoDecoder::ImplBase::DecodeFrame,
+                                         impl_,
+                                         base::Passed(&encoded_frame),
+                                         callback));
 }
 
 }  // namespace cast