Skip to content

Commit

Permalink
Add VideoFrameRecorder for use recording test frame sequences.
Browse files Browse the repository at this point in the history
This will be used to record representative frame sequences for performance evaluation of the new VP9 encoder.

BUG=260879

Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=279795

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@280255 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
wez@chromium.org committed Jun 27, 2014
1 parent be6c086 commit b2ba9b8
Show file tree
Hide file tree
Showing 5 changed files with 592 additions and 0 deletions.
207 changes: 207 additions & 0 deletions remoting/host/video_frame_recorder.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "remoting/host/video_frame_recorder.h"

#include "base/bind.h"
#include "base/location.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/thread_task_runner_handle.h"
#include "remoting/codec/video_encoder.h"
#include "remoting/proto/video.pb.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_region.h"

namespace remoting {

static int64_t FrameContentSize(const webrtc::DesktopFrame* frame) {
DCHECK_GT(frame->stride(), 0);
return frame->stride() * frame->size().height();
}

// VideoEncoder wrapper used to intercept frames passed to a real VideoEncoder.
class VideoFrameRecorder::RecordingVideoEncoder : public VideoEncoder {
public:
RecordingVideoEncoder(scoped_ptr<VideoEncoder> encoder,
scoped_refptr<base::TaskRunner> recorder_task_runner,
base::WeakPtr<VideoFrameRecorder> recorder)
: encoder_(encoder.Pass()),
recorder_task_runner_(recorder_task_runner),
recorder_(recorder),
enable_recording_(false),
weak_factory_(this) {
DCHECK(encoder_);
DCHECK(recorder_task_runner_);
}

base::WeakPtr<RecordingVideoEncoder> AsWeakPtr() {
return weak_factory_.GetWeakPtr();
}

void SetEnableRecording(bool enable_recording) {
DCHECK(!encoder_task_runner_ ||
encoder_task_runner_->BelongsToCurrentThread());
enable_recording_ = enable_recording;
}

// remoting::VideoEncoder interface.
virtual void SetLosslessEncode(bool want_lossless) OVERRIDE {
encoder_->SetLosslessEncode(want_lossless);
}
virtual void SetLosslessColor(bool want_lossless) OVERRIDE {
encoder_->SetLosslessColor(want_lossless);
}
virtual scoped_ptr<VideoPacket> Encode(
const webrtc::DesktopFrame& frame) OVERRIDE {
// If this is the first Encode() then store the TaskRunner and inform the
// VideoFrameRecorder so it can post SetEnableRecording() on it.
if (!encoder_task_runner_) {
encoder_task_runner_ = base::ThreadTaskRunnerHandle::Get();
recorder_task_runner_->PostTask(FROM_HERE,
base::Bind(&VideoFrameRecorder::SetEncoderTaskRunner,
recorder_,
encoder_task_runner_));
}

DCHECK(encoder_task_runner_->BelongsToCurrentThread());

if (enable_recording_) {
// Copy the frame and post it to the VideoFrameRecorder to store.
scoped_ptr<webrtc::DesktopFrame> frame_copy(
new webrtc::BasicDesktopFrame(frame.size()));
*frame_copy->mutable_updated_region() = frame.updated_region();
frame_copy->set_dpi(frame.dpi());
frame_copy->CopyPixelsFrom(frame.data(),
frame.stride(),
webrtc::DesktopRect::MakeSize(frame.size()));
recorder_task_runner_->PostTask(FROM_HERE,
base::Bind(&VideoFrameRecorder::RecordFrame,
recorder_,
base::Passed(&frame_copy)));
}

return encoder_->Encode(frame);
}

private:
scoped_ptr<VideoEncoder> encoder_;
scoped_refptr<base::TaskRunner> recorder_task_runner_;
base::WeakPtr<VideoFrameRecorder> recorder_;

bool enable_recording_;
scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner_;

base::WeakPtrFactory<RecordingVideoEncoder> weak_factory_;

DISALLOW_COPY_AND_ASSIGN(RecordingVideoEncoder);
};

VideoFrameRecorder::VideoFrameRecorder()
: content_bytes_(0),
max_content_bytes_(0),
enable_recording_(false),
weak_factory_(this) {
}

VideoFrameRecorder::~VideoFrameRecorder() {
SetEnableRecording(false);
STLDeleteElements(&recorded_frames_);
}

scoped_ptr<VideoEncoder> VideoFrameRecorder::WrapVideoEncoder(
scoped_ptr<VideoEncoder> encoder) {
DCHECK(!caller_task_runner_);
caller_task_runner_ = base::ThreadTaskRunnerHandle::Get();

scoped_ptr<RecordingVideoEncoder> recording_encoder(
new RecordingVideoEncoder(encoder.Pass(),
caller_task_runner_,
weak_factory_.GetWeakPtr()));
recording_encoder_ = recording_encoder->AsWeakPtr();

return recording_encoder.PassAs<VideoEncoder>();
}

void VideoFrameRecorder::SetEnableRecording(bool enable_recording) {
DCHECK(!caller_task_runner_ || caller_task_runner_->BelongsToCurrentThread());

if (enable_recording_ == enable_recording) {
return;
}
enable_recording_ = enable_recording;

if (encoder_task_runner_) {
encoder_task_runner_->PostTask(FROM_HERE,
base::Bind(&RecordingVideoEncoder::SetEnableRecording,
recording_encoder_,
enable_recording_));
}
}

void VideoFrameRecorder::SetMaxContentBytes(int64_t max_content_bytes) {
DCHECK(!caller_task_runner_ || caller_task_runner_->BelongsToCurrentThread());
DCHECK_GE(max_content_bytes, 0);

max_content_bytes_ = max_content_bytes;
}

scoped_ptr<webrtc::DesktopFrame> VideoFrameRecorder::NextFrame() {
DCHECK(caller_task_runner_->BelongsToCurrentThread());

scoped_ptr<webrtc::DesktopFrame> frame;
if (!recorded_frames_.empty()) {
frame.reset(recorded_frames_.front());
recorded_frames_.pop_front();
content_bytes_ -= FrameContentSize(frame.get());
DCHECK_GE(content_bytes_, 0);
}

return frame.Pass();
}

void VideoFrameRecorder::SetEncoderTaskRunner(
scoped_refptr<base::TaskRunner> task_runner) {
DCHECK(caller_task_runner_->BelongsToCurrentThread());
DCHECK(!encoder_task_runner_);
DCHECK(task_runner);

encoder_task_runner_ = task_runner;

// If the caller already enabled recording, inform the recording encoder.
if (enable_recording_ && encoder_task_runner_) {
encoder_task_runner_->PostTask(FROM_HERE,
base::Bind(&RecordingVideoEncoder::SetEnableRecording,
recording_encoder_,
enable_recording_));
}
}

void VideoFrameRecorder::RecordFrame(scoped_ptr<webrtc::DesktopFrame> frame) {
DCHECK(caller_task_runner_->BelongsToCurrentThread());

int64_t frame_bytes = FrameContentSize(frame.get());
DCHECK_GE(frame_bytes, 0);

// Purge existing frames until there is space for the new one.
while (content_bytes_ + frame_bytes > max_content_bytes_ &&
!recorded_frames_.empty()) {
scoped_ptr<webrtc::DesktopFrame> drop_frame(recorded_frames_.front());
recorded_frames_.pop_front();
content_bytes_ -= FrameContentSize(drop_frame.get());
DCHECK_GE(content_bytes_, 0);
}

// If the frame is still too big, ignore it.
if (content_bytes_ + frame_bytes > max_content_bytes_) {
return;
}

// Store the frame and update the content byte count.
recorded_frames_.push_back(frame.release());
content_bytes_ += frame_bytes;
}

} // namespace remoting
100 changes: 100 additions & 0 deletions remoting/host/video_frame_recorder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef REMOTING_HOST_VIDEO_FRAME_RECORDER_H_
#define REMOTING_HOST_VIDEO_FRAME_RECORDER_H_

#include <stdint.h>
#include <list>

#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/single_thread_task_runner.h"

namespace webrtc {
class DesktopFrame;
}

namespace remoting {

class VideoEncoder;

// Allows sequences of DesktopFrames passed to a VideoEncoder to be recorded.
//
// VideoFrameRecorder is design to support applications which use a dedicated
// thread for video encoding, but need to manage that process from a "main"
// or "control" thread.
//
// On the control thread:
// 1. Create the VideoFrameRecorder on the controlling thread.
// 2. Specify the amount of memory that may be used for recording.
// 3. Call WrapVideoEncoder(), passing the actual VideoEncoder that will be
// used to encode frames.
// 4. Hand the returned wrapper VideoEncoder of to the video encoding thread,
// to call in place of the actual VideoEncoder.
// 5. Start/stop frame recording as necessary.
// 6. Use NextFrame() to read each recorded frame in sequence.
//
// The wrapper VideoEncoder is designed to be handed off to the video encoding
// thread, and used and torn down there.
//
// The VideoFrameRecorder and VideoEncoder may be torn down in any order; frame
// recording will stop as soon as either is destroyed.

class VideoFrameRecorder {
public:
VideoFrameRecorder();
virtual ~VideoFrameRecorder();

// Wraps the supplied VideoEncoder, returning a replacement VideoEncoder that
// will route frames to the recorder, as well as passing them for encoding.
// This may be called at most once on each VideoFrameRecorder instance.
scoped_ptr<VideoEncoder> WrapVideoEncoder(scoped_ptr<VideoEncoder> encoder);

// Enables/disables frame recording. Frame recording is initially disabled.
void SetEnableRecording(bool enable_recording);

// Sets the maximum number of bytes of pixel data that may be recorded.
// When this maximum is reached older frames will be discard to make space
// for new ones.
void SetMaxContentBytes(int64_t max_content_bytes);

// Pops the next recorded frame in the sequence, and returns it.
scoped_ptr<webrtc::DesktopFrame> NextFrame();

private:
class RecordingVideoEncoder;
friend class RecordingVideoEncoder;

void SetEncoderTaskRunner(scoped_refptr<base::TaskRunner> task_runner);
void RecordFrame(scoped_ptr<webrtc::DesktopFrame> frame);

// The recorded frames, in sequence.
std::list<webrtc::DesktopFrame*> recorded_frames_;

// Size of the recorded frames' content, in bytes.
int64_t content_bytes_;

// Size that recorded frames' content must not exceed.
int64_t max_content_bytes_;

// True if recording is started, false otherwise.
bool enable_recording_;

// Task runner on which the wrapper VideoEncoder is being run.
scoped_refptr<base::TaskRunner> encoder_task_runner_;

// Weak reference to the wrapper VideoEncoder, to use to control it.
base::WeakPtr<RecordingVideoEncoder> recording_encoder_;

scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_;
base::WeakPtrFactory<VideoFrameRecorder> weak_factory_;

DISALLOW_COPY_AND_ASSIGN(VideoFrameRecorder);
};

} // namespace remoting

#endif // REMOTING_HOST_VIDEO_FRAME_RECORDER_H_
Loading

0 comments on commit b2ba9b8

Please sign in to comment.