Commit 5f83e5be authored by jfroy's avatar jfroy Committed by Commit bot

Add power monitoring to the Cast VideoToolbox encoder.

Chromium provides PowerObserver and PowerMonitor objects to allow
subsystems to react to power suspend and resume events. On iOS this
actually tracks app lifecycle events for backgrounding.

The patch adds a power observer that destroys the compression session
on power suspend events and re-initializes the compression session on
power resume events. This allows the encoder to properly transition to
the background on iOS and resume when the app comes back to the
foreground.

Note that for this to work overall, the sender client must create a
background task to allow networking to continue. Otherwise the receiver
will timeout per spec.

R=miu@chromium.org
BUG=477895

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

Cr-Commit-Position: refs/heads/master@{#326938}
parent 54f851d3
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "base/location.h" #include "base/location.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/power_monitor/power_monitor.h"
#include "base/synchronization/lock.h" #include "base/synchronization/lock.h"
#include "media/base/mac/corevideo_glue.h" #include "media/base/mac/corevideo_glue.h"
#include "media/base/mac/video_frame_mac.h" #include "media/base/mac/video_frame_mac.h"
...@@ -321,6 +322,7 @@ H264VideoToolboxEncoder::H264VideoToolboxEncoder( ...@@ -321,6 +322,7 @@ H264VideoToolboxEncoder::H264VideoToolboxEncoder(
status_change_cb_(status_change_cb), status_change_cb_(status_change_cb),
last_frame_id_(kStartFrameId), last_frame_id_(kStartFrameId),
encode_next_frame_as_keyframe_(false), encode_next_frame_as_keyframe_(false),
power_suspended_(false),
weak_factory_(this) { weak_factory_(this) {
DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
DCHECK(!status_change_cb_.is_null()); DCHECK(!status_change_cb_.is_null());
...@@ -334,19 +336,44 @@ H264VideoToolboxEncoder::H264VideoToolboxEncoder( ...@@ -334,19 +336,44 @@ H264VideoToolboxEncoder::H264VideoToolboxEncoder(
base::Bind(status_change_cb_, operational_status)); base::Bind(status_change_cb_, operational_status));
if (operational_status == STATUS_INITIALIZED) { if (operational_status == STATUS_INITIALIZED) {
// Create the shared video frame factory. It persists for the combined
// lifetime of the encoder and all video frame factory proxies created by
// |CreateVideoFrameFactory| that reference it.
video_frame_factory_ = video_frame_factory_ =
scoped_refptr<VideoFrameFactoryImpl>(new VideoFrameFactoryImpl( scoped_refptr<VideoFrameFactoryImpl>(new VideoFrameFactoryImpl(
weak_factory_.GetWeakPtr(), cast_environment_)); weak_factory_.GetWeakPtr(), cast_environment_));
// Register for power state changes.
auto power_monitor = base::PowerMonitor::Get();
if (power_monitor) {
power_monitor->AddObserver(this);
VLOG(1) << "Registered for power state changes.";
} else {
DLOG(WARNING) << "No power monitor. Process suspension will invalidate "
"the encoder.";
}
} }
} }
H264VideoToolboxEncoder::~H264VideoToolboxEncoder() { H264VideoToolboxEncoder::~H264VideoToolboxEncoder() {
DestroyCompressionSession(); DestroyCompressionSession();
// If video_frame_factory_ is not null, the encoder registered for power state
// changes in the ctor and it must now unregister.
if (video_frame_factory_) {
auto power_monitor = base::PowerMonitor::Get();
if (power_monitor)
power_monitor->RemoveObserver(this);
}
} }
void H264VideoToolboxEncoder::ResetCompressionSession() { void H264VideoToolboxEncoder::ResetCompressionSession() {
DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(thread_checker_.CalledOnValidThread());
// Ignore reset requests while power suspended.
if (power_suspended_)
return;
// Notify that we're resetting the encoder. // Notify that we're resetting the encoder.
cast_environment_->PostTask( cast_environment_->PostTask(
CastEnvironment::MAIN, FROM_HERE, CastEnvironment::MAIN, FROM_HERE,
...@@ -625,6 +652,27 @@ void H264VideoToolboxEncoder::EmitFrames() { ...@@ -625,6 +652,27 @@ void H264VideoToolboxEncoder::EmitFrames() {
} }
} }
void H264VideoToolboxEncoder::OnSuspend() {
VLOG(1)
<< "OnSuspend: Emitting all frames and destroying compression session.";
EmitFrames();
DestroyCompressionSession();
power_suspended_ = true;
}
void H264VideoToolboxEncoder::OnResume() {
power_suspended_ = false;
// Reset the compression session only if the frame size is not zero (which
// will obviously fail). It is possible for the frame size to be zero if no
// frame was submitted for encoding or requested from the video frame factory
// before suspension.
if (!frame_size_.IsEmpty()) {
VLOG(1) << "OnResume: Resetting compression session.";
ResetCompressionSession();
}
}
bool H264VideoToolboxEncoder::SetSessionProperty(CFStringRef key, bool H264VideoToolboxEncoder::SetSessionProperty(CFStringRef key,
int32_t value) { int32_t value) {
base::ScopedCFTypeRef<CFNumberRef> cfvalue( base::ScopedCFTypeRef<CFNumberRef> cfvalue(
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#define MEDIA_CAST_SENDER_H264_VT_ENCODER_H_ #define MEDIA_CAST_SENDER_H264_VT_ENCODER_H_
#include "base/mac/scoped_cftyperef.h" #include "base/mac/scoped_cftyperef.h"
#include "base/power_monitor/power_observer.h"
#include "base/threading/thread_checker.h" #include "base/threading/thread_checker.h"
#include "media/base/mac/videotoolbox_glue.h" #include "media/base/mac/videotoolbox_glue.h"
#include "media/cast/sender/size_adaptable_video_encoder_base.h" #include "media/cast/sender/size_adaptable_video_encoder_base.h"
...@@ -17,8 +18,10 @@ namespace cast { ...@@ -17,8 +18,10 @@ namespace cast {
// VideoToolbox implementation of the media::cast::VideoEncoder interface. // VideoToolbox implementation of the media::cast::VideoEncoder interface.
// VideoToolbox makes no guarantees that it is thread safe, so this object is // VideoToolbox makes no guarantees that it is thread safe, so this object is
// pinned to the thread on which it is constructed. Supports changing frame // pinned to the thread on which it is constructed. Supports changing frame
// sizes directly. // sizes directly. Implements the base::PowerObserver interface to reset the
class H264VideoToolboxEncoder : public VideoEncoder { // compression session when the host process is suspended.
class H264VideoToolboxEncoder : public VideoEncoder,
public base::PowerObserver {
typedef CoreMediaGlue::CMSampleBufferRef CMSampleBufferRef; typedef CoreMediaGlue::CMSampleBufferRef CMSampleBufferRef;
typedef VideoToolboxGlue::VTCompressionSessionRef VTCompressionSessionRef; typedef VideoToolboxGlue::VTCompressionSessionRef VTCompressionSessionRef;
typedef VideoToolboxGlue::VTEncodeInfoFlags VTEncodeInfoFlags; typedef VideoToolboxGlue::VTEncodeInfoFlags VTEncodeInfoFlags;
...@@ -45,6 +48,10 @@ class H264VideoToolboxEncoder : public VideoEncoder { ...@@ -45,6 +48,10 @@ class H264VideoToolboxEncoder : public VideoEncoder {
scoped_ptr<VideoFrameFactory> CreateVideoFrameFactory() override; scoped_ptr<VideoFrameFactory> CreateVideoFrameFactory() override;
void EmitFrames() override; void EmitFrames() override;
// base::PowerObserver
void OnSuspend() override;
void OnResume() override;
private: private:
// VideoFrameFactory tied to the VideoToolbox encoder. // VideoFrameFactory tied to the VideoToolbox encoder.
class VideoFrameFactoryImpl; class VideoFrameFactoryImpl;
...@@ -111,6 +118,9 @@ class H264VideoToolboxEncoder : public VideoEncoder { ...@@ -111,6 +118,9 @@ class H264VideoToolboxEncoder : public VideoEncoder {
// Force next frame to be a keyframe. // Force next frame to be a keyframe.
bool encode_next_frame_as_keyframe_; bool encode_next_frame_as_keyframe_;
// Power suspension state.
bool power_suspended_;
// NOTE: Weak pointers must be invalidated before all other member variables. // NOTE: Weak pointers must be invalidated before all other member variables.
base::WeakPtrFactory<H264VideoToolboxEncoder> weak_factory_; base::WeakPtrFactory<H264VideoToolboxEncoder> weak_factory_;
......
...@@ -7,7 +7,9 @@ ...@@ -7,7 +7,9 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop.h"
#include "base/power_monitor/power_monitor.h"
#include "base/test/launcher/unit_test_launcher.h" #include "base/test/launcher/unit_test_launcher.h"
#include "base/test/power_monitor_test_base.h"
#include "base/test/simple_test_tick_clock.h" #include "base/test/simple_test_tick_clock.h"
#include "base/test/test_suite.h" #include "base/test/test_suite.h"
#include "media/base/decoder_buffer.h" #include "media/base/decoder_buffer.h"
...@@ -190,24 +192,43 @@ void CreateFrameAndMemsetPlane(VideoFrameFactory* const video_frame_factory) { ...@@ -190,24 +192,43 @@ void CreateFrameAndMemsetPlane(VideoFrameFactory* const video_frame_factory) {
CVPixelBufferUnlockBaseAddress(cv_pixel_buffer, 0); CVPixelBufferUnlockBaseAddress(cv_pixel_buffer, 0);
} }
void NoopFrameEncodedCallback(
scoped_ptr<media::cast::EncodedFrame> /*encoded_frame*/) {
}
class TestPowerSource : public base::PowerMonitorSource {
public:
void GenerateSuspendEvent() {
ProcessPowerEvent(SUSPEND_EVENT);
base::MessageLoop::current()->RunUntilIdle();
}
void GenerateResumeEvent() {
ProcessPowerEvent(RESUME_EVENT);
base::MessageLoop::current()->RunUntilIdle();
}
private:
bool IsOnBatteryPowerImpl() override { return false; }
};
class H264VideoToolboxEncoderTest : public ::testing::Test { class H264VideoToolboxEncoderTest : public ::testing::Test {
protected: protected:
H264VideoToolboxEncoderTest() H264VideoToolboxEncoderTest() = default;
: operational_status_(STATUS_UNINITIALIZED) {
frame_->set_timestamp(base::TimeDelta());
}
void SetUp() override { void SetUp() override {
clock_ = new base::SimpleTestTickClock(); clock_ = new base::SimpleTestTickClock();
clock_->Advance(base::TimeTicks::Now() - base::TimeTicks()); clock_->Advance(base::TimeTicks::Now() - base::TimeTicks());
power_source_ = new TestPowerSource();
power_monitor_.reset(
new base::PowerMonitor(scoped_ptr<TestPowerSource>(power_source_)));
cast_environment_ = new CastEnvironment( cast_environment_ = new CastEnvironment(
scoped_ptr<base::TickClock>(clock_).Pass(), scoped_ptr<base::TickClock>(clock_).Pass(),
message_loop_.message_loop_proxy(), message_loop_.message_loop_proxy(), message_loop_.message_loop_proxy(), message_loop_.message_loop_proxy(),
message_loop_.message_loop_proxy()); message_loop_.message_loop_proxy());
encoder_.reset(new H264VideoToolboxEncoder( encoder_.reset(new H264VideoToolboxEncoder(
cast_environment_, cast_environment_, video_sender_config_,
video_sender_config_,
base::Bind(&SaveOperationalStatus, &operational_status_))); base::Bind(&SaveOperationalStatus, &operational_status_)));
message_loop_.RunUntilIdle(); message_loop_.RunUntilIdle();
EXPECT_EQ(STATUS_INITIALIZED, operational_status_); EXPECT_EQ(STATUS_INITIALIZED, operational_status_);
...@@ -216,6 +237,7 @@ class H264VideoToolboxEncoderTest : public ::testing::Test { ...@@ -216,6 +237,7 @@ class H264VideoToolboxEncoderTest : public ::testing::Test {
void TearDown() override { void TearDown() override {
encoder_.reset(); encoder_.reset();
message_loop_.RunUntilIdle(); message_loop_.RunUntilIdle();
power_monitor_.reset();
} }
void AdvanceClockAndVideoFrameTimestamp() { void AdvanceClockAndVideoFrameTimestamp() {
...@@ -244,6 +266,8 @@ class H264VideoToolboxEncoderTest : public ::testing::Test { ...@@ -244,6 +266,8 @@ class H264VideoToolboxEncoderTest : public ::testing::Test {
scoped_refptr<CastEnvironment> cast_environment_; scoped_refptr<CastEnvironment> cast_environment_;
scoped_ptr<VideoEncoder> encoder_; scoped_ptr<VideoEncoder> encoder_;
OperationalStatus operational_status_; OperationalStatus operational_status_;
TestPowerSource* power_source_; // Owned by the power monitor.
scoped_ptr<base::PowerMonitor> power_monitor_;
private: private:
DISALLOW_COPY_AND_ASSIGN(H264VideoToolboxEncoderTest); DISALLOW_COPY_AND_ASSIGN(H264VideoToolboxEncoderTest);
...@@ -314,5 +338,76 @@ TEST_F(H264VideoToolboxEncoderTest, CheckVideoFrameFactory) { ...@@ -314,5 +338,76 @@ TEST_F(H264VideoToolboxEncoderTest, CheckVideoFrameFactory) {
CreateFrameAndMemsetPlane(video_frame_factory.get()); CreateFrameAndMemsetPlane(video_frame_factory.get());
} }
TEST_F(H264VideoToolboxEncoderTest, CheckPowerMonitoring) {
// Encode a frame, suspend, encode a frame, resume, encode a frame.
VideoEncoder::FrameEncodedCallback cb = base::Bind(&NoopFrameEncodedCallback);
EXPECT_TRUE(encoder_->EncodeVideoFrame(frame_, clock_->NowTicks(), cb));
power_source_->GenerateSuspendEvent();
EXPECT_FALSE(encoder_->EncodeVideoFrame(frame_, clock_->NowTicks(), cb));
power_source_->GenerateResumeEvent();
EXPECT_TRUE(encoder_->EncodeVideoFrame(frame_, clock_->NowTicks(), cb));
}
TEST_F(H264VideoToolboxEncoderTest, CheckPowerMonitoringNoInitialFrame) {
// Suspend, encode a frame, resume, encode a frame.
VideoEncoder::FrameEncodedCallback cb = base::Bind(&NoopFrameEncodedCallback);
power_source_->GenerateSuspendEvent();
EXPECT_FALSE(encoder_->EncodeVideoFrame(frame_, clock_->NowTicks(), cb));
power_source_->GenerateResumeEvent();
EXPECT_TRUE(encoder_->EncodeVideoFrame(frame_, clock_->NowTicks(), cb));
}
TEST_F(H264VideoToolboxEncoderTest, CheckPowerMonitoringVideoFrameFactory) {
VideoEncoder::FrameEncodedCallback cb = base::Bind(&NoopFrameEncodedCallback);
auto video_frame_factory = encoder_->CreateVideoFrameFactory();
ASSERT_TRUE(video_frame_factory.get());
// The first call to |MaybeCreateFrame| will return null but post a task to
// the encoder to initialize for the specified frame size. We then drain the
// message loop. After that, the encoder should have initialized and we
// request a frame again.
ASSERT_FALSE(video_frame_factory->MaybeCreateFrame(
gfx::Size(kVideoWidth, kVideoHeight), base::TimeDelta()));
message_loop_.RunUntilIdle();
CreateFrameAndMemsetPlane(video_frame_factory.get());
// After a power suspension, the factory should not produce frames.
power_source_->GenerateSuspendEvent();
ASSERT_FALSE(video_frame_factory->MaybeCreateFrame(
gfx::Size(kVideoWidth, kVideoHeight), base::TimeDelta()));
message_loop_.RunUntilIdle();
ASSERT_FALSE(video_frame_factory->MaybeCreateFrame(
gfx::Size(kVideoWidth, kVideoHeight), base::TimeDelta()));
// After a power resume event, the factory should produce frames right away
// because the encoder re-initializes on its own.
power_source_->GenerateResumeEvent();
CreateFrameAndMemsetPlane(video_frame_factory.get());
}
TEST_F(H264VideoToolboxEncoderTest,
CheckPowerMonitoringVideoFrameFactoryNoInitialFrame) {
VideoEncoder::FrameEncodedCallback cb = base::Bind(&NoopFrameEncodedCallback);
auto video_frame_factory = encoder_->CreateVideoFrameFactory();
ASSERT_TRUE(video_frame_factory.get());
// After a power suspension, the factory should not produce frames.
power_source_->GenerateSuspendEvent();
ASSERT_FALSE(video_frame_factory->MaybeCreateFrame(
gfx::Size(kVideoWidth, kVideoHeight), base::TimeDelta()));
message_loop_.RunUntilIdle();
ASSERT_FALSE(video_frame_factory->MaybeCreateFrame(
gfx::Size(kVideoWidth, kVideoHeight), base::TimeDelta()));
// After a power resume event, the factory should produce frames right away
// because the encoder re-initializes on its own.
power_source_->GenerateResumeEvent();
CreateFrameAndMemsetPlane(video_frame_factory.get());
}
} // namespace cast } // namespace cast
} // namespace media } // namespace media
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