summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorsatish@chromium.org <satish@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-14 08:16:50 +0000
committersatish@chromium.org <satish@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-14 08:16:50 +0000
commit9fb3102a8332d0c49dc5c49733b7eb13c1d2a0b8 (patch)
treee6b0b0e3511933d9ee8405cce8f26d7e15f30319 /media
parenteee3452dafdca8a97fa73ea466b6621dcff77238 (diff)
downloadchromium_src-9fb3102a8332d0c49dc5c49733b7eb13c1d2a0b8.zip
chromium_src-9fb3102a8332d0c49dc5c49733b7eb13c1d2a0b8.tar.gz
chromium_src-9fb3102a8332d0c49dc5c49733b7eb13c1d2a0b8.tar.bz2
Add recording capability to AudioManager, and implemented on windows using the WaveIn APIs.
Implementation for other platforms will follow in future patches. Also includes a unit test. BUG=none TEST=no user visible change yet, just adding audio recording backend. Review URL: http://codereview.chromium.org/2966005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@52292 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/audio/audio_io.h68
-rw-r--r--media/audio/linux/alsa_output_unittest.cc7
-rw-r--r--media/audio/linux/audio_manager_linux.cc17
-rw-r--r--media/audio/linux/audio_manager_linux.h5
-rw-r--r--media/audio/mac/audio_manager_mac.cc17
-rw-r--r--media/audio/mac/audio_manager_mac.h7
-rw-r--r--media/audio/openbsd/audio_manager_openbsd.cc15
-rw-r--r--media/audio/openbsd/audio_manager_openbsd.h5
-rw-r--r--media/audio/win/audio_input_win_unittest.cc184
-rw-r--r--media/audio/win/audio_manager_win.cc44
-rw-r--r--media/audio/win/audio_manager_win.h11
-rw-r--r--media/audio/win/wavein_input_win.cc216
-rw-r--r--media/audio/win/wavein_input_win.h102
-rw-r--r--media/media.gyp3
14 files changed, 696 insertions, 5 deletions
diff --git a/media/audio/audio_io.h b/media/audio/audio_io.h
index 990f7c1..6e1e289 100644
--- a/media/audio/audio_io.h
+++ b/media/audio/audio_io.h
@@ -107,6 +107,52 @@ class AudioOutputStream {
virtual ~AudioOutputStream() {}
};
+// Models an audio sink receiving recorded audio from the audio driver.
+class AudioInputStream {
+ public:
+ class AudioInputCallback {
+ public:
+ virtual ~AudioInputCallback() {}
+
+ // Called by the audio recorder when a full packet of audio data is
+ // available. This is called from a special audio thread and the
+ // implementation should return as soon as possible.
+ virtual void OnData(AudioInputStream* stream, const uint8* src,
+ uint32 size) = 0;
+
+ // The stream is done with this callback, the last call received by this
+ // audio sink.
+ virtual void OnClose(AudioInputStream* stream) = 0;
+
+ // There was an error while recording audio. The audio sink cannot be
+ // destroyed yet. No direct action needed by the AudioInputStream, but it
+ // is a good place to stop accumulating sound data since is is likely that
+ // recording will not continue. |code| is an error code that is platform
+ // specific.
+ virtual void OnError(AudioInputStream* stream, int code) = 0;
+ };
+
+ // Open the stream and prepares it for recording. Call Start() to actually
+ // begin recording.
+ virtual bool Open() = 0;
+
+ // Starts recording audio and generating AudioInputCallback::OnData().
+ // The input stream does not take ownership of this callback.
+ virtual void Start(AudioInputCallback* callback) = 0;
+
+ // Stops recording audio. Effect might not be instantaneous as there could be
+ // pending audio callbacks in the queue which will be issued first before
+ // recording stops.
+ virtual void Stop() = 0;
+
+ // Close the stream. This also generates AudioInputCallback::OnClose(). This
+ // should be the last call made on this object.
+ virtual void Close() = 0;
+
+ protected:
+ virtual ~AudioInputStream() {}
+};
+
// Manages all audio resources. In particular it owns the AudioOutputStream
// objects. Provides some convenience functions that avoid the need to provide
// iterators over the existing streams.
@@ -130,6 +176,11 @@ class AudioManager {
// guarantee that the existing devices support all formats and sample rates.
virtual bool HasAudioOutputDevices() = 0;
+ // Returns true if the OS reports existence of audio recording devices. This
+ // does not guarantee that the existing devices support all formats and
+ // sample rates.
+ virtual bool HasAudioInputDevices() = 0;
+
// Factory for all the supported stream formats. The |channels| can be 1 to 5.
// The |sample_rate| is in hertz and can be any value supported by the
// platform. For some future formats the |sample_rate| and |bits_per_sample|
@@ -147,6 +198,23 @@ class AudioManager {
int sample_rate,
char bits_per_sample) = 0;
+ // Factory to create audio recording streams.
+ // |channels| can be 1 or 2.
+ // |sample_rate| is in hertz and can be any value supported by the platform.
+ // |bits_per_sample| can be any value supported by the platform.
+ // |samples_per_packet| is in hertz as well and can be 0 to |sample_rate|,
+ // with 0 suggesting that the implementation use a default value for that
+ // platform.
+ // Returns NULL if the combination of the parameters is not supported, or if
+ // we have reached some other platform specific limit.
+ //
+ // Do not free the returned AudioInputStream. It is owned by AudioManager.
+ // When you are done with it, call |Stop()| and |Close()| to release it.
+ virtual AudioInputStream* MakeAudioInputStream(Format format, int channels,
+ int sample_rate,
+ char bits_per_sample,
+ uint32 samples_per_packet) = 0;
+
// Muting continues playback but effectively the volume is set to zero.
// Un-muting returns the volume to the previous level.
virtual void MuteAll() = 0;
diff --git a/media/audio/linux/alsa_output_unittest.cc b/media/audio/linux/alsa_output_unittest.cc
index 8ae871ce4..922ecdd 100644
--- a/media/audio/linux/alsa_output_unittest.cc
+++ b/media/audio/linux/alsa_output_unittest.cc
@@ -71,10 +71,17 @@ class MockAudioManagerLinux : public AudioManagerLinux {
public:
MOCK_METHOD0(Init, void());
MOCK_METHOD0(HasAudioOutputDevices, bool());
+ MOCK_METHOD0(HasAudioInputDevices, bool());
MOCK_METHOD4(MakeAudioOutputStream, AudioOutputStream*(Format format,
int channels,
int sample_rate,
char bits_per_sample));
+ MOCK_METHOD5(MakeAudioInputStream, AudioInputStream*(
+ Format format,
+ int channels,
+ int sample_rate,
+ char bits_per_sample,
+ uint32 samples_per_packet));
MOCK_METHOD0(MuteAll, void());
MOCK_METHOD0(UnMuteAll, void());
diff --git a/media/audio/linux/audio_manager_linux.cc b/media/audio/linux/audio_manager_linux.cc
index c3cf942..6822c8b 100644
--- a/media/audio/linux/audio_manager_linux.cc
+++ b/media/audio/linux/audio_manager_linux.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -24,6 +24,21 @@ bool AudioManagerLinux::HasAudioOutputDevices() {
return true;
}
+bool AudioManagerLinux::HasAudioInputDevices() {
+ // TODO(satish): implement.
+ return false;
+}
+
+AudioInputStream* AudioManagerLinux::MakeAudioInputStream(
+ Format format,
+ int channels,
+ int sample_rate,
+ char bits_per_sample,
+ uint32 samples_per_packet) {
+ // TODO(satish): implement.
+ return NULL;
+}
+
AudioOutputStream* AudioManagerLinux::MakeAudioOutputStream(
Format format,
int channels,
diff --git a/media/audio/linux/audio_manager_linux.h b/media/audio/linux/audio_manager_linux.h
index d9229585..ea335f0 100644
--- a/media/audio/linux/audio_manager_linux.h
+++ b/media/audio/linux/audio_manager_linux.h
@@ -25,9 +25,14 @@ class AudioManagerLinux : public AudioManager {
// Implementation of AudioManager.
virtual bool HasAudioOutputDevices();
+ virtual bool HasAudioInputDevices();
virtual AudioOutputStream* MakeAudioOutputStream(Format format, int channels,
int sample_rate,
char bits_per_sample);
+ virtual AudioInputStream* MakeAudioInputStream(Format format, int channels,
+ int sample_rate,
+ char bits_per_sample,
+ uint32 samples_per_packet);
virtual void MuteAll();
virtual void UnMuteAll();
diff --git a/media/audio/mac/audio_manager_mac.cc b/media/audio/mac/audio_manager_mac.cc
index 2cc5b39..84f76de 100644
--- a/media/audio/mac/audio_manager_mac.cc
+++ b/media/audio/mac/audio_manager_mac.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -27,6 +27,21 @@ bool AudioManagerMac::HasAudioOutputDevices() {
output_device_id != kAudioObjectUnknown;
}
+bool AudioManagerMac::HasAudioInputDevices() {
+ // TODO(satish): implement.
+ return false;
+}
+
+AudioInputStream* AudioManagerMac::MakeAudioInputStream(
+ Format format,
+ int channels,
+ int sample_rate,
+ char bits_per_sample,
+ uint32 samples_per_packet) {
+ // TODO(satish): implement.
+ return NULL;
+}
+
AudioOutputStream* AudioManagerMac::MakeAudioOutputStream(
Format format,
int channels,
diff --git a/media/audio/mac/audio_manager_mac.h b/media/audio/mac/audio_manager_mac.h
index 492481d..c05e092 100644
--- a/media/audio/mac/audio_manager_mac.h
+++ b/media/audio/mac/audio_manager_mac.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// 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.
@@ -19,9 +19,14 @@ class AudioManagerMac : public AudioManager {
// Implementation of AudioManager.
virtual bool HasAudioOutputDevices();
+ virtual bool HasAudioInputDevices();
virtual AudioOutputStream* MakeAudioOutputStream(Format format, int channels,
int sample_rate,
char bits_per_sample);
+ virtual AudioInputStream* MakeAudioInputStream(Format format, int channels,
+ int sample_rate,
+ char bits_per_sample,
+ uint32 samples_per_packet);
virtual void MuteAll();
virtual void UnMuteAll();
diff --git a/media/audio/openbsd/audio_manager_openbsd.cc b/media/audio/openbsd/audio_manager_openbsd.cc
index 641b181..7d7a249 100644
--- a/media/audio/openbsd/audio_manager_openbsd.cc
+++ b/media/audio/openbsd/audio_manager_openbsd.cc
@@ -17,6 +17,21 @@ bool AudioManagerOpenBSD::HasAudioOutputDevices() {
return false;
}
+bool AudioManagerOpenBSD::HasAudioInputDevices() {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+AudioInputStream* AudioManagerOpenBSD::MakeAudioInputStream(
+ Format format,
+ int channels,
+ int sample_rate,
+ char bits_per_sample,
+ uint32 samples_per_packet) {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
AudioOutputStream* AudioManagerOpenBSD::MakeAudioOutputStream(
Format format,
int channels,
diff --git a/media/audio/openbsd/audio_manager_openbsd.h b/media/audio/openbsd/audio_manager_openbsd.h
index ffa352d..ddbce23 100644
--- a/media/audio/openbsd/audio_manager_openbsd.h
+++ b/media/audio/openbsd/audio_manager_openbsd.h
@@ -16,9 +16,14 @@ class AudioManagerOpenBSD : public AudioManager {
// Implementation of AudioManager.
virtual bool HasAudioOutputDevices();
+ virtual bool HasAudioInputDevices();
virtual AudioOutputStream* MakeAudioOutputStream(Format format, int channels,
int sample_rate,
char bits_per_sample);
+ virtual AudioInputStream* MakeAudioInputStream(Format format, int channels,
+ int sample_rate,
+ char bits_per_sample,
+ uint32 samples_per_packet);
virtual void MuteAll();
virtual void UnMuteAll();
diff --git a/media/audio/win/audio_input_win_unittest.cc b/media/audio/win/audio_input_win_unittest.cc
new file mode 100644
index 0000000..311f38f7
--- /dev/null
+++ b/media/audio/win/audio_input_win_unittest.cc
@@ -0,0 +1,184 @@
+// 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 <windows.h>
+
+#include "base/basictypes.h"
+#include "media/audio/audio_io.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// This class allows to find out if the callbacks are occurring as
+// expected and if any error has been reported.
+class TestInputCallback : public AudioInputStream::AudioInputCallback {
+ public:
+ explicit TestInputCallback(int max_data_bytes)
+ : callback_count_(0),
+ had_error_(0),
+ was_closed_(0),
+ max_data_bytes_(max_data_bytes) {
+ }
+ virtual void OnData(AudioInputStream* stream, const uint8* data,
+ uint32 size) {
+ ++callback_count_;
+ // Read the first byte to make sure memory is good.
+ if (size) {
+ ASSERT_LE(static_cast<int>(size), max_data_bytes_);
+ EXPECT_TRUE(data[0] >= 0);
+ }
+ }
+ virtual void OnClose(AudioInputStream* stream) {
+ ++was_closed_;
+ }
+ virtual void OnError(AudioInputStream* stream, int code) {
+ ++had_error_;
+ }
+ // Returns how many times OnData() has been called.
+ int callback_count() const {
+ return callback_count_;
+ }
+ // Returns how many times the OnError callback was called.
+ int had_error() const {
+ return had_error_;
+ }
+
+ void set_error(bool error) {
+ had_error_ += error ? 1 : 0;
+ }
+ // Returns how many times the OnClose callback was called.
+ int was_closed() const {
+ return was_closed_;
+ }
+
+ private:
+ int callback_count_;
+ int had_error_;
+ int was_closed_;
+ int max_data_bytes_;
+};
+
+// Specializes TestInputCallback to simulate a sink that blocks for some time
+// in the OnData callback.
+class TestInputCallbackBlocking : public TestInputCallback {
+ public:
+ TestInputCallbackBlocking(int max_data_bytes, int block_after_callback,
+ int block_for_ms)
+ : TestInputCallback(max_data_bytes),
+ block_after_callback_(block_after_callback),
+ block_for_ms_(block_for_ms) {
+ }
+ virtual void OnData(AudioInputStream* stream, const uint8* data,
+ uint32 size) {
+ // Call the base, which increments the callback_count_.
+ TestInputCallback::OnData(stream, data, size);
+ if (callback_count() > block_after_callback_)
+ ::Sleep(block_for_ms_);
+ }
+
+ private:
+ int block_after_callback_;
+ int block_for_ms_;
+};
+
+bool CanRunAudioTests() {
+ AudioManager* audio_man = AudioManager::GetAudioManager();
+ if (NULL == audio_man)
+ return false;
+ return (audio_man->HasAudioInputDevices() &&
+ ::GetEnvironmentVariableW(L"CHROME_HEADLESS", NULL, 0) == 0);
+}
+
+} // namespace.
+
+// Test that AudioInputStream rejects out of range parameters.
+TEST(WinAudioInputTest, SanityOnMakeParams) {
+ if (!CanRunAudioTests())
+ return;
+ AudioManager* audio_man = AudioManager::GetAudioManager();
+ AudioManager::Format fmt = AudioManager::AUDIO_PCM_LINEAR;
+ EXPECT_TRUE(NULL == audio_man->MakeAudioInputStream(fmt, 8, 8000, 16, 0));
+ EXPECT_TRUE(NULL == audio_man->MakeAudioInputStream(fmt, 1, 1024 * 1024, 16,
+ 0));
+ EXPECT_TRUE(NULL == audio_man->MakeAudioInputStream(fmt, 2, 8000, 80, 0));
+ EXPECT_TRUE(NULL == audio_man->MakeAudioInputStream(fmt, 2, 8000, 80,
+ 1024 * 4096));
+ EXPECT_TRUE(NULL == audio_man->MakeAudioInputStream(fmt, -2, 8000, 16, 0));
+ EXPECT_TRUE(NULL == audio_man->MakeAudioInputStream(fmt, 2, -8000, 16, 0));
+ EXPECT_TRUE(NULL == audio_man->MakeAudioInputStream(fmt, 2, 8000, -16, 0));
+ EXPECT_TRUE(NULL == audio_man->MakeAudioInputStream(fmt, 2, 8000, 16, -1024));
+}
+
+// Test create and close of an AudioInputStream without recording audio.
+TEST(WinAudioInputTest, CreateAndClose) {
+ if (!CanRunAudioTests())
+ return;
+ AudioManager* audio_man = AudioManager::GetAudioManager();
+ AudioInputStream* ais = audio_man->MakeAudioInputStream(
+ AudioManager::AUDIO_PCM_LINEAR, 2, 8000, 16, 0);
+ ASSERT_TRUE(NULL != ais);
+ ais->Close();
+}
+
+// Test create, open and close of an AudioInputStream without recording audio.
+TEST(WinAudioInputTest, OpenAndClose) {
+ if (!CanRunAudioTests())
+ return;
+ AudioManager* audio_man = AudioManager::GetAudioManager();
+ AudioInputStream* ais = audio_man->MakeAudioInputStream(
+ AudioManager::AUDIO_PCM_LINEAR, 2, 8000, 16, 0);
+ ASSERT_TRUE(NULL != ais);
+ EXPECT_TRUE(ais->Open());
+ ais->Close();
+}
+
+// Test a normal recording sequence using an AudioInputStream.
+TEST(WinAudioInputTest, Record) {
+ if (!CanRunAudioTests())
+ return;
+ AudioManager* audio_man = AudioManager::GetAudioManager();
+ const int kSamplingRate = 8000;
+ const int kSamplesPerPacket = kSamplingRate / 20;
+ AudioInputStream* ais = audio_man->MakeAudioInputStream(
+ AudioManager::AUDIO_PCM_LINEAR, 2, kSamplingRate, 16, kSamplesPerPacket);
+ ASSERT_TRUE(NULL != ais);
+ EXPECT_TRUE(ais->Open());
+
+ TestInputCallback test_callback(kSamplesPerPacket * 4);
+ ais->Start(&test_callback);
+ // Verify at least 500ms worth of audio was recorded, after giving sufficient
+ // extra time.
+ Sleep(590);
+ EXPECT_GE(test_callback.callback_count(), 10);
+ EXPECT_FALSE(test_callback.had_error());
+
+ ais->Stop();
+ ais->Close();
+}
+
+// Test a recording sequence with delays in the audio callback.
+TEST(WinAudioInputTest, RecordWithSlowSink) {
+ if (!CanRunAudioTests())
+ return;
+ AudioManager* audio_man = AudioManager::GetAudioManager();
+ const int kSamplingRate = 8000;
+ const int kSamplesPerPacket = kSamplingRate / 20;
+ AudioInputStream* ais = audio_man->MakeAudioInputStream(
+ AudioManager::AUDIO_PCM_LINEAR, 2, kSamplingRate, 16, kSamplesPerPacket);
+ ASSERT_TRUE(NULL != ais);
+ EXPECT_TRUE(ais->Open());
+
+ // We should normally get a callback every 50ms, and a 20ms delay inside each
+ // callback should not change this sequence.
+ TestInputCallbackBlocking test_callback(kSamplesPerPacket * 4, 0, 20);
+ ais->Start(&test_callback);
+ // Verify at least 500ms worth of audio was recorded, after giving sufficient
+ // extra time.
+ Sleep(590);
+ EXPECT_GE(test_callback.callback_count(), 10);
+ EXPECT_FALSE(test_callback.had_error());
+
+ ais->Stop();
+ ais->Close();
+}
diff --git a/media/audio/win/audio_manager_win.cc b/media/audio/win/audio_manager_win.cc
index 81a59f5..0c42015 100644
--- a/media/audio/win/audio_manager_win.cc
+++ b/media/audio/win/audio_manager_win.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -11,6 +11,7 @@
#include "base/basictypes.h"
#include "media/audio/fake_audio_output_stream.h"
#include "media/audio/win/audio_manager_win.h"
+#include "media/audio/win/wavein_input_win.h"
#include "media/audio/win/waveout_output_win.h"
namespace {
@@ -31,6 +32,15 @@ const int kMaxChannels = 6;
const int kMaxSampleRate = 192000;
const int kMaxBitsPerSample = 64;
+const int kMaxInputChannels = 2;
+const int kMaxSamplesPerPacket = kMaxSampleRate;
+// We use 3 buffers for recording audio so that if a recording callback takes
+// some time to return we won't lose audio. More buffers while recording are
+// ok because they don't introduce any delay in recording, unlike in playback
+// where you first need to fill in that number of buffers before starting to
+// play.
+const int kNumInputBuffers = 3;
+
AudioManagerWin* g_audio_manager = NULL;
} // namespace.
@@ -39,6 +49,10 @@ bool AudioManagerWin::HasAudioOutputDevices() {
return (::waveOutGetNumDevs() != 0);
}
+bool AudioManagerWin::HasAudioInputDevices() {
+ return (::waveInGetNumDevs() != 0);
+}
+
// Factory for the implementations of AudioOutputStream. Two implementations
// should suffice most windows user's needs.
// - PCMWaveOutAudioOutputStream: Based on the waveOutWrite API (in progress)
@@ -66,11 +80,39 @@ AudioOutputStream* AudioManagerWin::MakeAudioOutputStream(
return NULL;
}
+// Factory for the implementations of AudioInputStream.
+AudioInputStream* AudioManagerWin::MakeAudioInputStream(
+ Format format,
+ int channels,
+ int sample_rate,
+ char bits_per_sample,
+ uint32 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;
+
+ if (format == AUDIO_MOCK) {
+ // TODO(satish): Add mock audio input stream.
+ } else if (format == AUDIO_PCM_LINEAR) {
+ return new PCMWaveInAudioInputStream(this, channels, sample_rate,
+ kNumInputBuffers, bits_per_sample,
+ samples_per_packet, WAVE_MAPPER);
+ }
+ return NULL;
+}
+
void AudioManagerWin::ReleaseOutputStream(PCMWaveOutAudioOutputStream* stream) {
if (stream)
delete stream;
}
+void AudioManagerWin::ReleaseInputStream(PCMWaveInAudioInputStream* stream) {
+ if (stream)
+ delete stream;
+}
+
void AudioManagerWin::MuteAll() {
}
diff --git a/media/audio/win/audio_manager_win.h b/media/audio/win/audio_manager_win.h
index df6ab10..21a4014 100644
--- a/media/audio/win/audio_manager_win.h
+++ b/media/audio/win/audio_manager_win.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-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.
@@ -10,6 +10,7 @@
#include "base/basictypes.h"
#include "media/audio/audio_io.h"
+class PCMWaveInAudioInputStream;
class PCMWaveOutAudioOutputStream;
// Windows implementation of the AudioManager singleton. This class is internal
@@ -20,9 +21,14 @@ class AudioManagerWin : public AudioManager {
AudioManagerWin() {}
// Implementation of AudioManager.
virtual bool HasAudioOutputDevices();
+ virtual bool HasAudioInputDevices();
virtual AudioOutputStream* MakeAudioOutputStream(Format format, int channels,
int sample_rate,
char bits_per_sample);
+ virtual AudioInputStream* MakeAudioInputStream(Format format, int channels,
+ int sample_rate,
+ char bits_per_sample,
+ uint32 samples_per_packet);
virtual void MuteAll();
virtual void UnMuteAll();
@@ -30,6 +36,9 @@ class AudioManagerWin : public AudioManager {
// are called internally by the audio stream when it has been closed.
void ReleaseOutputStream(PCMWaveOutAudioOutputStream* stream);
+ // Called internally by the audio stream when it has been closed.
+ void ReleaseInputStream(PCMWaveInAudioInputStream* stream);
+
private:
friend void DestroyAudioManagerWin(void *);
virtual ~AudioManagerWin();
diff --git a/media/audio/win/wavein_input_win.cc b/media/audio/win/wavein_input_win.cc
new file mode 100644
index 0000000..58253c3
--- /dev/null
+++ b/media/audio/win/wavein_input_win.cc
@@ -0,0 +1,216 @@
+// 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/win/wavein_input_win.h"
+
+#include <windows.h>
+#include <mmsystem.h>
+#pragma comment(lib, "winmm.lib")
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_util.h"
+#include "media/audio/win/audio_manager_win.h"
+
+namespace {
+
+// Our sound buffers are allocated once and kept in a linked list using the
+// the WAVEHDR::dwUser variable. The last buffer points to the first buffer.
+WAVEHDR* GetNextBuffer(WAVEHDR* current) {
+ return reinterpret_cast<WAVEHDR*>(current->dwUser);
+}
+
+} // namespace
+
+PCMWaveInAudioInputStream::PCMWaveInAudioInputStream(
+ AudioManagerWin* manager, int channels, int sampling_rate, int num_buffers,
+ char bits_per_sample, uint32 samples_per_packet, UINT device_id)
+ : state_(kStateEmpty),
+ manager_(manager),
+ device_id_(device_id),
+ wavein_(NULL),
+ callback_(NULL),
+ num_buffers_(num_buffers),
+ buffer_(NULL),
+ channels_(channels) {
+ format_.wFormatTag = WAVE_FORMAT_PCM;
+ format_.nChannels = channels > 2 ? 2 : channels;
+ format_.nSamplesPerSec = sampling_rate;
+ format_.wBitsPerSample = bits_per_sample;
+ format_.cbSize = 0;
+ format_.nBlockAlign = (format_.nChannels * format_.wBitsPerSample) / 8;
+ format_.nAvgBytesPerSec = format_.nBlockAlign * format_.nSamplesPerSec;
+ buffer_size_ = samples_per_packet * format_.nBlockAlign;
+ // If we don't have a packet size we use 100ms.
+ if (!buffer_size_)
+ buffer_size_ = format_.nAvgBytesPerSec / 10;
+ // The event is auto-reset.
+ stopped_event_.Set(::CreateEventW(NULL, FALSE, FALSE, NULL));
+}
+
+PCMWaveInAudioInputStream::~PCMWaveInAudioInputStream() {
+ DCHECK(NULL == wavein_);
+}
+
+bool PCMWaveInAudioInputStream::Open() {
+ if (state_ != kStateEmpty)
+ return false;
+ if (num_buffers_ < 2 || num_buffers_ > 10)
+ return false;
+ MMRESULT result = ::waveInOpen(&wavein_, device_id_, &format_,
+ reinterpret_cast<DWORD_PTR>(WaveCallback),
+ reinterpret_cast<DWORD_PTR>(this),
+ CALLBACK_FUNCTION);
+ if (result != MMSYSERR_NOERROR)
+ return false;
+
+ SetupBuffers();
+ state_ = kStateReady;
+ return true;
+}
+
+void PCMWaveInAudioInputStream::SetupBuffers() {
+ WAVEHDR* last = NULL;
+ WAVEHDR* first = NULL;
+ for (int ix = 0; ix != num_buffers_; ++ix) {
+ uint32 sz = sizeof(WAVEHDR) + buffer_size_;
+ buffer_ = reinterpret_cast<WAVEHDR*>(new char[sz]);
+ buffer_->lpData = reinterpret_cast<char*>(buffer_) + sizeof(WAVEHDR);
+ buffer_->dwBufferLength = buffer_size_;
+ buffer_->dwBytesRecorded = 0;
+ buffer_->dwUser = reinterpret_cast<DWORD_PTR>(last);
+ buffer_->dwFlags = WHDR_DONE;
+ buffer_->dwLoops = 0;
+ if (ix == 0)
+ first = buffer_;
+ last = buffer_;
+ ::waveInPrepareHeader(wavein_, buffer_, sizeof(WAVEHDR));
+ }
+ // Fix the first buffer to point to the last one.
+ first->dwUser = reinterpret_cast<DWORD_PTR>(last);
+}
+
+void PCMWaveInAudioInputStream::FreeBuffers() {
+ WAVEHDR* current = buffer_;
+ for (int ix = 0; ix != num_buffers_; ++ix) {
+ WAVEHDR* next = GetNextBuffer(current);
+ if (current->dwFlags & WHDR_PREPARED)
+ ::waveInUnprepareHeader(wavein_, current, sizeof(WAVEHDR));
+ delete[] reinterpret_cast<char*>(current);
+ current = next;
+ }
+ buffer_ = NULL;
+}
+
+void PCMWaveInAudioInputStream::Start(AudioInputCallback* callback) {
+ if (state_ != kStateReady)
+ return;
+
+ callback_ = callback;
+ state_ = kStateRecording;
+
+ WAVEHDR* buffer = buffer_;
+ for (int ix = 0; ix != num_buffers_; ++ix) {
+ QueueNextPacket(buffer);
+ buffer = GetNextBuffer(buffer);
+ }
+ buffer = buffer_;
+
+ MMRESULT result = ::waveInStart(wavein_);
+ if (result != MMSYSERR_NOERROR) {
+ HandleError(result);
+ state_ = kStateReady;
+ }
+}
+
+// Stopping is tricky. First, no buffer should be locked by the audio driver
+// or else the waveInReset() will deadlock and secondly, the callback should
+// not be inside the AudioInputCallback's OnData because waveInReset()
+// forcefully kills the callback thread.
+void PCMWaveInAudioInputStream::Stop() {
+ if (state_ != kStateRecording)
+ return;
+ state_ = kStateStopping;
+ // Wait for the callback to finish, it will signal us when ready to be reset.
+ if (WAIT_OBJECT_0 != ::WaitForSingleObject(stopped_event_, INFINITE)) {
+ HandleError(::GetLastError());
+ return;
+ }
+ state_ = kStateStopped;
+ MMRESULT res = ::waveInReset(wavein_);
+ if (res != MMSYSERR_NOERROR) {
+ state_ = kStateRecording;
+ HandleError(res);
+ return;
+ }
+ state_ = kStateReady;
+}
+
+// We can Close in any state except that when trying to close a stream that is
+// recording Windows generates an error, which we propagate to the source.
+void PCMWaveInAudioInputStream::Close() {
+ if (wavein_) {
+ // waveInClose generates a callback with WIM_CLOSE id in the same thread.
+ MMRESULT res = ::waveInClose(wavein_);
+ if (res != MMSYSERR_NOERROR) {
+ HandleError(res);
+ return;
+ }
+ state_ = kStateClosed;
+ wavein_ = NULL;
+ FreeBuffers();
+ }
+ // Tell the audio manager that we have been released. This can result in
+ // the manager destroying us in-place so this needs to be the last thing
+ // we do on this function.
+ manager_->ReleaseInputStream(this);
+}
+
+void PCMWaveInAudioInputStream::HandleError(MMRESULT error) {
+ DLOG(WARNING) << "PCMWaveInAudio error " << error;
+ callback_->OnError(this, error);
+}
+
+void PCMWaveInAudioInputStream::QueueNextPacket(WAVEHDR *buffer) {
+ MMRESULT res = ::waveInAddBuffer(wavein_, buffer, sizeof(WAVEHDR));
+ if (res != MMSYSERR_NOERROR)
+ HandleError(res);
+}
+
+// Windows calls us back in this function when some events happen. Most notably
+// when it has an audio buffer with recorded data.
+void PCMWaveInAudioInputStream::WaveCallback(HWAVEIN hwi, UINT msg,
+ DWORD_PTR instance,
+ DWORD_PTR param1, DWORD_PTR) {
+ PCMWaveInAudioInputStream* obj =
+ reinterpret_cast<PCMWaveInAudioInputStream*>(instance);
+
+ if (msg == WIM_DATA) {
+ // WIM_DONE indicates that the driver is done with our buffer. We pass it
+ // to the callback and check if we need to stop playing.
+ WAVEHDR* buffer = reinterpret_cast<WAVEHDR*>(param1);
+ obj->callback_->OnData(obj, reinterpret_cast<const uint8*>(buffer->lpData),
+ buffer->dwBytesRecorded);
+
+ if (obj->state_ == kStateStopping) {
+ // The main thread has called Stop() and is waiting to issue waveOutReset
+ // which will kill this thread. We should not enter AudioSourceCallback
+ // code anymore.
+ ::SetEvent(obj->stopped_event_);
+ } else if (obj->state_ == kStateStopped) {
+ // Not sure if ever hit this but just in case.
+ } else {
+ // Queue the finished buffer back with the audio driver. Since we are
+ // reusing the same buffers we can get away without calling
+ // waveInPrepareHeader.
+ obj->QueueNextPacket(buffer);
+ }
+ } else if (msg == WIM_CLOSE) {
+ // We can be closed before calling Start, so it is possible to have a
+ // null callback at this point.
+ if (obj->callback_)
+ obj->callback_->OnClose(obj);
+ }
+}
diff --git a/media/audio/win/wavein_input_win.h b/media/audio/win/wavein_input_win.h
new file mode 100644
index 0000000..a4ea435
--- /dev/null
+++ b/media/audio/win/wavein_input_win.h
@@ -0,0 +1,102 @@
+// 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_WIN_WAVEIN_INPUT_WIN_H_
+#define MEDIA_AUDIO_WIN_WAVEIN_INPUT_WIN_H_
+
+#include <windows.h>
+#include <mmsystem.h>
+
+#include "base/basictypes.h"
+#include "base/scoped_handle_win.h"
+#include "media/audio/audio_io.h"
+
+class AudioManagerWin;
+
+class PCMWaveInAudioInputStream : public AudioInputStream {
+ public:
+ // The ctor takes all the usual parameters, plus |manager| which is the
+ // the audio manager who is creating this object and |device_id| which
+ // is provided by the operating system.
+ PCMWaveInAudioInputStream(AudioManagerWin* manager, int channels,
+ int sampling_rate, int num_buffers,
+ char bits_per_sample, uint32 samples_per_packet,
+ UINT device_id);
+ virtual ~PCMWaveInAudioInputStream();
+
+ // Implementation of AudioInputStream.
+ virtual bool Open();
+ virtual void Start(AudioInputCallback* callback);
+ virtual void Stop();
+ virtual void Close();
+
+ private:
+ enum State {
+ kStateEmpty, // Initial state.
+ kStateReady, // Device obtained and ready to record.
+ kStateRecording, // Recording audio.
+ kStateStopping, // Trying to stop, waiting for callback to finish.
+ kStateStopped, // Stopped. Device was reset.
+ kStateClosed // Device has been released.
+ };
+
+ // Windows calls us back with the recorded audio data here. See msdn
+ // documentation for 'waveInProc' for details about the parameters.
+ static void CALLBACK WaveCallback(HWAVEIN hwi, UINT msg, DWORD_PTR instance,
+ DWORD_PTR param1, DWORD_PTR param2);
+
+ // If windows reports an error this function handles it and passes it to
+ // the attached AudioInputCallback::OnError().
+ void HandleError(MMRESULT error);
+
+ // Allocates and prepares the memory that will be used for recording.
+ void SetupBuffers();
+
+ // Deallocates the memory allocated in SetupBuffers.
+ void FreeBuffers();
+
+ // Sends a buffer to the audio driver for recording.
+ void QueueNextPacket(WAVEHDR* buffer);
+
+ // Reader beware. Visual C has stronger guarantees on volatile vars than
+ // most people expect. In fact, it has release semantics on write and
+ // acquire semantics on reads. See the msdn documentation.
+ volatile State state_;
+
+ // The audio manager that created this input stream. We notify it when
+ // we close so it can release its own resources.
+ AudioManagerWin* manager_;
+
+ // We use the callback mostly to periodically give the recorded audio data.
+ AudioInputCallback* callback_;
+
+ // The number of buffers of size |buffer_size_| each to use.
+ const int num_buffers_;
+
+ // The size in bytes of each audio buffer.
+ uint32 buffer_size_;
+
+ // Channels, 1 or 2.
+ const int channels_;
+
+ // The id assigned by the operating system to the selected wave output
+ // hardware device. Usually this is just -1 which means 'default device'.
+ UINT device_id_;
+
+ // Windows native structure to encode the format parameters.
+ WAVEFORMATEX format_;
+
+ // Handle to the instance of the wave device.
+ HWAVEIN wavein_;
+
+ // Pointer to the first allocated audio buffer. This object owns it.
+ WAVEHDR* buffer_;
+
+ // An event that is signaled when the callback thread is ready to stop.
+ ScopedHandle stopped_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(PCMWaveInAudioInputStream);
+};
+
+#endif // MEDIA_AUDIO_WIN_WAVEIN_INPUT_WIN_H_
diff --git a/media/media.gyp b/media/media.gyp
index 11f5d34..86966d6 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -43,6 +43,8 @@
'audio/simple_sources.h',
'audio/win/audio_manager_win.h',
'audio/win/audio_manager_win.cc',
+ 'audio/win/wavein_input_win.cc',
+ 'audio/win/wavein_input_win.h',
'audio/win/waveout_output_win.cc',
'audio/win/waveout_output_win.h',
'base/buffers.cc',
@@ -191,6 +193,7 @@
'audio/linux/alsa_output_unittest.cc',
'audio/mac/audio_output_mac_unittest.cc',
'audio/simple_sources_unittest.cc',
+ 'audio/win/audio_input_win_unittest.cc',
'audio/win/audio_output_win_unittest.cc',
'base/clock_impl_unittest.cc',
'base/data_buffer_unittest.cc',