Index: content/browser/renderer_host/media/web_contents_audio_input_stream_unittest.cc |
diff --git a/content/browser/renderer_host/media/web_contents_audio_input_stream_unittest.cc b/content/browser/renderer_host/media/web_contents_audio_input_stream_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e226687ca4aaa9794921f15597a519800d99ec70 |
--- /dev/null |
+++ b/content/browser/renderer_host/media/web_contents_audio_input_stream_unittest.cc |
@@ -0,0 +1,512 @@ |
+// Copyright (c) 2013 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/browser/renderer_host/media/web_contents_audio_input_stream.h" |
+ |
+#include <list> |
+ |
+#include "base/bind.h" |
+#include "base/bind_helpers.h" |
+#include "base/message_loop.h" |
+#include "base/synchronization/waitable_event.h" |
+#include "base/threading/thread.h" |
+#include "content/browser/browser_thread_impl.h" |
+#include "content/browser/renderer_host/media/audio_mirroring_manager.h" |
+#include "content/browser/renderer_host/media/web_contents_tracker.h" |
+#include "media/audio/simple_sources.h" |
+#include "media/audio/virtual_audio_input_stream.h" |
+#include "testing/gmock/include/gmock/gmock.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+using ::testing::_; |
+using ::testing::Assign; |
+using ::testing::DoAll; |
+using ::testing::Invoke; |
+using ::testing::InvokeWithoutArgs; |
+using ::testing::NotNull; |
+using ::testing::SaveArg; |
+using ::testing::WithArgs; |
+ |
+using media::AudioInputStream; |
+using media::AudioOutputStream; |
+using media::AudioParameters; |
+using media::SineWaveAudioSource; |
+using media::VirtualAudioInputStream; |
+using media::VirtualAudioOutputStream; |
+ |
+namespace content { |
+ |
+namespace { |
+ |
+const int kRenderProcessId = 123; |
+const int kRenderViewId = 456; |
+const int kAnotherRenderProcessId = 789; |
+const int kAnotherRenderViewId = 1; |
+ |
+const AudioParameters& TestAudioParameters() { |
+ static const AudioParameters params( |
+ AudioParameters::AUDIO_FAKE, |
+ media::CHANNEL_LAYOUT_STEREO, |
+ AudioParameters::kAudioCDSampleRate, 16, |
+ AudioParameters::kAudioCDSampleRate / 100); |
+ return params; |
+} |
+ |
+class MockAudioMirroringManager : public AudioMirroringManager { |
+ public: |
+ MockAudioMirroringManager() : AudioMirroringManager() {} |
+ virtual ~MockAudioMirroringManager() {} |
+ |
+ MOCK_METHOD3(StartMirroring, |
+ void(int render_process_id, int render_view_id, |
+ MirroringDestination* destination)); |
+ MOCK_METHOD3(StopMirroring, |
+ void(int render_process_id, int render_view_id, |
+ MirroringDestination* destination)); |
+ |
+ private: |
+ DISALLOW_COPY_AND_ASSIGN(MockAudioMirroringManager); |
+}; |
+ |
+class MockWebContentsTracker : public WebContentsTracker { |
+ public: |
+ MockWebContentsTracker() : WebContentsTracker() {} |
+ |
+ MOCK_METHOD3(Start, |
+ void(int render_process_id, int render_view_id, |
+ const ChangeCallback& callback)); |
+ MOCK_METHOD0(Stop, void()); |
+ |
+ private: |
+ virtual ~MockWebContentsTracker() {} |
+ |
+ DISALLOW_COPY_AND_ASSIGN(MockWebContentsTracker); |
+}; |
+ |
+// A fully-functional VirtualAudioInputStream, but methods are mocked to allow |
+// tests to check how/when they are invoked. |
+class MockVirtualAudioInputStream : public VirtualAudioInputStream { |
+ public: |
+ explicit MockVirtualAudioInputStream(base::MessageLoopProxy* message_loop) |
+ : VirtualAudioInputStream(TestAudioParameters(), message_loop, |
+ VirtualAudioInputStream::AfterCloseCallback()), |
+ real_(TestAudioParameters(), message_loop, |
+ base::Bind(&MockVirtualAudioInputStream::OnRealStreamHasClosed, |
+ base::Unretained(this))), |
+ real_stream_is_closed_(false) { |
+ // Set default actions of mocked methods to delegate to the concrete |
+ // implementation. |
+ ON_CALL(*this, Open()) |
+ .WillByDefault(Invoke(&real_, &VirtualAudioInputStream::Open)); |
+ ON_CALL(*this, Start(_)) |
+ .WillByDefault(Invoke(&real_, &VirtualAudioInputStream::Start)); |
+ ON_CALL(*this, Stop()) |
+ .WillByDefault(Invoke(&real_, &VirtualAudioInputStream::Stop)); |
+ ON_CALL(*this, Close()) |
+ .WillByDefault(Invoke(&real_, &VirtualAudioInputStream::Close)); |
+ ON_CALL(*this, GetMaxVolume()) |
+ .WillByDefault(Invoke(&real_, &VirtualAudioInputStream::GetMaxVolume)); |
+ ON_CALL(*this, SetVolume(_)) |
+ .WillByDefault(Invoke(&real_, &VirtualAudioInputStream::SetVolume)); |
+ ON_CALL(*this, GetVolume()) |
+ .WillByDefault(Invoke(&real_, &VirtualAudioInputStream::GetVolume)); |
+ ON_CALL(*this, SetAutomaticGainControl(_)) |
+ .WillByDefault( |
+ Invoke(&real_, &VirtualAudioInputStream::SetAutomaticGainControl)); |
+ ON_CALL(*this, GetAutomaticGainControl()) |
+ .WillByDefault( |
+ Invoke(&real_, &VirtualAudioInputStream::GetAutomaticGainControl)); |
+ ON_CALL(*this, AddOutputStream(NotNull(), _)) |
+ .WillByDefault( |
+ Invoke(&real_, &VirtualAudioInputStream::AddOutputStream)); |
+ ON_CALL(*this, RemoveOutputStream(NotNull(), _)) |
+ .WillByDefault( |
+ Invoke(&real_, &VirtualAudioInputStream::RemoveOutputStream)); |
+ } |
+ |
+ ~MockVirtualAudioInputStream() { |
+ DCHECK(real_stream_is_closed_); |
+ } |
+ |
+ MOCK_METHOD0(Open, bool()); |
+ MOCK_METHOD1(Start, void(AudioInputStream::AudioInputCallback*)); |
+ MOCK_METHOD0(Stop, void()); |
+ MOCK_METHOD0(Close, void()); |
+ MOCK_METHOD0(GetMaxVolume, double()); |
+ MOCK_METHOD1(SetVolume, void(double)); |
+ MOCK_METHOD0(GetVolume, double()); |
+ MOCK_METHOD1(SetAutomaticGainControl, void(bool)); |
+ MOCK_METHOD0(GetAutomaticGainControl, bool()); |
+ MOCK_METHOD2(AddOutputStream, void(VirtualAudioOutputStream*, |
+ const AudioParameters&)); |
+ MOCK_METHOD2(RemoveOutputStream, void(VirtualAudioOutputStream*, |
+ const AudioParameters&)); |
+ |
+ private: |
+ void OnRealStreamHasClosed(VirtualAudioInputStream* stream) { |
+ DCHECK_EQ(&real_, stream); |
+ DCHECK(!real_stream_is_closed_); |
+ real_stream_is_closed_ = true; |
+ } |
+ |
+ VirtualAudioInputStream real_; |
+ bool real_stream_is_closed_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(MockVirtualAudioInputStream); |
+}; |
+ |
+class MockAudioInputCallback : public AudioInputStream::AudioInputCallback { |
+ public: |
+ MockAudioInputCallback() {} |
+ |
+ MOCK_METHOD5(OnData, void(AudioInputStream* stream, const uint8* src, |
+ uint32 size, uint32 hardware_delay_bytes, |
+ double volume)); |
+ MOCK_METHOD1(OnClose, void(AudioInputStream* stream)); |
+ MOCK_METHOD2(OnError, void(AudioInputStream* stream, int code)); |
+ |
+ private: |
+ DISALLOW_COPY_AND_ASSIGN(MockAudioInputCallback); |
+}; |
+ |
+} // namespace |
+ |
+class WebContentsAudioInputStreamTest : public testing::Test { |
+ public: |
+ WebContentsAudioInputStreamTest() |
+ : audio_thread_("Audio thread"), |
+ io_thread_(BrowserThread::IO), |
+ mock_mirroring_manager_(new MockAudioMirroringManager()), |
+ mock_tracker_(new MockWebContentsTracker()), |
+ mock_vais_(NULL), |
+ wcais_(NULL), |
+ destination_(NULL), |
+ current_render_process_id_(kRenderProcessId), |
+ current_render_view_id_(kRenderViewId), |
+ on_data_event_(false, false) { |
+ audio_thread_.Start(); |
+ io_thread_.Start(); |
+ } |
+ |
+ virtual ~WebContentsAudioInputStreamTest() { |
+ audio_thread_.Stop(); |
+ io_thread_.Stop(); |
+ |
+ DCHECK(!mock_vais_); |
+ DCHECK(!wcais_); |
+ EXPECT_FALSE(destination_); |
+ DCHECK(streams_.empty()); |
+ DCHECK(sources_.empty()); |
+ } |
+ |
+ void Open() { |
+ mock_vais_ = |
+ new MockVirtualAudioInputStream(audio_thread_.message_loop_proxy()); |
+ EXPECT_CALL(*mock_vais_, Open()); |
+ EXPECT_CALL(*mock_vais_, Close()); // At Close() time. |
+ |
+ ASSERT_EQ(kRenderProcessId, current_render_process_id_); |
+ ASSERT_EQ(kRenderViewId, current_render_view_id_); |
+ EXPECT_CALL(*mock_tracker_, Start(kRenderProcessId, kRenderViewId, _)) |
+ .WillOnce(DoAll( |
+ SaveArg<2>(&change_callback_), |
+ WithArgs<0, 1>( |
+ Invoke(&change_callback_, |
+ &WebContentsTracker::ChangeCallback::Run)))); |
+ EXPECT_CALL(*mock_tracker_, Stop()); // At Close() time. |
+ |
+ wcais_ = new WebContentsAudioInputStream( |
+ current_render_process_id_, current_render_view_id_, |
+ audio_thread_.message_loop_proxy(), mock_mirroring_manager_.get(), |
+ mock_tracker_, mock_vais_); |
+ wcais_->Open(); |
+ } |
+ |
+ void Start() { |
+ EXPECT_CALL(*mock_vais_, Start(&mock_input_callback_)); |
+ EXPECT_CALL(*mock_vais_, Stop()); // At Stop() time. |
+ |
+ EXPECT_CALL(*mock_mirroring_manager_, |
+ StartMirroring(kRenderProcessId, kRenderViewId, NotNull())) |
+ .WillOnce(SaveArg<2>(&destination_)) |
+ .RetiresOnSaturation(); |
+ // At Stop() time, or when the mirroring target changes: |
+ EXPECT_CALL(*mock_mirroring_manager_, |
+ StopMirroring(kRenderProcessId, kRenderViewId, NotNull())) |
+ .WillOnce(Assign( |
+ &destination_, |
+ static_cast<AudioMirroringManager::MirroringDestination*>(NULL))) |
+ .RetiresOnSaturation(); |
+ |
+ EXPECT_CALL(mock_input_callback_, OnData(NotNull(), NotNull(), _, _, _)) |
+ .WillRepeatedly( |
+ InvokeWithoutArgs(&on_data_event_, &base::WaitableEvent::Signal)); |
+ EXPECT_CALL(mock_input_callback_, OnClose(_)); // At Stop() time. |
+ |
+ wcais_->Start(&mock_input_callback_); |
+ |
+ // Test plumbing of volume controls and automatic gain controls. Calls to |
+ // wcais_ methods should delegate directly to mock_vais_. |
+ EXPECT_CALL(*mock_vais_, GetVolume()); |
+ double volume = wcais_->GetVolume(); |
+ EXPECT_CALL(*mock_vais_, GetMaxVolume()); |
+ const double max_volume = wcais_->GetMaxVolume(); |
+ volume *= 2.0; |
+ if (volume < max_volume) { |
+ volume = max_volume; |
+ } |
+ EXPECT_CALL(*mock_vais_, SetVolume(volume)); |
+ wcais_->SetVolume(volume); |
+ EXPECT_CALL(*mock_vais_, GetAutomaticGainControl()); |
+ bool auto_gain = wcais_->GetAutomaticGainControl(); |
+ auto_gain = !auto_gain; |
+ EXPECT_CALL(*mock_vais_, SetAutomaticGainControl(auto_gain)); |
+ wcais_->SetAutomaticGainControl(auto_gain); |
+ } |
+ |
+ void AddAnotherInput() { |
+ // Note: WCAIS posts a task to invoke |
+ // MockAudioMirroringManager::StartMirroring() on the IO thread, which |
+ // causes our mock to set |destination_|. Block until that has happened. |
+ base::WaitableEvent done(false, false); |
+ BrowserThread::PostTask( |
+ BrowserThread::IO, FROM_HERE, base::Bind( |
+ &base::WaitableEvent::Signal, base::Unretained(&done))); |
+ done.Wait(); |
+ ASSERT_TRUE(destination_); |
+ |
+ EXPECT_CALL(*mock_vais_, AddOutputStream(NotNull(), _)) |
+ .RetiresOnSaturation(); |
+ // Later, when stream is closed: |
+ EXPECT_CALL(*mock_vais_, RemoveOutputStream(NotNull(), _)) |
+ .RetiresOnSaturation(); |
+ |
+ const AudioParameters& params = TestAudioParameters(); |
+ AudioOutputStream* const out = destination_->AddInput(params); |
+ ASSERT_TRUE(out); |
+ streams_.push_back(out); |
+ EXPECT_TRUE(out->Open()); |
+ SineWaveAudioSource* const source = new SineWaveAudioSource( |
+ params.channel_layout(), 200.0, params.sample_rate()); |
+ sources_.push_back(source); |
+ out->Start(source); |
+ } |
+ |
+ void RemoveOneInputInFIFOOrder() { |
+ ASSERT_FALSE(streams_.empty()); |
+ AudioOutputStream* const out = streams_.front(); |
+ streams_.pop_front(); |
+ out->Stop(); |
+ out->Close(); // Self-deletes. |
+ ASSERT_TRUE(!sources_.empty()); |
+ delete sources_.front(); |
+ sources_.pop_front(); |
+ } |
+ |
+ void ChangeMirroringTarget() { |
+ const int next_render_process_id = |
+ current_render_process_id_ == kRenderProcessId ? |
+ kAnotherRenderProcessId : kRenderProcessId; |
+ const int next_render_view_id = |
+ current_render_view_id_ == kRenderViewId ? |
+ kAnotherRenderViewId : kRenderViewId; |
+ |
+ EXPECT_CALL(*mock_mirroring_manager_, |
+ StartMirroring(next_render_process_id, next_render_view_id, |
+ NotNull())) |
+ .WillOnce(SaveArg<2>(&destination_)) |
+ .RetiresOnSaturation(); |
+ // At Stop() time, or when the mirroring target changes: |
+ EXPECT_CALL(*mock_mirroring_manager_, |
+ StopMirroring(next_render_process_id, next_render_view_id, |
+ NotNull())) |
+ .WillOnce(Assign( |
+ &destination_, |
+ static_cast<AudioMirroringManager::MirroringDestination*>(NULL))) |
+ .RetiresOnSaturation(); |
+ |
+ // Simulate OnTargetChange() callback from WebContentsTracker. |
+ EXPECT_FALSE(change_callback_.is_null()); |
+ change_callback_.Run(next_render_process_id, next_render_view_id); |
+ |
+ current_render_process_id_ = next_render_process_id; |
+ current_render_view_id_ = next_render_view_id; |
+ } |
+ |
+ void LoseMirroringTarget() { |
+ EXPECT_CALL(mock_input_callback_, OnError(_, _)); |
+ |
+ // Simulate OnTargetChange() callback from WebContentsTracker. |
+ EXPECT_FALSE(change_callback_.is_null()); |
+ change_callback_.Run(-1, -1); |
+ } |
+ |
+ void Stop() { |
+ wcais_->Stop(); |
+ } |
+ |
+ void Close() { |
+ // WebContentsAudioInputStream self-destructs on Close(). Its internal |
+ // objects hang around until they are no longer referred to (e.g., as tasks |
+ // on other threads shut things down). |
+ wcais_->Close(); |
+ wcais_ = NULL; |
+ mock_vais_ = NULL; |
+ } |
+ |
+ void RunOnAudioThread(const base::Closure& closure) { |
+ audio_thread_.message_loop()->PostTask(FROM_HERE, closure); |
+ } |
+ |
+ // Block the calling thread until OnData() callbacks are being made. |
+ void WaitForData() { |
+ // Note: Arbitrarily chosen, but more iterations causes tests to take |
+ // significantly more time. |
+ static const int kNumIterations = 3; |
+ for (int i = 0; i < kNumIterations; ++i) |
+ on_data_event_.Wait(); |
+ } |
+ |
+ private: |
+ base::Thread audio_thread_; |
+ BrowserThreadImpl io_thread_; |
+ |
+ scoped_ptr<MockAudioMirroringManager> mock_mirroring_manager_; |
+ scoped_refptr<MockWebContentsTracker> mock_tracker_; |
+ |
+ MockVirtualAudioInputStream* mock_vais_; // Owned by wcais_. |
+ WebContentsAudioInputStream* wcais_; // Self-destructs on Close(). |
+ |
+ // Mock consumer of audio data. |
+ MockAudioInputCallback mock_input_callback_; |
+ |
+ // Provided by WebContentsAudioInputStream to the mock WebContentsTracker. |
+ // This callback is saved here, and test code will invoke it to simulate |
+ // target change events. |
+ WebContentsTracker::ChangeCallback change_callback_; |
+ |
+ // Provided by WebContentsAudioInputStream to the mock AudioMirroringManager. |
+ // A pointer to the implementation is saved here, and test code will invoke it |
+ // to simulate: 1) calls to AddInput(); and 2) diverting audio data. |
+ AudioMirroringManager::MirroringDestination* destination_; |
+ |
+ // Current target RenderView. These get flipped in ChangedMirroringTarget(). |
+ int current_render_process_id_; |
+ int current_render_view_id_; |
+ |
+ // Streams provided by calls to WebContentsAudioInputStream::AddInput(). Each |
+ // is started with a simulated source of audio data. |
+ std::list<AudioOutputStream*> streams_; |
+ std::list<SineWaveAudioSource*> sources_; // 1:1 with elements in streams_. |
+ |
+ base::WaitableEvent on_data_event_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(WebContentsAudioInputStreamTest); |
+}; |
+ |
+#define RUN_ON_AUDIO_THREAD(method) \ |
+ RunOnAudioThread(base::Bind(&WebContentsAudioInputStreamTest::method, \ |
+ base::Unretained(this))) |
+ |
+TEST_F(WebContentsAudioInputStreamTest, OpenedButNeverStarted) { |
+ RUN_ON_AUDIO_THREAD(Open); |
+ RUN_ON_AUDIO_THREAD(Close); |
+} |
+ |
+TEST_F(WebContentsAudioInputStreamTest, MirroringNothing) { |
+ RUN_ON_AUDIO_THREAD(Open); |
+ RUN_ON_AUDIO_THREAD(Start); |
+ RUN_ON_AUDIO_THREAD(Stop); |
+ RUN_ON_AUDIO_THREAD(Close); |
+} |
+ |
+TEST_F(WebContentsAudioInputStreamTest, MirroringOutputOutlivesSession) { |
+ RUN_ON_AUDIO_THREAD(Open); |
+ RUN_ON_AUDIO_THREAD(Start); |
+ RUN_ON_AUDIO_THREAD(AddAnotherInput); |
+ WaitForData(); |
+ RUN_ON_AUDIO_THREAD(Stop); |
+ RUN_ON_AUDIO_THREAD(Close); |
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); |
+} |
+ |
+TEST_F(WebContentsAudioInputStreamTest, MirroringOutputWithinSession) { |
+ RUN_ON_AUDIO_THREAD(Open); |
+ RUN_ON_AUDIO_THREAD(Start); |
+ RUN_ON_AUDIO_THREAD(AddAnotherInput); |
+ WaitForData(); |
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); |
+ RUN_ON_AUDIO_THREAD(Stop); |
+ RUN_ON_AUDIO_THREAD(Close); |
+} |
+ |
+TEST_F(WebContentsAudioInputStreamTest, MirroringNothingWithTargetChange) { |
+ RUN_ON_AUDIO_THREAD(Open); |
+ RUN_ON_AUDIO_THREAD(Start); |
+ RUN_ON_AUDIO_THREAD(ChangeMirroringTarget); |
+ RUN_ON_AUDIO_THREAD(Stop); |
+ RUN_ON_AUDIO_THREAD(Close); |
+} |
+ |
+TEST_F(WebContentsAudioInputStreamTest, MirroringOneStreamAfterTargetChange) { |
+ RUN_ON_AUDIO_THREAD(Open); |
+ RUN_ON_AUDIO_THREAD(Start); |
+ RUN_ON_AUDIO_THREAD(ChangeMirroringTarget); |
+ RUN_ON_AUDIO_THREAD(AddAnotherInput); |
+ WaitForData(); |
+ RUN_ON_AUDIO_THREAD(Stop); |
+ RUN_ON_AUDIO_THREAD(Close); |
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); |
+} |
+ |
+TEST_F(WebContentsAudioInputStreamTest, MirroringOneStreamWithTargetChange) { |
+ RUN_ON_AUDIO_THREAD(Open); |
+ RUN_ON_AUDIO_THREAD(Start); |
+ RUN_ON_AUDIO_THREAD(AddAnotherInput); |
+ WaitForData(); |
+ RUN_ON_AUDIO_THREAD(ChangeMirroringTarget); |
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); |
+ RUN_ON_AUDIO_THREAD(AddAnotherInput); |
+ WaitForData(); |
+ RUN_ON_AUDIO_THREAD(Stop); |
+ RUN_ON_AUDIO_THREAD(Close); |
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); |
+} |
+ |
+TEST_F(WebContentsAudioInputStreamTest, MirroringLostTarget) { |
+ RUN_ON_AUDIO_THREAD(Open); |
+ RUN_ON_AUDIO_THREAD(Start); |
+ RUN_ON_AUDIO_THREAD(AddAnotherInput); |
+ WaitForData(); |
+ RUN_ON_AUDIO_THREAD(LoseMirroringTarget); |
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); |
+ RUN_ON_AUDIO_THREAD(Stop); |
+ RUN_ON_AUDIO_THREAD(Close); |
+} |
+ |
+TEST_F(WebContentsAudioInputStreamTest, MirroringMultipleStreamsAndTargets) { |
+ RUN_ON_AUDIO_THREAD(Open); |
+ RUN_ON_AUDIO_THREAD(Start); |
+ RUN_ON_AUDIO_THREAD(AddAnotherInput); |
+ WaitForData(); |
+ RUN_ON_AUDIO_THREAD(AddAnotherInput); |
+ RUN_ON_AUDIO_THREAD(AddAnotherInput); |
+ RUN_ON_AUDIO_THREAD(AddAnotherInput); |
+ WaitForData(); |
+ RUN_ON_AUDIO_THREAD(ChangeMirroringTarget); |
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); |
+ WaitForData(); |
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); |
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); |
+ RUN_ON_AUDIO_THREAD(AddAnotherInput); |
+ WaitForData(); |
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); |
+ WaitForData(); |
+ RUN_ON_AUDIO_THREAD(ChangeMirroringTarget); |
+ RUN_ON_AUDIO_THREAD(RemoveOneInputInFIFOOrder); |
+ RUN_ON_AUDIO_THREAD(Stop); |
+ RUN_ON_AUDIO_THREAD(Close); |
+} |
+ |
+} // namespace content |