Add XAudio2
This commit is contained in:
parent
80e8ad0103
commit
3dfea0c315
5 changed files with 730 additions and 14 deletions
|
@ -497,7 +497,7 @@ set_property(SOURCE src/subtitles_provider_csri.cpp PROPERTY INCLUDE_DIRECTORIES
|
|||
if(MSVC)
|
||||
target_link_libraries (Aegisub dsound)
|
||||
add_definitions("-DWITH_DIRECTSOUND")
|
||||
target_sources(Aegisub PRIVATE src/audio_player_dsound.cpp src/audio_player_dsound2.cpp)
|
||||
target_sources(Aegisub PRIVATE src/audio_player_dsound.cpp src/audio_player_dsound2.cpp src/audio_player_xaudio2.cpp)
|
||||
endif(MSVC)
|
||||
|
||||
find_package(FFMS2)
|
||||
|
|
41
meson.build
41
meson.build
|
@ -237,20 +237,37 @@ if host_machine.system() == 'windows' and get_option('avisynth').enabled()
|
|||
deps_inc += avs.include_directories('AviSynth-Headers')
|
||||
endif
|
||||
|
||||
if host_machine.system() == 'windows' and not get_option('directsound').disabled()
|
||||
dsound_dep = cc.find_library('dsound', required: get_option('directsound'))
|
||||
winmm_dep = cc.find_library('winmm', required: get_option('directsound'))
|
||||
ole32_dep = cc.find_library('ole32', required: get_option('directsound'))
|
||||
have_dsound_h = cc.has_header('dsound.h')
|
||||
if not have_dsound_h and get_option('directsound').enabled()
|
||||
error('DirectSound enabled but dsound.h not found')
|
||||
if host_machine.system() == 'windows'
|
||||
|
||||
if not get_option('directsound').disabled()
|
||||
dsound_dep = cc.find_library('dsound', required: get_option('directsound'))
|
||||
winmm_dep = cc.find_library('winmm', required: get_option('directsound'))
|
||||
ole32_dep = cc.find_library('ole32', required: get_option('directsound'))
|
||||
have_dsound_h = cc.has_header('dsound.h')
|
||||
if not have_dsound_h and get_option('directsound').enabled()
|
||||
error('DirectSound enabled but dsound.h not found')
|
||||
endif
|
||||
|
||||
dxguid_dep = cc.find_library('dxguid', required: true)
|
||||
if dsound_dep.found() and winmm_dep.found() and ole32_dep.found() and dxguid_dep.found() and have_dsound_h
|
||||
deps += [dsound_dep, winmm_dep, ole32_dep, dxguid_dep]
|
||||
conf.set('WITH_DIRECTSOUND', 1)
|
||||
dep_avail += 'DirectSound'
|
||||
endif
|
||||
endif
|
||||
|
||||
dxguid_dep = cc.find_library('dxguid', required: true)
|
||||
if dsound_dep.found() and winmm_dep.found() and ole32_dep.found() and dxguid_dep.found() and have_dsound_h
|
||||
deps += [dsound_dep, winmm_dep, ole32_dep, dxguid_dep]
|
||||
conf.set('WITH_DIRECTSOUND', 1)
|
||||
dep_avail += 'DirectSound'
|
||||
if not get_option('xaudio2').disabled()
|
||||
have_xaudio_h = cc.has_header('xaudio2.h')
|
||||
xaudio2_dep = cc.find_library('xaudio2', required: true)
|
||||
if have_xaudio_h
|
||||
deps += [xaudio2_dep]
|
||||
conf.set('WITH_XAUDIO2', 1)
|
||||
dep_avail += 'XAudio2'
|
||||
endif
|
||||
|
||||
if not have_dsound_h and get_option('xaudio2').enabled()
|
||||
error('xaudio2 enabled but xaudio2.h not found')
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@ option('openal', type: 'feature', description: 'OpenAL audio output')
|
|||
option('libpulse', type: 'feature', description: 'PulseAudio audio output')
|
||||
option('portaudio', type: 'feature', description: 'PortAudio audio output')
|
||||
option('directsound', type: 'feature', description: 'DirectSound audio output')
|
||||
option('default_audio_output', type: 'combo', choices: ['auto', 'ALSA', 'OpenAL', 'PulseAudio', 'PortAudio', 'DirectSound'], description: 'Default audio output')
|
||||
option('xaudio2', type: 'feature', description: 'XAudio2 audio output')
|
||||
option('default_audio_output', type: 'combo', choices: ['auto', 'ALSA', 'OpenAL', 'PulseAudio', 'PortAudio', 'DirectSound', 'XAudio2'], description: 'Default audio output')
|
||||
|
||||
option('ffms2', type: 'feature', description: 'FFMS2 video source')
|
||||
option('avisynth', type: 'feature', description: 'AviSynth video source')
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
std::unique_ptr<AudioPlayer> CreateAlsaPlayer(agi::AudioProvider *providers, wxWindow *window);
|
||||
std::unique_ptr<AudioPlayer> CreateDirectSoundPlayer(agi::AudioProvider *providers, wxWindow *window);
|
||||
std::unique_ptr<AudioPlayer> CreateDirectSound2Player(agi::AudioProvider *providers, wxWindow *window);
|
||||
std::unique_ptr<AudioPlayer> CreateXAudio2Player(agi::AudioProvider *providers, wxWindow *window);
|
||||
std::unique_ptr<AudioPlayer> CreateOpenALPlayer(agi::AudioProvider *providers, wxWindow *window);
|
||||
std::unique_ptr<AudioPlayer> CreatePortAudioPlayer(agi::AudioProvider *providers, wxWindow *window);
|
||||
std::unique_ptr<AudioPlayer> CreatePulseAudioPlayer(agi::AudioProvider *providers, wxWindow *window);
|
||||
|
@ -63,6 +64,9 @@ namespace {
|
|||
{"DirectSound-old", CreateDirectSoundPlayer, false},
|
||||
{"DirectSound", CreateDirectSound2Player, false},
|
||||
#endif
|
||||
#ifdef WITH_XAUDIO2
|
||||
{"Xaudio2", CreateXAudio2Player, false},
|
||||
#endif
|
||||
#ifdef WITH_OPENAL
|
||||
{"OpenAL", CreateOpenALPlayer, false},
|
||||
#endif
|
||||
|
|
694
src/audio_player_xaudio2.cpp
Normal file
694
src/audio_player_xaudio2.cpp
Normal file
|
@ -0,0 +1,694 @@
|
|||
// Copyright (c) 2019, Qirui Wang
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
// * Neither the name of the Aegisub Group nor the names of its contributors
|
||||
// may be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
|
||||
#ifdef WITH_XAUDIO2
|
||||
#include "include/aegisub/audio_player.h"
|
||||
|
||||
#include "options.h"
|
||||
|
||||
#include <libaegisub/audio/provider.h>
|
||||
#include <libaegisub/scoped_ptr.h>
|
||||
#include <libaegisub/log.h>
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
#ifndef XAUDIO2_REDIST
|
||||
#include <xaudio2.h>
|
||||
#else
|
||||
#include <xaudio2redist.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
class XAudio2Thread;
|
||||
|
||||
/// @class XAudio2Player
|
||||
/// @brief XAudio2-based audio player
|
||||
///
|
||||
/// The core design idea is to have a playback thread that performs all playback operations, and use the player object as a proxy to send commands to the playback thread.
|
||||
class XAudio2Player final : public AudioPlayer {
|
||||
/// The playback thread
|
||||
std::unique_ptr<XAudio2Thread> thread;
|
||||
|
||||
/// Desired length in milliseconds to write ahead of the playback cursor
|
||||
int WantedLatency;
|
||||
|
||||
/// Multiplier for WantedLatency to get total buffer length
|
||||
int BufferLength;
|
||||
|
||||
/// @brief Tell whether playback thread is alive
|
||||
/// @return True if there is a playback thread and it's ready
|
||||
bool IsThreadAlive();
|
||||
|
||||
public:
|
||||
/// @brief Constructor
|
||||
XAudio2Player(agi::AudioProvider* provider);
|
||||
/// @brief Destructor
|
||||
~XAudio2Player() = default;
|
||||
|
||||
/// @brief Start playback
|
||||
/// @param start First audio frame to play
|
||||
/// @param count Number of audio frames to play
|
||||
void Play(int64_t start, int64_t count);
|
||||
|
||||
/// @brief Stop audio playback
|
||||
/// @param timerToo Whether to also stop the playback update timer
|
||||
void Stop();
|
||||
|
||||
/// @brief Tell whether playback is active
|
||||
/// @return True if audio is playing back
|
||||
bool IsPlaying();
|
||||
|
||||
/// @brief Get playback end position
|
||||
/// @return Audio frame index
|
||||
///
|
||||
/// Returns 0 if playback is stopped or there is no playback thread
|
||||
int64_t GetEndPosition();
|
||||
/// @brief Get approximate playback position
|
||||
/// @return Index of audio frame user is currently hearing
|
||||
///
|
||||
/// Returns 0 if playback is stopped or there is no playback thread
|
||||
int64_t GetCurrentPosition();
|
||||
|
||||
/// @brief Change playback end position
|
||||
/// @param pos New end position
|
||||
void SetEndPosition(int64_t pos);
|
||||
|
||||
/// @brief Change playback volume
|
||||
/// @param vol Amplification factor
|
||||
void SetVolume(double vol);
|
||||
};
|
||||
|
||||
/// @brief RAII support class to init and de-init the COM library
|
||||
struct COMInitialization {
|
||||
|
||||
/// Flag set if an inited COM library is managed
|
||||
bool inited = false;
|
||||
|
||||
/// @brief Destructor, de-inits COM if it is inited
|
||||
~COMInitialization() {
|
||||
if (inited) CoUninitialize();
|
||||
}
|
||||
|
||||
/// @brief Initialise the COM library as single-threaded apartment if isn't already inited by us
|
||||
bool Init() {
|
||||
if (!inited && SUCCEEDED(CoInitialize(nullptr)))
|
||||
inited = true;
|
||||
return inited;
|
||||
}
|
||||
};
|
||||
|
||||
struct ReleaseCOMObject {
|
||||
void operator()(IUnknown* obj) {
|
||||
if (obj) obj->Release();
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief RAII wrapper around Win32 HANDLE type
|
||||
struct Win32KernelHandle final : public agi::scoped_holder<HANDLE, BOOL(__stdcall*)(HANDLE)> {
|
||||
/// @brief Create with a managed handle
|
||||
/// @param handle Win32 handle to manage
|
||||
Win32KernelHandle(HANDLE handle = 0) :scoped_holder(handle, CloseHandle) {}
|
||||
|
||||
Win32KernelHandle& operator=(HANDLE new_handle) {
|
||||
scoped_holder::operator=(new_handle);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
/// @class XAudio2Thread
|
||||
/// @brief Playback thread class for XAudio2Player
|
||||
///
|
||||
/// Not based on wxThread, but uses Win32 threads directly
|
||||
class XAudio2Thread :public IXAudio2VoiceCallback {
|
||||
/// @brief Win32 thread entry point
|
||||
/// @param parameter Pointer to our thread object
|
||||
/// @return Thread return value, always 0 here
|
||||
static unsigned int __stdcall ThreadProc(void* parameter);
|
||||
/// @brief Thread entry point
|
||||
void Run();
|
||||
|
||||
/// @brief Check for error state and throw exception if one occurred
|
||||
void CheckError();
|
||||
|
||||
/// Win32 handle to the thread
|
||||
Win32KernelHandle thread_handle;
|
||||
|
||||
/// Event object, world to thread, set to start playback
|
||||
Win32KernelHandle event_start_playback;
|
||||
|
||||
/// Event object, world to thread, set to stop playback
|
||||
Win32KernelHandle event_stop_playback;
|
||||
|
||||
/// Event object, world to thread, set if playback end time was updated
|
||||
Win32KernelHandle event_update_end_time;
|
||||
|
||||
/// Event object, world to thread, set if the volume was changed
|
||||
Win32KernelHandle event_set_volume;
|
||||
|
||||
/// Event object, world to thread, set if the thread should end as soon as possible
|
||||
Win32KernelHandle event_buffer_end;
|
||||
|
||||
/// Event object, world to thread, set if the thread should end as soon as possible
|
||||
Win32KernelHandle event_kill_self;
|
||||
|
||||
/// Event object, thread to world, set when the thread has entered its main loop
|
||||
Win32KernelHandle thread_running;
|
||||
|
||||
/// Event object, thread to world, set when playback is ongoing
|
||||
Win32KernelHandle is_playing;
|
||||
|
||||
/// Event object, thread to world, set if an error state has occurred (implies thread is dying)
|
||||
Win32KernelHandle error_happened;
|
||||
|
||||
/// Statically allocated error message text describing reason for error_happened being set
|
||||
const char* error_message = nullptr;
|
||||
|
||||
/// Playback volume, 1.0 is "unchanged"
|
||||
double volume = 1.0;
|
||||
|
||||
/// Audio frame to start playback at
|
||||
int64_t start_frame = 0;
|
||||
|
||||
/// Audio frame to end playback at
|
||||
int64_t end_frame = 0;
|
||||
|
||||
/// Desired length in milliseconds to write ahead of the playback cursor
|
||||
int wanted_latency;
|
||||
|
||||
/// Multiplier for WantedLatency to get total buffer length
|
||||
int buffer_length;
|
||||
|
||||
/// System millisecond timestamp of last playback start, used to calculate playback position
|
||||
ULONGLONG last_playback_restart;
|
||||
|
||||
/// Audio provider to take sample data from
|
||||
agi::AudioProvider* provider;
|
||||
|
||||
/// Buffer occupied indicator
|
||||
std::vector<bool> buffer_occupied;
|
||||
|
||||
public:
|
||||
/// @brief Constructor, creates and starts playback thread
|
||||
/// @param provider Audio provider to take sample data from
|
||||
/// @param WantedLatency Desired length in milliseconds to write ahead of the playback cursor
|
||||
/// @param BufferLength Multiplier for WantedLatency to get total buffer length
|
||||
XAudio2Thread(agi::AudioProvider* provider, int WantedLatency, int BufferLength);
|
||||
/// @brief Destructor, waits for thread to have died
|
||||
~XAudio2Thread();
|
||||
|
||||
// IXAudio2VoiceCallback
|
||||
void STDMETHODCALLTYPE OnVoiceProcessingPassStart(UINT32 BytesRequired) override {}
|
||||
void STDMETHODCALLTYPE OnVoiceProcessingPassEnd() override {}
|
||||
void STDMETHODCALLTYPE OnStreamEnd() override {}
|
||||
void STDMETHODCALLTYPE OnBufferStart(void* pBufferContext) override {}
|
||||
void STDMETHODCALLTYPE OnBufferEnd(void* pBufferContext) override {
|
||||
intptr_t i = reinterpret_cast<intptr_t>(pBufferContext);
|
||||
buffer_occupied[i] = false;
|
||||
SetEvent(event_buffer_end);
|
||||
}
|
||||
void STDMETHODCALLTYPE OnLoopEnd(void* pBufferContext) override {}
|
||||
void STDMETHODCALLTYPE OnVoiceError(void* pBufferContext, HRESULT Error) override {}
|
||||
|
||||
/// @brief Start audio playback
|
||||
/// @param start Audio frame to start playback at
|
||||
/// @param count Number of audio frames to play
|
||||
void Play(int64_t start, int64_t count);
|
||||
|
||||
/// @brief Stop audio playback
|
||||
void Stop();
|
||||
|
||||
/// @brief Change audio playback end point
|
||||
/// @param new_end_frame New last audio frame to play
|
||||
///
|
||||
/// Playback stops instantly if new_end_frame is before the current playback position
|
||||
void SetEndFrame(int64_t new_end_frame);
|
||||
|
||||
/// @brief Change audio playback volume
|
||||
/// @param new_volume New playback amplification factor, 1.0 is "unchanged"
|
||||
void SetVolume(double new_volume);
|
||||
|
||||
/// @brief Tell whether audio playback is active
|
||||
/// @return True if audio is being played back, false if it is not
|
||||
bool IsPlaying();
|
||||
|
||||
/// @brief Get approximate current audio frame being heard by the user
|
||||
/// @return Audio frame index
|
||||
///
|
||||
/// Returns 0 if not playing
|
||||
int64_t GetCurrentFrame();
|
||||
|
||||
/// @brief Get audio playback end point
|
||||
/// @return Audio frame index
|
||||
int64_t GetEndFrame();
|
||||
|
||||
/// @brief Tell whether playback thread has died
|
||||
/// @return True if thread is no longer running
|
||||
bool IsDead();
|
||||
};
|
||||
|
||||
unsigned int __stdcall XAudio2Thread::ThreadProc(void* parameter) {
|
||||
static_cast<XAudio2Thread*>(parameter)->Run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Macro used to set error_message, error_happened and end the thread
|
||||
#define REPORT_ERROR(msg) \
|
||||
{ \
|
||||
ResetEvent(is_playing); \
|
||||
error_message = "XAudio2Thread: " msg; \
|
||||
SetEvent(error_happened); \
|
||||
return; \
|
||||
}
|
||||
|
||||
void XAudio2Thread::Run() {
|
||||
COMInitialization COM_library;
|
||||
if (!COM_library.Init()) {
|
||||
REPORT_ERROR("Could not initialise COM")
|
||||
}
|
||||
IXAudio2* pXAudio2;
|
||||
IXAudio2SourceVoice* pSourceVoice;
|
||||
HRESULT hr;
|
||||
if (FAILED(hr = XAudio2Create(&pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR))) {
|
||||
REPORT_ERROR("Failed initializing XAudio2")
|
||||
}
|
||||
IXAudio2MasteringVoice* pMasterVoice = NULL;
|
||||
if (FAILED(hr = pXAudio2->CreateMasteringVoice(&pMasterVoice))) {
|
||||
REPORT_ERROR("Failed initializing XAudio2 MasteringVoice")
|
||||
}
|
||||
|
||||
// Describe the wave format
|
||||
WAVEFORMATEX wfx;
|
||||
wfx.nSamplesPerSec = provider->GetSampleRate();
|
||||
wfx.cbSize = 0;
|
||||
bool original = true;
|
||||
wfx.wFormatTag = provider->AreSamplesFloat() ? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
|
||||
wfx.nChannels = provider->GetChannels();
|
||||
wfx.wBitsPerSample = provider->GetBytesPerSample() * 8;
|
||||
wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8;
|
||||
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
|
||||
|
||||
if (FAILED(hr = pXAudio2->CreateSourceVoice(&pSourceVoice, &wfx, 0, 2, this))) {
|
||||
if (hr == XAUDIO2_E_INVALID_CALL) {
|
||||
// Retry with 16bit mono
|
||||
original = false;
|
||||
wfx.wFormatTag = WAVE_FORMAT_PCM;
|
||||
wfx.nChannels = 1;
|
||||
wfx.wBitsPerSample = sizeof(int16_t) * 8;
|
||||
wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8;
|
||||
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
|
||||
if (FAILED(hr = pXAudio2->CreateSourceVoice(&pSourceVoice, &wfx, 0, 2, this))) {
|
||||
REPORT_ERROR("Failed initializing XAudio2 SourceVoice")
|
||||
}
|
||||
}
|
||||
else {
|
||||
REPORT_ERROR("Failed initializing XAudio2 SourceVoice")
|
||||
}
|
||||
}
|
||||
|
||||
// Now we're ready to roll!
|
||||
SetEvent(thread_running);
|
||||
bool running = true;
|
||||
|
||||
HANDLE events_to_wait[] = {
|
||||
event_start_playback,
|
||||
event_stop_playback,
|
||||
event_update_end_time,
|
||||
event_set_volume,
|
||||
event_buffer_end,
|
||||
event_kill_self
|
||||
};
|
||||
|
||||
int64_t next_input_frame = 0;
|
||||
DWORD buffer_offset = 0;
|
||||
bool playback_should_be_running = false;
|
||||
int current_latency = wanted_latency;
|
||||
const int wanted_frames = wanted_latency * wfx.nSamplesPerSec / 1000;
|
||||
const DWORD wanted_latency_bytes = wanted_frames * wfx.nBlockAlign;
|
||||
std::vector<std::vector<BYTE> > buff(buffer_length);
|
||||
for (auto& i : buff)
|
||||
i.resize(wanted_latency_bytes);
|
||||
|
||||
while (running) {
|
||||
DWORD wait_result = WaitForMultipleObjects(sizeof(events_to_wait) / sizeof(HANDLE), events_to_wait, FALSE, INFINITE);
|
||||
|
||||
switch (wait_result) {
|
||||
case WAIT_OBJECT_0 + 0:
|
||||
// Start or restart playback
|
||||
pSourceVoice->Stop();
|
||||
pSourceVoice->FlushSourceBuffers();
|
||||
|
||||
next_input_frame = start_frame;
|
||||
playback_should_be_running = true;
|
||||
pSourceVoice->Start();
|
||||
SetEvent(is_playing);
|
||||
goto do_fill_buffer;
|
||||
|
||||
case WAIT_OBJECT_0 + 1:
|
||||
stop_playback:
|
||||
// Stop playing
|
||||
ResetEvent(is_playing);
|
||||
pSourceVoice->Stop();
|
||||
pSourceVoice->FlushSourceBuffers();
|
||||
playback_should_be_running = false;
|
||||
break;
|
||||
|
||||
case WAIT_OBJECT_0 + 2:
|
||||
// Set end frame
|
||||
if (end_frame <= next_input_frame)
|
||||
goto stop_playback;
|
||||
goto do_fill_buffer;
|
||||
|
||||
case WAIT_OBJECT_0 + 3:
|
||||
// Change volume
|
||||
pSourceVoice->SetVolume(volume);
|
||||
break;
|
||||
|
||||
case WAIT_OBJECT_0 + 4:
|
||||
// Buffer end
|
||||
do_fill_buffer:
|
||||
// Time to fill more into buffer
|
||||
if (!playback_should_be_running)
|
||||
break;
|
||||
|
||||
for (int i = 0; i < buffer_length; ++i) {
|
||||
if (!buffer_occupied[i]) {
|
||||
int fill_len = std::min<int>(end_frame - next_input_frame, wanted_frames);
|
||||
if (fill_len <= 0)
|
||||
break;
|
||||
buffer_occupied[i] = true;
|
||||
if (original)
|
||||
provider->GetAudio(buff[i].data(), next_input_frame, fill_len);
|
||||
else
|
||||
provider->GetInt16MonoAudio(reinterpret_cast<int16_t*>(buff[i].data()), next_input_frame, fill_len);
|
||||
next_input_frame += fill_len;
|
||||
XAUDIO2_BUFFER xbf;
|
||||
xbf.Flags = fill_len + next_input_frame == end_frame ? XAUDIO2_END_OF_STREAM : 0;
|
||||
xbf.AudioBytes = fill_len * wfx.nBlockAlign;
|
||||
xbf.pAudioData = buff[i].data();
|
||||
xbf.PlayBegin = 0;
|
||||
xbf.PlayLength = 0;
|
||||
xbf.LoopBegin = 0;
|
||||
xbf.LoopLength = 0;
|
||||
xbf.LoopCount = 0;
|
||||
xbf.pContext = reinterpret_cast<void*>(static_cast<intptr_t>(i));
|
||||
if (FAILED(hr = pSourceVoice->SubmitSourceBuffer(&xbf))) {
|
||||
REPORT_ERROR("Failed initializing Submit Buffer")
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case WAIT_OBJECT_0 + 5:
|
||||
// Perform suicide
|
||||
running = false;
|
||||
pXAudio2->Release();
|
||||
ResetEvent(is_playing);
|
||||
playback_should_be_running = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
REPORT_ERROR("Something bad happened while waiting on events in playback loop, either the wait failed or an event object was abandoned.")
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef REPORT_ERROR
|
||||
|
||||
void XAudio2Thread::CheckError()
|
||||
{
|
||||
try {
|
||||
switch (WaitForSingleObject(error_happened, 0))
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
throw error_message;
|
||||
|
||||
case WAIT_ABANDONED:
|
||||
throw "The XAudio2Thread error signal event was abandoned, somehow. This should not happen.";
|
||||
|
||||
case WAIT_FAILED:
|
||||
throw "Failed checking state of XAudio2Thread error signal event.";
|
||||
|
||||
case WAIT_TIMEOUT:
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
ResetEvent(is_playing);
|
||||
ResetEvent(thread_running);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
XAudio2Thread::XAudio2Thread(agi::AudioProvider* provider, int WantedLatency, int BufferLength)
|
||||
: event_start_playback(CreateEvent(0, FALSE, FALSE, 0))
|
||||
, event_stop_playback(CreateEvent(0, FALSE, FALSE, 0))
|
||||
, event_update_end_time(CreateEvent(0, FALSE, FALSE, 0))
|
||||
, event_set_volume(CreateEvent(0, FALSE, FALSE, 0))
|
||||
, event_buffer_end(CreateEvent(0, FALSE, FALSE, 0))
|
||||
, event_kill_self(CreateEvent(0, FALSE, FALSE, 0))
|
||||
, thread_running(CreateEvent(0, TRUE, FALSE, 0))
|
||||
, is_playing(CreateEvent(0, TRUE, FALSE, 0))
|
||||
, error_happened(CreateEvent(0, FALSE, FALSE, 0))
|
||||
, wanted_latency(WantedLatency)
|
||||
, buffer_length(BufferLength < XAUDIO2_MAX_QUEUED_BUFFERS ? BufferLength : XAUDIO2_MAX_QUEUED_BUFFERS)
|
||||
, provider(provider)
|
||||
, buffer_occupied(BufferLength)
|
||||
{
|
||||
if (!(thread_handle = (HANDLE)_beginthreadex(0, 0, ThreadProc, this, 0, 0))) {
|
||||
throw AudioPlayerOpenError("Failed creating playback thread in XAudio2Player. This is bad.");
|
||||
}
|
||||
|
||||
HANDLE running_or_error[] = { thread_running, error_happened };
|
||||
switch (WaitForMultipleObjects(2, running_or_error, FALSE, INFINITE)) {
|
||||
case WAIT_OBJECT_0:
|
||||
// running, all good
|
||||
return;
|
||||
|
||||
case WAIT_OBJECT_0 + 1:
|
||||
// error happened, we fail
|
||||
throw AudioPlayerOpenError(error_message ? error_message : "Failed wait for thread start or thread error in XAudio2Player. This is bad.");
|
||||
|
||||
default:
|
||||
throw AudioPlayerOpenError("Failed wait for thread start or thread error in XAudio2Player. This is bad.");
|
||||
}
|
||||
}
|
||||
|
||||
XAudio2Thread::~XAudio2Thread() {
|
||||
SetEvent(event_kill_self);
|
||||
WaitForSingleObject(thread_handle, INFINITE);
|
||||
}
|
||||
|
||||
void XAudio2Thread::Play(int64_t start, int64_t count)
|
||||
{
|
||||
CheckError();
|
||||
|
||||
start_frame = start;
|
||||
end_frame = start + count;
|
||||
SetEvent(event_start_playback);
|
||||
|
||||
last_playback_restart = GetTickCount64();
|
||||
|
||||
// Block until playback actually begins to avoid race conditions with
|
||||
// checking if playback is in progress
|
||||
HANDLE events_to_wait[] = { is_playing, error_happened };
|
||||
switch (WaitForMultipleObjects(2, events_to_wait, FALSE, INFINITE)) {
|
||||
case WAIT_OBJECT_0 + 0: // Playing
|
||||
LOG_D("audio/player/xaudio2") << "Playback begun";
|
||||
break;
|
||||
case WAIT_OBJECT_0 + 1: // Error
|
||||
throw error_message;
|
||||
default:
|
||||
throw agi::InternalError("Unexpected result from WaitForMultipleObjects in XAudio2Thread::Play");
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2Thread::Stop() {
|
||||
CheckError();
|
||||
|
||||
SetEvent(event_stop_playback);
|
||||
}
|
||||
|
||||
void XAudio2Thread::SetEndFrame(int64_t new_end_frame) {
|
||||
CheckError();
|
||||
|
||||
end_frame = new_end_frame;
|
||||
SetEvent(event_update_end_time);
|
||||
}
|
||||
|
||||
void XAudio2Thread::SetVolume(double new_volume) {
|
||||
CheckError();
|
||||
|
||||
volume = new_volume;
|
||||
SetEvent(event_set_volume);
|
||||
}
|
||||
|
||||
bool XAudio2Thread::IsPlaying() {
|
||||
CheckError();
|
||||
|
||||
switch (WaitForSingleObject(is_playing, 0))
|
||||
{
|
||||
case WAIT_ABANDONED:
|
||||
throw "The XAudio2Thread playback state event was abandoned, somehow. This should not happen.";
|
||||
|
||||
case WAIT_FAILED:
|
||||
throw "Failed checking state of XAudio2Thread playback state event.";
|
||||
|
||||
case WAIT_OBJECT_0:
|
||||
return true;
|
||||
|
||||
case WAIT_TIMEOUT:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t XAudio2Thread::GetCurrentFrame() {
|
||||
CheckError();
|
||||
if (!IsPlaying()) return 0;
|
||||
ULONGLONG milliseconds_elapsed = GetTickCount64() - last_playback_restart;
|
||||
return start_frame + milliseconds_elapsed * provider->GetSampleRate() / 1000;
|
||||
}
|
||||
|
||||
int64_t XAudio2Thread::GetEndFrame() {
|
||||
CheckError();
|
||||
return end_frame;
|
||||
}
|
||||
|
||||
bool XAudio2Thread::IsDead() {
|
||||
switch (WaitForSingleObject(thread_running, 0))
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
XAudio2Player::XAudio2Player(agi::AudioProvider* provider) :AudioPlayer(provider) {
|
||||
// The buffer will hold BufferLength times WantedLatency milliseconds of audio
|
||||
WantedLatency = OPT_GET("Player/Audio/DirectSound/Buffer Latency")->GetInt();
|
||||
BufferLength = OPT_GET("Player/Audio/DirectSound/Buffer Length")->GetInt();
|
||||
|
||||
// sanity checking
|
||||
if (WantedLatency <= 0)
|
||||
WantedLatency = 100;
|
||||
if (BufferLength <= 0)
|
||||
BufferLength = 5;
|
||||
|
||||
try {
|
||||
thread = agi::make_unique<XAudio2Thread>(provider, WantedLatency, BufferLength);
|
||||
}
|
||||
catch (const char* msg) {
|
||||
LOG_E("audio/player/xaudio2") << msg;
|
||||
throw AudioPlayerOpenError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
bool XAudio2Player::IsThreadAlive() {
|
||||
if (thread && thread->IsDead())
|
||||
thread.reset();
|
||||
return static_cast<bool>(thread);
|
||||
}
|
||||
|
||||
void XAudio2Player::Play(int64_t start, int64_t count) {
|
||||
try {
|
||||
thread->Play(start, count);
|
||||
}
|
||||
catch (const char* msg) {
|
||||
LOG_E("audio/player/xaudio2") << msg;
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2Player::Stop() {
|
||||
try {
|
||||
if (IsThreadAlive()) thread->Stop();
|
||||
}
|
||||
catch (const char* msg) {
|
||||
LOG_E("audio/player/xaudio2") << msg;
|
||||
}
|
||||
}
|
||||
|
||||
bool XAudio2Player::IsPlaying() {
|
||||
try {
|
||||
if (!IsThreadAlive()) return false;
|
||||
return thread->IsPlaying();
|
||||
}
|
||||
catch (const char* msg) {
|
||||
LOG_E("audio/player/xaudio2") << msg;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t XAudio2Player::GetEndPosition() {
|
||||
try {
|
||||
if (!IsThreadAlive()) return 0;
|
||||
return thread->GetEndFrame();
|
||||
}
|
||||
catch (const char* msg) {
|
||||
LOG_E("audio/player/xaudio2") << msg;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t XAudio2Player::GetCurrentPosition() {
|
||||
try {
|
||||
if (!IsThreadAlive()) return 0;
|
||||
return thread->GetCurrentFrame();
|
||||
}
|
||||
catch (const char* msg) {
|
||||
LOG_E("audio/player/xaudio2") << msg;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2Player::SetEndPosition(int64_t pos) {
|
||||
try {
|
||||
if (IsThreadAlive()) thread->SetEndFrame(pos);
|
||||
}
|
||||
catch (const char* msg) {
|
||||
LOG_E("audio/player/xaudio2") << msg;
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2Player::SetVolume(double vol) {
|
||||
try {
|
||||
if (IsThreadAlive()) thread->SetVolume(vol);
|
||||
}
|
||||
catch (const char* msg) {
|
||||
LOG_E("audio/player/xaudio2") << msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioPlayer> CreateXAudio2Player(agi::AudioProvider* provider, wxWindow*) {
|
||||
return agi::make_unique<XAudio2Player>(provider);
|
||||
}
|
||||
|
||||
#endif // WITH_XAUDIO2
|
Loading…
Reference in a new issue