summaryrefslogtreecommitdiffstats
path: root/content
diff options
context:
space:
mode:
authordalecurtis@chromium.org <dalecurtis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-17 00:25:41 +0000
committerdalecurtis@chromium.org <dalecurtis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-17 00:25:41 +0000
commit3958e97c8efde46a9d72d8cdb5d9e78ccc49a7fc (patch)
treeabafb9736e2d358625aa03d5ba52d3fb2a1d3243 /content
parent9dd95221d9bbde5b91598319bcf1f62d0fbcf888 (diff)
downloadchromium_src-3958e97c8efde46a9d72d8cdb5d9e78ccc49a7fc.zip
chromium_src-3958e97c8efde46a9d72d8cdb5d9e78ccc49a7fc.tar.gz
chromium_src-3958e97c8efde46a9d72d8cdb5d9e78ccc49a7fc.tar.bz2
Enable renderer side mixing behind a flag.
Lands the final bits required to turn on renderer side mixing behind a flag! Switching <audio> over to the PCM_LOW_LATENCY track to boot. Specifically the flag is --enable-renderer-side-mixing. The big picture: RenderThreadImpl owns an AudioRendererMixerManager which RenderAudioSourceProvider uses to create AudioRenderMixerInput, which extensions of AudioRendererSink. When ARMM creates ARMI it passes in callbacks which allow ARMI to retrieve an AudioRendererMixer from ARMM once ARMI::Initialize() has been called. When ARMM gets a request for an ARM from ARMI::Initialize() it will first check if one exists and if so, hand out a reference. If not, it will create a new ARM instance (which it owns) and initialize it with the proper hardware output AudioParameters and an AudioDevice based AudioRenderSink. ARM will immediately call Initialize() and Start() on AudioDevice which will then begin requesting data via RenderCallback from ARM. If the hardware sampling rate is not equal to the input sampling rate, ARM will instantiate a MultiChannelResampler instance to resample the audio as required for use by a PCM_LOW_LATENCY AudioDevice. After ARMI::Initialize() nothing really happens until RASP() eventually calls ARMI::Start() which will then register the ARMI instance with ARM. At which point ARM will start mixing it into the output stream it's providing to AudioDevice. Everything works as far as I can tell, but an optimization pass and more careful testing is necessary on Windows before the feature comes out from behind the flag. BUG=133637 TEST=New unit tests. Manual testing. Review URL: https://chromiumcodereview.appspot.com/10636036 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@146935 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
-rw-r--r--content/browser/renderer_host/render_process_host_impl.cc1
-rw-r--r--content/content_renderer.gypi2
-rw-r--r--content/content_tests.gypi1
-rw-r--r--content/public/common/content_switches.cc3
-rw-r--r--content/public/common/content_switches.h1
-rw-r--r--content/renderer/media/audio_renderer_mixer_manager.cc73
-rw-r--r--content/renderer/media/audio_renderer_mixer_manager.h79
-rw-r--r--content/renderer/media/audio_renderer_mixer_manager_unittest.cc131
-rw-r--r--content/renderer/media/render_audiosourceprovider.cc22
-rw-r--r--content/renderer/render_thread_impl.cc14
-rw-r--r--content/renderer/render_thread_impl.h8
11 files changed, 330 insertions, 5 deletions
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index a8d2772..54fc2b8 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -764,6 +764,7 @@ void RenderProcessHostImpl::PropagateBrowserCommandLineToRenderer(
switches::kEnablePartialSwap,
switches::kEnablePeerConnection,
switches::kEnablePerTilePainting,
+ switches::kEnableRendererSideMixing,
switches::kEnableShadowDOM,
switches::kEnableStrictSiteIsolation,
switches::kEnableStyleScoped,
diff --git a/content/content_renderer.gypi b/content/content_renderer.gypi
index 570e48c..2d4a777 100644
--- a/content/content_renderer.gypi
+++ b/content/content_renderer.gypi
@@ -108,6 +108,8 @@
'renderer/media/audio_input_message_filter.h',
'renderer/media/audio_message_filter.cc',
'renderer/media/audio_message_filter.h',
+ 'renderer/media/audio_renderer_mixer_manager.cc',
+ 'renderer/media/audio_renderer_mixer_manager.h',
'renderer/media/capture_video_decoder.cc',
'renderer/media/capture_video_decoder.h',
'renderer/media/media_stream_center.h',
diff --git a/content/content_tests.gypi b/content/content_tests.gypi
index 8cc1a88..77b8f47 100644
--- a/content/content_tests.gypi
+++ b/content/content_tests.gypi
@@ -325,6 +325,7 @@
'renderer/gpu/input_event_filter_unittest.cc',
'renderer/media/audio_device_unittest.cc',
'renderer/media/audio_message_filter_unittest.cc',
+ 'renderer/media/audio_renderer_mixer_manager_unittest.cc',
'renderer/media/capture_video_decoder_unittest.cc',
'renderer/media/video_capture_impl_unittest.cc',
'renderer/media/video_capture_message_filter_unittest.cc',
diff --git a/content/public/common/content_switches.cc b/content/public/common/content_switches.cc
index 7aa4439..6fd3cad 100644
--- a/content/public/common/content_switches.cc
+++ b/content/public/common/content_switches.cc
@@ -312,6 +312,9 @@ const char kEnablePrivilegedWebGLExtensions[] =
const char kEnablePruneGpuCommandBuffers[] =
"enable-prune-gpu-command-buffers";
+// Enable renderer side mixing and low latency audio path for media elements.
+const char kEnableRendererSideMixing[] = "enable-renderer-side-mixing";
+
// Enables TLS cached info extension.
const char kEnableSSLCachedInfo[] = "enable-ssl-cached-info";
diff --git a/content/public/common/content_switches.h b/content/public/common/content_switches.h
index 983c9f5..1f7197c 100644
--- a/content/public/common/content_switches.h
+++ b/content/public/common/content_switches.h
@@ -104,6 +104,7 @@ extern const char kEnablePinch[];
extern const char kEnablePreparsedJsCaching[];
CONTENT_EXPORT extern const char kEnablePrivilegedWebGLExtensions[];
extern const char kEnablePruneGpuCommandBuffers[];
+extern const char kEnableRendererSideMixing[];
extern const char kEnableSSLCachedInfo[];
extern const char kEnableSandboxLogging[];
extern const char kEnableSeccompSandbox[];
diff --git a/content/renderer/media/audio_renderer_mixer_manager.cc b/content/renderer/media/audio_renderer_mixer_manager.cc
new file mode 100644
index 0000000..03e638e
--- /dev/null
+++ b/content/renderer/media/audio_renderer_mixer_manager.cc
@@ -0,0 +1,73 @@
+// Copyright (c) 2012 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 "content/renderer/media/audio_renderer_mixer_manager.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "content/renderer/media/audio_device_factory.h"
+#include "media/base/audio_renderer_mixer.h"
+#include "media/base/audio_renderer_mixer_input.h"
+
+namespace content {
+
+AudioRendererMixerManager::AudioRendererMixerManager(int hardware_sample_rate,
+ int hardware_buffer_size)
+ : hardware_sample_rate_(hardware_sample_rate),
+ hardware_buffer_size_(hardware_buffer_size) {
+}
+
+AudioRendererMixerManager::~AudioRendererMixerManager() {
+ DCHECK(mixers_.empty());
+}
+
+media::AudioRendererMixerInput* AudioRendererMixerManager::CreateInput() {
+ return new media::AudioRendererMixerInput(
+ base::Bind(
+ &AudioRendererMixerManager::GetMixer, base::Unretained(this)),
+ base::Bind(
+ &AudioRendererMixerManager::RemoveMixer, base::Unretained(this)));
+}
+
+media::AudioRendererMixer* AudioRendererMixerManager::GetMixer(
+ const media::AudioParameters& params) {
+ base::AutoLock auto_lock(mixers_lock_);
+
+ AudioRendererMixerMap::iterator it = mixers_.find(params);
+ if (it != mixers_.end()) {
+ it->second.ref_count++;
+ return it->second.mixer;
+ }
+
+ // Create output parameters based on the audio hardware configuration for
+ // passing on to the output sink. Force to 16-bit output for now since we
+ // know that works well for WebAudio and WebRTC.
+ media::AudioParameters output_params(
+ media::AudioParameters::AUDIO_PCM_LOW_LATENCY, params.channel_layout(),
+ hardware_sample_rate_, 16, hardware_buffer_size_);
+
+ media::AudioRendererMixer* mixer = new media::AudioRendererMixer(
+ params, output_params, AudioDeviceFactory::Create());
+
+ AudioRendererMixerReference mixer_reference = { mixer, 1 };
+ mixers_[params] = mixer_reference;
+ return mixer;
+}
+
+void AudioRendererMixerManager::RemoveMixer(
+ const media::AudioParameters& params) {
+ base::AutoLock auto_lock(mixers_lock_);
+
+ AudioRendererMixerMap::iterator it = mixers_.find(params);
+ DCHECK(it != mixers_.end());
+
+ // Only remove the mixer if AudioRendererMixerManager is the last owner.
+ it->second.ref_count--;
+ if (it->second.ref_count == 0) {
+ delete it->second.mixer;
+ mixers_.erase(it);
+ }
+}
+
+} // namespace content
diff --git a/content/renderer/media/audio_renderer_mixer_manager.h b/content/renderer/media/audio_renderer_mixer_manager.h
new file mode 100644
index 0000000..ab713f96
--- /dev/null
+++ b/content/renderer/media/audio_renderer_mixer_manager.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2012 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 CONTENT_RENDERER_MEDIA_AUDIO_RENDERER_MIXER_MANAGER_H_
+#define CONTENT_RENDERER_MEDIA_AUDIO_RENDERER_MIXER_MANAGER_H_
+
+#include <map>
+
+#include "base/synchronization/lock.h"
+#include "content/common/content_export.h"
+#include "media/audio/audio_parameters.h"
+
+namespace media {
+class AudioRendererMixer;
+class AudioRendererMixerInput;
+}
+
+namespace content {
+
+// Manages sharing of an AudioRendererMixer among AudioRendererMixerInputs based
+// on their AudioParameters configuration. Inputs with the same AudioParameters
+// configuration will share a mixer while a new AudioRendererMixer will be
+// lazily created if one with the exact AudioParameters does not exist.
+//
+// There should only be one instance of AudioRendererMixerManager per render
+// thread.
+//
+// TODO(dalecurtis): Right now we require AudioParameters to be an exact match
+// when we should be able to ignore bits per channel since we're only dealing
+// with floats. However, bits per channel is currently used to interleave the
+// audio data by AudioDevice::AudioThreadCallback::Process for consumption via
+// the shared memory. See http://crbug.com/114700.
+class CONTENT_EXPORT AudioRendererMixerManager {
+ public:
+ // Construct an instance using the given audio hardware configuration.
+ AudioRendererMixerManager(int hardware_sample_rate, int hardware_buffer_size);
+ ~AudioRendererMixerManager();
+
+ // Creates an AudioRendererMixerInput with the proper callbacks necessary to
+ // retrieve an AudioRendererMixer instance from AudioRendererMixerManager.
+ // Caller must ensure AudioRendererMixerManager outlives the returned input.
+ media::AudioRendererMixerInput* CreateInput();
+
+ private:
+ friend class AudioRendererMixerManagerTest;
+
+ // Returns a mixer instance based on AudioParameters; an existing one if one
+ // with the provided AudioParameters exists or a new one if not.
+ media::AudioRendererMixer* GetMixer(const media::AudioParameters& params);
+
+ // Remove a mixer instance given a mixer if the only other reference is held
+ // by AudioRendererMixerManager. Every AudioRendererMixer owner must call
+ // this method when it's done with a mixer.
+ void RemoveMixer(const media::AudioParameters& params);
+
+ // Map of AudioParameters to <AudioRendererMixer, Count>. Count allows
+ // AudioRendererMixerManager to keep track explicitly (v.s. RefCounted which
+ // is implicit) of the number of outstanding AudioRendererMixers.
+ struct AudioRendererMixerReference {
+ media::AudioRendererMixer* mixer;
+ int ref_count;
+ };
+ typedef std::map<media::AudioParameters, AudioRendererMixerReference,
+ media::AudioParameters::Compare> AudioRendererMixerMap;
+ AudioRendererMixerMap mixers_;
+ base::Lock mixers_lock_;
+
+ // Audio hardware configuration. Used to construct output AudioParameters for
+ // each AudioRendererMixer instance.
+ int hardware_sample_rate_;
+ int hardware_buffer_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioRendererMixerManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_RENDERER_MEDIA_AUDIO_RENDERER_MIXER_MANAGER_H_
diff --git a/content/renderer/media/audio_renderer_mixer_manager_unittest.cc b/content/renderer/media/audio_renderer_mixer_manager_unittest.cc
new file mode 100644
index 0000000..92a44d4
--- /dev/null
+++ b/content/renderer/media/audio_renderer_mixer_manager_unittest.cc
@@ -0,0 +1,131 @@
+// Copyright (c) 2012 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 "base/memory/scoped_ptr.h"
+#include "content/renderer/media/audio_device_factory.h"
+#include "content/renderer/media/audio_renderer_mixer_manager.h"
+#include "media/base/audio_renderer_mixer.h"
+#include "media/base/audio_renderer_mixer_input.h"
+#include "media/base/fake_audio_render_callback.h"
+#include "media/base/mock_audio_renderer_sink.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+static const int kBitsPerChannel = 16;
+static const int kSampleRate = 48000;
+static const int kBufferSize = 8192;
+static const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_STEREO;
+
+// By sub-classing AudioDeviceFactory we've overridden the factory to use our
+// CreateAudioDevice() method globally.
+class MockAudioRenderSinkFactory : public AudioDeviceFactory {
+ public:
+ MockAudioRenderSinkFactory() {}
+ virtual ~MockAudioRenderSinkFactory() {}
+
+ protected:
+ virtual media::MockAudioRendererSink* CreateAudioDevice() {
+ media::MockAudioRendererSink* sink = new media::MockAudioRendererSink();
+ EXPECT_CALL(*sink, Start());
+ EXPECT_CALL(*sink, Stop());
+ return sink;
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(MockAudioRenderSinkFactory);
+};
+
+class AudioRendererMixerManagerTest : public testing::Test {
+ public:
+ AudioRendererMixerManagerTest() {
+ // We don't want to deal with instantiating a real AudioDevice since it's
+ // not important to our testing, so use a mock AudioDeviceFactory.
+ mock_sink_factory_.reset(new MockAudioRenderSinkFactory());
+ manager_.reset(new AudioRendererMixerManager(kSampleRate, kBufferSize));
+ }
+
+ media::AudioRendererMixer* GetMixer(const media::AudioParameters& params) {
+ return manager_->GetMixer(params);
+ }
+
+ void RemoveMixer(const media::AudioParameters& params) {
+ return manager_->RemoveMixer(params);
+ }
+
+ // Number of instantiated mixers.
+ int mixer_count() {
+ return manager_->mixers_.size();
+ }
+
+ protected:
+ scoped_ptr<MockAudioRenderSinkFactory> mock_sink_factory_;
+ scoped_ptr<AudioRendererMixerManager> manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioRendererMixerManagerTest);
+};
+
+// Verify GetMixer() and RemoveMixer() both work as expected; particularly with
+// respect to the explicit ref counting done.
+TEST_F(AudioRendererMixerManagerTest, GetRemoveMixer) {
+ // There should be no mixers outstanding to start with.
+ EXPECT_EQ(mixer_count(), 0);
+
+ media::AudioParameters params1(
+ media::AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, kSampleRate,
+ kBitsPerChannel, kBufferSize);
+
+ media::AudioRendererMixer* mixer1 = GetMixer(params1);
+ ASSERT_TRUE(mixer1);
+ EXPECT_EQ(mixer_count(), 1);
+
+ // The same parameters should return the same mixer1.
+ EXPECT_EQ(mixer1, GetMixer(params1));
+ EXPECT_EQ(mixer_count(), 1);
+
+ // Remove the extra mixer we just acquired.
+ RemoveMixer(params1);
+ EXPECT_EQ(mixer_count(), 1);
+
+ media::AudioParameters params2(
+ media::AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, kSampleRate * 2,
+ kBitsPerChannel, kBufferSize * 2);
+ media::AudioRendererMixer* mixer2 = GetMixer(params2);
+ ASSERT_TRUE(mixer2);
+ EXPECT_EQ(mixer_count(), 2);
+
+ // Different parameters should result in a different mixer1.
+ EXPECT_NE(mixer1, mixer2);
+
+ // Remove both outstanding mixers.
+ RemoveMixer(params1);
+ EXPECT_EQ(mixer_count(), 1);
+ RemoveMixer(params2);
+ EXPECT_EQ(mixer_count(), 0);
+}
+
+// Verify CreateInput() provides AudioRendererMixerInput with the appropriate
+// callbacks and they are working as expected.
+TEST_F(AudioRendererMixerManagerTest, CreateInput) {
+ media::AudioParameters params(
+ media::AudioParameters::AUDIO_PCM_LINEAR, kChannelLayout, kSampleRate,
+ kBitsPerChannel, kBufferSize);
+
+ // Create a mixer input and ensure it doesn't instantiate a mixer yet.
+ EXPECT_EQ(mixer_count(), 0);
+ scoped_refptr<media::AudioRendererMixerInput> input(manager_->CreateInput());
+ EXPECT_EQ(mixer_count(), 0);
+
+ // Implicitly test that AudioRendererMixerInput was provided with the expected
+ // callbacks needed to acquire an AudioRendererMixer and remove it.
+ media::FakeAudioRenderCallback callback(0);
+ input->Initialize(params, &callback);
+ EXPECT_EQ(mixer_count(), 1);
+
+ // Destroying the input should destroy the mixer.
+ input = NULL;
+ EXPECT_EQ(mixer_count(), 0);
+}
+
+} // namespace content
diff --git a/content/renderer/media/render_audiosourceprovider.cc b/content/renderer/media/render_audiosourceprovider.cc
index c0a0580..da1e341 100644
--- a/content/renderer/media/render_audiosourceprovider.cc
+++ b/content/renderer/media/render_audiosourceprovider.cc
@@ -5,11 +5,17 @@
#include "content/renderer/media/render_audiosourceprovider.h"
#include "base/basictypes.h"
+#include "base/command_line.h"
#include "base/logging.h"
+#include "content/public/common/content_switches.h"
#include "content/renderer/media/audio_device_factory.h"
+#include "content/renderer/media/audio_renderer_mixer_manager.h"
+#include "content/renderer/render_thread_impl.h"
+#include "media/base/audio_renderer_mixer_input.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebAudioSourceProviderClient.h"
using content::AudioDeviceFactory;
+using content::AudioRendererMixerManager;
using std::vector;
using WebKit::WebVector;
@@ -20,11 +26,17 @@ RenderAudioSourceProvider::RenderAudioSourceProvider()
is_running_(false),
renderer_(NULL),
client_(NULL) {
- // We create the AudioDevice here using the factory. But we don't yet know
- // the audio format (sample-rate, etc.) at this point. Later, when
- // Initialize() is called, we have the audio format information and call
- // the AudioDevice::Initialize() method to fully initialize it.
- default_sink_ = AudioDeviceFactory::Create();
+ // We create an AudioRendererSink here, but we don't yet know the audio format
+ // (sample-rate, etc.) at this point. Later, when Initialize() is called, we
+ // have the audio format information and call AudioRendererSink::Initialize()
+ // to fully initialize it.
+ const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
+ if (cmd_line->HasSwitch(switches::kEnableRendererSideMixing)) {
+ default_sink_ = RenderThreadImpl::current()->
+ GetAudioRendererMixerManager()->CreateInput();
+ } else {
+ default_sink_ = AudioDeviceFactory::Create();
+ }
}
void RenderAudioSourceProvider::setClient(
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index 99988be..0cb9136 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -55,8 +55,10 @@
#include "content/renderer/dom_storage/webstoragenamespace_impl.h"
#include "content/renderer/gpu/compositor_thread.h"
#include "content/renderer/gpu/gpu_benchmarking_extension.h"
+#include "content/renderer/media/audio_hardware.h"
#include "content/renderer/media/audio_input_message_filter.h"
#include "content/renderer/media/audio_message_filter.h"
+#include "content/renderer/media/audio_renderer_mixer_manager.h"
#include "content/renderer/media/media_stream_center.h"
#include "content/renderer/media/video_capture_impl_manager.h"
#include "content/renderer/media/video_capture_message_filter.h"
@@ -111,6 +113,7 @@ using WebKit::WebScriptController;
using WebKit::WebSecurityPolicy;
using WebKit::WebString;
using WebKit::WebView;
+using content::AudioRendererMixerManager;
using content::RenderProcessObserver;
namespace {
@@ -750,6 +753,17 @@ RenderThreadImpl::GetGpuVDAContext3D() {
return gpu_vda_context3d_->AsWeakPtr();
}
+content::AudioRendererMixerManager*
+RenderThreadImpl::GetAudioRendererMixerManager() {
+ if (!audio_renderer_mixer_manager_.get()) {
+ audio_renderer_mixer_manager_.reset(new AudioRendererMixerManager(
+ audio_hardware::GetOutputSampleRate(),
+ audio_hardware::GetOutputBufferSize()));
+ }
+
+ return audio_renderer_mixer_manager_.get();
+}
+
#if defined(OS_WIN)
void RenderThreadImpl::PreCacheFont(const LOGFONT& log_font) {
Send(new ChildProcessHostMsg_PreCacheFont(log_font));
diff --git a/content/renderer/render_thread_impl.h b/content/renderer/render_thread_impl.h
index 8857120..d3048d7 100644
--- a/content/renderer/render_thread_impl.h
+++ b/content/renderer/render_thread_impl.h
@@ -53,6 +53,7 @@ class ScopedCOMInitializer;
}
namespace content {
+class AudioRendererMixerManager;
class BrowserPluginChannelManager;
class BrowserPluginRegistry;
class MediaStreamCenter;
@@ -223,6 +224,11 @@ class CONTENT_EXPORT RenderThreadImpl : public content::RenderThread,
// threaded compositing is enabled.
base::WeakPtr<WebGraphicsContext3DCommandBufferImpl> GetGpuVDAContext3D();
+ // AudioRendererMixerManager instance which manages renderer side mixer
+ // instances shared based on configured audio parameters. Lazily created on
+ // first call.
+ content::AudioRendererMixerManager* GetAudioRendererMixerManager();
+
private:
virtual bool OnControlMessageReceived(const IPC::Message& msg) OVERRIDE;
@@ -303,6 +309,8 @@ class CONTENT_EXPORT RenderThreadImpl : public content::RenderThread,
scoped_ptr<WebGraphicsContext3DCommandBufferImpl> gpu_vda_context3d_;
+ scoped_ptr<content::AudioRendererMixerManager> audio_renderer_mixer_manager_;
+
DISALLOW_COPY_AND_ASSIGN(RenderThreadImpl);
};