/*
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.SceKernelThreadInfo.PSP_WAIT_MUTEX;
import static jpcsp.HLE.modules.IoFileMgrForUser.PSP_SEEK_SET;
import static jpcsp.HLE.modules.sceAudiocodec.PSP_CODEC_AAC;
import static jpcsp.HLE.modules.sceMpeg.mpegTimestampPerSecond;
import static jpcsp.util.Utilities.alignUp;
import static jpcsp.util.Utilities.endianSwap32;
import static jpcsp.util.Utilities.getReturnValue64;
import static jpcsp.util.Utilities.readUnaligned32;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import jpcsp.Emulator;
import jpcsp.Memory;
import jpcsp.HLE.BufferInfo;
import jpcsp.HLE.CanBeNull;
import jpcsp.HLE.HLEFunction;
import jpcsp.HLE.HLELogging;
import jpcsp.HLE.HLEModule;
import jpcsp.HLE.HLEUnimplemented;
import jpcsp.HLE.TPointer;
import jpcsp.HLE.Modules;
import jpcsp.HLE.TPointer32;
import jpcsp.HLE.BufferInfo.Usage;
import jpcsp.HLE.kernel.types.IAction;
import jpcsp.HLE.kernel.types.IWaitStateChecker;
import jpcsp.HLE.kernel.types.SceKernelErrors;
import jpcsp.HLE.kernel.types.SceKernelThreadInfo;
import jpcsp.HLE.kernel.types.SceMp4SampleInfo;
import jpcsp.HLE.kernel.types.SceMp4TrackSampleBuf;
import jpcsp.HLE.kernel.types.SceMp4TrackSampleBuf.SceMp4TrackSampleBufInfo;
import jpcsp.HLE.kernel.types.SceMpegAu;
import jpcsp.HLE.kernel.types.ThreadWaitInfo;
import jpcsp.media.codec.CodecFactory;
import jpcsp.media.codec.ICodec;
import jpcsp.media.codec.h264.H264Utils;
import jpcsp.util.Utilities;
import org.apache.log4j.Logger;
public class sceMp4 extends HLEModule {
public static Logger log = Modules.getLogger("sceMp4");
protected int callbackParam;
protected int callbackGetCurrentPosition;
protected int callbackSeek;
protected int callbackRead;
protected int readBufferAddr;
protected int readBufferSize;
// Values for video track
protected int[] videoSamplesOffset;
protected int[] videoSamplesSize;
protected int[] videoSamplesDuration;
protected int[] videoSamplesPresentationOffset;
protected int[] videoSyncSamples;
protected int videoDuration;
protected int videoTimeScale;
protected long videoCurrentTimestamp;
// Values for audio track
protected int[] audioSamplesOffset;
protected int[] audioSamplesSize;
protected int[] audioSamplesDuration;
protected int[] audioSamplesPresentationOffset;
protected int[] audioSyncSamples;
protected int audioDuration;
protected int audioTimeScale;
protected long audioCurrentTimestamp;
protected int[] numberOfSamplesPerChunk;
protected int[] samplesSize;
protected long parseOffset;
protected int timeScale;
protected int duration;
protected int numberOfTracks;
protected int trackType;
protected int[] currentAtomContent;
protected int currentAtom;
protected int currentAtomSize;
protected int currentAtomOffset;
protected int trackTimeScale;
protected int trackDuration;
protected int[] videoCodecExtraData;
protected SceMp4TrackSampleBuf currentTrack;
protected TPointer currentTracAddr;
protected boolean bufferPutInProgress;
protected int bufferPutSamples;
protected int bufferPutCurrentSampleRemainingBytes;
protected int bufferPutSamplesPut;
protected SceKernelThreadInfo bufferPutThread;
protected ICodec audioCodec;
protected int audioChannels;
protected List<Integer> threadsWaitingOnBufferPut;
public static final int TRACK_TYPE_VIDEO = 0x10;
public static final int TRACK_TYPE_AUDIO = 0x20;
protected static final int SEARCH_BACKWARDS = 0;
protected static final int SEARCH_FORWARDS = 1;
protected static final int ATOM_FTYP = 0x66747970; // "ftyp"
protected static final int ATOM_MOOV = 0x6D6F6F76; // "moov"
protected static final int ATOM_TRAK = 0x7472616B; // "trak"
protected static final int ATOM_MDIA = 0x6D646961; // "mdia"
protected static final int ATOM_MINF = 0x6D696E66; // "minf"
protected static final int ATOM_STBL = 0x7374626C; // "stbl"
protected static final int ATOM_STSD = 0x73747364; // "stsd"
protected static final int ATOM_MVHD = 0x6D766864; // "mvhd"
protected static final int ATOM_STSC = 0x73747363; // "stsc"
protected static final int ATOM_STSZ = 0x7374737A; // "stsz"
protected static final int ATOM_STTS = 0x73747473; // "stts"
protected static final int ATOM_CTTS = 0x63747473; // "ctts"
protected static final int ATOM_STCO = 0x7374636F; // "stco"
protected static final int ATOM_STSS = 0x73747373; // "stss"
protected static final int ATOM_MDHD = 0x6D646864; // "mdhd"
protected static final int ATOM_AVCC = 0x61766343; // "avcC"
protected static final int FILE_TYPE_MSNV = 0x4D534E56; // "MSNV"
protected static final int FILE_TYPE_ISOM = 0x69736F6D; // "isom"
protected static final int FILE_TYPE_MP42 = 0x6D703432; // "mp42"
protected static final int DATA_FORMAT_AVC1 = 0x61766331; // "avc1"
protected static final int DATA_FORMAT_MP4A = 0x6D703461; // "mp4a"
private static boolean isContainerAtom(int atom) {
switch (atom) {
case ATOM_MOOV:
case ATOM_TRAK:
case ATOM_MDIA:
case ATOM_MINF:
case ATOM_STBL:
return true;
}
return false;
}
private static boolean isAtomContentRequired(int atom) {
switch (atom) {
case ATOM_MVHD:
case ATOM_STSD:
case ATOM_STSC:
case ATOM_STSZ:
case ATOM_STTS:
case ATOM_CTTS:
case ATOM_STCO:
case ATOM_STSS:
case ATOM_MDHD:
case ATOM_AVCC:
return true;
}
return false;
}
private static String atomToString(int atom) {
return String.format("%c%c%c%c", (char) (atom >>> 24), (char) ((atom >> 16) & 0xFF), (char) ((atom >> 8) & 0xFF), (char) (atom & 0xFF));
}
private static int read32(Memory mem, int addr) {
return endianSwap32(readUnaligned32(mem, addr));
}
private static int read32(int[] content, int o) {
return (content[o] << 24) | (content[o + 1] << 16) | (content[o + 2] << 8) | content[o + 3];
}
private static int read16(int[] content, int o) {
return (content[o] << 8) | content[o + 1];
}
private static int[] extend(int[] array, int length) {
if (length > 0) {
if (array == null) {
array = new int[length];
} else if (array.length < length) {
int[] newArray = new int[length];
System.arraycopy(array, 0, newArray, 0, array.length);
array = newArray;
}
}
return array;
}
private void processAtom(Memory mem, int addr, int atom, int size) {
int[] content = new int[size];
for (int i = 0; i < size; i++, addr++) {
content[i] = mem.read8(addr);
}
processAtom(atom, content, size);
}
private void setTrackDurationAndTimeScale() {
if (trackType == 0) {
return;
}
if (currentTrack != null && currentTracAddr != null && currentTrack.isOfType(trackType)) {
currentTrack.timeScale = trackTimeScale;
currentTrack.duration = trackDuration;
currentTrack.write(currentTracAddr);
}
switch (trackType) {
case TRACK_TYPE_VIDEO:
videoTimeScale = trackTimeScale;
videoDuration = trackDuration;
break;
case TRACK_TYPE_AUDIO:
audioTimeScale = trackTimeScale;
audioDuration = trackDuration;
break;
default:
log.error(String.format("processAtom 'mdhd' unknown track type %d", trackType));
break;
}
}
private void processAtom(int atom, int[] content, int size) {
switch (atom) {
case ATOM_MVHD:
if (size >= 20) {
timeScale = read32(content, 12);
duration = read32(content, 16);
}
break;
case ATOM_MDHD:
if (size >= 20) {
trackTimeScale = read32(content, 12);
trackDuration = read32(content, 16);
setTrackDurationAndTimeScale();
}
break;
case ATOM_STSD:
if (size >= 16) {
int dataFormat = read32(content, 12);
switch (dataFormat) {
case DATA_FORMAT_AVC1:
if (log.isDebugEnabled()) {
log.debug(String.format("trackType video %s", atomToString(dataFormat)));
}
trackType = TRACK_TYPE_VIDEO;
if (size >= 44) {
int videoFrameWidth = read16(content, 40);
int videoFrameHeight = read16(content, 42);
if (log.isDebugEnabled()) {
log.debug(String.format("Video frame size %dx%d", videoFrameWidth, videoFrameHeight));
}
Modules.sceMpegModule.setVideoFrameHeight(videoFrameHeight);
}
if (size >= 102) {
int atomAvcC = read32(content, 98);
int atomAvcCsize = read32(content, 94);
if (atomAvcC == ATOM_AVCC && atomAvcCsize <= size - 94) {
videoCodecExtraData = new int[atomAvcCsize - 8];
System.arraycopy(content, 102, videoCodecExtraData, 0, videoCodecExtraData.length);
Modules.sceMpegModule.setVideoCodecExtraData(videoCodecExtraData);
}
}
break;
case DATA_FORMAT_MP4A:
if (log.isDebugEnabled()) {
log.debug(String.format("trackType audio %s", atomToString(dataFormat)));
}
trackType = TRACK_TYPE_AUDIO;
if (size >= 34) {
audioChannels = read16(content, 32);
}
break;
default:
log.warn(String.format("Unknown track type 0x%08X(%s)", dataFormat, atomToString(dataFormat)));
break;
}
setTrackDurationAndTimeScale();
}
break;
case ATOM_STSC: {
numberOfSamplesPerChunk = null;
if (size >= 8) {
int numberOfEntries = read32(content, 4);
if (size >= numberOfEntries * 12 + 8) {
int offset = 8;
int previousChunk = 1;
int previousSamplesPerChunk = 0;
for (int i = 0; i < numberOfEntries; i++, offset += 12) {
int firstChunk = read32(content, offset);
int samplesPerChunk = read32(content, offset + 4);
numberOfSamplesPerChunk = extend(numberOfSamplesPerChunk, firstChunk);
for (int j = previousChunk; j < firstChunk; j++) {
numberOfSamplesPerChunk[j - 1] = previousSamplesPerChunk;
}
previousChunk = firstChunk;
previousSamplesPerChunk = samplesPerChunk;
}
numberOfSamplesPerChunk = extend(numberOfSamplesPerChunk, previousChunk);
numberOfSamplesPerChunk[previousChunk - 1] = previousSamplesPerChunk;
}
}
break;
}
case ATOM_STSZ: {
samplesSize = null;
if (size >= 8) {
int sampleSize = read32(content, 4);
if (sampleSize > 0) {
samplesSize = new int[1];
samplesSize[0] = sampleSize;
} else if (size >= 12) {
int numberOfEntries = read32(content, 8);
samplesSize = new int[numberOfEntries];
int offset = 12;
for (int i = 0; i < numberOfEntries; i++, offset += 4) {
samplesSize[i] = read32(content, offset);
}
}
}
switch (trackType) {
case TRACK_TYPE_VIDEO:
videoSamplesSize = samplesSize;
break;
case TRACK_TYPE_AUDIO:
audioSamplesSize = samplesSize;
break;
default:
log.error(String.format("processAtom 'stsz' unknown track type %d", trackType));
break;
}
break;
}
case ATOM_STTS: {
int[] samplesDuration = null;
if (size >= 8) {
int numberOfEntries = read32(content, 4);
int offset = 8;
int sample = 0;
for (int i = 0; i < numberOfEntries; i++, offset += 8) {
int sampleCount = read32(content, offset);
int sampleDuration = read32(content, offset + 4);
samplesDuration = extend(samplesDuration, sample + sampleCount);
Arrays.fill(samplesDuration, sample, sample + sampleCount, sampleDuration);
sample += sampleCount;
}
}
switch (trackType) {
case TRACK_TYPE_VIDEO:
videoSamplesDuration = samplesDuration;
break;
case TRACK_TYPE_AUDIO:
audioSamplesDuration = samplesDuration;
break;
default:
log.error(String.format("processAtom 'stts' unknown track type %d", trackType));
break;
}
break;
}
case ATOM_CTTS: {
int samplesPresentationOffset[] = null;
if (size >= 8) {
int numberOfEntries = read32(content, 4);
int offset = 8;
int sample = 0;
for (int i = 0; i < numberOfEntries; i++, offset += 8) {
int sampleCount = read32(content, offset);
int samplePresentationOffset = read32(content, offset + 4);
samplesPresentationOffset = extend(samplesPresentationOffset, sample + sampleCount);
Arrays.fill(samplesPresentationOffset, sample, sample + sampleCount, samplePresentationOffset);
sample += sampleCount;
}
}
switch (trackType) {
case TRACK_TYPE_VIDEO:
videoSamplesPresentationOffset = samplesPresentationOffset;
break;
case TRACK_TYPE_AUDIO:
audioSamplesPresentationOffset = samplesPresentationOffset;
break;
default:
log.error(String.format("processAtom 'ctts' unknown track type %d", trackType));
break;
}
break;
}
case ATOM_STCO: {
int[] chunksOffset = null;
if (size >= 8) {
int numberOfEntries = read32(content, 4);
chunksOffset = extend(chunksOffset, numberOfEntries);
int offset = 8;
for (int i = 0; i < numberOfEntries; i++, offset += 4) {
chunksOffset[i] = read32(content, offset);
}
}
int[] samplesOffset = null;
if (numberOfSamplesPerChunk != null && samplesSize != null && chunksOffset != null) {
// numberOfSamplesPerChunk could be shorter if the last chunks all have the same length.
// Extend numberOfSamplesPerChunk by repeating the size of the last chunk.
int compactedChunksLength = numberOfSamplesPerChunk.length;
numberOfSamplesPerChunk = extend(numberOfSamplesPerChunk, chunksOffset.length);
Arrays.fill(numberOfSamplesPerChunk, compactedChunksLength, chunksOffset.length, numberOfSamplesPerChunk[compactedChunksLength - 1]);
// Compute the total number of samples
int numberOfSamples = 0;
for (int i = 0; i < numberOfSamplesPerChunk.length; i++) {
numberOfSamples += numberOfSamplesPerChunk[i];
}
// samplesSize could be shorter than the number of samples.
// Extend samplesSize by repeating the size of the last sample.
int compactedSamplesLength = samplesSize.length;
samplesSize = extend(samplesSize, numberOfSamples);
Arrays.fill(samplesSize, compactedSamplesLength, numberOfSamples, samplesSize[compactedSamplesLength - 1]);
samplesOffset = new int[numberOfSamples];
int sample = 0;
for (int i = 0; i < chunksOffset.length; i++) {
int offset = chunksOffset[i];
for (int j = 0; j < numberOfSamplesPerChunk[i]; j++, sample++) {
samplesOffset[sample] = offset;
offset += samplesSize[sample];
}
}
if (log.isTraceEnabled()) {
for (int i = 0; i < samplesOffset.length; i++) {
log.trace(String.format("Sample#%d offset=0x%X, size=0x%X", i, samplesOffset[i], samplesSize[i]));
}
}
if (currentTrack != null && currentTracAddr != null && currentTrack.isOfType(trackType)) {
currentTrack.totalNumberSamples = numberOfSamples;
currentTrack.write(currentTracAddr);
}
}
switch (trackType) {
case TRACK_TYPE_VIDEO:
videoSamplesOffset = samplesOffset;
break;
case TRACK_TYPE_AUDIO:
audioSamplesOffset = samplesOffset;
break;
default:
log.error(String.format("processAtom 'stco' unknown track type %d", trackType));
break;
}
break;
}
case ATOM_STSS: {
int[] syncSamples = null;
if (size >= 8) {
int numberOfEntries = read32(content, 4);
syncSamples = new int[numberOfEntries];
int offset = 8;
for (int i = 0; i < numberOfEntries; i++, offset += 4) {
syncSamples[i] = read32(content, offset) - 1; // Sync samples are numbered starting at 1
if (log.isTraceEnabled()) {
log.trace(String.format("Sync sample#%d=0x%X", i, syncSamples[i]));
}
}
}
switch (trackType) {
case TRACK_TYPE_VIDEO:
videoSyncSamples = syncSamples;
break;
case TRACK_TYPE_AUDIO:
audioSyncSamples = syncSamples;
break;
default:
log.error(String.format("processAtom 'stss' unknown track type %d", trackType));
break;
}
break;
}
}
}
private void processAtom(int atom) {
switch (atom) {
case ATOM_TRAK:
// We start a new track.
trackType = 0;
numberOfSamplesPerChunk = null;
samplesSize = null;
numberOfTracks++;
break;
}
}
private void addCurrentAtomContent(Memory mem, int addr, int size) {
for (int i = 0; i < size; i++) {
currentAtomContent[currentAtomOffset++] = mem.read8(addr++);
}
}
private void parseAtoms(Memory mem, int addr, int size) {
int offset = 0;
if (currentAtom != 0) {
int length = Math.min(size, currentAtomSize - currentAtomOffset);
addCurrentAtomContent(mem, addr, length);
offset += length;
if (currentAtomOffset >= currentAtomSize) {
processAtom(currentAtom, currentAtomContent, currentAtomSize);
currentAtom = 0;
currentAtomContent = null;
}
}
while (offset + 8 <= size) {
int atomSize = read32(mem, addr + offset);
int atom = read32(mem, addr + offset + 4);
if (log.isDebugEnabled()) {
log.debug(String.format("parseAtoms atom=0x%08X(%s), size=0x%X, offset=0x%X", atom, atomToString(atom), atomSize, parseOffset + offset));
}
if (atomSize <= 0) {
break;
}
if (isAtomContentRequired(atom)) {
if (offset + atomSize <= size) {
processAtom(mem, addr + offset + 8, atom, atomSize - 8);
} else {
currentAtom = atom;
currentAtomSize = atomSize - 8;
currentAtomOffset = 0;
currentAtomContent = new int[currentAtomSize];
addCurrentAtomContent(mem, addr + offset + 8, size - offset - 8);
atomSize = size - offset;
}
} else {
// Process an atom without content
processAtom(atom);
}
if (isContainerAtom(atom)) {
offset += 8;
} else {
offset += atomSize;
}
}
parseOffset += offset;
}
private class AfterReadHeadersRead implements IAction {
@Override
public void execute() {
afterReadHeadersRead(Emulator.getProcessor().cpu._v0);
}
}
private class AfterReadHeadersSeek implements IAction {
@Override
public void execute() {
afterReadHeadersSeek(getReturnValue64(Emulator.getProcessor().cpu));
}
}
private void afterReadHeadersRead(int readSize) {
if (log.isTraceEnabled()) {
log.trace(String.format("afterReadHeadersRead: %s", Utilities.getMemoryDump(readBufferAddr, readSize)));
}
Memory mem = Memory.getInstance();
if (parseOffset == 0L && readSize >= 12) {
int header1 = read32(mem, readBufferAddr);
int header2 = read32(mem, readBufferAddr + 4);
int header3 = read32(mem, readBufferAddr + 8);
if (header1 < 12 || header2 != ATOM_FTYP || (header3 != FILE_TYPE_MSNV && header3 != FILE_TYPE_ISOM && header3 != FILE_TYPE_MP42)) {
log.warn(String.format("Invalid MP4 file header 0x%08X 0x%08X 0x%08X: %s", header1, header2, header3, Utilities.getMemoryDump(readBufferAddr, Math.min(16, readSize))));
readSize = 0;
}
}
parseAtoms(mem, readBufferAddr, readSize);
// Continue reading?
if (readSize > 0) {
// Seek to the next atom
callSeekCallback(null, new AfterReadHeadersSeek(), parseOffset, PSP_SEEK_SET);
} else {
if (log.isTraceEnabled() && currentTrack != null) {
log.trace(String.format("afterReadHeadersRead updated track %s", currentTrack));
}
currentTrack = null;
currentTracAddr = null;
Modules.sceMpegModule.setVideoFrameSizes(videoSamplesSize);
}
}
private void afterReadHeadersSeek(long seek) {
if (log.isDebugEnabled()) {
log.debug(String.format("afterReadHeadersSeek seek=0x%X", seek));
}
callReadCallback(null, new AfterReadHeadersRead(), readBufferAddr, readBufferSize);
}
protected void readHeaders(SceMp4TrackSampleBuf track, TPointer trackAddr) {
if (videoSamplesOffset != null && videoSamplesSize != null) {
return;
}
parseOffset = 0L;
duration = 0;
currentAtom = 0;
numberOfTracks = 0;
currentTrack = track;
currentTracAddr = trackAddr;
// Start reading all the atoms.
// First seek to the beginning of the file.
callSeekCallback(null, new AfterReadHeadersSeek(), parseOffset, PSP_SEEK_SET);
}
protected int getSampleOffset(int sample) {
return getSampleOffset(currentTrack.trackType, sample);
}
protected int getSampleOffset(int trackType, int sample) {
if ((trackType & TRACK_TYPE_AUDIO) != 0) {
if (audioSamplesOffset == null || sample < 0 || sample >= audioSamplesOffset.length) {
return -1;
}
return audioSamplesOffset[sample];
}
if ((trackType & TRACK_TYPE_VIDEO) != 0) {
if (videoSamplesOffset == null || sample < 0 || sample >= videoSamplesOffset.length) {
return -1;
}
return videoSamplesOffset[sample];
}
log.error(String.format("getSampleOffset unknown trackType=0x%X", trackType));
return -1;
}
protected int getSampleSize(int sample) {
return getSampleSize(currentTrack.trackType, sample);
}
protected int getSampleSize(int trackType, int sample) {
if ((trackType & TRACK_TYPE_AUDIO) != 0) {
if (audioSamplesSize == null || sample < 0 || sample >= audioSamplesSize.length) {
return -1;
}
return audioSamplesSize[sample];
}
if ((trackType & TRACK_TYPE_VIDEO) != 0) {
if (videoSamplesSize == null || sample < 0 || sample >= videoSamplesSize.length) {
return -1;
}
return videoSamplesSize[sample];
}
log.error(String.format("getSampleSize unknown trackType=0x%X", trackType));
return -1;
}
protected int getSampleDuration(int sample) {
return getSampleDuration(currentTrack.trackType, sample);
}
protected int getSampleDuration(int trackType, int sample) {
if ((trackType & TRACK_TYPE_AUDIO) != 0) {
if (audioSamplesDuration == null || sample < 0 || sample >= audioSamplesDuration.length) {
return -1;
}
return audioSamplesDuration[sample];
}
if ((trackType & TRACK_TYPE_VIDEO) != 0) {
if (videoSamplesDuration == null || sample < 0 || sample >= videoSamplesDuration.length) {
return -1;
}
return videoSamplesDuration[sample];
}
log.error(String.format("getSampleDuration unknown trackType=0x%X", trackType));
return -1;
}
protected int getSamplePresentationOffset(int sample) {
return getSamplePresentationOffset(currentTrack.trackType, sample);
}
protected int getSamplePresentationOffset(int trackType, int sample) {
if ((trackType & TRACK_TYPE_AUDIO) != 0) {
if (audioSamplesPresentationOffset == null || sample < 0 || sample >= audioSamplesPresentationOffset.length) {
return 0;
}
return audioSamplesPresentationOffset[sample];
}
if ((trackType & TRACK_TYPE_VIDEO) != 0) {
if (videoSamplesPresentationOffset == null || sample < 0 || sample >= videoSamplesPresentationOffset.length) {
return 0;
}
return videoSamplesPresentationOffset[sample];
}
log.error(String.format("getSamplePresentationOffset unknown trackType=0x%X", trackType));
return 0;
}
private class AfterBufferPutSeek implements IAction {
@Override
public void execute() {
afterBufferPutSeek(getReturnValue64(Emulator.getProcessor().cpu));
}
}
private class AfterBufferPutRead implements IAction {
@Override
public void execute() {
afterBufferPutRead(Emulator.getProcessor().cpu._v0);
}
}
private void afterBufferPutSeek(long seek) {
currentTrack.currentFileOffset = seek;
callReadCallback(bufferPutThread, new AfterBufferPutRead(), currentTrack.readBufferAddr, currentTrack.readBufferSize);
}
private void afterBufferPutRead(int size) {
currentTrack.sizeAvailableInReadBuffer = size;
bufferPut();
}
private void bufferPut(long seek) {
// PSP is always reading in multiples of readBufferSize
seek = Utilities.alignDown(seek, currentTrack.readBufferSize - 1);
callSeekCallback(bufferPutThread, new AfterBufferPutSeek(), seek, PSP_SEEK_SET);
}
private void addBytesToTrack(Memory mem, int addr, int length) {
if (log.isTraceEnabled()) {
log.trace(String.format("addBytesToTrack addr=0x%08X, length=0x%X: %s", addr, length, Utilities.getMemoryDump(addr, length)));
}
currentTrack.addBytesToTrack(addr, length);
if (currentTrack.isOfType(TRACK_TYPE_VIDEO)) {
Modules.sceMpegModule.addToVideoBuffer(mem, addr, length);
}
}
private void bufferPut() {
Memory mem = Memory.getInstance();
while (bufferPutSamples > 0) {
if (log.isTraceEnabled()) {
log.trace(String.format("bufferPut samples=0x%X, remainingBytes=0x%X, currentTrack=%s", bufferPutSamples, bufferPutCurrentSampleRemainingBytes, currentTrack));
}
if (bufferPutCurrentSampleRemainingBytes > 0) {
int length = Math.min(currentTrack.readBufferSize, bufferPutCurrentSampleRemainingBytes);
addBytesToTrack(mem, currentTrack.readBufferAddr, length);
bufferPutCurrentSampleRemainingBytes -= length;
if (bufferPutCurrentSampleRemainingBytes > 0) {
bufferPut(currentTrack.currentFileOffset + currentTrack.readBufferSize);
break;
}
currentTrack.addSamplesToTrack(1);
bufferPutSamples--;
bufferPutSamplesPut++;
} else {
// Read one sample
int sample = currentTrack.currentSample;
int sampleOffset = getSampleOffset(sample);
int sampleSize = getSampleSize(sample);
if (log.isTraceEnabled()) {
log.trace(String.format("bufferPut sample=0x%X, offset=0x%X, size=0x%X, currentFilePosition=0x%X, readSize=0x%X", sample, sampleOffset, sampleSize, currentTrack.currentFileOffset, currentTrack.sizeAvailableInReadBuffer));
}
if (sampleOffset < 0) {
if (log.isTraceEnabled()) {
log.trace(String.format("bufferPut reached last frame at sample 0x%X, stopping", sample));
}
bufferPutSamples = 0;
break;
}
if (sampleSize > currentTrack.bufBytes.getWritableSpace()) {
if (log.isTraceEnabled()) {
log.trace(String.format("bufferPut bufBytes full (remaining 0x%X bytes, sample size=0x%X), stopping", currentTrack.bufBytes.getWritableSpace(), sampleSize));
}
bufferPutSamples = 0;
break;
}
if (currentTrack.isInReadBuffer(sampleOffset)) {
int sampleReadBufferOffset = (int) (sampleOffset - currentTrack.currentFileOffset);
int sampleAddr = currentTrack.readBufferAddr + sampleReadBufferOffset;
if (currentTrack.isInReadBuffer(sampleOffset + sampleSize)) {
// Sample completely available in the read buffer
addBytesToTrack(mem, sampleAddr, sampleSize);
currentTrack.addSamplesToTrack(1);
bufferPutSamples--;
bufferPutSamplesPut++;
} else {
// Sample partially available in the read buffer
int availableSampleLength = currentTrack.sizeAvailableInReadBuffer - sampleReadBufferOffset;
addBytesToTrack(mem, sampleAddr, availableSampleLength);
bufferPutCurrentSampleRemainingBytes = sampleSize - availableSampleLength;
bufferPut(currentTrack.currentFileOffset + currentTrack.readBufferSize);
break;
}
} else {
bufferPut(sampleOffset);
break;
}
}
}
if (bufferPutSamples <= 0 && bufferPutInProgress) {
// sceMp4TrackSampleBufPut is now completed, write the current track back to memory...
currentTrack.write(currentTracAddr);
// ... and return the number of samples put
if (log.isTraceEnabled()) {
log.trace(String.format("bufferPut returning 0x%X for thread %s", bufferPutSamplesPut, bufferPutThread));
}
bufferPutThread.cpuContext._v0 = bufferPutSamplesPut;
Modules.sceMpegModule.hleMpegNotifyVideoDecoderThread();
bufferPutInProgress = false;
if (!threadsWaitingOnBufferPut.isEmpty()) {
int threadUid = threadsWaitingOnBufferPut.remove(0).intValue();
if (log.isTraceEnabled()) {
log.trace(String.format("bufferPut unblocking thread %s", Modules.ThreadManForUserModule.getThreadById(threadUid)));
}
Modules.ThreadManForUserModule.hleUnblockThread(threadUid);
}
}
}
private class StartBufferPut implements IAction {
private SceMp4TrackSampleBuf track;
private TPointer trackAddr;
private int samples;
private SceKernelThreadInfo thread;
public StartBufferPut(SceMp4TrackSampleBuf track, TPointer trackAddr, int samples, SceKernelThreadInfo thread) {
this.track = track;
this.trackAddr = trackAddr;
this.samples = samples;
this.thread = thread;
}
@Override
public void execute() {
bufferPut(thread, track, trackAddr, samples);
}
}
private class BufferPutUnblock implements IAction {
private SceMp4TrackSampleBuf track;
private TPointer trackAddr;
private int samples;
private SceKernelThreadInfo thread;
public BufferPutUnblock(SceMp4TrackSampleBuf track, TPointer trackAddr, int samples, SceKernelThreadInfo thread) {
this.track = track;
this.trackAddr = trackAddr;
this.samples = samples;
this.thread = thread;
}
@Override
public void execute() {
// Start bufferPut in the thread context when it will be scheduled
Modules.ThreadManForUserModule.pushActionForThread(thread, new StartBufferPut(track, trackAddr, samples, thread));
}
}
private class BufferPutWaitStateChecker implements IWaitStateChecker {
@Override
public boolean continueWaitState(SceKernelThreadInfo thread, ThreadWaitInfo wait) {
if (log.isTraceEnabled()) {
log.trace(String.format("BufferPutWaitStateChecker.continueWaitState for thread %s returning %b", thread, threadsWaitingOnBufferPut.contains(thread.uid)));
}
if (threadsWaitingOnBufferPut.contains(thread.uid)) {
return true;
}
return false;
}
}
protected void bufferPut(SceKernelThreadInfo thread, SceMp4TrackSampleBuf track, TPointer trackAddr, int samples) {
if (bufferPutInProgress) {
if (log.isTraceEnabled()) {
log.trace(String.format("bufferPut blocking thread %s", thread));
}
BufferPutUnblock bufferPutUnblock = new BufferPutUnblock(track, trackAddr, samples, thread);
threadsWaitingOnBufferPut.add(thread.uid);
Modules.ThreadManForUserModule.hleBlockThread(thread, PSP_WAIT_MUTEX, 0, false, bufferPutUnblock, new BufferPutWaitStateChecker());
} else {
if (log.isTraceEnabled()) {
log.trace(String.format("bufferPut starting samples=0x%X, thread=%s", samples, thread));
}
bufferPutInProgress = true;
currentTrack = track;
currentTracAddr = trackAddr;
bufferPutSamples = samples;
bufferPutCurrentSampleRemainingBytes = 0;
bufferPutSamplesPut = 0;
bufferPutThread = thread;
bufferPut();
}
}
protected void callReadCallback(SceKernelThreadInfo thread, IAction afterAction, int readAddr, int readBytes) {
if (log.isDebugEnabled()) {
log.debug(String.format("callReadCallback readAddr=0x%08X, readBytes=0x%X", readAddr, readBytes));
}
Modules.ThreadManForUserModule.executeCallback(thread, callbackRead, afterAction, false, callbackParam, readAddr, readBytes);
}
protected void callGetCurrentPositionCallback(SceKernelThreadInfo thread, IAction afterAction) {
if (log.isDebugEnabled()) {
log.debug(String.format("callGetCurrentPositionCallback"));
}
Modules.ThreadManForUserModule.executeCallback(thread, callbackGetCurrentPosition, afterAction, false, callbackParam);
}
protected void callSeekCallback(SceKernelThreadInfo thread, IAction afterAction, long offset, int whence) {
if (log.isDebugEnabled()) {
log.debug(String.format("callSeekCallback offset=0x%X, whence=%s", offset, IoFileMgrForUser.getWhenceName(whence)));
}
Modules.ThreadManForUserModule.executeCallback(thread, callbackSeek, afterAction, false, callbackParam, 0, (int) (offset & 0xFFFFFFFF), (int) (offset >>> 32), whence);
}
protected void hleMp4Init() {
readBufferAddr = 0;
readBufferSize = 0;
videoSamplesOffset = null;
videoSamplesSize = null;
videoSamplesDuration = null;
videoSamplesPresentationOffset = null;
videoCurrentTimestamp = 0L;
audioSamplesOffset = null;
audioSamplesSize = null;
audioSamplesDuration = null;
audioSamplesPresentationOffset = null;
audioCurrentTimestamp = 0L;
trackType = 0;
threadsWaitingOnBufferPut = new LinkedList<Integer>();
bufferPutInProgress = false;
// TODO MP4 videos seem to decode with no alpha... or does it depend on the movie data?
H264Utils.setAlpha(0x00);
}
protected void readCallbacks(TPointer32 callbacks) {
callbackParam = callbacks.getValue(0);
callbackGetCurrentPosition = callbacks.getValue(4);
callbackSeek = callbacks.getValue(8);
callbackRead = callbacks.getValue(12);
if (log.isDebugEnabled()) {
log.debug(String.format("sceMp4 callbacks: param=0x%08X, getCurrentPosition=0x%08X, seek=0x%08X, read=0x%08X", callbackParam, callbackGetCurrentPosition, callbackSeek, callbackRead));
}
}
protected long sampleToFrameDuration(long sampleDuration, SceMp4TrackSampleBuf track) {
return sampleToFrameDuration(sampleDuration, track.timeScale);
}
protected long sampleToFrameDuration(long sampleDuration, int timeScale) {
if (timeScale == 0) {
return sampleDuration;
}
return sampleDuration * mpegTimestampPerSecond / timeScale;
}
protected long getTotalFrameDuration(SceMp4TrackSampleBuf track) {
long totalSampleDuration = 0L;
for (int sample = 0; sample < track.totalNumberSamples; sample++) {
int sampleDuration = getSampleDuration(track.trackType, sample);
totalSampleDuration += sampleDuration;
}
long totalFrameDuration = sampleToFrameDuration(totalSampleDuration, track);
return totalFrameDuration;
}
@HLEFunction(nid = 0x68651CBC, version = 150, checkInsideInterrupt = true)
public int sceMp4Init(boolean unk1, boolean unk2) {
hleMp4Init();
return 0;
}
@HLEFunction(nid = 0x9042B257, version = 150, checkInsideInterrupt = true)
public int sceMp4Finish() {
videoSamplesOffset = null;
videoSamplesSize = null;
audioSamplesOffset = null;
audioSamplesSize = null;
currentTrack = null;
currentTracAddr = null;
return 0;
}
@HLELogging(level="info")
@HLEFunction(nid = 0xB1221EE7, version = 150, checkInsideInterrupt = true)
public int sceMp4Create(int mp4, TPointer32 callbacks, TPointer readBufferAddr, int readBufferSize) {
this.readBufferAddr = readBufferAddr.getAddress();
this.readBufferSize = readBufferSize;
Modules.sceMpegModule.hleCreateRingbuffer();
readCallbacks(callbacks);
readHeaders(null, null);
return 0;
}
@HLEFunction(nid = 0x538C2057, version = 150)
public int sceMp4Delete() {
// Reset default alpha
H264Utils.setAlpha(0xFF);
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x113E9E7B, version = 150)
public int sceMp4GetNumberOfMetaData(int mp4) {
return 0;
}
@HLEFunction(nid = 0x7443AF1D, version = 150)
public int sceMp4GetMovieInfo(int mp4, @CanBeNull TPointer32 movieInfo) {
movieInfo.setValue(0, numberOfTracks);
movieInfo.setValue(4, 0); // Always 0
movieInfo.setValue(8, (int) sampleToFrameDuration(duration, timeScale));
if (log.isDebugEnabled()) {
log.debug(String.format("sceMp4GetMovieInfo returning numberOfTracks=%d, duration=0x%X", movieInfo.getValue(0), movieInfo.getValue(8)));
}
return 0;
}
@HLEFunction(nid = 0x5EB65F26, version = 150)
public int sceMp4GetNumberOfSpecificTrack(int mp4, int trackType) {
if ((trackType & TRACK_TYPE_VIDEO) != 0) {
return videoSamplesOffset != null ? 1 : 0;
}
if ((trackType & TRACK_TYPE_AUDIO) != 0) {
return audioSamplesOffset != null ? 1 : 0;
}
log.warn(String.format("sceMp4GetNumberOfSpecificTrack unknown trackType=%X", trackType));
return 0;
}
@HLEFunction(nid = 0x7ADFD01C, version = 150)
public int sceMp4RegistTrack(int mp4, int trackType, int unknown, TPointer32 callbacks, TPointer trackAddr) {
SceMp4TrackSampleBuf track = new SceMp4TrackSampleBuf();
track.read(trackAddr);
track.currentSample = 0;
track.trackType = trackType;
if ((trackType & TRACK_TYPE_VIDEO) != 0) {
if (log.isDebugEnabled()) {
log.debug(String.format("sceMp4RegistTrack TRACK_TYPE_VIDEO"));
}
track.timeScale = videoTimeScale;
track.duration = videoDuration;
track.totalNumberSamples = videoSamplesSize != null ? videoSamplesSize.length : 0;
}
if ((trackType & TRACK_TYPE_AUDIO) != 0) {
if (log.isDebugEnabled()) {
log.debug(String.format("sceMp4RegistTrack TRACK_TYPE_AUDIO"));
}
track.timeScale = audioTimeScale;
track.duration = audioDuration;
track.totalNumberSamples = audioSamplesSize != null ? audioSamplesSize.length : 0;
}
readCallbacks(callbacks);
track.write(trackAddr);
if (log.isTraceEnabled()) {
log.trace(String.format("sceMp4RegistTrack track %s", track));
}
readHeaders(track, trackAddr);
return 0;
}
@HLEFunction(nid = 0xBCA9389C, version = 150)
public int sceMp4TrackSampleBufQueryMemSize(int trackType, int numSamples, int sampleSize, int unknown, int readBufferSize) {
int value = Math.max(numSamples * sampleSize, unknown << 1) + (numSamples << 6) + readBufferSize + 256;
if (log.isDebugEnabled()) {
log.debug(String.format("sceMp4TrackSampleBufQueryMemSize returning 0x%X", value));
}
return value;
}
@HLEFunction(nid = 0x9C8F4FC1, version = 150)
public int sceMp4TrackSampleBufConstruct(int mp4, TPointer trackAddr, TPointer buffer, int sampleBufQueyMemSize, int numSamples, int sampleSize, int unknown, int readBufferSize) {
// sampleBufQueyMemSize is the value returned by sceMp4TrackSampleBufQueryMemSize
SceMp4TrackSampleBuf track = new SceMp4TrackSampleBuf();
track.read(trackAddr);
track.mp4 = mp4;
track.baseBufferAddr = buffer.getAddress();
track.samplesPut = numSamples;
track.sampleSize = sampleSize;
track.unknown = unknown;
track.bytesBufferAddr = alignUp(buffer.getAddress(), 63) + (numSamples << 6);
track.bytesBufferLength = Math.max(numSamples * sampleSize, unknown << 1);
track.readBufferSize = readBufferSize;
track.readBufferAddr = track.bytesBufferAddr + track.bytesBufferLength + 48;
track.currentFileOffset = -1;
track.sizeAvailableInReadBuffer = 0;
track.bufBytes = new SceMp4TrackSampleBufInfo();
track.bufBytes.totalSize = track.bytesBufferLength;
track.bufBytes.readOffset = 0;
track.bufBytes.writeOffset = 0;
track.bufBytes.sizeAvailableForRead = 0;
track.bufBytes.unknown16 = 1;
track.bufBytes.bufferAddr = track.bytesBufferAddr;
track.bufBytes.callback24 = 0;
track.bufBytes.unknown28 = trackAddr.getAddress() + 184;
track.bufBytes.unknown36 = mp4;
track.bufSamples = new SceMp4TrackSampleBufInfo();
track.bufSamples.totalSize = numSamples;
track.bufSamples.readOffset = 0;
track.bufSamples.writeOffset = 0;
track.bufSamples.sizeAvailableForRead = 0;
track.bufSamples.unknown16 = 64;
track.bufSamples.bufferAddr = alignUp(buffer.getAddress(), 63);
track.bufSamples.callback24 = 0;
track.bufSamples.unknown28 = 0;
track.bufSamples.unknown36 = mp4;
track.write(trackAddr);
if (log.isTraceEnabled()) {
log.trace(String.format("sceMp4TrackSampleBufConstruct track %s", track));
}
return 0;
}
@HLEFunction(nid = 0x0F0187D2, version = 150)
public int sceMp4GetAvcTrackInfoData(int mp4, TPointer trackAddr, @CanBeNull TPointer32 infoAddr) {
SceMp4TrackSampleBuf track = new SceMp4TrackSampleBuf();
track.read(trackAddr);
if (log.isTraceEnabled()) {
log.trace(String.format("sceMp4GetAvcTrackInfoData track %s", track));
}
long totalFrameDuration = getTotalFrameDuration(track);
// Returning 3 32-bit values in infoAddr
infoAddr.setValue(0, 0); // Always 0
infoAddr.setValue(4, (int) totalFrameDuration);
infoAddr.setValue(8, track.totalNumberSamples);
if (log.isTraceEnabled()) {
log.trace(String.format("sceMp4GetAvcTrackInfoData returning info:%s", Utilities.getMemoryDump(infoAddr.getAddress(), 12)));
}
return 0;
}
@HLEFunction(nid = 0x9CE6F5CF, version = 150)
public int sceMp4GetAacTrackInfoData(int mp4, TPointer trackAddr, @CanBeNull TPointer32 infoAddr) {
SceMp4TrackSampleBuf track = new SceMp4TrackSampleBuf();
track.read(trackAddr);
if (log.isTraceEnabled()) {
log.trace(String.format("sceMp4GetAacTrackInfoData track %s", track));
}
long totalFrameDuration = getTotalFrameDuration(track);
// Returning 5 32-bit values in infoAddr
infoAddr.setValue(0, 0); // Always 0
infoAddr.setValue(4, (int) totalFrameDuration);
infoAddr.setValue(8, track.totalNumberSamples);
infoAddr.setValue(12, track.timeScale);
infoAddr.setValue(16, audioChannels);
if (log.isTraceEnabled()) {
log.trace(String.format("sceMp4GetAacTrackInfoData returning info:%s", Utilities.getMemoryDump(infoAddr.getAddress(), 20)));
}
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x4ED4AB1E, version = 150)
public int sceMp4AacDecodeInitResource(int unknown) {
return 0;
}
@HLEFunction(nid = 0x10EE0D2C, version = 150)
public int sceMp4AacDecodeInit(TPointer32 aac) {
aac.setValue(0); // Always 0?
audioCodec = CodecFactory.getCodec(PSP_CODEC_AAC);
int channels = 2; // Always stereo?
audioCodec.init(0, channels, channels, 0);
return 0;
}
@HLEFunction(nid = 0x496E8A65, version = 150)
public int sceMp4TrackSampleBufFlush(int mp4, TPointer trackAddr) {
SceMp4TrackSampleBuf track = new SceMp4TrackSampleBuf();
track.read(trackAddr);
track.bufBytes.flush();
track.bufSamples.flush();
track.write(trackAddr);
if (track.isOfType(TRACK_TYPE_VIDEO)) {
Modules.sceMpegModule.flushVideoFrameData();
}
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0xB4B400D1, version = 150)
public int sceMp4GetSampleNumWithTimeStamp(int mp4, TPointer trackAddr, TPointer32 timestampAddr) {
SceMp4TrackSampleBuf track = new SceMp4TrackSampleBuf();
track.read(trackAddr);
// Only value at offset 4 is used
int timestamp = timestampAddr.getValue(4);
if (log.isTraceEnabled()) {
log.trace(String.format("sceMp4GetSampleNumWithTimeStamp timestamp=0x%X, track %s", timestamp, track));
}
int sample = track.currentSample;
return sample;
}
@HLEFunction(nid = 0xF7C51EC1, version = 150)
public int sceMp4GetSampleInfo(int mp4, TPointer trackAddr, int sample, TPointer infoAddr) {
SceMp4TrackSampleBuf track = new SceMp4TrackSampleBuf();
track.read(trackAddr);
if (log.isTraceEnabled()) {
log.trace(String.format("sceMp4GetSampleInfo track %s", track));
}
if (sample == -1) {
sample = track.currentSample;
}
SceMp4SampleInfo info = new SceMp4SampleInfo();
int sampleDuration = getSampleDuration(track.trackType, sample);
long frameDuration = sampleToFrameDuration(sampleDuration, track);
info.sample = sample;
info.sampleOffset = getSampleOffset(track.trackType, sample);
info.sampleSize = getSampleSize(track.trackType, sample);
info.unknown1 = 0;
info.frameDuration = (int) frameDuration;
info.unknown2 = 0;
info.timestamp1 = sample * info.frameDuration;
info.timestamp2 = sample * info.frameDuration;
info.write(infoAddr);
if (log.isTraceEnabled()) {
log.trace(String.format("sceMp4GetSampleInfo returning info=%s", info));
}
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x74A1CA3E, version = 150)
public int sceMp4SearchSyncSampleNum(int mp4, TPointer trackAddr, int searchDirection, int sample) {
if (searchDirection != SEARCH_BACKWARDS && searchDirection != SEARCH_FORWARDS) {
return SceKernelErrors.ERROR_MP4_INVALID_VALUE;
}
SceMp4TrackSampleBuf track = new SceMp4TrackSampleBuf();
track.read(trackAddr);
if (log.isTraceEnabled()) {
log.trace(String.format("sceMp4SearchSyncSampleNum track %s", track));
}
int[] syncSamples;
if (track.isOfType(TRACK_TYPE_AUDIO)) {
syncSamples = audioSyncSamples;
} else if (track.isOfType(TRACK_TYPE_VIDEO)) {
syncSamples = videoSyncSamples;
} else {
log.error(String.format("sceMp4SearchSyncSampleNum unknown track type 0x%X", track.trackType));
return -1;
}
int syncSample = 0;
if (syncSamples != null) {
for (int i = 0; i < syncSamples.length; i++) {
if (sample > syncSamples[i]) {
syncSample = syncSamples[i];
} else if (sample == syncSamples[i] && searchDirection == SEARCH_FORWARDS) {
syncSample = syncSamples[i];
} else {
if (searchDirection == SEARCH_FORWARDS) {
syncSample = syncSamples[i];
}
break;
}
}
}
if (log.isDebugEnabled()) {
log.debug(String.format("sceMp4SearchSyncSampleNum returning 0x%X", syncSample));
}
return syncSample;
}
@HLEFunction(nid = 0xD8250B75, version = 150)
public int sceMp4PutSampleNum(int mp4, TPointer trackAddr, int sample) {
SceMp4TrackSampleBuf track = new SceMp4TrackSampleBuf();
track.read(trackAddr);
if (log.isTraceEnabled()) {
log.trace(String.format("sceMp4PutSampleNum track %s", track));
}
if (sample < 0 || sample >= track.totalNumberSamples) {
return SceKernelErrors.ERROR_MP4_INVALID_SAMPLE_NUMBER;
}
track.currentSample = sample;
track.write(trackAddr);
if (track.isOfType(TRACK_TYPE_VIDEO)) {
Modules.sceMpegModule.setVideoFrame(sample);
}
return 0;
}
/**
* Similar to sceMpegRingbufferAvailableSize.
*
* @param mp4
* @param trackAddr
* @param writableSamplesAddr
* @param writableBytesAddr
* @return
*/
@HLEFunction(nid = 0x8754ECB8, version = 150)
public int sceMp4TrackSampleBufAvailableSize(int mp4, TPointer trackAddr, @CanBeNull TPointer32 writableSamplesAddr, @CanBeNull TPointer32 writableBytesAddr) {
SceMp4TrackSampleBuf track = new SceMp4TrackSampleBuf();
track.read(trackAddr);
writableSamplesAddr.setValue(track.bufSamples.getWritableSpace());
writableBytesAddr.setValue(track.bufBytes.getWritableSpace());
int result = 0;
if (writableSamplesAddr.getValue() < 0 || writableBytesAddr.getValue() < 0) {
result = SceKernelErrors.ERROR_MP4_NO_AVAILABLE_SIZE;
}
if (log.isDebugEnabled()) {
log.debug(String.format("sceMp4TrackSampleBufAvailableSize returning writableSamples=0x%X, writableBytes=0x%X, result=0x%08X", writableSamplesAddr.getValue(), writableBytesAddr.getValue(), result));
}
return result;
}
/**
* Similar to sceMpegRingbufferPut.
*
* @param mp4
* @param track
* @param samples
* @return
*/
@HLEFunction(nid = 0x31BCD7E0, version = 150)
public int sceMp4TrackSampleBufPut(int mp4, TPointer trackAddr, int samples) {
SceMp4TrackSampleBuf track = new SceMp4TrackSampleBuf();
track.read(trackAddr);
readHeaders(track, trackAddr);
if (samples > 0) {
// Start bufferPut in the thread context when it will be scheduled
SceKernelThreadInfo currentThread = Modules.ThreadManForUserModule.getCurrentThread();
Modules.ThreadManForUserModule.pushActionForThread(currentThread, new StartBufferPut(track, trackAddr, samples, currentThread));
}
return 0;
}
/**
* Similar to sceMpegGetAtracAu.
*
* @param mp4
* @param trackAddr
* @param auAddr
* @param infoAddr
* @return
*/
@HLEFunction(nid = 0x5601A6F0, version = 150)
public int sceMp4GetAacAu(int mp4, TPointer trackAddr, TPointer auAddr, @CanBeNull TPointer infoAddr) {
SceMp4TrackSampleBuf track = new SceMp4TrackSampleBuf();
track.read(trackAddr);
SceMpegAu au = new SceMpegAu();
au.read(auAddr);
if (log.isTraceEnabled()) {
log.trace(String.format("sceMp4GetAacAu track %s, au %s", track, au));
}
if (track.bufSamples.sizeAvailableForRead <= 0) {
if (log.isDebugEnabled()) {
log.debug(String.format("sceMp4GetAacAu returning ERROR_MP4_NO_MORE_DATA"));
}
return SceKernelErrors.ERROR_MP4_NO_MORE_DATA;
}
int sample = track.currentSample - track.bufSamples.sizeAvailableForRead;
int sampleSize = getSampleSize(track.trackType, sample);
int sampleDuration = getSampleDuration(track.trackType, sample);
int samplePresentationOffset = getSamplePresentationOffset(track.trackType, sample);
long frameDuration = sampleToFrameDuration(sampleDuration, track);
long framePresentationOffset = sampleToFrameDuration(samplePresentationOffset, track);
// Consume one frame
track.bufSamples.notifyRead(1);
track.readBytes(au.esBuffer, sampleSize);
au.esSize = sampleSize;
au.dts = audioCurrentTimestamp;
audioCurrentTimestamp += frameDuration;
au.pts = au.dts + framePresentationOffset;
if (log.isTraceEnabled()) {
log.trace(String.format("sceMp4GetAacAu consuming one frame of size=0x%X, duration=0x%X, track %s", sampleSize, frameDuration, track));
}
au.write(auAddr);
track.write(trackAddr);
if (infoAddr.isNotNull()) {
SceMp4SampleInfo info = new SceMp4SampleInfo();
info.sample = sample;
info.sampleOffset = getSampleOffset(track.trackType, sample);
info.sampleSize = getSampleSize(track.trackType, sample);
info.unknown1 = 0;
info.frameDuration = (int) frameDuration;
info.unknown2 = 0;
info.timestamp1 = (int) au.dts;
info.timestamp2 = (int) au.pts;
info.write(infoAddr);
if (log.isTraceEnabled()) {
log.trace(String.format("sceMp4GetAacAu returning info=%s", info));
}
}
return 0;
}
/**
* Similar to sceMpegAtracDecode.
*
* @param aac
* @param auAddr
* @param outputBufferAddr
* @param init 1 at first call, 0 afterwards
* @param frequency 44100
* @return
*/
@HLEFunction(nid = 0x7663CB5C, version = 150)
public int sceMp4AacDecode(TPointer32 aac, TPointer auAddr, TPointer bufferAddr, int init, int frequency) {
SceMpegAu au = new SceMpegAu();
au.read(auAddr);
if (log.isTraceEnabled()) {
log.trace(String.format("sceMp4AacDecode au=%s, esBuffer:%s", au, Utilities.getMemoryDump(au.esBuffer, au.esSize)));
}
int result = audioCodec.decode(au.esBuffer, au.esSize, bufferAddr.getAddress());
if (result < 0) {
log.error(String.format("sceMp4AacDecode audio codec returned 0x%08X", result));
result = SceKernelErrors.ERROR_MP4_AAC_DECODE_ERROR;
} else {
result = 0;
}
if (log.isDebugEnabled()) {
log.debug(String.format("sceMp4AacDecode returning 0x%X", result));
}
return result;
}
/**
* Similar to sceMpegGetAvcAu.
* Video decoding is done by sceMpegAvcDecode.
*
* @param mp4
* @param trackAddr
* @param auAddr
* @param infoAddr
* @return
*/
@HLEFunction(nid = 0x503A3CBA, version = 150)
public int sceMp4GetAvcAu(int mp4, TPointer trackAddr, TPointer auAddr, @CanBeNull TPointer infoAddr) {
SceMp4TrackSampleBuf track = new SceMp4TrackSampleBuf();
track.read(trackAddr);
if (log.isTraceEnabled()) {
log.trace(String.format("sceMp4GetAvcAu track %s", track));
}
if (track.bufSamples.sizeAvailableForRead <= 0) {
if (log.isDebugEnabled()) {
log.debug(String.format("sceMp4GetAvcAu returning ERROR_MP4_NO_MORE_DATA"));
}
return SceKernelErrors.ERROR_MP4_NO_MORE_DATA;
}
SceMpegAu au = new SceMpegAu();
au.read(auAddr);
Modules.sceMpegModule.setMpegAvcAu(au);
int sample = track.currentSample - track.bufSamples.sizeAvailableForRead;
int sampleSize = getSampleSize(track.trackType, sample);
int sampleDuration = getSampleDuration(track.trackType, sample);
int samplePresentationOffset = getSamplePresentationOffset(track.trackType, sample);
long frameDuration = sampleToFrameDuration(sampleDuration, track);
long framePresentationOffset = sampleToFrameDuration(samplePresentationOffset, track);
// Consume one frame
track.bufSamples.notifyRead(1);
track.bufBytes.notifyRead(sampleSize);
au.dts = videoCurrentTimestamp;
videoCurrentTimestamp += frameDuration;
au.pts = au.dts + framePresentationOffset;
if (log.isTraceEnabled()) {
log.trace(String.format("sceMp4GetAvcAu consuming one frame of size=0x%X, duration=0x%X, track %s", sampleSize, frameDuration, track));
}
au.write(auAddr);
track.write(trackAddr);
if (infoAddr.isNotNull()) {
SceMp4SampleInfo info = new SceMp4SampleInfo();
info.sample = sample;
info.sampleOffset = getSampleOffset(track.trackType, sample);
info.sampleSize = getSampleSize(track.trackType, sample);
info.unknown1 = 0;
info.frameDuration = (int) frameDuration;
info.unknown2 = 0;
info.timestamp1 = (int) au.dts;
info.timestamp2 = (int) au.pts;
info.write(infoAddr);
if (log.isTraceEnabled()) {
log.trace(String.format("sceMp4GetAvcAu returning info=%s", info));
}
}
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x01C76489, version = 150)
public int sceMp4TrackSampleBufDestruct(int unknown1, int unknown2) {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x6710FE77, version = 150)
public int sceMp4UnregistTrack(int unknown1, int unknown2) {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x5D72B333, version = 150)
public int sceMp4AacDecodeExit(int unknown) {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x7D332394, version = 150)
public int sceMp4AacDecodeTermResource() {
return 0;
}
@HLEFunction(nid = 0x131BDE57, version = 150)
public int sceMp4InitAu(int mp4, TPointer bufferAddr, TPointer auAddr) {
SceMpegAu au = new SceMpegAu();
au.esBuffer = bufferAddr.getAddress();
au.esSize = 0;
au.write(auAddr);
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x17EAA97D, version = 150)
public int sceMp4GetAvcAuWithoutSampleBuf(int mp4) {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x28CCB940, version = 150)
public int sceMp4GetTrackEditList(int mp4) {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x3069C2B5, version = 150)
public int sceMp4GetAvcParamSet(int mp4) {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0xD2AC9A7E, version = 150)
public int sceMp4GetMetaData(int mp4) {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x4FB5B756, version = 150)
public int sceMp4GetMetaDataInfo(int mp4) {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x427BEF7F, version = 150)
public int sceMp4GetTrackNumOfEditList(int mp4) {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x532029B8, version = 150)
public int sceMp4GetAacAuWithoutSampleBuf(int mp4) {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0xA6C724DC, version = 150)
public int sceMp4GetSampleNum(int mp4) {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x3C2183C7, version = 150)
public int mp4msv_3C2183C7(int unknown, @CanBeNull TPointer addr) {
if (addr.isNotNull()) {
// addr is pointing to five 32-bit values (20 bytes)
log.warn(String.format("mp4msv_3C2183C7 unknown values: %s", Utilities.getMemoryDump(addr.getAddress(), 20, 4, 20)));
}
// mp4msv_3C2183C7 is called by sceMp4Init
hleMp4Init();
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x9CA13D1A, version = 150)
public int mp4msv_9CA13D1A(int unknown, @CanBeNull TPointer addr) {
if (addr.isNotNull()) {
// addr is pointing to 17 32-bit values (68 bytes)
log.warn(String.format("mp4msv_9CA13D1A unknown values: %s", Utilities.getMemoryDump(addr.getAddress(), 68, 4, 16)));
}
// mp4msv_9CA13D1A is called by sceMp4Init
hleMp4Init();
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0xF595F917, version = 150)
public int mp4msv_F595F917(@BufferInfo(usage=Usage.out) TPointer32 unknown) {
unknown.setValue(0);
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x3D8D41A0, version = 150)
public int mp4msv_3D8D41A0(int unknown1, int unknown2) {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x67AF9E0F, version = 150)
public int mp4msv_67AF9E0F(int unknown) {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x07C60A23, version = 150)
public int mp4msv_07C60A23(@BufferInfo(usage=Usage.out) TPointer32 unknown1, @BufferInfo(usage=Usage.out) TPointer32 unknown2) {
unknown1.setValue(0);
unknown2.setValue(0);
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x0D32271B, version = 150)
public int mp4msv_0D32271B(int unknown1, @BufferInfo(usage=Usage.out) TPointer32 unknown2) {
unknown2.setValue(0);
return 0;
}
}