/** ****************************************************************************** * 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 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* 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* 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(frame->data[ch])[sample]; framebuffer->push_back(sampleValue); } } break; } case AV_SAMPLE_FMT_FLT: { float* frameData = reinterpret_cast(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 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& 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* song_buffer = new std::vector(); 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& 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& 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* 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 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 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& 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(state_)); } void AudioMediaPlayer::ProcessAudioBuffer(std::vector* 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 guard(driver_mutex_); driver_semaphore_ = xe::threading::Semaphore::Create( AudioSystem::kMaximumQueuedFrames, AudioSystem::kMaximumQueuedFrames); if (!driver_semaphore_) { return false; } driver_ = std::unique_ptr(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 guard(driver_mutex_); if (driver_) { if (driver_semaphore_) { driver_semaphore_.reset(); } driver_->Shutdown(); driver_.reset(); } } } // namespace apu } // namespace xe