Commit 81bf18bf authored by mikhal@google.com's avatar mikhal@google.com

Cast: Passing the frame decoded callback to the codec.

This cl cleans up the video receiver from handling the raw
frames, and will align the frame handling of the cast vp8
decoder and the chromium/ffmpeg h264 decoder (to be added).

Review URL: https://codereview.chromium.org/59753007

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@233396 0039d316-1c4b-4281-b951-d872f2087c98
parent 984619dd
...@@ -8,7 +8,10 @@ ...@@ -8,7 +8,10 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "base/bind.h"
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "media/cast/cast_environment.h"
#include "media/cast/test/fake_task_runner.h"
#include "media/cast/test/video_utility.h" #include "media/cast/test/video_utility.h"
#include "media/cast/video_receiver/codecs/vp8/vp8_decoder.h" #include "media/cast/video_receiver/codecs/vp8/vp8_decoder.h"
#include "media/cast/video_sender/codecs/vp8/vp8_encoder.h" #include "media/cast/video_sender/codecs/vp8/vp8_encoder.h"
...@@ -16,18 +19,65 @@ ...@@ -16,18 +19,65 @@
namespace media { namespace media {
namespace cast { namespace cast {
static const int64 kStartMillisecond = GG_INT64_C(1245);
static const int kWidth = 1280;
static const int kHeight = 720;
static const int kStartbitrate = 4000000;
static const int kMaxQp = 54;
static const int kMinQp = 4;
static const int kMaxFrameRate = 30;
namespace { namespace {
const int kWidth = 1280; class EncodeDecodeTestFrameCallback :
const int kHeight = 720; public base::RefCountedThreadSafe<EncodeDecodeTestFrameCallback> {
const int kStartbitrate = 4000000; public:
const int kMaxQp = 54; EncodeDecodeTestFrameCallback()
const int kMinQp = 4; : num_called_(0) {
const int kMaxFrameRate = 30; original_frame_.width = kWidth;
original_frame_.height = kHeight;
}
void SetFrameStartValue(int start_value) {
PopulateVideoFrame(&original_frame_, start_value);
}
void DecodeComplete(scoped_ptr<I420VideoFrame> decoded_frame,
const base::TimeTicks& render_time) {
++num_called_;
// Compare frames.
// Compare resolution.
EXPECT_EQ(original_frame_.width, decoded_frame->width);
EXPECT_EQ(original_frame_.height, decoded_frame->height);
// Compare data.
EXPECT_GT(I420PSNR(original_frame_, *(decoded_frame.get())), 40.0);
}
int num_called() const {
return num_called_;
}
protected:
virtual ~EncodeDecodeTestFrameCallback() {}
private:
friend class base::RefCountedThreadSafe<EncodeDecodeTestFrameCallback>;
int num_called_;
I420VideoFrame original_frame_;
};
} // namespace } // namespace
class EncodeDecodeTest : public ::testing::Test { class EncodeDecodeTest : public ::testing::Test {
protected: protected:
EncodeDecodeTest() { EncodeDecodeTest()
: task_runner_(new test::FakeTaskRunner(&testing_clock_)),
// CastEnvironment will only be used by the vp8 decoder; Enable only the
// video decoder and main threads.
cast_environment_(new CastEnvironment(&testing_clock_, task_runner_,
NULL, NULL, NULL, task_runner_)),
test_callback_(new EncodeDecodeTestFrameCallback()) {
testing_clock_.Advance(
base::TimeDelta::FromMilliseconds(kStartMillisecond));
encoder_config_.max_number_of_video_buffers_used = 1; encoder_config_.max_number_of_video_buffers_used = 1;
encoder_config_.number_of_cores = 1; encoder_config_.number_of_cores = 1;
encoder_config_.width = kWidth; encoder_config_.width = kWidth;
...@@ -39,49 +89,46 @@ class EncodeDecodeTest : public ::testing::Test { ...@@ -39,49 +89,46 @@ class EncodeDecodeTest : public ::testing::Test {
int max_unacked_frames = 1; int max_unacked_frames = 1;
encoder_.reset(new Vp8Encoder(encoder_config_, max_unacked_frames)); encoder_.reset(new Vp8Encoder(encoder_config_, max_unacked_frames));
// Initialize to use one core. // Initialize to use one core.
decoder_.reset(new Vp8Decoder(1)); decoder_.reset(new Vp8Decoder(1, cast_environment_));
} }
virtual void SetUp() { virtual ~EncodeDecodeTest() {}
virtual void SetUp() OVERRIDE {
// Create test frame. // Create test frame.
int start_value = 10; // Random value to start from. int start_value = 10; // Random value to start from.
video_frame_.reset(new I420VideoFrame()); video_frame_.reset(new I420VideoFrame());
video_frame_->width = encoder_config_.width; video_frame_->width = encoder_config_.width;
video_frame_->height = encoder_config_.height; video_frame_->height = encoder_config_.height;
PopulateVideoFrame(video_frame_.get(), start_value); PopulateVideoFrame(video_frame_.get(), start_value);
test_callback_->SetFrameStartValue(start_value);
} }
virtual void TearDown() { virtual void TearDown() OVERRIDE {
delete [] video_frame_->y_plane.data; delete [] video_frame_->y_plane.data;
delete [] video_frame_->u_plane.data; delete [] video_frame_->u_plane.data;
delete [] video_frame_->v_plane.data; delete [] video_frame_->v_plane.data;
} }
void Compare(const I420VideoFrame& original_image,
const I420VideoFrame& decoded_image) {
// Compare resolution.
EXPECT_EQ(original_image.width, decoded_image.width);
EXPECT_EQ(original_image.height, decoded_image.height);
// Compare data.
EXPECT_GT(I420PSNR(original_image, decoded_image), 40.0);
}
VideoSenderConfig encoder_config_; VideoSenderConfig encoder_config_;
scoped_ptr<Vp8Encoder> encoder_; scoped_ptr<Vp8Encoder> encoder_;
scoped_ptr<Vp8Decoder> decoder_; scoped_ptr<Vp8Decoder> decoder_;
scoped_ptr<I420VideoFrame> video_frame_; scoped_ptr<I420VideoFrame> video_frame_;
base::SimpleTestTickClock testing_clock_;
scoped_refptr<test::FakeTaskRunner> task_runner_;
scoped_refptr<CastEnvironment> cast_environment_;
scoped_refptr<EncodeDecodeTestFrameCallback> test_callback_;
}; };
TEST_F(EncodeDecodeTest, BasicEncodeDecode) { TEST_F(EncodeDecodeTest, BasicEncodeDecode) {
EncodedVideoFrame encoded_frame; EncodedVideoFrame encoded_frame;
I420VideoFrame decoded_frame;
// Encode frame. // Encode frame.
encoder_->Encode(*(video_frame_.get()), &encoded_frame); encoder_->Encode(*(video_frame_.get()), &encoded_frame);
EXPECT_GT(encoded_frame.data.size(), GG_UINT64_C(0)); EXPECT_GT(encoded_frame.data.size(), GG_UINT64_C(0));
// Decode frame. // Decode frame.
decoder_->Decode(encoded_frame, &decoded_frame); decoder_->Decode(&encoded_frame, base::TimeTicks(), base::Bind(
// Validate data. &EncodeDecodeTestFrameCallback::DecodeComplete, test_callback_));
Compare(*(video_frame_.get()), decoded_frame); task_runner_->RunTasks();
} }
} // namespace cast } // namespace cast
......
...@@ -4,14 +4,18 @@ ...@@ -4,14 +4,18 @@
#include "media/cast/video_receiver/codecs/vp8/vp8_decoder.h" #include "media/cast/video_receiver/codecs/vp8/vp8_decoder.h"
#include "base/bind.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "third_party/libvpx/source/libvpx/vpx/vp8dx.h" #include "third_party/libvpx/source/libvpx/vpx/vp8dx.h"
namespace media { namespace media {
namespace cast { namespace cast {
Vp8Decoder::Vp8Decoder(int number_of_cores) { Vp8Decoder::Vp8Decoder(int number_of_cores,
decoder_.reset(new vpx_dec_ctx_t()); scoped_refptr<CastEnvironment> cast_environment)
: decoder_(new vpx_dec_ctx_t()),
cast_environment_(cast_environment) {
InitDecode(number_of_cores); InitDecode(number_of_cores);
} }
...@@ -27,18 +31,20 @@ void Vp8Decoder::InitDecode(int number_of_cores) { ...@@ -27,18 +31,20 @@ void Vp8Decoder::InitDecode(int number_of_cores) {
} }
} }
bool Vp8Decoder::Decode(const EncodedVideoFrame& input_image, bool Vp8Decoder::Decode(const EncodedVideoFrame* encoded_frame,
I420VideoFrame* decoded_frame) { const base::TimeTicks render_time,
VLOG(1) << "VP8 decode frame:" << static_cast<int>(input_image.frame_id) const VideoFrameDecodedCallback& frame_decoded_cb) {
<< " sized:" << input_image.data.size(); const int frame_id_int = static_cast<int>(encoded_frame->frame_id);
VLOG(1) << "VP8 decode frame:" << frame_id_int
<< " sized:" << encoded_frame->data.size();
if (input_image.data.empty()) return false; if (encoded_frame->data.empty()) return false;
vpx_codec_iter_t iter = NULL; vpx_codec_iter_t iter = NULL;
vpx_image_t* img; vpx_image_t* img;
if (vpx_codec_decode(decoder_.get(), if (vpx_codec_decode(decoder_.get(),
input_image.data.data(), encoded_frame->data.data(),
static_cast<unsigned int>(input_image.data.size()), static_cast<unsigned int>(encoded_frame->data.size()),
0, 0,
1 /* real time*/)) { 1 /* real time*/)) {
VLOG(1) << "Failed to decode VP8 frame."; VLOG(1) << "Failed to decode VP8 frame.";
...@@ -47,11 +53,12 @@ bool Vp8Decoder::Decode(const EncodedVideoFrame& input_image, ...@@ -47,11 +53,12 @@ bool Vp8Decoder::Decode(const EncodedVideoFrame& input_image,
img = vpx_codec_get_frame(decoder_.get(), &iter); img = vpx_codec_get_frame(decoder_.get(), &iter);
if (img == NULL) { if (img == NULL) {
VLOG(1) << "Skip rendering VP8 frame:" VLOG(1) << "Skip rendering VP8 frame:" << frame_id_int;
<< static_cast<int>(input_image.frame_id);
return false; return false;
} }
scoped_ptr<I420VideoFrame> decoded_frame(new I420VideoFrame());
// The img is only valid until the next call to vpx_codec_decode. // The img is only valid until the next call to vpx_codec_decode.
// Populate the decoded image. // Populate the decoded image.
decoded_frame->width = img->d_w; decoded_frame->width = img->d_w;
...@@ -76,6 +83,12 @@ bool Vp8Decoder::Decode(const EncodedVideoFrame& input_image, ...@@ -76,6 +83,12 @@ bool Vp8Decoder::Decode(const EncodedVideoFrame& input_image,
memcpy(decoded_frame->v_plane.data, img->planes[VPX_PLANE_V], memcpy(decoded_frame->v_plane.data, img->planes[VPX_PLANE_V],
decoded_frame->v_plane.length); decoded_frame->v_plane.length);
// Return frame.
VLOG(1) << "Decoded frame " << frame_id_int;
// Frame decoded - return frame to the user via callback.
cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE,
base::Bind(frame_decoded_cb, base::Passed(&decoded_frame), render_time));
return true; return true;
} }
......
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "base/threading/non_thread_safe.h" #include "base/threading/non_thread_safe.h"
#include "media/cast/cast_config.h" #include "media/cast/cast_config.h"
#include "media/cast/cast_environment.h"
#include "media/cast/cast_receiver.h"
#include "third_party/libvpx/source/libvpx/vpx/vpx_decoder.h" #include "third_party/libvpx/source/libvpx/vpx/vpx_decoder.h"
typedef struct vpx_codec_ctx vpx_dec_ctx_t; typedef struct vpx_codec_ctx vpx_dec_ctx_t;
...@@ -19,18 +21,24 @@ namespace cast { ...@@ -19,18 +21,24 @@ namespace cast {
// thread. // thread.
class Vp8Decoder : public base::NonThreadSafe { class Vp8Decoder : public base::NonThreadSafe {
public: public:
explicit Vp8Decoder(int number_of_cores); Vp8Decoder(int number_of_cores,
scoped_refptr<CastEnvironment> cast_environment);
~Vp8Decoder(); ~Vp8Decoder();
// Decode encoded image (as a part of a video stream). // Decode frame - The decoded frame will be passed via the callback.
bool Decode(const EncodedVideoFrame& input_image, // Will return false in case of error, and then it's up to the caller to
I420VideoFrame* decoded_frame); // release the memory.
// Ownership of the encoded_frame does not pass to the Vp8Decoder.
bool Decode(const EncodedVideoFrame* encoded_frame,
const base::TimeTicks render_time,
const VideoFrameDecodedCallback& frame_decoded_cb);
private: private:
// Initialize the decoder. // Initialize the decoder.
void InitDecode(int number_of_cores); void InitDecode(int number_of_cores);
scoped_ptr<vpx_dec_ctx_t> decoder_; scoped_ptr<vpx_dec_ctx_t> decoder_;
scoped_refptr<CastEnvironment> cast_environment_;
}; };
} // namespace cast } // namespace cast
......
...@@ -12,13 +12,14 @@ ...@@ -12,13 +12,14 @@
namespace media { namespace media {
namespace cast { namespace cast {
VideoDecoder::VideoDecoder(const VideoReceiverConfig& video_config) VideoDecoder::VideoDecoder(const VideoReceiverConfig& video_config,
scoped_refptr<CastEnvironment> cast_environment)
: codec_(video_config.codec), : codec_(video_config.codec),
vp8_decoder_() { vp8_decoder_() {
switch (video_config.codec) { switch (video_config.codec) {
case kVp8: case kVp8:
// Initializing to use one core. // Initializing to use one core.
vp8_decoder_.reset(new Vp8Decoder(1)); vp8_decoder_.reset(new Vp8Decoder(1, cast_environment));
break; break;
case kH264: case kH264:
NOTIMPLEMENTED(); NOTIMPLEMENTED();
...@@ -31,13 +32,13 @@ VideoDecoder::VideoDecoder(const VideoReceiverConfig& video_config) ...@@ -31,13 +32,13 @@ VideoDecoder::VideoDecoder(const VideoReceiverConfig& video_config)
VideoDecoder::~VideoDecoder() {} VideoDecoder::~VideoDecoder() {}
bool VideoDecoder::DecodeVideoFrame( bool VideoDecoder::DecodeVideoFrame(const EncodedVideoFrame* encoded_frame,
const EncodedVideoFrame* encoded_frame, const base::TimeTicks render_time,
const base::TimeTicks render_time, const VideoFrameDecodedCallback&
I420VideoFrame* video_frame) { frame_decoded_cb) {
DCHECK(encoded_frame->codec == codec_) << "Invalid codec"; DCHECK(encoded_frame->codec == codec_) << "Invalid codec";
DCHECK_GT(encoded_frame->data.size(), GG_UINT64_C(0)) << "Empty video frame"; DCHECK_GT(encoded_frame->data.size(), GG_UINT64_C(0)) << "Empty video frame";
return vp8_decoder_->Decode(*encoded_frame, video_frame); return vp8_decoder_->Decode(encoded_frame, render_time, frame_decoded_cb);
} }
} // namespace cast } // namespace cast
......
...@@ -19,14 +19,15 @@ class Vp8Decoder; ...@@ -19,14 +19,15 @@ class Vp8Decoder;
// thread. // thread.
class VideoDecoder : public base::NonThreadSafe { class VideoDecoder : public base::NonThreadSafe {
public: public:
explicit VideoDecoder(const VideoReceiverConfig& video_config); VideoDecoder(const VideoReceiverConfig& video_config,
scoped_refptr<CastEnvironment> cast_environment);
virtual ~VideoDecoder(); virtual ~VideoDecoder();
// Decode a video frame. Decoded (raw) frame will be returned in the // Decode a video frame. Decoded (raw) frame will be returned via the
// provided video_frame. // provided callback
bool DecodeVideoFrame(const EncodedVideoFrame* encoded_frame, bool DecodeVideoFrame(const EncodedVideoFrame* encoded_frame,
const base::TimeTicks render_time, const base::TimeTicks render_time,
I420VideoFrame* video_frame); const VideoFrameDecodedCallback& frame_decoded_cb);
private: private:
VideoCodec codec_; VideoCodec codec_;
......
...@@ -2,8 +2,15 @@ ...@@ -2,8 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "base/bind.h"
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/time/tick_clock.h"
#include "media/cast/cast_config.h"
#include "media/cast/cast_defines.h" #include "media/cast/cast_defines.h"
#include "media/cast/cast_environment.h"
#include "media/cast/cast_receiver.h"
#include "media/cast/test/fake_task_runner.h"
#include "media/cast/video_receiver/video_decoder.h" #include "media/cast/video_receiver/video_decoder.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
...@@ -14,44 +21,70 @@ using testing::_; ...@@ -14,44 +21,70 @@ using testing::_;
// Random frame size for testing. // Random frame size for testing.
const int kFrameSize = 2345; const int kFrameSize = 2345;
static const int64 kStartMillisecond = GG_INT64_C(1245);
namespace {
class DecodeTestFrameCallback :
public base::RefCountedThreadSafe<DecodeTestFrameCallback> {
public:
DecodeTestFrameCallback() {}
void DecodeComplete(scoped_ptr<I420VideoFrame> decoded_frame,
const base::TimeTicks& render_time) {}
protected:
virtual ~DecodeTestFrameCallback() {}
private:
friend class base::RefCountedThreadSafe<DecodeTestFrameCallback>;
};
} // namespace
class VideoDecoderTest : public ::testing::Test { class VideoDecoderTest : public ::testing::Test {
protected: protected:
VideoDecoderTest() { VideoDecoderTest()
: task_runner_(new test::FakeTaskRunner(&testing_clock_)),
cast_environment_(new CastEnvironment(&testing_clock_, task_runner_,
task_runner_, task_runner_, task_runner_, task_runner_)),
test_callback_(new DecodeTestFrameCallback()) {
// Configure to vp8. // Configure to vp8.
config_.codec = kVp8; config_.codec = kVp8;
config_.use_external_decoder = false; config_.use_external_decoder = false;
decoder_.reset(new VideoDecoder(config_)); decoder_.reset(new VideoDecoder(config_, cast_environment_));
testing_clock_.Advance(
base::TimeDelta::FromMilliseconds(kStartMillisecond));
} }
virtual ~VideoDecoderTest() {} virtual ~VideoDecoderTest() {}
scoped_ptr<VideoDecoder> decoder_; scoped_ptr<VideoDecoder> decoder_;
VideoReceiverConfig config_; VideoReceiverConfig config_;
base::SimpleTestTickClock testing_clock_;
scoped_refptr<test::FakeTaskRunner> task_runner_;
scoped_refptr<CastEnvironment> cast_environment_;
scoped_refptr<DecodeTestFrameCallback> test_callback_;
}; };
// TODO(pwestin): EXPECT_DEATH tests can not pass valgrind. // TODO(pwestin): EXPECT_DEATH tests can not pass valgrind.
TEST_F(VideoDecoderTest, DISABLED_SizeZero) { TEST_F(VideoDecoderTest, DISABLED_SizeZero) {
EncodedVideoFrame encoded_frame; EncodedVideoFrame encoded_frame;
I420VideoFrame video_frame;
base::TimeTicks render_time; base::TimeTicks render_time;
encoded_frame.codec = kVp8; encoded_frame.codec = kVp8;
EXPECT_DEATH( EXPECT_DEATH(
decoder_->DecodeVideoFrame(&encoded_frame, render_time, &video_frame), decoder_->DecodeVideoFrame(
"Empty video frame"); &encoded_frame, render_time,
base::Bind(&DecodeTestFrameCallback::DecodeComplete, test_callback_)),
"Empty frame");
} }
// TODO(pwestin): EXPECT_DEATH tests can not pass valgrind. // TODO(pwestin): EXPECT_DEATH tests can not pass valgrind.
TEST_F(VideoDecoderTest, DISABLED_InvalidCodec) { TEST_F(VideoDecoderTest, DISABLED_InvalidCodec) {
EncodedVideoFrame encoded_frame; EncodedVideoFrame encoded_frame;
I420VideoFrame video_frame;
base::TimeTicks render_time; base::TimeTicks render_time;
encoded_frame.data.assign(kFrameSize, 0); encoded_frame.data.assign(kFrameSize, 0);
encoded_frame.codec = kExternalVideo; encoded_frame.codec = kExternalVideo;
EXPECT_DEATH( EXPECT_DEATH(
decoder_->DecodeVideoFrame(&encoded_frame, render_time, &video_frame), decoder_->DecodeVideoFrame(&encoded_frame, render_time, base::Bind(
"Invalid codec"); &DecodeTestFrameCallback::DecodeComplete, test_callback_)),
"Invalid codec");
} }
// TODO(pwestin): Test decoding a real frame. // TODO(pwestin): Test decoding a real frame.
......
...@@ -131,7 +131,7 @@ VideoReceiver::VideoReceiver(scoped_refptr<CastEnvironment> cast_environment, ...@@ -131,7 +131,7 @@ VideoReceiver::VideoReceiver(scoped_refptr<CastEnvironment> cast_environment,
video_config.decoder_faster_than_max_frame_rate, video_config.decoder_faster_than_max_frame_rate,
max_unacked_frames)); max_unacked_frames));
if (!video_config.use_external_decoder) { if (!video_config.use_external_decoder) {
video_decoder_.reset(new VideoDecoder(video_config)); video_decoder_.reset(new VideoDecoder(video_config, cast_environment));
} }
rtcp_.reset( rtcp_.reset(
...@@ -180,23 +180,12 @@ void VideoReceiver::DecodeVideoFrameThread( ...@@ -180,23 +180,12 @@ void VideoReceiver::DecodeVideoFrameThread(
DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::VIDEO_DECODER)); DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::VIDEO_DECODER));
DCHECK(video_decoder_); DCHECK(video_decoder_);
// TODO(mikhal): Allow the application to allocate this memory. if (!(video_decoder_->DecodeVideoFrame(encoded_frame.get(), render_time,
scoped_ptr<I420VideoFrame> video_frame(new I420VideoFrame()); frame_decoded_callback))) {
bool success = video_decoder_->DecodeVideoFrame(encoded_frame.get(),
render_time, video_frame.get());
if (success) {
VLOG(1) << "Decoded frame " << static_cast<int>(encoded_frame->frame_id);
// Frame decoded - return frame to the user via callback.
cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE,
base::Bind(frame_decoded_callback,
base::Passed(&video_frame), render_time));
} else {
// This will happen if we decide to decode but not show a frame. // This will happen if we decide to decode but not show a frame.
cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE, cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE,
base::Bind(&VideoReceiver::GetRawVideoFrame, base::Bind(&VideoReceiver::GetRawVideoFrame,
weak_factory_.GetWeakPtr(), frame_decoded_callback)); weak_factory_.GetWeakPtr(), frame_decoded_callback));
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment