Index: media/audio/win/audio_low_latency_output_win.cc |
diff --git a/media/audio/win/audio_low_latency_output_win.cc b/media/audio/win/audio_low_latency_output_win.cc |
index b3f7dfe8085555f07d77c7b56f022c6298002ca2..f17735cb689988e89b595706df788bda3c1c805b 100644 |
--- a/media/audio/win/audio_low_latency_output_win.cc |
+++ b/media/audio/win/audio_low_latency_output_win.cc |
@@ -20,7 +20,8 @@ namespace media { |
WASAPIAudioOutputStream::WASAPIAudioOutputStream(AudioManagerWin* manager, |
const AudioParameters& params, |
- ERole device_role) |
+ ERole device_role, |
+ AUDCLNT_SHAREMODE share_mode) |
: com_init_(ScopedCOMInitializer::kMTA), |
creating_thread_id_(base::PlatformThread::CurrentId()), |
manager_(manager), |
@@ -31,6 +32,7 @@ WASAPIAudioOutputStream::WASAPIAudioOutputStream(AudioManagerWin* manager, |
volume_(1.0), |
endpoint_buffer_size_frames_(0), |
device_role_(device_role), |
+ share_mode_(share_mode), |
num_written_frames_(0), |
source_(NULL) { |
CHECK(com_init_.succeeded()); |
@@ -40,6 +42,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) { |
+ DVLOG(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 +93,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; |
} |
@@ -100,7 +106,8 @@ bool WASAPIAudioOutputStream::Open() { |
} |
// Retrieve the stream format which the audio engine uses for its internal |
- // processing/mixing of shared-mode streams. |
+ // processing/mixing of shared-mode streams. The result of this method is |
+ // ignored for shared mode streams. |
hr = GetAudioEngineStreamFormat(); |
if (FAILED(hr)) { |
return false; |
@@ -108,12 +115,18 @@ bool WASAPIAudioOutputStream::Open() { |
// 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 +242,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. |
@@ -278,6 +294,9 @@ void WASAPIAudioOutputStream::GetVolume(double* volume) { |
int 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. |
+ // Note that, 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. |
ScopedComPtr<IMMDeviceEnumerator> enumerator; |
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), |
NULL, |
@@ -311,6 +330,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)) { |
@@ -345,9 +366,9 @@ void WASAPIAudioOutputStream::Run() { |
bool playing = true; |
bool error = false; |
- HANDLE wait_array[] = { stop_render_event_, |
- stream_switch_event_, |
- audio_samples_render_event_ }; |
+ HANDLE wait_array[] = {stop_render_event_, |
+ stream_switch_event_, |
+ audio_samples_render_event_ }; |
UINT64 device_frequency = 0; |
// The IAudioClock interface enables us to monitor a stream's data |
@@ -394,15 +415,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 +446,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,7 +547,7 @@ void WASAPIAudioOutputStream::HandleError(HRESULT err) { |
source_->OnError(this, static_cast<int>(err)); |
} |
-HRESULT WASAPIAudioOutputStream::SetRenderDevice(ERole device_role) { |
+HRESULT WASAPIAudioOutputStream::SetRenderDevice() { |
// Create the IMMDeviceEnumerator interface. |
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), |
NULL, |
@@ -523,7 +559,7 @@ HRESULT WASAPIAudioOutputStream::SetRenderDevice(ERole device_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()); |
+ eRender, device_role_, endpoint_device_.Receive()); |
if (FAILED(hr)) |
return hr; |
@@ -559,13 +595,12 @@ HRESULT WASAPIAudioOutputStream::GetAudioEngineStreamFormat() { |
} |
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. |
base::win::ScopedCoMem<WAVEFORMATEX> closest_match; |
- HRESULT hr = audio_client_->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, |
+ HRESULT hr = audio_client_->IsFormatSupported(share_mode_, |
&format_, |
&closest_match); |
DLOG_IF(ERROR, hr == S_FALSE) << "Format is not supported " |
@@ -574,53 +609,6 @@ bool WASAPIAudioOutputStream::DesiredFormatIsSupported() { |
} |
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 |
@@ -633,11 +621,11 @@ HRESULT WASAPIAudioOutputStream::InitializeAudioEngine() { |
&minimum_device_period); |
if (SUCCEEDED(hr_dbg)) { |
// Shared mode device period. |
- DVLOG(1) << "default device period: " |
+ DVLOG(1) << "shared mode (default) device period: " |
<< static_cast<double>(default_device_period / 10000.0) |
<< " [ms]"; |
// Exclusive mode device period. |
- DVLOG(1) << "minimum device period: " |
+ DVLOG(1) << "exclusive mode (minimum) device period: " |
<< static_cast<double>(minimum_device_period / 10000.0) |
<< " [ms]"; |
} |
@@ -650,6 +638,125 @@ HRESULT WASAPIAudioOutputStream::InitializeAudioEngine() { |
} |
#endif |
+ HRESULT hr = S_FALSE; |
+ REFERENCE_TIME requested_buffer_duration = 0; |
+ |
+ // Perform different initialization depending on if the device shall be |
+ // opened in shared mode or in exclusive mode. |
+ if (share_mode() == AUDCLNT_SHAREMODE_SHARED) { |
+ // The device will be opened in shared mode and use the WAS format. |
+ |
+ // 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; |
+ requested_buffer_duration = |
+ 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. |
+ hr = audio_client_->Initialize(AUDCLNT_SHAREMODE_SHARED, |
+ AUDCLNT_STREAMFLAGS_EVENTCALLBACK | |
+ AUDCLNT_STREAMFLAGS_NOPERSIST, |
+ requested_buffer_duration, |
+ 0, |
+ &format_, |
+ NULL); |
+ } else { |
+ // The device will be opened in exclusive mode and use the application |
+ // specified format. |
+ |
+ float f = (1000.0 * packet_size_frames_) / format_.nSamplesPerSec; |
+ 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. |
+ 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) { |
+ DLOG(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). |
+ DLOG(ERROR) << "AUDCLNT_E_INVALID_DEVICE_PERIOD"; |
+ } |
+ } |
+ } |
+ |
+ if (FAILED(hr)) { |
+ DVLOG(1) << "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) { |
+ if (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()); |