/*
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.test;
import static jpcsp.HLE.modules.sceAtrac3plus.AT3_MAGIC;
import static jpcsp.HLE.modules.sceAtrac3plus.AT3_PLUS_MAGIC;
import static jpcsp.HLE.modules.sceAtrac3plus.FMT_CHUNK_MAGIC;
import static jpcsp.HLE.modules.sceAtrac3plus.RIFF_MAGIC;
import static jpcsp.HLE.modules.sceAudiocodec.PSP_CODEC_AAC;
import static jpcsp.HLE.modules.sceAudiocodec.PSP_CODEC_AT3;
import static jpcsp.HLE.modules.sceAudiocodec.PSP_CODEC_AT3PLUS;
import static jpcsp.HLE.modules.sceAudiocodec.PSP_CODEC_MP3;
import static jpcsp.HLE.modules.sceMpeg.PSMF_MAGIC;
import static jpcsp.HLE.modules.sceMpeg.PSMF_MAGIC_OFFSET;
import static jpcsp.HLE.modules.sceMpeg.PSMF_STREAM_OFFSET_OFFSET;
import static jpcsp.util.Utilities.endianSwap32;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;
import jpcsp.Memory;
import jpcsp.MemoryMap;
import jpcsp.HLE.VFS.IVirtualFile;
import jpcsp.HLE.VFS.MemoryVirtualFile;
import jpcsp.HLE.modules.sceAtrac3plus;
import jpcsp.HLE.modules.sceMp3;
import jpcsp.format.psmf.PsmfAudioDemuxVirtualFile;
import jpcsp.media.codec.CodecFactory;
import jpcsp.media.codec.ICodec;
import jpcsp.media.codec.atrac3plus.Atrac3plusDecoder;
import jpcsp.media.codec.mp3.Mp3Decoder;
import jpcsp.media.codec.mp3.Mp3Header;
import jpcsp.util.Utilities;
public class CodecTest {
private static Logger log = Atrac3plusDecoder.log;
private static final boolean dumpRawAudio = false;
private static void write(Memory mem, int addr, byte[] data, int offset, int length) {
length = Math.min(length, data.length - offset);
for (int i = 0; i < length; i++) {
mem.write8(addr + i, data[offset + i]);
}
}
private static void write(Memory mem, int addr, byte[] data) {
write(mem, addr, data, 0, data.length);
}
public static void main(String[] args) {
DOMConfigurator.configure("LogSettings.xml");
Memory mem = Memory.getInstance();
try {
String fileName = "sample.at3";
if (args != null && args.length > 0) {
fileName = args[0];
}
File file = new File(fileName);
log.info(String.format("Reading file %s", file));
int length = (int) file.length();
InputStream in = new FileInputStream(file);
byte buffer[] = new byte[length];
in.read(buffer);
in.close();
int samplesAddr = MemoryMap.START_USERSPACE;
int inputAddr = MemoryMap.START_USERSPACE + 0x10000;
write(mem, inputAddr, buffer);
int channels = 2;
int codecType = -1;
int bytesPerFrame = 0;
int codingMode = 0;
int dataOffset = 0;
if (mem.read32(inputAddr) == RIFF_MAGIC) {
int scanOffset = 12;
while (dataOffset <= 0) {
int chunkMagic = mem.read32(inputAddr + scanOffset);
int chunkLength = mem.read32(inputAddr + scanOffset + 4);
scanOffset += 8;
switch (chunkMagic) {
case FMT_CHUNK_MAGIC:
switch (mem.read16(inputAddr + scanOffset + 0)) {
case AT3_PLUS_MAGIC: codecType = PSP_CODEC_AT3PLUS; break;
case AT3_MAGIC : codecType = PSP_CODEC_AT3; break;
}
channels = mem.read16(inputAddr + scanOffset + 2);
bytesPerFrame = mem.read16(inputAddr + scanOffset + 12);
int extraDataSize = mem.read16(inputAddr + scanOffset + 16);
if (extraDataSize == 14) {
codingMode = mem.read16(inputAddr + scanOffset + 18 + 6);
}
break;
case sceAtrac3plus.DATA_CHUNK_MAGIC:
dataOffset = scanOffset;
break;
}
scanOffset += chunkLength;
}
} else if (mem.read32(inputAddr + PSMF_MAGIC_OFFSET) == PSMF_MAGIC) {
int mpegOffset = endianSwap32(mem.read32(inputAddr + PSMF_STREAM_OFFSET_OFFSET));
IVirtualFile vFile = new MemoryVirtualFile(inputAddr, length);
PsmfAudioDemuxVirtualFile demux = new PsmfAudioDemuxVirtualFile(vFile, mpegOffset, -1);
byte[] audioData = Utilities.readCompleteFile(demux);
bytesPerFrame = (((audioData[2] & 0x03) << 8) | ((audioData[3] & 0xFF) << 3)) + 8;
int headerLength = 8;
length = 0;
for (int i = 0; i < audioData.length; i += headerLength + bytesPerFrame) {
write(mem, inputAddr + length, audioData, i + headerLength, bytesPerFrame);
length += bytesPerFrame;
}
codecType = PSP_CODEC_AT3PLUS;
} else if (mem.read32(inputAddr) == 0x02334449 || mem.read32(inputAddr) == 0x03334449 || mem.read32(inputAddr) == 0x04334449) { // ID3v2, ID3v3, ID3v4
int headerLength = 0;
for (int i = 0; i < 4; i++) {
headerLength = (headerLength << 7) + (mem.read8(inputAddr + 6 + i) & 0x7F);
}
if (sceMp3.isMp3Magic(Utilities.readUnaligned16(mem, inputAddr + 10 + headerLength))) {
dataOffset = headerLength + 10;
codecType = PSP_CODEC_MP3;
}
} else if (sceMp3.isMp3Magic(mem.read16(inputAddr))) {
Mp3Header mp3Header = new Mp3Header();
if (Mp3Decoder.decodeHeader(mp3Header, Integer.reverseBytes(mem.read32(inputAddr))) == 0) {
dataOffset = mp3Header.frameSize;
}
codecType = PSP_CODEC_MP3;
} else if ((Utilities.endianSwap16(mem.read16(inputAddr)) & 0xFFF0) == 0xFFF0 || file.getName().endsWith(".aac")) {
codecType = PSP_CODEC_AAC;
} else {
log.error(String.format("File '%s' not in RIFF format", file));
return;
}
AudioFormat audioFormat = new AudioFormat(44100,
16,
channels,
true,
false);
DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
SourceDataLine mLine = (SourceDataLine) AudioSystem.getLine(info);
mLine.open(audioFormat);
mLine.start();
ICodec codec = CodecFactory.getCodec(codecType);
codec.init(bytesPerFrame, channels, channels, codingMode);
inputAddr += dataOffset;
length -= dataOffset;
OutputStream os = null;
if (dumpRawAudio) {
os = new FileOutputStream("sample.raw");
}
for (int frameNbr = 0; true; frameNbr++) {
int result = codec.decode(inputAddr, length, samplesAddr);
if (result < 0) {
log.error(String.format("Frame #%d, result 0x%08X", frameNbr, result));
break;
}
if (result == 0) {
// End of data
break;
}
int consumedBytes = bytesPerFrame;
if (result < bytesPerFrame - 2 || result > bytesPerFrame) {
if (bytesPerFrame == 0) {
consumedBytes = result;
} else {
log.warn(String.format("Frame #%d, result 0x%X, expected 0x%X", frameNbr, result, bytesPerFrame));
}
}
inputAddr += consumedBytes;
length -= consumedBytes;
byte bytes[] = new byte[codec.getNumberOfSamples() * 2 * channels];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) mem.read8(samplesAddr + i);
}
mLine.write(bytes, 0, bytes.length);
if (dumpRawAudio) {
os.write(bytes);
}
}
mLine.drain();
mLine.close();
if (os != null) {
os.close();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (LineUnavailableException e) {
e.printStackTrace();
}
}
}