| 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());
|
|
|