summaryrefslogtreecommitdiffstats
path: root/media/audio/win/wavein_input_win.cc
diff options
context:
space:
mode:
Diffstat (limited to 'media/audio/win/wavein_input_win.cc')
-rw-r--r--media/audio/win/wavein_input_win.cc216
1 files changed, 216 insertions, 0 deletions
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);
+ }
+}