| // Copyright 2017 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 <basetyps.h> |
| #include <wrl.h> |
| #include <xaudio2.h> |
| |
| #include <algorithm> |
| #include <limits> |
| #include <list> |
| |
| #include "starboard/atomic.h" |
| #include "starboard/common/condition_variable.h" |
| #include "starboard/common/log.h" |
| #include "starboard/common/mutex.h" |
| #include "starboard/configuration.h" |
| #include "starboard/shared/starboard/audio_sink/audio_sink_internal.h" |
| #include "starboard/shared/starboard/player/job_thread.h" |
| #include "starboard/shared/starboard/thread_checker.h" |
| #include "starboard/thread.h" |
| #include "starboard/time.h" |
| |
| namespace starboard { |
| namespace shared { |
| namespace win32 { |
| namespace { |
| |
| using Microsoft::WRL::ComPtr; |
| |
| const int kMaxBuffersSubmittedPerLoop = 2; |
| |
| // Fails an SB_DCHECK if an HRESULT is not S_OK |
| void CHECK_HRESULT_OK(HRESULT hr) { |
| SB_DCHECK(SUCCEEDED(hr)) << std::hex << hr; |
| } |
| |
| WORD SampleTypeToFormatTag(SbMediaAudioSampleType type) { |
| switch (type) { |
| #if SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES) |
| case kSbMediaAudioSampleTypeInt16: |
| return WAVE_FORMAT_PCM; |
| #endif // SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES) |
| case kSbMediaAudioSampleTypeFloat32: |
| return WAVE_FORMAT_IEEE_FLOAT; |
| default: |
| SB_NOTREACHED(); |
| return 0; |
| } |
| } |
| |
| WORD SampleTypeToBitsPerSample(SbMediaAudioSampleType type) { |
| switch (type) { |
| #if SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES) |
| case kSbMediaAudioSampleTypeInt16: |
| return 16; |
| #endif // SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES) |
| case kSbMediaAudioSampleTypeFloat32: |
| return 32; |
| default: |
| SB_NOTREACHED(); |
| return 0; |
| } |
| } |
| |
| class XAudioAudioSinkType; |
| |
| class XAudioAudioSink : public SbAudioSinkPrivate { |
| public: |
| XAudioAudioSink(XAudioAudioSinkType* type, |
| IXAudio2SourceVoice* source_voice, |
| const WAVEFORMATEX& wfx, |
| SbAudioSinkFrameBuffers frame_buffers, |
| int frame_buffers_size_in_frames, |
| SbAudioSinkUpdateSourceStatusFunc update_source_status_func, |
| ConsumeFramesFunc consume_frames_func, |
| void* context); |
| ~XAudioAudioSink() override{}; |
| |
| void SetSourceVoice(IXAudio2SourceVoice* source_voice) { |
| source_voice_ = source_voice; |
| samples_played_ = 0; |
| submitted_frames_ = 0; |
| if (source_voice_) { |
| CHECK_HRESULT_OK(source_voice_->Start(0)); |
| SbAtomicRelease_Store(&stop_callbacks_, 0); |
| } |
| } |
| |
| bool IsType(Type* type) override; |
| void SetPlaybackRate(double playback_rate) 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; |
| } |
| void SetVolume(double volume) override { |
| ScopedLock lock(mutex_); |
| volume_ = volume; |
| } |
| void Process(); |
| |
| void StopCallbacks() { |
| SbAtomicBarrier_Increment(&stop_callbacks_, 1); |
| // Make sure that any call to Process() returns so we know that |
| // no future callbacks will be invoked. |
| process_mutex_.Acquire(); |
| process_mutex_.Release(); |
| |
| // This must happen on a non-XAudio callback thread. |
| if (source_voice_) { |
| source_voice_->DestroyVoice(); |
| } |
| } |
| const WAVEFORMATEX& GetWaveFormatEx() const { return wfx_; } |
| |
| private: |
| bool AreCallbacksStopped() const { |
| return SbAtomicAcquire_Load(&stop_callbacks_) != 0; |
| } |
| void SubmitSourceBuffer(int offset_in_frames, int count_frames); |
| |
| // If true, this instance's source_voice_ has been destroyed and |
| // future Process() calls should return immediately. |
| SbAtomic32 stop_callbacks_; |
| |
| XAudioAudioSinkType* const type_; |
| const SbAudioSinkUpdateSourceStatusFunc update_source_status_func_; |
| const ConsumeFramesFunc consume_frames_func_; |
| void* const context_; |
| |
| SbAudioSinkFrameBuffers frame_buffers_; |
| const int frame_buffers_size_in_frames_; |
| const WAVEFORMATEX wfx_; |
| |
| // Note: despite some documentation to the contrary, it appears |
| // that IXAudio2SourceVoice cannot be a ComPtr. |
| IXAudio2SourceVoice* source_voice_; |
| |
| // |process_mutex_| is held during Process. Others may rapidly |
| // acquire/release to ensure they wait until the current Process() ends. |
| Mutex process_mutex_; |
| // |mutex_| protects |playback_rate_| and |volume_|. |
| Mutex mutex_; |
| double playback_rate_; |
| double volume_; |
| // The following variables are only used inside Process(). To keep it in the |
| // class simply to allow them to be kept between Process() calls. |
| int submitted_frames_; |
| int samples_played_; |
| int queued_buffers_; |
| bool was_playing_; |
| double current_volume_; |
| }; |
| |
| class XAudioAudioSinkType : public SbAudioSinkPrivate::Type, |
| private IXAudio2EngineCallback { |
| public: |
| XAudioAudioSinkType(); |
| |
| ComPtr<IXAudio2> XAudioCreate(); |
| |
| SbAudioSink 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, |
| SbAudioSinkPrivate::ConsumeFramesFunc consume_frames_func, |
| SbAudioSinkPrivate::ErrorFunc error_func, |
| void* context); |
| |
| bool IsValid(SbAudioSink audio_sink) override { |
| return audio_sink != kSbAudioSinkInvalid && audio_sink->IsType(this); |
| } |
| |
| void Destroy(SbAudioSink audio_sink) override; |
| |
| private: |
| void RestartAudioDevice(); |
| void TryAcquireSinkVoices(); |
| // IXAudio2EngineCallback methods |
| // This function will be called periodically with an interval of ~10ms. |
| void OnProcessingPassStart() override; |
| void OnProcessingPassEnd() override {} |
| void OnCriticalError(HRESULT hr) { |
| SB_LOG(INFO) << "OnCriticalError() called with code " << hr; |
| |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| // The thread id of callbacks will be changed after OnCriticalError() call. |
| thread_checker_.Detach(); |
| |
| if (sink_shutdown_in_progress_) { |
| return; |
| } |
| sink_shutdown_in_progress_ = true; |
| |
| x_audio2_->UnregisterForCallbacks(this); |
| |
| for (XAudioAudioSink* sink : audio_sinks_on_xaudio_callbacks_) { |
| sink->StopCallbacks(); |
| } |
| |
| restart_audio_thread_.job_queue()->Schedule( |
| std::bind(&XAudioAudioSinkType::RestartAudioDevice, this)); |
| } |
| |
| void ProcessSinksToAdd() { |
| if (!audio_sinks_to_add_.empty()) { |
| audio_sinks_on_xaudio_callbacks_.insert( |
| audio_sinks_on_xaudio_callbacks_.end(), audio_sinks_to_add_.begin(), |
| audio_sinks_to_add_.end()); |
| audio_sinks_to_add_.clear(); |
| } |
| } |
| |
| void ProcessSinksToDelete() { |
| if (!audio_sinks_to_delete_.empty()) { |
| for (auto sink : audio_sinks_to_delete_) { |
| audio_sinks_on_xaudio_callbacks_.erase( |
| std::find(audio_sinks_on_xaudio_callbacks_.begin(), |
| audio_sinks_on_xaudio_callbacks_.end(), sink)); |
| delete sink; |
| } |
| audio_sinks_to_delete_.clear(); |
| } |
| } |
| |
| ComPtr<IXAudio2> x_audio2_; |
| Mutex x_audio2_mutex_; |
| IXAudio2MasteringVoice* mastering_voice_ = nullptr; |
| |
| // This mutex protects |audio_sinks_to_add_| and |audio_sinks_to_delete_|. |
| Mutex mutex_; |
| std::list<XAudioAudioSink*> audio_sinks_to_add_; |
| std::list<SbAudioSink> audio_sinks_to_delete_; |
| |
| // This must only be accessed from the OnProcessingPassStart callback |
| std::list<XAudioAudioSink*> audio_sinks_on_xaudio_callbacks_; |
| starboard::ThreadChecker thread_checker_; |
| |
| starboard::player::JobThread restart_audio_thread_; |
| std::list<XAudioAudioSink*> audio_sinks_to_restart_; |
| bool sink_shutdown_in_progress_; |
| }; |
| |
| XAudioAudioSink::XAudioAudioSink( |
| XAudioAudioSinkType* type, |
| IXAudio2SourceVoice* source_voice, |
| const WAVEFORMATEX& wfx, |
| SbAudioSinkFrameBuffers frame_buffers, |
| int frame_buffers_size_in_frames, |
| SbAudioSinkUpdateSourceStatusFunc update_source_status_func, |
| ConsumeFramesFunc consume_frames_func, |
| void* context) |
| : stop_callbacks_(0), |
| type_(type), |
| source_voice_(source_voice), |
| update_source_status_func_(update_source_status_func), |
| consume_frames_func_(consume_frames_func), |
| context_(context), |
| frame_buffers_(frame_buffers), |
| frame_buffers_size_in_frames_(frame_buffers_size_in_frames), |
| wfx_(wfx), |
| playback_rate_(1.0), |
| volume_(1.0), |
| submitted_frames_(0), |
| samples_played_(0), |
| queued_buffers_(0), |
| was_playing_(false), |
| current_volume_(1.0) { |
| CHECK_HRESULT_OK(source_voice_->Stop(0)); |
| } |
| |
| bool XAudioAudioSink::IsType(Type* type) { |
| return type_ == type; |
| } |
| |
| void XAudioAudioSink::Process() { |
| ScopedLock process_lock(process_mutex_); |
| if (AreCallbacksStopped()) { |
| // We must not continue in this case, since |source_voice_| has been |
| // destroyed. |
| return; |
| } |
| int frames_in_buffer, offset_in_frames; |
| bool is_playing, is_eos_reached; |
| bool is_playback_rate_zero = false; |
| bool should_set_volume = false; |
| |
| // This function is run on the XAudio thread and shouldn't be blocked. |
| if (mutex_.AcquireTry()) { |
| is_playback_rate_zero = playback_rate_ == 0.0; |
| should_set_volume = current_volume_ != volume_; |
| current_volume_ = volume_; |
| mutex_.Release(); |
| } |
| |
| if (should_set_volume) { |
| CHECK_HRESULT_OK(source_voice_->SetVolume(current_volume_)); |
| } |
| |
| 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) { |
| return; |
| } |
| 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; |
| |
| 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_frames_func_(consumed_frames_int, SbTimeGetMonotonicNow(), context_); |
| submitted_frames_ -= consumed_frames_int; |
| samples_played_ = voice_state.SamplesPlayed; |
| queued_buffers_ = voice_state.BuffersQueued; |
| } |
| |
| 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)); |
| } |
| |
| XAudioAudioSinkType::XAudioAudioSinkType() |
| : restart_audio_thread_("RestartAudioDevice"), |
| sink_shutdown_in_progress_(false), |
| thread_checker_(starboard::ThreadChecker::kSetThreadIdOnFirstCheck) { |
| x_audio2_ = XAudioCreate(); |
| HRESULT hr = x_audio2_->CreateMasteringVoice(&mastering_voice_); |
| SB_LOG_IF(WARNING, FAILED(hr)) << "Audio failed to CreateMasteringVoice(), " |
| "sound will be disabled."; |
| } |
| |
| ComPtr<IXAudio2> XAudioAudioSinkType::XAudioCreate() { |
| ComPtr<IXAudio2> x_audio2; |
| HRESULT hr = XAudio2Create(&x_audio2, 0, XAUDIO2_DEFAULT_PROCESSOR); |
| if (FAILED(hr)) { |
| return nullptr; |
| } |
| |
| #if !defined(COBALT_BUILD_TYPE_GOLD) |
| XAUDIO2_DEBUG_CONFIGURATION debug_config = {}; |
| debug_config.TraceMask = XAUDIO2_LOG_ERRORS | XAUDIO2_LOG_WARNINGS | |
| XAUDIO2_LOG_INFO | XAUDIO2_LOG_DETAIL | |
| XAUDIO2_LOG_TIMING | XAUDIO2_LOG_LOCKS; |
| debug_config.LogThreadID = TRUE; |
| debug_config.LogFileline = TRUE; |
| debug_config.LogFunctionName = TRUE; |
| debug_config.LogTiming = TRUE; |
| x_audio2->SetDebugConfiguration(&debug_config, NULL); |
| #endif // !defined(COBALT_BUILD_TYPE_GOLD) |
| |
| x_audio2->RegisterForCallbacks(this); |
| return x_audio2; |
| } |
| |
| 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, |
| SbAudioSinkPrivate::ConsumeFramesFunc consume_frames_func, |
| SbAudioSinkPrivate::ErrorFunc error_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; |
| |
| IXAudio2SourceVoice* source_voice = nullptr; |
| HRESULT hr = E_FAIL; |
| { |
| ScopedLock lock(x_audio2_mutex_); |
| if (!x_audio2_) { |
| x_audio2_ = XAudioCreate(); |
| if (!x_audio2_) { |
| SB_DLOG(WARNING) << "Audio failed to XAudioCreate(), " |
| "sound will be disabled."; |
| return nullptr; |
| } |
| hr = x_audio2_->CreateMasteringVoice(&mastering_voice_); |
| if (FAILED(hr)) { |
| SB_DLOG(WARNING) << "Audio failed to CreateMasteringVoice(), " |
| "sound will be disabled."; |
| return nullptr; |
| } |
| } |
| hr = x_audio2_->CreateSourceVoice(&source_voice, &wfx, |
| XAUDIO2_VOICE_NOPITCH, 1.f); |
| } |
| if (FAILED(hr)) { |
| SB_DLOG(WARNING) << "Could not create source voice, error code: " << hr; |
| return nullptr; |
| } |
| |
| XAudioAudioSink* audio_sink = new XAudioAudioSink( |
| this, source_voice, wfx, frame_buffers, frame_buffers_size_in_frames, |
| update_source_status_func, consume_frames_func, context); |
| |
| ScopedLock lock(mutex_); |
| audio_sinks_to_add_.push_back(audio_sink); |
| return audio_sink; |
| } |
| |
| void XAudioAudioSinkType::Destroy(SbAudioSink audio_sink) { |
| if (audio_sink == kSbAudioSinkInvalid) { |
| return; |
| } |
| if (!IsValid(audio_sink)) { |
| SB_LOG(WARNING) << "audio_sink is invalid."; |
| return; |
| } |
| // Previous versions of this code waited for the next OnProcessingPassStart() |
| // call to occur before returning. However, various circumstances could |
| // cause that never to happen, especially during UWP suspend. |
| // Instead, we return immediately, ensuring no SbAudioSink callbacks occur |
| // and postpone the delete itself until the next OnProcessingPassStart() |
| static_cast<XAudioAudioSink*>(audio_sink)->StopCallbacks(); |
| |
| ScopedLock lock(mutex_); |
| auto it = std::find(audio_sinks_to_restart_.begin(), |
| audio_sinks_to_restart_.end(), audio_sink); |
| if (it == audio_sinks_to_restart_.end()) { |
| audio_sinks_to_delete_.push_back(audio_sink); |
| } else { |
| audio_sinks_to_restart_.erase(it); |
| delete audio_sink; |
| } |
| } |
| |
| void XAudioAudioSinkType::RestartAudioDevice() { |
| { |
| ScopedLock lock(mutex_); |
| audio_sinks_to_restart_.insert(audio_sinks_to_restart_.end(), |
| audio_sinks_on_xaudio_callbacks_.begin(), |
| audio_sinks_on_xaudio_callbacks_.end()); |
| audio_sinks_on_xaudio_callbacks_.clear(); |
| audio_sinks_to_restart_.insert(audio_sinks_to_restart_.end(), |
| audio_sinks_to_add_.begin(), |
| audio_sinks_to_add_.end()); |
| audio_sinks_to_add_.clear(); |
| |
| ProcessSinksToDelete(); |
| for (auto sink : audio_sinks_to_restart_) { |
| sink->SetSourceVoice(nullptr); |
| } |
| } |
| if (mastering_voice_) { |
| mastering_voice_->DestroyVoice(); |
| mastering_voice_ = nullptr; |
| } |
| { |
| ScopedLock lock(x_audio2_mutex_); |
| x_audio2_.Reset(); |
| } |
| sink_shutdown_in_progress_ = false; |
| |
| TryAcquireSinkVoices(); |
| } |
| |
| void XAudioAudioSinkType::TryAcquireSinkVoices() { |
| HRESULT hr = E_FAIL; |
| ScopedLock x_audio2_mutex_lock(x_audio2_mutex_); |
| if (!x_audio2_) { |
| hr = XAudio2Create(&x_audio2_, 0, XAUDIO2_DEFAULT_PROCESSOR); |
| if (FAILED(hr)) { |
| restart_audio_thread_.job_queue()->Schedule( |
| std::bind(&XAudioAudioSinkType::TryAcquireSinkVoices, this)); |
| return; |
| } |
| x_audio2_->RegisterForCallbacks(this); |
| } |
| |
| if (!mastering_voice_) { |
| hr = x_audio2_->CreateMasteringVoice(&mastering_voice_); |
| if (FAILED(hr)) { |
| restart_audio_thread_.job_queue()->Schedule( |
| std::bind(&XAudioAudioSinkType::TryAcquireSinkVoices, this)); |
| return; |
| } |
| } |
| |
| std::list<XAudioAudioSink*> audio_sinks_failed; |
| ScopedLock mutex_lock(mutex_); |
| for (auto sink : audio_sinks_to_restart_) { |
| IXAudio2SourceVoice* source_voice = nullptr; |
| hr = x_audio2_->CreateSourceVoice(&source_voice, &sink->GetWaveFormatEx(), |
| XAUDIO2_VOICE_NOPITCH, 1.f); |
| if (FAILED(hr)) { |
| audio_sinks_failed.push_back(sink); |
| continue; |
| } |
| sink->SetSourceVoice(source_voice); |
| audio_sinks_to_add_.push_back(sink); |
| } |
| audio_sinks_to_restart_ = audio_sinks_failed; |
| if (audio_sinks_to_restart_.empty()) { |
| return; |
| } |
| restart_audio_thread_.job_queue()->Schedule( |
| std::bind(&XAudioAudioSinkType::TryAcquireSinkVoices, this)); |
| } |
| |
| void XAudioAudioSinkType::OnProcessingPassStart() { |
| SB_DCHECK(thread_checker_.CalledOnValidThread()); |
| if (mutex_.AcquireTry()) { |
| ProcessSinksToAdd(); |
| ProcessSinksToDelete(); |
| mutex_.Release(); |
| } |
| |
| for (XAudioAudioSink* sink : audio_sinks_on_xaudio_callbacks_) { |
| sink->Process(); |
| } |
| } |
| |
| } // namespace |
| } // 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; |
| } |