[XMP] Added dedicated AudioMediaPlayer class

This commit is contained in:
Gliniak
2024-10-27 16:40:51 +01:00
committed by Radosław Gliński
parent 996b874d74
commit b471423c1a
9 changed files with 867 additions and 447 deletions

View File

@@ -16,15 +16,9 @@ project("xenia-kernel")
"xenia-cpu",
"xenia-hid",
"xenia-vfs",
"libavcodec",
"libavformat",
"libavutil",
})
defines({
})
includedirs({
project_root.."/third_party/FFmpeg/",
})
recursive_platform_files()
files({
"debug_visualizers.natvis",

View File

@@ -15,279 +15,14 @@
#include "xenia/emulator.h"
#include "xenia/xbox.h"
#include "xenia/apu/audio_driver.h"
#include "xenia/apu/audio_system.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/libavutil/opt.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");
#include "xenia/apu/audio_media_player.h"
namespace xe {
namespace kernel {
namespace xam {
namespace apps {
XmpApp::XmpApp(KernelState* kernel_state)
: App(kernel_state, 0xFA),
state_(State::kIdle),
playback_client_(PlaybackClient::kTitle),
playback_mode_(PlaybackMode::kUnknown),
repeat_mode_(RepeatMode::kUnknown),
unknown_flags_(0),
volume_(cvars::xmp_default_volume / 100.0f),
active_playlist_(nullptr),
active_song_index_(0),
next_playlist_handle_(1),
next_song_handle_(1) {
if (cvars::enable_xmp) {
worker_running_ = true;
worker_thread_ = threading::Thread::Create({}, [&] { WorkerThreadMain(); });
worker_thread_->set_name("Music Player");
}
}
struct VFSContext {
xe::vfs::File* file;
size_t byte_offset;
};
static int xenia_vfs_read(void* opaque, uint8_t* buf, int buf_size) {
auto ctx = static_cast<VFSContext*>(opaque);
size_t bytes_read;
X_STATUS status =
ctx->file->ReadSync(buf, buf_size, ctx->byte_offset, &bytes_read);
if (XFAILED(status)) {
return status == X_STATUS_END_OF_FILE ? AVERROR_EOF : status;
}
ctx->byte_offset += bytes_read;
return static_cast<int>(bytes_read);
}
bool XmpApp::PlayFile(std::string_view filename) {
auto playlist = active_playlist_;
const int buffer_size = 8192;
uint8_t* buffer = reinterpret_cast<uint8_t*>(av_malloc(buffer_size));
VFSContext vfs_ctx = {};
xe::vfs::FileAction file_action;
X_STATUS status = kernel_state_->file_system()->OpenFile(
nullptr, filename, xe::vfs::FileDisposition::kOpen,
xe::vfs::FileAccess::kGenericRead, false, true, &vfs_ctx.file,
&file_action);
if (XFAILED(status)) {
XELOGE("Opening {} failed with status {:X}", filename, status);
return false;
}
AVIOContext* avio_ctx = avio_alloc_context(buffer, buffer_size, 0, &vfs_ctx,
xenia_vfs_read, nullptr, nullptr);
AVFormatContext* formatContext = avformat_alloc_context();
formatContext->pb = avio_ctx;
int ret;
if ((ret = avformat_open_input(&formatContext, nullptr, nullptr, nullptr)) !=
0) {
XELOGE("ffmpeg: Could not open WMA file: {:x}", ret);
av_freep(&avio_ctx->buffer);
avio_context_free(&avio_ctx);
return false;
}
if (avformat_find_stream_info(formatContext, nullptr) < 0) {
XELOGE("ffmpeg: Could not find stream info");
avformat_close_input(&formatContext);
av_freep(&avio_ctx->buffer);
avio_context_free(&avio_ctx);
return false;
}
AVCodec* codec = nullptr;
int streamIndex =
av_find_best_stream(formatContext, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0);
if (streamIndex < 0) {
XELOGE("ffmpeg: Could not find audio stream");
avformat_close_input(&formatContext);
av_freep(&avio_ctx->buffer);
avio_context_free(&avio_ctx);
return false;
}
AVStream* audioStream = formatContext->streams[streamIndex];
AVCodecContext* codecContext = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(codecContext, audioStream->codecpar);
if (audioStream->codecpar->format != AV_SAMPLE_FMT_FLTP &&
audioStream->codecpar->format != AV_SAMPLE_FMT_FLT) {
XELOGE("Audio stream has unexpected sample format {:d}",
audioStream->codecpar->format);
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
av_freep(&avio_ctx->buffer);
avio_context_free(&avio_ctx);
return false;
}
if (avcodec_open2(codecContext, codec, nullptr) < 0) {
XELOGE("ffmpeg: Could not open codec");
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
av_freep(&avio_ctx->buffer);
avio_context_free(&avio_ctx);
return false;
}
auto driverReady = xe::threading::Semaphore::Create(64, 64);
{
std::unique_lock<std::mutex> guard(driver_mutex_);
driver_ = kernel_state_->emulator()->audio_system()->CreateDriver(
driverReady.get(), codecContext->sample_rate, codecContext->channels,
false);
if (!driver_->Initialize()) {
XELOGE("Driver initialization failed!");
driver_->Shutdown();
driver_ = nullptr;
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
av_freep(&avio_ctx->buffer);
avio_context_free(&avio_ctx);
return false;
}
}
if (volume_ == 0.0f) {
// Some games set volume to 0 on startup and then never call SetVolume
// again...
volume_ = cvars::xmp_default_volume / 100.0f;
}
driver_->SetVolume(volume_);
AVPacket* packet = av_packet_alloc();
AVFrame* frame = av_frame_alloc();
std::vector<float> frameBuffer;
// Read frames, decode & send to audio driver
while (av_read_frame(formatContext, packet) >= 0) {
if (active_playlist_ != playlist) {
frameBuffer.clear();
break;
}
if (packet->stream_index == streamIndex) {
int ret = avcodec_send_packet(codecContext, packet);
if (ret < 0) {
XELOGE("Error sending packet for decoding: {:X}", ret);
break;
}
while (ret >= 0) {
ret = avcodec_receive_frame(codecContext, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break;
if (ret < 0) {
XELOGW("Error during decoding: {:X}", ret);
break;
}
// If the frame is planar, convert it to interleaved
if (frame->format == AV_SAMPLE_FMT_FLTP) {
for (int sample = 0; sample < frame->nb_samples; sample++) {
for (int ch = 0; ch < codecContext->channels; ch++) {
float sampleValue =
reinterpret_cast<float*>(frame->data[ch])[sample];
frameBuffer.push_back(sampleValue);
}
}
} else if (frame->format == AV_SAMPLE_FMT_FLT) {
int frameSizeFloats = frame->nb_samples * codecContext->channels;
float* frameData = reinterpret_cast<float*>(frame->data[0]);
frameBuffer.insert(frameBuffer.end(), frameData,
frameData + frameSizeFloats);
}
while (frameBuffer.size() >= xe::apu::AudioDriver::kFrameSamplesMax) {
xe::threading::Wait(driverReady.get(), true);
if (active_playlist_ != playlist) {
frameBuffer.clear();
break;
}
driver_->SubmitFrame(frameBuffer.data());
frameBuffer.erase(
frameBuffer.begin(),
frameBuffer.begin() + xe::apu::AudioDriver::kFrameSamplesMax);
}
}
}
av_packet_unref(packet);
}
if (!frameBuffer.empty()) {
while (frameBuffer.size() < xe::apu::AudioDriver::kFrameSamplesMax) {
frameBuffer.push_back(0.0f);
}
xe::threading::Wait(driverReady.get(), true);
driver_->SubmitFrame(frameBuffer.data());
}
av_frame_free(&frame);
av_packet_free(&packet);
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
av_freep(&avio_ctx->buffer);
avio_context_free(&avio_ctx);
{
std::unique_lock<std::mutex> guard(driver_mutex_);
driver_->Shutdown();
driver_ = nullptr;
}
if (state_ == State::kPlaying && active_playlist_ == playlist) {
active_playlist_ = nullptr;
state_ = State::kIdle;
OnStateChanged();
}
return true;
}
void XmpApp::WorkerThreadMain() {
while (worker_running_) {
if (state_ != State::kPlaying) {
resume_fence_.Wait();
}
auto playlist = active_playlist_;
if (!playlist) {
continue;
}
auto utf8_path = xe::path_to_utf8(playlist->songs[0].get()->file_path);
XELOGI("Playing file {}", utf8_path);
if (!PlayFile(utf8_path)) {
XELOGE("Playback failed");
xe::threading::Sleep(std::chrono::minutes(1));
}
}
}
XmpApp::XmpApp(KernelState* kernel_state) : App(kernel_state, 0xFA) {}
X_HRESULT XmpApp::XMPGetStatus(uint32_t state_ptr) {
if (!XThread::GetCurrentThread()->main_thread()) {
@@ -296,9 +31,11 @@ X_HRESULT XmpApp::XMPGetStatus(uint32_t state_ptr) {
xe::threading::Sleep(std::chrono::milliseconds(1));
}
XELOGD("XMPGetStatus({:08X}) -> {:d}", state_ptr, (uint32_t)state_);
xe::store_and_swap<uint32_t>(memory_->TranslateVirtual(state_ptr),
static_cast<uint32_t>(state_));
const uint32_t state = static_cast<uint32_t>(
kernel_state_->emulator()->audio_media_player()->GetState());
XELOGD("XMPGetStatus({:08X}) -> {:d}", state_ptr, state);
xe::store_and_swap<uint32_t>(memory_->TranslateVirtual(state_ptr), state);
return X_E_SUCCESS;
}
@@ -311,40 +48,41 @@ X_HRESULT XmpApp::XMPCreateTitlePlaylist(
"{:08X})",
songs_ptr, song_count, playlist_name_ptr, xe::to_utf8(playlist_name),
flags, out_song_handles, out_playlist_handle);
auto playlist = std::make_unique<Playlist>();
playlist->handle = ++next_playlist_handle_;
playlist->name = playlist_name;
playlist->flags = flags;
if (songs_ptr) {
XMP_SONGDESCRIPTOR* song_descriptor =
memory_->TranslateVirtual<XMP_SONGDESCRIPTOR*>(songs_ptr);
for (uint32_t i = 0; i < song_count; ++i) {
auto song = std::make_unique<Song>();
song->handle = ++next_song_handle_;
uint8_t* song_base = memory_->TranslateVirtual(songs_ptr + (i * 36));
song->file_path =
xe::load_and_swap<std::u16string>(memory_->TranslateVirtual(
xe::load_and_swap<uint32_t>(song_base + 0)));
song->name = xe::load_and_swap<std::u16string>(memory_->TranslateVirtual(
xe::load_and_swap<uint32_t>(song_base + 4)));
song->artist =
xe::load_and_swap<std::u16string>(memory_->TranslateVirtual(
xe::load_and_swap<uint32_t>(song_base + 8)));
song->album = xe::load_and_swap<std::u16string>(memory_->TranslateVirtual(
xe::load_and_swap<uint32_t>(song_base + 12)));
song->album_artist =
xe::load_and_swap<std::u16string>(memory_->TranslateVirtual(
xe::load_and_swap<uint32_t>(song_base + 16)));
song->genre = xe::load_and_swap<std::u16string>(memory_->TranslateVirtual(
xe::load_and_swap<uint32_t>(song_base + 20)));
song->track_number = xe::load_and_swap<uint32_t>(song_base + 24);
song->duration_ms = xe::load_and_swap<uint32_t>(song_base + 28);
song->file_path = xe::load_and_swap<std::u16string>(
memory_->TranslateVirtual(song_descriptor[i].file_path_ptr));
song->name = xe::load_and_swap<std::u16string>(
memory_->TranslateVirtual(song_descriptor[i].title_ptr));
song->artist = xe::load_and_swap<std::u16string>(
memory_->TranslateVirtual(song_descriptor[i].artist_ptr));
song->album = xe::load_and_swap<std::u16string>(
memory_->TranslateVirtual(song_descriptor[i].album_ptr));
song->album_artist = xe::load_and_swap<std::u16string>(
memory_->TranslateVirtual(song_descriptor[i].album_artist_ptr));
song->genre = xe::load_and_swap<std::u16string>(
memory_->TranslateVirtual(song_descriptor[i].genre_ptr));
song->track_number = song_descriptor[i].track_number;
song->duration_ms = song_descriptor[i].duration;
song->format = static_cast<Song::Format>(
xe::load_and_swap<uint32_t>(song_base + 32));
xe::byte_swap<uint32_t>(song_descriptor[i].song_format));
if (out_song_handles) {
xe::store_and_swap<uint32_t>(
memory_->TranslateVirtual(out_song_handles + (i * 4)),
song->handle);
}
playlist->songs.emplace_back(std::move(song));
playlist->songs.push_back(std::move(song));
}
}
if (out_playlist_handle) {
@@ -352,48 +90,24 @@ X_HRESULT XmpApp::XMPCreateTitlePlaylist(
playlist->handle);
}
auto global_lock = global_critical_region_.Acquire();
playlists_.insert({playlist->handle, playlist.get()});
playlist.release();
kernel_state_->emulator()->audio_media_player()->AddPlaylist(
next_playlist_handle_, std::move(playlist));
return X_E_SUCCESS;
}
X_HRESULT XmpApp::XMPDeleteTitlePlaylist(uint32_t playlist_handle) {
XELOGD("XMPDeleteTitlePlaylist({:08X})", playlist_handle);
auto global_lock = global_critical_region_.Acquire();
auto it = playlists_.find(playlist_handle);
if (it == playlists_.end()) {
XELOGE("Playlist {:08X} not found", playlist_handle);
return X_E_NOTFOUND;
}
auto playlist = it->second;
if (playlist == active_playlist_) {
XMPStop(0);
}
playlists_.erase(it);
delete playlist;
kernel_state_->emulator()->audio_media_player()->RemovePlaylist(
playlist_handle);
return X_E_SUCCESS;
}
X_HRESULT XmpApp::XMPPlayTitlePlaylist(uint32_t playlist_handle,
uint32_t song_handle) {
XELOGD("XMPPlayTitlePlaylist({:08X}, {:08X})", playlist_handle, song_handle);
Playlist* playlist = nullptr;
{
auto global_lock = global_critical_region_.Acquire();
auto it = playlists_.find(playlist_handle);
if (it == playlists_.end()) {
XELOGE("Playlist {:08X} not found", playlist_handle);
return X_E_NOTFOUND;
}
playlist = it->second;
}
active_playlist_ = playlist;
active_song_index_ = 0;
state_ = State::kPlaying;
resume_fence_.Signal();
OnStateChanged();
kernel_state_->emulator()->audio_media_player()->Play(playlist_handle,
song_handle, false);
kernel_state_->BroadcastNotification(kNotificationXmpPlaybackBehaviorChanged,
1);
return X_E_SUCCESS;
@@ -401,75 +115,35 @@ X_HRESULT XmpApp::XMPPlayTitlePlaylist(uint32_t playlist_handle,
X_HRESULT XmpApp::XMPContinue() {
XELOGD("XMPContinue()");
if (state_ == State::kPaused) {
state_ = State::kPlaying;
resume_fence_.Signal();
{
std::unique_lock<std::mutex> guard(driver_mutex_);
if (driver_ != nullptr) driver_->Resume();
}
}
OnStateChanged();
kernel_state_->emulator()->audio_media_player()->Continue();
return X_E_SUCCESS;
}
X_HRESULT XmpApp::XMPStop(uint32_t unk) {
assert_zero(unk);
XELOGD("XMPStop({:08X})", unk);
active_playlist_ = nullptr; // ?
active_song_index_ = 0;
state_ = State::kIdle;
OnStateChanged();
kernel_state_->emulator()->audio_media_player()->Stop(true, false);
return X_E_SUCCESS;
}
X_HRESULT XmpApp::XMPPause() {
XELOGD("XMPPause()");
if (state_ == State::kPlaying) {
{
std::unique_lock<std::mutex> guard(driver_mutex_);
if (driver_ != nullptr) driver_->Pause();
}
state_ = State::kPaused;
}
OnStateChanged();
kernel_state_->emulator()->audio_media_player()->Pause();
return X_E_SUCCESS;
}
X_HRESULT XmpApp::XMPNext() {
XELOGD("XMPNext()");
if (!active_playlist_) {
return X_E_NOTFOUND;
}
state_ = State::kPlaying;
active_song_index_ =
(active_song_index_ + 1) % active_playlist_->songs.size();
resume_fence_.Signal();
OnStateChanged();
kernel_state_->emulator()->audio_media_player()->Next();
return X_E_SUCCESS;
}
X_HRESULT XmpApp::XMPPrevious() {
XELOGD("XMPPrevious()");
if (!active_playlist_) {
return X_E_NOTFOUND;
}
state_ = State::kPlaying;
if (!active_song_index_) {
active_song_index_ = static_cast<int>(active_playlist_->songs.size()) - 1;
} else {
--active_song_index_;
}
resume_fence_.Signal();
OnStateChanged();
kernel_state_->emulator()->audio_media_player()->Previous();
return X_E_SUCCESS;
}
void XmpApp::OnStateChanged() {
kernel_state_->BroadcastNotification(kNotificationXmpStateChanged,
static_cast<uint32_t>(state_));
}
X_HRESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
uint32_t buffer_length) {
// NOTE: buffer_length may be zero or valid.
@@ -531,9 +205,14 @@ X_HRESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
XELOGD("XMPSetPlaybackBehavior({:08X}, {:08X}, {:08X})",
uint32_t(args->playback_mode), uint32_t(args->repeat_mode),
uint32_t(args->flags));
playback_mode_ = static_cast<PlaybackMode>(uint32_t(args->playback_mode));
repeat_mode_ = static_cast<RepeatMode>(uint32_t(args->repeat_mode));
unknown_flags_ = args->flags;
kernel_state_->emulator()->audio_media_player()->SetPlaybackMode(
static_cast<PlaybackMode>(uint32_t(args->playback_mode)));
kernel_state_->emulator()->audio_media_player()->SetRepeatMode(
static_cast<RepeatMode>(uint32_t(args->repeat_mode)));
kernel_state_->emulator()->audio_media_player()->SetPlaybackFlags(
static_cast<PlaybackFlags>(uint32_t(args->flags)));
kernel_state_->BroadcastNotification(
kNotificationXmpPlaybackBehaviorChanged, 0);
return X_E_SUCCESS;
@@ -556,8 +235,10 @@ X_HRESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
assert_true(args->xmp_client == 0x00000002);
XELOGD("XMPGetVolume({:08X})", uint32_t(args->volume_ptr));
xe::store_and_swap<float>(memory_->TranslateVirtual(args->volume_ptr),
volume_);
xe::store_and_swap<float>(
memory_->TranslateVirtual(args->volume_ptr),
kernel_state_->emulator()->audio_media_player()->GetVolume());
return X_E_SUCCESS;
}
case 0x0007000C: {
@@ -569,12 +250,9 @@ X_HRESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
static_assert_size(decltype(*args), 8);
assert_true(args->xmp_client == 0x00000002);
XELOGD("XMPSetVolume({:g})", float(args->value));
volume_ = args->value;
{
std::unique_lock<std::mutex> guard(driver_mutex_);
if (driver_ != nullptr) driver_->SetVolume(volume_);
}
XELOGD("XMPSetVolume({:d}, {:g})", args->xmp_client, float(args->value));
kernel_state_->emulator()->audio_media_player()->SetVolume(
float(args->value));
return X_E_SUCCESS;
}
case 0x0007000D: {
@@ -604,9 +282,7 @@ X_HRESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
playlist_name = xe::load_and_swap<std::u16string>(
memory_->TranslateVirtual(args->playlist_name_ptr));
}
// dummy_alloc_ptr is the result of a XamAlloc of storage_size.
assert_true(uint32_t(args->storage_size) ==
4 + uint32_t(args->song_count) * 128);
return XMPCreateTitlePlaylist(args->songs_ptr, args->song_count,
args->playlist_name_ptr, playlist_name,
args->flags, args->song_handles_ptr,
@@ -621,26 +297,31 @@ X_HRESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
}* args = memory_->TranslateVirtual<decltype(args)>(buffer_ptr);
static_assert_size(decltype(*args), 12);
auto info = memory_->TranslateVirtual(args->info_ptr);
auto info = memory_->TranslateVirtual<XMP_SONGINFO*>(args->info_ptr);
assert_true(args->xmp_client == 0x00000002);
assert_zero(args->unk_ptr);
XELOGE("XMPGetInfo?({:08X}, {:08X})", uint32_t(args->unk_ptr),
XELOGE("XMPGetCurrentSong({:08X}, {:08X})", uint32_t(args->unk_ptr),
uint32_t(args->info_ptr));
if (!active_playlist_) {
Song* current_song =
kernel_state_->emulator()->audio_media_player()->GetCurrentSong();
if (!current_song) {
return X_E_FAIL;
}
auto& song = active_playlist_->songs[active_song_index_];
xe::store_and_swap<uint32_t>(info + 0, song->handle);
xe::store_and_swap<std::u16string>(info + 4 + 572 + 0, song->name);
xe::store_and_swap<std::u16string>(info + 4 + 572 + 40, song->artist);
xe::store_and_swap<std::u16string>(info + 4 + 572 + 80, song->album);
xe::store_and_swap<std::u16string>(info + 4 + 572 + 120,
song->album_artist);
xe::store_and_swap<std::u16string>(info + 4 + 572 + 160, song->genre);
xe::store_and_swap<uint32_t>(info + 4 + 572 + 200, song->track_number);
xe::store_and_swap<uint32_t>(info + 4 + 572 + 204, song->duration_ms);
xe::store_and_swap<uint32_t>(info + 4 + 572 + 208,
static_cast<uint32_t>(song->format));
memset(info, 0, sizeof(XMP_SONGINFO));
info->handle = current_song->handle;
xe::store_and_swap<std::u16string>(info->title, current_song->name);
xe::store_and_swap<std::u16string>(info->artist, current_song->artist);
xe::store_and_swap<std::u16string>(info->album, current_song->album);
xe::store_and_swap<std::u16string>(info->album_artist,
current_song->album_artist);
xe::store_and_swap<std::u16string>(info->genre, current_song->genre);
info->track_number = current_song->track_number;
info->duration = current_song->duration_ms;
info->song_format = static_cast<uint32_t>(current_song->format);
return X_E_SUCCESS;
}
case 0x00070013: {
@@ -673,9 +354,14 @@ X_HRESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
XELOGD("XMPSetPlaybackController({:08X}, {:08X})",
uint32_t(args->controller), uint32_t(args->playback_client));
playback_client_ = PlaybackClient(uint32_t(args->playback_client));
kernel_state_->emulator()->audio_media_player()->SetPlaybackClient(
PlaybackClient(uint32_t(args->playback_client)));
kernel_state_->BroadcastNotification(
kNotificationXmpPlaybackControllerChanged, !args->playback_client);
kNotificationXmpPlaybackControllerChanged,
kernel_state_->emulator()
->audio_media_player()
->IsTitleInPlaybackControl());
return X_E_SUCCESS;
}
case 0x0007001B: {
@@ -717,7 +403,7 @@ X_HRESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
xe::be<uint32_t> xmp_client;
xe::be<uint32_t> playback_mode_ptr;
xe::be<uint32_t> repeat_mode_ptr;
xe::be<uint32_t> unk3_ptr;
xe::be<uint32_t> playback_flags_ptr;
}* args = memory_->TranslateVirtual<decltype(args)>(buffer_ptr);
static_assert_size(decltype(*args), 16);
@@ -725,20 +411,27 @@ X_HRESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
args->xmp_client == 0x00000000);
XELOGD("XMPGetPlaybackBehavior({:08X}, {:08X}, {:08X})",
uint32_t(args->playback_mode_ptr), uint32_t(args->repeat_mode_ptr),
uint32_t(args->unk3_ptr));
uint32_t(args->playback_flags_ptr));
if (args->playback_mode_ptr) {
xe::store_and_swap<uint32_t>(
memory_->TranslateVirtual(args->playback_mode_ptr),
static_cast<uint32_t>(playback_mode_));
static_cast<uint32_t>(kernel_state_->emulator()
->audio_media_player()
->GetPlaybackMode()));
}
if (args->repeat_mode_ptr) {
xe::store_and_swap<uint32_t>(
memory_->TranslateVirtual(args->repeat_mode_ptr),
static_cast<uint32_t>(repeat_mode_));
static_cast<uint32_t>(kernel_state_->emulator()
->audio_media_player()
->GetRepeatMode()));
}
if (args->unk3_ptr) {
xe::store_and_swap<uint32_t>(memory_->TranslateVirtual(args->unk3_ptr),
unknown_flags_);
if (args->playback_flags_ptr) {
xe::store_and_swap<uint32_t>(
memory_->TranslateVirtual(args->playback_flags_ptr),
static_cast<uint32_t>(kernel_state_->emulator()
->audio_media_player()
->GetPlaybackFlags()));
}
return X_E_SUCCESS;
}
@@ -772,10 +465,22 @@ X_HRESULT XmpApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
return X_E_FAIL;
}
case 0x0007003D: {
// XMPCaptureOutput - not sure how this works :/
XELOGD("XMPCaptureOutput(...)");
assert_always("XMP output not unimplemented");
return X_E_FAIL;
// XMPCaptureOutput
assert_true(!buffer_length || buffer_length == 16);
struct {
xe::be<uint32_t> xmp_client;
xe::be<uint32_t> callback;
xe::be<uint32_t> context;
xe::be<uint32_t> title_render;
}* args = memory_->TranslateVirtual<decltype(args)>(buffer_ptr);
static_assert_size(decltype(*args), 16);
XELOGD("XMPCaptureOutput({:08X}, {:08X}, {:08X}, {:08X})",
args->xmp_client, args->callback, args->context,
args->title_render);
kernel_state_->emulator()->audio_media_player()->SetCaptureCallback(
args->callback, args->context, static_cast<bool>(args->title_render));
return X_E_SUCCESS;
}
case 0x00070044: {
// Called on the start up of all dashboard versions before kinect

View File

@@ -20,8 +20,6 @@
#include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/xam/app_manager.h"
#include "xenia/apu/audio_driver.h"
namespace xe {
namespace kernel {
namespace xam {
@@ -30,6 +28,36 @@ namespace apps {
// Only source of docs for a lot of these functions:
// https://github.com/oukiar/freestyledash/blob/master/Freestyle/Scenes/Media/Music/ScnMusic.cpp
struct XMP_SONGDESCRIPTOR {
xe::be<uint32_t> file_path_ptr;
xe::be<uint32_t> title_ptr;
xe::be<uint32_t> artist_ptr;
xe::be<uint32_t> album_ptr;
xe::be<uint32_t> album_artist_ptr;
xe::be<uint32_t> genre_ptr;
xe::be<uint32_t> track_number;
xe::be<uint32_t> duration;
xe::be<uint32_t> song_format;
};
static_assert_size(XMP_SONGDESCRIPTOR, 36);
constexpr uint32_t kMaxXmpMetadataStringLength = 40;
struct XMP_SONGINFO {
X_HANDLE handle;
uint8_t unknown[0x23C];
xe::be<char16_t> title[kMaxXmpMetadataStringLength];
xe::be<char16_t> artist[kMaxXmpMetadataStringLength];
xe::be<char16_t> album[kMaxXmpMetadataStringLength];
xe::be<char16_t> album_artist[kMaxXmpMetadataStringLength];
xe::be<char16_t> genre[kMaxXmpMetadataStringLength];
xe::be<uint32_t> track_number;
xe::be<uint32_t> duration;
xe::be<uint32_t> song_format;
};
static_assert_size(XMP_SONGINFO, 988);
class XmpApp : public App {
public:
enum class State : uint32_t {
@@ -42,12 +70,16 @@ class XmpApp : public App {
kTitle = 1,
};
enum class PlaybackMode : uint32_t {
// kInOrder = ?,
kUnknown = 0,
kInOrder = 0,
kShuffle = 1,
};
enum class RepeatMode : uint32_t {
// kNoRepeat = ?,
kUnknown = 0,
kPlaylist = 0,
kNoRepeat = 1,
};
enum class PlaybackFlags : uint32_t {
kDefault = 0,
kAutoPause = 1,
};
struct Song {
enum class Format : uint32_t {
@@ -95,31 +127,11 @@ class XmpApp : public App {
uint32_t buffer_length) override;
private:
void OnStateChanged();
void WorkerThreadMain();
bool PlayFile(std::string_view filename);
State state_;
PlaybackClient playback_client_;
PlaybackMode playback_mode_;
RepeatMode repeat_mode_;
uint32_t unknown_flags_;
float volume_;
Playlist* active_playlist_;
int active_song_index_;
xe::global_critical_region global_critical_region_;
std::unordered_map<uint32_t, Playlist*> playlists_;
uint32_t next_playlist_handle_;
uint32_t next_song_handle_;
std::atomic<bool> worker_running_ = {false};
std::unique_ptr<xe::threading::Thread> worker_thread_;
bool paused_ = true;
xe::threading::Fence resume_fence_; // Signaled when resume requested.
std::mutex driver_mutex_ = {};
xe::apu::AudioDriver* driver_ = nullptr;
// TODO: Remove it and replace with guest handles!
uint32_t next_playlist_handle_ = 0;
uint32_t next_song_handle_ = 0;
};
} // namespace apps