blob: 677f9e0260aea59c10879be3e4daef4e10a206e7 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/audio/win/audio_unified_win.h"
#include <Functiondiscoverykeys_devpkey.h>
#include "base/debug/trace_event.h"
#include "base/time.h"
#include "base/win/scoped_com_initializer.h"
#include "media/audio/win/audio_manager_win.h"
#include "media/audio/win/avrt_wrapper_win.h"
#include "media/audio/win/core_audio_util_win.h"
using base::win::ScopedComPtr;
using base::win::ScopedCOMInitializer;
using base::win::ScopedCoMem;
// Time in milliseconds between two successive delay measurements.
// We save resources by not updating the delay estimates for each capture
// event (typically 100Hz rate).
static const size_t kTimeDiffInMillisecondsBetweenDelayMeasurements = 1000;
// Compare two sets of audio parameters and return true if they are equal.
// Note that bits_per_sample() is excluded from this comparison since Core
// Audio can deal with most bit depths. As an example, if the native/mixing
// bit depth is 32 bits (default), opening at 16 or 24 still works fine and
// the audio engine will do the required conversion for us.
static bool CompareAudioParameters(const media::AudioParameters& a,
const media::AudioParameters& b) {
return (a.format() == b.format() &&
a.channels() == b.channels() &&
a.sample_rate() == b.sample_rate() &&
a.frames_per_buffer() == b.frames_per_buffer());
}
// Use the acquired IAudioClock interface to derive a time stamp of the audio
// sample which is currently playing through the speakers.
static double SpeakerStreamPosInMilliseconds(IAudioClock* clock) {
UINT64 device_frequency = 0, position = 0;
if (FAILED(clock->GetFrequency(&device_frequency)) ||
FAILED(clock->GetPosition(&position, NULL))) {
return 0.0;
}
return base::Time::kMillisecondsPerSecond *
(static_cast<double>(position) / device_frequency);
}
// Get a time stamp in milliseconds given number of audio frames in |num_frames|
// using the current sample rate |fs| as scale factor.
// Example: |num_frames| = 960 and |fs| = 48000 => 20 [ms].
static double CurrentStreamPosInMilliseconds(UINT64 num_frames, DWORD fs) {
return base::Time::kMillisecondsPerSecond *
(static_cast<double>(num_frames) / fs);
}
// Convert a timestamp in milliseconds to byte units given the audio format
// in |format|.
// Example: |ts_milliseconds| equals 10, sample rate is 48000 and frame size
// is 4 bytes per audio frame => 480 * 4 = 1920 [bytes].
static int MillisecondsToBytes(double ts_milliseconds,
const WAVEFORMATPCMEX& format) {
double seconds = ts_milliseconds / base::Time::kMillisecondsPerSecond;
return static_cast<int>(seconds * format.Format.nSamplesPerSec *
format.Format.nBlockAlign + 0.5);
}
namespace media {
WASAPIUnifiedStream::WASAPIUnifiedStream(AudioManagerWin* manager,
const AudioParameters& params)
: creating_thread_id_(base::PlatformThread::CurrentId()),
manager_(manager),
share_mode_(CoreAudioUtil::GetShareMode()),
audio_io_thread_(NULL),
opened_(false),
endpoint_render_buffer_size_frames_(0),
endpoint_capture_buffer_size_frames_(0),
num_written_frames_(0),
total_delay_ms_(0.0),
source_(NULL),
capture_bus_(AudioBus::Create(params)),
render_bus_(AudioBus::Create(params)) {
DCHECK(manager_);
DVLOG_IF(1, !HasUnifiedDefaultIO()) << "Unified audio I/O is not supported.";
DVLOG_IF(1, share_mode_ == AUDCLNT_SHAREMODE_EXCLUSIVE)
<< "Core Audio (WASAPI) EXCLUSIVE MODE is enabled.";
#if !defined(NDEBUG)
// Add log message if input parameters are not identical to the preferred
// parameters.
AudioParameters mix_params;
HRESULT hr = CoreAudioUtil::GetPreferredAudioParameters(
eRender, eConsole, &mix_params);
DVLOG_IF(1, SUCCEEDED(hr) && !CompareAudioParameters(params, mix_params)) <<
"Input and preferred parameters are not identical.";
#endif
// Load the Avrt DLL if not already loaded. Required to support MMCSS.
bool avrt_init = avrt::Initialize();
DCHECK(avrt_init) << "Failed to load the avrt.dll";
// Begin with the WAVEFORMATEX structure that specifies the basic format.
WAVEFORMATEX* format = &format_.Format;
format->wFormatTag = WAVE_FORMAT_EXTENSIBLE;
format->nChannels = params.channels();
format->nSamplesPerSec = params.sample_rate();
format->wBitsPerSample = params.bits_per_sample();
format->nBlockAlign = (format->wBitsPerSample / 8) * format->nChannels;
format->nAvgBytesPerSec = format->nSamplesPerSec * format->nBlockAlign;
format->cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
// Add the parts which are unique to WAVE_FORMAT_EXTENSIBLE.
format_.Samples.wValidBitsPerSample = params.bits_per_sample();
format_.dwChannelMask = KSAUDIO_SPEAKER_STEREO;
format_.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
// Store size (in different units) of audio packets which we expect to
// get from the audio endpoint device in each render event.
packet_size_frames_ = params.GetBytesPerBuffer() / format->nBlockAlign;
float packet_size_ms = (1000.0 * packet_size_frames_) / params.sample_rate();
DVLOG(1) << "Number of bytes per audio frame : " << format->nBlockAlign;
DVLOG(1) << "Number of audio frames per packet: " << packet_size_frames_;
DVLOG(1) << "Number of milliseconds per packet: " << packet_size_ms;
// All events are auto-reset events and non-signaled initially.
// Create the event which the audio engine will signal each time a buffer
// has been recorded.
capture_event_.Set(CreateEvent(NULL, FALSE, FALSE, NULL));
// Create the event which will be set in Stop() when straeming shall stop.
stop_streaming_event_.Set(CreateEvent(NULL, FALSE, FALSE, NULL));
}
WASAPIUnifiedStream::~WASAPIUnifiedStream() {
}
bool WASAPIUnifiedStream::Open() {
DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_);
if (opened_)
return true;
if (!HasUnifiedDefaultIO()) {
LOG(ERROR) << "Unified audio I/O is not supported.";
return false;
}
// Render side:
ScopedComPtr<IAudioClient> audio_output_client =
CoreAudioUtil::CreateDefaultClient(eRender, eConsole);
if (!audio_output_client)
return false;
if (!CoreAudioUtil::IsFormatSupported(audio_output_client,
share_mode_,
&format_)) {
return false;
}
HRESULT hr = S_FALSE;
if (share_mode_ == AUDCLNT_SHAREMODE_SHARED) {
hr = CoreAudioUtil::SharedModeInitialize(
audio_output_client, &format_, NULL,
&endpoint_render_buffer_size_frames_);
} else {
// TODO(henrika): add support for AUDCLNT_SHAREMODE_EXCLUSIVE.
}
if (FAILED(hr))
return false;
ScopedComPtr<IAudioRenderClient> audio_render_client =
CoreAudioUtil::CreateRenderClient(audio_output_client);
if (!audio_render_client)
return false;
// Capture side:
ScopedComPtr<IAudioClient> audio_input_client =
CoreAudioUtil::CreateDefaultClient(eCapture, eConsole);
if (!audio_input_client)
return false;
if (!CoreAudioUtil::IsFormatSupported(audio_input_client,
share_mode_,
&format_)) {
return false;
}
if (share_mode_ == AUDCLNT_SHAREMODE_SHARED) {
// Include valid event handle for event-driven initialization.
hr = CoreAudioUtil::SharedModeInitialize(
audio_input_client, &format_, capture_event_.Get(),
&endpoint_capture_buffer_size_frames_);
} else {
// TODO(henrika): add support for AUDCLNT_SHAREMODE_EXCLUSIVE.
}
if (FAILED(hr))
return false;
ScopedComPtr<IAudioCaptureClient> audio_capture_client =
CoreAudioUtil::CreateCaptureClient(audio_input_client);
if (!audio_capture_client)
return false;
// Store all valid COM interfaces.
audio_output_client_ = audio_output_client;
audio_render_client_ = audio_render_client;
audio_input_client_ = audio_input_client;
audio_capture_client_ = audio_capture_client;
opened_ = true;
return SUCCEEDED(hr);
}
void WASAPIUnifiedStream::Start(AudioSourceCallback* callback) {
DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_);
CHECK(callback);
CHECK(opened_);
if (audio_io_thread_.get()) {
CHECK_EQ(callback, source_);
return;
}
source_ = callback;
// Create and start the thread that will capturing and rendering.
audio_io_thread_.reset(
new base::DelegateSimpleThread(this, "wasapi_io_thread"));
audio_io_thread_->Start();
if (!audio_io_thread_->HasBeenStarted()) {
DLOG(ERROR) << "Failed to start WASAPI IO thread.";
return;
}
// Start input streaming data between the endpoint buffer and the audio
// engine.
HRESULT hr = audio_input_client_->Start();
if (FAILED(hr)) {
StopAndJoinThread(hr);
return;
}
// Reset the counter for number of rendered frames taking into account the
// fact that we always initialize the render side with silence.
UINT32 num_queued_frames = 0;
audio_output_client_->GetCurrentPadding(&num_queued_frames);
DCHECK_EQ(num_queued_frames, endpoint_render_buffer_size_frames_);
num_written_frames_ = num_queued_frames;
// Start output streaming data between the endpoint buffer and the audio
// engine.
hr = audio_output_client_->Start();
if (FAILED(hr)) {
StopAndJoinThread(hr);
return;
}
}
void WASAPIUnifiedStream::Stop() {
DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_);
if (!audio_io_thread_.get())
return;
// Stop input audio streaming.
HRESULT hr = audio_input_client_->Stop();
if (FAILED(hr)) {
DLOG_IF(ERROR, hr != AUDCLNT_E_NOT_INITIALIZED)
<< "Failed to stop input streaming: " << std::hex << hr;
}
// Stop output audio streaming.
hr = audio_output_client_->Stop();
if (FAILED(hr)) {
DLOG_IF(ERROR, hr != AUDCLNT_E_NOT_INITIALIZED)
<< "Failed to stop output streaming: " << std::hex << hr;
}
// Wait until the thread completes and perform cleanup.
SetEvent(stop_streaming_event_.Get());
audio_io_thread_->Join();
audio_io_thread_.reset();
// Ensure that we don't quit the main thread loop immediately next
// time Start() is called.
ResetEvent(stop_streaming_event_.Get());
// Clear source callback, it'll be set again on the next Start() call.
source_ = NULL;
// Flush all pending data and reset the audio clock stream position to 0.
hr = audio_output_client_->Reset();
if (FAILED(hr)) {
DLOG_IF(ERROR, hr != AUDCLNT_E_NOT_INITIALIZED)
<< "Failed to reset output streaming: " << std::hex << hr;
}
audio_input_client_->Reset();
if (FAILED(hr)) {
DLOG_IF(ERROR, hr != AUDCLNT_E_NOT_INITIALIZED)
<< "Failed to reset input streaming: " << std::hex << hr;
}
// 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().
// TODO(henrika): this check is is only needed for shared-mode streams.
UINT32 num_queued_frames = 0;
audio_output_client_->GetCurrentPadding(&num_queued_frames);
DCHECK_EQ(0u, num_queued_frames);
}
void WASAPIUnifiedStream::Close() {
DCHECK_EQ(GetCurrentThreadId(), creating_thread_id_);
// It is valid to call Close() before calling open or Start().
// It is also valid to call Close() after Start() has been called.
Stop();
// Inform the audio manager that we have been closed. This will cause our
// destruction.
manager_->ReleaseOutputStream(this);
}
void WASAPIUnifiedStream::SetVolume(double volume) {
NOTIMPLEMENTED();
}
void WASAPIUnifiedStream::GetVolume(double* volume) {
NOTIMPLEMENTED();
}
// static
bool WASAPIUnifiedStream::HasUnifiedDefaultIO() {
AudioParameters in_params;
HRESULT hr = CoreAudioUtil::GetPreferredAudioParameters(eCapture, eConsole,
&in_params);
if (FAILED(hr))
return false;
AudioParameters out_params;
hr = CoreAudioUtil::GetPreferredAudioParameters(eRender, eConsole,
&out_params);
if (FAILED(hr))
return false;
return CompareAudioParameters(in_params, out_params);
}
void WASAPIUnifiedStream::Run() {
ScopedCOMInitializer com_init(ScopedCOMInitializer::kMTA);
// Increase the thread priority.
audio_io_thread_->SetThreadPriority(base::kThreadPriority_RealtimeAudio);
// Enable MMCSS to ensure that this thread receives prioritized access to
// CPU resources.
// TODO(henrika): investigate if it is possible to include these additional
// settings in SetThreadPriority() as well.
DWORD task_index = 0;
HANDLE mm_task = avrt::AvSetMmThreadCharacteristics(L"Pro Audio",
&task_index);
bool mmcss_is_ok =
(mm_task && avrt::AvSetMmThreadPriority(mm_task, AVRT_PRIORITY_CRITICAL));
if (!mmcss_is_ok) {
// Failed to enable MMCSS on this thread. It is not fatal but can lead
// to reduced QoS at high load.
DWORD err = GetLastError();
LOG(WARNING) << "Failed to enable MMCSS (error code=" << err << ").";
}
// The IAudioClock interface enables us to monitor a stream's data
// rate and the current position in the stream. Allocate it before we
// start spinning.
ScopedComPtr<IAudioClock> audio_output_clock;
HRESULT hr = audio_output_client_->GetService(
__uuidof(IAudioClock), audio_output_clock.ReceiveVoid());
LOG_IF(WARNING, FAILED(hr)) << "Failed to create IAudioClock: "
<< std::hex << hr;
// Stores a delay measurement (unit is in bytes). This variable is not
// updated at each event, but the update frequency is set by a constant
// called |kTimeDiffInMillisecondsBetweenDelayMeasurements|.
int total_delay_bytes = 0;
bool streaming = true;
bool error = false;
HANDLE wait_array[] = { stop_streaming_event_,
capture_event_ };
const int bytes_per_sample = format_.Format.wBitsPerSample >> 3;
// Keep streaming audio until the stop, or error, event is signaled.
// The current implementation uses capture events as driving mechanism since
// extensive testing has shown that it gives us a more reliable callback
// sequence compared with a scheme where both capture and render events are
// utilized.
while (streaming && !error) {
// Wait for a close-down event, or a new capture event.
DWORD wait_result = WaitForMultipleObjects(arraysize(wait_array),
wait_array,
FALSE,
INFINITE);
switch (wait_result) {
case WAIT_OBJECT_0 + 0:
// |stop_streaming_event_| has been set.
streaming = false;
break;
case WAIT_OBJECT_0 + 1:
// |capture_event_| has been set
{
TRACE_EVENT0("audio", "WASAPIUnifiedStream::Run");
// --- Capture ---
BYTE* data_ptr = NULL;
UINT32 num_captured_frames = 0;
DWORD flags = 0;
UINT64 device_position = 0;
UINT64 capture_time_stamp = 0;
base::TimeTicks now_tick = base::TimeTicks::HighResNow();
// Retrieve the amount of data in the capture endpoint buffer.
// |endpoint_capture_time_stamp| is the value of the performance
// counter at the time that the audio endpoint device recorded
// the device position of the first audio frame in the data packet.
hr = audio_capture_client_->GetBuffer(&data_ptr,
&num_captured_frames,
&flags,
&device_position,
&capture_time_stamp);
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to get data from the capture buffer";
continue;
}
if (num_captured_frames != 0) {
if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
// Clear out the capture buffer since silence is reported.
capture_bus_->Zero();
} else {
// Store captured data in an audio bus after de-interleaving
// the data to match the audio bus structure.
capture_bus_->FromInterleaved(
data_ptr, num_captured_frames, bytes_per_sample);
}
}
hr = audio_capture_client_->ReleaseBuffer(num_captured_frames);
DLOG_IF(ERROR, FAILED(hr)) << "Failed to release capture buffer";
// Save resource by not asking for new delay estimates each time.
// These estimates are fairly stable and it is perfectly safe to only
// sample at a rate of ~1Hz.
// TODO(henrika): it might be possible to use a fixed delay instead.
if ((now_tick - last_delay_sample_time_).InMilliseconds() >
kTimeDiffInMillisecondsBetweenDelayMeasurements) {
// Calculate the estimated capture delay, i.e., the latency between
// the recording time and the time we when we are notified about
// the recorded data. Note that the capture time stamp is given in
// 100-nanosecond (0.1 microseconds) units.
base::TimeDelta diff = now_tick -
base::TimeTicks::FromInternalValue(0.1 * capture_time_stamp);
const double capture_delay_ms = diff.InMillisecondsF();
// Calculate the estimated render delay, i.e., the time difference
// between the time when data is added to the endpoint buffer and
// when the data is played out on the actual speaker.
const double stream_pos = CurrentStreamPosInMilliseconds(
num_written_frames_ + packet_size_frames_,
format_.Format.nSamplesPerSec);
const double speaker_pos =
SpeakerStreamPosInMilliseconds(audio_output_clock);
const double render_delay_ms = stream_pos - speaker_pos;
// Derive the total delay, i.e., the sum of the input and output
// delays. Also convert the value into byte units.
total_delay_ms_ = capture_delay_ms + render_delay_ms;
last_delay_sample_time_ = now_tick;
DVLOG(3) << "total_delay_ms : " << total_delay_ms_;
total_delay_bytes = MillisecondsToBytes(total_delay_ms_, format_);
}
// Prepare for rendering by calling OnMoreIOData().
int frames_filled = source_->OnMoreIOData(
capture_bus_.get(),
render_bus_.get(),
AudioBuffersState(0, total_delay_bytes));
DCHECK_EQ(frames_filled, render_bus_->frames());
// --- Render ---
// Keep track of number of rendered frames since we need it for
// our delay calculations.
num_written_frames_ += frames_filled;
// Derive the the amount of available space in the endpoint buffer.
// Avoid render attempt if there is no room for a captured packet.
UINT32 num_queued_frames = 0;
audio_output_client_->GetCurrentPadding(&num_queued_frames);
if (endpoint_render_buffer_size_frames_ - num_queued_frames <
packet_size_frames_)
continue;
// Grab all available space in the rendering endpoint buffer
// into which the client can write a data packet.
uint8* audio_data = NULL;
hr = audio_render_client_->GetBuffer(packet_size_frames_,
&audio_data);
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to access render buffer";
continue;
}
// Convert the audio bus content to interleaved integer data using
// |audio_data| as destination.
render_bus_->ToInterleaved(
packet_size_frames_, bytes_per_sample, audio_data);
// Release the buffer space acquired in the GetBuffer() call.
audio_render_client_->ReleaseBuffer(packet_size_frames_, 0);
DLOG_IF(ERROR, FAILED(hr)) << "Failed to release render buffer";
}
break;
default:
error = true;
break;
}
}
if (streaming && error) {
// Stop audio streaming since something has gone wrong in our main thread
// loop. Note that, we are still in a "started" state, hence a Stop() call
// is required to join the thread properly.
audio_input_client_->Stop();
audio_output_client_->Stop();
PLOG(ERROR) << "WASAPI streaming failed.";
}
// Disable MMCSS.
if (mm_task && !avrt::AvRevertMmThreadCharacteristics(mm_task)) {
PLOG(WARNING) << "Failed to disable MMCSS";
}
}
void WASAPIUnifiedStream::HandleError(HRESULT err) {
CHECK((started() && GetCurrentThreadId() == audio_io_thread_->tid()) ||
(!started() && GetCurrentThreadId() == creating_thread_id_));
NOTREACHED() << "Error code: " << std::hex << err;
if (source_)
source_->OnError(this, static_cast<int>(err));
}
void WASAPIUnifiedStream::StopAndJoinThread(HRESULT err) {
CHECK(GetCurrentThreadId() == creating_thread_id_);
DCHECK(audio_io_thread_.get());
SetEvent(stop_streaming_event_.Get());
audio_io_thread_->Join();
audio_io_thread_.reset();
HandleError(err);
}
} // namespace media