From b471423c1a6a4d6119d255b9b59b900a39d8ad55 Mon Sep 17 00:00:00 2001
From: Gliniak <Gliniak93@gmail.com>
Date: Sun, 27 Oct 2024 16:40:51 +0100
Subject: [PATCH] [XMP] Added dedicated AudioMediaPlayer class

---
 src/xenia/apu/audio_media_player.cc  | 558 +++++++++++++++++++++++++++
 src/xenia/apu/audio_media_player.h   | 138 +++++++
 src/xenia/apu/audio_system.h         |   8 +-
 src/xenia/apu/premake5.lua           |   1 +
 src/xenia/emulator.cc                |   5 +
 src/xenia/emulator.h                 |   7 +
 src/xenia/kernel/premake5.lua        |   6 -
 src/xenia/kernel/xam/apps/xmp_app.cc | 521 ++++++-------------------
 src/xenia/kernel/xam/apps/xmp_app.h  |  70 ++--
 9 files changed, 867 insertions(+), 447 deletions(-)
 create mode 100644 src/xenia/apu/audio_media_player.cc
 create mode 100644 src/xenia/apu/audio_media_player.h

diff --git a/src/xenia/apu/audio_media_player.cc b/src/xenia/apu/audio_media_player.cc
new file mode 100644
index 000000000..6c11f2f8e
--- /dev/null
+++ b/src/xenia/apu/audio_media_player.cc
@@ -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
\ No newline at end of file
diff --git a/src/xenia/apu/audio_media_player.h b/src/xenia/apu/audio_media_player.h
new file mode 100644
index 000000000..81ed9a0db
--- /dev/null
+++ b/src/xenia/apu/audio_media_player.h
@@ -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
diff --git a/src/xenia/apu/audio_system.h b/src/xenia/apu/audio_system.h
index 482559b6d..e18ce7c1d 100644
--- a/src/xenia/apu/audio_system.h
+++ b/src/xenia/apu/audio_system.h
@@ -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_;
diff --git a/src/xenia/apu/premake5.lua b/src/xenia/apu/premake5.lua
index 3f1b45aed..8f04e7593 100644
--- a/src/xenia/apu/premake5.lua
+++ b/src/xenia/apu/premake5.lua
@@ -9,6 +9,7 @@ project("xenia-apu")
   links({
     "libavcodec",
     "libavutil",
+    "libavformat",
     "xenia-base",
   })
   defines({
diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc
index 8e00cb0ae..7fd639525 100644
--- a/src/xenia/emulator.cc
+++ b/src/xenia/emulator.cc
@@ -120,6 +120,7 @@ Emulator::Emulator(const std::filesystem::path& command_line,
       display_window_(nullptr),
       memory_(),
       audio_system_(),
+      audio_media_player_(),
       graphics_system_(),
       input_system_(),
       export_resolver_(),
@@ -176,6 +177,7 @@ Emulator::~Emulator() {
   input_system_.reset();
   graphics_system_.reset();
   audio_system_.reset();
+  audio_media_player_.reset();
 
   kernel_state_.reset();
   file_system_.reset();
@@ -311,6 +313,9 @@ X_STATUS Emulator::Setup(
     if (result) {
       return result;
     }
+    audio_media_player_ = std::make_unique<apu::AudioMediaPlayer>(
+        audio_system_.get(), kernel_state_.get());
+    audio_media_player_->Setup();
   }
 
   // Initialize emulator fallback exception handling last.
diff --git a/src/xenia/emulator.h b/src/xenia/emulator.h
index ccf37d7f9..6bc037759 100644
--- a/src/xenia/emulator.h
+++ b/src/xenia/emulator.h
@@ -17,6 +17,7 @@
 #include <string>
 #include <vector>
 
+#include "xenia/apu/audio_media_player.h"
 #include "xenia/base/delegate.h"
 #include "xenia/base/exception_handler.h"
 #include "xenia/kernel/kernel_state.h"
@@ -141,6 +142,11 @@ class Emulator {
   // Audio hardware emulation for decoding and playback.
   apu::AudioSystem* audio_system() const { return audio_system_.get(); }
 
+  // Xbox media player (XMP) emulation for WMA and MP3 playback.
+  apu::AudioMediaPlayer* audio_media_player() const {
+    return audio_media_player_.get();
+  }
+
   // GPU emulation for command list processing.
   gpu::GraphicsSystem* graphics_system() const {
     return graphics_system_.get();
@@ -301,6 +307,7 @@ class Emulator {
 
   std::unique_ptr<cpu::Processor> processor_;
   std::unique_ptr<apu::AudioSystem> audio_system_;
+  std::unique_ptr<apu::AudioMediaPlayer> audio_media_player_;
   std::unique_ptr<gpu::GraphicsSystem> graphics_system_;
   std::unique_ptr<hid::InputSystem> input_system_;
 
diff --git a/src/xenia/kernel/premake5.lua b/src/xenia/kernel/premake5.lua
index 972e9ebd5..bd00330bc 100644
--- a/src/xenia/kernel/premake5.lua
+++ b/src/xenia/kernel/premake5.lua
@@ -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",
diff --git a/src/xenia/kernel/xam/apps/xmp_app.cc b/src/xenia/kernel/xam/apps/xmp_app.cc
index bd9e91e94..cceae3726 100644
--- a/src/xenia/kernel/xam/apps/xmp_app.cc
+++ b/src/xenia/kernel/xam/apps/xmp_app.cc
@@ -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
diff --git a/src/xenia/kernel/xam/apps/xmp_app.h b/src/xenia/kernel/xam/apps/xmp_app.h
index 21628c626..2d0b94136 100644
--- a/src/xenia/kernel/xam/apps/xmp_app.h
+++ b/src/xenia/kernel/xam/apps/xmp_app.h
@@ -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