mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
libatrac3plus picks the codec parameter from the frame size and the header's joint stereo flag, and the channel count plays no part. We guessed joint stereo from the frame size and channel count instead, so LocoRoco 2's MuiMui house music never started: the game writes a 2-channel normal-stereo header for every track it streams, and that 0xC0 track holds one mono sound unit per frame. Taken as joint stereo, its first frame failed during setup, and the game retried forever. Now the joint stereo flag comes from the track header, and the decoder's channel count from the parameter it maps to, so that track decodes as mono into both output channels, as on hardware. Low-level decoding, which has no header, still goes by the frame size. Atrac2 saves the flag; older states fall back to the guess. Fixes #8647. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
253 lines
9.7 KiB
C++
253 lines
9.7 KiB
C++
#pragma once
|
|
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/Swap.h"
|
|
|
|
#include "Core/MemMap.h"
|
|
#include "Core/HLE/sceAudiocodec.h"
|
|
|
|
constexpr u32 ATRAC3_MAX_SAMPLES = 0x400; // 1024
|
|
constexpr u32 ATRAC3PLUS_MAX_SAMPLES = 0x800; // 2048
|
|
|
|
// The "state" member of SceAtracIdInfo.
|
|
enum AtracStatus : u8 {
|
|
ATRAC_STATUS_UNINITIALIZED = 0, // bad state
|
|
|
|
ATRAC_STATUS_NO_DATA = 1,
|
|
|
|
// The entire file is loaded into memory, no further file access needed.
|
|
ATRAC_STATUS_ALL_DATA_LOADED = 2,
|
|
|
|
// The buffer is sized to fit the entire file, but it's only partially loaded, so you can start playback before loading the whole file.
|
|
ATRAC_STATUS_HALFWAY_BUFFER = 3,
|
|
|
|
// In these ones, the buffer is smaller than the file, and data is streamed into it as needed for playback.
|
|
// These are the most complex modes, both to implement and use.
|
|
ATRAC_STATUS_STREAMED_WITHOUT_LOOP = 4,
|
|
ATRAC_STATUS_STREAMED_LOOP_FROM_END = 5,
|
|
// This means there's additional audio after the loop.
|
|
// i.e. ~~before loop~~ [ ~~this part loops~~ ] ~~after loop~~
|
|
// The "fork in the road" means a second buffer is needed for the second path.
|
|
ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER = 6,
|
|
|
|
// In this mode, the only API to call is sceAtracLowLevelDecode, which decodes a stream packet by packet without any other metadata.
|
|
ATRAC_STATUS_LOW_LEVEL = 8,
|
|
|
|
// This mode is for using an Atrac context as the audio source for an sceSas channel. Not used a lot (Sol Trigger).
|
|
ATRAC_STATUS_FOR_SCESAS = 16,
|
|
|
|
// Bitwise-and the status with this to check for any of the streaming modes in a single test.
|
|
ATRAC_STATUS_STREAMED_MASK = 4,
|
|
};
|
|
|
|
const char *AtracStatusToString(AtracStatus status);
|
|
|
|
inline bool AtracStatusIsStreaming(AtracStatus status) {
|
|
return (status & ATRAC_STATUS_STREAMED_MASK) != 0;
|
|
}
|
|
inline bool AtracStatusIsNormal(AtracStatus status) {
|
|
return (int)status >= ATRAC_STATUS_ALL_DATA_LOADED && (int)status <= ATRAC_STATUS_STREAMED_LOOP_WITH_TRAILER;
|
|
}
|
|
|
|
struct SceAtracIdInfo {
|
|
s32 decodePos; // Sample position in the song that we'll next be decoding from.
|
|
s32 endSample; // Last sample index of the track.
|
|
s32 loopStart; // Start of the loop (sample index)
|
|
s32 loopEnd; // End of the loop (sample index)
|
|
s32 firstValidSample; // Seems to be the number of skipped samples at the start. After SetID, decodePos will match this. Was previously misnamed 'samplesPerChan'.
|
|
u8 numSkipFrames; // This is 1 for a single frame when a loop is triggered, otherwise seems to stay at 0. Likely mis-named.
|
|
AtracStatus state; // State enum, see AtracStatus.
|
|
u8 curBuffer; // Current buffer (1 == second, 2 == done?) Previously unk
|
|
u8 numChan; // Number of audio channels, usually 2 but 1 is possible.
|
|
u16 sampleSize; // Size in bytes of an encoded audio frame.
|
|
u16 codec; // Codec. 0x1000 is Atrac3+, 0x1001 is Atrac3. See the PSP_CODEC_ enum (only these two are supported).
|
|
s32 dataOff; // File offset in bytes where the Atrac3+ frames start appearing. The first dummy packet starts here.
|
|
s32 curFileOff; // File offset in bytes corresponding to the start of next packet that will be *decoded* (on the next call to sceAtracDecodeData).
|
|
s32 fileDataEnd; // File size in bytes.
|
|
s32 loopNum; // Current loop counter. If 0, will not loop. -1 loops for ever, positive numbers get decremented on the loop end. So to play a song 3 times and then end, set this to 2.
|
|
s32 streamDataByte; // Number of bytes of queued/buffered/uploaded data. In full and half-way modes, this isn't decremented as you decode.
|
|
s32 streamOff; // Streaming modes only: The byte offset inside the RAM buffer where sceAtracDecodeData will read from next. ONLY points to even packet boundaries.
|
|
s32 secondStreamOff; // A kind of stream position in the secondary buffer.
|
|
u32 buffer; // Address in RAM of the main buffer.
|
|
u32 secondBuffer; // Address in RAM of the second buffer, or 0 if not used.
|
|
u32 bufferByte; // Size in bytes of the main buffer.
|
|
u32 secondBufferByte; // Size in bytes of the second buffer.
|
|
// Offset 72 here.
|
|
// make sure the size is 128
|
|
u32 unk[14];
|
|
|
|
// Simple helpers. Similar ones are on track_, but we shouldn't need track_ anymore when playing back.
|
|
|
|
int SamplesPerFrame() const {
|
|
return codec == 0x1000 ? ATRAC3PLUS_MAX_SAMPLES : ATRAC3_MAX_SAMPLES;
|
|
}
|
|
int SamplesFrameMask() const {
|
|
return SamplesPerFrame() - 1;
|
|
}
|
|
int SkipSamples() const {
|
|
// These first samples are skipped, after first possibly skipping 0-2 full frames, it seems.
|
|
return codec == 0x1000 ? 0x170 : 0x45;
|
|
}
|
|
int BitRate() const {
|
|
int bitrate = (sampleSize * 352800) / 1000;
|
|
if (codec == PSP_CODEC_AT3PLUS) {
|
|
bitrate = ((bitrate >> 11) + 8) & 0xFFFFFFF0;
|
|
} else {
|
|
bitrate = (bitrate + 511) >> 10;
|
|
}
|
|
return bitrate;
|
|
}
|
|
};
|
|
|
|
// One of these structs is allocated for each Atrac context.
|
|
// The raw codec state is stored in 'codec'.
|
|
// The internal playback state is stored in 'info', and that is used for all state keeping in the Atrac2 implementation,
|
|
// imitating what happens on hardware as closely as possible.
|
|
struct SceAtracContext {
|
|
// size 128
|
|
SceAudiocodecCodec codec;
|
|
// size 128
|
|
SceAtracIdInfo info;
|
|
};
|
|
|
|
struct Atrac3LowLevelParams {
|
|
int encodedChannels;
|
|
int outputChannels;
|
|
int bytesPerFrame;
|
|
};
|
|
|
|
struct AtracSingleResetBufferInfo {
|
|
u32_le writePosPtr;
|
|
u32_le writableBytes;
|
|
u32_le minWriteBytes;
|
|
u32_le filePos;
|
|
};
|
|
|
|
struct AtracResetBufferInfo {
|
|
AtracSingleResetBufferInfo first;
|
|
AtracSingleResetBufferInfo second;
|
|
};
|
|
|
|
struct AtracSasStreamState {
|
|
u32 bufPtr[2]{};
|
|
u32 bufSize[2]{};
|
|
int streamOffset = 0;
|
|
int fileOffset = 0;
|
|
int curBuffer = 0;
|
|
bool isStreaming = false;
|
|
|
|
int CurPos() const {
|
|
int retval = fileOffset - bufSize[curBuffer] + streamOffset;
|
|
_dbg_assert_(retval >= 0);
|
|
return retval;
|
|
}
|
|
};
|
|
|
|
const int PSP_ATRAC_ALLDATA_IS_ON_MEMORY = -1;
|
|
const int PSP_ATRAC_NONLOOP_STREAM_DATA_IS_ON_MEMORY = -2;
|
|
const int PSP_ATRAC_LOOP_STREAM_DATA_IS_ON_MEMORY = -3;
|
|
|
|
// This is not a PSP-native struct.
|
|
// But, it's stored in its entirety in savestates, which makes it awkward to change it.
|
|
// This is used for both first_ and second_, but the latter doesn't use all the fields.
|
|
struct InputBuffer {
|
|
// Address of the buffer.
|
|
u32 addr;
|
|
// Size of data read so far into dataBuf_ (to be removed.)
|
|
u32 size;
|
|
// Offset into addr at which new data is added.
|
|
u32 offset;
|
|
// Last writableBytes number (to be removed.)
|
|
u32 writableBytes;
|
|
// Unused, always 0.
|
|
u32 neededBytes;
|
|
// Total size of the entire file data.
|
|
u32 _filesize_dontuse;
|
|
// Offset into the file at which new data is read.
|
|
u32 fileoffset;
|
|
};
|
|
|
|
class AudioDecoder;
|
|
class PointerWrap;
|
|
struct Track;
|
|
|
|
class AtracBase {
|
|
public:
|
|
virtual ~AtracBase();
|
|
|
|
virtual void DoState(PointerWrap &p) = 0;
|
|
|
|
// TODO: Find a way to get rid of this from the base class.
|
|
virtual void UpdateContextFromPSPMem() = 0;
|
|
|
|
virtual int Channels() const = 0;
|
|
|
|
int GetOutputChannels() const {
|
|
return outputChannels_;
|
|
}
|
|
void SetOutputChannels(int channels) {
|
|
// Only used for sceSas audio. To be refactored away in the future.
|
|
outputChannels_ = channels;
|
|
}
|
|
|
|
virtual u32 GetInternalCodecError() const { return 0; }
|
|
|
|
PSPPointer<SceAtracContext> context_{};
|
|
|
|
virtual AtracStatus BufferState() const = 0;
|
|
|
|
virtual int SetLoopNum(int loopNum) = 0;
|
|
virtual int LoopNum() const = 0;
|
|
virtual int LoopStatus() const = 0;
|
|
|
|
virtual int CodecType() const = 0;
|
|
|
|
AudioDecoder *Decoder() const {
|
|
return decoder_;
|
|
}
|
|
|
|
// jointStereo only matters for Atrac3. Take it from the track header where there is one.
|
|
void CreateDecoder(int codecType, int bytesPerFrame, int channels, bool jointStereo);
|
|
|
|
virtual void NotifyGetContextAddress() = 0;
|
|
|
|
virtual int GetNextDecodePosition(int *pos) const = 0;
|
|
virtual int RemainingFrames() const = 0;
|
|
virtual bool HasSecondBuffer() const = 0;
|
|
virtual int Bitrate() const = 0;
|
|
virtual int BytesPerFrame() const = 0;
|
|
virtual int SamplesPerFrame() const = 0;
|
|
|
|
virtual void GetStreamDataInfo(u32 *writePtr, u32 *writableBytes, u32 *readOffset) = 0; // This should be const, but the legacy impl stops it (it's wrong).
|
|
virtual int AddStreamData(u32 bytesToAdd) = 0;
|
|
virtual int ResetPlayPosition(int sample, int bytesWrittenFirstBuf, int bytesWrittenSecondBuf, bool *delay) = 0;
|
|
virtual int GetBufferInfoForResetting(AtracResetBufferInfo *bufferInfo, int sample, bool *delay) = 0; // NOTE: Not const! This can cause SkipFrames!
|
|
virtual int SetData(const Track &track, u32 buffer, u32 readSize, u32 bufferSize, u32 fileSize, int outputChannels, bool isAA3) = 0;
|
|
// How many frames the last SetData decoded and threw away, to get to the first sample.
|
|
int SkippedFramesOnSetData() const { return setDataSkippedFrames_; }
|
|
|
|
virtual int GetSecondBufferInfo(u32 *fileOffset, u32 *desiredSize) const = 0;
|
|
virtual int SetSecondBuffer(u32 secondBuffer, u32 secondBufferSize) = 0;
|
|
virtual u32 DecodeData(u8 *outbuf, u32 outbufPtr, int *SamplesNum, int *finish, int *remains) = 0;
|
|
virtual int DecodeLowLevel(const u8 *srcData, int *bytesConsumed, s16 *dstData, int *bytesWritten) = 0;
|
|
|
|
virtual u32 GetNextSamples() = 0; // This should be const, but the legacy impl stops it (it's wrong).
|
|
virtual void InitLowLevel(const Atrac3LowLevelParams ¶ms, int codecType) = 0;
|
|
|
|
virtual void CheckForSas() = 0;
|
|
virtual int EnqueueForSas(u32 address, u32 ptr) = 0;
|
|
virtual void DecodeForSas(s16 *dstData, int *bytesWritten, int *finish) = 0;
|
|
virtual const AtracSasStreamState *StreamStateForSas() const { return nullptr; }
|
|
|
|
virtual int GetSoundSample(int *endSample, int *loopStartSample, int *loopEndSample) const = 0;
|
|
|
|
virtual int GetContextVersion() const = 0;
|
|
|
|
protected:
|
|
u16 outputChannels_ = 2;
|
|
int setDataSkippedFrames_ = 0;
|
|
|
|
// TODO: Save the internal state of this, now technically possible.
|
|
AudioDecoder *decoder_ = nullptr;
|
|
};
|