/* 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.media; import java.nio.ByteBuffer; import java.nio.ByteOrder; import jpcsp.HLE.modules.sceAtrac3plus; public class OMAFormat { private static final int OMA_EA3_MAGIC = 0x45413301; private static final byte OMA_CODECID_ATRAC3P = 1; private static ByteBuffer getOmaHeader(byte codecId, byte headerCode1, byte headerCode2) { ByteBuffer header = ByteBuffer.allocate(96).order(ByteOrder.BIG_ENDIAN); header.putInt(OMA_EA3_MAGIC); header.putShort((short) header.capacity()); header.putShort((short) -1); // Unknown 24 bytes... header.putInt(0x00000000); header.putInt(0x010f5000); header.putInt(0x00040000); header.putInt(0x0000f5ce); header.putInt(0xd2929132); header.putInt(0x2480451c); header.put(codecId); header.put((byte) 0); header.put(headerCode1); header.put(headerCode2); while (header.position() < header.limit()) { header.put((byte) 0); } header.rewind(); return header; } private static boolean isHeader(ByteBuffer audioStream, int offset) { final byte header1 = (byte) 0x0F; final byte header2 = (byte) 0xD0; return audioStream.get(offset) == header1 && audioStream.get(offset + 1) == header2; } private static int getNextHeaderPosition(ByteBuffer audioStream, int frameSize) { int endScan = audioStream.limit() - 1; // Most common case: the header can be found at each frameSize int offset = audioStream.position() + frameSize - 8; if (offset < endScan && isHeader(audioStream, offset)) { return offset; } for (int scan = audioStream.position(); scan < endScan; scan++) { if (isHeader(audioStream, scan)) { return scan; } } return -1; } public static ByteBuffer convertStreamToOMA(ByteBuffer audioStream) { if (!isHeader(audioStream, 0)) { return null; } byte headerCode1 = audioStream.get(2); byte headerCode2 = audioStream.get(3); ByteBuffer header = getOmaHeader(OMA_CODECID_ATRAC3P, headerCode1, headerCode2); int frameSize = ((headerCode1 & 0x03) << 8) | (headerCode2 & 0xFF) * 8 + 0x10; int numCompleteFrames = audioStream.remaining() / (frameSize + 8); int lastFrameSize = audioStream.remaining() - (numCompleteFrames * (frameSize + 8)); int omaStreamSize = header.remaining() + numCompleteFrames * frameSize + lastFrameSize; // Allocate an OMA stream size large enough (better too large than too short) omaStreamSize = Math.max(omaStreamSize, audioStream.remaining()); ByteBuffer oma = ByteBuffer.allocate(omaStreamSize).order(ByteOrder.LITTLE_ENDIAN); oma.put(header); while (audioStream.remaining() > 8) { // Skip 8 bytes frame header audioStream.position(audioStream.position() + 8); int nextHeader = getNextHeaderPosition(audioStream, frameSize); ByteBuffer frame = audioStream.slice(); if (nextHeader >= 0) { frame.limit(nextHeader - audioStream.position()); audioStream.position(nextHeader); } else { audioStream.position(audioStream.limit()); } oma.put(frame); } oma.limit(oma.position()); oma.rewind(); return oma; } private static int getChunkOffset(ByteBuffer riff, int chunkMagic, int offset) { for (int i = offset; i <= riff.limit() - 4;) { if (riff.getInt(i) == chunkMagic) { return i; } // Move to next chunk int chunkSize = riff.getInt(i + 4); i += chunkSize + 8; } return -1; } public static ByteBuffer convertRIFFtoOMA(ByteBuffer riff) { final int firstChunkOffset = 12; riff.order(ByteOrder.LITTLE_ENDIAN); if (riff.getInt(0) != sceAtrac3plus.RIFF_MAGIC) { // Not a RIFF data return null; } int fmtChunkOffset = getChunkOffset(riff, sceAtrac3plus.FMT_CHUNK_MAGIC, firstChunkOffset); if (fmtChunkOffset < 0) { return null; } byte codecId = riff.get(fmtChunkOffset + 0x30); byte headerCode1 = riff.get(fmtChunkOffset + 0x32); byte headerCode2 = riff.get(fmtChunkOffset + 0x33); ByteBuffer header = getOmaHeader(codecId, headerCode1, headerCode2); int dataChunkOffset = getChunkOffset(riff, sceAtrac3plus.DATA_CHUNK_MAGIC, firstChunkOffset); if (dataChunkOffset < 0) { return null; } int dataSize = riff.getInt(dataChunkOffset + 4); ByteBuffer dataBuffer = riff.slice(); dataBuffer.position(dataChunkOffset + 8); dataBuffer.limit(dataBuffer.position() + dataSize); ByteBuffer oma = ByteBuffer.allocate(header.remaining() + dataBuffer.remaining()).order(ByteOrder.LITTLE_ENDIAN); oma.put(header); oma.put(dataBuffer); oma.rewind(); return oma; } public static int getOMANumberAudioChannels(ByteBuffer oma) { int headerParameters = oma.getInt(0x20); int channels = (headerParameters >> 18) & 0x7; return channels; } }