| // 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<WAVEHDR*>(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<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; |
| } 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<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. |
| // 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<WAVEHDR*>(param1); |
| obj->callback_->OnData(obj, reinterpret_cast<const uint8*>(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 |