| 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);
|
| + }
|
| +}
|
|
|