Commit ea125168 authored by wez's avatar wez Committed by Commit bot

Readability review.

These CLs implement a simple frame recording and fetch "extension" for the Chromoting protocol, to allow sample sessions to be captured for performance testing.

The three main classes introduced are:
- HostExtensionSessionManager
  This pulls logic for handling Chromoting extensions out of
  ClientSession instances into a dedicated manager class.
  It also introduces hooks through which extensions can
  wrap or replace the Chromoting video encoder or capturer.
- VideoFrameRecorder
  This allows a VideoEncoder to be wrapped to return a new
  encoder that will optionally copy & deliver frames to the
  VideoFrameRecorder before supplying them to the real
  encoder.
- VideoFrameRecorderHostExtension
  This extension uses a VideoFrameRecorder to allow a
  connected client to start/stop recording, and to retrieve
  the resulting frame data.

Original CLs:
crrev.com/402233003
crrev.com/339073002
crrev.com/372943002
crrev.com/462503002

BUG=260879

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

Cr-Commit-Position: refs/heads/master@{#292541}
parent 8d684e3f
...@@ -234,28 +234,26 @@ void CastExtensionSession::OnCreateSessionDescriptionFailure( ...@@ -234,28 +234,26 @@ void CastExtensionSession::OnCreateSessionDescriptionFailure(
// stream from the peer connection here, and then attempt to re-setup the // stream from the peer connection here, and then attempt to re-setup the
// peer connection in the OnRenegotiationNeeded() callback. // peer connection in the OnRenegotiationNeeded() callback.
// See crbug.com/403843. // See crbug.com/403843.
scoped_ptr<webrtc::DesktopCapturer> CastExtensionSession::OnCreateVideoCapturer( void CastExtensionSession::OnCreateVideoCapturer(
scoped_ptr<webrtc::DesktopCapturer> capturer) { scoped_ptr<webrtc::DesktopCapturer>* capturer) {
if (has_grabbed_capturer_) { if (has_grabbed_capturer_) {
LOG(ERROR) << "The video pipeline was reset unexpectedly."; LOG(ERROR) << "The video pipeline was reset unexpectedly.";
has_grabbed_capturer_ = false; has_grabbed_capturer_ = false;
peer_connection_->RemoveStream(stream_.release()); peer_connection_->RemoveStream(stream_.release());
return capturer.Pass(); return;
} }
if (received_offer_) { if (received_offer_) {
has_grabbed_capturer_ = true; has_grabbed_capturer_ = true;
if (SetupVideoStream(capturer.Pass())) { if (SetupVideoStream(capturer->Pass())) {
peer_connection_->CreateAnswer(create_session_desc_observer_, NULL); peer_connection_->CreateAnswer(create_session_desc_observer_, NULL);
} else { } else {
has_grabbed_capturer_ = false; has_grabbed_capturer_ = false;
// Ignore the received offer, since we failed to setup a video stream. // Ignore the received offer, since we failed to setup a video stream.
received_offer_ = false; received_offer_ = false;
} }
return scoped_ptr<webrtc::DesktopCapturer>(); return;
} }
return capturer.Pass();
} }
bool CastExtensionSession::ModifiesVideoPipeline() const { bool CastExtensionSession::ModifiesVideoPipeline() const {
...@@ -672,4 +670,3 @@ void CastExtensionSession::OnIceCandidate( ...@@ -672,4 +670,3 @@ void CastExtensionSession::OnIceCandidate(
} }
} // namespace remoting } // namespace remoting
...@@ -61,8 +61,8 @@ class CastExtensionSession : public HostExtensionSession, ...@@ -61,8 +61,8 @@ class CastExtensionSession : public HostExtensionSession,
void OnCreateSessionDescriptionFailure(const std::string& error); void OnCreateSessionDescriptionFailure(const std::string& error);
// HostExtensionSession interface. // HostExtensionSession interface.
virtual scoped_ptr<webrtc::DesktopCapturer> OnCreateVideoCapturer( virtual void OnCreateVideoCapturer(
scoped_ptr<webrtc::DesktopCapturer> capturer) OVERRIDE; scoped_ptr<webrtc::DesktopCapturer>* capturer) OVERRIDE;
virtual bool ModifiesVideoPipeline() const OVERRIDE; virtual bool ModifiesVideoPipeline() const OVERRIDE;
virtual bool OnExtensionMessage( virtual bool OnExtensionMessage(
ClientSessionControl* client_session_control, ClientSessionControl* client_session_control,
...@@ -243,4 +243,3 @@ class CastExtensionSession : public HostExtensionSession, ...@@ -243,4 +243,3 @@ class CastExtensionSession : public HostExtensionSession,
} // namespace remoting } // namespace remoting
#endif // REMOTING_HOST_CAST_EXTENSION_SESSION_H_ #endif // REMOTING_HOST_CAST_EXTENSION_SESSION_H_
...@@ -456,11 +456,11 @@ void ClientSession::ResetVideoPipeline() { ...@@ -456,11 +456,11 @@ void ClientSession::ResetVideoPipeline() {
// Create VideoEncoder and DesktopCapturer to match the session's video // Create VideoEncoder and DesktopCapturer to match the session's video
// channel configuration. // channel configuration.
scoped_ptr<webrtc::DesktopCapturer> video_capturer = scoped_ptr<webrtc::DesktopCapturer> video_capturer =
extension_manager_->OnCreateVideoCapturer( desktop_environment_->CreateVideoCapturer();
desktop_environment_->CreateVideoCapturer()); extension_manager_->OnCreateVideoCapturer(&video_capturer);
scoped_ptr<VideoEncoder> video_encoder = scoped_ptr<VideoEncoder> video_encoder =
extension_manager_->OnCreateVideoEncoder( CreateVideoEncoder(connection_->session()->config());
CreateVideoEncoder(connection_->session()->config())); extension_manager_->OnCreateVideoEncoder(&video_encoder);
// Don't start the VideoScheduler if either capturer or encoder are missing. // Don't start the VideoScheduler if either capturer or encoder are missing.
if (!video_capturer || !video_encoder) if (!video_capturer || !video_encoder)
......
...@@ -19,10 +19,10 @@ class FakeExtension::Session : public HostExtensionSession { ...@@ -19,10 +19,10 @@ class FakeExtension::Session : public HostExtensionSession {
Session(FakeExtension* extension, const std::string& message_type); Session(FakeExtension* extension, const std::string& message_type);
virtual ~Session() {} virtual ~Session() {}
virtual scoped_ptr<webrtc::DesktopCapturer> OnCreateVideoCapturer( // HostExtensionSession interface.
scoped_ptr<webrtc::DesktopCapturer> encoder) OVERRIDE; virtual void OnCreateVideoCapturer(
virtual scoped_ptr<VideoEncoder> OnCreateVideoEncoder( scoped_ptr<webrtc::DesktopCapturer>* encoder) OVERRIDE;
scoped_ptr<VideoEncoder> encoder) OVERRIDE; virtual void OnCreateVideoEncoder(scoped_ptr<VideoEncoder>* encoder) OVERRIDE;
virtual bool ModifiesVideoPipeline() const OVERRIDE; virtual bool ModifiesVideoPipeline() const OVERRIDE;
virtual bool OnExtensionMessage( virtual bool OnExtensionMessage(
ClientSessionControl* client_session_control, ClientSessionControl* client_session_control,
...@@ -42,20 +42,17 @@ FakeExtension::Session::Session( ...@@ -42,20 +42,17 @@ FakeExtension::Session::Session(
message_type_(message_type) { message_type_(message_type) {
} }
scoped_ptr<webrtc::DesktopCapturer> void FakeExtension::Session::OnCreateVideoCapturer(
FakeExtension::Session::OnCreateVideoCapturer( scoped_ptr<webrtc::DesktopCapturer>* capturer) {
scoped_ptr<webrtc::DesktopCapturer> capturer) {
extension_->has_wrapped_video_capturer_ = true; extension_->has_wrapped_video_capturer_ = true;
if (extension_->steal_video_capturer_) { if (extension_->steal_video_capturer_) {
capturer.reset(); capturer->reset();
} }
return capturer.Pass();
} }
scoped_ptr<VideoEncoder> FakeExtension::Session::OnCreateVideoEncoder( void FakeExtension::Session::OnCreateVideoEncoder(
scoped_ptr<VideoEncoder> encoder) { scoped_ptr<VideoEncoder>* encoder) {
extension_->has_wrapped_video_encoder_ = true; extension_->has_wrapped_video_encoder_ = true;
return encoder.Pass();
} }
bool FakeExtension::Session::ModifiesVideoPipeline() const { bool FakeExtension::Session::ModifiesVideoPipeline() const {
...@@ -100,23 +97,4 @@ scoped_ptr<HostExtensionSession> FakeExtension::CreateExtensionSession( ...@@ -100,23 +97,4 @@ scoped_ptr<HostExtensionSession> FakeExtension::CreateExtensionSession(
return session.Pass(); return session.Pass();
} }
void FakeExtension::set_steal_video_capturer(bool steal_video_capturer) {
steal_video_capturer_ = steal_video_capturer;
}
bool FakeExtension::has_wrapped_video_encoder() {
DCHECK(was_instantiated());
return has_wrapped_video_encoder_;
}
bool FakeExtension::has_wrapped_video_capturer() {
DCHECK(was_instantiated());
return has_wrapped_video_capturer_;
}
bool FakeExtension::has_handled_message() {
DCHECK(was_instantiated());
return has_handled_message_;
}
} // namespace remoting } // namespace remoting
...@@ -33,26 +33,40 @@ class FakeExtension : public HostExtension { ...@@ -33,26 +33,40 @@ class FakeExtension : public HostExtension {
protocol::ClientStub* client_stub) OVERRIDE; protocol::ClientStub* client_stub) OVERRIDE;
// Controls for testing. // Controls for testing.
void set_steal_video_capturer(bool steal_video_capturer); void set_steal_video_capturer(bool steal_video_capturer) {
steal_video_capturer_ = steal_video_capturer;
}
// Accessors for testing. // Accessors for testing.
bool has_handled_message(); bool has_handled_message() const { return has_handled_message_; }
bool has_wrapped_video_encoder(); bool has_wrapped_video_encoder() const { return has_wrapped_video_encoder_; }
bool has_wrapped_video_capturer(); bool has_wrapped_video_capturer() const {
bool was_instantiated() { return was_instantiated_; } return has_wrapped_video_capturer_;
}
bool was_instantiated() const { return was_instantiated_; }
private: private:
class Session; class Session;
friend class Session; friend class Session;
// The type name of extension messages that this fake consumes.
std::string message_type_; std::string message_type_;
// The capability this fake reports, and requires clients to support, if any.
std::string capability_; std::string capability_;
// True if this extension should intercept creation of the session's video
// capturer and consume it, preventing the video pipeline being created.
bool steal_video_capturer_; bool steal_video_capturer_;
// True if a message of |message_type_| has been processed by this extension.
bool has_handled_message_; bool has_handled_message_;
// True if this extension had the opportunity to modify the video pipeline.
bool has_wrapped_video_encoder_; bool has_wrapped_video_encoder_;
bool has_wrapped_video_capturer_; bool has_wrapped_video_capturer_;
// True if CreateExtensionSession() was called on this extension.
bool was_instantiated_; bool was_instantiated_;
DISALLOW_COPY_AND_ASSIGN(FakeExtension); DISALLOW_COPY_AND_ASSIGN(FakeExtension);
......
...@@ -9,14 +9,12 @@ ...@@ -9,14 +9,12 @@
namespace remoting { namespace remoting {
scoped_ptr<webrtc::DesktopCapturer> HostExtensionSession::OnCreateVideoCapturer( void HostExtensionSession::OnCreateVideoCapturer(
scoped_ptr<webrtc::DesktopCapturer> capturer) { scoped_ptr<webrtc::DesktopCapturer>* capturer) {
return capturer.Pass();
} }
scoped_ptr<VideoEncoder> HostExtensionSession::OnCreateVideoEncoder( void HostExtensionSession::OnCreateVideoEncoder(
scoped_ptr<VideoEncoder> encoder) { scoped_ptr<VideoEncoder>* encoder) {
return encoder.Pass();
} }
bool HostExtensionSession::ModifiesVideoPipeline() const { bool HostExtensionSession::ModifiesVideoPipeline() const {
......
...@@ -27,13 +27,18 @@ class HostExtensionSession { ...@@ -27,13 +27,18 @@ class HostExtensionSession {
public: public:
virtual ~HostExtensionSession() {} virtual ~HostExtensionSession() {}
// Optional hook functions for HostExtensions which need to wrap or replace // Hook functions called when the video pipeline is being (re)constructed.
// parts of the video, audio, input, etc pipelines. // Implementations will receive these calls only if they express the need to
// These are called in response to ResetVideoPipeline(). // modify the pipeline (see below). They may replace or wrap |capturer| and/or
virtual scoped_ptr<webrtc::DesktopCapturer> OnCreateVideoCapturer( // |encoder|, e.g. to filter video frames in some way.
scoped_ptr<webrtc::DesktopCapturer> capturer); // If either |capturer| or |encoder| are reset then the video pipeline is not
virtual scoped_ptr<VideoEncoder> OnCreateVideoEncoder( // constructed.
scoped_ptr<VideoEncoder> encoder); virtual void OnCreateVideoCapturer(
scoped_ptr<webrtc::DesktopCapturer>* capturer);
virtual void OnCreateVideoEncoder(scoped_ptr<VideoEncoder>* encoder);
// Must return true if the HostExtensionSession needs the opportunity to
// modify the video pipeline.
virtual bool ModifiesVideoPipeline() const; virtual bool ModifiesVideoPipeline() const;
// Called when the host receives an |ExtensionMessage| for the |ClientSession| // Called when the host receives an |ExtensionMessage| for the |ClientSession|
......
...@@ -23,11 +23,11 @@ HostExtensionSessionManager::HostExtensionSessionManager( ...@@ -23,11 +23,11 @@ HostExtensionSessionManager::HostExtensionSessionManager(
HostExtensionSessionManager::~HostExtensionSessionManager() { HostExtensionSessionManager::~HostExtensionSessionManager() {
} }
std::string HostExtensionSessionManager::GetCapabilities() { std::string HostExtensionSessionManager::GetCapabilities() const {
std::string capabilities; std::string capabilities;
for (HostExtensionList::const_iterator extension = extensions_.begin(); for (HostExtensions::const_iterator extension = extensions_.begin();
extension != extensions_.end(); ++extension) { extension != extensions_.end(); ++extension) {
std::string capability = (*extension)->capability(); const std::string& capability = (*extension)->capability();
if (capability.empty()) { if (capability.empty()) {
continue; continue;
} }
...@@ -39,27 +39,24 @@ std::string HostExtensionSessionManager::GetCapabilities() { ...@@ -39,27 +39,24 @@ std::string HostExtensionSessionManager::GetCapabilities() {
return capabilities; return capabilities;
} }
scoped_ptr<webrtc::DesktopCapturer> void HostExtensionSessionManager::OnCreateVideoCapturer(
HostExtensionSessionManager::OnCreateVideoCapturer( scoped_ptr<webrtc::DesktopCapturer>* capturer) {
scoped_ptr<webrtc::DesktopCapturer> capturer) { for (HostExtensionSessions::const_iterator it = extension_sessions_.begin();
for(HostExtensionSessionList::const_iterator it = extension_sessions_.begin();
it != extension_sessions_.end(); ++it) { it != extension_sessions_.end(); ++it) {
if ((*it)->ModifiesVideoPipeline()) { if ((*it)->ModifiesVideoPipeline()) {
capturer = (*it)->OnCreateVideoCapturer(capturer.Pass()); (*it)->OnCreateVideoCapturer(capturer);
} }
} }
return capturer.Pass();
} }
scoped_ptr<VideoEncoder> HostExtensionSessionManager::OnCreateVideoEncoder( void HostExtensionSessionManager::OnCreateVideoEncoder(
scoped_ptr<VideoEncoder> encoder) { scoped_ptr<VideoEncoder>* encoder) {
for(HostExtensionSessionList::const_iterator it = extension_sessions_.begin(); for (HostExtensionSessions::const_iterator it = extension_sessions_.begin();
it != extension_sessions_.end(); ++it) { it != extension_sessions_.end(); ++it) {
if ((*it)->ModifiesVideoPipeline()) { if ((*it)->ModifiesVideoPipeline()) {
encoder = (*it)->OnCreateVideoEncoder(encoder.Pass()); (*it)->OnCreateVideoEncoder(encoder);
} }
} }
return encoder.Pass();
} }
void HostExtensionSessionManager::OnNegotiatedCapabilities( void HostExtensionSessionManager::OnNegotiatedCapabilities(
...@@ -72,7 +69,7 @@ void HostExtensionSessionManager::OnNegotiatedCapabilities( ...@@ -72,7 +69,7 @@ void HostExtensionSessionManager::OnNegotiatedCapabilities(
bool reset_video_pipeline = false; bool reset_video_pipeline = false;
for (HostExtensionList::const_iterator extension = extensions_.begin(); for (HostExtensions::const_iterator extension = extensions_.begin();
extension != extensions_.end(); ++extension) { extension != extensions_.end(); ++extension) {
// If the extension requires a capability that was not negotiated then do // If the extension requires a capability that was not negotiated then do
// not instantiate it. // not instantiate it.
...@@ -101,7 +98,7 @@ void HostExtensionSessionManager::OnNegotiatedCapabilities( ...@@ -101,7 +98,7 @@ void HostExtensionSessionManager::OnNegotiatedCapabilities(
bool HostExtensionSessionManager::OnExtensionMessage( bool HostExtensionSessionManager::OnExtensionMessage(
const protocol::ExtensionMessage& message) { const protocol::ExtensionMessage& message) {
for(HostExtensionSessionList::const_iterator it = extension_sessions_.begin(); for(HostExtensionSessions::const_iterator it = extension_sessions_.begin();
it != extension_sessions_.end(); ++it) { it != extension_sessions_.end(); ++it) {
if ((*it)->OnExtensionMessage( if ((*it)->OnExtensionMessage(
client_session_control_, client_stub_, message)) { client_session_control_, client_stub_, message)) {
......
...@@ -32,45 +32,47 @@ class ExtensionMessage; ...@@ -32,45 +32,47 @@ class ExtensionMessage;
// set of capabilities negotiated between client and host. // set of capabilities negotiated between client and host.
class HostExtensionSessionManager { class HostExtensionSessionManager {
public: public:
typedef std::vector<HostExtension*> HostExtensions;
// Creates an extension manager for the specified |extensions|. // Creates an extension manager for the specified |extensions|.
HostExtensionSessionManager(const std::vector<HostExtension*>& extensions, HostExtensionSessionManager(const HostExtensions& extensions,
ClientSessionControl* client_session_control); ClientSessionControl* client_session_control);
virtual ~HostExtensionSessionManager(); virtual ~HostExtensionSessionManager();
// Returns the union of all capabilities supported by registered extensions. // Returns the union of all capabilities supported by registered extensions.
std::string GetCapabilities(); std::string GetCapabilities() const;
// Calls the corresponding hook functions in each extension in turn, to give // Calls the corresponding hook functions for each extension, to allow them
// them an opportunity to wrap or replace video components. // to wrap or replace video pipeline components. Only extensions which return
scoped_ptr<webrtc::DesktopCapturer> OnCreateVideoCapturer( // true from ModifiesVideoPipeline() will be called.
scoped_ptr<webrtc::DesktopCapturer> capturer); // The order in which extensions are called is undefined.
scoped_ptr<VideoEncoder> OnCreateVideoEncoder( void OnCreateVideoCapturer(scoped_ptr<webrtc::DesktopCapturer>* capturer);
scoped_ptr<VideoEncoder> encoder); void OnCreateVideoEncoder(scoped_ptr<VideoEncoder>* encoder);
// Handles completion of authentication and capabilities negotiation, creating // Handles completion of authentication and capabilities negotiation, creating
// the set of |HostExtensionSession| to match the client's capabilities. // the set of HostExtensionSessions to match the client's capabilities.
void OnNegotiatedCapabilities(protocol::ClientStub* client_stub, void OnNegotiatedCapabilities(protocol::ClientStub* client_stub,
const std::string& capabilities); const std::string& capabilities);
// Passes |message| to each |HostExtensionSession| in turn until the message // Passes |message| to each HostExtensionSession in turn until the message
// is handled, or none remain. Returns true if the message was handled. // is handled, or none remain. Returns true if the message was handled.
// It is not valid for more than one extension to handle the same message.
bool OnExtensionMessage(const protocol::ExtensionMessage& message); bool OnExtensionMessage(const protocol::ExtensionMessage& message);
private: private:
typedef std::vector<HostExtension*> HostExtensionList; typedef ScopedVector<HostExtensionSession> HostExtensionSessions;
typedef ScopedVector<HostExtensionSession> HostExtensionSessionList;
// Passed to HostExtensionSessions to allow them to send messages, // Passed to HostExtensionSessions to allow them to send messages,
// disconnect the session, etc. // disconnect the session, etc.
ClientSessionControl* client_session_control_; ClientSessionControl* client_session_control_;
protocol::ClientStub* client_stub_; protocol::ClientStub* client_stub_;
// List of HostExtensions to attach to the session, if it reaches the // The HostExtensions to instantiate for the session, if it reaches the
// authenticated state. // authenticated state.
HostExtensionList extensions_; HostExtensions extensions_;
// List of HostExtensionSessions, used to handle extension messages. // The instantiated HostExtensionSessions, used to handle extension messages.
HostExtensionSessionList extension_sessions_; HostExtensionSessions extension_sessions_;
DISALLOW_COPY_AND_ASSIGN(HostExtensionSessionManager); DISALLOW_COPY_AND_ASSIGN(HostExtensionSessionManager);
}; };
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// 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/strings/string_util.h"
#include "remoting/codec/video_encoder.h" #include "remoting/codec/video_encoder.h"
#include "remoting/host/fake_host_extension.h" #include "remoting/host/fake_host_extension.h"
#include "remoting/host/host_extension_session_manager.h" #include "remoting/host/host_extension_session_manager.h"
...@@ -17,7 +18,7 @@ class HostExtensionSessionManagerTest : public testing::Test { ...@@ -17,7 +18,7 @@ class HostExtensionSessionManagerTest : public testing::Test {
public: public:
HostExtensionSessionManagerTest() HostExtensionSessionManagerTest()
: extension1_("ext1", "cap1"), : extension1_("ext1", "cap1"),
extension2_("ext2", ""), extension2_("ext2", std::string()),
extension3_("ext3", "cap3") { extension3_("ext3", "cap3") {
extensions_.push_back(&extension1_); extensions_.push_back(&extension1_);
extensions_.push_back(&extension2_); extensions_.push_back(&extension2_);
...@@ -30,14 +31,16 @@ class HostExtensionSessionManagerTest : public testing::Test { ...@@ -30,14 +31,16 @@ class HostExtensionSessionManagerTest : public testing::Test {
FakeExtension extension1_; FakeExtension extension1_;
FakeExtension extension2_; FakeExtension extension2_;
FakeExtension extension3_; FakeExtension extension3_;
std::vector<HostExtension*> extensions_; HostExtensionSessionManager::HostExtensions extensions_;
// Mocks of interfaces provided by ClientSession. // Mocks of interfaces provided by ClientSession.
MockClientSessionControl client_session_control_; MockClientSessionControl client_session_control_;
protocol::MockClientStub client_stub_; protocol::MockClientStub client_stub_;
DISALLOW_COPY_AND_ASSIGN(HostExtensionSessionManagerTest);
}; };
// Verifies that messages are passed to be handled by the correct extension. // Verifies that messages are handled by the correct extension.
TEST_F(HostExtensionSessionManagerTest, ExtensionMessages_MessageHandled) { TEST_F(HostExtensionSessionManagerTest, ExtensionMessages_MessageHandled) {
HostExtensionSessionManager extension_manager(extensions_, HostExtensionSessionManager extension_manager(extensions_,
&client_session_control_); &client_session_control_);
...@@ -76,7 +79,13 @@ TEST_F(HostExtensionSessionManagerTest, ExtensionCapabilities_AreReported) { ...@@ -76,7 +79,13 @@ TEST_F(HostExtensionSessionManagerTest, ExtensionCapabilities_AreReported) {
HostExtensionSessionManager extension_manager(extensions_, HostExtensionSessionManager extension_manager(extensions_,
&client_session_control_); &client_session_control_);
EXPECT_EQ(extension_manager.GetCapabilities(), "cap1 cap3"); std::vector<std::string> reported_caps;
Tokenize(extension_manager.GetCapabilities(), " ", &reported_caps);
std::sort(reported_caps.begin(), reported_caps.end());
ASSERT_EQ(2U, reported_caps.size());
EXPECT_EQ("cap1", reported_caps[0]);
EXPECT_EQ("cap3", reported_caps[1]);
} }
// Verifies that an extension is not instantiated if the client does not // Verifies that an extension is not instantiated if the client does not
...@@ -107,8 +116,8 @@ TEST_F(HostExtensionSessionManagerTest, CanWrapVideoCapturer) { ...@@ -107,8 +116,8 @@ TEST_F(HostExtensionSessionManagerTest, CanWrapVideoCapturer) {
extension3_.set_steal_video_capturer(true); extension3_.set_steal_video_capturer(true);
extension_manager.OnNegotiatedCapabilities(&client_stub_, "cap1"); extension_manager.OnNegotiatedCapabilities(&client_stub_, "cap1");
extension_manager.OnCreateVideoCapturer( scoped_ptr<webrtc::DesktopCapturer> dummy_capturer;
scoped_ptr<webrtc::DesktopCapturer>()); extension_manager.OnCreateVideoCapturer(&dummy_capturer);
EXPECT_FALSE(extension1_.has_wrapped_video_encoder()); EXPECT_FALSE(extension1_.has_wrapped_video_encoder());
EXPECT_TRUE(extension1_.has_wrapped_video_capturer()); EXPECT_TRUE(extension1_.has_wrapped_video_capturer());
...@@ -129,7 +138,8 @@ TEST_F(HostExtensionSessionManagerTest, CanWrapVideoEncoder) { ...@@ -129,7 +138,8 @@ TEST_F(HostExtensionSessionManagerTest, CanWrapVideoEncoder) {
extension3_.set_steal_video_capturer(true); extension3_.set_steal_video_capturer(true);
extension_manager.OnNegotiatedCapabilities(&client_stub_, "cap1"); extension_manager.OnNegotiatedCapabilities(&client_stub_, "cap1");
extension_manager.OnCreateVideoEncoder(scoped_ptr<VideoEncoder>()); scoped_ptr<VideoEncoder> dummy_encoder;
extension_manager.OnCreateVideoEncoder(&dummy_encoder);
EXPECT_TRUE(extension1_.has_wrapped_video_encoder()); EXPECT_TRUE(extension1_.has_wrapped_video_encoder());
EXPECT_FALSE(extension1_.has_wrapped_video_capturer()); EXPECT_FALSE(extension1_.has_wrapped_video_capturer());
...@@ -148,9 +158,10 @@ TEST_F(HostExtensionSessionManagerTest, RespectModifiesVideoPipeline) { ...@@ -148,9 +158,10 @@ TEST_F(HostExtensionSessionManagerTest, RespectModifiesVideoPipeline) {
extension2_.set_steal_video_capturer(true); extension2_.set_steal_video_capturer(true);
extension_manager.OnNegotiatedCapabilities(&client_stub_, "cap1"); extension_manager.OnNegotiatedCapabilities(&client_stub_, "cap1");
extension_manager.OnCreateVideoCapturer( scoped_ptr<webrtc::DesktopCapturer> dummy_capturer;
scoped_ptr<webrtc::DesktopCapturer>()); extension_manager.OnCreateVideoCapturer(&dummy_capturer);
extension_manager.OnCreateVideoEncoder(scoped_ptr<VideoEncoder>()); scoped_ptr<VideoEncoder> dummy_encoder;
extension_manager.OnCreateVideoEncoder(&dummy_encoder);
EXPECT_FALSE(extension1_.has_wrapped_video_encoder()); EXPECT_FALSE(extension1_.has_wrapped_video_encoder());
EXPECT_FALSE(extension1_.has_wrapped_video_capturer()); EXPECT_FALSE(extension1_.has_wrapped_video_capturer());
...@@ -159,7 +170,7 @@ TEST_F(HostExtensionSessionManagerTest, RespectModifiesVideoPipeline) { ...@@ -159,7 +170,7 @@ TEST_F(HostExtensionSessionManagerTest, RespectModifiesVideoPipeline) {
EXPECT_FALSE(extension3_.was_instantiated()); EXPECT_FALSE(extension3_.was_instantiated());
} }
// Verifies that if an extension reports that they modify the video pipeline // Verifies that if an extension reports that it modifies the video pipeline
// then ResetVideoPipeline() is called on the ClientSessionControl interface. // then ResetVideoPipeline() is called on the ClientSessionControl interface.
TEST_F(HostExtensionSessionManagerTest, CallsResetVideoPipeline) { TEST_F(HostExtensionSessionManagerTest, CallsResetVideoPipeline) {
HostExtensionSessionManager extension_manager(extensions_, HostExtensionSessionManager extension_manager(extensions_,
......
...@@ -27,64 +27,21 @@ class VideoFrameRecorder::RecordingVideoEncoder : public VideoEncoder { ...@@ -27,64 +27,21 @@ class VideoFrameRecorder::RecordingVideoEncoder : public VideoEncoder {
public: public:
RecordingVideoEncoder(scoped_ptr<VideoEncoder> encoder, RecordingVideoEncoder(scoped_ptr<VideoEncoder> encoder,
scoped_refptr<base::TaskRunner> recorder_task_runner, scoped_refptr<base::TaskRunner> recorder_task_runner,
base::WeakPtr<VideoFrameRecorder> recorder) 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_.get());
}
base::WeakPtr<RecordingVideoEncoder> AsWeakPtr() { base::WeakPtr<RecordingVideoEncoder> AsWeakPtr();
return weak_factory_.GetWeakPtr();
}
void SetEnableRecording(bool enable_recording) { void set_enable_recording(bool enable_recording) {
DCHECK(!encoder_task_runner_.get() || DCHECK(!encoder_task_runner_.get() ||
encoder_task_runner_->BelongsToCurrentThread()); encoder_task_runner_->BelongsToCurrentThread());
enable_recording_ = enable_recording; enable_recording_ = enable_recording;
} }
// remoting::VideoEncoder interface. // remoting::VideoEncoder interface.
virtual void SetLosslessEncode(bool want_lossless) OVERRIDE { virtual void SetLosslessEncode(bool want_lossless) OVERRIDE;
encoder_->SetLosslessEncode(want_lossless); virtual void SetLosslessColor(bool want_lossless) OVERRIDE;
}
virtual void SetLosslessColor(bool want_lossless) OVERRIDE {
encoder_->SetLosslessColor(want_lossless);
}
virtual scoped_ptr<VideoPacket> Encode( virtual scoped_ptr<VideoPacket> Encode(
const webrtc::DesktopFrame& frame) OVERRIDE { 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_.get()) {
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: private:
scoped_ptr<VideoEncoder> encoder_; scoped_ptr<VideoEncoder> encoder_;
...@@ -99,6 +56,66 @@ class VideoFrameRecorder::RecordingVideoEncoder : public VideoEncoder { ...@@ -99,6 +56,66 @@ class VideoFrameRecorder::RecordingVideoEncoder : public VideoEncoder {
DISALLOW_COPY_AND_ASSIGN(RecordingVideoEncoder); DISALLOW_COPY_AND_ASSIGN(RecordingVideoEncoder);
}; };
VideoFrameRecorder::RecordingVideoEncoder::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_.get());
}
base::WeakPtr<VideoFrameRecorder::RecordingVideoEncoder>
VideoFrameRecorder::RecordingVideoEncoder::AsWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void VideoFrameRecorder::RecordingVideoEncoder::SetLosslessEncode(
bool want_lossless) {
encoder_->SetLosslessEncode(want_lossless);
}
void VideoFrameRecorder::RecordingVideoEncoder::SetLosslessColor(
bool want_lossless) {
encoder_->SetLosslessColor(want_lossless);
}
scoped_ptr<VideoPacket> VideoFrameRecorder::RecordingVideoEncoder::Encode(
const webrtc::DesktopFrame& frame) {
// If this is the first Encode() then store the TaskRunner and inform the
// VideoFrameRecorder so it can post set_enable_recording() on it.
if (!encoder_task_runner_.get()) {
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);
}
VideoFrameRecorder::VideoFrameRecorder() VideoFrameRecorder::VideoFrameRecorder()
: content_bytes_(0), : content_bytes_(0),
max_content_bytes_(0), max_content_bytes_(0),
...@@ -139,7 +156,7 @@ void VideoFrameRecorder::DetachVideoEncoderWrapper() { ...@@ -139,7 +156,7 @@ void VideoFrameRecorder::DetachVideoEncoderWrapper() {
// Tell the wrapper to stop recording and posting frames to us. // Tell the wrapper to stop recording and posting frames to us.
if (encoder_task_runner_.get()) { if (encoder_task_runner_.get()) {
encoder_task_runner_->PostTask(FROM_HERE, encoder_task_runner_->PostTask(FROM_HERE,
base::Bind(&RecordingVideoEncoder::SetEnableRecording, base::Bind(&RecordingVideoEncoder::set_enable_recording,
recording_encoder_, false)); recording_encoder_, false));
} }
...@@ -159,7 +176,7 @@ void VideoFrameRecorder::SetEnableRecording(bool enable_recording) { ...@@ -159,7 +176,7 @@ void VideoFrameRecorder::SetEnableRecording(bool enable_recording) {
if (encoder_task_runner_.get()) { if (encoder_task_runner_.get()) {
encoder_task_runner_->PostTask(FROM_HERE, encoder_task_runner_->PostTask(FROM_HERE,
base::Bind(&RecordingVideoEncoder::SetEnableRecording, base::Bind(&RecordingVideoEncoder::set_enable_recording,
recording_encoder_, recording_encoder_,
enable_recording_)); enable_recording_));
} }
...@@ -198,7 +215,7 @@ void VideoFrameRecorder::SetEncoderTaskRunner( ...@@ -198,7 +215,7 @@ void VideoFrameRecorder::SetEncoderTaskRunner(
// If the caller already enabled recording, inform the recording encoder. // If the caller already enabled recording, inform the recording encoder.
if (enable_recording_ && encoder_task_runner_.get()) { if (enable_recording_ && encoder_task_runner_.get()) {
encoder_task_runner_->PostTask(FROM_HERE, encoder_task_runner_->PostTask(FROM_HERE,
base::Bind(&RecordingVideoEncoder::SetEnableRecording, base::Bind(&RecordingVideoEncoder::set_enable_recording,
recording_encoder_, recording_encoder_,
enable_recording_)); enable_recording_));
} }
......
...@@ -28,11 +28,11 @@ class VideoEncoder; ...@@ -28,11 +28,11 @@ class VideoEncoder;
// or "control" thread. // or "control" thread.
// //
// On the control thread: // On the control thread:
// 1. Create the VideoFrameRecorder on the controlling thread. // 1. Create the VideoFrameRecorder.
// 2. Specify the amount of memory that may be used for recording. // 2. Specify the amount of memory that may be used for recording.
// 3. Call WrapVideoEncoder(), passing the actual VideoEncoder that will be // 3. Call WrapVideoEncoder(), passing the actual VideoEncoder that will be
// used to encode frames. // used to encode frames.
// 4. Hand the returned wrapper VideoEncoder of to the video encoding thread, // 4. Hand off the returned wrapper VideoEncoder to the video encoding thread,
// to call in place of the actual VideoEncoder. // to call in place of the actual VideoEncoder.
// 5. Start/stop frame recording as necessary. // 5. Start/stop frame recording as necessary.
// 6. Use NextFrame() to read each recorded frame in sequence. // 6. Use NextFrame() to read each recorded frame in sequence.
...@@ -50,19 +50,21 @@ class VideoFrameRecorder { ...@@ -50,19 +50,21 @@ class VideoFrameRecorder {
// Wraps the supplied VideoEncoder, returning a replacement VideoEncoder that // Wraps the supplied VideoEncoder, returning a replacement VideoEncoder that
// will route frames to the recorder, as well as passing them for encoding. // will route frames to the recorder, as well as passing them for encoding.
// The caller must delete the previous recording VideoEncoder, or call // Each VideoFrameRecorder supports at most one wrapper at a time; if a new
// DetachVideoEncoderWrapper() before calling WrapVideoEncoder() to create // wrapper is required then any existing one must be deleted, or detached via
// a new wrapper. // DetachVideoEncoderWrapper(), before WrapVideoEncoder() is called again.
scoped_ptr<VideoEncoder> WrapVideoEncoder(scoped_ptr<VideoEncoder> encoder); scoped_ptr<VideoEncoder> WrapVideoEncoder(scoped_ptr<VideoEncoder> encoder);
// Detaches the existing VideoEncoder wrapper, stopping it from recording. // Detaches the existing VideoEncoder wrapper, stopping it from recording.
// The detached wrapper remains owned by the caller and will continue to
// pass-through frames to the wrapped encoder, without recording them.
void DetachVideoEncoderWrapper(); void DetachVideoEncoderWrapper();
// Enables/disables frame recording. Frame recording is initially disabled. // Enables/disables frame recording. Frame recording is initially disabled.
void SetEnableRecording(bool enable_recording); void SetEnableRecording(bool enable_recording);
// Sets the maximum number of bytes of pixel data that may be recorded. // 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 // When this maximum is reached older frames will be discarded to make space
// for new ones. // for new ones.
void SetMaxContentBytes(int64_t max_content_bytes); void SetMaxContentBytes(int64_t max_content_bytes);
......
...@@ -21,26 +21,17 @@ namespace remoting { ...@@ -21,26 +21,17 @@ namespace remoting {
namespace { namespace {
const char kVideoRecorderCapabilities[] = "videoRecorder"; // Name of the extension message type field, and its value for this extension.
const char kVideoRecorderType[] = "video-recorder";
const char kType[] = "type"; const char kType[] = "type";
const char kData[] = "data"; const char kVideoRecorderType[] = "video-recorder";
const char kStartType[] = "start";
const char kStopType[] = "stop";
const char kNextFrameType[] = "next-frame";
const char kNextFrameReplyType[] = "next-frame-reply";
class VideoFrameRecorderHostExtensionSession : public HostExtensionSession { class VideoFrameRecorderHostExtensionSession : public HostExtensionSession {
public: public:
explicit VideoFrameRecorderHostExtensionSession(int64_t max_content_bytes); explicit VideoFrameRecorderHostExtensionSession(int64_t max_content_bytes);
virtual ~VideoFrameRecorderHostExtensionSession() {} virtual ~VideoFrameRecorderHostExtensionSession();
// remoting::HostExtensionSession interface. // remoting::HostExtensionSession interface.
virtual scoped_ptr<VideoEncoder> OnCreateVideoEncoder( virtual void OnCreateVideoEncoder(scoped_ptr<VideoEncoder>* encoder) OVERRIDE;
scoped_ptr<VideoEncoder> encoder) OVERRIDE;
virtual bool ModifiesVideoPipeline() const OVERRIDE; virtual bool ModifiesVideoPipeline() const OVERRIDE;
virtual bool OnExtensionMessage( virtual bool OnExtensionMessage(
ClientSessionControl* client_session_control, ClientSessionControl* client_session_control,
...@@ -48,23 +39,32 @@ class VideoFrameRecorderHostExtensionSession : public HostExtensionSession { ...@@ -48,23 +39,32 @@ class VideoFrameRecorderHostExtensionSession : public HostExtensionSession {
const protocol::ExtensionMessage& message) OVERRIDE; const protocol::ExtensionMessage& message) OVERRIDE;
private: private:
VideoEncoderVerbatim verbatim_encoder; // Handlers for the different frame recorder extension message types.
VideoFrameRecorder video_frame_recorder; void OnStart();
void OnStop();
void OnNextFrame(protocol::ClientStub* client_stub);
VideoEncoderVerbatim verbatim_encoder_;
VideoFrameRecorder video_frame_recorder_;
bool first_frame_; bool first_frame_;
DISALLOW_COPY_AND_ASSIGN(VideoFrameRecorderHostExtensionSession); DISALLOW_COPY_AND_ASSIGN(VideoFrameRecorderHostExtensionSession);
}; };
VideoFrameRecorderHostExtensionSession::VideoFrameRecorderHostExtensionSession( VideoFrameRecorderHostExtensionSession::VideoFrameRecorderHostExtensionSession(
int64_t max_content_bytes) : first_frame_(false) { int64_t max_content_bytes)
video_frame_recorder.SetMaxContentBytes(max_content_bytes); : first_frame_(false) {
video_frame_recorder_.SetMaxContentBytes(max_content_bytes);
}
VideoFrameRecorderHostExtensionSession::
~VideoFrameRecorderHostExtensionSession() {
} }
scoped_ptr<VideoEncoder> void VideoFrameRecorderHostExtensionSession::OnCreateVideoEncoder(
VideoFrameRecorderHostExtensionSession::OnCreateVideoEncoder( scoped_ptr<VideoEncoder>* encoder) {
scoped_ptr<VideoEncoder> encoder) { video_frame_recorder_.DetachVideoEncoderWrapper();
video_frame_recorder.DetachVideoEncoderWrapper(); *encoder = video_frame_recorder_.WrapVideoEncoder(encoder->Pass());
return video_frame_recorder.WrapVideoEncoder(encoder.Pass());
} }
bool VideoFrameRecorderHostExtensionSession::ModifiesVideoPipeline() const { bool VideoFrameRecorderHostExtensionSession::ModifiesVideoPipeline() const {
...@@ -85,80 +85,106 @@ bool VideoFrameRecorderHostExtensionSession::OnExtensionMessage( ...@@ -85,80 +85,106 @@ bool VideoFrameRecorderHostExtensionSession::OnExtensionMessage(
scoped_ptr<base::Value> value(base::JSONReader::Read(message.data())); scoped_ptr<base::Value> value(base::JSONReader::Read(message.data()));
base::DictionaryValue* client_message; base::DictionaryValue* client_message;
if (value && value->GetAsDictionary(&client_message)) { if (!value || !value->GetAsDictionary(&client_message)) {
std::string type; return true;
if (!client_message->GetString(kType, &type)) { }
LOG(ERROR) << "Invalid video-recorder message";
return true;
}
if (type == kStartType) { std::string type;
video_frame_recorder.SetEnableRecording(true); if (!client_message->GetString(kType, &type)) {
first_frame_ = true; LOG(ERROR) << "Invalid video-recorder message";
} else if (type == kStopType) { return true;
video_frame_recorder.SetEnableRecording(false); }
} else if (type == kNextFrameType) {
scoped_ptr<webrtc::DesktopFrame> frame(video_frame_recorder.NextFrame()); const char kStartType[] = "start";
const char kStopType[] = "stop";
// TODO(wez): This involves six copies of the entire frame. const char kNextFrameType[] = "next-frame";
// See if there's some way to optimize at least a few of them out.
base::DictionaryValue reply_message; if (type == kStartType) {
reply_message.SetString(kType, kNextFrameReplyType); OnStart();
if (frame) { } else if (type == kStopType) {
// If this is the first frame then override the updated region so that OnStop();
// the encoder will send the whole frame's contents. } else if (type == kNextFrameType) {
if (first_frame_) { OnNextFrame(client_stub);
first_frame_ = false;
frame->mutable_updated_region()->SetRect(
webrtc::DesktopRect::MakeSize(frame->size()));
}
// Encode the frame into a raw ARGB VideoPacket.
scoped_ptr<VideoPacket> encoded_frame(
verbatim_encoder.Encode(*frame));
// Serialize that packet into a string.
std::string data;
data.resize(encoded_frame->ByteSize());
encoded_frame->SerializeWithCachedSizesToArray(
reinterpret_cast<uint8_t*>(&data[0]));
// Convert that string to Base64, so it's JSON-friendly.
std::string base64_data;
base::Base64Encode(data, &base64_data);
// Copy the Base64 data into the message.
reply_message.SetString(kData, base64_data);
}
// JSON-encode the reply into a string.
std::string reply_json;
if (!base::JSONWriter::Write(&reply_message, &reply_json)) {
LOG(ERROR) << "Failed to create reply json";
return true;
}
// Return the frame (or a 'data'-less reply) to the client.
protocol::ExtensionMessage message;
message.set_type(kVideoRecorderType);
message.set_data(reply_json);
client_stub->DeliverHostMessage(message);
}
} }
return true; return true;
} }
void VideoFrameRecorderHostExtensionSession::OnStart() {
video_frame_recorder_.SetEnableRecording(true);
first_frame_ = true;
}
void VideoFrameRecorderHostExtensionSession::OnStop() {
video_frame_recorder_.SetEnableRecording(false);
}
void VideoFrameRecorderHostExtensionSession::OnNextFrame(
protocol::ClientStub* client_stub) {
scoped_ptr<webrtc::DesktopFrame> frame(video_frame_recorder_.NextFrame());
// TODO(wez): This involves six copies of the entire frame.
// See if there's some way to optimize at least a few of them out.
const char kNextFrameReplyType[] = "next-frame-reply";
base::DictionaryValue reply_message;
reply_message.SetString(kType, kNextFrameReplyType);
if (frame) {
// If this is the first frame then override the updated region so that
// the encoder will send the whole frame's contents.
if (first_frame_) {
first_frame_ = false;
frame->mutable_updated_region()->SetRect(
webrtc::DesktopRect::MakeSize(frame->size()));
}
// Encode the frame into a raw ARGB VideoPacket.
scoped_ptr<VideoPacket> encoded_frame(
verbatim_encoder_.Encode(*frame));
// Serialize that packet into a string.
std::string data(encoded_frame->ByteSize(), 0);
encoded_frame->SerializeWithCachedSizesToArray(
reinterpret_cast<uint8_t*>(&data[0]));
// Convert that string to Base64, so it's JSON-friendly.
std::string base64_data;
base::Base64Encode(data, &base64_data);
// Copy the Base64 data into the message.
const char kData[] = "data";
reply_message.SetString(kData, base64_data);
}
// JSON-encode the reply into a string.
// Note that JSONWriter::Write() can only fail due to invalid inputs, and will
// DCHECK in Debug builds in that case.
std::string reply_json;
if (!base::JSONWriter::Write(&reply_message, &reply_json)) {
return;
}
// Return the frame (or a 'data'-less reply) to the client.
protocol::ExtensionMessage message;
message.set_type(kVideoRecorderType);
message.set_data(reply_json);
client_stub->DeliverHostMessage(message);
}
} // namespace } // namespace
VideoFrameRecorderHostExtension::VideoFrameRecorderHostExtension() {}
VideoFrameRecorderHostExtension::~VideoFrameRecorderHostExtension() {}
void VideoFrameRecorderHostExtension::SetMaxContentBytes( void VideoFrameRecorderHostExtension::SetMaxContentBytes(
int64_t max_content_bytes) { int64_t max_content_bytes) {
max_content_bytes_ = max_content_bytes; max_content_bytes_ = max_content_bytes;
} }
std::string VideoFrameRecorderHostExtension::capability() const { std::string VideoFrameRecorderHostExtension::capability() const {
return kVideoRecorderCapabilities; const char kVideoRecorderCapability[] = "videoRecorder";
return kVideoRecorderCapability;
} }
scoped_ptr<HostExtensionSession> scoped_ptr<HostExtensionSession>
......
...@@ -15,8 +15,8 @@ namespace remoting { ...@@ -15,8 +15,8 @@ namespace remoting {
// sequences of frames to run tests against. // sequences of frames to run tests against.
class VideoFrameRecorderHostExtension : public HostExtension { class VideoFrameRecorderHostExtension : public HostExtension {
public: public:
VideoFrameRecorderHostExtension() {} VideoFrameRecorderHostExtension();
virtual ~VideoFrameRecorderHostExtension() {} virtual ~VideoFrameRecorderHostExtension();
// Sets the maximum number of bytes that each session may record. // Sets the maximum number of bytes that each session may record.
void SetMaxContentBytes(int64_t max_content_bytes); void SetMaxContentBytes(int64_t max_content_bytes);
......
...@@ -18,29 +18,34 @@ namespace webrtc { ...@@ -18,29 +18,34 @@ namespace webrtc {
// Define equality operator for DesktopFrame to allow use of EXPECT_EQ(). // Define equality operator for DesktopFrame to allow use of EXPECT_EQ().
static bool operator==(const DesktopFrame& a, static bool operator==(const DesktopFrame& a,
const DesktopFrame& b) { const DesktopFrame& b) {
if ((a.size().equals(b.size())) && if ((!a.size().equals(b.size())) ||
(a.updated_region().Equals(b.updated_region())) && (!a.updated_region().Equals(b.updated_region())) ||
(a.dpi().equals(b.dpi()))) { (!a.dpi().equals(b.dpi()))) {
for (int i = 0; i < a.size().height(); ++i) { return false;
if (memcmp(a.data() + a.stride() * i, }
b.data() + b.stride() * i,
a.size().width() * DesktopFrame::kBytesPerPixel) != 0) { for (int i = 0; i < a.size().height(); ++i) {
return false; if (memcmp(a.data() + a.stride() * i,
} b.data() + b.stride() * i,
a.size().width() * DesktopFrame::kBytesPerPixel) != 0) {
return false;
} }
return true;
} }
return false;
return true;
} }
} // namespace } // namespace
namespace remoting { namespace remoting {
const int64_t kMaxContentBytes = 10 * 1024 * 1024; namespace {
const int kWidth = 640; const int kFrameWidth = 640;
const int kHeight = 480; const int kFrameHeight = 480;
const int kTestFrameCount = 6; const size_t kTestFrameCount = 6;
const int64 kTestFrameBytes =
kFrameWidth * kFrameHeight * webrtc::DesktopFrame::kBytesPerPixel;
} // namespace
class VideoFrameRecorderTest : public testing::Test { class VideoFrameRecorderTest : public testing::Test {
public: public:
...@@ -49,27 +54,51 @@ class VideoFrameRecorderTest : public testing::Test { ...@@ -49,27 +54,51 @@ class VideoFrameRecorderTest : public testing::Test {
virtual void SetUp() OVERRIDE; virtual void SetUp() OVERRIDE;
virtual void TearDown() OVERRIDE; virtual void TearDown() OVERRIDE;
// Creates a new VideoEncoder, wraps it using |recorder_|, and stores the
// newly wrapped encoder in |encoder_|.
void CreateAndWrapEncoder(); void CreateAndWrapEncoder();
// Creates the next test frame to pass to |encoder_|. Each test frame's pixel
// values are set uniquely, so that tests can verify that the correct set of
// frames were recorded.
scoped_ptr<webrtc::DesktopFrame> CreateNextFrame(); scoped_ptr<webrtc::DesktopFrame> CreateNextFrame();
// Calls CreateNextFrame() to create kTextFrameCount test frames, and stores
// them to |test_frames_|.
void CreateTestFrames(); void CreateTestFrames();
// Passes the frames in |test_frames_| to |encoder_|, in order, to encode.
void EncodeTestFrames(); void EncodeTestFrames();
// Creates a frame and passes it to |encoder_| without adding it to
// |test_frames_|.
void EncodeDummyFrame(); void EncodeDummyFrame();
// Configures |recorder_| to start recording, and pumps events to ensure that
// |encoder_| is ready to record frames.
void StartRecording(); void StartRecording();
// Reads frames from |recorder_| and compares them to the |test_frames_|.
void VerifyTestFrames(); void VerifyTestFrames();
protected: protected:
typedef std::list<webrtc::DesktopFrame*> DesktopFrames;
base::MessageLoop message_loop_; base::MessageLoop message_loop_;
scoped_ptr<VideoFrameRecorder> recorder_; scoped_ptr<VideoFrameRecorder> recorder_;
scoped_ptr<VideoEncoder> encoder_; scoped_ptr<VideoEncoder> encoder_;
std::list<webrtc::DesktopFrame*> test_frames_; DesktopFrames test_frames_;
int frame_count_; int frame_count_;
DISALLOW_COPY_AND_ASSIGN(VideoFrameRecorderTest);
}; };
VideoFrameRecorderTest::VideoFrameRecorderTest() : frame_count_(0) {} VideoFrameRecorderTest::VideoFrameRecorderTest() : frame_count_(0) {}
void VideoFrameRecorderTest::SetUp() { void VideoFrameRecorderTest::SetUp() {
const int64_t kMaxContentBytes = 10 * 1024 * 1024;
recorder_.reset(new VideoFrameRecorder()); recorder_.reset(new VideoFrameRecorder());
recorder_->SetMaxContentBytes(kMaxContentBytes); recorder_->SetMaxContentBytes(kMaxContentBytes);
} }
...@@ -97,11 +126,12 @@ void VideoFrameRecorderTest::CreateAndWrapEncoder() { ...@@ -97,11 +126,12 @@ void VideoFrameRecorderTest::CreateAndWrapEncoder() {
scoped_ptr<webrtc::DesktopFrame> VideoFrameRecorderTest::CreateNextFrame() { scoped_ptr<webrtc::DesktopFrame> VideoFrameRecorderTest::CreateNextFrame() {
scoped_ptr<webrtc::DesktopFrame> frame( scoped_ptr<webrtc::DesktopFrame> frame(
new webrtc::BasicDesktopFrame(webrtc::DesktopSize(kWidth, kHeight))); new webrtc::BasicDesktopFrame(webrtc::DesktopSize(kFrameWidth,
kFrameHeight)));
// Fill content, DPI and updated-region based on |frame_count_| so that each // Fill content, DPI and updated-region based on |frame_count_| so that each
// generated frame is different. // generated frame is different.
memset(frame->data(), frame_count_, frame->stride() * kHeight); memset(frame->data(), frame_count_, frame->stride() * kFrameHeight);
frame->set_dpi(webrtc::DesktopVector(frame_count_, frame_count_)); frame->set_dpi(webrtc::DesktopVector(frame_count_, frame_count_));
frame->mutable_updated_region()->SetRect( frame->mutable_updated_region()->SetRect(
webrtc::DesktopRect::MakeWH(frame_count_, frame_count_)); webrtc::DesktopRect::MakeWH(frame_count_, frame_count_));
...@@ -111,15 +141,15 @@ scoped_ptr<webrtc::DesktopFrame> VideoFrameRecorderTest::CreateNextFrame() { ...@@ -111,15 +141,15 @@ scoped_ptr<webrtc::DesktopFrame> VideoFrameRecorderTest::CreateNextFrame() {
} }
void VideoFrameRecorderTest::CreateTestFrames() { void VideoFrameRecorderTest::CreateTestFrames() {
for (int i=0; i < kTestFrameCount; ++i) { for (size_t i = 0; i < kTestFrameCount; ++i) {
test_frames_.push_back(CreateNextFrame().release()); test_frames_.push_back(CreateNextFrame().release());
} }
} }
void VideoFrameRecorderTest::EncodeTestFrames() { void VideoFrameRecorderTest::EncodeTestFrames() {
std::list<webrtc::DesktopFrame*>::iterator i; for (DesktopFrames::iterator i = test_frames_.begin();
for (i = test_frames_.begin(); i != test_frames_.end(); ++i) { i != test_frames_.end(); ++i) {
scoped_ptr<VideoPacket> packet = encoder_->Encode(*(*i)); ASSERT_TRUE(encoder_->Encode(**i));
// Process tasks to let the recorder pick up the frame. // Process tasks to let the recorder pick up the frame.
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
...@@ -127,8 +157,9 @@ void VideoFrameRecorderTest::EncodeTestFrames() { ...@@ -127,8 +157,9 @@ void VideoFrameRecorderTest::EncodeTestFrames() {
} }
void VideoFrameRecorderTest::EncodeDummyFrame() { void VideoFrameRecorderTest::EncodeDummyFrame() {
webrtc::BasicDesktopFrame dummy_frame(webrtc::DesktopSize(kWidth, kHeight)); webrtc::BasicDesktopFrame dummy_frame(
scoped_ptr<VideoPacket> packet = encoder_->Encode(dummy_frame); webrtc::DesktopSize(kFrameWidth, kFrameHeight));
ASSERT_TRUE(encoder_->Encode(dummy_frame));
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
} }
...@@ -158,7 +189,7 @@ TEST_F(VideoFrameRecorderTest, CreateDestroy) { ...@@ -158,7 +189,7 @@ TEST_F(VideoFrameRecorderTest, CreateDestroy) {
} }
// Basic test that creating, starting, stopping and destroying a // Basic test that creating, starting, stopping and destroying a
// VideoFrameRecorder don't end the world. // VideoFrameRecorder succeeds (e.g. does not crash or DCHECK).
TEST_F(VideoFrameRecorderTest, StartStop) { TEST_F(VideoFrameRecorderTest, StartStop) {
StartRecording(); StartRecording();
recorder_->SetEnableRecording(false); recorder_->SetEnableRecording(false);
...@@ -218,8 +249,7 @@ TEST_F(VideoFrameRecorderTest, MaxContentBytesEnforced) { ...@@ -218,8 +249,7 @@ TEST_F(VideoFrameRecorderTest, MaxContentBytesEnforced) {
CreateAndWrapEncoder(); CreateAndWrapEncoder();
// Configure a maximum content size sufficient for five and a half frames. // Configure a maximum content size sufficient for five and a half frames.
int64 frame_bytes = kWidth * kHeight * webrtc::DesktopFrame::kBytesPerPixel; recorder_->SetMaxContentBytes((kTestFrameBytes * 11) / 2);
recorder_->SetMaxContentBytes((frame_bytes * 11) / 2);
// Start the recorder, so that the wrapper will push frames to it. // Start the recorder, so that the wrapper will push frames to it.
StartRecording(); StartRecording();
...@@ -238,14 +268,13 @@ TEST_F(VideoFrameRecorderTest, MaxContentBytesEnforced) { ...@@ -238,14 +268,13 @@ TEST_F(VideoFrameRecorderTest, MaxContentBytesEnforced) {
VerifyTestFrames(); VerifyTestFrames();
} }
// Test that when asked to record more frames than the maximum content bytes // Test that when frames are consumed the corresponding space is freed up in
// limit allows, the first encoded frames are dropped. // the content buffer, allowing subsequent frames to be recorded.
TEST_F(VideoFrameRecorderTest, ContentBytesUpdatedByNextFrame) { TEST_F(VideoFrameRecorderTest, ContentBytesUpdatedByNextFrame) {
CreateAndWrapEncoder(); CreateAndWrapEncoder();
// Configure a maximum content size sufficient for kTestFrameCount frames. // Configure a maximum content size sufficient for kTestFrameCount frames.
int64 frame_bytes = kWidth * kHeight * webrtc::DesktopFrame::kBytesPerPixel; recorder_->SetMaxContentBytes(kTestFrameBytes * kTestFrameCount);
recorder_->SetMaxContentBytes(frame_bytes * kTestFrameCount);
// Start the recorder, so that the wrapper will push frames to it. // Start the recorder, so that the wrapper will push frames to it.
StartRecording(); StartRecording();
......
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