Chromium Code Reviews| 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..22acd129b466c547fbe60b5fecea3269e4769b2e 100644 |
| --- a/media/audio/win/audio_low_latency_output_win.cc |
| +++ b/media/audio/win/audio_low_latency_output_win.cc |
| @@ -6,18 +6,34 @@ |
| #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; |
| namespace media { |
| +AUDCLNT_SHAREMODE GetShareModeImpl() { |
| + const CommandLine* cmd_line = CommandLine::ForCurrentProcess(); |
| + if (cmd_line->HasSwitch(switches::kEnableExclusiveMode)) |
| + return AUDCLNT_SHAREMODE_EXCLUSIVE; |
| + else |
|
tommi (sloooow) - chröme
2012/07/25 11:49:40
nit: can remove the else
henrika (OOO until Aug 14)
2012/07/25 15:26:30
Done.
|
| + return AUDCLNT_SHAREMODE_SHARED; |
| +} |
| + |
| +// static |
| +AUDCLNT_SHAREMODE WASAPIAudioOutputStream::GetShareMode() { |
| + static const AUDCLNT_SHAREMODE kShareMode = GetShareModeImpl(); |
|
no longer working on chromium
2012/07/25 12:32:57
do we need const for an enum?
henrika (OOO until Aug 14)
2012/07/25 15:26:30
Why not ;-)
|
| + return kShareMode; |
| +} |
| + |
| WASAPIAudioOutputStream::WASAPIAudioOutputStream(AudioManagerWin* manager, |
| const AudioParameters& params, |
| ERole device_role) |
| @@ -31,6 +47,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 +57,10 @@ WASAPIAudioOutputStream::WASAPIAudioOutputStream(AudioManagerWin* manager, |
| bool avrt_init = avrt::Initialize(); |
| DCHECK(avrt_init) << "Failed to load the avrt.dll"; |
| + if (AUDCLNT_SHAREMODE_EXCLUSIVE == share_mode()) { |
| + DVLOG(1) << ">> Note that EXCLUSIVE MODE is enabled <<"; |
|
tommi (sloooow) - chröme
2012/07/25 11:49:40
maybe VLOG?
henrika (OOO until Aug 14)
2012/07/25 15:26:30
Done.
|
| + } |
| + |
| // Set up the desired render format specified by the client. |
| format_.nSamplesPerSec = params.sample_rate(); |
| format_.wFormatTag = WAVE_FORMAT_PCM; |
| @@ -87,7 +108,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 +121,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 +130,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 +257,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 +309,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 +345,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 +381,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_, |
|
tommi (sloooow) - chröme
2012/07/25 11:49:40
nit: revert this change? (there's still space for
henrika (OOO until Aug 14)
2012/07/25 15:26:30
Fixed.
|
| + stream_switch_event_, |
| + audio_samples_render_event_ }; |
| UINT64 device_frequency = 0; |
| // The IAudioClock interface enables us to monitor a stream's data |
| @@ -394,15 +430,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 +461,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 +562,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 +574,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 +610,12 @@ HRESULT WASAPIAudioOutputStream::GetAudioEngineStreamFormat() { |
| } |
| bool WASAPIAudioOutputStream::DesiredFormatIsSupported() { |
| + // Determine, before calling IAudioClient::Initialize, whether the audio |
|
tommi (sloooow) - chröme
2012/07/25 11:49:40
nit: ()
henrika (OOO until Aug 14)
2012/07/25 15:26:30
Done.
|
| + // 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 +624,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 +636,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 +653,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. |
|
tommi (sloooow) - chröme
2012/07/25 11:49:40
what about pulling these two initialization paths
|
| + |
| + // 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; |
|
tommi (sloooow) - chröme
2012/07/25 11:49:40
should this perhaps be LOG(WARNING)?
henrika (OOO until Aug 14)
2012/07/25 15:26:30
Changed to PLOG(WARNING)
|
| + 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) { |
|
no longer working on chromium
2012/07/25 12:32:57
combine two if:
if (share_mode() == AUDCLNT_SHAREM
henrika (OOO until Aug 14)
2012/07/25 15:26:30
Done.
|
| + 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()); |