/*
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_AAC_DECODING_ERROR;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_AAC_INVALID_ADDRESS;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_AAC_INVALID_ID;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_AAC_INVALID_PARAMETER;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_AAC_RESOURCE_NOT_INITIALIZED;
import static jpcsp.HLE.modules.sceAudiocodec.PSP_CODEC_AAC;
import org.apache.log4j.Logger;
import jpcsp.Memory;
import jpcsp.HLE.CanBeNull;
import jpcsp.HLE.CheckArgument;
import jpcsp.HLE.HLEFunction;
import jpcsp.HLE.HLEModule;
import jpcsp.HLE.HLEUnimplemented;
import jpcsp.HLE.Modules;
import jpcsp.HLE.SceKernelErrorException;
import jpcsp.HLE.TPointer;
import jpcsp.HLE.TPointer32;
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.util.Utilities;
public class sceAac extends HLEModule {
public static Logger log = Modules.getLogger("sceAac");
protected SysMemInfo resourceMem;
protected AacInfo[] ids;
public static class AacInfo extends AudiocodecInfo {
// The PSP is always reserving this size at the beginning of the input buffer
private static final int reservedBufferSize = 1600;
private static final int minimumInputBufferSize = reservedBufferSize;
private boolean init;
private pspFileBuffer inputBuffer;
private int bufferAddr;
private int outputAddr;
private int outputSize;
private int sumDecodedSamples;
private int halfBufferSize;
private int outputIndex;
private int loopNum;
private int startPos;
protected AacInfo(int id) {
super(id);
}
public boolean isInit() {
return init;
}
public void init(int bufferAddr, int bufferSize, int outputAddr, int outputSize, long startPos, long endPos) {
this.bufferAddr = bufferAddr;
this.outputAddr = outputAddr;
this.outputSize = outputSize;
this.startPos = (int) startPos;
inputBuffer = new pspFileBuffer(bufferAddr + reservedBufferSize, bufferSize - reservedBufferSize, 0, this.startPos);
inputBuffer.setFileMaxSize((int) endPos);
loopNum = -1; // Looping indefinitely by default
initCodec();
halfBufferSize = (bufferSize - reservedBufferSize) >> 1;
}
@Override
public void initCodec() {
init = true;
codec = CodecFactory.getCodec(PSP_CODEC_AAC);
codec.init(0, outputChannels, outputChannels, 0);
}
@Override
public void release() {
init = false;
}
public int notifyAddStream(int bytesToAdd) {
if (log.isTraceEnabled()) {
log.trace(String.format("notifyAddStream: %s", Utilities.getMemoryDump(inputBuffer.getWriteAddr(), bytesToAdd)));
}
inputBuffer.notifyWrite(bytesToAdd);
return 0;
}
public pspFileBuffer getInputBuffer() {
return inputBuffer;
}
public boolean isStreamDataNeeded() {
int writeSize = inputBuffer.getWriteSize();
if (writeSize <= 0) {
return false;
}
if (writeSize >= halfBufferSize) {
return true;
}
if (writeSize >= inputBuffer.getFileWriteSize()) {
return true;
}
return false;
}
public int getSumDecodedSamples() {
return sumDecodedSamples;
}
public int decode(TPointer32 outputBufferAddress) {
int result;
int decodeOutputAddr = outputAddr + outputIndex;
if (inputBuffer.isFileEnd() && inputBuffer.getCurrentSize() <= 0) {
int outputBytes = codec.getNumberOfSamples() * 4;
Memory mem = Memory.getInstance();
mem.memset(decodeOutputAddr, (byte) 0, outputBytes);
result = outputBytes;
} else if (inputBuffer.getCurrentSize() <= 0) {
int outputBytes = outputSize >> 1;
Memory mem = Memory.getInstance();
mem.memset(decodeOutputAddr, (byte) 0, outputBytes);
result = outputBytes;
} else {
int decodeInputAddr = inputBuffer.getReadAddr();
int decodeInputLength = inputBuffer.getReadSize();
// Reaching the end of the input buffer (wrapping to its beginning)?
if (decodeInputLength < minimumInputBufferSize && decodeInputLength < inputBuffer.getCurrentSize()) {
// Concatenate the input into a temporary buffer
Memory mem = Memory.getInstance();
mem.memcpy(bufferAddr, decodeInputAddr, decodeInputLength);
int wrapLength = Math.min(inputBuffer.getCurrentSize(), minimumInputBufferSize) - decodeInputLength;
mem.memcpy(bufferAddr + decodeInputLength, inputBuffer.getAddr(), wrapLength);
decodeInputAddr = bufferAddr;
decodeInputLength += wrapLength;
}
if (log.isDebugEnabled()) {
log.debug(String.format("Decoding from 0x%08X, length=0x%X to 0x%08X", decodeInputAddr, decodeInputLength, decodeOutputAddr));
}
result = codec.decode(decodeInputAddr, decodeInputLength, decodeOutputAddr);
if (result < 0) {
result = ERROR_AAC_DECODING_ERROR;
} else {
int readSize = result;
int samples = codec.getNumberOfSamples();
int outputBytes = samples * 4;
inputBuffer.notifyRead(readSize);
sumDecodedSamples += samples;
// Update index in output buffer for next decode()
outputIndex += outputBytes;
if (outputIndex + outputBytes > outputSize) {
// No space enough to store the same amount of output bytes,
// reset to beginning of output buffer
outputIndex = 0;
}
result = outputBytes;
}
if (inputBuffer.getCurrentSize() < minimumInputBufferSize && inputBuffer.isFileEnd() && loopNum != 0) {
if (log.isDebugEnabled()) {
log.debug(String.format("Looping loopNum=%d", loopNum));
}
if (loopNum > 0) {
loopNum--;
}
resetPlayPosition();
}
}
outputBufferAddress.setValue(decodeOutputAddr);
return result;
}
public int getWritableBytes() {
int writeSize = inputBuffer.getWriteSize();
if (writeSize >= 2 * halfBufferSize) {
return 2 * halfBufferSize;
}
if (writeSize >= halfBufferSize) {
return halfBufferSize;
}
if (writeSize >= inputBuffer.getFileWriteSize()) {
return halfBufferSize;
}
return 0;
}
public int getLoopNum() {
return loopNum;
}
public void setLoopNum(int loopNum) {
this.loopNum = loopNum;
}
public int resetPlayPosition() {
inputBuffer.reset(0, startPos);
sumDecodedSamples = 0;
return 0;
}
}
@Override
public void start() {
ids = null;
super.start();
}
public int checkId(int id) {
if (ids == null || ids.length == 0) {
throw new SceKernelErrorException(ERROR_AAC_RESOURCE_NOT_INITIALIZED);
}
if (id < 0 || id >= ids.length) {
throw new SceKernelErrorException(ERROR_AAC_INVALID_ID);
}
return id;
}
public int checkInitId(int id) {
id = checkId(id);
if (!ids[id].isInit()) {
throw new SceKernelErrorException(SceKernelErrors.ERROR_AAC_ID_NOT_INITIALIZED);
}
return id;
}
public int getFreeAacId() {
int id = -1;
for (int i = 0; i < ids.length; i++) {
if (!ids[i].isInit()) {
id = i;
break;
}
}
if (id < 0) {
return SceKernelErrors.ERROR_AAC_NO_MORE_FREE_ID;
}
return id;
}
public AacInfo getAacInfo(int id) {
return ids[id];
}
public void hleAacInit(int numberIds) {
ids = new AacInfo[numberIds];
for (int i = 0; i < numberIds; i++) {
ids[i] = new AacInfo(i);
}
}
@HLEFunction(nid = 0xE0C89ACA, version = 395)
public int sceAacInit(@CanBeNull TPointer parameters, int unknown1, int unknown2, int unknown3) {
if (parameters.isNull()) {
return ERROR_AAC_INVALID_ADDRESS;
}
long startPos = parameters.getValue64(0); // Audio data frame start position.
long endPos = parameters.getValue64(8); // Audio data frame end position.
int bufferAddr = parameters.getValue32(16); // Input AAC data buffer.
int bufferSize = parameters.getValue32(20); // Input AAC data buffer size.
int outputAddr = parameters.getValue32(24); // Output PCM data buffer.
int outputSize = parameters.getValue32(28); // Output PCM data buffer size.
int freq = parameters.getValue32(32); // Frequency.
int reserved = parameters.getValue32(36); // Always null.
if (log.isDebugEnabled()) {
log.debug(String.format("sceAacInit parameters: startPos=0x%X, endPos=0x%X, "
+ "bufferAddr=0x%08X, bufferSize=0x%X, outputAddr=0x%08X, outputSize=0x%X, freq=%d, reserved=0x%08X",
startPos, endPos, bufferAddr, bufferSize, outputAddr, outputSize, freq, reserved));
}
if (bufferAddr == 0 || outputAddr == 0) {
return ERROR_AAC_INVALID_ADDRESS;
}
if (startPos < 0 || startPos > endPos) {
return ERROR_AAC_INVALID_PARAMETER;
}
if (bufferSize < 8192 || outputSize < 8192 || reserved != 0) {
return ERROR_AAC_INVALID_PARAMETER;
}
if (freq != 44100 && freq != 32000 && freq != 48000 && freq != 24000) {
return ERROR_AAC_INVALID_PARAMETER;
}
int id = getFreeAacId();
if (id < 0) {
return id;
}
ids[id].init(bufferAddr, bufferSize, outputAddr, outputSize, startPos, endPos);
return id;
}
@HLEFunction(nid = 0x33B8C009, version = 395)
public int sceAacExit(@CheckArgument("checkId") int id) {
getAacInfo(id).release();
return 0;
}
@HLEFunction(nid = 0x5CFFC57C, version = 395)
public int sceAacInitResource(int numberIds) {
int memSize = numberIds * 0x19000;
resourceMem = Modules.SysMemUserForUserModule.malloc(SysMemUserForUser.USER_PARTITION_ID, "SceLibAacResource", SysMemUserForUser.PSP_SMEM_Low, memSize, 0);
if (resourceMem == null) {
return SceKernelErrors.ERROR_AAC_NOT_ENOUGH_MEMORY;
}
Memory.getInstance().memset(resourceMem.addr, (byte) 0, memSize);
hleAacInit(numberIds);
return 0;
}
@HLEFunction(nid = 0x23D35CAE, version = 395)
public int sceAacTermResource() {
if (resourceMem != null) {
Modules.SysMemUserForUserModule.free(resourceMem);
resourceMem = null;
}
return 0;
}
@HLEFunction(nid = 0x7E4CFEE4, version = 395)
public int sceAacDecode(@CheckArgument("checkInitId") int id, @CanBeNull TPointer32 bufferAddress) {
int result = getAacInfo(id).decode(bufferAddress);
if (log.isDebugEnabled()) {
log.debug(String.format("sceAacDecode bufferAddress=%s(0x%08X) returning 0x%X", bufferAddress, bufferAddress.getValue(), result));
}
if (result >= 0) {
Modules.ThreadManForUserModule.hleKernelDelayThread(sceAtrac3plus.atracDecodeDelay, false);
}
return result;
}
@HLEFunction(nid = 0x523347D9, version = 395)
public int sceAacGetLoopNum(@CheckArgument("checkInitId") int id) {
return getAacInfo(id).getLoopNum();
}
@HLEFunction(nid = 0xBBDD6403, version = 395)
public int sceAacSetLoopNum(@CheckArgument("checkInitId") int id, int loopNum) {
getAacInfo(id).setLoopNum(loopNum);
return 0;
}
@HLEFunction(nid = 0xD7C51541, version = 395)
public boolean sceAacCheckStreamDataNeeded(@CheckArgument("checkInitId") int id) {
return getAacInfo(id).isStreamDataNeeded();
}
@HLEFunction(nid = 0xAC6DCBE3, version = 395)
public int sceAacNotifyAddStreamData(@CheckArgument("checkInitId") int id, int bytesToAdd) {
return getAacInfo(id).notifyAddStream(bytesToAdd);
}
@HLEFunction(nid = 0x02098C69, version = 395)
public int sceAacGetInfoToAddStreamData(@CheckArgument("checkInitId") int id, @CanBeNull TPointer32 writeAddr, @CanBeNull TPointer32 writableBytesAddr, @CanBeNull TPointer32 readOffsetAddr) {
AacInfo info = getAacInfo(id);
writeAddr.setValue(info.getInputBuffer().getWriteAddr());
writableBytesAddr.setValue(info.getWritableBytes());
readOffsetAddr.setValue(info.getInputBuffer().getFilePosition());
if (log.isDebugEnabled()) {
log.debug(String.format("sceAacGetInfoToAddStreamData returning writeAddr=0x%08X, writableBytes=0x%X, readOffset=0x%X", writeAddr.getValue(), writableBytesAddr.getValue(), readOffsetAddr.getValue()));
}
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x6DC7758A, version = 395)
public int sceAacGetMaxOutputSample(@CheckArgument("checkInitId") int id) {
return 0;
}
@HLEFunction(nid = 0x506BF66C, version = 395)
public int sceAacGetSumDecodedSample(@CheckArgument("checkInitId") int id) {
int sumDecodedSamples = getAacInfo(id).getSumDecodedSamples();
if (log.isDebugEnabled()) {
log.debug(String.format("sceAacGetSumDecodedSample returning 0x%X", sumDecodedSamples));
}
return sumDecodedSamples;
}
@HLEFunction(nid = 0xD2DA2BBA, version = 395)
public int sceAacResetPlayPosition(@CheckArgument("checkInitId") int id) {
return getAacInfo(id).resetPlayPosition();
}
}