Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(987)

Unified Diff: media/audio/win/audio_low_latency_output_win.cc

Issue 10575017: Adding experimental exclusive-mode streaming to WASAPIAudioOutputStream (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Changes based on review from Andrew Created 8 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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());

Powered by Google App Engine
This is Rietveld 408576698