blob: afd778fb6379ac2441d91c372c59a75a4a9d1d10 [file] [log] [blame]
// Copyright 2018 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "starboard/shared/uwp/wasapi_audio.h"
#include <mfapi.h>
#include "starboard/common/atomic.h"
#include "starboard/common/log.h"
#include "starboard/common/mutex.h"
#include "starboard/once.h"
#include "starboard/shared/starboard/media/mime_supportability_cache.h"
#include "starboard/shared/win32/wasapi_include.h"
namespace starboard {
namespace shared {
namespace uwp {
using Microsoft::WRL::ComPtr;
using ::starboard::shared::starboard::media::MimeSupportabilityCache;
using Windows::Foundation::TypedEventHandler;
using Windows::Media::Devices::DefaultAudioRenderDeviceChangedEventArgs;
using Windows::Media::Devices::MediaDevice;
namespace {
const int kWaitForActivateTimeout = 500; // 0.5 sec
void SetPassthroughWaveformat(WAVEFORMATEXTENSIBLE* wfext,
SbMediaAudioCodec audio_codec) {
SB_DCHECK(audio_codec == kSbMediaAudioCodecAc3 ||
audio_codec == kSbMediaAudioCodecEac3);
wfext->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
wfext->Format.nChannels = kIec60958Channels;
wfext->Format.wBitsPerSample = kIec60958BitsPerSample;
wfext->Format.nSamplesPerSec = audio_codec == kSbMediaAudioCodecAc3
? kAc3SamplesPerSecond
: kEac3SamplesPerSecond;
wfext->Format.nBlockAlign = kIec60958BlockAlign;
wfext->Format.nAvgBytesPerSec =
wfext->Format.nSamplesPerSec * wfext->Format.nBlockAlign;
wfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
wfext->Samples.wValidBitsPerSample = wfext->Format.wBitsPerSample;
wfext->dwChannelMask = KSAUDIO_SPEAKER_DIRECTOUT;
wfext->SubFormat = audio_codec == kSbMediaAudioCodecAc3
? MFAudioFormat_Dolby_AC3_SPDIF
: KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS;
}
class DefaultAudioRenderParams {
public:
DefaultAudioRenderParams() {
device_changed_token_ = MediaDevice::DefaultAudioRenderDeviceChanged +=
ref new TypedEventHandler<Platform::Object ^,
DefaultAudioRenderDeviceChangedEventArgs ^>(
[this](Platform::Object ^ sender,
DefaultAudioRenderDeviceChangedEventArgs ^ args) {
OnDefaultAudioRenderDeviceChanged(sender, args);
});
}
~DefaultAudioRenderParams() {
MediaDevice::DefaultAudioRenderDeviceChanged -= device_changed_token_;
}
int GetBitrate() {
ScopedLock lock(mutex_);
RefreshCachedParamsIfNeeded_Locked();
return cached_bitrate_;
}
int GetNumChannels() {
ScopedLock lock(mutex_);
RefreshCachedParamsIfNeeded_Locked();
return cached_channels_;
}
bool GetPassthroughCodecSupport(SbMediaAudioCodec audio_codec) {
SB_DCHECK(audio_codec == kSbMediaAudioCodecAc3 ||
audio_codec == kSbMediaAudioCodecEac3);
ScopedLock lock(mutex_);
RefreshCachedParamsIfNeeded_Locked();
return audio_codec == kSbMediaAudioCodecAc3 ? supports_ac3_passthrough_
: supports_eac3_passthrough_;
}
private:
atomic_bool is_dirty_{true};
Mutex mutex_;
int cached_bitrate_ = 0;
int cached_channels_ = 0;
bool supports_ac3_passthrough_ = false;
bool supports_eac3_passthrough_ = false;
Windows::Foundation::EventRegistrationToken device_changed_token_{};
void OnDefaultAudioRenderDeviceChanged(
Platform::Object ^ sender,
Windows::Media::Devices::DefaultAudioRenderDeviceChangedEventArgs ^
event_args) {
is_dirty_.store(true);
MimeSupportabilityCache::GetInstance()->ClearCachedMimeSupportabilities();
}
void RefreshCachedParamsIfNeeded_Locked() {
if (is_dirty_.load()) {
Microsoft::WRL::ComPtr<WASAPIAudioDevice> audio_device =
Microsoft::WRL::Make<WASAPIAudioDevice>();
cached_bitrate_ = audio_device->GetBitrate();
cached_channels_ = audio_device->GetNumChannels();
supports_ac3_passthrough_ =
audio_device->GetPassthroughCodecSupport(kSbMediaAudioCodecAc3);
supports_eac3_passthrough_ =
audio_device->GetPassthroughCodecSupport(kSbMediaAudioCodecEac3);
// when a timeout occurres before WASAPIAudioDevice::ActivateCompleted()
// has been called the cached values are not set with correct values. We
// need to initializa again for getting correct values nex time.
if (cached_bitrate_ != kInitialValue && cached_channels_ != kInitialValue)
is_dirty_.store(false);
}
}
};
SB_ONCE_INITIALIZE_FUNCTION(DefaultAudioRenderParams,
GetDefaultAudioRenderParams);
} // namespace
// static
int WASAPIAudioDevice::GetCachedBitrateOfDefaultAudioRenderer() {
return GetDefaultAudioRenderParams()->GetBitrate();
}
// static
int WASAPIAudioDevice::GetCachedNumChannelsOfDefaultAudioRenderer() {
return GetDefaultAudioRenderParams()->GetNumChannels();
}
// static
bool WASAPIAudioDevice::GetPassthroughSupportOfDefaultAudioRenderer(
SbMediaAudioCodec audio_codec) {
return GetDefaultAudioRenderParams()->GetPassthroughCodecSupport(audio_codec);
}
WASAPIAudioDevice::WASAPIAudioDevice() {
InitializeAudioDevice();
}
WASAPIAudioDevice::~WASAPIAudioDevice() {
CloseHandle(activate_completed_);
}
void WASAPIAudioDevice::InitializeAudioDevice() {
ComPtr<IActivateAudioInterfaceAsyncOperation> async_operation;
// Default Audio Device Renderer
Platform::String ^ deviceIdString = MediaDevice::GetDefaultAudioRenderId(
Windows::Media::Devices::AudioDeviceRole::Default);
if (FAILED(ActivateAudioInterfaceAsync(deviceIdString->Data(),
__uuidof(IAudioClient3), nullptr, this,
&async_operation))) {
return;
}
WaitForSingleObject(activate_completed_, kWaitForActivateTimeout);
}
HRESULT WASAPIAudioDevice::ActivateCompleted(
IActivateAudioInterfaceAsyncOperation* operation) {
HRESULT hr = S_OK;
HRESULT hr_activate = S_OK;
ComPtr<IUnknown> audio_interface;
// Check for a successful activation result
hr = operation->GetActivateResult(&hr_activate, &audio_interface);
if (SUCCEEDED(hr) && SUCCEEDED(hr_activate)) {
// Get the pointer for the Audio Client.
hr = audio_interface->QueryInterface(IID_PPV_ARGS(&audio_client_));
SB_DCHECK(audio_client_);
if (SUCCEEDED(hr)) {
AudioClientProperties audio_props = {0};
audio_props.cbSize = sizeof(AudioClientProperties);
audio_props.bIsOffload = false;
audio_props.eCategory = AudioCategory_Media;
hr = audio_client_->SetClientProperties(&audio_props);
if (SUCCEEDED(hr)) {
WAVEFORMATEX* format;
hr = audio_client_->GetMixFormat(&format);
SB_DCHECK(format);
if (SUCCEEDED(hr)) {
bitrate_ = format->nSamplesPerSec * format->wBitsPerSample;
channels_ = format->nChannels;
CoTaskMemFree(format);
if (channels_ > 2) {
WAVEFORMATEXTENSIBLE passthrough_format;
SetPassthroughWaveformat(&passthrough_format,
kSbMediaAudioCodecAc3);
hr = audio_client_->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE,
&passthrough_format.Format,
nullptr);
supports_ac3_passthrough_ = hr == S_OK;
SetPassthroughWaveformat(&passthrough_format,
kSbMediaAudioCodecEac3);
hr = audio_client_->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE,
&passthrough_format.Format,
nullptr);
supports_eac3_passthrough_ = hr == S_OK;
}
} // audio_client_->GetMixFormat
} // audio_client_->SetClientProperties
} // audio_interface->QueryInterface
}
audio_client_ = nullptr;
SetEvent(activate_completed_);
// Need to return S_OK
return S_OK;
}
} // namespace uwp
} // namespace shared
} // namespace starboard