diff options
Diffstat (limited to 'media/audio/win/wavein_input_win.cc')
-rw-r--r-- | media/audio/win/wavein_input_win.cc | 216 |
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); + } +} |