Pure whitespace/line-break reformatting (braces, newlines, and line continuations matching this codebase's existing wrap conventions) - no logic, string content, or identifiers changed anywhere. Confirmed via diff against the pre-change tree and by rebuilding + re-running the affected test suites, which produce identical pass/fail results. Co-Authored-By: Claude Sonnet 5 <[email protected]>
141 lines
6.0 KiB
C
141 lines
6.0 KiB
C
/**
|
|
* Copyright (c) 2026 Dominic Masters
|
|
*
|
|
* This software is released under the MIT License.
|
|
* https://opensource.org/licenses/MIT
|
|
*/
|
|
|
|
#pragma once
|
|
#include "error/error.h"
|
|
#include <mad.h>
|
|
|
|
typedef struct audiostream_s audiostream_t;
|
|
|
|
// Size of the sliding compressed-byte window libmad decodes from - just
|
|
// needs to comfortably exceed one MPEG frame's worth of bytes plus
|
|
// whatever bit-reservoir carryover libmad itself buffers internally
|
|
// (MAD_BUFFER_MDLEN), with room to refill in reasonably-sized chunks
|
|
// rather than one frame at a time. Unlike this file's previous minimp3-
|
|
// based implementation, libmad reports "not enough data" (MAD_ERROR_BUFLEN)
|
|
// and "genuinely bad data" (every other error code) as distinct, explicit
|
|
// signals rather than one overloaded return value - so, verified against a
|
|
// real ~236s VBR file, this window doesn't need minimp3's 256KB (itself
|
|
// only ~99.7% accurate) to decode every frame correctly.
|
|
#define AUDIO_MP3_SW_BUFFER_SIZE (32 * 1024)
|
|
|
|
/**
|
|
* Software MP3 decoder state, shared by every platform that doesn't have
|
|
* (or doesn't use) a hardware MP3 decoder - currently Linux and Dolphin,
|
|
* both via this same libmad-based implementation
|
|
* (audiostreammp3decodersw.c). See audiostreammp3.h for how this plugs
|
|
* into the shared MP3 stream layer, and audiostreammp3.h's own
|
|
* documentation of audioStreamMp3DecoderInit()/Dispose()/Rewind()/
|
|
* DecodeFrame() for the interface this and the PSP hardware backend
|
|
* (src/duskpsp/audio/audiostreammp3decoder.c) both implement.
|
|
*
|
|
* Note this is libmad (https://www.underbit.com/products/mad/), used here
|
|
* for its bitstream-accurate frame sync (see AUDIO_MP3_SW_BUFFER_SIZE's own
|
|
* comment) - unlike the rest of this project, libmad is GPL-licensed, not
|
|
* MIT. Any binary linking this file's compiled output must comply with the
|
|
* GPL; a deliberate tradeoff made after minimp3 proved unable to decode a
|
|
* real-world VBR file without audible, unresolvable data loss.
|
|
*/
|
|
typedef struct {
|
|
struct mad_stream stream;
|
|
struct mad_frame frame;
|
|
struct mad_synth synth;
|
|
|
|
// libmad reads up to MAD_BUFFER_GUARD bytes past whatever it's given as
|
|
// the end of valid data while decoding the final real frame(s) in a
|
|
// buffer - the trailing MAD_BUFFER_GUARD bytes here are that padding
|
|
// (zeroed once endOfFile is set), never counted in bufferFilled.
|
|
uint8_t buffer[AUDIO_MP3_SW_BUFFER_SIZE + MAD_BUFFER_GUARD];
|
|
size_t bufferFilled;
|
|
|
|
// Set once assetFileRead() returns fewer bytes than requested - there's
|
|
// no more compressed data to refill `buffer` with, though whatever's
|
|
// still in it may still decode into one or more final frames.
|
|
bool_t endOfFile;
|
|
} audiostreammp3decoder_t;
|
|
|
|
/**
|
|
* Initializes the software MP3 decoder for the given stream - resets
|
|
* libmad's stream/frame/synth state and the compressed-byte window (empty,
|
|
* not yet filled from the asset).
|
|
*
|
|
* @param stream The audio stream to initialize. Must be AUDIO_STREAM_TYPE_MP3.
|
|
* @return Error indicating success or failure.
|
|
*/
|
|
errorret_t audioStreamMp3DecoderInit(audiostream_t *stream);
|
|
|
|
/**
|
|
* Disposes the software MP3 decoder for the given stream: tears down
|
|
* libmad's stream/frame state (mad_stream_finish()/mad_frame_finish()) -
|
|
* mad_synth carries no allocated state of its own, so there's nothing to
|
|
* release for it.
|
|
*
|
|
* @param stream The audio stream to dispose. Must be AUDIO_STREAM_TYPE_MP3.
|
|
* @return Error indicating success or failure.
|
|
*/
|
|
errorret_t audioStreamMp3DecoderDispose(audiostream_t *stream);
|
|
|
|
/**
|
|
* Resets the decoder to the very start of the compressed stream: rewinds
|
|
* stream->mp3.file back to the asset's parsed data offset, resets libmad's
|
|
* stream/frame/synth state and the compressed-byte window, so the next
|
|
* DecodeFrame() call starts decoding from the first MPEG frame again. See
|
|
* audioStreamMp3Seek()'s own comment on why every seek goes through here.
|
|
*
|
|
* @param stream The audio stream to rewind. Must be AUDIO_STREAM_TYPE_MP3.
|
|
* @return Error indicating success or failure.
|
|
*/
|
|
errorret_t audioStreamMp3DecoderRewind(audiostream_t *stream);
|
|
|
|
/**
|
|
* Tops up the compressed-byte window from stream->mp3.file: shifts
|
|
* whatever libmad hasn't consumed yet (from its own stream.next_frame
|
|
* cursor) to the front of the window, reads as much more as there's room
|
|
* for, and re-buffers libmad's stream (mad_stream_buffer()) over the
|
|
* result - padding with MAD_BUFFER_GUARD zero bytes once the file's
|
|
* genuinely exhausted, since libmad may read slightly past the end of
|
|
* valid data while decoding a final frame.
|
|
*
|
|
* @param stream The audio stream to refill. Must be AUDIO_STREAM_TYPE_MP3.
|
|
* @return Error indicating success or failure.
|
|
*/
|
|
errorret_t audioStreamMp3DecoderRefill(audiostream_t *stream);
|
|
|
|
/**
|
|
* Converts one libmad fixed-point PCM sample to a clamped 16-bit sample -
|
|
* the standard scale() conversion from libmad's own reference examples
|
|
* (minimad.c et al.), rounding to nearest and clamping to +/-1.0 rather
|
|
* than wrapping on overflow.
|
|
*
|
|
* @param sample The fixed-point sample to convert (mad_fixed_t,
|
|
* Q1.MAD_F_FRACBITS).
|
|
* @return The converted 16-bit signed sample.
|
|
*/
|
|
int16_t audioStreamMp3DecoderScale(mad_fixed_t sample);
|
|
|
|
/**
|
|
* Decodes the next MPEG frame's worth of PCM samples, topping up the
|
|
* compressed-byte window from stream->mp3.file as needed
|
|
* (audioStreamMp3DecoderRefill()). Writes decoded samples to `out` (sized
|
|
* for at least AUDIO_MP3_MAX_SAMPLES_PER_FRAME int16_t values) and sets
|
|
* *outFrames to how many frames (not samples) were produced - 0 once the
|
|
* compressed stream is genuinely exhausted, never negative (a corrupt/
|
|
* truncated stream is treated the same as a clean end, not an error,
|
|
* matching audioStreamPcmRead()'s own tolerance for a short/truncated
|
|
* asset).
|
|
*
|
|
* @param stream The audio stream to decode. Must be AUDIO_STREAM_TYPE_MP3.
|
|
* @param out Destination buffer for decoded samples.
|
|
* @param outFrames Set to the number of frames actually decoded.
|
|
* @return Error indicating success or failure.
|
|
*/
|
|
errorret_t audioStreamMp3DecoderDecodeFrame(
|
|
audiostream_t *stream,
|
|
int16_t *out,
|
|
size_t *outFrames
|
|
);
|