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

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: Minor changes proposed by 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..65ed8be35ed262630c51fe0588c21ab65b172242 100644
--- a/media/audio/win/audio_low_latency_output_win.cc
+++ b/media/audio/win/audio_low_latency_output_win.cc
@@ -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;
}
« no previous file with comments | « media/audio/win/audio_low_latency_output_win.h ('k') | media/audio/win/audio_low_latency_output_win_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698