// Copyright (c) 2012 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" #pragma comment(lib, "winmm.lib") #include "base/logging.h" #include "media/audio/audio_io.h" #include "media/audio/audio_util.h" #include "media/audio/win/audio_manager_win.h" #include "media/audio/win/device_enumeration_win.h" namespace { const int kStopInputStreamCallbackTimeout = 3000; // Three seconds. } namespace media { // 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. static WAVEHDR* GetNextBuffer(WAVEHDR* current) { return reinterpret_cast(current->dwUser); } PCMWaveInAudioInputStream::PCMWaveInAudioInputStream( AudioManagerWin* manager, const AudioParameters& params, int num_buffers, const std::string& device_id) : state_(kStateEmpty), manager_(manager), device_id_(device_id), wavein_(NULL), callback_(NULL), num_buffers_(num_buffers), buffer_(NULL), channels_(params.channels()) { DCHECK_GT(num_buffers_, 0); format_.wFormatTag = WAVE_FORMAT_PCM; format_.nChannels = params.channels() > 2 ? 2 : params.channels(); format_.nSamplesPerSec = params.sample_rate(); format_.wBitsPerSample = params.bits_per_sample(); format_.cbSize = 0; format_.nBlockAlign = (format_.nChannels * format_.wBitsPerSample) / 8; format_.nAvgBytesPerSec = format_.nBlockAlign * format_.nSamplesPerSec; buffer_size_ = params.frames_per_buffer() * 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; // Convert the stored device id string into an unsigned integer // corresponding to the selected device. UINT device_id = WAVE_MAPPER; if (!GetDeviceId(&device_id)) { return false; } // Open the specified input device for recording. MMRESULT result = MMSYSERR_NOERROR; result = ::waveInOpen(&wavein_, device_id, &format_, reinterpret_cast(WaveCallback), reinterpret_cast(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(new char[sz]); buffer_->lpData = reinterpret_cast(buffer_) + sizeof(WAVEHDR); buffer_->dwBufferLength = buffer_size_; buffer_->dwBytesRecorded = 0; buffer_->dwUser = reinterpret_cast(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(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(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; } else { manager_->IncreaseActiveInputStreamCount(); } } // 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_, kStopInputStreamCallbackTimeout)) { HandleError(::GetLastError()); return; } // Stop is always called before Close. In case of error, this will be // also called when closing the input controller. manager_->DecreaseActiveInputStreamCount(); 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); } double PCMWaveInAudioInputStream::GetMaxVolume() { // TODO(henrika): Add volume support using the Audio Mixer API. return 0.0; } void PCMWaveInAudioInputStream::SetVolume(double volume) { // TODO(henrika): Add volume support using the Audio Mixer API. } double PCMWaveInAudioInputStream::GetVolume() { // TODO(henrika): Add volume support using the Audio Mixer API. return 0.0; } void PCMWaveInAudioInputStream::SetAutomaticGainControl(bool enabled) { // TODO(henrika): Add AGC support when volume control has been added. NOTIMPLEMENTED(); } bool PCMWaveInAudioInputStream::GetAutomaticGainControl() { // TODO(henrika): Add AGC support when volume control has been added. NOTIMPLEMENTED(); return false; } 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); } bool PCMWaveInAudioInputStream::GetDeviceId(UINT* device_index) { // Deliver the default input device id (WAVE_MAPPER) if the default // device has been selected. if (device_id_ == AudioManagerBase::kDefaultDeviceId) { *device_index = WAVE_MAPPER; return true; } // Get list of all available and active devices. AudioDeviceNames device_names; if (!media::GetInputDeviceNamesWinXP(&device_names)) return false; if (device_names.empty()) return false; // Search the full list of devices and compare with the specified // device id which was specified in the constructor. Stop comparing // when a match is found and return the corresponding index. UINT index = 0; bool found_device = false; AudioDeviceNames::const_iterator it = device_names.begin(); while (it != device_names.end()) { if (it->unique_id.compare(device_id_) == 0) { *device_index = index; found_device = true; break; } ++index; ++it; } return found_device; } // 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(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. // It should be OK to assume the data in the buffer is what has been // recorded in the soundcard. // TODO(henrika): the |volume| parameter is always set to zero since there // is currently no support for controlling the microphone volume level. WAVEHDR* buffer = reinterpret_cast(param1); obj->callback_->OnData(obj, reinterpret_cast(buffer->lpData), buffer->dwBytesRecorded, buffer->dwBytesRecorded, 0.0); 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); } } } // namespace media