/*
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.format.psmf;
import static jpcsp.HLE.VFS.AbstractVirtualFileSystem.IO_ERROR;
import org.apache.log4j.Logger;
import jpcsp.Emulator;
import jpcsp.HLE.TPointer;
import jpcsp.HLE.VFS.AbstractProxyVirtualFile;
import jpcsp.HLE.VFS.IVirtualFile;
/**
* Provides a IVirtualFile interface to read only the audio from a PSMF file.
*
* @author gid15
*
*/
public class PsmfAudioDemuxVirtualFile extends AbstractProxyVirtualFile {
private static Logger log = Emulator.log;
public static final int PACKET_START_CODE_MASK = 0xffffff00;
public static final int PACKET_START_CODE_PREFIX = 0x00000100;
public static final int SEQUENCE_START_CODE = 0x000001b3;
public static final int EXT_START_CODE = 0x000001b5;
public static final int SEQUENCE_END_CODE = 0x000001b7;
public static final int GOP_START_CODE = 0x000001b8;
public static final int ISO_11172_END_CODE = 0x000001b9;
public static final int PACK_START_CODE = 0x000001ba;
public static final int SYSTEM_HEADER_START_CODE = 0x000001bb;
public static final int PROGRAM_STREAM_MAP = 0x000001bc;
public static final int PRIVATE_STREAM_1 = 0x000001bd;
public static final int PADDING_STREAM = 0x000001be;
public static final int PRIVATE_STREAM_2 = 0x000001bf;
private byte[] buffer = new byte[1];
private int audioChannel;
private long position;
private int mpegOffset;
private long startPosition;
private int remainingPacketLength;
public PsmfAudioDemuxVirtualFile(IVirtualFile vFile, int mpegOffset, int audioChannel) {
super(vFile);
this.mpegOffset = mpegOffset;
this.audioChannel = audioChannel;
startPosition = vFile.getPosition();
if (mpegOffset > 0) {
vFile.ioLseek(startPosition + mpegOffset);
} else {
this.mpegOffset = 0;
}
}
private int read8() {
if (vFile.ioRead(buffer, 0, 1) != 1) {
return -1;
}
return buffer[0] & 0xFF;
}
private int read16() {
return (read8() << 8) | read8();
}
private long readPts() {
return readPts(read8());
}
private long readPts(int c) {
return (((long) (c & 0x0E)) << 29) | ((read16() >> 1) << 15) | (read16() >> 1);
}
private void skip(int n) {
if (n > 0) {
vFile.ioLseek(vFile.getPosition() + n);
}
}
private int readPesHeader(PesHeader pesHeader, int length, int startCode) {
int c = 0;
while (length > 0) {
c = read8();
length--;
if (c != 0xFF) {
break;
}
}
if ((c & 0xC0) == 0x40) {
read8();
c = read8();
length -= 2;
}
pesHeader.setDtsPts(0);
if ((c & 0xE0) == 0x20) {
pesHeader.setDtsPts(readPts(c));
length -= 4;
if ((c & 0x10) != 0) {
pesHeader.setDts(readPts());
length -= 5;
}
} else if ((c & 0xC0) == 0x80) {
int flags = read8();
int headerLength = read8();
length -= 2;
length -= headerLength;
if ((flags & 0x80) != 0) {
pesHeader.setDtsPts(readPts());
headerLength -= 5;
if ((flags & 0x40) != 0) {
pesHeader.setDts(readPts());
headerLength -= 5;
}
}
if ((flags & 0x3F) != 0 && headerLength == 0) {
flags &= 0xC0;
}
if ((flags & 0x01) != 0) {
int pesExt = read8();
headerLength--;
int skip = (pesExt >> 4) & 0x0B;
skip += skip & 0x09;
if ((pesExt & 0x40) != 0 || skip > headerLength) {
pesExt = skip = 0;
}
skip(skip);
headerLength -= skip;
if ((pesExt & 0x01) != 0) {
int ext2Length = read8();
headerLength--;
if ((ext2Length & 0x7F) != 0) {
int idExt = read8();
headerLength--;
if ((idExt & 0x80) == 0) {
startCode = ((startCode & 0xFF) << 8) | idExt;
}
}
}
}
skip(headerLength);
}
if (startCode == PRIVATE_STREAM_1) {
int channel = read8();
pesHeader.setChannel(channel);
length--;
if (channel >= 0x80 && channel <= 0xCF) {
// Skip audio header
skip(3);
length -= 3;
if (channel >= 0xB0 && channel <= 0xBF) {
skip(1);
length--;
}
} else {
// PSP audio has additional 3 bytes in header
skip(3);
length -= 3;
}
}
return length;
}
private boolean isEOF() {
return vFile.getPosition() >= vFile.length();
}
private int doRead(TPointer outputPointer, byte[] outputBuffer, int outputOffset, int outputLength) {
if (isEOF()) {
return IO_ERROR;
}
int readLength = 0;
int readAddr = outputPointer != null ? outputPointer.getAddress() : 0;
while (remainingPacketLength > 0 && readLength < outputLength) {
int maxReadLength = Math.min(remainingPacketLength, outputLength - readLength);
int l;
if (outputBuffer != null) {
l = vFile.ioRead(outputBuffer, outputOffset, maxReadLength);
} else if (outputPointer != null) {
l = vFile.ioRead(new TPointer(outputPointer.getMemory(), readAddr), maxReadLength);
} else {
l = maxReadLength;
}
if (l > 0) {
remainingPacketLength -= l;
readLength += l;
readAddr += l;
outputOffset += l;
position += l;
} else if (l < 0) {
break;
}
}
if (remainingPacketLength > 0) {
return readLength;
}
while (!isEOF() && readLength < outputLength) {
long startIndex = vFile.getPosition();
int startCode = 0xFF;
while ((startCode & PACKET_START_CODE_MASK) != PACKET_START_CODE_PREFIX && !isEOF()) {
startCode = (startCode << 8) | read8();
}
if (log.isDebugEnabled()) {
log.debug(String.format("StartCode 0x%08X, offset %08X, skipped %d", startCode, vFile.getPosition(), vFile.getPosition() - startIndex - 4));
}
switch (startCode) {
case PACK_START_CODE: {
skip(10);
break;
}
case SYSTEM_HEADER_START_CODE: {
skip(14);
break;
}
case PADDING_STREAM:
case PRIVATE_STREAM_2: {
int length = read16();
skip(length);
break;
}
case PRIVATE_STREAM_1: {
// Audio stream
int length = read16();
PesHeader pesHeader = new PesHeader(audioChannel);
length = readPesHeader(pesHeader, length, startCode);
if (pesHeader.getChannel() == audioChannel || audioChannel < 0) {
int packetLength = 0;
while (packetLength < length && readLength < outputLength) {
int maxReadLength = Math.min(length - packetLength, outputLength - readLength);
int l;
if (outputBuffer != null) {
l = vFile.ioRead(outputBuffer, outputOffset, maxReadLength);
} else if (outputPointer != null) {
l = vFile.ioRead(new TPointer(outputPointer.getMemory(), readAddr), maxReadLength);
} else {
l = maxReadLength;
}
if (l > 0) {
readLength += l;
readAddr += l;
outputOffset += l;
packetLength += l;
position += l;
} else if (l < 0) {
break;
}
}
remainingPacketLength = length - packetLength;
} else {
skip(length);
}
break;
}
case 0x1E0: case 0x1E1: case 0x1E2: case 0x1E3:
case 0x1E4: case 0x1E5: case 0x1E6: case 0x1E7:
case 0x1E8: case 0x1E9: case 0x1EA: case 0x1EB:
case 0x1EC: case 0x1ED: case 0x1EE: case 0x1EF: {
// Video Stream, skipped
int length = read16();
skip(length);
break;
}
default: {
log.warn(String.format("Unknown StartCode 0x%08X, offset %08X", startCode, vFile.getPosition()));
}
}
}
return readLength;
}
@Override
public int ioRead(TPointer outputPointer, int outputLength) {
return doRead(outputPointer, null, 0, outputLength);
}
@Override
public int ioRead(byte[] outputBuffer, int outputOffset, int outputLength) {
return doRead(null, outputBuffer, outputOffset, outputLength);
}
@Override
public long ioLseek(long offset) {
long result = vFile.ioLseek(startPosition + mpegOffset);
if (result < 0) {
return result;
}
position = 0;
while (getPosition() < offset) {
int length = doRead(null, null, 0, offset < Integer.MAX_VALUE ? (int) offset : Integer.MAX_VALUE);
if (length < 0) {
return IO_ERROR;
}
}
return getPosition();
}
@Override
public long length() {
return super.length() - startPosition - mpegOffset;
}
@Override
public long getPosition() {
return position;
}
@Override
public IVirtualFile duplicate() {
return super.duplicate();
}
}