Index: media/audio/win/wavein_input_win.cc |
diff --git a/media/audio/win/wavein_input_win.cc b/media/audio/win/wavein_input_win.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..58253c337144b9ac509a62f60f99cb43026b1842 |
--- /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); |
+ } |
+} |