[XMP] Added dedicated AudioMediaPlayer class
This commit is contained in:
committed by
Radosław Gliński
parent
996b874d74
commit
b471423c1a
558
src/xenia/apu/audio_media_player.cc
Normal file
558
src/xenia/apu/audio_media_player.cc
Normal file
@@ -0,0 +1,558 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2024 Xenia Canary. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/apu/audio_media_player.h"
|
||||
#include "xenia/apu/audio_driver.h"
|
||||
#include "xenia/apu/audio_system.h"
|
||||
#include "xenia/apu/xma_context.h"
|
||||
#include "xenia/base/logging.h"
|
||||
|
||||
#include <XAudio2.h>
|
||||
|
||||
extern "C" {
|
||||
#if XE_COMPILER_MSVC
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4101 4244 5033)
|
||||
#endif
|
||||
#include "third_party/FFmpeg/libavcodec/avcodec.h"
|
||||
#include "third_party/FFmpeg/libavformat/avformat.h"
|
||||
#include "third_party/FFmpeg/libavformat/avio.h"
|
||||
#if XE_COMPILER_MSVC
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
} // extern "C"
|
||||
|
||||
DEFINE_bool(enable_xmp, true, "Enables Music Player playback.", "APU");
|
||||
DEFINE_int32(xmp_default_volume, 70,
|
||||
"Default music volume if game doesn't set it [0-100].", "APU");
|
||||
|
||||
namespace xe {
|
||||
namespace apu {
|
||||
|
||||
int32_t InitializeAndOpenAvCodec(std::vector<uint8_t>* song_data,
|
||||
AVFormatContext*& format_context,
|
||||
AVCodecContext*& av_context) {
|
||||
AVIOContext* io_ctx =
|
||||
avio_alloc_context(song_data->data(), (int)song_data->size(), 0, nullptr,
|
||||
nullptr, nullptr, nullptr);
|
||||
|
||||
format_context = avformat_alloc_context();
|
||||
format_context->pb = io_ctx;
|
||||
|
||||
int ret = avformat_open_input(&format_context, nullptr, nullptr, nullptr);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
// Processing data
|
||||
ret = avformat_find_stream_info(format_context, nullptr);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
AVStream* stream = format_context->streams[0];
|
||||
|
||||
// find & open codec
|
||||
AVCodecParameters* codec = stream->codecpar;
|
||||
auto decoder = avcodec_find_decoder(codec->codec_id);
|
||||
av_context = avcodec_alloc_context3(decoder);
|
||||
|
||||
// Fill codec context with codec parameters
|
||||
ret = avcodec_parameters_to_context(av_context, codec);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = avcodec_open2(av_context, decoder, NULL);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ConvertAudioFrame(AVFrame* frame, int channel_count,
|
||||
std::vector<float>* framebuffer) {
|
||||
framebuffer->reserve(frame->nb_samples * channel_count);
|
||||
|
||||
switch (frame->format) {
|
||||
case AV_SAMPLE_FMT_FLTP: {
|
||||
for (int sample = 0; sample < frame->nb_samples; sample++) {
|
||||
for (int ch = 0; ch < channel_count; ch++) {
|
||||
float sampleValue = reinterpret_cast<float*>(frame->data[ch])[sample];
|
||||
framebuffer->push_back(sampleValue);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case AV_SAMPLE_FMT_FLT: {
|
||||
float* frameData = reinterpret_cast<float*>(frame->data[0]);
|
||||
framebuffer->insert(framebuffer->end(), frameData,
|
||||
frameData + frame->nb_samples * channel_count);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ProcessAudioResult ProcessAudioLoop(AudioMediaPlayer* player,
|
||||
AudioDriver* driver, AVFormatContext* s,
|
||||
AVCodecContext* avctx, int streamIndex) {
|
||||
AVPacket* packet = av_packet_alloc();
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
std::vector<float> frameBuffer;
|
||||
|
||||
while (av_read_frame(s, packet) >= 0) {
|
||||
if (!player->IsSongLoaded()) {
|
||||
av_frame_free(&frame);
|
||||
av_packet_free(&packet);
|
||||
return ProcessAudioResult::ForcedFinish;
|
||||
}
|
||||
|
||||
if (packet->stream_index == streamIndex) {
|
||||
int ret = avcodec_send_packet(avctx, packet);
|
||||
if (ret < 0) {
|
||||
XELOGE("Error sending packet for decoding: {:X}", ret);
|
||||
break;
|
||||
}
|
||||
|
||||
while (ret >= 0) {
|
||||
if (!player->IsSongLoaded()) {
|
||||
break;
|
||||
}
|
||||
|
||||
ret = avcodec_receive_frame(avctx, frame);
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break;
|
||||
if (ret < 0) {
|
||||
XELOGW("Error during decoding: {:X}", ret);
|
||||
break;
|
||||
}
|
||||
|
||||
ConvertAudioFrame(frame, avctx->channels, &frameBuffer);
|
||||
player->ProcessAudioBuffer(&frameBuffer);
|
||||
}
|
||||
}
|
||||
av_packet_unref(packet);
|
||||
}
|
||||
|
||||
av_frame_free(&frame);
|
||||
av_packet_free(&packet);
|
||||
return ProcessAudioResult::Successful;
|
||||
}
|
||||
|
||||
AudioMediaPlayer::AudioMediaPlayer(apu::AudioSystem* audio_system,
|
||||
kernel::KernelState* kernel_state)
|
||||
: audio_system_(audio_system),
|
||||
kernel_state_(kernel_state),
|
||||
active_playlist_(nullptr),
|
||||
active_song_(nullptr) {};
|
||||
|
||||
AudioMediaPlayer::~AudioMediaPlayer() {
|
||||
Stop();
|
||||
DeleteDriver();
|
||||
};
|
||||
|
||||
void AudioMediaPlayer::WorkerThreadMain() {
|
||||
while (worker_running_) {
|
||||
if (!IsPlaying()) {
|
||||
resume_fence_.Wait();
|
||||
}
|
||||
|
||||
if (!active_playlist_) {
|
||||
xe::threading::Sleep(std::chrono::milliseconds(500));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (active_song_) {
|
||||
Play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioMediaPlayer::Setup() {
|
||||
if (!cvars::enable_xmp) {
|
||||
return;
|
||||
}
|
||||
|
||||
// sample_buffer_ptr_ = kernel_state_->memory()->SystemHeapAlloc(
|
||||
// xe::apu::AudioDriver::kFrameSamplesMax);
|
||||
|
||||
worker_running_ = true;
|
||||
worker_thread_ = threading::Thread::Create({}, [&] { WorkerThreadMain(); });
|
||||
worker_thread_->set_name("Audio Media Player");
|
||||
};
|
||||
|
||||
X_STATUS AudioMediaPlayer::Play(uint32_t playlist_handle, uint32_t song_handle,
|
||||
bool force) {
|
||||
auto playlist_itr = playlists_.find(playlist_handle);
|
||||
if (playlist_itr == playlists_.cend()) {
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
active_playlist_ = playlist_itr->second.get();
|
||||
|
||||
// I've set it to false (force) for PGR3
|
||||
if (!IsIdle()) {
|
||||
Stop(false, force);
|
||||
}
|
||||
|
||||
if (!song_handle) {
|
||||
active_song_ = active_playlist_->songs.cbegin()->get();
|
||||
resume_fence_.Signal();
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
auto song_itr = std::find_if(
|
||||
active_playlist_->songs.cbegin(), active_playlist_->songs.cend(),
|
||||
[song_handle](const std::unique_ptr<XmpApp::Song>& song) {
|
||||
return song->handle == song_handle;
|
||||
});
|
||||
|
||||
if (song_itr == active_playlist_->songs.cend()) {
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
active_song_ = song_itr->get();
|
||||
resume_fence_.Signal();
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void AudioMediaPlayer::Play() {
|
||||
std::vector<uint8_t>* song_buffer = new std::vector<uint8_t>();
|
||||
|
||||
if (!LoadSongToMemory(song_buffer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AVFormatContext* formatContext = nullptr;
|
||||
AVCodecContext* codecContext = nullptr;
|
||||
InitializeAndOpenAvCodec(song_buffer, formatContext, codecContext);
|
||||
|
||||
if (!SetupDriver(codecContext->sample_rate, codecContext->channels)) {
|
||||
XELOGE("Driver initialization failed!");
|
||||
avcodec_free_context(&codecContext);
|
||||
av_freep(&formatContext->pb->buffer);
|
||||
avio_context_free(&formatContext->pb);
|
||||
avformat_close_input(&formatContext);
|
||||
return;
|
||||
}
|
||||
|
||||
state_ = XmpApp::State::kPlaying;
|
||||
current_song_handle_ = active_song_->handle;
|
||||
OnStateChanged();
|
||||
|
||||
if (volume_ == 0.0f) {
|
||||
volume_ = cvars::xmp_default_volume / 100.0f;
|
||||
SetVolume(volume_);
|
||||
}
|
||||
|
||||
auto result =
|
||||
ProcessAudioLoop(this, driver_.get(), formatContext, codecContext, 0);
|
||||
|
||||
// We need to stop playback only if it wasn't
|
||||
if (result != ProcessAudioResult::ForcedFinish) {
|
||||
Stop();
|
||||
}
|
||||
|
||||
// We're waiting for dangling samples to finish playing.
|
||||
if (result == ProcessAudioResult::Successful) {
|
||||
xe::threading::Wait(driver_semaphore_.get(), true,
|
||||
std::chrono::milliseconds(500));
|
||||
}
|
||||
|
||||
// Cleanup after work
|
||||
avcodec_free_context(&codecContext);
|
||||
av_freep(&formatContext->pb->buffer);
|
||||
avio_context_free(&formatContext->pb);
|
||||
avformat_close_input(&formatContext);
|
||||
|
||||
DeleteDriver();
|
||||
processing_end_fence_.Signal();
|
||||
|
||||
if (result == ProcessAudioResult::ForcedFinish) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsLastSongInPlaylist()) {
|
||||
Next();
|
||||
return;
|
||||
}
|
||||
|
||||
// We're after last song in playlist
|
||||
if (IsInRepeatMode()) {
|
||||
Play(active_playlist_->handle, 0, true);
|
||||
};
|
||||
}
|
||||
|
||||
void AudioMediaPlayer::Pause() {
|
||||
if (!IsPlaying()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (driver_) {
|
||||
driver_->Pause();
|
||||
}
|
||||
state_ = XmpApp::State::kPaused;
|
||||
OnStateChanged();
|
||||
};
|
||||
|
||||
void AudioMediaPlayer::Stop(bool change_state, bool force) {
|
||||
if (IsIdle()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (driver_ && IsPaused()) {
|
||||
driver_->Resume();
|
||||
}
|
||||
|
||||
state_ = XmpApp::State::kIdle;
|
||||
active_song_ = nullptr;
|
||||
|
||||
if (!force) {
|
||||
processing_end_fence_.Wait();
|
||||
}
|
||||
|
||||
if (change_state) {
|
||||
OnStateChanged();
|
||||
}
|
||||
};
|
||||
|
||||
void AudioMediaPlayer::Continue() {
|
||||
if (!IsPaused()) {
|
||||
return;
|
||||
}
|
||||
|
||||
state_ = XmpApp::State::kPlaying;
|
||||
resume_fence_.Signal();
|
||||
if (driver_) {
|
||||
driver_->Resume();
|
||||
}
|
||||
OnStateChanged();
|
||||
}
|
||||
|
||||
X_STATUS AudioMediaPlayer::Next() {
|
||||
if (!active_playlist_) {
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
if (active_song_) {
|
||||
Stop(false, false);
|
||||
}
|
||||
|
||||
auto itr = std::find_if(active_playlist_->songs.cbegin(),
|
||||
active_playlist_->songs.cend(),
|
||||
[this](const std::unique_ptr<XmpApp::Song>& song) {
|
||||
return song->handle == current_song_handle_;
|
||||
});
|
||||
|
||||
// There is no song with such ID?
|
||||
if (itr == active_playlist_->songs.cend()) {
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
itr = std::next(itr);
|
||||
|
||||
if (itr != active_playlist_->songs.cend()) {
|
||||
active_song_ = itr->get();
|
||||
resume_fence_.Signal();
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
active_song_ = active_playlist_->songs.cbegin()->get();
|
||||
resume_fence_.Signal();
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
X_STATUS AudioMediaPlayer::Previous() {
|
||||
if (!active_playlist_) {
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
if (active_song_) {
|
||||
Stop(false, false);
|
||||
}
|
||||
|
||||
auto itr = std::find_if(active_playlist_->songs.cbegin(),
|
||||
active_playlist_->songs.cend(),
|
||||
[this](const std::unique_ptr<XmpApp::Song>& song) {
|
||||
return song->handle == current_song_handle_;
|
||||
});
|
||||
|
||||
// We're at the first entry, we need to go to the end.
|
||||
if (itr == active_playlist_->songs.cbegin()) {
|
||||
active_song_ = active_playlist_->songs.crbegin()->get();
|
||||
resume_fence_.Signal();
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
itr = std::prev(itr);
|
||||
active_song_ = itr->get();
|
||||
resume_fence_.Signal();
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
bool AudioMediaPlayer::LoadSongToMemory(std::vector<uint8_t>* buffer) {
|
||||
if (!active_song_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find file based on provided path?
|
||||
vfs::File* vfs_file;
|
||||
vfs::FileAction file_action;
|
||||
X_STATUS result = kernel_state_->file_system()->OpenFile(
|
||||
nullptr, xe::to_utf8(active_song_->file_path),
|
||||
vfs::FileDisposition::kOpen, vfs::FileAccess::kGenericRead, false, true,
|
||||
&vfs_file, &file_action);
|
||||
|
||||
if (result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer->resize(vfs_file->entry()->size());
|
||||
size_t bytes_read = 0;
|
||||
result = vfs_file->ReadSync(buffer->data(), vfs_file->entry()->size(), 0,
|
||||
&bytes_read);
|
||||
|
||||
return !result;
|
||||
}
|
||||
|
||||
void AudioMediaPlayer::AddPlaylist(uint32_t handle,
|
||||
std::unique_ptr<XmpApp::Playlist> playlist) {
|
||||
if (playlists_.count(handle) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (playback_mode_ == XmpApp::PlaybackMode::kShuffle) {
|
||||
auto rng = std::default_random_engine{};
|
||||
std::shuffle(playlist->songs.begin(), playlist->songs.end(), rng);
|
||||
}
|
||||
|
||||
playlists_.insert({handle, std::move(playlist)});
|
||||
}
|
||||
|
||||
void AudioMediaPlayer::RemovePlaylist(uint32_t handle) {
|
||||
if (playlists_.count(handle) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Check if currently played song is from that playlist and stop
|
||||
// playback.
|
||||
if (active_playlist_ && active_song_) {
|
||||
Stop();
|
||||
}
|
||||
|
||||
playlists_.erase(handle);
|
||||
}
|
||||
|
||||
X_STATUS AudioMediaPlayer::SetVolume(float volume) {
|
||||
volume_ = std::min(volume, 1.0f);
|
||||
|
||||
std::unique_lock<xe::xe_fast_mutex> guard(driver_mutex_);
|
||||
if (!driver_) {
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
driver_->SetVolume(volume_);
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
bool AudioMediaPlayer::IsLastSongInPlaylist() const {
|
||||
if (!active_playlist_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto itr = std::find_if(active_playlist_->songs.cbegin(),
|
||||
active_playlist_->songs.cend(),
|
||||
[this](const std::unique_ptr<XmpApp::Song>& song) {
|
||||
return song->handle == current_song_handle_;
|
||||
});
|
||||
itr = std::next(itr);
|
||||
return itr == active_playlist_->songs.cend();
|
||||
}
|
||||
|
||||
void AudioMediaPlayer::SetCaptureCallback(uint32_t callback, uint32_t context,
|
||||
bool title_render) {
|
||||
// TODO: Something is incorrect with callback and causes audio to be muted!
|
||||
callback_ = 0;
|
||||
callback_context_ = context;
|
||||
is_title_rendering_enabled_ = false; // title_render;
|
||||
}
|
||||
|
||||
void AudioMediaPlayer::OnStateChanged() {
|
||||
kernel_state_->BroadcastNotification(kNotificationXmpStateChanged,
|
||||
static_cast<uint32_t>(state_));
|
||||
}
|
||||
|
||||
void AudioMediaPlayer::ProcessAudioBuffer(std::vector<float>* buffer) {
|
||||
while (buffer->size() >= xe::apu::AudioDriver::kFrameSamplesMax) {
|
||||
xe::threading::Wait(driver_semaphore_.get(), true);
|
||||
|
||||
if (!IsSongLoaded()) {
|
||||
buffer->clear();
|
||||
break;
|
||||
}
|
||||
|
||||
// XMP should be processed on Audio thread. Some games goes insane when they
|
||||
// have additional unexpected guest thread (NG2 for example).
|
||||
/*
|
||||
if (callback_) {
|
||||
std::memcpy(kernel_state_->memory()->TranslateVirtual(sample_buffer_ptr_),
|
||||
buffer->data(), xe::apu::AudioDriver::kFrameSamplesMax);
|
||||
|
||||
uint64_t args[] = {sample_buffer_ptr_, callback_context_, true};
|
||||
audio_system_->processor()->Execute(worker_thread_->thread_state(),
|
||||
callback_, args, xe::countof(args));
|
||||
}*/
|
||||
|
||||
driver_->SubmitFrame(buffer->data());
|
||||
buffer->erase(buffer->begin(),
|
||||
buffer->begin() + xe::apu::AudioDriver::kFrameSamplesMax);
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioMediaPlayer::SetupDriver(uint32_t sample_rate, uint32_t channels) {
|
||||
DeleteDriver();
|
||||
|
||||
std::unique_lock<xe::xe_fast_mutex> guard(driver_mutex_);
|
||||
driver_semaphore_ = xe::threading::Semaphore::Create(
|
||||
AudioSystem::kMaximumQueuedFrames, AudioSystem::kMaximumQueuedFrames);
|
||||
|
||||
if (!driver_semaphore_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
driver_ = std::unique_ptr<AudioDriver>(audio_system_->CreateDriver(
|
||||
driver_semaphore_.get(), sample_rate, channels, false));
|
||||
if (!driver_) {
|
||||
driver_semaphore_.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!driver_->Initialize()) {
|
||||
driver_semaphore_.reset();
|
||||
driver_->Shutdown();
|
||||
driver_.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioMediaPlayer::DeleteDriver() {
|
||||
std::unique_lock<xe::xe_fast_mutex> guard(driver_mutex_);
|
||||
if (driver_) {
|
||||
if (driver_semaphore_) {
|
||||
driver_semaphore_.reset();
|
||||
}
|
||||
|
||||
driver_->Shutdown();
|
||||
driver_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace apu
|
||||
} // namespace xe
|
||||
138
src/xenia/apu/audio_media_player.h
Normal file
138
src/xenia/apu/audio_media_player.h
Normal file
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2024 Xenia Canary. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_APU_AUDIO_MEDIA_PLAYER_H_
|
||||
#define XENIA_APU_AUDIO_MEDIA_PLAYER_H_
|
||||
|
||||
#include "xenia/apu/audio_system.h"
|
||||
#include "xenia/kernel/xam/apps/xmp_app.h"
|
||||
|
||||
namespace xe {
|
||||
namespace apu {
|
||||
|
||||
using XmpApp = kernel::xam::apps::XmpApp;
|
||||
|
||||
enum ProcessAudioResult { Successful = 0, ForcedFinish = 1, Failure = 2 };
|
||||
|
||||
class AudioMediaPlayer {
|
||||
public:
|
||||
AudioMediaPlayer(apu::AudioSystem* audio_system,
|
||||
kernel::KernelState* kernel_state);
|
||||
~AudioMediaPlayer();
|
||||
|
||||
void Setup();
|
||||
|
||||
X_STATUS Play(uint32_t playlist_handle, uint32_t song_handle, bool force);
|
||||
X_STATUS Next();
|
||||
X_STATUS Previous();
|
||||
|
||||
void Stop(bool change_state = false, bool force = true);
|
||||
void Pause();
|
||||
void Continue();
|
||||
|
||||
XmpApp::State GetState() const { return state_; }
|
||||
|
||||
bool IsIdle() const { return state_ == XmpApp::State::kIdle; }
|
||||
bool IsPlaying() const { return state_ == XmpApp::State::kPlaying; }
|
||||
bool IsPaused() const { return state_ == XmpApp::State::kPaused; }
|
||||
bool IsSongLoaded() const { return active_song_; }
|
||||
|
||||
bool IsInRepeatMode() const {
|
||||
return repeat_mode_ == XmpApp::RepeatMode::kPlaylist;
|
||||
}
|
||||
|
||||
bool IsLastSongInPlaylist() const;
|
||||
|
||||
X_STATUS SetVolume(float volume);
|
||||
float GetVolume() const { return volume_; }
|
||||
|
||||
void SetPlaybackMode(XmpApp::PlaybackMode playback_mode) {
|
||||
playback_mode_ = playback_mode;
|
||||
}
|
||||
XmpApp::PlaybackMode GetPlaybackMode() const { return playback_mode_; }
|
||||
|
||||
void SetRepeatMode(XmpApp::RepeatMode repeat_mode) {
|
||||
repeat_mode_ = repeat_mode;
|
||||
}
|
||||
XmpApp::RepeatMode GetRepeatMode() { return repeat_mode_; }
|
||||
|
||||
void SetPlaybackFlags(XmpApp::PlaybackFlags playback_flags) {
|
||||
playback_flags_ = playback_flags;
|
||||
}
|
||||
XmpApp::PlaybackFlags GetPlaybackFlags() const { return playback_flags_; }
|
||||
|
||||
void SetPlaybackClient(XmpApp::PlaybackClient playback_client) {
|
||||
playback_client_ = playback_client;
|
||||
}
|
||||
|
||||
XmpApp::PlaybackClient GetPlaybackClient() const { return playback_client_; }
|
||||
|
||||
bool IsTitleInPlaybackControl() const {
|
||||
return playback_client_ == XmpApp::PlaybackClient::kTitle ||
|
||||
is_title_rendering_enabled_;
|
||||
}
|
||||
|
||||
void SetCaptureCallback(uint32_t callback, uint32_t context,
|
||||
bool title_render);
|
||||
|
||||
void AddPlaylist(uint32_t handle, std::unique_ptr<XmpApp::Playlist> playlist);
|
||||
void RemovePlaylist(uint32_t handle);
|
||||
|
||||
XmpApp::Song* GetCurrentSong() const { return active_song_; }
|
||||
|
||||
void ProcessAudioBuffer(std::vector<float>* buffer);
|
||||
|
||||
private:
|
||||
void OnStateChanged();
|
||||
|
||||
void Play();
|
||||
void WorkerThreadMain();
|
||||
bool LoadSongToMemory(std::vector<uint8_t>* buffer);
|
||||
|
||||
XmpApp::State state_ = XmpApp::State::kIdle;
|
||||
XmpApp::PlaybackClient playback_client_ = XmpApp::PlaybackClient::kSystem;
|
||||
XmpApp::PlaybackMode playback_mode_ = XmpApp::PlaybackMode::kInOrder;
|
||||
XmpApp::RepeatMode repeat_mode_ = XmpApp::RepeatMode::kPlaylist;
|
||||
XmpApp::PlaybackFlags playback_flags_ = XmpApp::PlaybackFlags::kDefault;
|
||||
float volume_ = 1.0f;
|
||||
|
||||
std::unordered_map<uint32_t, std::unique_ptr<XmpApp::Playlist>> playlists_;
|
||||
XmpApp::Playlist* active_playlist_;
|
||||
XmpApp::Song* active_song_;
|
||||
|
||||
size_t current_song_handle_ = 0;
|
||||
|
||||
uint32_t callback_ = 0;
|
||||
uint32_t callback_context_ = 0;
|
||||
uint32_t sample_buffer_ptr_ = 0;
|
||||
bool is_title_rendering_enabled_ = false;
|
||||
|
||||
AudioSystem* audio_system_ = nullptr;
|
||||
kernel::KernelState* kernel_state_ = nullptr;
|
||||
|
||||
xe::global_critical_region global_critical_region_;
|
||||
std::atomic<bool> worker_running_ = {false};
|
||||
std::unique_ptr<threading::Thread> worker_thread_;
|
||||
xe::threading::Fence resume_fence_; // Signaled when resume requested.
|
||||
xe::threading::Fence processing_end_fence_;
|
||||
|
||||
// Driver part - This should be integrated into audio_system, but it isn't
|
||||
// really compatible with it.
|
||||
std::unique_ptr<AudioDriver> driver_ = nullptr;
|
||||
std::unique_ptr<xe::threading::Semaphore> driver_semaphore_ = {};
|
||||
xe::xe_fast_mutex driver_mutex_ = {};
|
||||
|
||||
bool SetupDriver(uint32_t sample_rate, uint32_t channels);
|
||||
void DeleteDriver();
|
||||
};
|
||||
|
||||
} // namespace apu
|
||||
} // namespace xe
|
||||
|
||||
#endif
|
||||
@@ -30,6 +30,10 @@ class XmaDecoder;
|
||||
|
||||
class AudioSystem {
|
||||
public:
|
||||
// TODO(gibbed): respect XAUDIO2_MAX_QUEUED_BUFFERS somehow (ie min(64,
|
||||
// XAUDIO2_MAX_QUEUED_BUFFERS))
|
||||
static const size_t kMaximumQueuedFrames = 64;
|
||||
|
||||
virtual ~AudioSystem();
|
||||
|
||||
Memory* memory() const { return memory_; }
|
||||
@@ -68,10 +72,6 @@ class AudioSystem {
|
||||
AudioDriver** out_driver) = 0;
|
||||
virtual void DestroyDriver(AudioDriver* driver) = 0;
|
||||
|
||||
// TODO(gibbed): respect XAUDIO2_MAX_QUEUED_BUFFERS somehow (ie min(64,
|
||||
// XAUDIO2_MAX_QUEUED_BUFFERS))
|
||||
static const size_t kMaximumQueuedFrames = 64;
|
||||
|
||||
Memory* memory_ = nullptr;
|
||||
cpu::Processor* processor_ = nullptr;
|
||||
std::unique_ptr<XmaDecoder> xma_decoder_;
|
||||
|
||||
@@ -9,6 +9,7 @@ project("xenia-apu")
|
||||
links({
|
||||
"libavcodec",
|
||||
"libavutil",
|
||||
"libavformat",
|
||||
"xenia-base",
|
||||
})
|
||||
defines({
|
||||
|
||||
Reference in New Issue
Block a user