summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorsatish@chromium.org <satish@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-16 05:58:04 +0000
committersatish@chromium.org <satish@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-16 05:58:04 +0000
commit41bdde54e45a13ff9c9f34b34f8e4f0395e9183b (patch)
tree5da8b35681a91144827aa8cd38f93814b6c9e8c2 /media
parentc60d64ec89c1a8eb5279c38c101ec710c8d931f9 (diff)
downloadchromium_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.cc157
-rw-r--r--media/audio/audio_input_controller.h121
-rw-r--r--media/audio/audio_input_controller_unittest.cc128
-rw-r--r--media/media.gyp3
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',