blob: c5d8de2d9c862f96663e0e9f17fcb71b1d2df6d6 [file] [log] [blame]
// Copyright 2017 Google Inc. 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/win32/audio_sink.h"
#include <basetyps.h>
#include <wrl.h>
#include <xaudio2.h>
#include <limits>
#include <sstream>
#include <string>
#include "starboard/configuration.h"
#include "starboard/log.h"
#include "starboard/mutex.h"
#include "starboard/thread.h"
#include "starboard/time.h"
using Microsoft::WRL::ComPtr;
namespace {
// Fails an SB_DCHECK if an HRESULT is not S_OK
void CHECK_HRESULT_OK(HRESULT hr) {
SB_DCHECK(SUCCEEDED(hr)) << std::hex << hr;
}
const int kMaxBuffersSubmittedPerLoop = 2;
std::string GenerateThreadName() {
static int s_count = 0;
std::stringstream ss;
ss << "AudioOut_" << s_count++;
return ss.str();
}
} // namespace.
namespace starboard {
namespace shared {
namespace win32 {
class XAudioAudioSink : public SbAudioSinkPrivate {
public:
XAudioAudioSink(Type* type,
const WAVEFORMATEX& wfx,
SbAudioSinkFrameBuffers frame_buffers,
int frame_buffers_size_in_frames,
SbAudioSinkUpdateSourceStatusFunc update_source_status_func,
SbAudioSinkConsumeFramesFunc consume_frame_func,
void* context);
~XAudioAudioSink() SB_OVERRIDE;
bool IsType(Type* type) SB_OVERRIDE { return type_ == type; }
void SetPlaybackRate(double playback_rate) SB_OVERRIDE {
SB_DCHECK(playback_rate >= 0.0);
if (playback_rate != 0.0 && playback_rate != 1.0) {
SB_NOTIMPLEMENTED() << "TODO: Only playback rates of 0.0 and 1.0 are "
"currently supported.";
playback_rate = (playback_rate > 0.0) ? 1.0 : 0.0;
}
ScopedLock lock(mutex_);
playback_rate_ = playback_rate;
}
private:
static void* ThreadEntryPoint(void* context);
void AudioThreadFunc();
void SubmitSourceBuffer(int offset_in_frames, int count_frames);
XAudioAudioSinkType* type_;
SbAudioSinkUpdateSourceStatusFunc update_source_status_func_;
SbAudioSinkConsumeFramesFunc consume_frame_func_;
void* context_;
SbThread audio_out_thread_;
SbAudioSinkFrameBuffers frame_buffers_;
int frame_buffers_size_in_frames_;
WAVEFORMATEX wfx_;
// Note: despite some documentation to the contrary, it appears
// that IXAudio2SourceVoice cannot be a ComPtr.
IXAudio2SourceVoice* source_voice_;
// mutex_ protects only destroying_ and playback_rate_.
// Everything else is immutable
// after the constructor.
::starboard::Mutex mutex_;
bool destroying_;
double playback_rate_;
};
XAudioAudioSink::XAudioAudioSink(
Type* type,
const WAVEFORMATEX& wfx,
SbAudioSinkFrameBuffers frame_buffers,
int frame_buffers_size_in_frames,
SbAudioSinkUpdateSourceStatusFunc update_source_status_func,
SbAudioSinkConsumeFramesFunc consume_frame_func,
void* context)
: type_(static_cast<XAudioAudioSinkType*>(type)),
update_source_status_func_(update_source_status_func),
consume_frame_func_(consume_frame_func),
context_(context),
audio_out_thread_(kSbThreadInvalid),
frame_buffers_(frame_buffers),
frame_buffers_size_in_frames_(frame_buffers_size_in_frames),
wfx_(wfx),
destroying_(false),
playback_rate_(1.0) {
// TODO: Check MaxFrequencyRatio
CHECK_HRESULT_OK(
type_->x_audio2_->CreateSourceVoice(&source_voice_, &wfx, 0,
/*MaxFrequencyRatio = */ 1.0));
CHECK_HRESULT_OK(source_voice_->Stop(0));
std::string thread_name = GenerateThreadName();
audio_out_thread_ = SbThreadCreate(
0, kSbThreadPriorityRealTime, kSbThreadNoAffinity, true,
thread_name.c_str(), &XAudioAudioSink::ThreadEntryPoint, this);
SB_DCHECK(SbThreadIsValid(audio_out_thread_));
}
XAudioAudioSink::~XAudioAudioSink() {
{
ScopedLock lock(mutex_);
destroying_ = true;
}
SbThreadJoin(audio_out_thread_, nullptr);
source_voice_->DestroyVoice();
}
// static
void* XAudioAudioSink::ThreadEntryPoint(void* context) {
SB_DCHECK(context);
XAudioAudioSink* sink = static_cast<XAudioAudioSink*>(context);
sink->AudioThreadFunc();
return nullptr;
}
void XAudioAudioSink::SubmitSourceBuffer(int offset_in_frames,
int count_frames) {
XAUDIO2_BUFFER audio_buffer_info;
audio_buffer_info.Flags = 0;
audio_buffer_info.AudioBytes = wfx_.nChannels *
frame_buffers_size_in_frames_ *
(wfx_.wBitsPerSample / 8);
audio_buffer_info.pAudioData = static_cast<const BYTE*>(frame_buffers_[0]);
audio_buffer_info.PlayBegin = offset_in_frames;
audio_buffer_info.PlayLength = count_frames;
audio_buffer_info.LoopBegin = 0;
audio_buffer_info.LoopLength = 0;
audio_buffer_info.LoopCount = 0;
audio_buffer_info.pContext = nullptr;
CHECK_HRESULT_OK(source_voice_->SubmitSourceBuffer(&audio_buffer_info));
}
void XAudioAudioSink::AudioThreadFunc() {
const int kMaxFramesToConsumePerRequest = 1024;
int submitted_frames = 0;
uint64_t samples_played = 0;
int queued_buffers = 0;
bool was_playing = false; // The player starts out playing by default.
for (;;) {
{
ScopedLock lock(mutex_);
if (destroying_) {
break;
}
}
int frames_in_buffer, offset_in_frames;
bool is_playing, is_eos_reached;
bool is_playback_rate_zero;
{
ScopedLock lock(mutex_);
is_playback_rate_zero = playback_rate_ == 0.0;
}
update_source_status_func_(&frames_in_buffer, &offset_in_frames,
&is_playing, &is_eos_reached, context_);
if (is_playback_rate_zero) {
is_playing = false;
}
if (is_playing != was_playing) {
if (is_playing) {
CHECK_HRESULT_OK(source_voice_->Start(0));
} else {
CHECK_HRESULT_OK(source_voice_->Stop(0));
}
}
was_playing = is_playing;
// TODO: make sure that frames_in_buffer is large enough
// that it exceeds the voice state pool interval
if (!is_playing || frames_in_buffer == 0 || is_playback_rate_zero) {
SbThreadSleep(kSbTimeMillisecond * 5);
continue;
}
int unsubmitted_frames = frames_in_buffer - submitted_frames;
int unsubmitted_start =
(offset_in_frames + submitted_frames) % frame_buffers_size_in_frames_;
if (unsubmitted_frames == 0 ||
queued_buffers +
kMaxBuffersSubmittedPerLoop > XAUDIO2_MAX_QUEUED_BUFFERS) {
// submit nothing
} else if (unsubmitted_start + unsubmitted_frames <=
frame_buffers_size_in_frames_) {
SubmitSourceBuffer(unsubmitted_start, unsubmitted_frames);
} else {
int count_tail_frames = frame_buffers_size_in_frames_ - unsubmitted_start;
// Note since we can submit up to two source buffers at a time,
// kMaxBuffersSubmittedPerLoop = 2.
SubmitSourceBuffer(unsubmitted_start, count_tail_frames);
SubmitSourceBuffer(0, unsubmitted_frames - count_tail_frames);
}
submitted_frames = frames_in_buffer;
SbThreadSleep(kSbTimeMillisecond);
XAUDIO2_VOICE_STATE voice_state;
source_voice_->GetState(&voice_state);
int64_t consumed_frames = voice_state.SamplesPlayed - samples_played;
SB_DCHECK(consumed_frames >= 0);
SB_DCHECK(consumed_frames < std::numeric_limits<int>::max());
int consumed_frames_int = static_cast<int>(consumed_frames);
consume_frame_func_(consumed_frames_int, context_);
submitted_frames -= consumed_frames_int;
samples_played = voice_state.SamplesPlayed;
queued_buffers = voice_state.BuffersQueued;
}
}
namespace {
WORD SampleTypeToFormatTag(SbMediaAudioSampleType type) {
switch (type) {
case kSbMediaAudioSampleTypeInt16:
return WAVE_FORMAT_PCM;
case kSbMediaAudioSampleTypeFloat32:
return WAVE_FORMAT_IEEE_FLOAT;
default:
SB_NOTREACHED();
return 0;
}
}
WORD SampleTypeToBitsPerSample(SbMediaAudioSampleType type) {
switch (type) {
case kSbMediaAudioSampleTypeInt16:
return 16;
case kSbMediaAudioSampleTypeFloat32:
return 32;
default:
SB_NOTREACHED();
return 0;
}
}
} // namespace
XAudioAudioSinkType::XAudioAudioSinkType() {
CHECK_HRESULT_OK(XAudio2Create(&x_audio2_, 0, XAUDIO2_DEFAULT_PROCESSOR));
CHECK_HRESULT_OK(x_audio2_->CreateMasteringVoice(&mastering_voice_));
}
SbAudioSink XAudioAudioSinkType::Create(
int channels,
int sampling_frequency_hz,
SbMediaAudioSampleType audio_sample_type,
SbMediaAudioFrameStorageType audio_frame_storage_type,
SbAudioSinkFrameBuffers frame_buffers,
int frame_buffers_size_in_frames,
SbAudioSinkUpdateSourceStatusFunc update_source_status_func,
SbAudioSinkConsumeFramesFunc consume_frames_func,
void* context) {
SB_DCHECK(audio_frame_storage_type ==
kSbMediaAudioFrameStorageTypeInterleaved);
WAVEFORMATEX wfx;
wfx.wFormatTag = SampleTypeToFormatTag(audio_sample_type);
wfx.nChannels = static_cast<WORD>(channels);
wfx.nSamplesPerSec = sampling_frequency_hz;
wfx.nAvgBytesPerSec = channels *
SampleTypeToBitsPerSample(audio_sample_type) *
sampling_frequency_hz / 8;
wfx.wBitsPerSample = SampleTypeToBitsPerSample(audio_sample_type);
wfx.nBlockAlign = static_cast<WORD>((channels * wfx.wBitsPerSample) / 8);
wfx.cbSize = 0;
return new XAudioAudioSink(
this, wfx, frame_buffers, frame_buffers_size_in_frames,
update_source_status_func, consume_frames_func, context);
}
} // namespace win32
} // namespace shared
} // namespace starboard
namespace {
SbAudioSinkPrivate::Type* audio_sink_;
} // namespace
// static
void SbAudioSinkPrivate::PlatformInitialize() {
SB_DCHECK(!audio_sink_);
audio_sink_ = new starboard::shared::win32::XAudioAudioSinkType();
SetPrimaryType(audio_sink_);
EnableFallbackToStub();
}
// static
void SbAudioSinkPrivate::PlatformTearDown() {
SB_DCHECK(audio_sink_ == GetPrimaryType());
SetPrimaryType(nullptr);
delete audio_sink_;
audio_sink_ = nullptr;
}