/*
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 java.util.HashMap;
import java.util.Map;
import org.apache.log4j.Logger;
import jpcsp.Memory;
import jpcsp.HLE.BufferInfo;
import jpcsp.HLE.BufferInfo.LengthInfo;
import jpcsp.HLE.BufferInfo.Usage;
import jpcsp.HLE.HLEFunction;
import jpcsp.HLE.HLELogging;
import jpcsp.HLE.HLEModule;
import jpcsp.HLE.HLEUnimplemented;
import jpcsp.HLE.Modules;
import jpcsp.HLE.TPointer;
import jpcsp.HLE.TPointer32;
import jpcsp.HLE.kernel.types.SceKernelErrors;
import jpcsp.HLE.modules.SysMemUserForUser.SysMemInfo;
import jpcsp.media.codec.ICodec;
import jpcsp.media.codec.mp3.Mp3Decoder;
import jpcsp.media.codec.mp3.Mp3Header;
import jpcsp.util.Utilities;
public class sceAudiocodec extends HLEModule {
public static Logger log = Modules.getLogger("sceAudiocodec");
public static final int PSP_CODEC_AT3PLUS = 0x00001000;
public static final int PSP_CODEC_AT3 = 0x00001001;
public static final int PSP_CODEC_MP3 = 0x00001002;
public static final int PSP_CODEC_AAC = 0x00001003;
public static abstract class AudiocodecInfo {
protected ICodec codec;
protected boolean codecInitialized;
protected final int id;
protected int outputChannels = 2; // Always default with 2 output channels
protected AudiocodecInfo(int id) {
this.id = id;
}
public ICodec getCodec() {
return codec;
}
public boolean isCodecInitialized() {
return codecInitialized;
}
public void setCodecInitialized(boolean codecInitialized) {
this.codecInitialized = codecInitialized;
}
public void setCodecInitialized() {
setCodecInitialized(true);
}
public abstract void release();
public abstract void initCodec();
}
private Map<Integer, AudiocodecInfo> infos;
private SysMemInfo edramInfo;
@Override
public void start() {
infos = new HashMap<Integer, sceAudiocodec.AudiocodecInfo>();
edramInfo = null;
super.start();
}
private int hleAudiocodecInit(TPointer workArea, int codecType, int outputChannels) {
AudiocodecInfo info = infos.remove(workArea.getAddress());
if (info != null) {
info.release();
info.setCodecInitialized(false);
info = null;
}
int id;
switch (codecType) {
case PSP_CODEC_AT3:
case PSP_CODEC_AT3PLUS:
id = Modules.sceAtrac3plusModule.hleGetAtracID(codecType);
if (id < 0) {
return id;
}
info = Modules.sceAtrac3plusModule.getAtracID(id);
info.outputChannels = outputChannels;
break;
case PSP_CODEC_AAC:
Modules.sceAacModule.hleAacInit(1);
id = Modules.sceAacModule.getFreeAacId();
if (id < 0) {
return id;
}
info = Modules.sceAacModule.getAacInfo(id);
info.outputChannels = outputChannels;
info.initCodec();
break;
case PSP_CODEC_MP3:
id = Modules.sceMp3Module.getFreeMp3Id();
if (id < 0) {
return id;
}
info = Modules.sceMp3Module.getMp3Info(id);
info.outputChannels = outputChannels;
info.initCodec();
break;
default:
log.warn(String.format("sceAudiocodecInit unimplemented codecType=0x%X", codecType));
return -1;
}
infos.put(workArea.getAddress(), info);
workArea.setValue32(8, 0); // error field
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x9D3F790C, version = 150)
public int sceAudiocodecCheckNeedMem(TPointer workArea, int codecType) {
workArea.setValue32(0, 0x05100601);
switch (codecType) {
case PSP_CODEC_AT3:
workArea.setValue32(16, 0x3DE0);
break;
case PSP_CODEC_AT3PLUS:
workArea.setValue32(16, 0x7BC0);
workArea.setValue32(52, 44100);
workArea.setValue32(60, 2);
workArea.setValue32(64, 0x2E8);
break;
case PSP_CODEC_MP3:
break;
case PSP_CODEC_AAC:
workArea.setValue32(16, 0x658C);
break;
}
return 0;
}
@HLELogging(level = "info")
@HLEFunction(nid = 0x5B37EB1D, version = 150)
public int sceAudiocodecInit(@BufferInfo(lengthInfo=LengthInfo.fixedLength, length=108, usage=Usage.inout) TPointer workArea, int codecType) {
// Same as sceAudiocodec_3DD7EE1A, but for stereo audio
return hleAudiocodecInit(workArea, codecType, 2);
}
private int getOutputBufferSize(TPointer workArea, int codecType) {
int outputBufferSize;
switch (codecType) {
case PSP_CODEC_AT3PLUS:
if (workArea.getValue32(56) == 1 && workArea.getValue32(72) != workArea.getValue32(56)) {
outputBufferSize = 0x2000;
} else {
outputBufferSize = workArea.getValue32(72) << 12;
}
break;
case PSP_CODEC_AT3:
outputBufferSize = 0x1000;
break;
case PSP_CODEC_MP3:
if (workArea.getValue32(56) == 1) {
outputBufferSize = 0x1200;
} else {
outputBufferSize = 0x900;
}
break;
case PSP_CODEC_AAC:
if (workArea.getValue8(45) == 0) {
outputBufferSize = 0x1000;
} else {
outputBufferSize = 0x2000;
}
break;
case 0x1004:
outputBufferSize = 0x1200;
break;
default:
outputBufferSize = 0x1000;
}
return outputBufferSize;
}
@HLEFunction(nid = 0x70A703F8, version = 150)
public int sceAudiocodecDecode(@BufferInfo(lengthInfo=LengthInfo.fixedLength, length=108, usage=Usage.inout) TPointer workArea, int codecType) {
workArea.setValue32(8, 0); // err field
int inputBuffer = workArea.getValue32(24);
int outputBuffer = workArea.getValue32(32);
int unknown1 = workArea.getValue32(40);
int codingMode = 0; // TODO How to find out the correct value?
int inputBufferSize;
switch (codecType) {
case PSP_CODEC_AT3PLUS:
if (workArea.getValue32(48) == 0) {
inputBufferSize = workArea.getValue32(64) + 2;
} else {
inputBufferSize = 0x100A;
}
// Skip any audio frame header (found in PSMF files)
Memory mem = workArea.getMemory();
if (mem.read8(inputBuffer) == 0x0F && mem.read8(inputBuffer + 1) == 0xD0) {
int frameHeader23 = (mem.read8(inputBuffer + 2) << 8) | mem.read8(inputBuffer + 3);
int audioFrameLength = (frameHeader23 & 0x3FF) << 3;
inputBufferSize = audioFrameLength;
inputBuffer += 8;
}
break;
case PSP_CODEC_AT3:
inputBufferSize = workArea.getValue32(40) == 6 ? 0x130 : 0x180;
break;
case PSP_CODEC_MP3:
inputBufferSize = workArea.getValue32(40);
break;
case PSP_CODEC_AAC:
if (workArea.getValue8(44) == 0) {
inputBufferSize = 0x600;
} else {
inputBufferSize = 0x609;
}
break;
case 0x1004:
inputBufferSize = workArea.getValue32(40);
break;
default:
return -1;
}
int outputBufferSize = getOutputBufferSize(workArea, codecType);
workArea.setValue32(36, outputBufferSize);
if (log.isDebugEnabled()) {
log.debug(String.format("sceAudiocodecDecode inputBuffer=0x%08X, outputBuffer=0x%08X, inputBufferSize=0x%X, outputBufferSize=0x%X", inputBuffer, outputBuffer, inputBufferSize, outputBufferSize));
log.debug(String.format("sceAudiocodecDecode unknown1=0x%08X", unknown1));
if (log.isTraceEnabled()) {
log.trace(String.format("sceAudiocodecDecode inputBuffer: %s", Utilities.getMemoryDump(inputBuffer, inputBufferSize)));
}
}
AudiocodecInfo info = infos.get(workArea.getAddress());
if (info == null) {
log.warn(String.format("sceAudiocodecDecode no info available for workArea=%s", workArea));
return -1;
}
ICodec codec = info.getCodec();
if (!info.isCodecInitialized()) {
codec.init(inputBufferSize, info.outputChannels, info.outputChannels, codingMode);
info.setCodecInitialized();
}
if (codec == null) {
log.warn(String.format("sceAudiocodecDecode no codec available for codecType=0x%X", codecType));
return -1;
}
int bytesConsumed = codec.decode(inputBuffer, inputBufferSize, outputBuffer);
if (log.isDebugEnabled()) {
if (bytesConsumed < 0) {
log.debug(String.format("codec.decode returned error 0x%08X, data: %s", bytesConsumed, Utilities.getMemoryDump(inputBuffer, inputBufferSize)));
} else {
log.debug(String.format("sceAudiocodecDecode bytesConsumed=0x%X", bytesConsumed));
}
}
if (codec instanceof Mp3Decoder) {
Mp3Header mp3Header = ((Mp3Decoder) codec).getMp3Header();
if (mp3Header != null) {
// See https://github.com/uofw/uofw/blob/master/src/avcodec/audiocodec.c
workArea.setValue32(68, mp3Header.bitrateIndex); // MP3 bitrateIndex [0..14]
workArea.setValue32(72, mp3Header.rawSampleRateIndex); // MP3 freqType [0..3]
int type;
if (mp3Header.mpeg25 != 0) {
type = 2;
} else if (mp3Header.lsf != 0) {
type = 0;
} else {
type = 1;
}
workArea.setValue32(56, type); // type [0..2]
if (log.isDebugEnabled()) {
log.debug(String.format("sceAudiocodecDecode MP3 bitrateIndex=%d, rawSampleRateIndex=%d, type=%d", mp3Header.bitrateIndex, mp3Header.rawSampleRateIndex, type));
}
}
}
workArea.setValue32(28, bytesConsumed > 0 ? bytesConsumed : inputBufferSize);
Modules.ThreadManForUserModule.hleKernelDelayThread(sceMpeg.atracDecodeDelay, false);
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x8ACA11D5, version = 150)
public int sceAudiocodecGetInfo(@BufferInfo(lengthInfo=LengthInfo.fixedLength, length=108, usage=Usage.inout) TPointer workArea, int codecType) {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x59176A0F, version = 150)
public int sceAudiocodecAlcExtendParameter(@BufferInfo(lengthInfo=LengthInfo.fixedLength, length=108, usage=Usage.inout) TPointer workArea, int codecType, @BufferInfo(usage=Usage.out) TPointer32 sizeAddr) {
int outputBufferSize = getOutputBufferSize(workArea, codecType);
sizeAddr.setValue(outputBufferSize);
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x3A20A200, version = 150)
public int sceAudiocodecGetEDRAM(TPointer workArea, int codecType) {
int neededMem = workArea.getValue32(16);
edramInfo = Modules.SysMemUserForUserModule.malloc(SysMemUserForUser.KERNEL_PARTITION_ID, "sceAudiocodec-EDRAM", SysMemUserForUser.PSP_SMEM_LowAligned, neededMem, 0x40);
if (edramInfo == null) {
return -1;
}
workArea.setValue32(12, edramInfo.addr);
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x29681260, version = 150)
public int sceAudiocodecReleaseEDRAM(TPointer workArea) {
if (edramInfo == null) {
return SceKernelErrors.ERROR_CODEC_AUDIO_EDRAM_NOT_ALLOCATED;
}
Modules.SysMemUserForUserModule.free(edramInfo);
edramInfo = null;
AudiocodecInfo info = infos.remove(workArea.getAddress());
if (info != null) {
info.release();
info.setCodecInitialized(false);
}
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x6CD2A861, version = 150)
public int sceAudiocodec_6CD2A861() {
return 0;
}
@HLELogging(level = "info")
@HLEFunction(nid = 0x3DD7EE1A, version = 150)
public int sceAudiocodec_3DD7EE1A(@BufferInfo(lengthInfo=LengthInfo.fixedLength, length=108, usage=Usage.inout) TPointer workArea, int codecType) {
// Same as sceAudiocodecInit, but for mono audio
return hleAudiocodecInit(workArea, codecType, 1);
}
}