Commit 4ee1f756 authored by henrika@chromium.org's avatar henrika@chromium.org

This CL adds support for experimental exclusive-mode streaming to...

This CL adds support for experimental exclusive-mode streaming to WASAPIAudioOutputStream. Enabling the flag "enable-exclusive-mode" will ensure that output audio streaming on Windows runs in exclusive mode (if the user has allowed it).

Initial tests shows that we can reach latencies of about 3.33ms for 48kHz. As a comparison, for shared-mode, the total latency is ~35ms (even if the time between each OnMoreData() callback is ~10ms).

BUG=133483
TEST=media_unittests --enable-exclusive-audio --gtest_filter=WinAudioOutputTest*

Review URL: https://chromiumcodereview.appspot.com/10575017

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@148731 0039d316-1c4b-4281-b951-d872f2087c98
parent 288235fb
......@@ -20,20 +20,20 @@
#include "base/logging.h"
#include "base/shared_memory.h"
#include "base/time.h"
#if defined(OS_WIN)
#include "base/sys_info.h"
#include "base/win/windows_version.h"
#include "media/audio/audio_manager_base.h"
#endif
#include "media/audio/audio_parameters.h"
#include "media/audio/audio_util.h"
#if defined(OS_MACOSX)
#include "media/audio/mac/audio_low_latency_input_mac.h"
#include "media/audio/mac/audio_low_latency_output_mac.h"
#endif
#if defined(OS_WIN)
#elif defined(OS_WIN)
#include "base/command_line.h"
#include "base/sys_info.h"
#include "base/win/windows_version.h"
#include "media/audio/audio_manager_base.h"
#include "media/audio/win/audio_low_latency_input_win.h"
#include "media/audio/win/audio_low_latency_output_win.h"
#include "media/base/media_switches.h"
#endif
using base::subtle::Atomic32;
......@@ -351,16 +351,28 @@ int GetAudioHardwareSampleRate() {
return 48000;
}
// TODO(crogers): tune this rate for best possible WebAudio performance.
// WebRTC works well at 48kHz and a buffer size of 480 samples will be used
// for this case. Note that exclusive mode is experimental.
const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
if (cmd_line->HasSwitch(switches::kEnableExclusiveAudio)) {
// This sample rate will be combined with a buffer size of 256 samples
// (see GetAudioHardwareBufferSize()), which corresponds to an output
// delay of ~5.33ms.
return 48000;
}
// 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.
// TODO(henrika): improve possibility to specify an audio endpoint.
// Use the default device (same as for Wave) for now to be compatible
// or possibly remove the ERole argument completely until it is in use.
return WASAPIAudioOutputStream::HardwareSampleRate(eConsole);
#elif defined(OS_ANDROID)
return 16000;
#else
// Hardware for Linux is nearly always 48KHz.
// TODO(crogers) : return correct value in rare non-48KHz cases.
return 48000;
// Hardware for Linux is nearly always 48KHz.
// TODO(crogers) : return correct value in rare non-48KHz cases.
return 48000;
#endif
}
......@@ -397,6 +409,15 @@ size_t GetAudioHardwareBufferSize() {
// and assume 48kHz as default sample rate.
return 2048;
}
// TODO(crogers): tune this size to best possible WebAudio performance.
// WebRTC always uses 10ms for Windows and does not call this method.
// Note that exclusive mode is experimental.
const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
if (cmd_line->HasSwitch(switches::kEnableExclusiveAudio)) {
return 256;
}
// 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 =
......
......@@ -6,18 +6,29 @@
#include <Functiondiscoverykeys_devpkey.h>
#include "base/command_line.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"
#include "media/base/media_switches.h"
using base::win::ScopedComPtr;
using base::win::ScopedCOMInitializer;
using base::win::ScopedCoMem;
namespace media {
// static
AUDCLNT_SHAREMODE WASAPIAudioOutputStream::GetShareMode() {
const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
if (cmd_line->HasSwitch(switches::kEnableExclusiveAudio))
return AUDCLNT_SHAREMODE_EXCLUSIVE;
return AUDCLNT_SHAREMODE_SHARED;
}
WASAPIAudioOutputStream::WASAPIAudioOutputStream(AudioManagerWin* manager,
const AudioParameters& params,
ERole device_role)
......@@ -31,6 +42,7 @@ WASAPIAudioOutputStream::WASAPIAudioOutputStream(AudioManagerWin* manager,
volume_(1.0),
endpoint_buffer_size_frames_(0),
device_role_(device_role),
share_mode_(GetShareMode()),
num_written_frames_(0),
source_(NULL) {
CHECK(com_init_.succeeded());
......@@ -40,6 +52,10 @@ WASAPIAudioOutputStream::WASAPIAudioOutputStream(AudioManagerWin* manager,
bool avrt_init = avrt::Initialize();
DCHECK(avrt_init) << "Failed to load the avrt.dll";
if (share_mode() == AUDCLNT_SHAREMODE_EXCLUSIVE) {
VLOG(1) << ">> Note that EXCLUSIVE MODE is enabled <<";
}
// Set up the desired render format specified by the client.
format_.nSamplesPerSec = params.sample_rate();
format_.wFormatTag = WAVE_FORMAT_PCM;
......@@ -87,7 +103,7 @@ bool WASAPIAudioOutputStream::Open() {
// Create an IMMDeviceEnumerator interface and obtain a reference to
// the IMMDevice interface of the default rendering device with the
// specified role.
HRESULT hr = SetRenderDevice(device_role_);
HRESULT hr = SetRenderDevice();
if (FAILED(hr)) {
return false;
}
......@@ -99,21 +115,20 @@ bool WASAPIAudioOutputStream::Open() {
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)) {
return false;
}
// Verify that the selected audio endpoint supports the specified format
// set during construction.
// In exclusive mode, the client can choose to open the stream in any audio
// format that the endpoint device supports. In shared mode, the client must
// open the stream in the mix format that is currently in use by the audio
// engine (or a format that is similar to the mix format). The audio engine's
// input streams and the output mix from the engine are all in this format.
if (!DesiredFormatIsSupported()) {
return false;
}
// Initialize the audio stream between the client and the device using
// shared mode and a lowest possible glitch-free latency.
// shared or exclusive mode and a lowest possible glitch-free latency.
// We will enter different code paths depending on the specified share mode.
hr = InitializeAudioEngine();
if (FAILED(hr)) {
return false;
......@@ -229,9 +244,12 @@ void WASAPIAudioOutputStream::Stop() {
// Extra safety check to ensure that the buffers are cleared.
// If the buffers are not cleared correctly, the next call to Start()
// would fail with AUDCLNT_E_BUFFER_ERROR at IAudioRenderClient::GetBuffer().
UINT32 num_queued_frames = 0;
audio_client_->GetCurrentPadding(&num_queued_frames);
DCHECK_EQ(0u, num_queued_frames);
// This check is is only needed for shared-mode streams.
if (share_mode() == AUDCLNT_SHAREMODE_SHARED) {
UINT32 num_queued_frames = 0;
audio_client_->GetCurrentPadding(&num_queued_frames);
DCHECK_EQ(0u, num_queued_frames);
}
// Ensure that we don't quit the main thread loop immediately next
// time Start() is called.
......@@ -276,6 +294,13 @@ void WASAPIAudioOutputStream::GetVolume(double* volume) {
// static
int WASAPIAudioOutputStream::HardwareSampleRate(ERole device_role) {
// Calling this function only makes sense for shared mode streams, since
// if the device will be opened in exclusive mode, then the application
// specified format is used instead. However, the result of this method can
// be useful for testing purposes so we don't DCHECK here.
DLOG_IF(WARNING, GetShareMode() == AUDCLNT_SHAREMODE_EXCLUSIVE) <<
"The mixing sample rate will be ignored for exclusive-mode streams.";
// 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;
......@@ -311,6 +336,8 @@ int WASAPIAudioOutputStream::HardwareSampleRate(ERole device_role) {
return 0.0;
}
// Retrieve the stream format that the audio engine uses for its internal
// processing of shared-mode streams.
base::win::ScopedCoMem<WAVEFORMATEX> audio_engine_mix_format;
hr = audio_client->GetMixFormat(&audio_engine_mix_format);
if (FAILED(hr)) {
......@@ -394,15 +421,29 @@ void WASAPIAudioOutputStream::Run() {
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
// Contains 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;
size_t num_available_frames = 0;
if (share_mode() == AUDCLNT_SHAREMODE_SHARED) {
// 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);
num_available_frames =
endpoint_buffer_size_frames_ - num_queued_frames;
} else {
// While the stream is running, the system alternately sends one
// buffer or the other to the client. This form of double buffering
// is referred to as "ping-ponging". Each time the client receives
// a buffer from the system (triggers this event) the client must
// process the entire buffer. Calls to the GetCurrentPadding method
// are unnecessary because the packet size must always equal the
// buffer size. In contrast to the shared mode buffering scheme,
// the latency for an event-driven, exclusive-mode stream depends
// directly on the buffer size.
num_available_frames = endpoint_buffer_size_frames_;
}
// Check if there is enough available space to fit the packet size
// specified by the client.
......@@ -411,6 +452,7 @@ void WASAPIAudioOutputStream::Run() {
// Derive the number of packets we need get from the client to
// fill up the available area in the endpoint buffer.
// |num_packets| will always be one for exclusive-mode streams.
size_t num_packets = (num_available_frames / packet_size_frames_);
// Get data from the client/source.
......@@ -511,26 +553,29 @@ void WASAPIAudioOutputStream::HandleError(HRESULT err) {
source_->OnError(this, static_cast<int>(err));
}
HRESULT WASAPIAudioOutputStream::SetRenderDevice(ERole device_role) {
HRESULT WASAPIAudioOutputStream::SetRenderDevice() {
ScopedComPtr<IMMDeviceEnumerator> device_enumerator;
ScopedComPtr<IMMDevice> endpoint_device;
// Create the IMMDeviceEnumerator interface.
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
NULL,
CLSCTX_INPROC_SERVER,
__uuidof(IMMDeviceEnumerator),
device_enumerator_.ReceiveVoid());
device_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 = device_enumerator_->GetDefaultAudioEndpoint(
eRender, device_role, endpoint_device_.Receive());
hr = device_enumerator->GetDefaultAudioEndpoint(
eRender, device_role_, endpoint_device.Receive());
if (FAILED(hr))
return hr;
// 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);
hr = endpoint_device->GetState(&state);
if (SUCCEEDED(hr)) {
if (!(state & DEVICE_STATE_ACTIVE)) {
DLOG(ERROR) << "Selected render device is not active.";
......@@ -539,41 +584,145 @@ HRESULT WASAPIAudioOutputStream::SetRenderDevice(ERole device_role) {
}
}
if (SUCCEEDED(hr)) {
device_enumerator_ = device_enumerator;
endpoint_device_ = endpoint_device;
}
return hr;
}
HRESULT WASAPIAudioOutputStream::ActivateRenderDevice() {
ScopedComPtr<IAudioClient> audio_client;
// 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;
}
audio_client.ReceiveVoid());
if (SUCCEEDED(hr)) {
// Retrieve the stream format that the audio engine uses for its internal
// processing/mixing of shared-mode streams.
audio_engine_mix_format_.Reset(NULL);
hr = audio_client->GetMixFormat(&audio_engine_mix_format_);
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_);
if (SUCCEEDED(hr)) {
audio_client_ = audio_client;
}
}
return hr;
}
bool WASAPIAudioOutputStream::DesiredFormatIsSupported() {
// Determine, before calling IAudioClient::Initialize(), whether the audio
// engine supports a particular stream format.
// 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.
// which is stored in the |audio_engine_mix_format_| member and it is also
// possible to receive a proposed (closest) format if the current format is
// not supported.
base::win::ScopedCoMem<WAVEFORMATEX> closest_match;
HRESULT hr = audio_client_->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED,
HRESULT hr = audio_client_->IsFormatSupported(share_mode(),
&format_,
&closest_match);
// This log can only be triggered for shared mode.
DLOG_IF(ERROR, hr == S_FALSE) << "Format is not supported "
<< "but a closest match exists.";
// This log can be triggered both for shared and exclusive modes.
DLOG_IF(ERROR, hr == AUDCLNT_E_UNSUPPORTED_FORMAT) << "Unsupported format.";
if (hr == S_FALSE) {
DVLOG(1) << "wFormatTag : " << closest_match->wFormatTag;
DVLOG(1) << "nChannels : " << closest_match->nChannels;
DVLOG(1) << "nSamplesPerSec: " << closest_match->nSamplesPerSec;
DVLOG(1) << "wBitsPerSample: " << closest_match->wBitsPerSample;
}
return (hr == S_OK);
}
HRESULT WASAPIAudioOutputStream::InitializeAudioEngine() {
#if !defined(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) << "shared mode (default) device period: "
<< static_cast<double>(default_device_period / 10000.0)
<< " [ms]";
// Exclusive mode device period.
DVLOG(1) << "exclusive mode (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
HRESULT hr = S_FALSE;
// Perform different initialization depending on if the device shall be
// opened in shared mode or in exclusive mode.
hr = (share_mode() == AUDCLNT_SHAREMODE_SHARED) ?
SharedModeInitialization() : ExclusiveModeInitialization();
if (FAILED(hr)) {
LOG(WARNING) << "IAudioClient::Initialize() failed: " << std::hex << hr;
return hr;
}
// Retrieve the length of the endpoint buffer. The buffer length represents
// 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]";
// The buffer scheme for exclusive mode streams is not designed for max
// flexibility. We only allow a "perfect match" between the packet size set
// by the user and the actual endpoint buffer size.
if (share_mode() == AUDCLNT_SHAREMODE_EXCLUSIVE &&
endpoint_buffer_size_frames_ != packet_size_frames_) {
hr = AUDCLNT_E_INVALID_SIZE;
DLOG(ERROR) << "AUDCLNT_E_INVALID_SIZE";
return hr;
}
// 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;
}
HRESULT WASAPIAudioOutputStream::SharedModeInitialization() {
DCHECK_EQ(share_mode(), AUDCLNT_SHAREMODE_SHARED);
// TODO(henrika): this buffer scheme is still under development.
// The exact details are yet to be determined based on tests with different
// audio clients.
......@@ -590,7 +739,7 @@ HRESULT WASAPIAudioOutputStream::InitializeAudioEngine() {
glitch_free_buffer_size_ms += 20;
}
DVLOG(1) << "glitch_free_buffer_size_ms: " << glitch_free_buffer_size_ms;
REFERENCE_TIME requested_buffer_duration_hns =
REFERENCE_TIME requested_buffer_duration =
static_cast<REFERENCE_TIME>(glitch_free_buffer_size_ms * 10000);
// Initialize the audio stream between the client and the device.
......@@ -604,64 +753,63 @@ HRESULT WASAPIAudioOutputStream::InitializeAudioEngine() {
HRESULT hr = audio_client_->Initialize(AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
AUDCLNT_STREAMFLAGS_NOPERSIST,
requested_buffer_duration_hns,
requested_buffer_duration,
0,
&format_,
NULL);
if (FAILED(hr))
return 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
HRESULT WASAPIAudioOutputStream::ExclusiveModeInitialization() {
DCHECK_EQ(share_mode(), AUDCLNT_SHAREMODE_EXCLUSIVE);
// 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;
float f = (1000.0 * packet_size_frames_) / format_.nSamplesPerSec;
REFERENCE_TIME requested_buffer_duration =
static_cast<REFERENCE_TIME>(f * 10000.0 + 0.5);
// Initialize the audio stream between the client and the device.
// For an exclusive-mode stream that uses event-driven buffering, the
// caller must specify nonzero values for hnsPeriodicity and
// hnsBufferDuration, and the values of these two parameters must be equal.
// The Initialize method allocates two buffers for the stream. Each buffer
// is equal in duration to the value of the hnsBufferDuration parameter.
// Following the Initialize call for a rendering stream, the caller should
// fill the first of the two buffers before starting the stream.
HRESULT hr = audio_client_->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
AUDCLNT_STREAMFLAGS_NOPERSIST,
requested_buffer_duration,
requested_buffer_duration,
&format_,
NULL);
if (FAILED(hr)) {
if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
LOG(ERROR) << "AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED";
UINT32 aligned_buffer_size = 0;
audio_client_->GetBufferSize(&aligned_buffer_size);
DVLOG(1) << "Use aligned buffer size instead: " << aligned_buffer_size;
audio_client_.Release();
// Calculate new aligned periodicity. Each unit of reference time
// is 100 nanoseconds.
REFERENCE_TIME aligned_buffer_duration = static_cast<REFERENCE_TIME>(
(10000000.0 * aligned_buffer_size / format_.nSamplesPerSec) + 0.5);
// It is possible to re-activate and re-initialize the audio client
// at this stage but we bail out with an error code instead and
// combine it with a log message which informs about the suggested
// aligned buffer size which should be used instead.
DVLOG(1) << "aligned_buffer_duration: "
<< static_cast<double>(aligned_buffer_duration / 10000.0)
<< " [ms]";
} else if (hr == AUDCLNT_E_INVALID_DEVICE_PERIOD) {
// We will get this error if we try to use a smaller buffer size than
// the minimum supported size (usually ~3ms on Windows 7).
LOG(ERROR) << "AUDCLNT_E_INVALID_DEVICE_PERIOD";
}
}
// 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;
}
......
......@@ -112,6 +112,27 @@
// - All callback methods from the IMMNotificationClient interface will be
// called on a Windows-internal MMDevice thread.
//
// Experimental exclusive mode:
//
// - It is possible to open up a stream in exclusive mode by using the
// --enable-exclusive-mode command line flag.
// - The internal buffering scheme is less flexible for exclusive streams.
// Hence, some manual tuning will be required before deciding what frame
// size to use. See the WinAudioOutputTest unit test for more details.
// - If an application opens a stream in exclusive mode, the application has
// exclusive use of the audio endpoint device that plays the stream.
// - Exclusive-mode should only be utilized when the lowest possible latency
// is important.
// - In exclusive mode, the client can choose to open the stream in any audio
// format that the endpoint device supports, i.e. not limited to the device's
// current (default) configuration.
// - Initial measurements on Windows 7 (HP Z600 workstation) have shown that
// the lowest possible latencies we can achieve on this machine are:
// o ~3.3333ms @ 48kHz <=> 160 audio frames per buffer.
// o ~3.6281ms @ 44.1kHz <=> 160 audio frames per buffer.
// - See http://msdn.microsoft.com/en-us/library/windows/desktop/dd370844(v=vs.85).aspx
// for more details.
#ifndef MEDIA_AUDIO_WIN_AUDIO_LOW_LATENCY_OUTPUT_WIN_H_
#define MEDIA_AUDIO_WIN_AUDIO_LOW_LATENCY_OUTPUT_WIN_H_
......@@ -163,8 +184,13 @@ class MEDIA_EXPORT WASAPIAudioOutputStream
// Retrieves the stream format that the audio engine uses for its internal
// processing/mixing of shared-mode streams.
// This method should not be used in combination with exclusive-mode streams.
static int HardwareSampleRate(ERole device_role);
// Returns AUDCLNT_SHAREMODE_EXCLUSIVE if --enable-exclusive-mode is used
// as command-line flag and AUDCLNT_SHAREMODE_SHARED otherwise (default).
static AUDCLNT_SHAREMODE GetShareMode();
bool started() const { return started_; }
private:
......@@ -206,12 +232,19 @@ class MEDIA_EXPORT WASAPIAudioOutputStream
void HandleError(HRESULT err);
// The Open() method is divided into these sub methods.
HRESULT SetRenderDevice(ERole device_role);
HRESULT SetRenderDevice();
HRESULT ActivateRenderDevice();
HRESULT GetAudioEngineStreamFormat();
bool DesiredFormatIsSupported();
HRESULT InitializeAudioEngine();
// Called when the device will be opened in shared mode and use the
// internal audio engine's mix format.
HRESULT SharedModeInitialization();
// Called when the device will be opened in exclusive mode and use the
// application specified format.
HRESULT ExclusiveModeInitialization();
// Converts unique endpoint ID to user-friendly device name.
std::string GetDeviceName(LPCWSTR device_id) const;
......@@ -222,6 +255,8 @@ class MEDIA_EXPORT WASAPIAudioOutputStream
// new default audio device.
bool RestartRenderingUsingNewDefaultDevice();
AUDCLNT_SHAREMODE share_mode() const { return share_mode_; }
// 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_;
......@@ -276,6 +311,11 @@ class MEDIA_EXPORT WASAPIAudioOutputStream
// Defines the role that the system has assigned to an audio endpoint device.
ERole device_role_;
// The sharing mode for the connection.
// Valid values are AUDCLNT_SHAREMODE_SHARED and AUDCLNT_SHAREMODE_EXCLUSIVE
// where AUDCLNT_SHAREMODE_SHARED is the default.
AUDCLNT_SHAREMODE share_mode_;
// Counts the number of audio frames written to the endpoint buffer.
UINT64 num_written_frames_;
......
......@@ -41,7 +41,7 @@ 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 kNumFileSegments = 1;
static const size_t kNumFileSegments = 2;
static const size_t kMaxDeltaSamples = 1000;
static const char* kDeltaTimeMsFileName = "delta_times_ms.txt";
......@@ -53,6 +53,12 @@ MATCHER_P(HasValidDelay, value, "") {
return arg.hardware_delay_bytes > value.hardware_delay_bytes;
}
// Used to terminate a loop from a different thread than the loop belongs to.
// |loop| should be a MessageLoopProxy.
ACTION_P(QuitLoop, loop) {
loop->PostTask(FROM_HERE, MessageLoop::QuitClosure());
}
class MockAudioSourceCallback : public AudioOutputStream::AudioSourceCallback {
public:
MOCK_METHOD3(OnMoreData, uint32(uint8* dest,
......@@ -138,6 +144,11 @@ class ReadFromFileAudioSource : public AudioOutputStream::AudioSourceCallback {
size_t elements_to_write_;
};
static bool ExclusiveModeIsEnabled() {
return (WASAPIAudioOutputStream::GetShareMode() ==
AUDCLNT_SHAREMODE_EXCLUSIVE);
}
// Convenience method which ensures that we are not running on the build
// bots and that at least one valid output device can be found. We also
// verify that we are not running on XP since the low-latency (WASAPI-
......@@ -185,6 +196,14 @@ class AudioOutputStreamWrapper {
return CreateOutputStream();
}
// Creates AudioOutputStream object using non-default parameters where the
// sample rate and frame size are modified.
AudioOutputStream* Create(int sample_rate, int samples_per_packet) {
sample_rate_ = sample_rate;
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) {
......@@ -224,18 +243,16 @@ static AudioOutputStream* CreateDefaultAudioOutputStream(
return aos;
}
static void QuitMessageLoop(base::MessageLoopProxy* proxy) {
proxy->PostTask(FROM_HERE, MessageLoop::QuitClosure());
}
// 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) {
// Skip this test in exclusive mode since the resulting rate is only utilized
// for shared mode streams.
scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
if (!CanRunAudioTests(audio_manager.get()))
if (!CanRunAudioTests(audio_manager.get()) || ExclusiveModeIsEnabled())
return;
ScopedCOMInitializer com_init(ScopedCOMInitializer::kMTA);
......@@ -389,8 +406,6 @@ TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestPacketSizeInMilliseconds) {
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
......@@ -409,11 +424,9 @@ TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestPacketSizeInMilliseconds) {
// Wait for the first callback and verify its parameters.
EXPECT_CALL(source, OnMoreData(NotNull(), bytes_per_packet,
HasValidDelay(state)))
.WillOnce(
DoAll(
InvokeWithoutArgs(
CreateFunctor(&QuitMessageLoop, proxy.get())),
Return(bytes_per_packet)));
.WillOnce(DoAll(
QuitLoop(loop.message_loop_proxy()),
Return(bytes_per_packet)));
aos->Start(&source);
loop.PostDelayedTask(FROM_HERE, MessageLoop::QuitClosure(),
......@@ -431,8 +444,6 @@ TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestPacketSizeInSamples) {
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
......@@ -448,14 +459,13 @@ TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestPacketSizeInSamples) {
// Set up expected minimum delay estimation.
AudioBuffersState state(0, bytes_per_packet);
// Wait for the first callback and verify its parameters.
// Ensure that callbacks start correctly.
EXPECT_CALL(source, OnMoreData(NotNull(), bytes_per_packet,
HasValidDelay(state)))
.WillOnce(
DoAll(
InvokeWithoutArgs(
CreateFunctor(&QuitMessageLoop, proxy.get())),
Return(bytes_per_packet)));
.WillOnce(DoAll(
QuitLoop(loop.message_loop_proxy()),
Return(bytes_per_packet)))
.WillRepeatedly(Return(bytes_per_packet));
aos->Start(&source);
loop.PostDelayedTask(FROM_HERE, MessageLoop::QuitClosure(),
......@@ -471,8 +481,6 @@ TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestMono) {
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
......@@ -497,11 +505,10 @@ TEST(WinAudioOutputTest, WASAPIAudioOutputStreamTestMono) {
EXPECT_CALL(source, OnMoreData(NotNull(), bytes_per_packet,
HasValidDelay(state)))
.WillOnce(
DoAll(
InvokeWithoutArgs(
CreateFunctor(&QuitMessageLoop, proxy.get())),
Return(bytes_per_packet)));
.WillOnce(DoAll(
QuitLoop(loop.message_loop_proxy()),
Return(bytes_per_packet)))
.WillRepeatedly(Return(bytes_per_packet));
aos->Start(&source);
loop.PostDelayedTask(FROM_HERE, MessageLoop::QuitClosure(),
......@@ -562,4 +569,199 @@ TEST(WinAudioOutputTest, DISABLED_WASAPIAudioOutputStreamReadFromFile) {
aos->Close();
}
// Verify that we can open the output stream in exclusive mode using a
// certain set of audio parameters and a sample rate of 48kHz.
// The expected outcomes of each setting in this test has been derived
// manually using log outputs (--v=1).
TEST(WinAudioOutputTest, WASAPIExclusiveModeBufferSizesAt48kHz) {
if (!ExclusiveModeIsEnabled())
return;
scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
if (!CanRunAudioTests(audio_manager.get()))
return;
AudioOutputStreamWrapper aosw(audio_manager.get());
// 10ms @ 48kHz shall work.
// Note that, this is the same size as we can use for shared-mode streaming
// but here the endpoint buffer delay is only 10ms instead of 20ms.
AudioOutputStream* aos = aosw.Create(48000, 480);
EXPECT_TRUE(aos->Open());
aos->Close();
// 5ms @ 48kHz does not work due to misalignment.
// This test will propose an aligned buffer size of 5.3333ms.
// Note that we must call Close() even is Open() fails since Close() also
// deletes the object and we want to create a new object in the next test.
aos = aosw.Create(48000, 240);
EXPECT_FALSE(aos->Open());
aos->Close();
// 5.3333ms @ 48kHz should work (see test above).
aos = aosw.Create(48000, 256);
EXPECT_TRUE(aos->Open());
aos->Close();
// 2.6667ms is smaller than the minimum supported size (=3ms).
aos = aosw.Create(48000, 128);
EXPECT_FALSE(aos->Open());
aos->Close();
// 3ms does not correspond to an aligned buffer size.
// This test will propose an aligned buffer size of 3.3333ms.
aos = aosw.Create(48000, 144);
EXPECT_FALSE(aos->Open());
aos->Close();
// 3.3333ms @ 48kHz <=> smallest possible buffer size we can use.
aos = aosw.Create(48000, 160);
EXPECT_TRUE(aos->Open());
aos->Close();
}
// Verify that we can open the output stream in exclusive mode using a
// certain set of audio parameters and a sample rate of 44.1kHz.
// The expected outcomes of each setting in this test has been derived
// manually using log outputs (--v=1).
TEST(WinAudioOutputTest, WASAPIExclusiveModeBufferSizesAt44kHz) {
if (!ExclusiveModeIsEnabled())
return;
scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
if (!CanRunAudioTests(audio_manager.get()))
return;
AudioOutputStreamWrapper aosw(audio_manager.get());
// 10ms @ 44.1kHz does not work due to misalignment.
// This test will propose an aligned buffer size of 10.1587ms.
AudioOutputStream* aos = aosw.Create(44100, 441);
EXPECT_FALSE(aos->Open());
aos->Close();
// 10.1587ms @ 44.1kHz shall work (see test above).
aos = aosw.Create(44100, 448);
EXPECT_TRUE(aos->Open());
aos->Close();
// 5.8050ms @ 44.1 should work.
aos = aosw.Create(44100, 256);
EXPECT_TRUE(aos->Open());
aos->Close();
// 4.9887ms @ 44.1kHz does not work to misalignment.
// This test will propose an aligned buffer size of 5.0794ms.
// Note that we must call Close() even is Open() fails since Close() also
// deletes the object and we want to create a new object in the next test.
aos = aosw.Create(44100, 220);
EXPECT_FALSE(aos->Open());
aos->Close();
// 5.0794ms @ 44.1kHz shall work (see test above).
aos = aosw.Create(44100, 224);
EXPECT_TRUE(aos->Open());
aos->Close();
// 2.9025ms is smaller than the minimum supported size (=3ms).
aos = aosw.Create(44100, 132);
EXPECT_FALSE(aos->Open());
aos->Close();
// 3.01587ms is larger than the minimum size but is not aligned.
// This test will propose an aligned buffer size of 3.6281ms.
aos = aosw.Create(44100, 133);
EXPECT_FALSE(aos->Open());
aos->Close();
// 3.6281ms @ 44.1kHz <=> smallest possible buffer size we can use.
aos = aosw.Create(44100, 160);
EXPECT_TRUE(aos->Open());
aos->Close();
}
// Verify that we can open and start the output stream in exclusive mode at
// the lowest possible delay at 48kHz.
TEST(WinAudioOutputTest, WASAPIExclusiveModeMinBufferSizeAt48kHz) {
if (!ExclusiveModeIsEnabled())
return;
scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
if (!CanRunAudioTests(audio_manager.get()))
return;
MessageLoopForUI loop;
MockAudioSourceCallback source;
// Create exclusive-mode WASAPI output stream which plays out in stereo
// using the minimum buffer size at 48kHz sample rate.
AudioOutputStreamWrapper aosw(audio_manager.get());
AudioOutputStream* aos = aosw.Create(48000, 160);
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(NotNull(), bytes_per_packet,
HasValidDelay(state)))
.WillOnce(DoAll(
QuitLoop(loop.message_loop_proxy()),
Return(bytes_per_packet)))
.WillRepeatedly(Return(bytes_per_packet));
aos->Start(&source);
loop.PostDelayedTask(FROM_HERE, MessageLoop::QuitClosure(),
TestTimeouts::action_timeout());
loop.Run();
aos->Stop();
aos->Close();
}
// Verify that we can open and start the output stream in exclusive mode at
// the lowest possible delay at 44.1kHz.
TEST(WinAudioOutputTest, WASAPIExclusiveModeMinBufferSizeAt44kHz) {
if (!ExclusiveModeIsEnabled())
return;
scoped_ptr<AudioManager> audio_manager(AudioManager::Create());
if (!CanRunAudioTests(audio_manager.get()))
return;
MessageLoopForUI loop;
MockAudioSourceCallback source;
// Create exclusive-mode WASAPI output stream which plays out in stereo
// using the minimum buffer size at 44.1kHz sample rate.
AudioOutputStreamWrapper aosw(audio_manager.get());
AudioOutputStream* aos = aosw.Create(44100, 160);
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(NotNull(), bytes_per_packet,
HasValidDelay(state)))
.WillOnce(DoAll(
QuitLoop(loop.message_loop_proxy()),
Return(bytes_per_packet)))
.WillRepeatedly(Return(bytes_per_packet));
aos->Start(&source);
loop.PostDelayedTask(FROM_HERE, MessageLoop::QuitClosure(),
TestTimeouts::action_timeout());
loop.Run();
aos->Stop();
aos->Close();
}
} // namespace media
......@@ -23,6 +23,15 @@ const char kUseCras[] = "use-cras";
const char kUsePulseAudio[] = "use-pulseaudio";
#endif
#if defined(OS_WIN)
// Use exclusive mode audio streaming for Windows Vista and higher.
// Leads to lower latencies for audio streams which uses the
// AudioParameters::AUDIO_PCM_LOW_LATENCY audio path.
// See http://msdn.microsoft.com/en-us/library/windows/desktop/dd370844(v=vs.85).aspx
// for details.
const char kEnableExclusiveAudio[] = "enable-exclusive-audio";
#endif
// Set number of threads to use for video decoding.
const char kVideoThreads[] = "video-threads";
......
......@@ -25,6 +25,10 @@ MEDIA_EXPORT extern const char kUseCras[];
MEDIA_EXPORT extern const char kUsePulseAudio[];
#endif
#if defined(OS_WIN)
MEDIA_EXPORT extern const char kEnableExclusiveAudio[];
#endif
MEDIA_EXPORT extern const char kVideoThreads[];
MEDIA_EXPORT extern const char kDisableAudioMixer[];
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment