diff --git a/src/xenia/apu/audio_decoder.h b/src/xenia/apu/audio_decoder.h index bf08b2442..9a3a687d4 100644 --- a/src/xenia/apu/audio_decoder.h +++ b/src/xenia/apu/audio_decoder.h @@ -45,12 +45,12 @@ class AudioDecoder { int DecodePacket(uint8_t* output, size_t offset, size_t size); private: + // libav structures AVCodec* codec_; AVCodecContext* context_; AVFrame* decoded_frame_; AVPacket* packet_; - uint8_t bits_per_frame_; size_t current_frame_pos_; uint8_t* current_frame_; uint32_t frame_samples_size_; diff --git a/src/xenia/apu/audio_system.cc b/src/xenia/apu/audio_system.cc index 24eaaf437..c7ba2077c 100644 --- a/src/xenia/apu/audio_system.cc +++ b/src/xenia/apu/audio_system.cc @@ -360,13 +360,38 @@ void AudioSystem::ProcessXmaContext(XMAContext& context, XMAContextData& data) { // 3 - 48 kHz ? // SPUs also support stereo decoding. (data.is_stereo) + bool output_written_bytes = false; + while (data.output_buffer_valid) { // Check the output buffer - we cannot decode anything else if it's // unavailable. // Output buffers are in raw PCM samples, 256 bytes per block. + // Output buffer is a ring buffer. We need to write from the write offset + // to the read offset. uint32_t output_size_bytes = data.output_buffer_block_count * 256; - uint32_t output_offset_bytes = data.output_buffer_write_offset * 256; - uint32_t output_remaining_bytes = output_size_bytes - output_offset_bytes; + uint32_t output_write_offset_bytes = data.output_buffer_write_offset * 256; + uint32_t output_read_offset_bytes = data.output_buffer_read_offset * 256; + + uint32_t output_remaining_bytes = 0; + bool output_wraparound = false; + bool output_all = false; + + if (output_write_offset_bytes < output_read_offset_bytes) { + // Case 1: write -> read + output_remaining_bytes = + output_read_offset_bytes - output_write_offset_bytes; + } else if (output_read_offset_bytes < output_write_offset_bytes) { + // Case 2: write -> end -> read + output_remaining_bytes = output_size_bytes - output_write_offset_bytes; + output_remaining_bytes += output_read_offset_bytes; + + // Doesn't count if it's 0! + output_wraparound = true; + } else if (!output_written_bytes) { + output_remaining_bytes = output_size_bytes; + output_all = true; + } + if (!output_remaining_bytes) { // Can't write any more data. Break. // The game will kick us again with a new output buffer later. @@ -380,8 +405,45 @@ void AudioSystem::ProcessXmaContext(XMAContext& context, XMAContextData& data) { int read_bytes = 0; int decode_attempts_remaining = 3; while (decode_attempts_remaining) { - read_bytes = context.decoder->DecodePacket(out, output_offset_bytes, - output_remaining_bytes); + // TODO: We need a ringbuffer util class! + if (output_all) { + read_bytes = context.decoder->DecodePacket(out, + output_write_offset_bytes, + output_size_bytes + - output_write_offset_bytes); + } else if (output_wraparound) { + // write -> end + int r1 = context.decoder->DecodePacket(out, + output_write_offset_bytes, + output_size_bytes + - output_write_offset_bytes); + if (r1 < 0) { + --decode_attempts_remaining; + continue; + } + + // begin -> read + // FIXME: If it fails here this'll break stuff + int r2 = context.decoder->DecodePacket(out, 0, + output_read_offset_bytes); + if (r2 < 0) { + --decode_attempts_remaining; + continue; + } + + read_bytes = r1 + r2; + } else { + // write -> read + read_bytes = context.decoder->DecodePacket(out, + output_write_offset_bytes, + output_read_offset_bytes + - output_write_offset_bytes); + } + + if (read_bytes > 0) { + output_written_bytes = true; + } + if (read_bytes >= 0) { // Ok. break; @@ -389,11 +451,13 @@ void AudioSystem::ProcessXmaContext(XMAContext& context, XMAContextData& data) { // Sometimes the decoder will fail on a packet. I think it's // looking for cross-packet frames and failing. If you run it again // on the same packet it'll work though. - XELOGAPU("APU failed to decode packet (returned %.8X)", -read_bytes); --decode_attempts_remaining; } } + if (!decode_attempts_remaining) { + XELOGAPU("AudioSystem: libav failed to decode packet (returned %.8X)", -read_bytes); + // Failed out. if (data.input_buffer_0_valid || data.input_buffer_1_valid) { // There's new data available - maybe we'll be ok if we decode it? @@ -404,7 +468,12 @@ void AudioSystem::ProcessXmaContext(XMAContext& context, XMAContextData& data) { break; } } + data.output_buffer_write_offset += uint32_t(read_bytes) / 256; + if (data.output_buffer_write_offset > data.output_buffer_block_count) { + // Wraparound! + data.output_buffer_write_offset -= data.output_buffer_block_count; + } // If we need more data and the input buffers have it, grab it. if (read_bytes) { @@ -426,10 +495,8 @@ void AudioSystem::ProcessXmaContext(XMAContext& context, XMAContextData& data) { // See if we've finished with the input. // Block count is in packets, so expand by packet size. - uint32_t input_size_0_bytes = - (data.input_buffer_0_block_count) * 2048; - uint32_t input_size_1_bytes = - (data.input_buffer_1_block_count) * 2048; + uint32_t input_size_0_bytes = (data.input_buffer_0_packet_count) * 2048; + uint32_t input_size_1_bytes = (data.input_buffer_1_packet_count) * 2048; // Total input size uint32_t input_size_bytes = input_size_0_bytes + input_size_1_bytes; @@ -437,8 +504,7 @@ void AudioSystem::ProcessXmaContext(XMAContext& context, XMAContextData& data) { // Input read offset is in bits. Typically starts at 32 (4 bytes). // "Sequence" offset - used internally for WMA Pro decoder. // Just the read offset. - uint32_t seq_offset_bytes = - (data.input_buffer_read_offset & ~0x7FF) / 8; + uint32_t seq_offset_bytes = (data.input_buffer_read_offset & ~0x7FF) / 8; if (seq_offset_bytes < input_size_bytes) { // Setup input offset and input buffer. @@ -529,16 +595,15 @@ void AudioSystem::WriteRegister(uint32_t addr, uint64_t value) { auto context_ptr = memory()->TranslateVirtual(context.guest_ptr); XMAContextData data(context_ptr); - XELOGAPU( - "AudioSystem: kicking context %d (%d/%d bytes)", context_id, + XELOGAPU("AudioSystem: kicking context %d (%d/%d bytes)", context_id, (data.input_buffer_read_offset & ~0x7FF) / 8, - (data.input_buffer_0_block_count + data.input_buffer_1_block_count) + (data.input_buffer_0_packet_count + data.input_buffer_1_packet_count) * XMAContextData::kBytesPerBlock); // Reset valid flags so our audio decoder knows to process this one. data.input_buffer_0_valid = data.input_buffer_0_ptr != 0; data.input_buffer_1_valid = data.input_buffer_1_ptr != 0; - data.output_buffer_write_offset = 0; + //data.output_buffer_write_offset = 0; data.Store(context_ptr); diff --git a/src/xenia/apu/audio_system.h b/src/xenia/apu/audio_system.h index 25bd2f9db..3bc5e6ff4 100644 --- a/src/xenia/apu/audio_system.h +++ b/src/xenia/apu/audio_system.h @@ -44,9 +44,9 @@ struct XMAContextData { static const uint32_t kSamplesPerSubframe = 128; // DWORD 0 - uint32_t input_buffer_0_block_count : 12; // XMASetInputBuffer0, number of - // 2KB blocks. AKA SizeRead0 - // Maximum 4095 packets. + uint32_t input_buffer_0_packet_count : 12; // XMASetInputBuffer0, number of + // 2KB packets. Max 4095 packets. + // These packets form a block. uint32_t loop_count : 8; // +12bit, XMASetLoopData NumLoops uint32_t input_buffer_0_valid : 1; // +20bit, XMAIsInputBuffer0Valid uint32_t input_buffer_1_valid : 1; // +21bit, XMAIsInputBuffer1Valid @@ -56,8 +56,9 @@ struct XMAContextData { // AKA OffsetWrite // DWORD 1 - uint32_t input_buffer_1_block_count : 12; // XMASetInputBuffer1, number of - // 2KB blocks. + uint32_t input_buffer_1_packet_count : 12; // XMASetInputBuffer1, number of + // 2KB packets. Max 4095 packets. + // These packets form a block. uint32_t loop_subframe_end : 2; // +12bit, XMASetLoopData uint32_t unk_dword_1_a : 3; // ? might be loop_subframe_skip uint32_t loop_subframe_skip : 3; // +17bit, XMASetLoopData might be @@ -65,8 +66,8 @@ struct XMAContextData { uint32_t subframe_decode_count : 4; // +20bit might be subframe_skip_count uint32_t unk_dword_1_b : 3; // ? NumSubframesToSkip/NumChannels(?) uint32_t sample_rate : 2; // +27bit enum of sample rates - uint32_t is_stereo : 1; // +29bit might be NumChannels - uint32_t unk_dword_1_c : 1; // ? part of NumChannels? + uint32_t is_stereo : 1; // +29bit + uint32_t unk_dword_1_c : 1; // +29bit uint32_t output_buffer_valid : 1; // +31bit, XMAIsOutputBufferValid // DWORD 2 diff --git a/src/xenia/kernel/xboxkrnl_audio_xma.cc b/src/xenia/kernel/xboxkrnl_audio_xma.cc index f3994d605..fa81d2c47 100644 --- a/src/xenia/kernel/xboxkrnl_audio_xma.cc +++ b/src/xenia/kernel/xboxkrnl_audio_xma.cc @@ -104,9 +104,9 @@ SHIM_CALL XMAInitializeContext_shim(PPCContext* ppc_context, XMAContextData context(SHIM_MEM_ADDR(context_ptr)); context.input_buffer_0_ptr = SHIM_MEM_32(context_init_ptr + 0 * 4); - context.input_buffer_0_block_count = SHIM_MEM_32(context_init_ptr + 1 * 4); + context.input_buffer_0_packet_count = SHIM_MEM_32(context_init_ptr + 1 * 4); context.input_buffer_1_ptr = SHIM_MEM_32(context_init_ptr + 2 * 4); - context.input_buffer_1_block_count = SHIM_MEM_32(context_init_ptr + 3 * 4); + context.input_buffer_1_packet_count = SHIM_MEM_32(context_init_ptr + 3 * 4); context.input_buffer_read_offset = SHIM_MEM_32(context_init_ptr + 4 * 4); context.output_buffer_ptr = SHIM_MEM_32(context_init_ptr + 5 * 4); context.output_buffer_block_count = SHIM_MEM_32(context_init_ptr + 6 * 4); @@ -191,7 +191,7 @@ SHIM_CALL XMASetInputBuffer0_shim(PPCContext* ppc_context, XMAContextData context(SHIM_MEM_ADDR(context_ptr)); context.input_buffer_0_ptr = buffer_ptr; - context.input_buffer_0_block_count = block_count; + context.input_buffer_0_packet_count = block_count; context.input_buffer_read_offset = 32; // in bits context.input_buffer_0_valid = buffer_ptr ? 1 : 0; @@ -240,7 +240,7 @@ SHIM_CALL XMASetInputBuffer1_shim(PPCContext* ppc_context, XMAContextData context(SHIM_MEM_ADDR(context_ptr)); context.input_buffer_1_ptr = buffer_ptr; - context.input_buffer_1_block_count = block_count; + context.input_buffer_1_packet_count = block_count; context.input_buffer_read_offset = 32; // in bits context.input_buffer_1_valid = buffer_ptr ? 1 : 0; @@ -419,17 +419,21 @@ void xe::kernel::xboxkrnl::RegisterAudioXmaExports( SHIM_SET_MAPPING("xboxkrnl.exe", XMASetLoopData, state); SHIM_SET_MAPPING("xboxkrnl.exe", XMAGetInputBufferReadOffset, state); SHIM_SET_MAPPING("xboxkrnl.exe", XMASetInputBufferReadOffset, state); + SHIM_SET_MAPPING("xboxkrnl.exe", XMASetInputBuffer0, state); SHIM_SET_MAPPING("xboxkrnl.exe", XMAIsInputBuffer0Valid, state); SHIM_SET_MAPPING("xboxkrnl.exe", XMASetInputBuffer0Valid, state); + SHIM_SET_MAPPING("xboxkrnl.exe", XMASetInputBuffer1, state); SHIM_SET_MAPPING("xboxkrnl.exe", XMAIsInputBuffer1Valid, state); SHIM_SET_MAPPING("xboxkrnl.exe", XMASetInputBuffer1Valid, state); + SHIM_SET_MAPPING("xboxkrnl.exe", XMAIsOutputBufferValid, state); SHIM_SET_MAPPING("xboxkrnl.exe", XMASetOutputBufferValid, state); SHIM_SET_MAPPING("xboxkrnl.exe", XMASetOutputBufferReadOffset, state); SHIM_SET_MAPPING("xboxkrnl.exe", XMAGetOutputBufferReadOffset, state); SHIM_SET_MAPPING("xboxkrnl.exe", XMAGetOutputBufferWriteOffset, state); + SHIM_SET_MAPPING("xboxkrnl.exe", XMAGetPacketMetadata, state); SHIM_SET_MAPPING("xboxkrnl.exe", XMAEnableContext, state); SHIM_SET_MAPPING("xboxkrnl.exe", XMADisableContext, state);