Files
dusk/src/dusk/audio/stream/audiostreammp3.h
T
YourWishesandClaude Sonnet 5 3ee0a53688 Split audio into stream/ and mixer/ subdirs, fix resulting build breaks
Reorganizes src/dusk/audio/: individual stream implementations
(audiostream, audiostreampcm, audiostreammp3, audiostreammp3decodersw)
move into audio/stream/, and a new audio/mixer/ holds a channel-based
playback queue (audiomixer.c/.h) for future use - not yet wired into
the engine.

Fixes needed to keep the tree buildable after the move:
 - A stray duplicate of audiostreammp3decodersw.c/.h was left at the old
   flat path; removed in favor of the canonical copy in stream/.
 - Updated every #include "audio/audiostream*.h" and the two platform
   CMakeLists.txt (dusklinux, duskdolphin) that still pointed at the old
   flat location.
 - audiomixer.c/.h didn't compile: audiomixerqueue_t was referenced but
   never defined (audiomixerchanneldata_t has the matching fields), a
   trailing comma in audioMixerPlayLooped's parameter list is illegal in
   C, and `file` was declared as an array of pointers instead of a char
   buffer.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-02 09:32:41 -05:00

140 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 "asset/assetfile.h"
#include "audio/audiostreammp3decoder.h"
typedef struct audiostream_s audiostream_t;
// How many interleaved PCM samples (not frames - samples, i.e. frames *
// channels) a single audioStreamMp3DecoderDecodeFrame() call is ever
// allowed to produce. Sizes the pending-sample buffer below, which every
// decoder backend writes directly into - this must be sized to the
// largest of them, not just "one MPEG frame":
// - minimp3 (software, Linux/Dolphin): always exactly one frame, 1152
// samples/channel for MPEG-1 Layer III, doubled here for stereo.
// - sceMp3 (hardware, PSP): its own pcmBuf is provisioned at double that
// (see AUDIO_MP3_PSP_PCM_BUF_SIZE) - i.e. sceMp3Decode() can
// legitimately hand back more than one frame's worth in a single call.
// Sizing this for only one frame silently overflowed stream->mp3.pending
// on real hardware whenever that happened - confirmed as the cause of
// very intermittent audio corruption/clicking, since it only bit when
// sceMp3 actually returned the larger amount.
// So this covers the larger (PSP) case; minimp3's decode is safely well
// within it.
#define AUDIO_MP3_MAX_SAMPLES_PER_FRAME (1152 * 2 * 2)
typedef struct {
// This stream's own private handle into its asset's underlying file -
// see audiostreampcm_t.file's own comment for why this isn't shared
// across streams playing the same asset. Holds compressed MP3 bytes;
// the decoder backend (audiostreammp3decoder.h) reads from it directly.
assetfile_t file;
// Opaque per-platform decoder state - a reserved sceMp3 handle plus its
// buffers on PSP, or an mp3dec_t plus a sliding compressed-byte window on
// the minimp3-based software backend (Linux/Dolphin). Defined by
// whichever audiostreammp3decoder.h is actually visible when this file
// is compiled - see that header's own comment.
audiostreammp3decoder_t decoder;
// One decoded MPEG frame's worth of PCM, held here across Read() calls
// since a frame (576 or 1152 samples/channel) rarely lines up exactly
// with whatever frameCount a caller asks for. pendingPosition marks how
// many of the first pendingFrames frames have already been consumed.
int16_t pending[AUDIO_MP3_MAX_SAMPLES_PER_FRAME];
size_t pendingFrames;
size_t pendingPosition;
// Current logical PCM frame position, relative to the start of the
// decoded stream - decoding is push-forward-only (see
// audioStreamMp3Seek()'s own comment on why a backward seek re-decodes
// from the start rather than truly random-accessing).
size_t position;
// Total decoded PCM frame count for the whole clip - copied from the
// asset's parsed metadata at Init (see assetmp3file_t.totalFrames),
// exact if a Xing/Info header was present, otherwise an estimate.
size_t totalFrames;
} audiostreammp3_t;
/**
* Configures the given MP3 audio stream from its already-assigned asset
* (see audiostream_t.asset - set by audioStreamInit(), which is what
* should be calling this, not application code directly): opens this
* stream's own private file handle onto the asset's compressed MPEG data,
* and initializes the platform decoder backend
* (audioStreamMp3DecoderInit()).
*
* Must be called after stream->asset is set and before audioStreamPlay().
*
* @param stream The audio stream to configure.
* @return Error indicating success or failure.
*/
errorret_t audioStreamMp3Init(audiostream_t *stream);
/**
* Disposes the given MP3 audio stream's decoder backend and private file
* handle.
*
* @param stream The audio stream to dispose.
* @return Error indicating success or failure.
*/
errorret_t audioStreamMp3Dispose(audiostream_t *stream);
/**
* Returns the total number of frames (one sample per channel) available in
* the stream's underlying MP3 data - see assetmp3file_t.totalFrames's own
* comment on why this is sometimes an estimate rather than an exact count,
* unlike audioStreamPcmGetTotalFrames().
*
* @param stream The audio stream to query. Must be AUDIO_STREAM_TYPE_MP3.
* @return The total number of frames available.
*/
size_t audioStreamMp3GetTotalFrames(const audiostream_t *stream);
/**
* Seeks the stream's decode position to the given frame offset. Unlike
* audioStreamPcmSeek(), this is never cheap: MPEG frames aren't
* independently decodable (each one's bit reservoir can depend on data
* carried over from prior frames), so there's no equivalent of PCM's
* direct byte-offset seek - every call, forward or backward, rewinds the
* decoder to the very start of the compressed stream and decodes (and
* discards) frames until reaching the target. Acceptable for this engine's
* actual use (looping background music, where loopTo is typically near
* the start anyway), but a real, non-constant cost for a seek deep into a
* long track.
*
* @param stream The audio stream to seek. Must be AUDIO_STREAM_TYPE_MP3.
* @param frame Frame offset to seek to, relative to the start of the
* decoded stream.
* @return Error indicating success or failure.
*/
errorret_t audioStreamMp3Seek(audiostream_t *stream, const size_t frame);
/**
* Reads up to frameCount frames of PCM sample data from the stream's
* current position, advancing it by however many frames were actually
* read. Reads fewer than frameCount (down to zero) once the underlying
* MP3 data is exhausted, rather than erroring - same contract as
* audioStreamPcmRead().
*
* @param stream The audio stream to read from. Must be AUDIO_STREAM_TYPE_MP3.
* @param buffer Destination buffer, sized for at least frameCount frames.
* @param frameCount Maximum number of frames to read.
* @param outFramesRead Set to the number of frames actually read.
* @return Error indicating success or failure.
*/
errorret_t audioStreamMp3Read(
audiostream_t *stream,
int16_t *buffer,
const size_t frameCount,
size_t *outFramesRead
);