diff options
author | satish@chromium.org <satish@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-16 05:58:04 +0000 |
---|---|---|
committer | satish@chromium.org <satish@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-16 05:58:04 +0000 |
commit | 41bdde54e45a13ff9c9f34b34f8e4f0395e9183b (patch) | |
tree | 5da8b35681a91144827aa8cd38f93814b6c9e8c2 /media | |
parent | c60d64ec89c1a8eb5279c38c101ec710c8d931f9 (diff) | |
download | chromium_src-41bdde54e45a13ff9c9f34b34f8e4f0395e9183b.zip chromium_src-41bdde54e45a13ff9c9f34b34f8e4f0395e9183b.tar.gz chromium_src-41bdde54e45a13ff9c9f34b34f8e4f0395e9183b.tar.bz2 |
Add an AudioInputController to help with audio recording.
This is similar to the existing AudioOutputController class used for playback.
Also adds a unit test which uses the new fake audio input stream, so this patch is dependant on http://codereview.chromium.org/2909015
TEST=no new user visible change to test, just some backend recording additions which are unused at the moment.
BUG=none
Review URL: http://codereview.chromium.org/2905010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@52617 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/audio/audio_input_controller.cc | 157 | ||||
-rw-r--r-- | media/audio/audio_input_controller.h | 121 | ||||
-rw-r--r-- | media/audio/audio_input_controller_unittest.cc | 128 | ||||
-rw-r--r-- | media/media.gyp | 3 |
4 files changed, 409 insertions, 0 deletions
diff --git a/media/audio/audio_input_controller.cc b/media/audio/audio_input_controller.cc new file mode 100644 index 0000000..666d490 --- /dev/null +++ b/media/audio/audio_input_controller.cc @@ -0,0 +1,157 @@ +// Copyright (c) 2010 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 "media/audio/audio_input_controller.h" + +namespace { + +const int kMaxSampleRate = 192000; +const int kMaxBitsPerSample = 64; +const int kMaxInputChannels = 2; +const int kMaxSamplesPerPacket = kMaxSampleRate; + +} // namespace + +namespace media { + +AudioInputController::AudioInputController(EventHandler* handler) + : handler_(handler), + state_(kEmpty), + thread_("AudioInputControllerThread") { +} + +AudioInputController::~AudioInputController() { + DCHECK(kClosed == state_ || kCreated == state_ || kEmpty == state_); +} + +// static +scoped_refptr<AudioInputController> AudioInputController::Create( + EventHandler* event_handler, + AudioManager::Format format, + int channels, + int sample_rate, + int bits_per_sample, + int samples_per_packet) { + if ((channels > kMaxInputChannels) || (channels <= 0) || + (sample_rate > kMaxSampleRate) || (sample_rate <= 0) || + (bits_per_sample > kMaxBitsPerSample) || (bits_per_sample <= 0) || + (samples_per_packet > kMaxSamplesPerPacket) || (samples_per_packet < 0)) + return NULL; + + scoped_refptr<AudioInputController> controller = new AudioInputController( + event_handler); + + // Start the thread and post a task to create the audio input stream. + controller->thread_.Start(); + controller->thread_.message_loop()->PostTask( + FROM_HERE, + NewRunnableMethod(controller.get(), &AudioInputController::DoCreate, + format, channels, sample_rate, bits_per_sample, + samples_per_packet)); + return controller; +} + +void AudioInputController::Record() { + DCHECK(thread_.IsRunning()); + thread_.message_loop()->PostTask( + FROM_HERE, + NewRunnableMethod(this, &AudioInputController::DoRecord)); +} + +void AudioInputController::Close() { + if (!thread_.IsRunning()) { + // If the thread is not running make sure we are stopped. + DCHECK_EQ(kClosed, state_); + return; + } + + // Wait for all tasks to complete on the audio thread. + thread_.message_loop()->PostTask( + FROM_HERE, + NewRunnableMethod(this, &AudioInputController::DoClose)); + thread_.Stop(); +} + +void AudioInputController::DoCreate(AudioManager::Format format, int channels, + int sample_rate, int bits_per_sample, + uint32 samples_per_packet) { + stream_ = AudioManager::GetAudioManager()->MakeAudioInputStream( + format, channels, sample_rate, bits_per_sample, samples_per_packet); + + if (!stream_) { + // TODO(satish): Define error types. + handler_->OnError(this, 0); + return; + } + + if (stream_ && !stream_->Open()) { + stream_->Close(); + stream_ = NULL; + // TODO(satish): Define error types. + handler_->OnError(this, 0); + return; + } + + state_ = kCreated; + handler_->OnCreated(this); +} + +void AudioInputController::DoRecord() { + DCHECK_EQ(thread_.message_loop(), MessageLoop::current()); + + if (state_ != kCreated) + return; + + { + AutoLock auto_lock(lock_); + state_ = kRecording; + } + + stream_->Start(this); + handler_->OnRecording(this); +} + +void AudioInputController::DoClose() { + DCHECK_EQ(thread_.message_loop(), MessageLoop::current()); + DCHECK_NE(kClosed, state_); + + // |stream_| can be null if creating the device failed in DoCreate(). + if (stream_) { + stream_->Stop(); + stream_->Close(); + // After stream is closed it is destroyed, so don't keep a reference to it. + stream_ = NULL; + } + + // Since the stream is closed at this point there's no other threads reading + // |state_| so we don't need to lock. + state_ = kClosed; +} + +void AudioInputController::DoReportError(int code) { + DCHECK_EQ(thread_.message_loop(), MessageLoop::current()); + handler_->OnError(this, code); +} + +void AudioInputController::OnData(AudioInputStream* stream, const uint8* data, + uint32 size) { + { + AutoLock auto_lock(lock_); + if (state_ != kRecording) + return; + } + handler_->OnData(this, data, size); +} + +void AudioInputController::OnClose(AudioInputStream* stream) { +} + +void AudioInputController::OnError(AudioInputStream* stream, int code) { + // Handle error on the audio controller thread. + thread_.message_loop()->PostTask( + FROM_HERE, + NewRunnableMethod(this, &AudioInputController::DoReportError, code)); +} + +} // namespace media diff --git a/media/audio/audio_input_controller.h b/media/audio/audio_input_controller.h new file mode 100644 index 0000000..27b3a54 --- /dev/null +++ b/media/audio/audio_input_controller.h @@ -0,0 +1,121 @@ +// Copyright (c) 2010 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 MEDIA_AUDIO_AUDIO_INPUT_CONTROLLER_H_ +#define MEDIA_AUDIO_AUDIO_INPUT_CONTROLLER_H_ + +#include "base/lock.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "base/thread.h" +#include "media/audio/audio_io.h" + +// An AudioInputController controls an AudioInputStream and records data +// from this input stream. It has an important function that it executes +// audio operations like record, pause, stop, etc. on a separate thread. +// +// All the public methods of AudioInputController are non-blocking except +// close, the actual operations are performed on the audio input controller +// thread. +// +// Here is a state diagram for the AudioInputController: +// +// .--> [ Closed / Error ] <--. +// | | +// | | +// [ Created ] ----------> [ Recording ] +// ^ +// | +// *[ Empty ] +// +// * Initial state +// +namespace media { + +class AudioInputController : + public base::RefCountedThreadSafe<AudioInputController>, + public AudioInputStream::AudioInputCallback { + public: + // An event handler that receives events from the AudioInputController. The + // following methods are called on the audio input controller thread. + class EventHandler { + public: + virtual ~EventHandler() {} + virtual void OnCreated(AudioInputController* controller) = 0; + virtual void OnRecording(AudioInputController* controller) = 0; + virtual void OnError(AudioInputController* controller, int error_code) = 0; + virtual void OnData(AudioInputController* controller, const uint8* data, + uint32 size) = 0; + }; + + virtual ~AudioInputController(); + + // Factory method for creating an AudioInputController. + // If successful, an audio input controller thread is created. The audio + // device will be created on the new thread and when that is done event + // handler will receive a OnCreated() call. + static scoped_refptr<AudioInputController> Create( + EventHandler* event_handler, + AudioManager::Format format, // Format of the stream. + int channels, // Number of channels. + int sample_rate, // Sampling frequency/rate. + int bits_per_sample, // Number of bits per sample. + int samples_per_packet); // Size of the hardware buffer. + + // Starts recording in this audio input stream. + void Record(); + + // Closes the audio input stream and shutdown the audio input controller + // thread. This method returns only after all operations are completed. This + // input controller cannot be used after this method is called. + // + // It is safe to call this method more than once. Calls after the first one + // will have no effect. + void Close(); + + /////////////////////////////////////////////////////////////////////////// + // AudioInputCallback methods. + virtual void OnData(AudioInputStream* stream, const uint8* src, uint32 size); + virtual void OnClose(AudioInputStream* stream); + virtual void OnError(AudioInputStream* stream, int code); + + private: + // Internal state of the source. + enum State { + kEmpty, + kCreated, + kRecording, + kClosed, + kError + }; + + AudioInputController(EventHandler* handler); + + // The following methods are executed on the audio controller thread. + void DoCreate(AudioManager::Format format, int channels, + int sample_rate, int bits_per_sample, + uint32 samples_per_packet); + void DoRecord(); + void DoClose(); + void DoReportError(int code); + + EventHandler* handler_; + AudioInputStream* stream_; + + // |state_| is written on the audio input controller thread and is read on + // the hardware audio thread. These operations need to be locked. But lock + // is not required for reading on the audio input controller thread. + State state_; + + Lock lock_; + + // The audio input controller thread that this object runs on. + base::Thread thread_; + + DISALLOW_COPY_AND_ASSIGN(AudioInputController); +}; + +} // namespace media + +#endif // MEDIA_AUDIO_AUDIO_INPUT_CONTROLLER_H_ diff --git a/media/audio/audio_input_controller_unittest.cc b/media/audio/audio_input_controller_unittest.cc new file mode 100644 index 0000000..5242398 --- /dev/null +++ b/media/audio/audio_input_controller_unittest.cc @@ -0,0 +1,128 @@ +// Copyright (c) 2010 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/env_var.h" +#include "base/basictypes.h" +#include "base/waitable_event.h" +#include "media/audio/audio_input_controller.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::AtLeast; +using ::testing::Exactly; +using ::testing::InvokeWithoutArgs; +using ::testing::NotNull; + +namespace { + +const int kSampleRate = AudioManager::kAudioCDSampleRate; +const int kBitsPerSample = 16; +const int kChannels = 2; +const int kSamplesPerPacket = kSampleRate / 10; + +ACTION_P3(CheckCountAndSignalEvent, count, limit, event) { + if (++*count >= limit) { + event->Signal(); + } +} + +// Test AudioInputController for create and close without recording audio. +} + +namespace media { + +class MockAudioInputControllerEventHandler + : public AudioInputController::EventHandler { + public: + MockAudioInputControllerEventHandler() {} + + MOCK_METHOD1(OnCreated, void(AudioInputController* controller)); + MOCK_METHOD1(OnRecording, void(AudioInputController* controller)); + MOCK_METHOD2(OnError, void(AudioInputController* controller, int error_code)); + MOCK_METHOD3(OnData, void(AudioInputController* controller, + const uint8* data, uint32 size)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockAudioInputControllerEventHandler); +}; + +TEST(AudioInputControllerTest, CreateAndClose) { + MockAudioInputControllerEventHandler event_handler; + base::WaitableEvent event(false, false); + // If OnCreated is called then signal the event. + EXPECT_CALL(event_handler, OnCreated(NotNull())) + .WillOnce(InvokeWithoutArgs(&event, &base::WaitableEvent::Signal)); + + scoped_refptr<AudioInputController> controller = AudioInputController::Create( + &event_handler, AudioManager::AUDIO_MOCK, kChannels, + kSampleRate, kBitsPerSample, kSamplesPerPacket); + ASSERT_TRUE(controller.get()); + + // Wait for OnCreated() to be called. + event.Wait(); + + controller->Close(); +} + +// Test a normal call sequence of create, record and close. +TEST(AudioInputControllerTest, RecordAndClose) { + MockAudioInputControllerEventHandler event_handler; + base::WaitableEvent event(false, false); + int count = 0; + + // If OnCreated is called then signal the event. + EXPECT_CALL(event_handler, OnCreated(NotNull())) + .WillOnce(InvokeWithoutArgs(&event, &base::WaitableEvent::Signal)); + + // OnRecording() will be called only once. + EXPECT_CALL(event_handler, OnRecording(NotNull())) + .Times(Exactly(1)); + + // If OnData is called enough then signal the event. + EXPECT_CALL(event_handler, OnData(NotNull(), NotNull(), _)) + .Times(AtLeast(10)) + .WillRepeatedly(CheckCountAndSignalEvent(&count, 10, &event)); + + scoped_refptr<AudioInputController> controller = AudioInputController::Create( + &event_handler, AudioManager::AUDIO_MOCK, kChannels, + kSampleRate, kBitsPerSample, kSamplesPerPacket); + ASSERT_TRUE(controller.get()); + + // Wait for OnCreated() to be called. + event.Wait(); + event.Reset(); + + // Play and then wait for the event to be signaled. + controller->Record(); + event.Wait(); + + controller->Close(); +} + +// Test that AudioInputController rejects insanely large packet sizes. +TEST(AudioInputControllerTest, SamplesPerPacketTooLarge) { + // Create an audio device with a very large packet size. + MockAudioInputControllerEventHandler event_handler; + + scoped_refptr<AudioInputController> controller = AudioInputController::Create( + &event_handler, AudioManager::AUDIO_MOCK, kChannels, + kSampleRate, kBitsPerSample, kSamplesPerPacket * 1000); + ASSERT_FALSE(controller); +} + +// Test calling AudioInputController::Close multiple times. +TEST(AudioInputControllerTest, CloseTwice) { + MockAudioInputControllerEventHandler event_handler; + EXPECT_CALL(event_handler, OnCreated(NotNull())); + scoped_refptr<AudioInputController> controller = AudioInputController::Create( + &event_handler, AudioManager::AUDIO_MOCK, kChannels, + kSampleRate, kBitsPerSample, kSamplesPerPacket); + ASSERT_TRUE(controller.get()); + + controller->Close(); + controller->Close(); +} + +} // namespace media diff --git a/media/media.gyp b/media/media.gyp index 554dcd1..7ef2bae 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -21,6 +21,8 @@ 'msvs_guid': '6AE76406-B03B-11DD-94B1-80B556D89593', 'sources': [ 'audio/audio_io.h', + 'audio/audio_input_controller.cc', + 'audio/audio_input_controller.h', 'audio/audio_output_controller.cc', 'audio/audio_output_controller.h', 'audio/audio_util.cc', @@ -190,6 +192,7 @@ '../third_party/openmax/omx_stub.cc', ], 'sources': [ + 'audio/audio_input_controller_unittest.cc', 'audio/audio_output_controller_unittest.cc', 'audio/audio_util_unittest.cc', 'audio/fake_audio_input_stream_unittest.cc', |