/* This file is part of jpcsp. Jpcsp is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Jpcsp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Jpcsp. If not, see <http://www.gnu.org/licenses/>. */ package jpcsp.HLE.modules; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_AA3_INVALID_CODEC; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_AA3_INVALID_HEADER; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_AA3_INVALID_HEADER_FLAGS; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_AA3_INVALID_HEADER_VERSION; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_ATRAC_ALL_DATA_DECODED; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_ATRAC_API_FAIL; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_ATRAC_BAD_ID; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_ATRAC_BUFFER_IS_EMPTY; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_ATRAC_INCORRECT_READ_SIZE; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_ATRAC_INVALID_SIZE; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_ATRAC_NO_ID; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_ATRAC_UNKNOWN_FORMAT; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_BUSY; import static jpcsp.HLE.modules.SysMemUserForUser.KERNEL_PARTITION_ID; import static jpcsp.HLE.modules.SysMemUserForUser.PSP_SMEM_Low; import static jpcsp.HLE.modules.sceAudiocodec.PSP_CODEC_AT3; import static jpcsp.HLE.modules.sceAudiocodec.PSP_CODEC_AT3PLUS; import static jpcsp.util.Utilities.readUnaligned32; import jpcsp.HLE.BufferInfo; import jpcsp.HLE.BufferInfo.Usage; import jpcsp.HLE.CanBeNull; import jpcsp.HLE.CheckArgument; import jpcsp.HLE.HLEFunction; import jpcsp.HLE.HLELogging; import jpcsp.HLE.HLEModule; import jpcsp.HLE.HLEUnimplemented; import jpcsp.HLE.SceKernelErrorException; import jpcsp.HLE.TPointer; import jpcsp.HLE.TPointer16; import jpcsp.HLE.TPointer32; import jpcsp.Memory; import jpcsp.HLE.Modules; import jpcsp.HLE.kernel.types.SceKernelErrors; import jpcsp.HLE.kernel.types.pspFileBuffer; import jpcsp.HLE.modules.SysMemUserForUser.SysMemInfo; import jpcsp.HLE.modules.sceAudiocodec.AudiocodecInfo; import jpcsp.media.codec.CodecFactory; import jpcsp.media.codec.ICodec; import jpcsp.media.codec.atrac3.Atrac3Decoder; import jpcsp.media.codec.atrac3plus.Atrac3plusDecoder; import jpcsp.util.Utilities; import org.apache.log4j.Logger; public class sceAtrac3plus extends HLEModule { public static Logger log = Modules.getLogger("sceAtrac3plus"); @Override public void start() { for (int i = 0; i < atracIDs.length; i++) { atracIDs[i] = new AtracID(i); } // Tested on PSP: // Only 2 atracIDs per format can be registered at the same time. // Note: After firmware 2.50, these limits can be changed by sceAtracReinit. hleAtracReinit(2, 2); super.start(); } @Override public void stop() { if (temporaryDecodeArea != null) { Modules.SysMemUserForUserModule.free(temporaryDecodeArea); temporaryDecodeArea = null; } super.stop(); } @Override public int getMemoryUsage() { // No need to allocate additional memory when the module has been // loaded using sceKernelLoadModuleToBlock() // by the PSP "flash0:/kd/utility.prx". // The memory has already been allocated in that case. if (Modules.ModuleMgrForKernelModule.isMemoryAllocatedForModule("flash0:/kd/libatrac3plus.prx")) { return 0; } return 0x8000; } public static final int AT3_MAGIC = 0x0270; // "AT3" public static final int AT3_PLUS_MAGIC = 0xFFFE; // "AT3PLUS" public static final int RIFF_MAGIC = 0x46464952; // "RIFF" public static final int WAVE_MAGIC = 0x45564157; // "WAVE" public static final int FMT_CHUNK_MAGIC = 0x20746D66; // "FMT " protected static final int FACT_CHUNK_MAGIC = 0x74636166; // "FACT" protected static final int SMPL_CHUNK_MAGIC = 0x6C706D73; // "SMPL" public static final int DATA_CHUNK_MAGIC = 0x61746164; // "DATA" private static final int ATRAC3_CONTEXT_READ_SIZE_OFFSET = 160; private static final int ATRAC3_CONTEXT_REQUIRED_SIZE_OFFSET = 164; private static final int ATRAC3_CONTEXT_DECODE_RESULT_OFFSET = 188; public static final int PSP_ATRAC_ALLDATA_IS_ON_MEMORY = -1; public static final int PSP_ATRAC_NONLOOP_STREAM_DATA_IS_ON_MEMORY = -2; public static final int PSP_ATRAC_LOOP_STREAM_DATA_IS_ON_MEMORY = -3; protected static final int PSP_ATRAC_STATUS_NONLOOP_STREAM_DATA = 0; protected static final int PSP_ATRAC_STATUS_LOOP_STREAM_DATA = 1; public static final int ATRAC_HEADER_HASH_LENGTH = 512; public static final int atracDecodeDelay = 2300; // Microseconds, based on PSP tests protected AtracID atracIDs[] = new AtracID[6]; private static SysMemInfo temporaryDecodeArea; protected static class LoopInfo { protected int cuePointID; protected int type; protected int startSample; protected int endSample; protected int fraction; protected int playCount; @Override public String toString() { return String.format("LoopInfo[cuePointID %d, type %d, startSample 0x%X, endSample 0x%X, fraction %d, playCount %d]", cuePointID, type, startSample, endSample, fraction, playCount); } } public static class AtracFileInfo { public int atracBitrate = 64; public int atracChannels = 2; public int atracSampleRate = 0xAC44; public int atracBytesPerFrame = 0x0230; public int atracEndSample; public int atracSampleOffset; public int atracCodingMode; public int inputFileDataOffset; public int inputFileSize; public int inputDataSize; public int loopNum; public int numLoops; public LoopInfo[] loops; } public static class AtracID extends AudiocodecInfo { // Internal info. protected int codecType; protected boolean inUse; protected int currentReadPosition; // Context (used only from firmware 6.00) protected SysMemInfo atracContext; protected SysMemInfo internalBuffer; // Sound data. protected AtracFileInfo info; protected int atracCurrentSample; protected int maxSamples; protected int skippedSamples; protected int skippedEndSamples; private int startSkippedSamples; protected int lastDecodedSamples; protected int channels; // First buffer. protected pspFileBuffer inputBuffer; protected boolean reloadingFromLoopStart; // Second buffer. protected int secondBufferAddr = -1; protected int secondBufferSize; // Input file. protected int secondInputFileSize; protected boolean isSecondBufferNeeded; protected boolean isSecondBufferSet; protected int internalErrorInfo; // Loops protected int currentLoopNum = -1; // LowLevel decoding protected int sourceBufferLength; // AddStreamData protected int getStreamDataInfoCurrentSample; public AtracID(int id) { super(id); info = new AtracFileInfo(); } @Override public void release() { setInUse(false); releaseContext(); releaseInternalBuffer(); } public int setHalfwayBuffer(int addr, int readSize, int bufferSize, boolean isMonoOutput, AtracFileInfo info) { this.info = info; channels = info.atracChannels; inputBuffer = new pspFileBuffer(addr, bufferSize, readSize, readSize); inputBuffer.notifyRead(info.inputFileDataOffset); inputBuffer.setFileMaxSize(info.inputFileSize); currentReadPosition = info.inputFileDataOffset; atracCurrentSample = 0; currentLoopNum = -1; lastDecodedSamples = 0; setOutputChannels(isMonoOutput ? 1 : 2); int result = codec.init(info.atracBytesPerFrame, channels, outputChannels, info.atracCodingMode); if (result < 0) { return result; } setCodecInitialized(); return 0; } public int decodeData(int samplesAddr, TPointer32 outEndAddr) { skippedEndSamples = 0; if (currentReadPosition + info.atracBytesPerFrame > info.inputFileSize || getAtracCurrentSample() - info.atracSampleOffset > info.atracEndSample) { if (log.isDebugEnabled()) { log.debug(String.format("decodeData returning ERROR_ATRAC_ALL_DATA_DECODED")); } outEndAddr.setValue(true); return ERROR_ATRAC_ALL_DATA_DECODED; } if (inputBuffer.getCurrentSize() < info.atracBytesPerFrame) { if (getSecondBufferAddr() > 0 && getSecondBufferSize() >= info.atracBytesPerFrame) { addSecondBufferStreamData(); } else { if (log.isDebugEnabled()) { log.debug(String.format("decodeData returning ERROR_ATRAC_BUFFER_IS_EMPTY")); } outEndAddr.setValue(false); return ERROR_ATRAC_BUFFER_IS_EMPTY; } } int currentSample = getAtracCurrentSample(); int nextCurrentSample = currentSample; if (currentSample == 0) { skippedSamples = startSkippedSamples + info.atracSampleOffset; } else { // When looping or changing the play position, the PSP re-aligns // on multiples of maxSamples skippedSamples = (currentSample + startSkippedSamples + info.atracSampleOffset) % maxSamples; } // Skip complete frames while (skippedSamples >= maxSamples) { inputBuffer.notifyRead(info.atracBytesPerFrame); currentReadPosition += info.atracBytesPerFrame; skippedSamples -= maxSamples; nextCurrentSample += maxSamples; } int readAddr = inputBuffer.getReadAddr(); if (inputBuffer.getReadSize() < info.atracBytesPerFrame) { if (temporaryDecodeArea == null || temporaryDecodeArea.allocatedSize < info.atracBytesPerFrame) { if (temporaryDecodeArea != null) { Modules.SysMemUserForUserModule.free(temporaryDecodeArea); } temporaryDecodeArea = Modules.SysMemUserForUserModule.malloc(KERNEL_PARTITION_ID, "Temporary-sceAtrac3plus-DecodeData", PSP_SMEM_Low, info.atracBytesPerFrame, 0); } if (temporaryDecodeArea != null) { Memory mem = Memory.getInstance(); readAddr = temporaryDecodeArea.addr; int wrapLength = inputBuffer.getReadSize(); mem.memcpy(readAddr, inputBuffer.getReadAddr(), wrapLength); mem.memcpy(readAddr + wrapLength, inputBuffer.getAddr(), info.atracBytesPerFrame - wrapLength); } } SysMemInfo tempBuffer = null; int decodedSamplesAddr = samplesAddr; int bytesPerSample = 2 * getOutputChannels(); if (skippedSamples > 0) { // Decode to a temporary buffer if we need to skip the first samples. int tempBufferSize = getMaxSamples() * bytesPerSample; tempBuffer = Modules.SysMemUserForUserModule.malloc(SysMemUserForUser.KERNEL_PARTITION_ID, "sceAtrac3plus-temp-decode-buffer", SysMemUserForUser.PSP_SMEM_Low, tempBufferSize, 0); if (tempBuffer == null) { log.warn(String.format("decodeData cannot allocate required temporary buffer of size=0x%X", tempBufferSize)); } else { decodedSamplesAddr = tempBuffer.addr; } } if (log.isDebugEnabled()) { log.debug(String.format("decodeData from 0x%08X(0x%X) to 0x%08X(0x%X), skippedSamples=0x%X, currentSample=0x%X, outputChannels=%d", readAddr, info.atracBytesPerFrame, decodedSamplesAddr, maxSamples, skippedSamples, currentSample, outputChannels)); } int result = codec.decode(readAddr, info.atracBytesPerFrame, decodedSamplesAddr); if (result < 0) { if (log.isDebugEnabled()) { log.debug(String.format("decodeData received codec decode error 0x%08X", result)); } outEndAddr.setValue(false); if (tempBuffer != null) { Modules.SysMemUserForUserModule.free(tempBuffer); } return ERROR_ATRAC_API_FAIL; } inputBuffer.notifyRead(info.atracBytesPerFrame); currentReadPosition += info.atracBytesPerFrame; nextCurrentSample += codec.getNumberOfSamples() - skippedSamples; if (nextCurrentSample - info.atracSampleOffset > info.atracEndSample) { outEndAddr.setValue(info.loopNum == 0); skippedEndSamples = nextCurrentSample - info.atracSampleOffset - info.atracEndSample - 1; } else { outEndAddr.setValue(false); } setAtracCurrentSample(nextCurrentSample); if (skippedSamples > 0) { Memory mem = Memory.getInstance(); int returnedSamples = getNumberOfSamples(); // Move the sample buffer to skip the needed samples mem.memmove(samplesAddr, decodedSamplesAddr + skippedSamples * bytesPerSample, returnedSamples * bytesPerSample); } if (tempBuffer != null) { Modules.SysMemUserForUserModule.free(tempBuffer); tempBuffer = null; } for (int i = 0; i < info.numLoops; i++) { LoopInfo loop = info.loops[i]; if (currentSample <= loop.startSample && loop.startSample < nextCurrentSample) { // We are just starting a loop currentLoopNum = i; break; } else if (currentSample <= loop.endSample && loop.endSample < nextCurrentSample && currentLoopNum == i) { // We are just ending the current loop if (info.loopNum == 0) { // No more loop playback currentLoopNum = -1; } else { // Replay the loop log.info(String.format("Replaying atrac loop atracID=%d, loopStart=0x%X, loopEnd=0x%X", id, loop.startSample, loop.endSample)); setPlayPosition(loop.startSample); nextCurrentSample = loop.startSample; // loopNum < 0: endless loop playback // loopNum > 0: play the loop loopNum times if (info.loopNum > 0) { info.loopNum--; } break; } } } return 0; } public void getStreamDataInfo(TPointer32 writeAddr, TPointer32 writableBytesAddr, TPointer32 readOffsetAddr) { if (inputBuffer.getFileWriteSize() <= 0 && currentLoopNum >= 0 && info.loopNum != 0) { // Read ahead to restart the loop inputBuffer.setFilePosition(getFilePositionFromSample(info.loops[currentLoopNum].startSample)); reloadingFromLoopStart = true; } // Remember the CurrentSample at the time of the getStreamDataInfo getStreamDataInfoCurrentSample = getAtracCurrentSample(); writeAddr.setValue(inputBuffer.getWriteAddr()); writableBytesAddr.setValue(inputBuffer.getWriteSize()); readOffsetAddr.setValue(inputBuffer.getFilePosition()); } protected void addStreamData(int length) { if (length > 0) { if (getAtracCurrentSample() < getStreamDataInfoCurrentSample) { // The atrac has looped since sceAtracGetStreamDataInfo() has been called. // Ignore sceAtracAddStreamData as we now need atrac data from the loop start. if (log.isDebugEnabled()) { log.debug(String.format("addStreamData ignored as the atrac has looped inbetween: sample 0x%X -> 0x%X", getStreamDataInfoCurrentSample, getAtracCurrentSample())); } } else { inputBuffer.notifyWrite(length); } } } private void addSecondBufferStreamData() { while (inputBuffer.getWriteSize() > 0 && secondBufferSize > 0) { int length = Math.min(inputBuffer.getWriteSize(), secondBufferSize); if (log.isDebugEnabled()) { log.debug(String.format("addSecondBufferStreamData from 0x%08X to 0x%08X, length=0x%X", secondBufferAddr, inputBuffer.getWriteAddr(), length)); } Memory.getInstance().memcpy(inputBuffer.getWriteAddr(), secondBufferAddr, length); addStreamData(length); secondBufferAddr += length; secondBufferSize -= length; } if (secondBufferSize <= 0) { secondBufferAddr = -1; secondBufferSize = 0; } } public void setSecondBuffer(int address, int size) { secondBufferAddr = address; secondBufferSize = size; } public int getSecondBufferAddr() { return secondBufferAddr; } public int getSecondBufferSize() { return secondBufferSize; } public int getSecondBufferReadPosition() { // TODO return 0; } public void createContext() { if (atracContext == null) { atracContext = Modules.SysMemUserForUserModule.malloc(SysMemUserForUser.USER_PARTITION_ID, String.format("ThreadMan-AtracCtx-%d", id), SysMemUserForUser.PSP_SMEM_High, 200, 0); if (atracContext != null) { Memory mem = Memory.getInstance(); int contextAddr = atracContext.addr; mem.memset(contextAddr, (byte) 0, atracContext.size); if (hasLoop()) { mem.write32(contextAddr + 140, info.loops[0].endSample); // loop end } else { mem.write32(contextAddr + 140, 0); // no loop } mem.write8(contextAddr + 149, (byte) 2); // state mem.write8(contextAddr + 151, (byte) getChannels()); // number of channels mem.write16(contextAddr + 154, (short) getCodecType()); //mem.write32(contextAddr + 168, 0); // Voice associated to this Atrac context using __sceSasSetVoiceATRAC3? // Used by SampleSourceAtrac3 (input for __sceSasConcatenateATRAC3): mem.write32(contextAddr + ATRAC3_CONTEXT_READ_SIZE_OFFSET, getInputBuffer().getFilePosition()); mem.write32(contextAddr + ATRAC3_CONTEXT_REQUIRED_SIZE_OFFSET, getInputBuffer().getFilePosition()); mem.write32(contextAddr + ATRAC3_CONTEXT_DECODE_RESULT_OFFSET, 0); } } } private void releaseContext() { if (atracContext != null) { Modules.SysMemUserForUserModule.free(atracContext); atracContext = null; } } public SysMemInfo getContext() { return atracContext; } public void createInternalBuffer(int size) { if (internalBuffer == null) { internalBuffer = Modules.SysMemUserForUserModule.malloc(SysMemUserForUser.USER_PARTITION_ID, String.format("ThreadMan-AtracBuf-%d", id), SysMemUserForUser.PSP_SMEM_Low, size, 0); } } private void releaseInternalBuffer() { if (internalBuffer != null) { Modules.SysMemUserForUserModule.free(internalBuffer); internalBuffer = null; } } public SysMemInfo getInternalBuffer() { return internalBuffer; } public int getCodecType() { return codecType; } public void setCodecType(int codecType) { this.codecType = codecType; if (codecType == PSP_CODEC_AT3) { maxSamples = Atrac3Decoder.SAMPLES_PER_FRAME; startSkippedSamples = 69; } else if (codecType == PSP_CODEC_AT3PLUS) { maxSamples = Atrac3plusDecoder.ATRAC3P_FRAME_SAMPLES; startSkippedSamples = 368; } else { maxSamples = 0; startSkippedSamples = 0; } } public int getAtracBitrate() { return info.atracBitrate; } public int getChannels() { return channels; } public void setChannels(int channels) { this.channels = channels; } public int getAtracSampleRate() { return info.atracSampleRate; } public int getAtracEndSample() { return info.atracEndSample; } public int getAtracCurrentSample() { return atracCurrentSample; } public int getAtracBytesPerFrame() { return info.atracBytesPerFrame; } public void setAtracCurrentSample(int sample) { atracCurrentSample = sample; } public int getLoopNum() { if (!hasLoop()) { return 0; } return info.loopNum; } public void setLoopNum(int num) { info.loopNum = num; } public int getMaxSamples() { return maxSamples; } public pspFileBuffer getInputBuffer() { return inputBuffer; } public int getInputFileSize() { return info.inputFileSize; } public void setInputFileSize(int bytes) { info.inputFileSize = bytes; } public int getSecondInputFileSize() { return secondInputFileSize; } public boolean isSecondBufferNeeded() { return isSecondBufferNeeded; } public boolean isSecondBufferSet() { return isSecondBufferSet; } public int getInternalErrorInfo() { return internalErrorInfo; } public int getRemainFrames() { if (inputBuffer == null) { return 0; } if (inputBufferContainsAllData()) { return PSP_ATRAC_ALLDATA_IS_ON_MEMORY; } if ((!hasLoop() || info.loopNum == 0) && inputBuffer.getFileWriteSize() <= 0) { return PSP_ATRAC_NONLOOP_STREAM_DATA_IS_ON_MEMORY; } int remainFrames = inputBuffer.getCurrentSize() / info.atracBytesPerFrame; return remainFrames; } public int getBufferInfoForResetting(int sample, TPointer32 bufferInfoAddr) { if (sample > getAtracEndSample()) { return SceKernelErrors.ERROR_ATRAC_BAD_SAMPLE; } int writableBytes; int minimumWriteBytes; int readPosition; if (inputBufferContainsAllData()) { writableBytes = 0; minimumWriteBytes = 0; readPosition = 0; } else { writableBytes = inputBuffer.getMaxSize(); minimumWriteBytes = info.atracBytesPerFrame * 2; if (sample == 0) { readPosition = 0; } else { readPosition = getFilePositionFromSample(sample); } } // Holds buffer related parameters. // Main buffer. bufferInfoAddr.setValue(0, inputBuffer.getAddr()); // Pointer to current writing position in the buffer. bufferInfoAddr.setValue(4, writableBytes); // Number of bytes which can be written to the buffer. bufferInfoAddr.setValue(8, minimumWriteBytes); // Number of bytes that must to be written to the buffer. bufferInfoAddr.setValue(12, readPosition); // Read offset in the input file for the given sample. // Secondary buffer. bufferInfoAddr.setValue(16, getSecondBufferAddr()); // Pointer to current writing position in the buffer. bufferInfoAddr.setValue(20, getSecondBufferSize()); // Number of bytes which can be written to the buffer. bufferInfoAddr.setValue(24, getSecondBufferSize()); // Number of bytes that must to be written to the buffer. bufferInfoAddr.setValue(28, getSecondBufferReadPosition()); // Read offset for input file. if (log.isDebugEnabled()) { log.debug(String.format("sceAtracGetBufferInfoForReseting returning writeAddr=0x%08X, writeMaxSize=0x%X, writeMinSize=0x%X, readPosition=0x%X", bufferInfoAddr.getValue(0), bufferInfoAddr.getValue(4), bufferInfoAddr.getValue(8), bufferInfoAddr.getValue(12))); } return 0; } public void setPlayPosition(int sample, int bytesWrittenFirstBuf, int bytesWrittenSecondBuf) { if (log.isTraceEnabled()) { log.trace(String.format("sceAtracResetPlayPosition: %s", Utilities.getMemoryDump(inputBuffer.getWriteAddr(), bytesWrittenFirstBuf))); } if (sample != atracCurrentSample) { // Do not change the position of the inputBuffer when it contains all the Atrac data if (!inputBufferContainsAllData()) { currentReadPosition = getFilePositionFromSample(sample); inputBuffer.reset(bytesWrittenFirstBuf, currentReadPosition); } else { currentReadPosition = getFilePositionFromSample(sample); inputBuffer.reset(inputBuffer.getFilePosition(), 0); inputBuffer.notifyRead(currentReadPosition); } setAtracCurrentSample(sample); } } private boolean inputBufferContainsAllData() { if (inputBuffer != null && inputBuffer.getMaxSize() >= info.inputFileSize) { if (inputBuffer.getReadSize() + currentReadPosition >= info.inputFileSize) { return true; } } return false; } private int getFilePositionFromSample(int sample) { return info.inputFileDataOffset + sample / maxSamples * info.atracBytesPerFrame; } public void setPlayPosition(int sample) { if ((sample / maxSamples * maxSamples) != getAtracCurrentSample()) { if (inputBufferContainsAllData()) { getInputBuffer().reset(inputBuffer.getFilePosition(), 0); getInputBuffer().notifyRead(getFilePositionFromSample(sample)); } else if (reloadingFromLoopStart && currentLoopNum >= 0 && sample == info.loops[currentLoopNum].startSample) { // We have already started to reload data from the loop start reloadingFromLoopStart = false; } else { getInputBuffer().reset(0, getFilePositionFromSample(sample)); } currentReadPosition = getFilePositionFromSample(sample); setAtracCurrentSample(sample); } } public boolean hasLoop() { return info.numLoops > 0; } public int getLoopStatus() { if (!hasLoop()) { return PSP_ATRAC_STATUS_NONLOOP_STREAM_DATA; } return PSP_ATRAC_STATUS_LOOP_STREAM_DATA; } public int getLoopStartSample() { if (!hasLoop()) { return -1; } return info.loops[0].startSample; } public int getLoopEndSample() { if (!hasLoop()) { return -1; } return info.loops[0].endSample; } public void setContextDecodeResult(int result, int requestedSize) { if (getContext() != null) { Memory mem = Memory.getInstance(); int contextAddr = getContext().addr; mem.write32(contextAddr + ATRAC3_CONTEXT_DECODE_RESULT_OFFSET, result); int readSize = mem.read32(contextAddr + ATRAC3_CONTEXT_READ_SIZE_OFFSET); mem.write32(contextAddr + ATRAC3_CONTEXT_REQUIRED_SIZE_OFFSET, readSize + requestedSize); } } public int getSourceBufferLength() { return sourceBufferLength; } public void setSourceBufferLength(int sourceBufferLength) { this.sourceBufferLength = sourceBufferLength; } public int getLastDecodedSamples() { return lastDecodedSamples; } public void setLastDecodedSamples(int lastDecodedSamples) { this.lastDecodedSamples = lastDecodedSamples; } public int getOutputChannels() { return outputChannels; } public void setOutputChannels(int outputChannels) { this.outputChannels = outputChannels; } public int getNumberOfSamples() { return codec.getNumberOfSamples() - skippedSamples - skippedEndSamples; } public boolean isInUse() { return inUse; } public void setInUse(boolean inUse) { this.inUse = inUse; if (inUse) { initCodec(); } else { setCodecInitialized(false); codec = null; } } @Override public void initCodec() { codec = CodecFactory.getCodec(getCodecType()); setCodecInitialized(false); } @Override public String toString() { return String.format("AtracID[id=%d, inputBuffer=%s, channels=%d, outputChannels=%d]", id, inputBuffer, getChannels(), getOutputChannels()); } } protected static int read28(Memory mem, int address) { return ((mem.read8(address + 0) & 0x7F) << 21) | ((mem.read8(address + 1) & 0x7F) << 14) | ((mem.read8(address + 2) & 0x7F) << 7) | ((mem.read8(address + 3) & 0x7F) << 0); } protected static String getStringFromInt32(int n) { char c1 = (char) ((n ) & 0xFF); char c2 = (char) ((n >> 8) & 0xFF); char c3 = (char) ((n >> 16) & 0xFF); char c4 = (char) ((n >> 24) & 0xFF); return String.format("%c%c%c%c", c1, c2, c3, c4); } protected int hleAtracReinit(int numAT3IdCount, int numAT3plusIdCount) { for (int i = 0; i < atracIDs.length; i++) { if (atracIDs[i].isInUse()) { return ERROR_BUSY; } } int i; for (i = 0; i < numAT3plusIdCount && i < atracIDs.length; i++) { atracIDs[i].setCodecType(PSP_CODEC_AT3PLUS); } for (int j = 0; j < numAT3IdCount && i < atracIDs.length; j++, i++) { atracIDs[i].setCodecType(PSP_CODEC_AT3); } // The rest is unused for (; i < atracIDs.length; i++) { atracIDs[i].setCodecType(0); } return 0; } public int hleGetAtracID(int codecType) { for (int i = 0; i < atracIDs.length; i++) { if (atracIDs[i].getCodecType() == codecType && !atracIDs[i].isInUse()) { atracIDs[i].setInUse(true); return i; } } return ERROR_ATRAC_NO_ID; } public AtracID getAtracID(int atID) { return atracIDs[atID]; } public static int analyzeRiffFile(Memory mem, int addr, int length, AtracFileInfo info) { int result = ERROR_ATRAC_UNKNOWN_FORMAT; int currentAddr = addr; int bufferSize = length; info.atracEndSample = -1; info.numLoops = 0; info.inputFileDataOffset = 0; if (bufferSize < 12) { log.error(String.format("Atrac buffer too small %d", bufferSize)); return ERROR_ATRAC_INVALID_SIZE; } // RIFF file format: // Offset 0: 'RIFF' // Offset 4: file length - 8 // Offset 8: 'WAVE' int magic = readUnaligned32(mem, currentAddr); int WAVEMagic = readUnaligned32(mem, currentAddr + 8); if (magic != RIFF_MAGIC || WAVEMagic != WAVE_MAGIC) { log.error(String.format("Not a RIFF/WAVE format! %s", Utilities.getMemoryDump(currentAddr, 16))); return ERROR_ATRAC_UNKNOWN_FORMAT; } info.inputFileSize = readUnaligned32(mem, currentAddr + 4) + 8; info.inputDataSize = info.inputFileSize; if (log.isDebugEnabled()) { log.debug(String.format("FileSize 0x%X", info.inputFileSize)); } currentAddr += 12; bufferSize -= 12; boolean foundData = false; while (bufferSize >= 8 && !foundData) { int chunkMagic = readUnaligned32(mem, currentAddr); int chunkSize = readUnaligned32(mem, currentAddr + 4); currentAddr += 8; bufferSize -= 8; switch (chunkMagic) { case DATA_CHUNK_MAGIC: foundData = true; // Offset of the data chunk in the input file info.inputFileDataOffset = currentAddr - addr; info.inputDataSize = chunkSize; if (log.isDebugEnabled()) { log.debug(String.format("DATA Chunk: data offset=0x%X, data size=0x%X", info.inputFileDataOffset, info.inputDataSize)); } break; case FMT_CHUNK_MAGIC: { if (chunkSize >= 16) { int compressionCode = mem.read16(currentAddr); info.atracChannels = mem.read16(currentAddr + 2); info.atracSampleRate = readUnaligned32(mem, currentAddr + 4); info.atracBitrate = readUnaligned32(mem, currentAddr + 8); info.atracBytesPerFrame = mem.read16(currentAddr + 12); int hiBytesPerSample = mem.read16(currentAddr + 14); int extraDataSize = mem.read16(currentAddr + 16); if (extraDataSize == 14) { info.atracCodingMode = mem.read16(currentAddr + 18 + 6); } if (log.isDebugEnabled()) { log.debug(String.format("WAVE format: magic=0x%08X('%s'), chunkSize=%d, compressionCode=0x%04X, channels=%d, sampleRate=%d, bitrate=%d, bytesPerFrame=0x%X, hiBytesPerSample=%d, codingMode=%d", chunkMagic, getStringFromInt32(chunkMagic), chunkSize, compressionCode, info.atracChannels, info.atracSampleRate, info.atracBitrate, info.atracBytesPerFrame, hiBytesPerSample, info.atracCodingMode)); // Display rest of chunk as debug information StringBuilder restChunk = new StringBuilder(); for (int i = 16; i < chunkSize; i++) { int b = mem.read8(currentAddr + i); restChunk.append(String.format(" %02X", b)); } if (restChunk.length() > 0) { log.debug(String.format("Additional chunk data:%s", restChunk)); } } if (compressionCode == AT3_MAGIC) { result = PSP_CODEC_AT3; } else if (compressionCode == AT3_PLUS_MAGIC) { result = PSP_CODEC_AT3PLUS; } else { return ERROR_ATRAC_UNKNOWN_FORMAT; } } break; } case FACT_CHUNK_MAGIC: { if (chunkSize >= 8) { info.atracEndSample = readUnaligned32(mem, currentAddr); if (info.atracEndSample > 0) { info.atracEndSample -= 1; } if (chunkSize >= 12) { // Is the value at offset 4 ignored? info.atracSampleOffset = readUnaligned32(mem, currentAddr + 8); // The loop samples are offset by this value } else { info.atracSampleOffset = readUnaligned32(mem, currentAddr + 4); // The loop samples are offset by this value } if (log.isDebugEnabled()) { log.debug(String.format("FACT Chunk: chunkSize=%d, endSample=0x%X, sampleOffset=0x%X", chunkSize, info.atracEndSample, info.atracSampleOffset)); } } break; } case SMPL_CHUNK_MAGIC: { if (chunkSize >= 36) { int checkNumLoops = readUnaligned32(mem, currentAddr + 28); if (chunkSize >= 36 + checkNumLoops * 24) { info.numLoops = checkNumLoops; info.loops = new LoopInfo[info.numLoops]; int loopInfoAddr = currentAddr + 36; for (int i = 0; i < info.numLoops; i++) { LoopInfo loop = new LoopInfo(); info.loops[i] = loop; loop.cuePointID = readUnaligned32(mem, loopInfoAddr); loop.type = readUnaligned32(mem, loopInfoAddr + 4); loop.startSample = readUnaligned32(mem, loopInfoAddr + 8) - info.atracSampleOffset; loop.endSample = readUnaligned32(mem, loopInfoAddr + 12) - info.atracSampleOffset; loop.fraction = readUnaligned32(mem, loopInfoAddr + 16); loop.playCount = readUnaligned32(mem, loopInfoAddr + 20); if (log.isDebugEnabled()) { log.debug(String.format("Loop #%d: %s", i, loop.toString())); } loopInfoAddr += 24; } // TODO Second buffer processing disabled because still incomplete //isSecondBufferNeeded = true; } } break; } } if (chunkSize > bufferSize) { break; } currentAddr += chunkSize; bufferSize -= chunkSize; } if (info.loops != null) { // If a loop end is past the atrac end, assume the atrac end for (LoopInfo loop : info.loops) { if (loop.endSample > info.atracEndSample) { loop.endSample = info.atracEndSample; } } } return result; } protected int hleSetHalfwayBuffer(int atID, TPointer buffer, int readSize, int bufferSize, boolean isMonoOutput) { if (readSize > bufferSize) { return SceKernelErrors.ERROR_ATRAC_INCORRECT_READ_SIZE; } if (log.isTraceEnabled()) { log.trace(String.format("hleSetHalfwayBuffer buffer: %s", Utilities.getMemoryDump(buffer.getAddress(), readSize))); } AtracFileInfo info = new AtracFileInfo(); int codecType = analyzeRiffFile(buffer.getMemory(), buffer.getAddress(), readSize, info); if (codecType < 0) { return codecType; } AtracID id = atracIDs[atID]; if (codecType != id.getCodecType()) { return SceKernelErrors.ERROR_ATRAC_WRONG_CODEC; } int result = id.setHalfwayBuffer(buffer.getAddress(), readSize, bufferSize, isMonoOutput, info); if (result < 0) { return result; } // Reschedule Modules.ThreadManForUserModule.hleYieldCurrentThread(); return result; } protected int hleSetHalfwayBufferAndGetID(TPointer buffer, int readSize, int bufferSize, boolean isMonoOutput) { if (readSize > bufferSize) { if (log.isDebugEnabled()) { log.debug(String.format("hleSetHalfwayBufferAndGetID returning 0x%X", ERROR_ATRAC_INCORRECT_READ_SIZE)); } return ERROR_ATRAC_INCORRECT_READ_SIZE; } // readSize and bufferSize are unsigned int's. // Allow negative values. // "Tales of VS - ULJS00209" is even passing an uninitialized value bufferSize=0xDEADBEEF AtracFileInfo info = new AtracFileInfo(); int codecType = analyzeRiffFile(buffer.getMemory(), buffer.getAddress(), readSize, info); if (codecType < 0) { if (log.isDebugEnabled()) { log.debug(String.format("hleSetHalfwayBufferAndGetID returning 0x%X", codecType)); } return codecType; } int atID = hleGetAtracID(codecType); if (atID < 0) { if (log.isDebugEnabled()) { log.debug(String.format("hleSetHalfwayBufferAndGetID returning 0x%X", atID)); } return atID; } AtracID id = atracIDs[atID]; int result = id.setHalfwayBuffer(buffer.getAddress(), readSize, bufferSize, isMonoOutput, info); if (result < 0) { hleReleaseAtracID(atID); if (log.isDebugEnabled()) { log.debug(String.format("hleSetHalfwayBufferAndGetID returning 0x%X", result)); } return result; } if (log.isDebugEnabled()) { log.debug(String.format("hleSetHalfwayBufferAndGetID returning atID=0x%X", atID)); } // Reschedule Modules.ThreadManForUserModule.hleYieldCurrentThread(); return atID; } protected void hleReleaseAtracID(int atracID) { atracIDs[atracID].release(); } public int checkAtracID(int atID) { if (atID < 0 || atID >= atracIDs.length || !atracIDs[atID].isInUse()) { if (log.isDebugEnabled()) { log.debug(String.format("checkAtracID invalid atracID=0x%X", atID)); } throw new SceKernelErrorException(ERROR_ATRAC_BAD_ID); } return atID; } private static int read24(Memory mem, int address) { return (mem.read8(address + 0) << 16) | (mem.read8(address + 1) << 8) | (mem.read8(address + 2) << 0); } private static int read16(Memory mem, int address) { return (mem.read8(address) << 8) | mem.read8(address + 1); } private static int analyzeAA3File(TPointer buffer, int fileSize, AtracFileInfo info) { Memory mem = buffer.getMemory(); int address = buffer.getAddress(); int codecType = 0; int magic = read24(mem, address); address += 3; if (magic != 0x656133 && magic != 0x494433) { // 3ae | 3AE log.error(String.format("Unknown AA3 magic 0x%06X", magic)); return codecType; } if (mem.read8(address) != 3 || mem.read8(address + 1) != 0) { log.error(String.format("Unknown AA3 bytes 0x%08X 0x%08X", mem.read8(address), mem.read8(address + 1))); return ERROR_AA3_INVALID_HEADER_VERSION; } address += 3; int headerSize = read28(mem, address); address += 4 + headerSize; if (mem.read8(address) == 0) { address += 16; } info.inputFileDataOffset = address - buffer.getAddress(); magic = read24(mem, address); if (magic != 0x454133) { // 3AE log.error(String.format("Unknown AA3 magic 0x%06X", magic)); return ERROR_AA3_INVALID_HEADER; } address += 4; int dataOffset = read16(mem, address); if (dataOffset == 0xFFFF) { return ERROR_AA3_INVALID_HEADER; } address += 2; int unknown2 = read16(mem, address); if (unknown2 != 0xFFFF) { return ERROR_AA3_INVALID_HEADER; } address += 2; address += 24; int samplesPerFrame; int flags = read16(mem, address + 2); switch (mem.read8(address)) { case 0: // AT3 if ((flags & 0xE000) != 0x2000) { // Number of channels? return ERROR_AA3_INVALID_HEADER_FLAGS; } codecType = PSP_CODEC_AT3; samplesPerFrame = Atrac3Decoder.SAMPLES_PER_FRAME; info.atracChannels = 2; info.atracCodingMode = (mem.read8(address + 1) & 0x2) >> 1; info.atracBytesPerFrame = ((flags & 0x3FF) << 3); break; case 1: // AT3+ if ((flags & 0x1C00) != 0x0800) { return ERROR_AA3_INVALID_HEADER_FLAGS; } if ((flags & 0xE000) != 0x2000) { // Number of channels? return ERROR_AA3_INVALID_HEADER_FLAGS; } codecType = PSP_CODEC_AT3PLUS; samplesPerFrame = Atrac3plusDecoder.ATRAC3P_FRAME_SAMPLES; info.atracChannels = 2; info.atracBytesPerFrame = ((flags & 0x3FF) << 3) + 8; break; default: return ERROR_AA3_INVALID_CODEC; } info.inputFileDataOffset += dataOffset; info.inputFileSize = fileSize; info.inputDataSize = fileSize - info.inputFileDataOffset; info.atracEndSample = (info.inputDataSize / info.atracBytesPerFrame) * samplesPerFrame; return codecType; } protected int hleSetAA3HalfwayBufferAndGetID(TPointer buffer, int readSize, int bufferSize, boolean isMonoOutput, int fileSize) { if (readSize > bufferSize) { return ERROR_ATRAC_INCORRECT_READ_SIZE; } // readSize and bufferSize are unsigned int's. // Allow negative values. // "Tales of VS - ULJS00209" is even passing an uninitialized value bufferSize=0xDEADBEEF AtracFileInfo info = new AtracFileInfo(); int codecType = analyzeAA3File(buffer, fileSize, info); if (codecType < 0) { return codecType; } int atID = hleGetAtracID(codecType); if (atID < 0) { return atID; } AtracID id = atracIDs[atID]; int result = id.setHalfwayBuffer(buffer.getAddress(), readSize, bufferSize, isMonoOutput, info); if (result < 0) { hleReleaseAtracID(atID); return result; } if (log.isDebugEnabled()) { log.debug(String.format("hleSetHalfwayBufferAndGetID returning atID=0x%X", atID)); } // Reschedule Modules.ThreadManForUserModule.hleYieldCurrentThread(); return atID; } public AtracID getAtracIdFromContext(int atrac3Context) { for (int i = 0; i < atracIDs.length; i++) { AtracID id = atracIDs[i]; if (id.isInUse()) { SysMemInfo context = id.getContext(); if (context != null && context.addr == atrac3Context) { return id; } } } return null; } @HLEUnimplemented @HLEFunction(nid = 0xD1F59FDB, version = 150, checkInsideInterrupt = true) public int sceAtracStartEntry() { return 0; } @HLEUnimplemented @HLEFunction(nid = 0xD5C28CC0, version = 150, checkInsideInterrupt = true) public int sceAtracEndEntry() { return 0; } @HLEFunction(nid = 0x780F88D1, version = 150, checkInsideInterrupt = true) public int sceAtracGetAtracID(int codecType) { int atId = hleGetAtracID(codecType); if (log.isDebugEnabled()) { log.debug(String.format("sceAtracGetAtracID: returning atID=0x%X", atId)); } return atId; } @HLEFunction(nid = 0x61EB33F5, version = 150, checkInsideInterrupt = true) public int sceAtracReleaseAtracID(@CheckArgument("checkAtracID") int atID) { hleReleaseAtracID(atID); return 0; } @HLEFunction(nid = 0x0E2A73AB, version = 150, checkInsideInterrupt = true) public int sceAtracSetData(@CheckArgument("checkAtracID") int atID, TPointer buffer, int bufferSize) { return hleSetHalfwayBuffer(atID, buffer, bufferSize, bufferSize, false); } @HLEFunction(nid = 0x3F6E26B5, version = 150, checkInsideInterrupt = true) public int sceAtracSetHalfwayBuffer(@CheckArgument("checkAtracID") int atID, TPointer halfBuffer, int readSize, int halfBufferSize) { return hleSetHalfwayBuffer(atID, halfBuffer, readSize, halfBufferSize, false); } @HLEFunction(nid = 0x7A20E7AF, version = 150, checkInsideInterrupt = true) public int sceAtracSetDataAndGetID(TPointer buffer, int bufferSize) { return hleSetHalfwayBufferAndGetID(buffer, bufferSize, bufferSize, false); } @HLEFunction(nid = 0x0FAE370E, version = 150, checkInsideInterrupt = true) public int sceAtracSetHalfwayBufferAndGetID(TPointer halfBuffer, int readSize, int halfBufferSize) { return hleSetHalfwayBufferAndGetID(halfBuffer, readSize, halfBufferSize, false); } @HLEFunction(nid = 0x6A8C3CD5, version = 150, checkInsideInterrupt = true) public int sceAtracDecodeData(@CheckArgument("checkAtracID") int atID, @CanBeNull TPointer16 samplesAddr, @CanBeNull @BufferInfo(usage=Usage.out) TPointer32 samplesNbrAddr, @CanBeNull @BufferInfo(usage=Usage.out) TPointer32 outEndAddr, @CanBeNull @BufferInfo(usage=Usage.out) TPointer32 remainFramesAddr) { AtracID id = atracIDs[atID]; if (id.isSecondBufferNeeded() && !id.isSecondBufferSet()) { log.warn(String.format("sceAtracDecodeData atracID=0x%X needs second buffer!", atID)); return SceKernelErrors.ERROR_ATRAC_SECOND_BUFFER_NEEDED; } int result = id.decodeData(samplesAddr.getAddress(), outEndAddr); if (result < 0) { samplesNbrAddr.setValue(0); return result; } samplesNbrAddr.setValue(id.getNumberOfSamples()); remainFramesAddr.setValue(id.getRemainFrames()); if (log.isDebugEnabled()) { log.debug(String.format("sceAtracDecodeData returning 0x%08X, samples=0x%X, end=%d, remainFrames=%d, currentSample=0x%X/0x%X, %s", result, samplesNbrAddr.getValue(), outEndAddr.getValue(), remainFramesAddr.getValue(), id.getAtracCurrentSample(), id.getAtracEndSample(), id)); } // Delay the thread decoding the Atrac data, // the thread is also blocking using semaphores/event flags on a real PSP. if (result == 0) { Modules.ThreadManForUserModule.hleKernelDelayThread(atracDecodeDelay, false); } return result; } @HLEFunction(nid = 0x9AE849A7, version = 150, checkInsideInterrupt = true) public int sceAtracGetRemainFrame(@CheckArgument("checkAtracID") int atID, @BufferInfo(usage=Usage.out) TPointer32 remainFramesAddr) { AtracID id = atracIDs[atID]; remainFramesAddr.setValue(id.getRemainFrames()); if (log.isDebugEnabled()) { log.debug(String.format("sceAtracGetRemainFrame returning %d, %s", remainFramesAddr.getValue(), id)); } return 0; } @HLEFunction(nid = 0x5D268707, version = 150, checkInsideInterrupt = true) public int sceAtracGetStreamDataInfo(@CheckArgument("checkAtracID") int atID, @CanBeNull @BufferInfo(usage=Usage.out) TPointer32 writeAddr, @CanBeNull @BufferInfo(usage=Usage.out) TPointer32 writableBytesAddr, @CanBeNull @BufferInfo(usage=Usage.out) TPointer32 readOffsetAddr) { AtracID id = atracIDs[atID]; id.getStreamDataInfo(writeAddr, writableBytesAddr, readOffsetAddr); if (log.isDebugEnabled()) { log.debug(String.format("sceAtracGetStreamDataInfo write=0x%08X, writableBytes=0x%X, readOffset=0x%X, %s", writeAddr.getValue(), writableBytesAddr.getValue(), readOffsetAddr.getValue(), id)); } return 0; } @HLEFunction(nid = 0x7DB31251, version = 150, checkInsideInterrupt = true) public int sceAtracAddStreamData(@CheckArgument("checkAtracID") int atID, int bytesToAdd) { AtracID id = atracIDs[atID]; id.addStreamData(bytesToAdd); if (log.isDebugEnabled()) { log.debug(String.format("sceAtracAddStreamData: %s", id)); } return 0; } @HLEFunction(nid = 0x83E85EA0, version = 150, checkInsideInterrupt = true) public int sceAtracGetSecondBufferInfo(@CheckArgument("checkAtracID") int atID, TPointer32 outPosition, TPointer32 outBytes) { // Checked: outPosition and outBytes have to be non-NULL AtracID id = atracIDs[atID]; if (!id.isSecondBufferNeeded()) { // PSP clears both values when returning this error code. outPosition.setValue(0); outBytes.setValue(0); return SceKernelErrors.ERROR_ATRAC_SECOND_BUFFER_NOT_NEEDED; } outPosition.setValue(id.getSecondBufferReadPosition()); outBytes.setValue(id.getSecondBufferSize()); return 0; } @HLEFunction(nid = 0x83BF7AFD, version = 150, checkInsideInterrupt = true) public int sceAtracSetSecondBuffer(@CheckArgument("checkAtracID") int atID, TPointer secondBuffer, int secondBufferSize) { AtracID id = atracIDs[atID]; id.setSecondBuffer(secondBuffer.getAddress(), secondBufferSize); return 0; } @HLEFunction(nid = 0xE23E3A35, version = 150, checkInsideInterrupt = true) public int sceAtracGetNextDecodePosition(@CheckArgument("checkAtracID") int atID, TPointer32 posAddr) { AtracID id = atracIDs[atID]; if (id.getAtracCurrentSample() >= id.getAtracEndSample()) { return SceKernelErrors.ERROR_ATRAC_ALL_DATA_DECODED; } posAddr.setValue(id.getAtracCurrentSample()); if (log.isDebugEnabled()) { log.debug(String.format("sceAtracGetNextDecodePosition returning pos=%d", posAddr.getValue())); } return 0; } @HLEFunction(nid = 0xA2BBA8BE, version = 150, checkInsideInterrupt = true) public int sceAtracGetSoundSample(@CheckArgument("checkAtracID") int atID, @CanBeNull @BufferInfo(usage=Usage.out) TPointer32 endSampleAddr, @CanBeNull @BufferInfo(usage=Usage.out) TPointer32 loopStartSampleAddr, @CanBeNull @BufferInfo(usage=Usage.out) TPointer32 loopEndSampleAddr) { AtracID id = atracIDs[atID]; int endSample = id.getAtracEndSample(); int loopStartSample = id.getLoopStartSample(); int loopEndSample = id.getLoopEndSample(); if (endSample < 0) { endSample = id.getAtracEndSample(); } if (endSample < 0) { endSample = id.getInputFileSize(); } endSampleAddr.setValue(endSample); loopStartSampleAddr.setValue(loopStartSample); loopEndSampleAddr.setValue(loopEndSample); if (log.isDebugEnabled()) { log.debug(String.format("sceAtracGetSoundSample returning endSample=0x%X, loopStartSample=0x%X, loopEndSample=0x%X", endSample, loopStartSample, loopEndSample)); } return 0; } @HLEFunction(nid = 0x31668BAA, version = 150, checkInsideInterrupt = true) public int sceAtracGetChannel(@CheckArgument("checkAtracID") int atID, TPointer32 channelAddr) { AtracID id = atracIDs[atID]; channelAddr.setValue(id.getChannels()); return 0; } @HLEFunction(nid = 0xD6A5F2F7, version = 150, checkInsideInterrupt = true) public int sceAtracGetMaxSample(@CheckArgument("checkAtracID") int atID, @BufferInfo(usage=Usage.out) TPointer32 maxSamplesAddr) { AtracID id = atracIDs[atID]; maxSamplesAddr.setValue(id.getMaxSamples()); if (log.isDebugEnabled()) { log.debug(String.format("sceAtracGetMaxSample returning maxSamples=0x%X", id.getMaxSamples())); } return 0; } @HLEFunction(nid = 0x36FAABFB, version = 150, checkInsideInterrupt = true) public int sceAtracGetNextSample(@CheckArgument("checkAtracID") int atID, TPointer32 nbrSamplesAddr) { AtracID id = atracIDs[atID]; int samples = id.getMaxSamples(); if (id.getInputBuffer().isEmpty()) { samples = 0; // No more data available in input buffer } if (log.isDebugEnabled()) { log.debug(String.format("sceAtracGetNextSample returning %d samples", samples)); } nbrSamplesAddr.setValue(samples); return 0; } @HLEFunction(nid = 0xA554A158, version = 150, checkInsideInterrupt = true) public int sceAtracGetBitrate(@CheckArgument("checkAtracID") int atID, TPointer32 bitrateAddr) { AtracID id = atracIDs[atID]; // Bitrate based on https://github.com/uofw/uofw/blob/master/src/libatrac3plus/libatrac3plus.c int bitrate = (id.getAtracBytesPerFrame() * 352800) / 1000; if (id.getCodecType() == PSP_CODEC_AT3PLUS) { bitrate = ((bitrate >> 11) + 8) & 0xFFFFFFF0; } else { bitrate = (bitrate + 511) >> 10; } bitrateAddr.setValue(bitrate); if (log.isDebugEnabled()) { log.debug(String.format("sceAtracGetBitrate returning bitRate=0x%X", bitrate)); } return 0; } @HLEFunction(nid = 0xFAA4F89B, version = 150, checkInsideInterrupt = true) public int sceAtracGetLoopStatus(@CheckArgument("checkAtracID") int atID, @CanBeNull @BufferInfo(usage=Usage.out) TPointer32 loopNbr, @CanBeNull @BufferInfo(usage=Usage.out) TPointer32 statusAddr) { AtracID id = atracIDs[atID]; loopNbr.setValue(id.getLoopNum()); statusAddr.setValue(id.getLoopStatus()); return 0; } @HLEFunction(nid = 0x868120B5, version = 150, checkInsideInterrupt = true) public int sceAtracSetLoopNum(@CheckArgument("checkAtracID") int atID, int loopNbr) { AtracID id = atracIDs[atID]; if (!id.hasLoop()) { return SceKernelErrors.ERROR_ATRAC_NO_LOOP_INFORMATION; } id.setLoopNum(loopNbr); return 0; } @HLELogging(level = "info") @HLEFunction(nid = 0xCA3CA3D2, version = 150, checkInsideInterrupt = true) public int sceAtracGetBufferInfoForReseting(@CheckArgument("checkAtracID") int atID, int sample, TPointer32 bufferInfoAddr) { AtracID id = atracIDs[atID]; return id.getBufferInfoForResetting(sample, bufferInfoAddr); } @HLEFunction(nid = 0x644E5607, version = 150, checkInsideInterrupt = true) public int sceAtracResetPlayPosition(@CheckArgument("checkAtracID") int atID, int sample, int bytesWrittenFirstBuf, int bytesWrittenSecondBuf) { AtracID id = atracIDs[atID]; id.setPlayPosition(sample, bytesWrittenFirstBuf, bytesWrittenSecondBuf); return 0; } @HLEFunction(nid = 0xE88F759B, version = 150, checkInsideInterrupt = true) public int sceAtracGetInternalErrorInfo(@CheckArgument("checkAtracID") int atID, TPointer32 errorAddr) { AtracID id = atracIDs[atID]; errorAddr.setValue(id.getInternalErrorInfo()); return 0; } @HLEFunction(nid = 0xB3B5D042, version = 250, checkInsideInterrupt = true) public int sceAtracGetOutputChannel(@CheckArgument("checkAtracID") int atID, TPointer32 outputChannelAddr) { AtracID id = atracIDs[atID]; outputChannelAddr.setValue(id.getOutputChannels()); return 0; } @HLEFunction(nid = 0xECA32A99, version = 250, checkInsideInterrupt = true) public boolean sceAtracIsSecondBufferNeeded(@CheckArgument("checkAtracID") int atID) { AtracID id = atracIDs[atID]; // 0 -> Second buffer isn't needed. // 1 -> Second buffer is needed. return id.isSecondBufferNeeded(); } @HLEFunction(nid = 0x132F1ECA, version = 250, checkInsideInterrupt = true) public int sceAtracReinit(int at3IDNum, int at3plusIDNum) { int result = 0; if (at3IDNum != 0 || at3plusIDNum != 0) { result = hleAtracReinit(at3IDNum, at3plusIDNum); if (result >= 0) { Modules.ThreadManForUserModule.hleYieldCurrentThread(); } } return result; } @HLEFunction(nid = 0x2DD3E298, version = 250, checkInsideInterrupt = true) public int sceAtracGetBufferInfoForResetting(@CheckArgument("checkAtracID") int atID, int sample, TPointer32 bufferInfoAddr) { AtracID id = atracIDs[atID]; return id.getBufferInfoForResetting(sample, bufferInfoAddr); } @HLEFunction(nid = 0x5CF9D852, version = 250, checkInsideInterrupt = true) public int sceAtracSetMOutHalfwayBuffer(@CheckArgument("checkAtracID") int atID, TPointer MOutHalfBuffer, int readSize, int MOutHalfBufferSize) { return hleSetHalfwayBuffer(atID, MOutHalfBuffer, readSize, MOutHalfBufferSize, true); } // Not sure if this function does really exist @HLEUnimplemented @HLEFunction(nid = 0xF6837A1A, version = 250, checkInsideInterrupt = true) public int sceAtracSetMOutData(@CheckArgument("checkAtracID") int atID, int unknown2, int unknown3, int unknown4, int unknown5, int unknown6) { return 0; } // Not sure if this function does really exist @HLEUnimplemented @HLEFunction(nid = 0x472E3825, version = 250, checkInsideInterrupt = true) public int sceAtracSetMOutDataAndGetID(int unknown1, int unknown2, int unknown3, int unknown4, int unknown5, int unknown6) { return 0; } @HLEFunction(nid = 0x9CD7DE03, version = 250, checkInsideInterrupt = true) public int sceAtracSetMOutHalfwayBufferAndGetID(TPointer MOutHalfBuffer, int readSize, int MOutHalfBufferSize) { return hleSetHalfwayBufferAndGetID(MOutHalfBuffer, readSize, MOutHalfBufferSize, true); } @HLEFunction(nid = 0x5622B7C1, version = 250, checkInsideInterrupt = true) public int sceAtracSetAA3DataAndGetID(TPointer buffer, int bufferSize, int fileSize, int unused) { if (log.isDebugEnabled()) { log.debug(String.format("sceAtracSetAA3DataAndGetID buffer:%s", Utilities.getMemoryDump(buffer.getAddress(), bufferSize))); } return hleSetAA3HalfwayBufferAndGetID(buffer, bufferSize, bufferSize, false, fileSize); } @HLEFunction(nid = 0x5DD66588, version = 250) public int sceAtracSetAA3HalfwayBufferAndGetID(TPointer buffer, int readSize, int bufferSize, int fileSize, int unused) { if (log.isDebugEnabled()) { log.debug(String.format("sceAtracSetAA3HalfwayBufferAndGetID buffer:%s", Utilities.getMemoryDump(buffer.getAddress(), readSize))); } return hleSetAA3HalfwayBufferAndGetID(buffer, readSize, bufferSize, false, fileSize); } @HLELogging(level="info") @HLEFunction(nid = 0x231FC6B7, version = 600, checkInsideInterrupt = true) public int _sceAtracGetContextAddress(int atID) { if (atID < 0 || atID >= atracIDs.length || !atracIDs[atID].isInUse()) { return 0; } AtracID id = atracIDs[atID]; id.createContext(); SysMemInfo atracContext = id.getContext(); if (atracContext == null) { return 0; } if (log.isDebugEnabled()) { log.debug(String.format("_sceAtracGetContextAddress returning 0x%08X", atracContext.addr)); } return atracContext.addr; } @HLEFunction(nid = 0x0C116E1B, version = 620) public int sceAtracLowLevelDecode(@CheckArgument("checkAtracID") int atID, TPointer sourceAddr, TPointer32 sourceBytesConsumedAddr, TPointer samplesAddr, TPointer32 sampleBytesAddr) { AtracID id = atracIDs[atID]; ICodec codec = id.getCodec(); if (log.isTraceEnabled()) { log.trace(String.format("sceAtracLowLevelDecode input:%s", Utilities.getMemoryDump(sourceAddr.getAddress(), id.getSourceBufferLength()))); } int sourceBytesConsumed = 0; int bytesPerSample = id.getOutputChannels() << 1; int result = codec.decode(sourceAddr.getAddress(), id.getSourceBufferLength(), samplesAddr.getAddress()); if (log.isDebugEnabled()) { log.debug(String.format("sceAtracLowLevelDecode codec returned 0x%08X", result)); } if (result < 0) { log.info(String.format("sceAtracLowLevelDecode codec returning 0x%08X", result)); return result; } sourceBytesConsumed = result > 0 ? id.getSourceBufferLength() : 0; sampleBytesAddr.setValue(codec.getNumberOfSamples() * bytesPerSample); // Consume a part of the Atrac3 source buffer sourceBytesConsumedAddr.setValue(sourceBytesConsumed); Modules.ThreadManForUserModule.hleKernelDelayThread(atracDecodeDelay, false); return 0; } @HLELogging(level="info") @HLEFunction(nid = 0x1575D64B, version = 620) public int sceAtracLowLevelInitDecoder(@CheckArgument("checkAtracID") int atID, TPointer32 paramsAddr) { int numberOfChannels = paramsAddr.getValue(0); int outputChannels = paramsAddr.getValue(4); int sourceBufferLength = paramsAddr.getValue(8); if (log.isDebugEnabled()) { log.debug(String.format("sceAtracLowLevelInitDecoder values at %s: numberOfChannels=%d, outputChannels=%d, sourceBufferLength=0x%08X", paramsAddr, numberOfChannels, outputChannels, sourceBufferLength)); } AtracID id = atracIDs[atID]; int result = 0; id.setChannels(numberOfChannels); id.setOutputChannels(outputChannels); id.setSourceBufferLength(sourceBufferLength); // TODO How to find out the codingMode for AT3 audio? Assume STEREO, not JOINT_STEREO result = id.getCodec().init(sourceBufferLength, numberOfChannels, outputChannels, 0); id.setCodecInitialized(); return result; } }