/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2021 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/apu/xma_context.h" #include #include "xenia/apu/xma_decoder.h" #include "xenia/base/bit_stream.h" #include "xenia/base/logging.h" #include "xenia/base/platform.h" #include "xenia/base/profiling.h" #include "xenia/base/ring_buffer.h" extern "C" { #if XE_COMPILER_MSVC #pragma warning(push) #pragma warning(disable : 4101 4244 5033) #endif #include "third_party/FFmpeg/libavcodec/avcodec.h" #if XE_COMPILER_MSVC #pragma warning(pop) #endif } // extern "C" // Credits for most of this code goes to: // https://github.com/koolkdev/libertyv/blob/master/libav_wrapper/xma2dec.c namespace xe { namespace apu { XmaContext::XmaContext() : work_completion_event_( xe::threading::Event::CreateAutoResetEvent(false)) {} XmaContext::~XmaContext() {} void XmaContext::DumpRaw(AVFrame* frame, int id) { FILE* outfile = xe::filesystem::OpenFile(fmt::format("out{}.raw", id).c_str(), "ab"); if (!outfile) { return; } size_t data_size = sizeof(float); for (int i = 0; i < frame->nb_samples; i++) { for (int ch = 0; ch < frame->channels; ch++) { fwrite(frame->data[ch] + data_size * i, 1, data_size, outfile); } } fclose(outfile); } void XmaContext::ConvertFrame(const uint8_t** samples, bool is_two_channel, uint8_t* output_buffer) { // Loop through every sample, convert and drop it into the output array. // If more than one channel, we need to interleave the samples from each // channel next to each other. Always saturate because FFmpeg output is // not limited to [-1, 1] (for example 1.095 as seen in 5454082B). constexpr float scale = (1 << 15) - 1; auto out = reinterpret_cast(output_buffer); // For testing of vectorized versions, stereo audio is common in 4D5307E6, // since the first menu frame; the intro cutscene also has more than 2 // channels. #if XE_ARCH_AMD64 static_assert(kSamplesPerFrame % 8 == 0); const auto in_channel_0 = reinterpret_cast(samples[0]); const __m128 scale_mm = _mm_set1_ps(scale); if (is_two_channel && samples[1] != nullptr) { const auto in_channel_1 = reinterpret_cast(samples[1]); const __m128i shufmask = _mm_set_epi8(14, 15, 6, 7, 12, 13, 4, 5, 10, 11, 2, 3, 8, 9, 0, 1); for (uint32_t i = 0; i < kSamplesPerFrame; i += 4) { // Load 8 samples, 4 for each channel. __m128 in_mm0 = _mm_loadu_ps(&in_channel_0[i]); __m128 in_mm1 = _mm_loadu_ps(&in_channel_1[i]); // Rescale. in_mm0 = _mm_mul_ps(in_mm0, scale_mm); in_mm1 = _mm_mul_ps(in_mm1, scale_mm); // Cast to int32. __m128i out_mm0 = _mm_cvtps_epi32(in_mm0); __m128i out_mm1 = _mm_cvtps_epi32(in_mm1); // Saturated cast and pack to int16. __m128i out_mm = _mm_packs_epi32(out_mm0, out_mm1); // Interleave channels and byte swap. out_mm = _mm_shuffle_epi8(out_mm, shufmask); // Store, as [out + i * 4] movdqu. _mm_storeu_si128(reinterpret_cast<__m128i*>(&out[i * 2]), out_mm); } } else { const __m128i shufmask = _mm_set_epi8(14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1); for (uint32_t i = 0; i < kSamplesPerFrame; i += 8) { // Load 8 samples, as [in_channel_0 + i * 4] and // [in_channel_0 + i * 4 + 16] movups. __m128 in_mm0 = _mm_loadu_ps(&in_channel_0[i]); __m128 in_mm1 = _mm_loadu_ps(&in_channel_0[i + 4]); // Rescale. in_mm0 = _mm_mul_ps(in_mm0, scale_mm); in_mm1 = _mm_mul_ps(in_mm1, scale_mm); // Cast to int32. __m128i out_mm0 = _mm_cvtps_epi32(in_mm0); __m128i out_mm1 = _mm_cvtps_epi32(in_mm1); // Saturated cast and pack to int16. __m128i out_mm = _mm_packs_epi32(out_mm0, out_mm1); // Byte swap. out_mm = _mm_shuffle_epi8(out_mm, shufmask); // Store, as [out + i * 2] movdqu. _mm_storeu_si128(reinterpret_cast<__m128i*>(&out[i]), out_mm); } } #else uint32_t o = 0; for (uint32_t i = 0; i < kSamplesPerFrame; i++) { for (uint32_t j = 0; j <= uint32_t(is_two_channel); j++) { // Select the appropriate array based on the current channel. auto in = reinterpret_cast(samples[j]); // Raw samples sometimes aren't within [-1, 1] float scaled_sample = xe::clamp_float(in[i], -1.0f, 1.0f) * scale; // Convert the sample and output it in big endian. auto sample = static_cast(scaled_sample); out[o++] = xe::byte_swap(sample); } } #endif } } // namespace apu } // namespace xe