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]>
140 lines
6.0 KiB
C
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
|
|
);
|