diff options
author | henrika@chromium.org <henrika@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-16 10:30:38 +0000 |
---|---|---|
committer | henrika@chromium.org <henrika@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-16 10:30:38 +0000 |
commit | eed96f8f96ea288667839c336694e86c802e0dfd (patch) | |
tree | 9ab0499744a0defca8315a81f3b6d3237457315c /media | |
parent | 7fb108bf0d7031c410150d1b996e45246551879c (diff) | |
download | chromium_src-eed96f8f96ea288667839c336694e86c802e0dfd.zip chromium_src-eed96f8f96ea288667839c336694e86c802e0dfd.tar.gz chromium_src-eed96f8f96ea288667839c336694e86c802e0dfd.tar.bz2 |
Low-latency AudioOutputStream implementation based on WASAPI for Windows.
BUG=none
TEST=audio_low_latency_output_win_unittest.cc
Review URL: http://codereview.chromium.org/8440002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@110282 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/audio/audio_util.cc | 43 | ||||
-rw-r--r-- | media/audio/win/audio_low_latency_output_win.cc | 601 | ||||
-rw-r--r-- | media/audio/win/audio_low_latency_output_win.h | 206 | ||||
-rw-r--r-- | media/audio/win/audio_low_latency_output_win_unittest.cc | 528 | ||||
-rw-r--r-- | media/audio/win/audio_manager_win.cc | 18 | ||||
-rw-r--r-- | media/audio/win/audio_manager_win.h | 2 | ||||
-rw-r--r-- | media/media.gyp | 11 | ||||
-rw-r--r-- | media/test/data/speech_16b_stereo_44kHz.raw | bin | 0 -> 3548068 bytes | |||
-rw-r--r-- | media/test/data/speech_16b_stereo_48kHz.raw | bin | 0 -> 3861836 bytes |
9 files changed, 1388 insertions, 21 deletions
diff --git a/media/audio/audio_util.cc b/media/audio/audio_util.cc index 6f365b2..4aec30d 100644 --- a/media/audio/audio_util.cc +++ b/media/audio/audio_util.cc @@ -24,6 +24,7 @@ #endif #if defined(OS_WIN) #include "media/audio/win/audio_low_latency_input_win.h" +#include "media/audio/win/audio_low_latency_output_win.h" #endif using base::subtle::Atomic32; @@ -241,8 +242,19 @@ double GetAudioHardwareSampleRate() { #if defined(OS_MACOSX) // Hardware sample-rate on the Mac can be configured, so we must query. return AUAudioOutputStream::HardwareSampleRate(); +#elif defined(OS_WIN) + if (base::win::GetVersion() <= base::win::VERSION_XP) { + // Fall back to Windows Wave implementation on Windows XP or lower + // and use 48kHz as default input sample rate. + return 48000.0; + } + + // Hardware sample-rate on Windows can be configured, so we must query. + // TODO(henrika): improve possibility to specify audio endpoint. + // Use the default device (same as for Wave) for now to be compatible. + return WASAPIAudioOutputStream::HardwareSampleRate(eConsole); #else - // Hardware for Windows and Linux is nearly always 48KHz. + // Hardware for Linux is nearly always 48KHz. // TODO(crogers) : return correct value in rare non-48KHz cases. return 48000.0; #endif @@ -257,12 +269,12 @@ double GetAudioInputHardwareSampleRate() { // Fall back to Windows Wave implementation on Windows XP or lower // and use 48kHz as default input sample rate. return 48000.0; - } else { - // Hardware sample-rate on Windows can be configured, so we must query. - // TODO(henrika): improve possibility to specify audio endpoint. - // Use the default device (same as for Wave) for now to be compatible. - return WASAPIAudioInputStream::HardwareSampleRate(eConsole); } + + // Hardware sample-rate on Windows can be configured, so we must query. + // TODO(henrika): improve possibility to specify audio endpoint. + // Use the default device (same as for Wave) for now to be compatible. + return WASAPIAudioInputStream::HardwareSampleRate(eConsole); #else // Hardware for Linux is nearly always 48KHz. // TODO(henrika): return correct value in rare non-48KHz cases. @@ -275,13 +287,22 @@ size_t GetAudioHardwareBufferSize() { // the lowest value (for low latency) that still allowed glitch-free // audio under high loads. // - // For Mac OS X the chromium audio backend uses a low-latency - // CoreAudio API, so a low buffer size is possible. For other OSes, - // further tuning may be needed. + // For Mac OS X and Windows the chromium audio backend uses a low-latency + // Core Audio API, so a low buffer size is possible. For Linux, further + // tuning may be needed. #if defined(OS_MACOSX) return 128; -#elif defined(OS_LINUX) - return 2048; +#elif defined(OS_WIN) + // This call must be done on a COM thread configured as MTA. + // TODO(tommi): http://code.google.com/p/chromium/issues/detail?id=103835. + int mixing_sample_rate = + static_cast<int>(WASAPIAudioOutputStream::HardwareSampleRate(eConsole)); + if (mixing_sample_rate == 48000) + return 480; + else if (mixing_sample_rate == 44100) + return 448; + else + return 960; #else return 2048; #endif diff --git a/media/audio/win/audio_low_latency_output_win.cc b/media/audio/win/audio_low_latency_output_win.cc new file mode 100644 index 0000000..6877b64 --- /dev/null +++ b/media/audio/win/audio_low_latency_output_win.cc @@ -0,0 +1,601 @@ +// Copyright (c) 2011 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/audio_low_latency_output_win.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/utf_string_conversions.h" +#include "media/audio/audio_util.h" +#include "media/audio/win/audio_manager_win.h" +#include "media/audio/win/avrt_wrapper_win.h" + +using base::win::ScopedComPtr; +using base::win::ScopedCOMInitializer; + +WASAPIAudioOutputStream::WASAPIAudioOutputStream(AudioManagerWin* manager, + const AudioParameters& params, + ERole device_role) + : com_init_(ScopedCOMInitializer::kMTA), + creating_thread_id_(base::PlatformThread::CurrentId()), + manager_(manager), + render_thread_(NULL), + opened_(false), + started_(false), + volume_(1.0), + endpoint_buffer_size_frames_(0), + device_role_(device_role), + num_written_frames_(0), + source_(NULL) { + CHECK(com_init_.succeeded()); + DCHECK(manager_); + + // Load the Avrt DLL if not already loaded. Required to support MMCSS. + bool avrt_init = avrt::Initialize(); + DCHECK(avrt_init) << "Failed to load the avrt.dll"; + + // Set up the desired render format specified by the client. + format_.nSamplesPerSec = params.sample_rate; + format_.wFormatTag = WAVE_FORMAT_PCM; + format_.wBitsPerSample = params.bits_per_sample; + format_.nChannels = params.channels; + format_.nBlockAlign = (format_.wBitsPerSample / 8) * format_.nChannels; + format_.nAvgBytesPerSec = format_.nSamplesPerSec * format_.nBlockAlign; + format_.cbSize = 0; + + // Size in bytes of each audio frame. + frame_size_ = format_.nBlockAlign; + + // Store size (in different units) of audio packets which we expect to + // get from the audio endpoint device in each render event. + packet_size_frames_ = params.GetPacketSize() / format_.nBlockAlign; + packet_size_bytes_ = params.GetPacketSize(); + packet_size_ms_ = (1000.0 * packet_size_frames_) / params.sample_rate; + DVLOG(1) << "Number of bytes per audio frame : " << frame_size_; + DVLOG(1) << "Number of audio frames per packet: " << packet_size_frames_; + DVLOG(1) << "Number of milliseconds per packet: " << packet_size_ms_; + + // All events are auto-reset events and non-signaled initially. + + // Create the event which the audio engine will signal each time + // a buffer becomes ready to be processed by the client. + audio_samples_render_event_.Set(CreateEvent(NULL, FALSE, FALSE, NULL)); + DCHECK(audio_samples_render_event_.IsValid()); + + // Create the event which will be set in Stop() when capturing shall stop. + stop_render_event_.Set(CreateEvent(NULL, FALSE, FALSE, NULL)); + DCHECK(stop_render_event_.IsValid()); +} + +WASAPIAudioOutputStream::~WASAPIAudioOutputStream() {} + +bool WASAPIAudioOutputStream::Open() { + DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_); + if (opened_) + return true; + + // Obtain a reference to the IMMDevice interface of the default rendering + // device with the specified role. + HRESULT hr = SetRenderDevice(device_role_); + if (FAILED(hr)) { + HandleError(hr); + return false; + } + + // Obtain an IAudioClient interface which enables us to create and initialize + // an audio stream between an audio application and the audio engine. + hr = ActivateRenderDevice(); + if (FAILED(hr)) { + HandleError(hr); + return false; + } + + // Retrieve the stream format which the audio engine uses for its internal + // processing/mixing of shared-mode streams. + hr = GetAudioEngineStreamFormat(); + if (FAILED(hr)) { + HandleError(hr); + return false; + } + + // Verify that the selected audio endpoint supports the specified format + // set during construction. + if (!DesiredFormatIsSupported()) { + hr = E_INVALIDARG; + HandleError(hr); + return false; + } + + // Initialize the audio stream between the client and the device using + // shared mode and a lowest possible glitch-free latency. + hr = InitializeAudioEngine(); + if (FAILED(hr)) { + HandleError(hr); + return false; + } + + opened_ = true; + + return true; +} + +void WASAPIAudioOutputStream::Start(AudioSourceCallback* callback) { + DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_); + DCHECK(callback); + DCHECK(opened_); + + if (!opened_) + return; + + if (started_) + return; + + source_ = callback; + + // Avoid start-up glitches by filling up the endpoint buffer with "silence" + // before starting the stream. + BYTE* data_ptr = NULL; + HRESULT hr = audio_render_client_->GetBuffer(endpoint_buffer_size_frames_, + &data_ptr); + if (FAILED(hr)) { + DLOG(ERROR) << "Failed to use rendering audio buffer: " << std::hex << hr; + return; + } + + // Using the AUDCLNT_BUFFERFLAGS_SILENT flag eliminates the need to + // explicitly write silence data to the rendering buffer. + audio_render_client_->ReleaseBuffer(endpoint_buffer_size_frames_, + AUDCLNT_BUFFERFLAGS_SILENT); + num_written_frames_ = endpoint_buffer_size_frames_; + + // Sanity check: verify that the endpoint buffer is filled with silence. + UINT32 num_queued_frames = 0; + audio_client_->GetCurrentPadding(&num_queued_frames); + DCHECK(num_queued_frames == num_written_frames_); + + // Create and start the thread that will drive the rendering by waiting for + // render events. + render_thread_ = new base::DelegateSimpleThread(this, "wasapi_render_thread"); + render_thread_->Start(); + if (!render_thread_->HasBeenStarted()) { + DLOG(ERROR) << "Failed to start WASAPI render thread."; + return; + } + + // Start streaming data between the endpoint buffer and the audio engine. + hr = audio_client_->Start(); + if (FAILED(hr)) { + SetEvent(stop_render_event_.Get()); + render_thread_->Join(); + render_thread_ = NULL; + HandleError(hr); + return; + } + + started_ = true; +} + +void WASAPIAudioOutputStream::Stop() { + DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_); + if (!started_) + return; + + // Shut down the render thread. + if (stop_render_event_.IsValid()) { + SetEvent(stop_render_event_.Get()); + } + + // Stop output audio streaming. + HRESULT hr = audio_client_->Stop(); + DLOG_IF(ERROR, FAILED(hr)) << "Failed to stop output streaming: " + << std::hex << hr; + + // Wait until the thread completes and perform cleanup. + if (render_thread_) { + SetEvent(stop_render_event_.Get()); + render_thread_->Join(); + render_thread_ = NULL; + } + + started_ = false; +} + +void WASAPIAudioOutputStream::Close() { + DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_); + + // It is valid to call Close() before calling open or Start(). + // It is also valid to call Close() after Start() has been called. + Stop(); + + // Inform the audio manager that we have been closed. This will cause our + // destruction. + manager_->ReleaseOutputStream(this); +} + +void WASAPIAudioOutputStream::SetVolume(double volume) { + float volume_float = static_cast<float>(volume); + if (volume_float < 0.0f || volume_float > 1.0f) { + return; + } + volume_ = volume_float; +} + +void WASAPIAudioOutputStream::GetVolume(double* volume) { + *volume = static_cast<double>(volume_); +} + +// static +double WASAPIAudioOutputStream::HardwareSampleRate(ERole device_role) { + // It is assumed that this static method is called from a COM thread, i.e., + // CoInitializeEx() is not called here again to avoid STA/MTA conflicts. + ScopedComPtr<IMMDeviceEnumerator> enumerator; + HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), + NULL, + CLSCTX_INPROC_SERVER, + __uuidof(IMMDeviceEnumerator), + enumerator.ReceiveVoid()); + if (FAILED(hr)) { + NOTREACHED() << "error code: " << std::hex << hr; + return 0.0; + } + + ScopedComPtr<IMMDevice> endpoint_device; + hr = enumerator->GetDefaultAudioEndpoint(eRender, + device_role, + endpoint_device.Receive()); + if (FAILED(hr)) { + // This will happen if there's no audio output device found or available + // (e.g. some audio cards that have outputs will still report them as + // "not found" when no speaker is plugged into the output jack). + LOG(WARNING) << "No audio end point: " << std::hex << hr; + return 0.0; + } + + ScopedComPtr<IAudioClient> audio_client; + hr = endpoint_device->Activate(__uuidof(IAudioClient), + CLSCTX_INPROC_SERVER, + NULL, + audio_client.ReceiveVoid()); + if (FAILED(hr)) { + NOTREACHED() << "error code: " << std::hex << hr; + return 0.0; + } + + base::win::ScopedCoMem<WAVEFORMATEX> audio_engine_mix_format; + hr = audio_client->GetMixFormat(&audio_engine_mix_format); + if (FAILED(hr)) { + NOTREACHED() << "error code: " << std::hex << hr; + return 0.0; + } + + return static_cast<double>(audio_engine_mix_format->nSamplesPerSec); +} + +void WASAPIAudioOutputStream::Run() { + ScopedCOMInitializer com_init(ScopedCOMInitializer::kMTA); + + // Increase the thread priority. + render_thread_->SetThreadPriority(base::kThreadPriority_RealtimeAudio); + + // Enable MMCSS to ensure that this thread receives prioritized access to + // CPU resources. + DWORD task_index = 0; + HANDLE mm_task = avrt::AvSetMmThreadCharacteristics(L"Pro Audio", + &task_index); + bool mmcss_is_ok = + (mm_task && avrt::AvSetMmThreadPriority(mm_task, AVRT_PRIORITY_CRITICAL)); + if (!mmcss_is_ok) { + // Failed to enable MMCSS on this thread. It is not fatal but can lead + // to reduced QoS at high load. + DWORD err = GetLastError(); + LOG(WARNING) << "Failed to enable MMCSS (error code=" << err << ")."; + } + + HRESULT hr = S_FALSE; + + bool playing = true; + bool error = false; + HANDLE wait_array[2] = {stop_render_event_, audio_samples_render_event_}; + UINT64 device_frequency = 0; + + // The IAudioClock interface enables us to monitor a stream's data + // rate and the current position in the stream. Allocate it before we + // start spinning. + ScopedComPtr<IAudioClock> audio_clock; + hr = audio_client_->GetService(__uuidof(IAudioClock), + audio_clock.ReceiveVoid()); + if (SUCCEEDED(hr)) { + // The device frequency is the frequency generated by the hardware clock in + // the audio device. The GetFrequency() method reports a constant frequency. + hr = audio_clock->GetFrequency(&device_frequency); + } + error = FAILED(hr); + PLOG_IF(ERROR, error) << "Failed to acquire IAudioClock interface: " + << std::hex << hr; + + // Render audio until stop event or error. + while (playing && !error) { + // Wait for a close-down event or a new render event. + DWORD wait_result = WaitForMultipleObjects(2, wait_array, FALSE, INFINITE); + + switch (wait_result) { + case WAIT_OBJECT_0 + 0: + // |stop_render_event_| has been set. + playing = false; + break; + case WAIT_OBJECT_0 + 1: + { + // |audio_samples_render_event_| has been set. + UINT32 num_queued_frames = 0; + uint8* audio_data = NULL; + + // Get the padding value which represents the amount of rendering + // data that is queued up to play in the endpoint buffer. + hr = audio_client_->GetCurrentPadding(&num_queued_frames); + + // Determine how much new data we can write to the buffer without + // the risk of overwriting previously written data that the audio + // engine has not yet read from the buffer. + size_t num_available_frames = + endpoint_buffer_size_frames_ - num_queued_frames; + + // Check if there is enough available space to fit the packet size + // specified by the client. + if (FAILED(hr) || (num_available_frames < packet_size_frames_)) + continue; + + // Derive the number of packets we need get from the client to + // fill up the available area in the endpoint buffer. + size_t num_packets = (num_available_frames / packet_size_frames_); + + // Get data from the client/source. + for (size_t n = 0; n < num_packets; ++n) { + // Grab all available space in the rendering endpoint buffer + // into which the client can write a data packet. + hr = audio_render_client_->GetBuffer(packet_size_frames_, + &audio_data); + if (FAILED(hr)) { + DLOG(ERROR) << "Failed to use rendering audio buffer: " + << std::hex << hr; + continue; + } + + // Derive the audio delay which corresponds to the delay between + // a render event and the time when the first audio sample in a + // packet is played out through the speaker. This delay value + // can typically be utilized by an acoustic echo-control (AEC) + // unit at the render side. + UINT64 position = 0; + int audio_delay_bytes = 0; + hr = audio_clock->GetPosition(&position, NULL); + if (SUCCEEDED(hr)) { + // Stream position of the sample that is currently playing + // through the speaker. + double pos_sample_playing_frames = format_.nSamplesPerSec * + (static_cast<double>(position) / device_frequency); + + // Stream position of the last sample written to the endpoint + // buffer. Note that, the packet we are about to receive in + // the upcoming callback is also included. + size_t pos_last_sample_written_frames = + num_written_frames_ + packet_size_frames_; + + // Derive the actual delay value which will be fed to the + // render client using the OnMoreData() callback. + audio_delay_bytes = (pos_last_sample_written_frames - + pos_sample_playing_frames) * frame_size_; + } + + // Read a data packet from the registered client source and + // deliver a delay estimate in the same callback to the client. + // A time stamp is also stored in the AudioBuffersState. This + // time stamp can be used at the client side to compensate for + // the delay between the usage of the delay value and the time + // of generation. + uint32 num_filled_bytes = source_->OnMoreData( + this, audio_data, packet_size_bytes_, + AudioBuffersState(0, audio_delay_bytes)); + + // Perform in-place, software-volume adjustments. + media::AdjustVolume(audio_data, + num_filled_bytes, + format_.nChannels, + format_.wBitsPerSample >> 3, + volume_); + + // Zero out the part of the packet which has not been filled by + // the client. Using silence is the least bad option in this + // situation. + if (num_filled_bytes < packet_size_bytes_) { + memset(&audio_data[num_filled_bytes], 0, + (packet_size_bytes_ - num_filled_bytes)); + } + + // Release the buffer space acquired in the GetBuffer() call. + DWORD flags = 0; + audio_render_client_->ReleaseBuffer(packet_size_frames_, + flags); + + num_written_frames_ += packet_size_frames_; + } + } + break; + default: + error = true; + break; + } + } + + if (playing && error) { + // Stop audio rendering since something has gone wrong in our main thread + // loop. Note that, we are still in a "started" state, hence a Stop() call + // is required to join the thread properly. + audio_client_->Stop(); + PLOG(ERROR) << "WASAPI rendering failed."; + } + + // Disable MMCSS. + if (mm_task && !avrt::AvRevertMmThreadCharacteristics(mm_task)) { + PLOG(WARNING) << "Failed to disable MMCSS"; + } +} + +void WASAPIAudioOutputStream::HandleError(HRESULT err) { + NOTREACHED() << "Error code: " << std::hex << err; + if (source_) + source_->OnError(this, static_cast<int>(err)); +} + +HRESULT WASAPIAudioOutputStream::SetRenderDevice(ERole device_role) { + ScopedComPtr<IMMDeviceEnumerator> enumerator; + HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), + NULL, + CLSCTX_INPROC_SERVER, + __uuidof(IMMDeviceEnumerator), + enumerator.ReceiveVoid()); + if (SUCCEEDED(hr)) { + // Retrieve the default render audio endpoint for the specified role. + // Note that, in Windows Vista, the MMDevice API supports device roles + // but the system-supplied user interface programs do not. + hr = enumerator->GetDefaultAudioEndpoint(eRender, + device_role, + endpoint_device_.Receive()); + + // Verify that the audio endpoint device is active. That is, the audio + // adapter that connects to the endpoint device is present and enabled. + DWORD state = DEVICE_STATE_DISABLED; + hr = endpoint_device_->GetState(&state); + if (SUCCEEDED(hr)) { + if (!(state & DEVICE_STATE_ACTIVE)) { + DLOG(ERROR) << "Selected render device is not active."; + hr = E_ACCESSDENIED; + } + } + } + + return hr; +} + +HRESULT WASAPIAudioOutputStream::ActivateRenderDevice() { + // Creates and activates an IAudioClient COM object given the selected + // render endpoint device. + HRESULT hr = endpoint_device_->Activate(__uuidof(IAudioClient), + CLSCTX_INPROC_SERVER, + NULL, + audio_client_.ReceiveVoid()); + return hr; +} + +HRESULT WASAPIAudioOutputStream::GetAudioEngineStreamFormat() { + // Retrieve the stream format that the audio engine uses for its internal + // processing/mixing of shared-mode streams. + return audio_client_->GetMixFormat(&audio_engine_mix_format_); +} + +bool WASAPIAudioOutputStream::DesiredFormatIsSupported() { + // In shared mode, the audio engine always supports the mix format, + // which is stored in the |audio_engine_mix_format_| member. In addition, + // the audio engine *might* support similar formats that have the same + // sample rate and number of channels as the mix format but differ in + // the representation of audio sample values. + base::win::ScopedCoMem<WAVEFORMATEX> closest_match; + HRESULT hr = audio_client_->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, + &format_, + &closest_match); + DLOG_IF(ERROR, hr == S_FALSE) << "Format is not supported " + << "but a closest match exists."; + return (hr == S_OK); +} + +HRESULT WASAPIAudioOutputStream::InitializeAudioEngine() { + // TODO(henrika): this buffer scheme is still under development. + // The exact details are yet to be determined based on tests with different + // audio clients. + int glitch_free_buffer_size_ms = static_cast<int>(packet_size_ms_ + 0.5); + if (audio_engine_mix_format_->nSamplesPerSec == 48000) { + // Initial tests have shown that we have to add 10 ms extra to + // ensure that we don't run empty for any packet size. + glitch_free_buffer_size_ms += 10; + } else if (audio_engine_mix_format_->nSamplesPerSec == 44100) { + // Initial tests have shown that we have to add 20 ms extra to + // ensure that we don't run empty for any packet size. + glitch_free_buffer_size_ms += 20; + } else { + glitch_free_buffer_size_ms += 20; + } + DVLOG(1) << "glitch_free_buffer_size_ms: " << glitch_free_buffer_size_ms; + REFERENCE_TIME requested_buffer_duration_hns = + static_cast<REFERENCE_TIME>(glitch_free_buffer_size_ms * 10000); + + // Initialize the audio stream between the client and the device. + // We connect indirectly through the audio engine by using shared mode + // and WASAPI is initialized in an event driven mode. + // Note that this API ensures that the buffer is never smaller than the + // minimum buffer size needed to ensure glitch-free rendering. + // If we requests a buffer size that is smaller than the audio engine's + // minimum required buffer size, the method sets the buffer size to this + // minimum buffer size rather than to the buffer size requested. + HRESULT hr = audio_client_->Initialize(AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK | + AUDCLNT_STREAMFLAGS_NOPERSIST, + requested_buffer_duration_hns, + 0, + &format_, + NULL); + if (FAILED(hr)) + return hr; + + // Retrieve the length of the endpoint buffer shared between the client + // and the audio engine. The buffer length the buffer length determines + // the maximum amount of rendering data that the client can write to + // the endpoint buffer during a single processing pass. + // A typical value is 960 audio frames <=> 20ms @ 48kHz sample rate. + hr = audio_client_->GetBufferSize(&endpoint_buffer_size_frames_); + if (FAILED(hr)) + return hr; + DVLOG(1) << "endpoint buffer size: " << endpoint_buffer_size_frames_ + << " [frames]"; +#ifndef NDEBUG + // The period between processing passes by the audio engine is fixed for a + // particular audio endpoint device and represents the smallest processing + // quantum for the audio engine. This period plus the stream latency between + // the buffer and endpoint device represents the minimum possible latency + // that an audio application can achieve in shared mode. + REFERENCE_TIME default_device_period = 0; + REFERENCE_TIME minimum_device_period = 0; + HRESULT hr_dbg = audio_client_->GetDevicePeriod(&default_device_period, + &minimum_device_period); + if (SUCCEEDED(hr_dbg)) { + // Shared mode device period. + DVLOG(1) << "default device period: " + << static_cast<double>(default_device_period / 10000.0) + << " [ms]"; + // Exclusive mode device period. + DVLOG(1) << "minimum device period: " + << static_cast<double>(minimum_device_period / 10000.0) + << " [ms]"; + } + + REFERENCE_TIME latency = 0; + hr_dbg = audio_client_->GetStreamLatency(&latency); + if (SUCCEEDED(hr_dbg)) { + DVLOG(1) << "stream latency: " << static_cast<double>(latency / 10000.0) + << " [ms]"; + } +#endif + + // Set the event handle that the audio engine will signal each time + // a buffer becomes ready to be processed by the client. + hr = audio_client_->SetEventHandle(audio_samples_render_event_.Get()); + if (FAILED(hr)) + return hr; + + // Get access to the IAudioRenderClient interface. This interface + // enables us to write output data to a rendering endpoint buffer. + // The methods in this interface manage the movement of data packets + // that contain audio-rendering data. + hr = audio_client_->GetService(__uuidof(IAudioRenderClient), + audio_render_client_.ReceiveVoid()); + return hr; +} diff --git a/media/audio/win/audio_low_latency_output_win.h b/media/audio/win/audio_low_latency_output_win.h new file mode 100644 index 0000000..72fb585 --- /dev/null +++ b/media/audio/win/audio_low_latency_output_win.h @@ -0,0 +1,206 @@ +// Copyright (c) 2011 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. +// +// Implementation of AudioOutputStream for Windows using Windows Core Audio +// WASAPI for low latency rendering. +// +// Overview of operation and performance: +// +// - An object of WASAPIAudioOutputStream is created by the AudioManager +// factory. +// - Next some thread will call Open(), at that point the underlying +// Core Audio APIs are utilized to create two WASAPI interfaces called +// IAudioClient and IAudioRenderClient. +// - Then some thread will call Start(source). +// A thread called "wasapi_render_thread" is started and this thread listens +// on an event signal which is set periodically by the audio engine to signal +// render events. As a result, OnMoreData() will be called and the registered +// client is then expected to provide data samples to be played out. +// - At some point, a thread will call Stop(), which stops and joins the +// render thread and at the same time stops audio streaming. +// - The same thread that called stop will call Close() where we cleanup +// and notify the audio manager, which likely will destroy this object. +// - Initial tests on Windows 7 shows that this implementation results in a +// latency of approximately 35 ms if the selected packet size is less than +// or equal to 20 ms. Using a packet size of 10 ms does not result in a +// lower latency but only affects the size of the data buffer in each +// OnMoreData() callback. +// - A total typical delay of 35 ms contains three parts: +// o Audio endpoint device period (~10 ms). +// o Stream latency between the buffer and endpoint device (~5 ms). +// o Endpoint buffer (~20 ms to ensure glitch-free rendering). +// - Note that, if the user selects a packet size of e.g. 100 ms, the total +// delay will be approximately 115 ms (10 + 5 + 100). +// +// Implementation notes: +// +// - The minimum supported client is Windows Vista. +// - This implementation is single-threaded, hence: +// o Construction and destruction must take place from the same thread. +// o All APIs must be called from the creating thread as well. +// - It is recommended to first acquire the native sample rate of the default +// input device and then use the same rate when creating this object. Use +// WASAPIAudioOutputStream::HardwareSampleRate() to retrieve the sample rate. +// - Calling Close() also leads to self destruction. +// +// Core Audio API details: +// +// - CoInitializeEx() is called on the creating thread and on the internal +// capture thread. Each thread's concurrency model and apartment is set +// to multi-threaded (MTA). CHECK() is called to ensure that we crash if +// CoInitializeEx(MTA) fails. +// - The public API methods (Open(), Start(), Stop() and Close()) must be +// called on constructing thread. The reason is that we want to ensure that +// the COM environment is the same for all API implementations. +// - Utilized MMDevice interfaces: +// o IMMDeviceEnumerator +// o IMMDevice +// - Utilized WASAPI interfaces: +// o IAudioClient +// o IAudioRenderClient +// - The stream is initialized in shared mode and the processing of the +// audio buffer is event driven. +// - The Multimedia Class Scheduler service (MMCSS) is utilized to boost +// the priority of the render thread. +// - Audio-rendering endpoint devices can have three roles: +// Console (eConsole), Communications (eCommunications), and Multimedia +// (eMultimedia). Search for "Device Roles" on MSDN for more details. +// +#ifndef MEDIA_AUDIO_WIN_AUDIO_LOW_LATENCY_OUTPUT_WIN_H_ +#define MEDIA_AUDIO_WIN_AUDIO_LOW_LATENCY_OUTPUT_WIN_H_ + +#include <Audioclient.h> +#include <MMDeviceAPI.h> + +#include "base/compiler_specific.h" +#include "base/threading/platform_thread.h" +#include "base/threading/simple_thread.h" +#include "base/win/scoped_co_mem.h" +#include "base/win/scoped_com_initializer.h" +#include "base/win/scoped_comptr.h" +#include "base/win/scoped_handle.h" +#include "media/audio/audio_io.h" +#include "media/audio/audio_parameters.h" +#include "media/base/media_export.h" + +class AudioManagerWin; + +// AudioOutputStream implementation using Windows Core Audio APIs. +class MEDIA_EXPORT WASAPIAudioOutputStream + : public AudioOutputStream, + public base::DelegateSimpleThread::Delegate { + public: + // The ctor takes all the usual parameters, plus |manager| which is the + // the audio manager who is creating this object. + WASAPIAudioOutputStream(AudioManagerWin* manager, + const AudioParameters& params, + ERole device_role); + // The dtor is typically called by the AudioManager only and it is usually + // triggered by calling AudioOutputStream::Close(). + virtual ~WASAPIAudioOutputStream(); + + // Implementation of AudioOutputStream. + virtual bool Open() OVERRIDE; + virtual void Start(AudioSourceCallback* callback) OVERRIDE; + virtual void Stop() OVERRIDE; + virtual void Close() OVERRIDE; + virtual void SetVolume(double volume) OVERRIDE; + virtual void GetVolume(double* volume) OVERRIDE; + + // Retrieves the stream format that the audio engine uses for its internal + // processing/mixing of shared-mode streams. + static double HardwareSampleRate(ERole device_role); + + bool started() const { return started_; } + + private: + // DelegateSimpleThread::Delegate implementation. + virtual void Run() OVERRIDE; + + // Issues the OnError() callback to the |sink_|. + void HandleError(HRESULT err); + + // The Open() method is divided into these sub methods. + HRESULT SetRenderDevice(ERole device_role); + HRESULT ActivateRenderDevice(); + HRESULT GetAudioEngineStreamFormat(); + bool DesiredFormatIsSupported(); + HRESULT InitializeAudioEngine(); + + // Initializes the COM library for use by the calling thread and sets the + // thread's concurrency model to multi-threaded. + base::win::ScopedCOMInitializer com_init_; + + // Contains the thread ID of the creating thread. + base::PlatformThreadId creating_thread_id_; + + // Our creator, the audio manager needs to be notified when we close. + AudioManagerWin* manager_; + + // Rendering is driven by this thread (which has no message loop). + // All OnMoreData() callbacks will be called from this thread. + base::DelegateSimpleThread* render_thread_; + + // Contains the desired audio format which is set up at construction. + WAVEFORMATEX format_; + + // Copy of the audio format which we know the audio engine supports. + // It is recommended to ensure that the sample rate in |format_| is identical + // to the sample rate in |audio_engine_mix_format_|. + base::win::ScopedCoMem<WAVEFORMATEX> audio_engine_mix_format_; + + bool opened_; + bool started_; + + // Volume level from 0 to 1. + float volume_; + + // Size in bytes of each audio frame (4 bytes for 16-bit stereo PCM). + size_t frame_size_; + + // Size in audio frames of each audio packet where an audio packet + // is defined as the block of data which the source is expected to deliver + // in each OnMoreData() callback. + size_t packet_size_frames_; + + // Size in bytes of each audio packet. + size_t packet_size_bytes_; + + // Size in milliseconds of each audio packet. + float packet_size_ms_; + + // Length of the audio endpoint buffer. + size_t endpoint_buffer_size_frames_; + + // Defines the role that the system has assigned to an audio endpoint device. + ERole device_role_; + + // Counts the number of audio frames written to the endpoint buffer. + UINT64 num_written_frames_; + + // Pointer to the client that will deliver audio samples to be played out. + AudioSourceCallback* source_; + + // An IMMDevice interface which represents an audio endpoint device. + base::win::ScopedComPtr<IMMDevice> endpoint_device_; + + // An IAudioClient interface which enables a client to create and initialize + // an audio stream between an audio application and the audio engine. + base::win::ScopedComPtr<IAudioClient> audio_client_; + + // The IAudioRenderClient interface enables a client to write output + // data to a rendering endpoint buffer. + base::win::ScopedComPtr<IAudioRenderClient> audio_render_client_; + + // The audio engine will signal this event each time a buffer becomes + // ready to be filled by the client. + base::win::ScopedHandle audio_samples_render_event_; + + // This event will be signaled when rendering shall stop. + base::win::ScopedHandle stop_render_event_; + + DISALLOW_COPY_AND_ASSIGN(WASAPIAudioOutputStream); +}; + +#endif // MEDIA_AUDIO_WIN_AUDIO_LOW_LATENCY_OUTPUT_WIN_H_ diff --git a/media/audio/win/audio_low_latency_output_win_unittest.cc b/media/audio/win/audio_low_latency_output_win_unittest.cc new file mode 100644 index 0000000..ae3470d --- /dev/null +++ b/media/audio/win/audio_low_latency_output_win_unittest.cc @@ -0,0 +1,528 @@ +// Copyright (c) 2011 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 <windows.h> +#include <mmsystem.h> + +#include "base/basictypes.h" +#include "base/environment.h" +#include "base/file_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/test/test_timeouts.h" +#include "base/time.h" +#include "base/path_service.h" +#include "base/win/scoped_com_initializer.h" +#include "media/audio/audio_io.h" +#include "media/audio/audio_manager.h" +#include "media/audio/win/audio_low_latency_output_win.h" +#include "media/base/seekable_buffer.h" +#include "media/base/test_data_util.h" +#include "testing/gmock_mutant.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::Between; +using ::testing::CreateFunctor; +using ::testing::DoAll; +using ::testing::Gt; +using ::testing::InvokeWithoutArgs; +using ::testing::NotNull; +using ::testing::Return; +using base::win::ScopedCOMInitializer; + +namespace media { + +static const char kSpeechFile_16b_s_48k[] = "speech_16b_stereo_48kHz.raw"; +static const char kSpeechFile_16b_s_44k[] = "speech_16b_stereo_44kHz.raw"; +static const size_t kFileDurationMs = 20000; + +static const size_t kMaxDeltaSamples = 1000; +static const char* kDeltaTimeMsFileName = "delta_times_ms.txt"; + +MATCHER_P(HasValidDelay, value, "") { + // It is difficult to come up with a perfect test condition for the delay + // estimation. For now, verify that the produced output delay is always + // larger than the selected buffer size. + return arg.hardware_delay_bytes > value.hardware_delay_bytes; +} + +class MockAudioSourceCallback : public AudioOutputStream::AudioSourceCallback { + public: + MOCK_METHOD4(OnMoreData, uint32(AudioOutputStream* stream, + uint8* dest, + uint32 max_size, + AudioBuffersState buffers_state)); + MOCK_METHOD2(OnError, void(AudioOutputStream* stream, int code)); +}; + +// This audio source implementation should be used for manual tests only since +// it takes about 20 seconds to play out a file. +class ReadFromFileAudioSource : public AudioOutputStream::AudioSourceCallback { + public: + explicit ReadFromFileAudioSource(const std::string& name) + : pos_(0), + previous_call_time_(base::Time::Now()), + text_file_(NULL), + elements_to_write_(0) { + // Reads a test file from media/test/data directory and stores it in + // a scoped_array. + ReadTestDataFile(name, &file_, &file_size_); + file_size_ = file_size_; + + // Creates an array that will store delta times between callbacks. + // The content of this array will be written to a text file at + // destruction and can then be used for off-line analysis of the exact + // timing of callbacks. The text file will be stored in media/test/data. + delta_times_.reset(new int[kMaxDeltaSamples]); + } + + virtual ~ReadFromFileAudioSource() { + // Get complete file path to output file in directory containing + // media_unittests.exe. + FilePath file_name; + EXPECT_TRUE(PathService::Get(base::DIR_EXE, &file_name)); + file_name = file_name.AppendASCII(kDeltaTimeMsFileName); + + EXPECT_TRUE(!text_file_); + text_file_ = file_util::OpenFile(file_name, "wt"); + DLOG_IF(ERROR, !text_file_) << "Failed to open log file."; + + // Write the array which contains delta times to a text file. + size_t elements_written = 0; + while (elements_written < elements_to_write_) { + fprintf(text_file_, "%d\n", delta_times_[elements_written]); + ++elements_written; + } + + file_util::CloseFile(text_file_); + } + + // AudioOutputStream::AudioSourceCallback implementation. + virtual uint32 OnMoreData(AudioOutputStream* stream, + uint8* dest, + uint32 max_size, + AudioBuffersState buffers_state) { + // Store time difference between two successive callbacks in an array. + // These values will be written to a file in the destructor. + int diff = (base::Time::Now() - previous_call_time_).InMilliseconds(); + previous_call_time_ = base::Time::Now(); + if (elements_to_write_ < kMaxDeltaSamples) { + delta_times_[elements_to_write_] = diff; + ++elements_to_write_; + } + + // Use samples read from a data file and fill up the audio buffer + // provided to us in the callback. + if (pos_ + static_cast<int>(max_size) > file_size_) + max_size = file_size_ - pos_; + if (max_size) { + memcpy(dest, &file_[pos_], max_size); + pos_ += max_size; + } + return max_size; + } + + virtual void OnError(AudioOutputStream* stream, int code) {} + + int file_size() { return file_size_; } + + private: + scoped_array<uint8> file_; + scoped_array<int> delta_times_; + int file_size_; + int pos_; + base::Time previous_call_time_; + FILE* text_file_; + size_t elements_to_write_; +}; + +// Convenience method which ensures that we are not running on the build +// bots and that at least one valid output device can be found. +static bool CanRunAudioTests() { + scoped_ptr<base::Environment> env(base::Environment::Create()); + if (env->HasVar("CHROME_HEADLESS")) + return false; + AudioManager* audio_man = AudioManager::GetAudioManager(); + if (NULL == audio_man) + return false; + // TODO(henrika): note that we use Wave today to query the number of + // existing output devices. + return audio_man->HasAudioOutputDevices(); +} + +// Convenience method which creates a default AudioOutputStream object but +// also allows the user to modify the default settings. +class AudioOutputStreamWrapper { + public: + AudioOutputStreamWrapper() + : com_init_(ScopedCOMInitializer::kMTA), + audio_man_(AudioManager::GetAudioManager()), + format_(AudioParameters::AUDIO_PCM_LOW_LATENCY), + channel_layout_(CHANNEL_LAYOUT_STEREO), + bits_per_sample_(16) { + // Use native/mixing sample rate and 10ms frame size as default. + sample_rate_ = static_cast<int>( + WASAPIAudioOutputStream::HardwareSampleRate(eConsole)); + samples_per_packet_ = sample_rate_ / 100; + DCHECK(sample_rate_); + } + + ~AudioOutputStreamWrapper() {} + + // Creates AudioOutputStream object using default parameters. + AudioOutputStream* Create() { + return CreateOutputStream(); + } + + // Creates AudioOutputStream object using non-default parameters where the + // frame size is modified. + AudioOutputStream* Create(int samples_per_packet) { + samples_per_packet_ = samples_per_packet; + return CreateOutputStream(); + } + + // Creates AudioOutputStream object using non-default parameters where the + // channel layout is modified. + AudioOutputStream* Create(ChannelLayout channel_layout) { + channel_layout_ = channel_layout; + return CreateOutputStream(); + } + + AudioParameters::Format format() const { return format_; } + int channels() const { return ChannelLayoutToChannelCount(channel_layout_); } + int bits_per_sample() const { return bits_per_sample_; } + int sample_rate() const { return sample_rate_; } + int samples_per_packet() const { return samples_per_packet_; } + + private: + AudioOutputStream* CreateOutputStream() { + AudioOutputStream* aos = audio_man_->MakeAudioOutputStream( + AudioParameters(format_, channel_layout_, sample_rate_, + bits_per_sample_, samples_per_packet_)); + EXPECT_TRUE(aos); + return aos; + } + + ScopedCOMInitializer com_init_; + AudioManager* audio_man_; + AudioParameters::Format format_; + ChannelLayout channel_layout_; + int bits_per_sample_; + int sample_rate_; + int samples_per_packet_; +}; + +// Convenience method which creates a default AudioOutputStream object. +static AudioOutputStream* CreateDefaultAudioOutputStream() { + AudioOutputStreamWrapper aosw; + AudioOutputStream* aos = aosw.Create(); + return aos; +} + +static void QuitMessageLoop(base::MessageLoopProxy* proxy) { + proxy->PostTask(FROM_HERE, new MessageLoop::QuitTask()); +} + +// Verify that we can retrieve the current hardware/mixing sample rate +// for all supported device roles. The ERole enumeration defines constants +// that indicate the role that the system/user has assigned to an audio +// endpoint device. +// TODO(henrika): modify this test when we support full device enumeration. +TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestHardwareSampleRate) { + if (!CanRunAudioTests()) + return; + + ScopedCOMInitializer com_init(ScopedCOMInitializer::kMTA); + + // Default device intended for games, system notification sounds, + // and voice commands. + int fs = static_cast<int>( + WASAPIAudioOutputStream::HardwareSampleRate(eConsole)); + EXPECT_GE(fs, 0); + + // Default communication device intended for e.g. VoIP communication. + fs = static_cast<int>( + WASAPIAudioOutputStream::HardwareSampleRate(eCommunications)); + EXPECT_GE(fs, 0); + + // Multimedia device for music, movies and live music recording. + fs = static_cast<int>( + WASAPIAudioOutputStream::HardwareSampleRate(eMultimedia)); + EXPECT_GE(fs, 0); +} + +// Test Create(), Close() calling sequence. +TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestCreateAndClose) { + if (!CanRunAudioTests()) + return; + AudioOutputStream* aos = CreateDefaultAudioOutputStream(); + aos->Close(); +} + +// Test Open(), Close() calling sequence. +TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestOpenAndClose) { + if (!CanRunAudioTests()) + return; + AudioOutputStream* aos = CreateDefaultAudioOutputStream(); + EXPECT_TRUE(aos->Open()); + aos->Close(); +} + +// Test Open(), Start(), Close() calling sequence. +TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestOpenStartAndClose) { + if (!CanRunAudioTests()) + return; + AudioOutputStream* aos = CreateDefaultAudioOutputStream(); + EXPECT_TRUE(aos->Open()); + MockAudioSourceCallback source; + EXPECT_CALL(source, OnError(aos, _)) + .Times(0); + aos->Start(&source); + aos->Close(); +} + +// Test Open(), Start(), Stop(), Close() calling sequence. +TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestOpenStartStopAndClose) { + if (!CanRunAudioTests()) + return; + AudioOutputStream* aos = CreateDefaultAudioOutputStream(); + EXPECT_TRUE(aos->Open()); + MockAudioSourceCallback source; + EXPECT_CALL(source, OnError(aos, _)) + .Times(0); + aos->Start(&source); + aos->Stop(); + aos->Close(); +} + +// Test SetVolume(), GetVolume() +TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestVolume) { + if (!CanRunAudioTests()) + return; + AudioOutputStream* aos = CreateDefaultAudioOutputStream(); + + // Initial volume should be full volume (1.0). + double volume = 0.0; + aos->GetVolume(&volume); + EXPECT_EQ(1.0, volume); + + // Verify some valid volume settings. + aos->SetVolume(0.0); + aos->GetVolume(&volume); + EXPECT_EQ(0.0, volume); + + aos->SetVolume(0.5); + aos->GetVolume(&volume); + EXPECT_EQ(0.5, volume); + + aos->SetVolume(1.0); + aos->GetVolume(&volume); + EXPECT_EQ(1.0, volume); + + // Ensure that invalid volume setting have no effect. + aos->SetVolume(1.5); + aos->GetVolume(&volume); + EXPECT_EQ(1.0, volume); + + aos->SetVolume(-0.5); + aos->GetVolume(&volume); + EXPECT_EQ(1.0, volume); + + aos->Close(); +} + +// Test some additional calling sequences. +TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestMiscCallingSequences) { + if (!CanRunAudioTests()) + return; + AudioOutputStream* aos = CreateDefaultAudioOutputStream(); + WASAPIAudioOutputStream* waos = static_cast<WASAPIAudioOutputStream*>(aos); + + // Open(), Open() is a valid calling sequence (second call does nothing). + EXPECT_TRUE(aos->Open()); + EXPECT_TRUE(aos->Open()); + + MockAudioSourceCallback source; + + // Start(), Start() is a valid calling sequence (second call does nothing). + aos->Start(&source); + EXPECT_TRUE(waos->started()); + aos->Start(&source); + EXPECT_TRUE(waos->started()); + + // Stop(), Stop() is a valid calling sequence (second call does nothing). + aos->Stop(); + EXPECT_FALSE(waos->started()); + aos->Stop(); + EXPECT_FALSE(waos->started()); + + aos->Close(); +} + +// Use default packet size (10ms) and verify that rendering starts. +TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestPacketSizeInMilliseconds) { + if (!CanRunAudioTests()) + return; + + MessageLoopForUI loop; + scoped_refptr<base::MessageLoopProxy> proxy(loop.message_loop_proxy()); + + MockAudioSourceCallback source; + + // Create default WASAPI output stream which plays out in stereo using + // the shared mixing rate. The default buffer size is 10ms. + AudioOutputStreamWrapper aosw; + AudioOutputStream* aos = aosw.Create(); + EXPECT_TRUE(aos->Open()); + + // Derive the expected size in bytes of each packet. + uint32 bytes_per_packet = aosw.channels() * aosw.samples_per_packet() * + (aosw.bits_per_sample() / 8); + + // Set up expected minimum delay estimation. + AudioBuffersState state(0, bytes_per_packet); + + // Wait for the first callback and verify its parameters. + EXPECT_CALL(source, OnMoreData(aos, NotNull(), bytes_per_packet, + HasValidDelay(state))) + .WillOnce( + DoAll( + InvokeWithoutArgs( + CreateFunctor(&QuitMessageLoop, proxy.get())), + Return(bytes_per_packet))); + + aos->Start(&source); + loop.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask(), + TestTimeouts::action_timeout_ms()); + loop.Run(); + aos->Stop(); + aos->Close(); +} + +// Use a fixed packets size (independent of sample rate) and verify +// that rendering starts. +TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestPacketSizeInSamples) { + if (!CanRunAudioTests()) + return; + + MessageLoopForUI loop; + scoped_refptr<base::MessageLoopProxy> proxy(loop.message_loop_proxy()); + + MockAudioSourceCallback source; + + // Create default WASAPI output stream which plays out in stereo using + // the shared mixing rate. The buffer size is set to 1024 samples. + AudioOutputStreamWrapper aosw; + AudioOutputStream* aos = aosw.Create(1024); + EXPECT_TRUE(aos->Open()); + + // Derive the expected size in bytes of each packet. + uint32 bytes_per_packet = aosw.channels() * aosw.samples_per_packet() * + (aosw.bits_per_sample() / 8); + + // Set up expected minimum delay estimation. + AudioBuffersState state(0, bytes_per_packet); + + // Wait for the first callback and verify its parameters. + EXPECT_CALL(source, OnMoreData(aos, NotNull(), bytes_per_packet, + HasValidDelay(state))) + .WillOnce( + DoAll( + InvokeWithoutArgs( + CreateFunctor(&QuitMessageLoop, proxy.get())), + Return(bytes_per_packet))); + + aos->Start(&source); + loop.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask(), + TestTimeouts::action_timeout_ms()); + loop.Run(); + aos->Stop(); + aos->Close(); +} + +TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestMono) { + if (!CanRunAudioTests()) + return; + + MessageLoopForUI loop; + scoped_refptr<base::MessageLoopProxy> proxy(loop.message_loop_proxy()); + + MockAudioSourceCallback source; + + // Create default WASAPI output stream which plays out in *mono* using + // the shared mixing rate. The default buffer size is 10ms. + AudioOutputStreamWrapper aosw; + AudioOutputStream* aos = aosw.Create(CHANNEL_LAYOUT_MONO); + EXPECT_TRUE(aos->Open()); + + // Derive the expected size in bytes of each packet. + uint32 bytes_per_packet = aosw.channels() * aosw.samples_per_packet() * + (aosw.bits_per_sample() / 8); + + // Set up expected minimum delay estimation. + AudioBuffersState state(0, bytes_per_packet); + + EXPECT_CALL(source, OnMoreData(aos, NotNull(), bytes_per_packet, + HasValidDelay(state))) + .WillOnce( + DoAll( + InvokeWithoutArgs( + CreateFunctor(&QuitMessageLoop, proxy.get())), + Return(bytes_per_packet))); + + aos->Start(&source); + loop.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask(), + TestTimeouts::action_timeout_ms()); + loop.Run(); + aos->Stop(); + aos->Close(); +} + +// This test is intended for manual tests and should only be enabled +// when it is required to store the captured data on a local file. +// By default, GTest will print out YOU HAVE 1 DISABLED TEST. +// To include disabled tests in test execution, just invoke the test program +// with --gtest_also_run_disabled_tests or set the GTEST_ALSO_RUN_DISABLED_TESTS +// environment variable to a value greater than 0. +// The test files are approximately 20 seconds long. +TEST(WinAudioOutputTest, DISABLE_WASAPIAudioOutputStreamReadFromFile) { + if (!CanRunAudioTests()) + return; + + AudioOutputStreamWrapper aosw; + AudioOutputStream* aos = aosw.Create(); + EXPECT_TRUE(aos->Open()); + + std::string file_name; + if (aosw.sample_rate() == 48000) { + file_name = kSpeechFile_16b_s_48k; + } else if (aosw.sample_rate() == 44100) { + file_name = kSpeechFile_16b_s_44k; + } else if (aosw.sample_rate() == 96000) { + // Use 48kHz file at 96kHz as well. Will sound like Donald Duck. + file_name = kSpeechFile_16b_s_48k; + } else { + FAIL() << "This test supports 44.1, 48kHz and 96kHz only."; + return; + } + ReadFromFileAudioSource file_source(file_name); + int file_duration_ms = kFileDurationMs; + + LOG(INFO) << "File name : " << file_name.c_str(); + LOG(INFO) << "Sample rate: " << aosw.sample_rate(); + LOG(INFO) << "File size : " << file_source.file_size(); + LOG(INFO) << ">> Listen to the file while playing..."; + + aos->Start(&file_source); + base::PlatformThread::Sleep(file_duration_ms); + aos->Stop(); + + LOG(INFO) << ">> File playout has stopped."; + aos->Close(); +} + +} // namespace media diff --git a/media/audio/win/audio_manager_win.cc b/media/audio/win/audio_manager_win.cc index e9c1d20..4e41394 100644 --- a/media/audio/win/audio_manager_win.cc +++ b/media/audio/win/audio_manager_win.cc @@ -22,6 +22,7 @@ #include "media/audio/fake_audio_input_stream.h" #include "media/audio/fake_audio_output_stream.h" #include "media/audio/win/audio_low_latency_input_win.h" +#include "media/audio/win/audio_low_latency_output_win.h" #include "media/audio/win/audio_manager_win.h" #include "media/audio/win/wavein_input_win.h" #include "media/audio/win/waveout_output_win.h" @@ -113,8 +114,8 @@ bool AudioManagerWin::HasAudioInputDevices() { // Factory for the implementations of AudioOutputStream. Two implementations // should suffice most windows user's needs. -// - PCMWaveOutAudioOutputStream: Based on the waveOutWrite API (in progress) -// - PCMDXSoundAudioOutputStream: Based on DirectSound or XAudio (future work). +// - PCMWaveOutAudioOutputStream: Based on the waveOut API. +// - WASAPIAudioOutputStream: Based on Core Audio (WASAPI) API. AudioOutputStream* AudioManagerWin::MakeAudioOutputStream( const AudioParameters& params) { if (!params.IsValid() || (params.channels > kWinMaxChannels)) @@ -132,8 +133,15 @@ AudioOutputStream* AudioManagerWin::MakeAudioOutputStream( return new PCMWaveOutAudioOutputStream(this, params, 3, WAVE_MAPPER); } else if (params.format == AudioParameters::AUDIO_PCM_LOW_LATENCY) { num_output_streams_++; - // TODO(cpu): waveout cannot hit 20ms latency. Use other method. - return new PCMWaveOutAudioOutputStream(this, params, 2, WAVE_MAPPER); + if (base::win::GetVersion() <= base::win::VERSION_XP) { + // Fall back to Windows Wave implementation on Windows XP or lower. + DLOG(INFO) << "Using WaveOut since WASAPI requires at least Vista."; + return new PCMWaveOutAudioOutputStream(this, params, 2, WAVE_MAPPER); + } else { + // TODO(henrika): improve possibility to specify audio endpoint. + // Use the default device (same as for Wave) for now to be compatible. + return new WASAPIAudioOutputStream(this, params, eConsole); + } } return NULL; } @@ -164,7 +172,7 @@ AudioInputStream* AudioManagerWin::MakeAudioInputStream( return NULL; } -void AudioManagerWin::ReleaseOutputStream(PCMWaveOutAudioOutputStream* stream) { +void AudioManagerWin::ReleaseOutputStream(AudioOutputStream* stream) { DCHECK(stream); num_output_streams_--; delete stream; diff --git a/media/audio/win/audio_manager_win.h b/media/audio/win/audio_manager_win.h index 3412527..f1ad7b4 100644 --- a/media/audio/win/audio_manager_win.h +++ b/media/audio/win/audio_manager_win.h @@ -36,7 +36,7 @@ class AudioManagerWin : public AudioManagerBase { // Windows-only methods to free a stream created in MakeAudioStream. These // are called internally by the audio stream when it has been closed. - void ReleaseOutputStream(PCMWaveOutAudioOutputStream* stream); + void ReleaseOutputStream(AudioOutputStream* stream); // Called internally by the audio stream when it has been closed. void ReleaseInputStream(AudioInputStream* stream); diff --git a/media/media.gyp b/media/media.gyp index 747c3a9..25bf46c 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -71,8 +71,6 @@ 'audio/mac/audio_input_mac.h', 'audio/mac/audio_low_latency_input_mac.cc', 'audio/mac/audio_low_latency_input_mac.h', - 'audio/win/audio_low_latency_input_win.cc', - 'audio/win/audio_low_latency_input_win.h', 'audio/mac/audio_low_latency_output_mac.cc', 'audio/mac/audio_low_latency_output_mac.h', 'audio/mac/audio_manager_mac.cc', @@ -81,10 +79,14 @@ 'audio/mac/audio_output_mac.h', 'audio/simple_sources.cc', 'audio/simple_sources.h', - 'audio/win/audio_manager_win.h', + 'audio/win/audio_low_latency_input_win.cc', + 'audio/win/audio_low_latency_input_win.h', + 'audio/win/audio_low_latency_output_win.cc', + 'audio/win/audio_low_latency_output_win.h', 'audio/win/audio_manager_win.cc', - 'audio/win/avrt_wrapper_win.h', + 'audio/win/audio_manager_win.h', 'audio/win/avrt_wrapper_win.cc', + 'audio/win/avrt_wrapper_win.h', 'audio/win/wavein_input_win.cc', 'audio/win/wavein_input_win.h', 'audio/win/waveout_output_win.cc', @@ -565,6 +567,7 @@ 'audio/mac/audio_output_mac_unittest.cc', 'audio/simple_sources_unittest.cc', 'audio/win/audio_low_latency_input_win_unittest.cc', + 'audio/win/audio_low_latency_output_win_unittest.cc', 'audio/win/audio_output_win_unittest.cc', 'base/clock_unittest.cc', 'base/composite_filter_unittest.cc', diff --git a/media/test/data/speech_16b_stereo_44kHz.raw b/media/test/data/speech_16b_stereo_44kHz.raw Binary files differnew file mode 100644 index 0000000..cdbb644 --- /dev/null +++ b/media/test/data/speech_16b_stereo_44kHz.raw diff --git a/media/test/data/speech_16b_stereo_48kHz.raw b/media/test/data/speech_16b_stereo_48kHz.raw Binary files differnew file mode 100644 index 0000000..e29432e --- /dev/null +++ b/media/test/data/speech_16b_stereo_48kHz.raw |