diff options
author | satish@chromium.org <satish@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-14 08:16:50 +0000 |
---|---|---|
committer | satish@chromium.org <satish@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-14 08:16:50 +0000 |
commit | 9fb3102a8332d0c49dc5c49733b7eb13c1d2a0b8 (patch) | |
tree | e6b0b0e3511933d9ee8405cce8f26d7e15f30319 /media | |
parent | eee3452dafdca8a97fa73ea466b6621dcff77238 (diff) | |
download | chromium_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.h | 68 | ||||
-rw-r--r-- | media/audio/linux/alsa_output_unittest.cc | 7 | ||||
-rw-r--r-- | media/audio/linux/audio_manager_linux.cc | 17 | ||||
-rw-r--r-- | media/audio/linux/audio_manager_linux.h | 5 | ||||
-rw-r--r-- | media/audio/mac/audio_manager_mac.cc | 17 | ||||
-rw-r--r-- | media/audio/mac/audio_manager_mac.h | 7 | ||||
-rw-r--r-- | media/audio/openbsd/audio_manager_openbsd.cc | 15 | ||||
-rw-r--r-- | media/audio/openbsd/audio_manager_openbsd.h | 5 | ||||
-rw-r--r-- | media/audio/win/audio_input_win_unittest.cc | 184 | ||||
-rw-r--r-- | media/audio/win/audio_manager_win.cc | 44 | ||||
-rw-r--r-- | media/audio/win/audio_manager_win.h | 11 | ||||
-rw-r--r-- | media/audio/win/wavein_input_win.cc | 216 | ||||
-rw-r--r-- | media/audio/win/wavein_input_win.h | 102 | ||||
-rw-r--r-- | media/media.gyp | 3 |
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', |