/*
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.Allegrex.compiler.RuntimeContext.getMemoryInt;
import static jpcsp.Allegrex.compiler.RuntimeContext.hasMemoryInt;
import static jpcsp.HLE.modules.SysMemUserForUser.PSP_SMEM_High;
import static jpcsp.HLE.modules.SysMemUserForUser.USER_PARTITION_ID;
import static jpcsp.HLE.modules.sceAudiocodec.PSP_CODEC_AT3PLUS;
import static jpcsp.format.psmf.PsmfAudioDemuxVirtualFile.PACK_START_CODE;
import static jpcsp.format.psmf.PsmfAudioDemuxVirtualFile.PADDING_STREAM;
import static jpcsp.format.psmf.PsmfAudioDemuxVirtualFile.PRIVATE_STREAM_1;
import static jpcsp.format.psmf.PsmfAudioDemuxVirtualFile.PRIVATE_STREAM_2;
import static jpcsp.format.psmf.PsmfAudioDemuxVirtualFile.SYSTEM_HEADER_START_CODE;
import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_16BIT_BGR5650;
import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888;
import static jpcsp.util.Utilities.endianSwap16;
import static jpcsp.util.Utilities.endianSwap32;
import static jpcsp.util.Utilities.readUnaligned16;
import jpcsp.HLE.BufferInfo;
import jpcsp.HLE.BufferInfo.LengthInfo;
import jpcsp.HLE.BufferInfo.Usage;
import jpcsp.HLE.CanBeNull;
import jpcsp.HLE.CheckArgument;
import jpcsp.HLE.DebugMemory;
import jpcsp.HLE.HLEFunction;
import jpcsp.HLE.HLELogging;
import jpcsp.HLE.HLEModule;
import jpcsp.HLE.HLEUnimplemented;
import jpcsp.HLE.SceKernelErrorException;
import jpcsp.HLE.TPointer;
import jpcsp.HLE.TPointer32;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import jpcsp.Emulator;
import jpcsp.Memory;
import jpcsp.Processor;
import jpcsp.HLE.Modules;
import jpcsp.HLE.kernel.managers.SceUidManager;
import jpcsp.HLE.kernel.types.IAction;
import jpcsp.HLE.kernel.types.SceKernelErrors;
import jpcsp.HLE.kernel.types.SceKernelThreadInfo;
import jpcsp.HLE.kernel.types.SceMp4AvcNalStruct;
import jpcsp.HLE.kernel.types.SceMpegAu;
import jpcsp.HLE.kernel.types.SceMpegRingbuffer;
import jpcsp.HLE.kernel.types.pspFileBuffer;
import jpcsp.HLE.modules.SysMemUserForUser.SysMemInfo;
import jpcsp.format.psmf.PesHeader;
import jpcsp.graphics.VideoEngine;
import jpcsp.hardware.Screen;
import jpcsp.media.codec.CodecFactory;
import jpcsp.media.codec.ICodec;
import jpcsp.media.codec.IVideoCodec;
import jpcsp.media.codec.h264.H264Utils;
import jpcsp.memory.IMemoryReader;
import jpcsp.memory.IMemoryWriter;
import jpcsp.memory.MemoryReader;
import jpcsp.memory.MemoryWriter;
import jpcsp.scheduler.DelayThreadAction;
import jpcsp.scheduler.UnblockThreadAction;
import jpcsp.util.Debug;
import jpcsp.util.Utilities;
import org.apache.log4j.Logger;
import com.twilight.h264.decoder.H264Context;
//
// The stackUsage values are based on tests performed using JpcspTrace
//
public class sceMpeg extends HLEModule {
public static Logger log = Modules.getLogger("sceMpeg");
@Override
public int getMemoryUsage() {
// No need to allocate additional memory when the module has been
// loaded using sceKernelLoadModuleToBlock()
// by the PSP "flash0:/kd/utility.prx".
// The memory has already been allocated in that case.
if (Modules.ModuleMgrForKernelModule.isMemoryAllocatedForModule("flash0:/kd/mpeg.prx")) {
return 0;
}
return 0xC000;
}
@Override
public void start() {
mpegHandle = 0;
mpegRingbuffer = null;
mpegRingbufferAddr = null;
avcAuAddr = 0;
atracAuAddr = 0;
mpegAtracAu = new SceMpegAu();
mpegAvcAu = new SceMpegAu();
mpegUserDataAu = new SceMpegAu();
psmfHeader = null;
intBuffers = new HashSet<int[]>();
lastFrameABGR = null;
audioDecodeBuffer = new byte[MPEG_ATRAC_ES_OUTPUT_SIZE];
allocatedEsBuffers = new boolean[2];
streamMap = new HashMap<Integer, StreamInfo>();
videoCodecExtraData = null;
super.start();
}
@Override
public void stop() {
// Free the temporary arrays
intBuffers.clear();
// Free objects no longer used
audioDecodeBuffer = null;
allocatedEsBuffers = null;
streamMap = null;
mpegAtracAu = null;
mpegAvcAu = null;
super.stop();
}
// MPEG statics.
public static final int PSMF_MAGIC = 0x464D5350;
public static final int PSMF_MAGIC_LITTLE_ENDIAN = 0x50534D46;
public static final int PSMF_VERSION_0012 = 0x32313030;
public static final int PSMF_VERSION_0013 = 0x33313030;
public static final int PSMF_VERSION_0014 = 0x34313030;
public static final int PSMF_VERSION_0015 = 0x35313030;
public static final int PSMF_MAGIC_OFFSET = 0x0;
public static final int PSMF_STREAM_VERSION_OFFSET = 0x4;
public static final int PSMF_STREAM_OFFSET_OFFSET = 0x8;
public static final int PSMF_STREAM_SIZE_OFFSET = 0xC;
public static final int PSMF_FIRST_TIMESTAMP_OFFSET = 0x54;
public static final int PSMF_LAST_TIMESTAMP_OFFSET = 0x5A;
public static final int PSMF_NUMBER_STREAMS_OFFSET = 0x80;
public static final int PSMF_FRAME_WIDTH_OFFSET = 0x8E;
public static final int PSMF_FRAME_HEIGHT_OFFSET = 0x8F;
protected static final int MPEG_MEMSIZE = 0x10000; // 64k.
private static final int AUDIO_BUFFER_OFFSET = 0x100; // Offset of the audio buffer inside MPEG structure
private static final int AUDIO_BUFFER_SIZE = 0x1000; // Size of the audio buffer
public static final int atracDecodeDelay = 3000; // Microseconds
public static final int avcDecodeDelay = 5400; // Microseconds
public static final int mpegDecodeErrorDelay = 100; // Delay in Microseconds in case of decode error
public static final int mpegTimestampPerSecond = 90000; // How many MPEG Timestamp units in a second.
public static final int videoTimestampStep = 3003; // Value based on pmfplayer (mpegTimestampPerSecond / 29.970 (fps)).
public static final int audioTimestampStep = 4180; // For audio play at 44100 Hz (2048 samples / 44100 * mpegTimestampPerSecond == 4180)
public static final long UNKNOWN_TIMESTAMP = -1;
public static final int PSMF_AVC_STREAM = 0;
public static final int PSMF_ATRAC_STREAM = 1;
public static final int PSMF_PCM_STREAM = 2;
public static final int PSMF_DATA_STREAM = 3;
public static final int PSMF_AUDIO_STREAM = 15;
public static final int PSMF_VIDEO_STREAM_ID = 0xE0;
public static final int PSMF_AUDIO_STREAM_ID = 0xBD;
// The YCbCr buffer is starting with 128 bytes of unknown data
protected static final int YCBCR_DATA_OFFSET = 128;
// At least 2048 bytes of MPEG data is provided when analysing the MPEG header
public static final int MPEG_HEADER_BUFFER_MINIMUM_SIZE = 2048;
// MPEG processing vars.
protected int mpegHandle;
protected TPointer mpegAvcDetail2Struct;
protected TPointer mpegAvcInfoStruct;
protected TPointer mpegAvcYuvStruct;
protected SceMpegRingbuffer mpegRingbuffer;
protected TPointer mpegRingbufferAddr;
protected SceMpegAu mpegAtracAu;
protected SceMpegAu mpegAvcAu;
protected SceMpegAu mpegUserDataAu;
protected long lastAtracSystemTime;
protected long lastAvcSystemTime;
protected int avcAuAddr;
protected int atracAuAddr;
protected int videoFrameCount;
protected int audioFrameCount;
private long currentVideoTimestamp;
private long currentAudioTimestamp;
protected int videoPixelMode;
protected int defaultFrameWidth;
// MPEG AVC elementary stream.
public static final int MPEG_AVC_ES_SIZE = 2048; // MPEG packet size.
// MPEG ATRAC elementary stream.
protected static final int MPEG_ATRAC_ES_SIZE = 2112;
public static final int MPEG_ATRAC_ES_OUTPUT_SIZE = 8192;
// MPEG PCM elementary stream.
protected static final int MPEG_PCM_ES_SIZE = 320;
protected static final int MPEG_PCM_ES_OUTPUT_SIZE = 320;
// MPEG Userdata elementary stream.
protected static final int MPEG_DATA_ES_SIZE = 0xA0000;
protected static final int MPEG_DATA_ES_OUTPUT_SIZE = 0xA0000;
// MPEG analysis results.
public static final int MPEG_VERSION_0012 = 0;
public static final int MPEG_VERSION_0013 = 1;
public static final int MPEG_VERSION_0014 = 2;
public static final int MPEG_VERSION_0015 = 3;
protected static final int MPEG_AU_MODE_DECODE = 0;
protected static final int MPEG_AU_MODE_SKIP = 1;
protected int registeredVideoChannel = -1;
protected int registeredAudioChannel = -1;
// MPEG decoding results.
protected static final int MPEG_AVC_DECODE_SUCCESS = 1; // Internal value.
protected static final int MPEG_AVC_DECODE_ERROR_FATAL = -8;
protected int avcDecodeResult;
protected boolean avcGotFrame;
protected boolean startedMpeg;
protected byte[] audioDecodeBuffer;
protected boolean[] allocatedEsBuffers;
protected HashMap<Integer, StreamInfo> streamMap;
protected static final String streamPurpose = "sceMpeg-Stream";
protected static final int mpegAudioOutputChannels = 2;
protected SysMemInfo avcEsBuf;
public PSMFHeader psmfHeader;
private AudioBuffer audioBuffer;
private int audioFrameLength;
private final int frameHeader[] = new int[8];
private int frameHeaderLength;
private ICodec audioCodec;
private VideoBuffer videoBuffer;
private IVideoCodec videoCodec;
private int videoCodecExtraData[];
private static final int MAX_INT_BUFFERS_SIZE = 12;
private static Set<int[]> intBuffers;
private PesHeader audioPesHeader;
private PesHeader videoPesHeader;
private final PesHeader dummyPesHeader = new PesHeader(0);
private VideoDecoderThread videoDecoderThread;
private LinkedList<DecodedImageInfo> decodedImages;
private int lastFrameABGR[];
private int lastFrameWidth;
private int lastFrameHeight;
private PesHeader userDataPesHeader;
private UserDataBuffer userDataBuffer;
private final int userDataHeader[] = new int[8];
private int userDataLength;
private int videoFrameHeight;
// Not sure about the real size.
public static final int AVC_ES_BUF_SIZE = 0x2000;
private static class DecodedImageInfo {
public PesHeader pesHeader;
public int frameEnd;
public boolean gotFrame;
public int imageWidth;
public int imageHeight;
public int luma[];
public int cr[];
public int cb[];
public int abgr[];
@Override
public String toString() {
return String.format("pesHeader=%s, frameEnd=0x%X, gotFrame=%b, image %dx%d", pesHeader, frameEnd, gotFrame, imageWidth, imageHeight);
}
}
private static class AudioBuffer {
private int addr;
private int size;
private int length;
public AudioBuffer(int addr, int size) {
this.addr = addr;
this.size = size;
length = 0;
}
public int write(Memory mem, int dataAddr, int size) {
size = Math.min(size, getFreeLength());
mem.memcpy(addr + length, dataAddr, size);
length += size;
return size;
}
public int getLength() {
return length;
}
public int getReadAddr() {
return addr;
}
public int getFreeLength() {
return size - length;
}
public int notifyRead(Memory mem, int size) {
size = Math.min(size, length);
length -= size;
mem.memcpy(addr, addr + size, length);
return size;
}
public boolean isEmpty() {
return length == 0;
}
public void reset() {
length = 0;
}
}
private static class VideoBuffer {
private int buffer[] = new int[10000];
private int length;
private static int quickSearch[];
private int frameSizes[];
private int frame;
public VideoBuffer() {
length = 0;
frame = 0;
frameSizes = null;
initQuickSearch();
}
private static void initQuickSearch() {
if (quickSearch != null) {
return;
}
quickSearch = new int[256];
Arrays.fill(quickSearch, 5);
quickSearch[0] = 2;
quickSearch[1] = 1;
}
public synchronized void write(Memory mem, int dataAddr, int size) {
if (log.isTraceEnabled()) {
log.trace(String.format("VideoBuffer.write addr=0x%08X, size=0x%X, %s", dataAddr, size, this));
}
if (size + length > buffer.length) {
int extendedBuffer[] = new int[size + length];
System.arraycopy(buffer, 0, extendedBuffer, 0, length);
buffer = extendedBuffer;
}
IMemoryReader memoryReader = MemoryReader.getMemoryReader(dataAddr, size, 1);
for (int i = 0; i < size; i++) {
buffer[length++] = memoryReader.readNext();
}
}
public int[] getBuffer() {
return buffer;
}
public int getBufferOffset() {
return 0;
}
public synchronized void notifyRead(int size) {
if (log.isTraceEnabled()) {
log.trace(String.format("VideoBuffer.notifyRead size=0x%X, %s", size, this));
}
size = Math.min(size, length);
length -= size;
System.arraycopy(buffer, size, buffer, 0, length);
frame++;
}
public synchronized void reset() {
length = 0;
}
public int getLength() {
return length;
}
public boolean isEmpty() {
return length == 0;
}
public synchronized int findFrameEnd() {
if (frameSizes != null && frame < frameSizes.length) {
if (log.isTraceEnabled()) {
log.trace(String.format("VideoBuffer.findFrameEnd frameSize=0x%X, %s", frameSizes[frame], this));
}
return frameSizes[frame];
}
for (int i = 5; i < length; ) {
int value = buffer[i];
if (buffer[i - 4] == 0x00 &&
buffer[i - 3] == 0x00 &&
buffer[i - 2] == 0x00 &&
buffer[i - 1] == 0x01) {
int nalUnitType = value & 0x1F;
if (nalUnitType == H264Context.NAL_AUD) {
return i - 4;
}
}
i += quickSearch[value];
}
return -1;
}
public void setFrameSizes(int frameSizes[]) {
this.frameSizes = frameSizes;
frame = 0;
}
public synchronized void setFrame(int newFrame) {
if (frameSizes != null) {
if (log.isTraceEnabled()) {
log.trace(String.format("VideoBuffer.setFrame newFrame=0x%X, %s", newFrame, this));
}
if (newFrame < frame) {
reset();
} else {
// Skip frames up to new frame
while (frame < newFrame && !isEmpty()) {
notifyRead(frameSizes[frame]);
}
}
}
this.frame = newFrame;
}
@Override
public String toString() {
return String.format("VideoBuffer[length=0x%X, frame=0x%X]", length, frame);
}
}
private static class UserDataBuffer {
private int addr;
private int size;
private int length;
public UserDataBuffer(int addr, int size) {
this.addr = addr;
this.size = size;
length = 0;
}
public int write(Memory mem, int dataAddr, int size) {
size = Math.min(size, getFreeLength());
mem.memcpy(addr + length, dataAddr, size);
length += size;
return size;
}
public int getLength() {
return length;
}
public int getFreeLength() {
return size - length;
}
public int notifyRead(Memory mem, int size) {
size = Math.min(size, length);
length -= size;
mem.memcpy(addr, addr + size, length);
return size;
}
}
// Entry class for the PSMF streams.
public static class PSMFStream {
private int streamType = -1;
private int streamChannel = -1;
private int streamNumber;
private int EPMapNumEntries;
private int EPMapOffset;
private List<PSMFEntry> EPMap;
private int frameWidth;
private int frameHeight;
public PSMFStream(int streamNumber) {
this.streamNumber = streamNumber;
}
public int getStreamType() {
return streamType;
}
public int getStreamChannel() {
return streamChannel;
}
public int getStreamNumber() {
return streamNumber;
}
public boolean isStreamOfType(int type) {
if (streamType == type) {
return true;
}
if (type == PSMF_AUDIO_STREAM) {
// Atrac or PCM
return streamType == PSMF_ATRAC_STREAM || streamType == PSMF_PCM_STREAM;
}
return false;
}
public void readMPEGVideoStreamParams(Memory mem, int addr, byte[] mpegHeader, int offset, PSMFHeader psmfHeader) {
int streamID = read8(mem, addr, mpegHeader, offset); // 0xE0
int privateStreamID = read8(mem, addr, mpegHeader, offset + 1); // 0x00
int unk1 = read8(mem, addr, mpegHeader, offset + 2); // Found values: 0x20/0x21
int unk2 = read8(mem, addr, mpegHeader, offset + 3); // Found values: 0x44/0xFB/0x75
EPMapOffset = endianSwap32(sceMpeg.readUnaligned32(mem, addr, mpegHeader, offset + 4));
EPMapNumEntries = endianSwap32(sceMpeg.readUnaligned32(mem, addr, mpegHeader, offset + 8));
frameWidth = read8(mem, addr, mpegHeader, offset + 12) * 0x10; // PSMF video width (bytes per line).
frameHeight = read8(mem, addr, mpegHeader, offset + 13) * 0x10; // PSMF video heigth (bytes per line).
if (log.isInfoEnabled()) {
log.info(String.format("Found PSMF MPEG video stream data: streamID=0x%X, privateStreamID=0x%X, unk1=0x%X, unk2=0x%X, EPMapOffset=0x%x, EPMapNumEntries=%d, frameWidth=%d, frameHeight=%d", streamID, privateStreamID, unk1, unk2, EPMapOffset, EPMapNumEntries, frameWidth, frameHeight));
}
streamType = PSMF_AVC_STREAM;
streamChannel = streamID & 0x0F;
}
public void readPrivateAudioStreamParams(Memory mem, int addr, byte[] mpegHeader, int offset, PSMFHeader psmfHeader) {
int streamID = read8(mem, addr, mpegHeader, offset); // 0xBD
int privateStreamID = read8(mem, addr, mpegHeader, offset + 1); // 0x00
int unk1 = read8(mem, addr, mpegHeader, offset + 2); // Always 0x20
int unk2 = read8(mem, addr, mpegHeader, offset + 3); // Always 0x04
int audioChannelConfig = read8(mem, addr, mpegHeader, offset + 14); // 1 - mono, 2 - stereo
int audioSampleFrequency = read8(mem, addr, mpegHeader, offset + 15); // 2 - 44khz
if (log.isInfoEnabled()) {
log.info(String.format("Found PSMF MPEG audio stream data: streamID=0x%X, privateStreamID=0x%X, unk1=0x%X, unk2=0x%X, audioChannelConfig=%d, audioSampleFrequency=%d", streamID, privateStreamID, unk1, unk2, audioChannelConfig, audioSampleFrequency));
}
if (psmfHeader != null) {
psmfHeader.audioChannelConfig = audioChannelConfig;
psmfHeader.audioSampleFrequency = audioSampleFrequency;
}
streamType = ((privateStreamID & 0xF0) == 0 ? PSMF_ATRAC_STREAM : PSMF_PCM_STREAM);
streamChannel = privateStreamID & 0x0F;
}
public void readUserDataStreamParams(Memory mem, int addr, byte[] mpegHeader, int offset, PSMFHeader psmfHeader) {
log.warn(String.format("Unknown User Data stream format"));
streamType = PSMF_DATA_STREAM;
}
}
// Entry class for the EPMap.
protected static class PSMFEntry {
private int EPIndex;
private int EPPicOffset;
private int EPPts;
private int EPOffset;
private int id;
public PSMFEntry(int id, int index, int picOffset, int pts, int offset) {
this.id = id;
EPIndex = index;
EPPicOffset = picOffset;
EPPts = pts;
EPOffset = offset;
}
public int getEntryIndex() {
return EPIndex;
}
public int getEntryPicOffset() {
return EPPicOffset;
}
public int getEntryPTS() {
return EPPts;
}
public int getEntryOffset() {
return EPOffset * MPEG_AVC_ES_SIZE;
}
public int getId() {
return id;
}
@Override
public String toString() {
return String.format("id=%d, index=0x%X, picOffset=0x%X, PTS=0x%X, offset=0x%X", getId(), getEntryIndex(), getEntryPicOffset(), getEntryPTS(), getEntryOffset());
}
}
public static class PSMFHeader {
private static final int size = 2048;
// Header vars.
public int mpegMagic;
public int mpegRawVersion;
public int mpegVersion;
public int mpegOffset;
public int mpegStreamSize;
public long mpegFirstTimestamp;
public long mpegLastTimestamp;
public Date mpegFirstDate;
public Date mpegLastDate;
private int streamNum;
private int audioSampleFrequency;
private int audioChannelConfig;
private int avcDetailFrameWidth;
private int avcDetailFrameHeight;
// Stream map.
public List<PSMFStream> psmfStreams;
private PSMFStream currentStream = null;
private PSMFStream currentVideoStream = null;
public PSMFHeader() {
}
public PSMFHeader(int bufferAddr, byte[] mpegHeader) {
Memory mem = Memory.getInstance();
int streamDataTotalSize = endianSwap32(readUnaligned32(mem, bufferAddr, mpegHeader, 0x50));
int unk = endianSwap32(readUnaligned32(mem, bufferAddr, mpegHeader, 0x60));
int streamDataNextBlockSize = endianSwap32(readUnaligned32(mem, bufferAddr, mpegHeader, 0x6A)); // General stream information block size.
int streamDataNextInnerBlockSize = endianSwap32(readUnaligned32(mem, bufferAddr, mpegHeader, 0x7C)); // Inner stream information block size.
streamNum = endianSwap16(read16(mem, bufferAddr, mpegHeader, PSMF_NUMBER_STREAMS_OFFSET)); // Number of total registered streams.
mpegMagic = read32(mem, bufferAddr, mpegHeader, PSMF_MAGIC_OFFSET);
mpegRawVersion = read32(mem, bufferAddr, mpegHeader, PSMF_STREAM_VERSION_OFFSET);
mpegVersion = getMpegVersion(mpegRawVersion);
mpegOffset = endianSwap32(read32(mem, bufferAddr, mpegHeader, PSMF_STREAM_OFFSET_OFFSET));
mpegStreamSize = endianSwap32(read32(mem, bufferAddr, mpegHeader, PSMF_STREAM_SIZE_OFFSET));
mpegFirstTimestamp = readTimestamp(mem, bufferAddr, mpegHeader, PSMF_FIRST_TIMESTAMP_OFFSET);
mpegLastTimestamp = readTimestamp(mem, bufferAddr, mpegHeader, PSMF_LAST_TIMESTAMP_OFFSET);
avcDetailFrameWidth = read8(mem, bufferAddr, mpegHeader, PSMF_FRAME_WIDTH_OFFSET) << 4;
avcDetailFrameHeight = read8(mem, bufferAddr, mpegHeader, PSMF_FRAME_HEIGHT_OFFSET) << 4;
mpegFirstDate = convertTimestampToDate(mpegFirstTimestamp);
mpegLastDate = convertTimestampToDate(mpegLastTimestamp);
if (log.isDebugEnabled()) {
log.debug(String.format("PSMFHeader: version=0x%04X, firstTimestamp=%d, lastTimestamp=%d, streamDataTotalSize=%d, unk=0x%08X, streamDataNextBlockSize=%d, streamDataNextInnerBlockSize=%d, streamNum=%d", getVersion(), mpegFirstTimestamp, mpegLastTimestamp, streamDataTotalSize, unk, streamDataNextBlockSize, streamDataNextInnerBlockSize, streamNum));
}
if (isValid()) {
psmfStreams = readPsmfStreams(mem, bufferAddr, mpegHeader, this);
// PSP seems to default to stream 0.
if (psmfStreams.size() > 0) {
setStreamNum(0);
}
// EPMap info:
// - Located at EPMapOffset (set by the AVC stream);
// - Each entry is composed by a total of 10 bytes:
// - 1 byte: Reference picture index (RAPI);
// - 1 byte: Reference picture offset from the current index;
// - 4 bytes: PTS of the entry point;
// - 4 bytes: Relative offset of the entry point in the MPEG data.
for (PSMFStream stream : psmfStreams) {
stream.EPMap = new LinkedList<sceMpeg.PSMFEntry>();
int EPMapOffset = stream.EPMapOffset;
for (int i = 0; i < stream.EPMapNumEntries; i++) {
int index = read8(mem, bufferAddr, mpegHeader, EPMapOffset + i * 10);
int picOffset = read8(mem, bufferAddr, mpegHeader, EPMapOffset + 1 + i * 10);
int pts = endianSwap32(readUnaligned32(mem, bufferAddr, mpegHeader, EPMapOffset + 2 + i * 10));
int offset = endianSwap32(readUnaligned32(mem, bufferAddr, mpegHeader, EPMapOffset + 6 + i * 10));
PSMFEntry psmfEntry = new PSMFEntry(i, index, picOffset, pts, offset);
stream.EPMap.add(psmfEntry);
if (log.isDebugEnabled()) {
log.debug(String.format("EPMap stream %d, entry#%d: %s", stream.getStreamChannel(), i, psmfEntry));
}
}
}
}
}
private long readTimestamp(Memory mem, int bufferAddr, byte[] mpegHeader, int offset) {
long timestamp = endianSwap32(readUnaligned32(mem, bufferAddr, mpegHeader, offset + 2)) & 0xFFFFFFFFL;
timestamp |= ((long) read8(mem, bufferAddr, mpegHeader, offset + 1)) << 32;
timestamp |= ((long) read8(mem, bufferAddr, mpegHeader, offset + 0)) << 40;
return timestamp;
}
public boolean isValid() {
return mpegFirstTimestamp == 90000 && mpegFirstTimestamp < mpegLastTimestamp && mpegLastTimestamp > 0;
}
public boolean isInvalid() {
return !isValid();
}
public int getVersion() {
return mpegRawVersion;
}
public int getHeaderSize() {
return size;
}
public int getStreamOffset() {
return mpegOffset;
}
public int getStreamSize() {
return mpegStreamSize;
}
public int getPresentationStartTime() {
return (int) mpegFirstTimestamp;
}
public int getPresentationEndTime() {
return (int) mpegLastTimestamp;
}
public int getVideoWidth() {
return avcDetailFrameWidth;
}
public int getVideoHeight() {
return avcDetailFrameHeight;
}
public int getAudioSampleFrequency() {
return audioSampleFrequency;
}
public int getAudioChannelConfig() {
return audioChannelConfig;
}
public int getEPMapEntriesNum() {
if (currentVideoStream == null) {
return 0;
}
return currentVideoStream.EPMapNumEntries;
}
public boolean hasEPMap() {
return getEPMapEntriesNum() > 0;
}
public PSMFEntry getEPMapEntry(int id) {
if (!hasEPMap()) {
return null;
}
if (id < 0 || id >= currentVideoStream.EPMap.size()) {
return null;
}
return currentVideoStream.EPMap.get(id);
}
public PSMFEntry getEPMapEntryWithTimestamp(int ts) {
if (!hasEPMap()) {
return null;
}
PSMFEntry foundEntry = null;
for (PSMFEntry entry : currentVideoStream.EPMap) {
if (foundEntry == null || entry.getEntryPTS() <= ts) {
foundEntry = entry;
} else if (entry.getEntryPTS() > ts) {
break;
}
}
return foundEntry;
}
public PSMFEntry getEPMapEntryWithOffset(int offset) {
if (!hasEPMap()) {
return null;
}
PSMFEntry foundEntry = null;
for (PSMFEntry entry : currentVideoStream.EPMap) {
if (foundEntry == null || entry.getEntryOffset() <= offset) {
foundEntry = entry;
} else if (entry.getEntryOffset() > offset) {
break;
}
}
return foundEntry;
}
public int getNumberOfStreams() {
return streamNum;
}
public int getCurrentStreamNumber() {
if (!isValidCurrentStreamNumber()) {
return -1;
}
return currentStream.getStreamNumber();
}
public boolean isValidCurrentStreamNumber() {
return currentStream != null;
}
public int getCurrentStreamType() {
if (!isValidCurrentStreamNumber()) {
return -1;
}
return currentStream.getStreamType();
}
public int getCurrentStreamChannel() {
if (!isValidCurrentStreamNumber()) {
return -1;
}
return currentStream.getStreamChannel();
}
public int getSpecificStreamNum(int type) {
int num = 0;
if (psmfStreams != null) {
for (PSMFStream stream : psmfStreams) {
if (stream.isStreamOfType(type)) {
num++;
}
}
}
return num;
}
public void setStreamNum(int id) {
if (id < 0 || id >= psmfStreams.size()) {
currentStream = null;
} else {
currentStream = psmfStreams.get(id);
int type = getCurrentStreamType();
int channel = getCurrentStreamChannel();
switch (type) {
case PSMF_AVC_STREAM:
currentVideoStream = currentStream;
Modules.sceMpegModule.setRegisteredVideoChannel(channel);
break;
case PSMF_PCM_STREAM:
case PSMF_ATRAC_STREAM:
Modules.sceMpegModule.setRegisteredAudioChannel(channel);
break;
}
}
}
private int getStreamNumber(int type, int typeNum, int channel) {
if (psmfStreams != null) {
for (PSMFStream stream : psmfStreams) {
if (stream.isStreamOfType(type)) {
if (typeNum <= 0) {
if (channel < 0 || stream.getStreamChannel() == channel) {
return stream.getStreamNumber();
}
}
typeNum--;
}
}
}
return -1;
}
public boolean setStreamWithType(int type, int channel) {
int streamNumber = getStreamNumber(type, 0, channel);
if (streamNumber < 0) {
return false;
}
setStreamNum(streamNumber);
return true;
}
public boolean setStreamWithTypeNum(int type, int typeNum) {
int streamNumber = getStreamNumber(type, typeNum, -1);
if (streamNumber < 0) {
return false;
}
setStreamNum(streamNumber);
return true;
}
}
public static int getPsmfNumStreams(Memory mem, int addr, byte[] mpegHeader) {
return endianSwap16(read16(mem, addr, mpegHeader, sceMpeg.PSMF_NUMBER_STREAMS_OFFSET));
}
public static LinkedList<PSMFStream> readPsmfStreams(Memory mem, int addr, byte[] mpegHeader, PSMFHeader psmfHeader) {
int numStreams = getPsmfNumStreams(mem, addr, mpegHeader);
// Stream area:
// At offset 0x82, each 16 bytes represent one stream.
LinkedList<PSMFStream> streams = new LinkedList<PSMFStream>();
// Parse the stream field and assign each one to it's type.
int numberOfStreams = 0;
for (int i = 0; i < numStreams; i++) {
PSMFStream stream = null;
int currentStreamOffset = 0x82 + i * 16;
int streamID = read8(mem, addr, mpegHeader, currentStreamOffset);
int subStreamID = read8(mem, addr, mpegHeader, currentStreamOffset + 1);
if ((streamID & 0xF0) == PSMF_VIDEO_STREAM_ID) {
stream = new PSMFStream(numberOfStreams);
stream.readMPEGVideoStreamParams(mem, addr, mpegHeader, currentStreamOffset, psmfHeader);
} else if (streamID == PSMF_AUDIO_STREAM_ID && subStreamID < 0x20) {
stream = new PSMFStream(numberOfStreams);
stream.readPrivateAudioStreamParams(mem, addr, mpegHeader, currentStreamOffset, psmfHeader);
} else {
stream = new PSMFStream(numberOfStreams);
stream.readUserDataStreamParams(mem, addr, mpegHeader, currentStreamOffset, psmfHeader);
}
if (stream != null) {
streams.add(stream);
numberOfStreams++;
}
}
return streams;
}
private class StreamInfo {
private int uid;
private final int type;
private final int channel;
private int auMode;
public StreamInfo(int type, int channel) {
this.type = type;
this.channel = channel;
uid = SceUidManager.getNewUid(streamPurpose);
setAuMode(MPEG_AU_MODE_DECODE);
streamMap.put(uid, this);
}
public int getUid() {
return uid;
}
public int getType() {
return type;
}
public int getChannel() {
return channel;
}
public void release() {
SceUidManager.releaseUid(uid, streamPurpose);
streamMap.remove(uid);
}
public int getAuMode() {
return auMode;
}
public void setAuMode(int auMode) {
this.auMode = auMode;
}
public boolean isStreamType(int type) {
if (this.type == type) {
return true;
}
if (this.type == PSMF_ATRAC_STREAM && type == PSMF_AUDIO_STREAM) {
return true;
}
return false;
}
@Override
public String toString() {
return String.format("StreamInfo(uid=0x%X, type=%d, auMode=%d)", getUid(), getType(), getAuMode());
}
}
public class AfterRingbufferPutCallback implements IAction {
private int putDataAddr;
private int remainingPackets;
private int totalPacketsAdded;
public AfterRingbufferPutCallback(int putDataAddr, int remainingPackets) {
this.putDataAddr = putDataAddr;
this.remainingPackets = remainingPackets;
}
@Override
public void execute() {
hleMpegRingbufferPostPut(this, Emulator.getProcessor().cpu._v0);
}
public int getPutDataAddr() {
return putDataAddr;
}
public void setPutDataAddr(int putDataAddr) {
this.putDataAddr = putDataAddr;
}
public int getRemainingPackets() {
return remainingPackets;
}
public void setRemainingPackets(int remainingPackets) {
this.remainingPackets = remainingPackets;
}
public int getTotalPacketsAdded() {
return totalPacketsAdded;
}
public void addPacketsAdded(int packetsAdded) {
// Add only if we don't return an error code
if (packetsAdded > 0 && totalPacketsAdded >= 0) {
totalPacketsAdded += packetsAdded;
}
}
public void setErrorCode(int errorCode) {
totalPacketsAdded = errorCode;
}
}
/**
* Always decode one frame in advance so that sceMpegAvcDecode
* can be timed like on a real PSP.
*/
private class VideoDecoderThread extends Thread {
private volatile boolean exit = false;
private volatile boolean done = false;
// Start with one semaphore permit to not wait on the first loop
private Semaphore sema = new Semaphore(1);
private int threadUid = -1;
private int buffer;
private int frameWidth;
private int pixelMode;
private TPointer32 gotFrameAddr;
private boolean writeAbgr;
private TPointer auAddr;
private long threadWakeupMicroTime;
@Override
public void run() {
while (!exit) {
if (waitForTrigger(100) && !exit) {
hleVideoDecoderStep(threadUid, buffer, frameWidth, pixelMode, gotFrameAddr, writeAbgr, auAddr, threadWakeupMicroTime);
}
}
if (log.isDebugEnabled()) {
log.debug("Exiting the VideoDecoderThread");
}
done = true;
}
public void exit() {
exit = true;
trigger(-1, 0, 0, -1, null, false, TPointer.NULL, 0L);
while (!done) {
Utilities.sleep(1);
}
}
public void trigger(int threadUid, int buffer, int frameWidth, int pixelMode, TPointer32 gotFrameAddr, boolean writeAbgr, TPointer auAddr, long threadWakeupMicroTime) {
this.threadUid = threadUid;
this.buffer = buffer;
this.frameWidth = frameWidth;
this.pixelMode = pixelMode;
this.gotFrameAddr = gotFrameAddr;
this.writeAbgr = writeAbgr;
this.auAddr = auAddr;
this.threadWakeupMicroTime = threadWakeupMicroTime;
trigger();
}
public void resetWaitingThreadInfo() {
threadUid = -1;
buffer = 0;
frameWidth = 0;
pixelMode = -1;
gotFrameAddr = null;
threadWakeupMicroTime = 0;
}
public void trigger() {
if (sema != null) {
sema.release();
}
}
private boolean waitForTrigger(int millis) {
while (true) {
try {
int availablePermits = sema.drainPermits();
if (availablePermits > 0) {
break;
}
if (sema.tryAcquire(millis, TimeUnit.MILLISECONDS)) {
break;
}
return false;
} catch (InterruptedException e) {
// Ignore exception and retry
}
}
return true;
}
}
protected void mpegRingbufferWrite() {
if (mpegRingbuffer != null && mpegRingbufferAddr != null && mpegRingbufferAddr.isNotNull()) {
synchronized (mpegRingbuffer) {
mpegRingbuffer.notifyConsumed();
mpegRingbuffer.write(mpegRingbufferAddr);
}
}
}
protected void mpegRingbufferNotifyRead() {
int numberDecodedImages = decodedImages.size();
// Assume we have one more pending image when the videoBuffer is not empty
if (!videoBuffer.isEmpty()) {
numberDecodedImages++;
}
synchronized (mpegRingbuffer) {
mpegRingbuffer.notifyRead(numberDecodedImages);
}
}
protected void mpegRingbufferRead() {
if (mpegRingbuffer != null) {
if (mpegRingbufferAddr != null && mpegRingbufferAddr.isNotNull()) {
synchronized (mpegRingbuffer) {
mpegRingbuffer.read(mpegRingbufferAddr);
}
}
mpegRingbuffer.setHasAudio(isRegisteredAudioChannel());
mpegRingbuffer.setHasVideo(isRegisteredVideoChannel());
mpegRingbuffer.setHasUserData(isRegisteredUserDataChannel());
}
}
private void rememberLastFrame(int imageWidth, int imageHeight, int abgr[]) {
if (abgr != null) {
releaseIntBuffer(lastFrameABGR);
int length = imageWidth * imageHeight;
lastFrameABGR = getIntBuffer(length);
System.arraycopy(abgr, 0, lastFrameABGR, 0, length);
lastFrameWidth = imageWidth;
lastFrameHeight = imageHeight;
}
}
public void writeLastFrameABGR(int buffer, int frameWidth, int pixelMode) {
if (lastFrameABGR != null) {
writeImageABGR(buffer, frameWidth, lastFrameWidth, lastFrameHeight, pixelMode, lastFrameABGR);
}
}
SysMemInfo mpegAvcYuvMemInfo;
private boolean decodeImage(int buffer, int frameWidth, int pixelMode, TPointer32 gotFrameAddr, boolean writeAbgr) {
if (pixelMode < 0) {
return false;
}
DecodedImageInfo decodedImageInfo;
synchronized (decodedImages) {
decodedImageInfo = decodedImages.pollFirst();
}
if (decodedImageInfo == null) {
avcGotFrame = false;
if (gotFrameAddr != null) {
gotFrameAddr.setValue(avcGotFrame);
}
return false;
}
avcGotFrame = decodedImageInfo.gotFrame;
if (gotFrameAddr != null) {
if (log.isTraceEnabled()) {
log.trace(String.format("decodeImage returning avcGotFrame(0x%08X)=%b", gotFrameAddr.getAddress(), avcGotFrame));
}
gotFrameAddr.setValue(avcGotFrame);
}
if (decodedImageInfo.gotFrame) {
if (buffer == 0) {
int width = decodedImageInfo.imageWidth;
int height = decodedImageInfo.imageHeight - 18;
mpegAvcInfoStruct.setValue32(8, width);
mpegAvcInfoStruct.setValue32(12, height);
mpegAvcInfoStruct.setValue32(28, 1);
mpegAvcInfoStruct.setValue32(32, decodedImageInfo.gotFrame);
mpegAvcInfoStruct.setValue32(36, !decodedImageInfo.gotFrame);
int size1 = ((width + 16) >> 5) * (height >> 1) * 16;
int size2 = (width >> 5) * (height >> 1) * 16;
if (mpegAvcYuvMemInfo == null) {
int size = (size1 + size2) * 3 + 2 * 164;
mpegAvcYuvMemInfo = Modules.SysMemUserForUserModule.malloc(SysMemUserForUser.USER_PARTITION_ID, "mpegAvcYuv", SysMemUserForUser.PSP_SMEM_Low, size, 0);
}
int addr = mpegAvcYuvMemInfo.addr;
mpegAvcYuvStruct.setValue32(0, addr);
sceVideocodec.write(addr, size2, decodedImageInfo.luma, 0);
addr += size1;
mpegAvcYuvStruct.setValue32(4, addr);
sceVideocodec.write(addr, size2, decodedImageInfo.luma, 1 * size2);
addr += size2;
mpegAvcYuvStruct.setValue32(8, addr);
sceVideocodec.write(addr, size2, decodedImageInfo.luma, 2 * size2);
addr += size1;
mpegAvcYuvStruct.setValue32(12, addr);
sceVideocodec.write(addr, size2, decodedImageInfo.luma, 3 * size2);
addr += size2;
mpegAvcYuvStruct.setValue32(16, addr);
sceVideocodec.write(addr, size2 >> 1, decodedImageInfo.cr, 0);
addr += size1 >> 1;
mpegAvcYuvStruct.setValue32(20, addr);
sceVideocodec.write(addr, size2 >> 1, decodedImageInfo.cr, size2 >> 1);
addr += size2 >> 1;
mpegAvcYuvStruct.setValue32(24, addr);
sceVideocodec.write(addr, size2 >> 1, decodedImageInfo.cb, 0);
addr += size1 >> 1;
mpegAvcYuvStruct.setValue32(28, addr);
sceVideocodec.write(addr, size2 >> 1, decodedImageInfo.cb, size2 >> 1);
addr += size2 >> 1;
} else {
if (writeAbgr) {
writeImageABGR(buffer, frameWidth, decodedImageInfo.imageWidth, decodedImageInfo.imageHeight, pixelMode, decodedImageInfo.abgr);
} else {
writeImageYCbCr(buffer, decodedImageInfo.imageWidth, decodedImageInfo.imageHeight, decodedImageInfo.luma, decodedImageInfo.cb, decodedImageInfo.cr);
}
}
rememberLastFrame(decodedImageInfo.imageWidth, decodedImageInfo.imageHeight, decodedImageInfo.abgr);
releaseIntBuffer(decodedImageInfo.luma);
releaseIntBuffer(decodedImageInfo.cb);
releaseIntBuffer(decodedImageInfo.cr);
releaseIntBuffer(decodedImageInfo.abgr);
decodedImageInfo.luma = null;
decodedImageInfo.cb = null;
decodedImageInfo.cr = null;
decodedImageInfo.abgr = null;
videoFrameCount++;
}
return true;
}
private void restartThread(int threadUid, int buffer, int frameWidth, int pixelMode, TPointer32 gotFrameAddr, boolean writeAbgr, long threadWakeupMicroTime) {
if (threadUid < 0) {
return;
}
if (!decodeImage(buffer, frameWidth, pixelMode, gotFrameAddr, writeAbgr)) {
return;
}
videoDecoderThread.resetWaitingThreadInfo();
IAction action;
long delayMicros = threadWakeupMicroTime - Emulator.getClock().microTime();
if (delayMicros > 0L) {
if (log.isDebugEnabled()) {
log.debug(String.format("Further delaying thread=0x%X by %d microseconds", threadUid, delayMicros));
}
action = new DelayThreadAction(threadUid, (int) delayMicros, false, true);
} else {
if (log.isDebugEnabled()) {
log.debug(String.format("Unblocking thread=0x%X", threadUid));
}
action = new UnblockThreadAction(threadUid);
}
// The action cannot be executed immediately as we are running
// in a non-PSP thread. The action has to be executed by the scheduler
// as soon as possible.
Emulator.getScheduler().addAction(action);
}
private boolean isDecoderInErrorCondition() {
synchronized (decodedImages) {
if (decodedImages.size() == 0) {
return false;
}
DecodedImageInfo decodedImageInfo = decodedImages.peek();
if (decodedImageInfo.frameEnd >= 0 || decodedImageInfo.gotFrame) {
return false;
}
return true;
}
}
private void removeErrorImages() {
synchronized (decodedImages) {
while (isDecoderInErrorCondition()) {
if (log.isDebugEnabled()) {
log.debug(String.format("Removing error image %s", decodedImages.peek()));
}
decodedImages.remove();
}
}
}
private void decodeNextImage(TPointer auAddr) {
PesHeader pesHeader = new PesHeader(getRegisteredVideoChannel());
pesHeader.setDtsPts(UNKNOWN_TIMESTAMP);
DecodedImageInfo decodedImageInfo = new DecodedImageInfo();
decodedImageInfo.frameEnd = readNextVideoFrame(pesHeader, auAddr);
if (decodedImageInfo.frameEnd >= 0) {
if (videoBuffer.getLength() < decodedImageInfo.frameEnd) {
// The content of the frame is not yet completely available in the videoBuffer
return;
}
if (videoCodec == null) {
videoCodec = CodecFactory.getVideoCodec();
videoCodec.init(videoCodecExtraData);
}
synchronized (videoBuffer) {
int result = videoCodec.decode(videoBuffer.getBuffer(), videoBuffer.getBufferOffset(), decodedImageInfo.frameEnd);
if (log.isTraceEnabled()) {
byte bytes[] = new byte[decodedImageInfo.frameEnd];
int inputBuffer[] = videoBuffer.getBuffer();
int inputOffset = videoBuffer.getBufferOffset();
for (int i = 0; i < decodedImageInfo.frameEnd; i++) {
bytes[i] = (byte) inputBuffer[inputOffset + i];
}
log.trace(String.format("decodeNextImage codec returned 0x%X. Decoding 0x%X bytes from %s", result, decodedImageInfo.frameEnd, Utilities.getMemoryDump(bytes, 0, decodedImageInfo.frameEnd)));
}
if (result < 0) {
log.error(String.format("decodeNextImage codec returned 0x%08X", result));
// Skip this incorrect frame
videoBuffer.notifyRead(decodedImageInfo.frameEnd);
decodedImageInfo.gotFrame = false;
} else {
videoBuffer.notifyRead(result);
decodedImageInfo.gotFrame = videoCodec.hasImage();
if (decodedImageInfo.gotFrame) {
decodedImageInfo.imageWidth = videoCodec.getImageWidth();
decodedImageInfo.imageHeight = videoCodec.getImageHeight();
if (!getImage(decodedImageInfo)) {
return;
}
}
}
}
} else if (mpegRingbuffer != null && mpegRingbuffer.getPacketSize() == 0) {
// Do not add a new decoded image when we are decoding in low level mode
return;
}
if (videoPesHeader == null) {
videoPesHeader = new PesHeader(pesHeader);
pesHeader.setDtsPts(UNKNOWN_TIMESTAMP);
}
decodedImageInfo.pesHeader = videoPesHeader;
videoPesHeader = pesHeader;
if (decodedImageInfo.gotFrame) {
removeErrorImages();
}
if (log.isDebugEnabled()) {
log.debug(String.format("Adding decoded image %s", decodedImageInfo));
}
synchronized (decodedImages) {
decodedImages.add(decodedImageInfo);
}
}
private void hleVideoDecoderStep(int threadUid, int buffer, int frameWidth, int pixelMode, TPointer32 gotFrameAddr, boolean writeAbgr, TPointer auAddr, long threadWakeupMicroTime) {
if (log.isDebugEnabled()) {
if (threadUid >= 0) {
log.debug(String.format("hleVideoDecoderStep threadUid=0x%X, buffer=0x%08X, frameWidth=%d, pixelMode=%d, gotFrameAddr=%s, writeAbgr=%b, %d decoded images", threadUid, buffer, frameWidth, pixelMode, gotFrameAddr, writeAbgr, decodedImages.size()));
} else {
log.debug(String.format("hleVideoDecoderStep %d decoded images", decodedImages.size()));
}
}
if (buffer == 0) {
decodeNextImage(auAddr);
}
restartThread(threadUid, buffer, frameWidth, pixelMode, gotFrameAddr, writeAbgr, threadWakeupMicroTime);
// Always decode one frame in advance
if (decodedImages.size() <= 1 || isDecoderInErrorCondition()) {
decodeNextImage(auAddr);
}
}
private int read32(Memory mem, pspFileBuffer buffer) {
if (buffer.getCurrentSize() < 4) {
return 0;
}
int addr = buffer.getReadAddr();
int value;
if (buffer.getReadSize() >= 4) {
value = endianSwap32(Utilities.readUnaligned32(mem, addr));
buffer.notifyRead(4);
} else {
value = read8(mem, buffer);
value = (value << 8) | read8(mem, buffer);
value = (value << 8) | read8(mem, buffer);
value = (value << 8) | read8(mem, buffer);
}
return value;
}
private int read16(Memory mem, pspFileBuffer buffer) {
if (buffer.getCurrentSize() < 2) {
return 0;
}
int addr = buffer.getReadAddr();
int value;
if (buffer.getReadSize() >= 2) {
value = endianSwap16(readUnaligned16(mem, addr));
buffer.notifyRead(2);
} else {
value = (read8(mem, buffer) << 8) | read8(mem, buffer);
}
return value;
}
private int read8(Memory mem, pspFileBuffer buffer) {
if (buffer.getCurrentSize() < 1) {
return 0;
}
int addr = buffer.getReadAddr();
int value = mem.read8(addr);
buffer.notifyRead(1);
return value;
}
private void skip(pspFileBuffer buffer, int n) {
buffer.notifyRead(n);
}
private void addToAudioBuffer(Memory mem, pspFileBuffer buffer, int length) {
while (length > 0) {
int currentFrameLength = audioFrameLength == 0 ? 0 : audioBuffer.getLength() % audioFrameLength;
if (currentFrameLength == 0) {
// 8 bytes header:
// - byte 0: 0x0F
// - byte 1: 0xD0
// - byte 2: 0x28
// - byte 3: (frameLength - 8) / 8
// - bytes 4-7: 0x00
if (log.isTraceEnabled()) {
log.trace(String.format("Reading an audio frame from 0x%08X (length=0x%X) to the Audio buffer (already read %d)", buffer.getReadAddr(), length, frameHeaderLength));
}
while (frameHeaderLength < frameHeader.length && length > 0) {
frameHeader[frameHeaderLength++] = read8(mem, buffer);
length--;
}
if (frameHeaderLength < frameHeader.length) {
// Frame header not yet complete
break;
}
if (length == 0) {
// Frame header is complete but no data is following the header.
// Retry when some data is available
break;
}
if (frameHeader[0] != 0x0F || frameHeader[1] != 0xD0) {
if (log.isInfoEnabled()) {
log.warn(String.format("Audio frame length 0x%X with incorrect header (header: %02X %02X %02X %02X %02X %02X %02X %02X)", audioFrameLength, frameHeader[0], frameHeader[1], frameHeader[2], frameHeader[3], frameHeader[4], frameHeader[5], frameHeader[6], frameHeader[7]));
}
} else {
// Use values from the frame header only if it is valid
int frameHeader23 = (frameHeader[2] << 8) | frameHeader[3];
audioFrameLength = ((frameHeader23 & 0x3FF) << 3) + frameHeader.length;
if (log.isTraceEnabled()) {
log.trace(String.format("Audio frame length 0x%X (header: %02X %02X %02X %02X %02X %02X %02X %02X)", audioFrameLength, frameHeader[0], frameHeader[1], frameHeader[2], frameHeader[3], frameHeader[4], frameHeader[5], frameHeader[6], frameHeader[7]));
}
}
frameHeaderLength = 0;
}
int lengthToNextFrame = audioFrameLength - currentFrameLength;
int readLength = Utilities.min(length, buffer.getReadSize(), lengthToNextFrame);
int addr = buffer.getReadAddr();
if (audioBuffer.write(mem, addr, readLength) != readLength) {
log.error(String.format("AudioBuffer too small"));
}
buffer.notifyRead(readLength);
length -= readLength;
}
}
private void addToVideoBuffer(Memory mem, pspFileBuffer buffer, int length) {
while (length > 0) {
int readLength = Math.min(length, buffer.getReadSize());
int addr = buffer.getReadAddr();
addToVideoBuffer(mem, addr, readLength);
buffer.notifyRead(readLength);
length -= readLength;
}
}
public void addToVideoBuffer(Memory mem, int addr, int length) {
if (videoBuffer != null) {
videoBuffer.write(mem, addr, length);
}
}
private void addToUserDataBuffer(Memory mem, pspFileBuffer buffer, int length) {
while (length > 0) {
int readLength = Math.min(length, buffer.getReadSize());
int addr = buffer.getReadAddr();
userDataBuffer.write(mem, addr, readLength);
buffer.notifyRead(readLength);
length -= readLength;
}
}
private long readPts(Memory mem, pspFileBuffer buffer) {
return readPts(mem, buffer, read8(mem, buffer));
}
private long readPts(Memory mem, pspFileBuffer buffer, int c) {
return (((long) (c & 0x0E)) << 29) | ((read16(mem, buffer) >> 1) << 15) | (read16(mem, buffer) >> 1);
}
private int readPesHeader(Memory mem, pspFileBuffer buffer, PesHeader pesHeader, int length, int startCode) {
int c = 0;
while (length > 0) {
c = read8(mem, buffer);
length--;
if (c != 0xFF) {
break;
}
}
if ((c & 0xC0) == 0x40) {
skip(buffer, 1);
c = read8(mem, buffer);
length -= 2;
}
pesHeader.setDtsPts(UNKNOWN_TIMESTAMP);
if ((c & 0xE0) == 0x20) {
pesHeader.setDtsPts(readPts(mem, buffer, c));
length -= 4;
if ((c & 0x10) != 0) {
pesHeader.setDts(readPts(mem, buffer));
length -= 5;
}
} else if ((c & 0xC0) == 0x80) {
int flags = read8(mem, buffer);
int headerLength = read8(mem, buffer);
length -= 2;
length -= headerLength;
if ((flags & 0x80) != 0) {
pesHeader.setDtsPts(readPts(mem, buffer));
headerLength -= 5;
if ((flags & 0x40) != 0) {
pesHeader.setDts(readPts(mem, buffer));
headerLength -= 5;
}
}
if ((flags & 0x3F) != 0 && headerLength == 0) {
flags &= 0xC0;
}
if ((flags & 0x01) != 0) {
int pesExt = read8(mem, buffer);
headerLength--;
int skip = (pesExt >> 4) & 0x0B;
skip += skip & 0x09;
if ((pesExt & 0x40) != 0 || skip > headerLength) {
pesExt = skip = 0;
}
skip(buffer, skip);
headerLength -= skip;
if ((pesExt & 0x01) != 0) {
int ext2Length = read8(mem, buffer);
headerLength--;
if ((ext2Length & 0x7F) != 0) {
int idExt = read8(mem, buffer);
headerLength--;
if ((idExt & 0x80) == 0) {
startCode = ((startCode & 0xFF) << 8) | idExt;
}
}
}
}
skip(buffer, headerLength);
}
if (startCode == PRIVATE_STREAM_1) {
int channel = read8(mem, buffer);
pesHeader.setChannel(channel);
length--;
if (channel >= 0x80 && channel <= 0xCF) {
// Skip audio header
skip(buffer, 3);
length -= 3;
if (channel >= 0xB0 && channel <= 0xBF) {
skip(buffer, 1);
length--;
}
} else if (channel >= 0x20) {
// Userdata
skip(buffer, 1);
length--;
} else {
// PSP audio has additional 3 bytes in header
skip(buffer, 3);
length -= 3;
}
}
return length;
}
private void readNextAudioFrame(PesHeader pesHeader) {
if (mpegRingbuffer == null) {
return;
}
Memory mem = Memory.getInstance();
pspFileBuffer buffer = mpegRingbuffer.getAudioBuffer();
if (log.isDebugEnabled()) {
log.debug(String.format("readNextAudioFrame %s", mpegRingbuffer));
}
int audioChannel = getRegisteredAudioChannel();
boolean endOfAudio = false;
while (!endOfAudio && !buffer.isEmpty() && (audioFrameLength == 0 || audioBuffer.getLength() < audioFrameLength)) {
int startCode = read32(mem, buffer);
int codeLength;
switch (startCode) {
case PACK_START_CODE:
skip(buffer, 10);
break;
case SYSTEM_HEADER_START_CODE:
skip(buffer, 14);
break;
case PADDING_STREAM:
case PRIVATE_STREAM_2:
case 0x1E0: case 0x1E1: case 0x1E2: case 0x1E3: // Video streams
case 0x1E4: case 0x1E5: case 0x1E6: case 0x1E7:
case 0x1E8: case 0x1E9: case 0x1EA: case 0x1EB:
case 0x1EC: case 0x1ED: case 0x1EE: case 0x1EF:
codeLength = read16(mem, buffer);
skip(buffer, codeLength);
break;
case PRIVATE_STREAM_1:
// Audio stream
codeLength = read16(mem, buffer);
codeLength = readPesHeader(mem, buffer, pesHeader, codeLength, startCode);
if (pesHeader.getChannel() == audioChannel || audioChannel < 0) {
addToAudioBuffer(mem, buffer, codeLength);
} else {
skip(buffer, codeLength);
}
break;
case PSMF_MAGIC_LITTLE_ENDIAN:
// Skip any PSMF header
skip(buffer, PSMF_STREAM_OFFSET_OFFSET - 4);
int streamOffset = read32(mem, buffer);
skip(buffer, streamOffset - PSMF_STREAM_OFFSET_OFFSET - 4);
break;
default:
endOfAudio = true;
if (log.isDebugEnabled()) {
log.debug(String.format("Unknown StartCode 0x%08X at 0x%08X", startCode, buffer.getReadAddr() - 4));
}
break;
}
}
if (log.isDebugEnabled()) {
log.debug(String.format("After readNextAudioFrame %s", mpegRingbuffer));
}
}
private boolean reachedEndOfVideo() {
if (psmfHeader == null) {
return true;
}
int pendingVideoFrame = decodedImages.size() + (videoBuffer.getLength() > 0 ? 1 : 0);
if (currentVideoTimestamp + pendingVideoFrame * videoTimestampStep >= psmfHeader.mpegLastTimestamp) {
return true;
}
return false;
}
private int readNextVideoFrame(PesHeader pesHeader, TPointer auAddr) {
Memory mem = Memory.getInstance();
if (mpegRingbuffer == null || mpegRingbuffer.getPacketSize() == 0) {
if (mpegAvcAu.esSize <= 0) {
return -1;
}
int esSize = mpegAvcAu.esSize;
addToVideoBuffer(mem, mpegAvcAu.esBuffer, esSize);
mpegAvcAu.esSize = 0;
if (auAddr.isNotNull()) {
mpegAvcAu.write(auAddr);
}
return esSize;
}
pspFileBuffer buffer = mpegRingbuffer.getVideoBuffer();
if (log.isDebugEnabled()) {
log.debug(String.format("readNextVideoFrame %s", mpegRingbuffer));
}
int videoChannel = getRegisteredVideoChannel();
int frameEnd = videoBuffer.findFrameEnd();
boolean endOfVideo = false;
while (!endOfVideo && !buffer.isEmpty() && frameEnd < 0) {
int startCode = read32(mem, buffer);
int codeLength;
switch (startCode) {
case PACK_START_CODE:
skip(buffer, 10);
break;
case SYSTEM_HEADER_START_CODE:
skip(buffer, 14);
break;
case 0x1E0: case 0x1E1: case 0x1E2: case 0x1E3: // Video streams
case 0x1E4: case 0x1E5: case 0x1E6: case 0x1E7:
case 0x1E8: case 0x1E9: case 0x1EA: case 0x1EB:
case 0x1EC: case 0x1ED: case 0x1EE: case 0x1EF:
codeLength = read16(mem, buffer);
codeLength = readPesHeader(mem, buffer, pesHeader, codeLength, startCode);
if (videoChannel < 0 || videoChannel == startCode - 0x1E0) {
addToVideoBuffer(mem, buffer, codeLength);
frameEnd = videoBuffer.findFrameEnd();
// Ignore next PES headers for this current video frame
pesHeader = dummyPesHeader;
} else {
skip(buffer, codeLength);
}
break;
case PADDING_STREAM:
case PRIVATE_STREAM_2:
case PRIVATE_STREAM_1: // Audio stream
codeLength = read16(mem, buffer);
skip(buffer, codeLength);
break;
case PSMF_MAGIC_LITTLE_ENDIAN:
// Skip any PSMF header
skip(buffer, PSMF_STREAM_OFFSET_OFFSET - 4);
int streamOffset = read32(mem, buffer);
skip(buffer, streamOffset - PSMF_STREAM_OFFSET_OFFSET - 4);
break;
default:
endOfVideo = true;
if (log.isDebugEnabled()) {
log.debug(String.format("Unknown StartCode 0x%08X at 0x%08X", startCode, buffer.getReadAddr() - 4));
}
break;
}
}
// Reaching the last frame?
if (frameEnd < 0 && (buffer.isEmpty() || endOfVideo) && !videoBuffer.isEmpty()) {
if (endOfVideo || reachedEndOfVideo()) {
// There is no next frame any more but the video buffer is not yet empty,
// so use the rest of the video buffer
frameEnd = videoBuffer.getLength();
}
}
if (log.isDebugEnabled()) {
log.debug(String.format("After readNextVideoFrame frameEnd=0x%X, %s", frameEnd, mpegRingbuffer));
}
return frameEnd;
}
private void readNextUserDataFrame(PesHeader pesHeader) {
if (mpegRingbuffer == null) {
return;
}
Memory mem = Memory.getInstance();
pspFileBuffer buffer = mpegRingbuffer.getUserDataBuffer();
if (log.isDebugEnabled()) {
log.debug(String.format("readNextUserDataFrame %s", mpegRingbuffer));
}
int userDataChannel = 0x20 + getRegisteredUserDataChannel();
while (!buffer.isEmpty() && (userDataLength == 0 || userDataBuffer.getLength() < userDataLength)) {
int startCode = read32(mem, buffer);
int codeLength;
switch (startCode) {
case PACK_START_CODE:
skip(buffer, 10);
break;
case SYSTEM_HEADER_START_CODE:
skip(buffer, 14);
break;
case PADDING_STREAM:
case PRIVATE_STREAM_2:
case 0x1E0: case 0x1E1: case 0x1E2: case 0x1E3: // Video streams
case 0x1E4: case 0x1E5: case 0x1E6: case 0x1E7:
case 0x1E8: case 0x1E9: case 0x1EA: case 0x1EB:
case 0x1EC: case 0x1ED: case 0x1EE: case 0x1EF:
codeLength = read16(mem, buffer);
skip(buffer, codeLength);
break;
case PRIVATE_STREAM_1:
// Audio/Userdata stream
if (userDataLength > 0) {
// Keep only the PES header of the first data chunk
pesHeader = dummyPesHeader;
}
codeLength = read16(mem, buffer);
codeLength = readPesHeader(mem, buffer, pesHeader, codeLength, startCode);
if (pesHeader.getChannel() == userDataChannel) {
if (userDataLength == 0) {
for (int i = 0; i < userDataHeader.length; i++) {
userDataHeader[i] = read8(mem, buffer);
codeLength--;
}
userDataLength = ((userDataHeader[0] << 24) |
(userDataHeader[1] << 16) |
(userDataHeader[2] << 8) |
(userDataHeader[3] << 0)) - 4;
}
addToUserDataBuffer(mem, buffer, codeLength);
} else {
skip(buffer, codeLength);
}
break;
case PSMF_MAGIC_LITTLE_ENDIAN:
// Skip any PSMF header
skip(buffer, PSMF_STREAM_OFFSET_OFFSET - 4);
int streamOffset = read32(mem, buffer);
skip(buffer, streamOffset - PSMF_STREAM_OFFSET_OFFSET - 4);
break;
default:
log.warn(String.format("Unknown StartCode 0x%08X at 0x%08X", startCode, buffer.getReadAddr() - 4));
break;
}
}
if (log.isDebugEnabled()) {
log.debug(String.format("After readNextUserDataFrame %s", mpegRingbuffer));
}
}
public void setVideoFrameHeight(int videoFrameHeight) {
this.videoFrameHeight = videoFrameHeight;
}
private int getFrameHeight(int imageHeight) {
int frameHeight = imageHeight;
if (psmfHeader != null && psmfHeader.isValid()) {
// The decoded image height can be 290 while the header
// gives an height of 272.
frameHeight = Math.min(frameHeight, psmfHeader.getVideoHeight());
} else if (videoFrameHeight >= 0) {
// The decoded image height can be 290 while the MP4 header
// gives an height of 272.
frameHeight = Math.min(frameHeight, videoFrameHeight);
} else if (imageHeight == 290) {
// No valid PSMF header is available, but a decoded image height
// of 290 usually means an height of 272.
frameHeight = Screen.height;
}
return frameHeight;
}
private void writeImageABGR(int addr, int frameWidth, int imageWidth, int imageHeight, int pixelMode, int[] abgr) {
int frameHeight = getFrameHeight(imageHeight);
int bytesPerPixel = sceDisplay.getPixelFormatBytes(pixelMode);
if (log.isDebugEnabled()) {
log.debug(String.format("writeImageABGR addr=0x%08X-0x%08X, frameWidth=%d, frameHeight=%d, width=%d, height=%d, pixelMode=%d", addr, addr + frameWidth * frameHeight * bytesPerPixel, frameWidth, frameHeight, imageWidth, imageHeight, pixelMode));
}
IMemoryWriter memoryWriter = MemoryWriter.getMemoryWriter(addr, frameWidth * frameHeight * bytesPerPixel, bytesPerPixel);
int lineWidth = Math.min(imageWidth, frameWidth);
int lineSkip = frameWidth - lineWidth;
if (pixelMode == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888 && hasMemoryInt()) {
// Optimize the most common case
int offset = 0;
int memoryIntOffset = addr >> 2;
for (int y = 0; y < frameHeight; y++) {
System.arraycopy(abgr, offset, getMemoryInt(), memoryIntOffset, lineWidth);
memoryIntOffset += frameWidth;
offset += imageWidth;
}
} else {
// The general case with color format transformation
for (int y = 0; y < frameHeight; y++) {
int offset = y * imageWidth;
for (int x = 0; x < lineWidth; x++, offset++) {
int pixelColor = Debug.getPixelColor(abgr[offset], pixelMode);
memoryWriter.writeNext(pixelColor);
}
memoryWriter.skip(lineSkip);
}
}
memoryWriter.flush();
}
private void writeImageYCbCr(int addr, int imageWidth, int imageHeight, int[] luma, int[] cb, int[] cr) {
int frameWidth = imageWidth;
int frameHeight = imageHeight;
if (psmfHeader != null) {
// The decoded image height can be 290 while the header
// gives an height of 272.
frameHeight = Math.min(frameHeight, psmfHeader.getVideoHeight());
} else {
// No PSMF header is available assume the video is not higher than the PSP screen height
frameHeight = Math.min(frameHeight, Screen.height);
}
int width2 = frameWidth >> 1;
int height2 = frameHeight >> 1;
int length = frameWidth * frameHeight;
int length2 = width2 * height2;
if (log.isDebugEnabled()) {
log.debug(String.format("writeImageYCbCr addr=0x%08X-0x%08X, frameWidth=%d, frameHeight=%d", addr, addr + length + length2 + length2, frameWidth, frameHeight));
}
IMemoryWriter memoryWriter = MemoryWriter.getMemoryWriter(addr, length, 1);
for (int i = 0; i < length; i++) {
memoryWriter.writeNext(luma[i] & 0xFF);
}
for (int i = 0; i < length2; i++) {
memoryWriter.writeNext(cb[i] & 0xFF);
}
for (int i = 0; i < length2; i++) {
memoryWriter.writeNext(cr[i] & 0xFF);
}
memoryWriter.flush();
}
public static int[] getIntBuffer(int length) {
synchronized (intBuffers) {
for (int[] intBuffer : intBuffers) {
if (intBuffer.length >= length) {
intBuffers.remove(intBuffer);
return intBuffer;
}
}
}
return new int[length];
}
public static void releaseIntBuffer(int[] intBuffer) {
if (intBuffer == null) {
return;
}
synchronized (intBuffers) {
intBuffers.add(intBuffer);
if (intBuffers.size() > MAX_INT_BUFFERS_SIZE) {
// Remove the smallest int buffer
int[] smallestIntBuffer = null;
for (int[] buffer : intBuffers) {
if (smallestIntBuffer == null || buffer.length < smallestIntBuffer.length) {
smallestIntBuffer = buffer;
}
}
intBuffers.remove(smallestIntBuffer);
}
}
}
private boolean getImage(DecodedImageInfo decodedImageInfo) {
int width = videoCodec.getImageWidth();
int height = videoCodec.getImageHeight();
int width2 = width >> 1;
int height2 = height >> 1;
int length = width * height;
int length2 = width2 * height2;
// Allocate buffers
decodedImageInfo.luma = getIntBuffer(length);
decodedImageInfo.cb = getIntBuffer(length2);
decodedImageInfo.cr = getIntBuffer(length2);
int result = videoCodec.getImage(decodedImageInfo.luma, decodedImageInfo.cb, decodedImageInfo.cr);
if (result < 0) {
log.error(String.format("VideoCodec error 0x%08X while retrieving the image", result));
return false;
}
decodedImageInfo.abgr = getIntBuffer(length);
H264Utils.YUV2ABGR(width, height, decodedImageInfo.luma, decodedImageInfo.cb, decodedImageInfo.cr, decodedImageInfo.abgr);
return true;
}
private void resetMpegRingbuffer() {
if (mpegRingbuffer != null) {
mpegRingbuffer.reset();
}
if (videoBuffer != null) {
videoBuffer.reset();
}
if (audioBuffer != null) {
audioBuffer.reset();
}
userDataBuffer = null;
audioPesHeader = null;
videoPesHeader = null;
userDataPesHeader = null;
audioFrameLength = 0;
frameHeaderLength = 0;
userDataLength = 0;
if (decodedImages != null) {
synchronized (decodedImages) {
decodedImages.clear();
}
}
}
protected void startVideoDecoderThread() {
if (videoDecoderThread == null) {
videoDecoderThread = new VideoDecoderThread();
videoDecoderThread.setDaemon(true);
videoDecoderThread.setName("Video Decoder Thread");
videoDecoderThread.start();
}
}
public int hleMpegCreate(TPointer mpeg, TPointer data, int size, @CanBeNull TPointer ringbufferAddr, int frameWidth, int mode, int ddrtop) {
Memory mem = data.getMemory();
// Check size.
if (size < MPEG_MEMSIZE) {
log.warn("sceMpegCreate bad size " + size);
return SceKernelErrors.ERROR_MPEG_NO_MEMORY;
}
finishStreams();
// Update the ring buffer struct.
if (ringbufferAddr != null && ringbufferAddr.isNotNull()) {
mpegRingbuffer = SceMpegRingbuffer.fromMem(ringbufferAddr);
resetMpegRingbuffer();
mpegRingbuffer.setMpeg(mpeg.getAddress());
mpegRingbufferWrite();
}
// Write mpeg system handle.
mpegHandle = data.getAddress() + 0x30;
mpeg.setValue32(mpegHandle);
// Initialize fake mpeg struct.
Utilities.writeStringZ(mem, mpegHandle, "LIBMPEG.001");
mem.write32(mpegHandle + 12, -1);
if (ringbufferAddr != null) {
mem.write32(mpegHandle + 16, ringbufferAddr.getAddress());
}
if (mpegRingbuffer != null) {
mem.write32(mpegHandle + 20, mpegRingbuffer.getUpperDataAddr());
}
// Initialize mpeg values.
mpegRingbufferAddr = ringbufferAddr;
videoFrameCount = 0;
audioFrameCount = 0;
currentVideoTimestamp = 0;
currentAudioTimestamp = 0;
videoPixelMode = TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888;
defaultFrameWidth = frameWidth;
audioBuffer = new AudioBuffer(data.getAddress() + AUDIO_BUFFER_OFFSET, AUDIO_BUFFER_SIZE);
videoBuffer = new VideoBuffer();
decodedImages = new LinkedList<sceMpeg.DecodedImageInfo>();
startVideoDecoderThread();
videoDecoderThread.resetWaitingThreadInfo();
// Initialize the memory structure used by sceMpegAvcDecodeDetail2()
mpegAvcInfoStruct = new TPointer(mem, mpegHandle + 0x200); // We need a structure of 40 bytes
mpegAvcInfoStruct.clear(40);
mpegAvcYuvStruct = new TPointer(mem, mpegHandle + 0x300); // We need a structure of 44 bytes
mpegAvcYuvStruct.clear(44);
// This is the structure passed to sceVideocodecDecode
mpegAvcDetail2Struct = new TPointer(mem, mpegHandle + 0x400); // We need a structure of 96 bytes
mpegAvcDetail2Struct.clear(96);
mpegAvcDetail2Struct.setValue32(16, mpegAvcInfoStruct.getAddress());
mpegAvcDetail2Struct.setValue32(44, mpegAvcYuvStruct.getAddress());
mpegAvcDetail2Struct.setValue32(48, 0); // Unknown value
return 0;
}
public void hleMpegNotifyVideoDecoderThread() {
if (videoDecoderThread != null) {
videoDecoderThread.trigger();
}
}
protected void hleMpegRingbufferPostPut(AfterRingbufferPutCallback afterRingbufferPutCallback, int packetsAdded) {
int putDataAddr = afterRingbufferPutCallback.getPutDataAddr();
int remainingPackets = afterRingbufferPutCallback.getRemainingPackets();
mpegRingbufferRead();
if (packetsAdded > 0) {
if (log.isTraceEnabled()) {
log.trace(String.format("hleMpegRingbufferPostPut:%s", Utilities.getMemoryDump(putDataAddr, packetsAdded * mpegRingbuffer.getPacketSize())));
}
if (packetsAdded > mpegRingbuffer.getFreePackets()) {
log.warn(String.format("sceMpegRingbufferPut clamping packetsAdded old=%d, new=%d", packetsAdded, mpegRingbuffer.getFreePackets()));
packetsAdded = mpegRingbuffer.getFreePackets();
}
mpegRingbuffer.addPackets(packetsAdded);
mpegRingbufferWrite();
afterRingbufferPutCallback.addPacketsAdded(packetsAdded);
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegRingbufferPut packetsAdded=0x%X, packetsRead=0x%X, new availableSize=0x%X", packetsAdded, mpegRingbuffer.getReadPackets(), mpegRingbuffer.getFreePackets()));
}
int dataSizeInRingbuffer = mpegRingbuffer.getPacketsInRingbuffer() * mpegRingbuffer.getPacketSize();
if (psmfHeader != null && dataSizeInRingbuffer > psmfHeader.mpegStreamSize) {
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegRingbufferPut returning ERROR_MPEG_INVALID_VALUE, size of data in ringbuffer=0x%X, mpegStreamSize=0x%X", dataSizeInRingbuffer, psmfHeader.mpegStreamSize));
}
afterRingbufferPutCallback.setErrorCode(SceKernelErrors.ERROR_MPEG_INVALID_VALUE);
// No further callbacks
remainingPackets = 0;
}
removeErrorImages();
hleMpegNotifyVideoDecoderThread();
if (remainingPackets > 0) {
int putNumberPackets = Math.min(remainingPackets, mpegRingbuffer.getPutSequentialPackets());
putDataAddr = mpegRingbuffer.getPutDataAddr();
afterRingbufferPutCallback.setPutDataAddr(putDataAddr);
afterRingbufferPutCallback.setRemainingPackets(remainingPackets - putNumberPackets);
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegRingbufferPut executing callback 0x%08X to read 0x%X packets at 0x%08X", mpegRingbuffer.getCallbackAddr(), putNumberPackets, putDataAddr));
}
Modules.ThreadManForUserModule.executeCallback(null, mpegRingbuffer.getCallbackAddr(), afterRingbufferPutCallback, false, putDataAddr, putNumberPackets, mpegRingbuffer.getCallbackArgs());
}
} else {
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegRingbufferPut callback returning packetsAdded=0x%X", packetsAdded));
}
}
}
public void hleCreateRingbuffer() {
mpegRingbuffer = new SceMpegRingbuffer(0, 0, 0, 0, 0);
mpegRingbuffer.setReadPackets(1);
mpegRingbufferAddr = null;
}
public void hleMpegNotifyRingbufferRead() {
if (mpegRingbuffer != null && !mpegRingbuffer.hasReadPackets()) {
mpegRingbuffer.setReadPackets(1);
mpegRingbufferWrite();
}
}
public void setVideoCodecExtraData(int videoCodecExtraData[]) {
this.videoCodecExtraData = videoCodecExtraData;
}
public boolean hasVideoCodecExtraData() {
return videoCodecExtraData != null;
}
public void setVideoFrameSizes(int videoFrameSizes[]) {
videoBuffer.setFrameSizes(videoFrameSizes);
}
public void flushVideoFrameData() {
videoBuffer.reset();
}
public void setVideoFrame(int frame) {
videoBuffer.setFrame(frame);
}
public void hleCreateRingbuffer(int packets, int data, int size) {
mpegRingbuffer = new SceMpegRingbuffer(packets, data, size, 0, 0);
mpegRingbufferAddr = null;
}
public SceMpegRingbuffer getMpegRingbuffer() {
return mpegRingbuffer;
}
public PSMFHeader getPsmfHeader() {
return psmfHeader;
}
private int getRegisteredChannel(int streamType, int registeredChannel) {
int channel = -1;
for (StreamInfo stream : streamMap.values()) {
if (stream != null && stream.isStreamType(streamType) && stream.getAuMode() == MPEG_AU_MODE_DECODE) {
if (channel < 0 || stream.getChannel() < channel) {
channel = stream.getChannel();
if (channel == registeredChannel) {
// We have found the registered channel
break;
}
}
}
}
if (channel < 0) {
channel = registeredChannel;
}
return channel;
}
public int getRegisteredAudioChannel() {
return getRegisteredChannel(PSMF_ATRAC_STREAM, registeredAudioChannel);
}
public boolean isRegisteredAudioChannel() {
return getRegisteredAudioChannel() >= 0;
}
public int getRegisteredVideoChannel() {
return getRegisteredChannel(PSMF_AVC_STREAM, registeredVideoChannel);
}
public boolean isRegisteredVideoChannel() {
return getRegisteredVideoChannel() >= 0;
}
public boolean isRegisteredUserDataChannel() {
return getRegisteredUserDataChannel() >= 0;
}
public int getRegisteredPcmChannel() {
return getRegisteredChannel(PSMF_PCM_STREAM, -1);
}
public int getRegisteredUserDataChannel() {
return getRegisteredChannel(PSMF_DATA_STREAM, -1);
}
public void setRegisteredVideoChannel(int registeredVideoChannel) {
if (this.registeredVideoChannel != registeredVideoChannel) {
this.registeredVideoChannel = registeredVideoChannel;
}
}
public void setRegisteredAudioChannel(int registeredAudioChannel) {
this.registeredAudioChannel = registeredAudioChannel;
}
public long getCurrentVideoTimestamp() {
return currentVideoTimestamp;
}
public long getCurrentAudioTimestamp() {
return currentAudioTimestamp;
}
public int hleMpegGetAvcAu(TPointer auAddr) {
int result = 0;
// Read Au of next Avc frame
if (isRegisteredVideoChannel()) {
startVideoDecoderThread();
DecodedImageInfo decodedImageInfo;
while (true) {
synchronized (decodedImages) {
decodedImageInfo = decodedImages.peek();
}
if (decodedImageInfo != null) {
break;
}
// Wait for the video decoder thread
if (log.isDebugEnabled()) {
log.debug(String.format("hleMpegGetAvcAu waiting for the video decoder thread..."));
}
Utilities.sleep(1);
}
if (log.isDebugEnabled()) {
log.debug(String.format("hleMpegGetAvcAu decodedImageInfo: %s", decodedImageInfo));
}
mpegAvcAu.pts = decodedImageInfo.pesHeader.getPts();
mpegAvcAu.dts = decodedImageInfo.pesHeader.getDts();
mpegAvcAu.esSize = Math.max(0, decodedImageInfo.frameEnd);
if (auAddr != null && auAddr.isNotNull()) {
mpegAvcAu.write(auAddr);
}
// Packets from the ringbuffer are consumed during sceMpegGetXXXAu(),
// they are not consumed during sceMpegXXXDecode().
mpegRingbufferNotifyRead();
mpegRingbufferWrite();
if (decodedImageInfo.frameEnd < 0) {
// Return an error only past the last video timestamp
if (psmfHeader == null || currentVideoTimestamp > psmfHeader.mpegLastTimestamp || !isRegisteredAudioChannel()) {
result = SceKernelErrors.ERROR_MPEG_NO_DATA;
}
}
}
if (result == 0) {
if (mpegAvcAu.pts != UNKNOWN_TIMESTAMP) {
currentVideoTimestamp = mpegAvcAu.pts;
} else {
currentVideoTimestamp += videoTimestampStep;
}
}
if (log.isDebugEnabled()) {
log.debug(String.format("hleMpegGetAvcAu returning 0x%08X, AvcAu=%s", result, mpegAvcAu));
}
if (result != 0) {
delayThread(mpegDecodeErrorDelay);
}
startedMpeg = true;
return result;
}
public int hleMpegGetAtracAu(TPointer auAddr) {
int result = 0;
if (isRegisteredAudioChannel()) {
mpegAtracAu.esSize = audioFrameLength == 0 ? 0 : audioFrameLength + 8;
mpegRingbufferNotifyRead();
if (audioFrameLength == 0 || audioBuffer == null || audioBuffer.getLength() < audioFrameLength) {
boolean needUpdateAu;
if (audioPesHeader == null) {
audioPesHeader = new PesHeader(getRegisteredAudioChannel());
needUpdateAu = true;
} else {
// Take the PTS from the previous PES header.
mpegAtracAu.pts = audioPesHeader.getPts();
// On PSP, the audio DTS is always set to -1
mpegAtracAu.dts = UNKNOWN_TIMESTAMP;
if (auAddr != null && auAddr.isNotNull()) {
mpegAtracAu.write(auAddr);
}
needUpdateAu = false;
}
audioPesHeader.setDtsPts(UNKNOWN_TIMESTAMP);
readNextAudioFrame(audioPesHeader);
if (needUpdateAu) {
// Take the PTS from the first PES header and reset it.
mpegAtracAu.pts = audioPesHeader.getPts();
audioPesHeader.setDtsPts(UNKNOWN_TIMESTAMP);
// On PSP, the audio DTS is always set to -1
mpegAtracAu.dts = UNKNOWN_TIMESTAMP;
if (auAddr != null && auAddr.isNotNull()) {
mpegAtracAu.write(auAddr);
}
}
if (audioBuffer.getLength() < audioFrameLength) {
// It seems that sceMpegGetAtracAu returns ERROR_MPEG_NO_DATA only when both
// the audio and the video have reached the end of the stream.
// No error is returned when only the audio has reached the end of the stream.
if (psmfHeader == null || currentVideoTimestamp > psmfHeader.mpegLastTimestamp) {
result = SceKernelErrors.ERROR_MPEG_NO_DATA;
}
} else if (audioFrameLength <= 0 && (psmfHeader == null || psmfHeader.getSpecificStreamNum(PSMF_AUDIO_STREAM) <= 0)) {
// There is no audio stream, return ERROR_MPEG_NO_DATA
result = SceKernelErrors.ERROR_MPEG_NO_DATA;
} else {
// Update the ringbuffer only in case of no error
mpegRingbufferWrite();
}
} else {
// Take the PTS from the previous PES header and reset it.
mpegAtracAu.pts = audioPesHeader.getPts();
audioPesHeader.setDtsPts(UNKNOWN_TIMESTAMP);
// On PSP, the audio DTS is always set to -1
mpegAtracAu.dts = UNKNOWN_TIMESTAMP;
if (auAddr != null && auAddr.isNotNull()) {
mpegAtracAu.write(auAddr);
}
mpegRingbufferWrite();
}
}
if (result == 0) {
if (mpegAtracAu.pts != UNKNOWN_TIMESTAMP) {
currentAudioTimestamp = mpegAtracAu.pts;
} else {
currentAudioTimestamp += audioTimestampStep;
}
}
if (log.isDebugEnabled()) {
log.debug(String.format("hleMpegGetAtracAu returning result=0x%08X, %s", result, mpegAtracAu));
}
return result;
}
public int hleMpegAtracDecode(TPointer auAddr, TPointer bufferAddr, int bufferSize) {
int result = 0;
int bytes = 0;
if (audioBuffer != null && audioFrameLength > 0 && audioBuffer.getLength() >= audioFrameLength) {
int channels = psmfHeader == null ? 2 : psmfHeader.getAudioChannelConfig();
if (audioCodec == null) {
audioCodec = CodecFactory.getCodec(PSP_CODEC_AT3PLUS);
result = audioCodec.init(audioFrameLength, channels, mpegAudioOutputChannels, 0);
}
result = audioCodec.decode(audioBuffer.getReadAddr(), audioFrameLength, bufferAddr.getAddress());
if (result < 0) {
log.error(String.format("Error received from codec.decode: 0x%08X", result));
} else {
if (log.isTraceEnabled()) {
log.trace(String.format("sceMpegAtracDecode codec returned 0x%X. Decoding from %s", result, Utilities.getMemoryDump(audioBuffer.getReadAddr(), audioFrameLength)));
}
bytes = audioCodec.getNumberOfSamples() * 2 * channels;
}
if (audioBuffer.notifyRead(Memory.getInstance(), audioFrameLength) != audioFrameLength) {
log.error(String.format("Internal error while consuming from the audio buffer"));
}
startedMpeg = true;
audioFrameCount++;
delayThread(atracDecodeDelay);
result = 0;
}
// Fill the rest of the buffer with 0's
bufferAddr.clear(bytes, bufferSize - bytes);
if (auAddr != null && auAddr.isNotNull()) {
mpegAtracAu.write(auAddr);
}
return 0;
}
public int hleMpegAvcDecode(int buffer, int frameWidth, int pixelMode, TPointer32 gotFrameAddr, boolean writeAbgr, TPointer auAddr) {
int threadUid = Modules.ThreadManForUserModule.getCurrentThreadID();
Modules.ThreadManForUserModule.hleBlockCurrentThread(SceKernelThreadInfo.JPCSP_WAIT_VIDEO_DECODER);
videoDecoderThread.trigger(threadUid, buffer, frameWidth, pixelMode, gotFrameAddr, writeAbgr, auAddr, Emulator.getClock().microTime() + avcDecodeDelay);
return 0;
}
public static Date convertTimestampToDate(long timestamp) {
long millis = timestamp / (mpegTimestampPerSecond / 1000);
return new Date(millis);
}
protected StreamInfo getStreamInfo(int uid) {
return streamMap.get(uid);
}
protected int getMpegHandle(int mpegAddr) {
if (Memory.isAddressGood(mpegAddr)) {
return Processor.memory.read32(mpegAddr);
}
return -1;
}
public int checkMpegHandle(int mpeg) {
if (getMpegHandle(mpeg) != mpegHandle) {
log.warn(String.format("checkMpegHandler bad mpeg handle 0x%08X", mpeg));
throw new SceKernelErrorException(-1);
}
return mpeg;
}
protected void writeTimestamp(Memory mem, int address, long ts) {
mem.write32(address, (int) ((ts >> 32) & 0x1));
mem.write32(address + 4, (int) ts);
}
public static int getMpegVersion(int mpegRawVersion) {
switch (mpegRawVersion) {
case PSMF_VERSION_0012: return MPEG_VERSION_0012;
case PSMF_VERSION_0013: return MPEG_VERSION_0013;
case PSMF_VERSION_0014: return MPEG_VERSION_0014;
case PSMF_VERSION_0015: return MPEG_VERSION_0015;
}
return -1;
}
public static int read8(Memory mem, int bufferAddr, byte[] buffer, int offset) {
if (buffer != null) {
return Utilities.read8(buffer, offset);
}
return mem.read8(bufferAddr + offset);
}
public static int readUnaligned32(Memory mem, int bufferAddr, byte[] buffer, int offset) {
if (buffer != null) {
return Utilities.readUnaligned32(buffer, offset);
}
return Utilities.readUnaligned32(mem, bufferAddr + offset);
}
public static int read32(Memory mem, int bufferAddr, byte[] buffer, int offset) {
if (buffer != null) {
return Utilities.readUnaligned32(buffer, offset);
}
return mem.read32(bufferAddr + offset);
}
public static int read16(Memory mem, int bufferAddr, byte[] buffer, int offset) {
if (buffer != null) {
return Utilities.readUnaligned16(buffer, offset);
}
return mem.read16(bufferAddr + offset);
}
protected void analyseMpeg(int bufferAddr, byte[] mpegHeader) {
psmfHeader = new PSMFHeader(bufferAddr, mpegHeader);
avcDecodeResult = MPEG_AVC_DECODE_SUCCESS;
avcGotFrame = false;
if (mpegRingbuffer != null) {
resetMpegRingbuffer();
mpegRingbufferWrite();
}
mpegAtracAu.dts = UNKNOWN_TIMESTAMP;
mpegAtracAu.pts = 0;
mpegAvcAu.dts = 0;
mpegAvcAu.pts = 0;
videoFrameCount = 0;
audioFrameCount = 0;
currentVideoTimestamp = 0;
currentAudioTimestamp = 0;
}
protected void analyseMpeg(int bufferAddr) {
analyseMpeg(bufferAddr, null);
if (log.isDebugEnabled()) {
log.debug(String.format("Stream offset: 0x%X, Stream size: 0x%X", psmfHeader.mpegOffset, psmfHeader.mpegStreamSize));
log.debug(String.format("First timestamp: %d, Last timestamp: %d", psmfHeader.mpegFirstTimestamp, psmfHeader.mpegLastTimestamp));
if (log.isTraceEnabled()) {
log.trace(Utilities.getMemoryDump(bufferAddr, MPEG_HEADER_BUFFER_MINIMUM_SIZE));
}
}
}
protected boolean hasPsmfStream(int streamType) {
if (psmfHeader == null || psmfHeader.psmfStreams == null) {
// Header not analyzed, assume that the PSMF has the given stream
return true;
}
for (PSMFStream stream : psmfHeader.psmfStreams) {
if (stream.isStreamOfType(streamType)) {
return true;
}
}
return false;
}
protected boolean hasPsmfVideoStream() {
return hasPsmfStream(PSMF_AVC_STREAM);
}
protected boolean hasPsmfAudioStream() {
return hasPsmfStream(PSMF_AUDIO_STREAM);
}
protected boolean hasPsmfUserdataStream() {
return hasPsmfStream(PSMF_DATA_STREAM);
}
public static void generateFakeImage(int dest_addr, int frameWidth, int imageWidth, int imageHeight, int pixelMode) {
Memory mem = Memory.getInstance();
Random random = new Random();
final int pixelSize = 3;
final int bytesPerPixel = sceDisplay.getPixelFormatBytes(pixelMode);
for (int y = 0; y < imageHeight - pixelSize + 1; y += pixelSize) {
int address = dest_addr + y * frameWidth * bytesPerPixel;
final int width = Math.min(imageWidth, frameWidth);
for (int x = 0; x < width; x += pixelSize) {
int n = random.nextInt(256);
int color = 0xFF000000 | (n << 16) | (n << 8) | n;
int pixelColor = Debug.getPixelColor(color, pixelMode);
if (bytesPerPixel == 4) {
for (int i = 0; i < pixelSize; i++) {
for (int j = 0; j < pixelSize; j++) {
mem.write32(address + (i * frameWidth + j) * 4, pixelColor);
}
}
} else if (bytesPerPixel == 2) {
for (int i = 0; i < pixelSize; i++) {
for (int j = 0; j < pixelSize; j++) {
mem.write16(address + (i * frameWidth + j) * 2, (short) pixelColor);
}
}
}
address += pixelSize * bytesPerPixel;
}
}
}
public static void delayThread(long startMicros, int delayMicros) {
long now = Emulator.getClock().microTime();
int threadDelayMicros = delayMicros - (int) (now - startMicros);
delayThread(threadDelayMicros);
}
public static void delayThread(int delayMicros) {
if (delayMicros > 0) {
Modules.ThreadManForUserModule.hleKernelDelayThread(delayMicros, false);
} else {
Modules.ThreadManForUserModule.hleRescheduleCurrentThread();
}
}
protected void finishStreams() {
if (log.isDebugEnabled()) {
log.debug("finishStreams");
}
// Release all the streams (can't loop on streamMap as release() modifies it)
List<StreamInfo> streams = new LinkedList<sceMpeg.StreamInfo>();
streams.addAll(streamMap.values());
for (StreamInfo stream : streams) {
stream.release();
}
}
protected void finishMpeg() {
if (log.isDebugEnabled()) {
log.debug("finishMpeg");
}
resetMpegRingbuffer();
mpegRingbufferWrite();
VideoEngine.getInstance().resetVideoTextures();
registeredVideoChannel = -1;
registeredAudioChannel = -1;
mpegAtracAu.dts = UNKNOWN_TIMESTAMP;
mpegAtracAu.pts = 0;
mpegAvcAu.dts = 0;
mpegAvcAu.pts = 0;
videoFrameCount = 0;
audioFrameCount = 0;
currentVideoTimestamp = 0;
currentAudioTimestamp = 0;
startedMpeg = false;
videoFrameHeight = -1;
if (videoDecoderThread != null) {
videoDecoderThread.resetWaitingThreadInfo();
}
videoCodec = null;
}
protected void checkEmptyVideoRingbuffer() {
if (mpegAvcAu.esSize > 0) {
return;
}
if (mpegRingbuffer == null) {
log.warn("ringbuffer not created");
throw new SceKernelErrorException(SceKernelErrors.ERROR_MPEG_NO_DATA); // No more data in ringbuffer.
}
if (!mpegRingbuffer.hasReadPackets() || (mpegRingbuffer.isEmpty() && videoBuffer.isEmpty() && decodedImages.isEmpty())) {
delayThread(mpegDecodeErrorDelay);
log.debug("ringbuffer and video buffer are empty");
throw new SceKernelErrorException(SceKernelErrors.ERROR_MPEG_NO_DATA); // No more data in ringbuffer.
}
}
protected void checkEmptyAudioRingbuffer() {
if (!mpegRingbuffer.hasReadPackets() || (mpegRingbuffer.isEmpty() && audioBuffer.isEmpty())) {
log.debug("ringbuffer and audio buffer are empty");
delayThread(mpegDecodeErrorDelay);
throw new SceKernelErrorException(SceKernelErrors.ERROR_MPEG_NO_DATA); // No more data in ringbuffer.
}
}
protected int getYCbCrSize() {
int width = psmfHeader == null ? Screen.width : psmfHeader.getVideoWidth();
int height = psmfHeader == null ? Screen.height : psmfHeader.getVideoHeight();
return getYCbCrSize(width, height);
}
protected int getYCbCrSize(int width, int height) {
return (width / 2) * (height / 2) * 6; // 12 bits per pixel
}
public void setMpegAvcAu(SceMpegAu au) {
mpegAvcAu.esBuffer = au.esBuffer;
mpegAvcAu.esSize = au.esSize;
}
/**
* sceMpegQueryStreamOffset
*
* @param mpeg
* @param bufferAddr
* @param offsetAddr
*
* @return
*/
@HLEFunction(nid = 0x21FF80E4, version = 150, checkInsideInterrupt = true, stackUsage = 0x18)
public int sceMpegQueryStreamOffset(@CheckArgument("checkMpegHandle") int mpeg, TPointer bufferAddr, @BufferInfo(usage=Usage.out) TPointer32 offsetAddr) {
analyseMpeg(bufferAddr.getAddress());
// Check magic.
if (psmfHeader.mpegMagic != PSMF_MAGIC) {
log.warn("sceMpegQueryStreamOffset bad magic " + String.format("0x%08X", psmfHeader.mpegMagic));
offsetAddr.setValue(0);
return SceKernelErrors.ERROR_MPEG_INVALID_VALUE;
}
// Check version.
if (psmfHeader.mpegVersion < 0) {
log.warn("sceMpegQueryStreamOffset bad version " + String.format("0x%08X", psmfHeader.mpegRawVersion));
offsetAddr.setValue(0);
return SceKernelErrors.ERROR_MPEG_BAD_VERSION;
}
// Check offset.
if ((psmfHeader.mpegOffset & 2047) != 0 || psmfHeader.mpegOffset == 0) {
log.warn("sceMpegQueryStreamOffset bad offset " + String.format("0x%08X", psmfHeader.mpegOffset));
offsetAddr.setValue(0);
return SceKernelErrors.ERROR_MPEG_INVALID_VALUE;
}
offsetAddr.setValue(psmfHeader.mpegOffset);
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegQueryStreamOffset returning 0x%X", offsetAddr.getValue()));
}
return 0;
}
/**
* sceMpegQueryStreamSize
*
* @param bufferAddr
* @param sizeAddr
*
* @return
*/
@HLEFunction(nid = 0x611E9E11, version = 150, checkInsideInterrupt = true, stackUsage = 0x8)
public int sceMpegQueryStreamSize(TPointer bufferAddr, @BufferInfo(usage=Usage.out) TPointer32 sizeAddr) {
analyseMpeg(bufferAddr.getAddress());
// Check magic.
if (psmfHeader.mpegMagic != PSMF_MAGIC) {
log.warn(String.format("sceMpegQueryStreamSize bad magic 0x%08X", psmfHeader.mpegMagic));
return -1;
}
// Check alignment.
if ((psmfHeader.mpegStreamSize & 2047) != 0) {
sizeAddr.setValue(0);
return SceKernelErrors.ERROR_MPEG_INVALID_VALUE;
}
sizeAddr.setValue(psmfHeader.mpegStreamSize);
return 0;
}
/**
* sceMpegInit
*
* @return
*/
@HLELogging(level="info")
@HLEFunction(nid = 0x682A619B, version = 150, checkInsideInterrupt = true, stackUsage = 0x48)
public int sceMpegInit() {
finishMpeg();
finishStreams();
return 0;
}
/**
* sceMpegFinish
*
* @return
*/
@HLELogging(level="info")
@HLEFunction(nid = 0x874624D6, version = 150, checkInsideInterrupt = true, stackUsage = 0x18)
public int sceMpegFinish() {
finishMpeg();
finishStreams();
return 0;
}
/**
* sceMpegQueryMemSize
*
* @param mode
*
* @return
*/
@HLEFunction(nid = 0xC132E22F, version = 150, checkInsideInterrupt = true)
public int sceMpegQueryMemSize(int mode) {
// Mode = 0 -> 64k (constant).
return MPEG_MEMSIZE;
}
/**
* sceMpegCreate
*
* @param mpeg
* @param data
* @param size
* @param ringbufferAddr
* @param frameWidth
* @param mode
* @param ddrtop
*
* @return
*/
@HLEFunction(nid = 0xD8C5F121, version = 150, checkInsideInterrupt = true, stackUsage = 0xA8)
public int sceMpegCreate(TPointer mpeg, TPointer data, int size, @CanBeNull TPointer ringbufferAddr, int frameWidth, int mode, int ddrtop) {
return hleMpegCreate(mpeg, data, size, ringbufferAddr, frameWidth, mode, ddrtop);
}
/**
* sceMpegDelete
*
* @param mpeg
*
* @return
*/
@HLEFunction(nid = 0x606A4649, version = 150, checkInsideInterrupt = true, stackUsage = 0x28)
public int sceMpegDelete(@CheckArgument("checkMpegHandle") int mpeg) {
if (videoDecoderThread != null) {
videoDecoderThread.exit();
videoDecoderThread = null;
}
finishMpeg();
finishStreams();
Modules.ThreadManForUserModule.hleKernelDelayThread(sceVideocodec.videocodecDeleteDelay, false);
return 0;
}
/**
* sceMpegRegistStream
*
* @param mpeg
* @param streamType
* @param streamNum
*
* @return stream Uid
*/
@HLEFunction(nid = 0x42560F23, version = 150, checkInsideInterrupt = true, stackUsage = 0x48)
public int sceMpegRegistStream(@CheckArgument("checkMpegHandle") int mpeg, int streamType, int streamChannelNum) {
StreamInfo info = new StreamInfo(streamType, streamChannelNum);
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegRegistStream returning 0x%X", info.getUid()));
}
return info.getUid();
}
/**
* sceMpegUnRegistStream
*
* @param mpeg
* @param streamUid
*
* @return
*/
@HLEFunction(nid = 0x591A4AA2, version = 150, checkInsideInterrupt = true, stackUsage = 0x18)
public int sceMpegUnRegistStream(@CheckArgument("checkMpegHandle") int mpeg, int streamUid) {
StreamInfo info = getStreamInfo(streamUid);
if (info == null) {
log.warn(String.format("sceMpegUnRegistStream unknown stream=0x%X", streamUid));
return SceKernelErrors.ERROR_MPEG_UNKNOWN_STREAM_ID;
}
info.release();
return 0;
}
/**
* sceMpegMallocAvcEsBuf
*
* @param mpeg
*
* @return
*/
@HLEFunction(nid = 0xA780CF7E, version = 150, checkInsideInterrupt = true, stackUsage = 0x18)
public int sceMpegMallocAvcEsBuf(@CheckArgument("checkMpegHandle") int mpeg) {
// sceMpegMallocAvcEsBuf does not allocate any memory.
// It returns 0x00000001 for the first call,
// 0x00000002 for the second call
// and 0x00000000 for subsequent calls.
int esBufferId = 0;
for (int i = 0; i < allocatedEsBuffers.length; i++) {
if (!allocatedEsBuffers[i]) {
esBufferId = i + 1;
allocatedEsBuffers[i] = true;
break;
}
}
return esBufferId;
}
/**
* sceMpegFreeAvcEsBuf
*
* @param mpeg
* @param esBuf
*
* @return
*/
@HLEFunction(nid = 0xCEB870B1, version = 150, checkInsideInterrupt = true, stackUsage = 0x28)
public int sceMpegFreeAvcEsBuf(@CheckArgument("checkMpegHandle") int mpeg, int esBuf) {
if (esBuf == 0) {
log.warn("sceMpegFreeAvcEsBuf(mpeg=0x" + Integer.toHexString(mpeg) + ", esBuf=0x" + Integer.toHexString(esBuf) + ") bad esBuf handle");
return SceKernelErrors.ERROR_MPEG_INVALID_VALUE;
}
if (esBuf >= 1 && esBuf <= allocatedEsBuffers.length) {
allocatedEsBuffers[esBuf - 1] = false;
}
return 0;
}
/**
* sceMpegQueryAtracEsSize
*
* @param mpeg
* @param esSize_addr
* @param outSize_addr
*
* @return
*/
@HLEFunction(nid = 0xF8DCB679, version = 150, checkInsideInterrupt = true, stackUsage = 0x18)
public int sceMpegQueryAtracEsSize(@CheckArgument("checkMpegHandle") int mpeg, @CanBeNull @BufferInfo(usage=Usage.out) TPointer32 esSizeAddr, @CanBeNull @BufferInfo(usage=Usage.out) TPointer32 outSizeAddr) {
esSizeAddr.setValue(MPEG_ATRAC_ES_SIZE);
outSizeAddr.setValue(MPEG_ATRAC_ES_OUTPUT_SIZE);
return 0;
}
/**
* sceMpegQueryPcmEsSize
*
* @param mpeg
* @param esSize_addr
* @param outSize_addr
*
* @return
*/
@HLEFunction(nid = 0xC02CF6B5, version = 150, checkInsideInterrupt = true)
public int sceMpegQueryPcmEsSize(@CheckArgument("checkMpegHandle") int mpeg, TPointer32 esSizeAddr, TPointer32 outSizeAddr) {
esSizeAddr.setValue(MPEG_PCM_ES_SIZE);
outSizeAddr.setValue(MPEG_PCM_ES_OUTPUT_SIZE);
return 0;
}
/**
* sceMpegInitAu
*
* @param mpeg
* @param buffer_addr
* @param auAddr
*
* @return
*/
@HLEFunction(nid = 0x167AFD9E, version = 150, checkInsideInterrupt = true, stackUsage = 0x18)
public int sceMpegInitAu(@CheckArgument("checkMpegHandle") int mpeg, int buffer_addr, TPointer auAddr) {
// Check if sceMpegInitAu is being called for AVC or ATRAC
// and write the proper AU (access unit) struct.
if (buffer_addr >= 1 && buffer_addr <= allocatedEsBuffers.length && allocatedEsBuffers[buffer_addr - 1]) {
mpegAvcAu.esBuffer = buffer_addr;
mpegAvcAu.esSize = 0;
mpegAvcAu.write(auAddr);
} else {
mpegAtracAu.esBuffer = buffer_addr;
mpegAtracAu.esSize = 0;
mpegAtracAu.write(auAddr);
}
return 0;
}
/**
* sceMpegChangeGetAvcAuMode
*
* @param mpeg
* @param stream_addr
* @param mode
*
* @return
*/
@HLEUnimplemented
@HLEFunction(nid = 0x234586AE, version = 150, checkInsideInterrupt = true)
public int sceMpegChangeGetAvcAuMode(int mpeg, int stream_addr, int mode) {
return 0;
}
/**
* sceMpegChangeGetAuMode
*
* @param mpeg
* @param streamUid
* @param mode
*
* @return
*/
@HLEFunction(nid = 0x9DCFB7EA, version = 150, checkInsideInterrupt = true)
public int sceMpegChangeGetAuMode(int mpeg, int streamUid, int mode) {
StreamInfo info = getStreamInfo(streamUid);
if (info == null) {
log.warn(String.format("sceMpegChangeGetAuMode unknown stream=0x%X", streamUid));
return -1;
}
// When changing a stream from SKIP to DECODE mode,
// change all the other streams of the same type to SKIP mode.
// There is only on stream of a given type in DECODE mode.
if (info.getAuMode() == MPEG_AU_MODE_SKIP && mode == MPEG_AU_MODE_DECODE) {
for (StreamInfo stream : streamMap.values()) {
if (stream != null && stream != info && stream.isStreamType(info.getType()) && stream.getAuMode() == MPEG_AU_MODE_DECODE) {
stream.setAuMode(MPEG_AU_MODE_SKIP);
}
}
}
info.setAuMode(mode);
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegChangeGetAuMode mode=%s: %s", mode == MPEG_AU_MODE_DECODE ? "DECODE" : "SKIP", info));
}
return 0;
}
/**
* sceMpegGetAvcAu
*
* @param mpeg
* @param streamUid
* @param au_addr
* @param attr_addr
*
* @return
*/
@HLEFunction(nid = 0xFE246728, version = 150, checkInsideInterrupt = true)
public int sceMpegGetAvcAu(@CheckArgument("checkMpegHandle") int mpeg, int streamUid, @BufferInfo(lengthInfo=LengthInfo.fixedLength, length=24, usage=Usage.out) TPointer auAddr, @CanBeNull TPointer32 attrAddr) {
mpegRingbufferRead();
if (auAddr != null && auAddr.isNotNull()) {
mpegAvcAu.read(auAddr);
}
checkEmptyVideoRingbuffer();
// @NOTE: Shouldn't this be negated?
if (Memory.isAddressGood(streamUid)) {
log.warn("sceMpegGetAvcAu didn't get a fake stream");
return SceKernelErrors.ERROR_MPEG_INVALID_ADDR;
}
if (!streamMap.containsKey(streamUid)) {
log.warn(String.format("sceMpegGetAvcAu bad stream 0x%X", streamUid));
return -1;
}
int result = 0;
// Update the video timestamp (AVC).
if (isRegisteredVideoChannel()) {
result = hleMpegGetAvcAu(auAddr);
}
attrAddr.setValue(1); // Unknown.
if (log.isDebugEnabled()) {
log.debug(String.format("videoFrameCount=%d(pts=%d), audioFrameCount=%d(pts=%d), pts difference %d, vcount=%d", videoFrameCount, currentVideoTimestamp, audioFrameCount, currentAudioTimestamp, currentAudioTimestamp - currentVideoTimestamp, Modules.sceDisplayModule.getVcount()));
}
return result;
}
/**
* sceMpegGetPcmAu
*
* @param mpeg
* @param streamUid
* @param au_addr
* @param attr_addr
*
* @return
*/
@HLEFunction(nid = 0x8C1E027D, version = 150, checkInsideInterrupt = true)
public int sceMpegGetPcmAu(@CheckArgument("checkMpegHandle") int mpeg, int streamUid, @BufferInfo(lengthInfo=LengthInfo.fixedLength, length=24, usage=Usage.out) TPointer auAddr, @CanBeNull TPointer32 attrAddr) {
mpegRingbufferRead();
if (!mpegRingbuffer.hasReadPackets() || mpegRingbuffer.isEmpty()) {
delayThread(mpegDecodeErrorDelay);
return SceKernelErrors.ERROR_MPEG_NO_DATA; // No more data in ringbuffer.
}
// Should be negated?
if (Memory.isAddressGood(streamUid)) {
log.warn("sceMpegGetPcmAu didn't get a fake stream");
return SceKernelErrors.ERROR_MPEG_INVALID_ADDR;
}
if (!streamMap.containsKey(streamUid)) {
log.warn(String.format("sceMpegGetPcmAu bad streamUid 0x%08X", streamUid));
return -1;
}
int result = 0;
// Update the audio timestamp (Atrac).
if (getRegisteredPcmChannel() >= 0) {
// Read Au of next Atrac frame
mpegAtracAu.write(auAddr);
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegGetPcmAu returning AtracAu=%s", mpegAtracAu.toString()));
}
}
// Bitfield used to store data attributes.
// Uses same bitfield as the one in the PSMF header.
int attr = 1 << 7; // Sampling rate (1 = 44.1kHz).
attr |= 2; // Number of channels (1 - MONO / 2 - STEREO).
attrAddr.setValue(attr);
if (result != 0) {
delayThread(mpegDecodeErrorDelay);
}
return result;
}
/**
* sceMpegGetAtracAu
*
* @param mpeg
* @param streamUid
* @param auAddr
* @param attrAddr
*
* @return
*/
@HLEFunction(nid = 0xE1CE83A7, version = 150, checkInsideInterrupt = true)
public int sceMpegGetAtracAu(@CheckArgument("checkMpegHandle") int mpeg, int streamUid, @BufferInfo(lengthInfo=LengthInfo.fixedLength, length=24, usage=Usage.out) TPointer auAddr, @CanBeNull TPointer32 attrAddr) {
mpegRingbufferRead();
checkEmptyAudioRingbuffer();
if (Memory.isAddressGood(streamUid)) {
log.warn("sceMpegGetAtracAu didn't get a fake stream");
return SceKernelErrors.ERROR_MPEG_INVALID_ADDR;
}
if (!streamMap.containsKey(streamUid)) {
log.warn("sceMpegGetAtracAu bad address " + String.format("0x%08X 0x%08X", streamUid, auAddr));
return -1;
}
// Update the audio timestamp (Atrac).
int result = hleMpegGetAtracAu(auAddr);
// Bitfield used to store data attributes.
attrAddr.setValue(0); // Pointer to ATRAC3plus stream (from PSMF file).
if (log.isDebugEnabled()) {
log.debug(String.format("videoFrameCount=%d(pts=%d), audioFrameCount=%d(pts=%d), pts difference %d, vcount=%d", videoFrameCount, currentVideoTimestamp, audioFrameCount, currentAudioTimestamp, currentAudioTimestamp - currentVideoTimestamp, Modules.sceDisplayModule.getVcount()));
}
return result;
}
/**
* sceMpegFlushStream
*
* @param mpeg
* @param stream_addr
*
* @return
*/
@HLEFunction(nid = 0x500F0429, version = 150, checkInsideInterrupt = true)
public int sceMpegFlushStream(int mpeg, int stream_addr) {
// This call is not deleting the registered streams.
finishMpeg();
return 0;
}
/**
* sceMpegFlushAllStream
*
* @param mpeg
*
* @return
*/
@HLEFunction(nid = 0x707B7629, version = 150, checkInsideInterrupt = true)
public int sceMpegFlushAllStream(int mpeg) {
// Finish the Mpeg only if we are not at the start of a new video,
// otherwise the analyzed video could be lost.
// This call is not deleting the registered streams.
if (startedMpeg) {
finishMpeg();
}
return 0;
}
/**
* sceMpegAvcDecode
*
* @param mpeg
* @param au_addr
* @param frameWidth
* @param buffer_addr
* @param init_addr
*
* @return
*/
@HLEFunction(nid = 0x0E3C2E9D, version = 150, checkInsideInterrupt = true)
public int sceMpegAvcDecode(@CheckArgument("checkMpegHandle") int mpeg, @DebugMemory @BufferInfo(lengthInfo=LengthInfo.fixedLength, length=24, usage=Usage.inout) TPointer auAddr, int frameWidth, @CanBeNull TPointer32 bufferAddr, @BufferInfo(usage=Usage.out) TPointer32 gotFrameAddr) {
int au = auAddr.getValue32();
int buffer = 0;
if (bufferAddr.isNotNull()) {
buffer = bufferAddr.getValue();
}
if (avcEsBuf != null && au == -1 && mpegRingbuffer == null) {
final int width = frameWidth;
final int height = width < 480 ? 160 : 272; // How to retrieve the real video height?
// The application seems to stream the MPEG data into the avcEsBuf.addr buffer,
// probably only one frame at a time.
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegAvcDecode buffer=0x%08X, avcEsBuf: %s", buffer, Utilities.getMemoryDump(avcEsBuf.addr, AVC_ES_BUF_SIZE)));
}
// Generate a faked image. We cannot use the MediaEngine at this point
// as we have not enough MPEG data buffered in advance.
generateFakeImage(buffer, frameWidth, width, height, videoPixelMode);
// Clear the avcEsBuf buffer to better recognize the new MPEG data sent next time
Processor.memory.memset(avcEsBuf.addr, (byte) 0, AVC_ES_BUF_SIZE);
return 0;
}
// When frameWidth is 0, take the frameWidth specified at sceMpegCreate.
if (frameWidth == 0) {
if (defaultFrameWidth == 0) {
frameWidth = psmfHeader.getVideoWidth();
} else {
frameWidth = defaultFrameWidth;
}
}
mpegRingbufferRead();
if (auAddr.isNotNull()) {
mpegAvcAu.read(auAddr);
}
if (mpegRingbuffer == null && mpegAvcAu.esSize == 0) {
gotFrameAddr.setValue(false);
return 0;
}
checkEmptyVideoRingbuffer();
hleMpegAvcDecode(buffer, frameWidth, videoPixelMode, gotFrameAddr, true, auAddr);
startedMpeg = true;
if (buffer != 0) {
// Do not cache the video image as a texture in the VideoEngine to allow fluid rendering
final int height = psmfHeader != null ? psmfHeader.getVideoHeight() : 272;
VideoEngine.getInstance().addVideoTexture(buffer, buffer + height * frameWidth * sceDisplay.getPixelFormatBytes(videoPixelMode));
}
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegAvcDecode buffer=0x%08X, dts=0x%X, pts=0x%X, gotFrame=%b", buffer, mpegAvcAu.dts, mpegAvcAu.pts, avcGotFrame));
}
// Correct decoding.
avcDecodeResult = MPEG_AVC_DECODE_SUCCESS;
if (mpegRingbuffer != null && mpegRingbuffer.getPacketSize() > 0) {
mpegAvcAu.esSize = 0;
}
if (auAddr.isNotNull()) {
mpegAvcAu.write(auAddr);
}
return 0;
}
/**
* sceMpegAvcDecodeDetail
*
* @param mpeg
* @param detailPointer
*
* @return
*/
@HLEFunction(nid = 0x0F6C18D7, version = 150, checkInsideInterrupt = true)
public int sceMpegAvcDecodeDetail(@CheckArgument("checkMpegHandle") int mpeg, TPointer detailPointer) {
detailPointer.setValue32( 0, avcDecodeResult); // Stores the result
detailPointer.setValue32( 4, videoFrameCount); // Last decoded frame
detailPointer.setValue32( 8, psmfHeader != null ? psmfHeader.getVideoWidth() : lastFrameWidth); // Frame width
detailPointer.setValue32(12, psmfHeader != null ? psmfHeader.getVideoHeight() : (videoFrameHeight < 0 ? Math.min(lastFrameHeight, Screen.height) : videoFrameHeight)); // Frame height
detailPointer.setValue32(16, 0 ); // Frame crop rect (left)
detailPointer.setValue32(20, 0 ); // Frame crop rect (right)
detailPointer.setValue32(24, 0 ); // Frame crop rect (top)
detailPointer.setValue32(28, 0 ); // Frame crop rect (bottom)
detailPointer.setValue32(32, avcGotFrame ); // Status of the last decoded frame
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegAvcDecodeDetail returning decodeResult=0x%X, frameCount=%d, width=%d, height=%d, gotFrame=0x%X", detailPointer.getValue32(0), detailPointer.getValue32(4), detailPointer.getValue32(8), detailPointer.getValue32(12), detailPointer.getValue32(32)));
}
return 0;
}
/**
* sceMpegAvcDecodeMode
*
* @param mpeg
* @param mode_addr
*
* @return
*/
@HLEFunction(nid = 0xA11C7026, version = 150, checkInsideInterrupt = true)
public int sceMpegAvcDecodeMode(@CheckArgument("checkMpegHandle") int mpeg, @BufferInfo(lengthInfo=LengthInfo.fixedLength, length=8, usage=Usage.in) TPointer32 modeAddr) {
// -1 is a default value.
int mode = modeAddr.getValue(0);
int pixelMode = modeAddr.getValue(4);
if (pixelMode >= TPSM_PIXEL_STORAGE_MODE_16BIT_BGR5650 && pixelMode <= TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888) {
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegAvcDecodeMode mode=0x%X, pixelMode=0x%X", mode, pixelMode));
}
videoPixelMode = pixelMode;
} else {
log.warn(String.format("sceMpegAvcDecodeMode mode=0x%X, pixel mode=0x%X: unknown pixel mode", mode, pixelMode));
}
return 0;
}
/**
* sceMpegAvcDecodeStop
*
* @param mpeg
* @param frameWidth
* @param buffer_addr
* @param status_addr
*
* @return
*/
@HLEFunction(nid = 0x740FCCD1, version = 150, checkInsideInterrupt = true)
public int sceMpegAvcDecodeStop(@CheckArgument("checkMpegHandle") int mpeg, int frameWidth, @CanBeNull TPointer32 bufferAddr, TPointer32 gotFrameAddr) {
int buffer = 0;
if (bufferAddr.isNotNull()) {
buffer = bufferAddr.getValue();
}
// Decode any pending image
decodeImage(buffer, frameWidth, videoPixelMode, gotFrameAddr, true);
if (videoDecoderThread != null) {
videoDecoderThread.exit();
videoDecoderThread = null;
}
return 0;
}
/**
* sceMpegAvcDecodeFlush
*
* @param mpeg
*
* @return
*/
@HLEFunction(nid = 0x4571CC64, version = 150, checkInsideInterrupt = true)
public int sceMpegAvcDecodeFlush(int mpeg) {
// Finish the Mpeg if it had no audio.
// Finish the Mpeg only if we are not at the start of a new video,
// otherwise the analyzed video could be lost.
if (startedMpeg && audioFrameCount <= 0) {
finishMpeg();
}
return 0;
}
/**
* sceMpegAvcQueryYCbCrSize
*
* @param mpeg
* @param mode - 1 -> Loaded from file. 2 -> Loaded from memory.
* @param width - 480.
* @param height - 272.
* @param resultAddr - Where to store the result.
*
* @return
*/
@HLEFunction(nid = 0x211A057C, version = 150, checkInsideInterrupt = true)
public int sceMpegAvcQueryYCbCrSize(@CheckArgument("checkMpegHandle") int mpeg, int mode, int width, int height, TPointer32 resultAddr) {
if ((width & 15) != 0 || (height & 15) != 0 || width > 480 || height > 272) {
log.warn("sceMpegAvcQueryYCbCrSize invalid size width=" + width + ", height=" + height);
return SceKernelErrors.ERROR_MPEG_INVALID_VALUE;
}
// Write the size of the buffer used by sceMpegAvcDecodeYCbCr
int size = YCBCR_DATA_OFFSET + getYCbCrSize(width, height);
resultAddr.setValue(size);
return 0;
}
/**
* sceMpegAvcInitYCbCr
*
* @param mpeg
* @param mode
* @param width
* @param height
* @param ycbcr_addr
*
* @return
*/
@HLEFunction(nid = 0x67179B1B, version = 150, checkInsideInterrupt = true)
public int sceMpegAvcInitYCbCr(@CheckArgument("checkMpegHandle") int mpeg, int mode, int width, int height, TPointer yCbCrBuffer) {
yCbCrBuffer.memset((byte) 0, YCBCR_DATA_OFFSET);
return 0;
}
/**
* sceMpegAvcDecodeYCbCr
*
* @param mpeg
* @param au_addr
* @param buffer_addr
* @param init_addr
*
* @return
*/
@HLEFunction(nid = 0xF0EB1125, version = 150, checkInsideInterrupt = true)
public int sceMpegAvcDecodeYCbCr(@CheckArgument("checkMpegHandle") int mpeg, TPointer auAddr, TPointer32 bufferAddr, TPointer32 gotFrameAddr) {
mpegRingbufferRead();
if (auAddr.isNotNull()) {
mpegAvcAu.read(auAddr);
}
checkEmptyVideoRingbuffer();
// sceMpegAvcDecodeYCbCr() is performing the video decoding and
// sceMpegAvcCsc() is transforming the YCbCr image into ABGR.
hleMpegAvcDecode(bufferAddr.getValue() + YCBCR_DATA_OFFSET, 0, videoPixelMode, gotFrameAddr, false, auAddr);
startedMpeg = true;
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegAvcDecodeYCbCr *buffer=0x%08X, currentTimestamp=%d, avcGotFrame=%b", bufferAddr.getValue(), mpegAvcAu.pts, avcGotFrame));
}
// Correct decoding.
avcDecodeResult = MPEG_AVC_DECODE_SUCCESS;
if (auAddr.isNotNull()) {
mpegAvcAu.esSize = 0;
mpegAvcAu.write(auAddr);
}
return 0;
}
/**
* sceMpegAvcDecodeStopYCbCr
*
* @param mpeg
* @param buffer_addr
* @param status_addr
*
* @return
*/
@HLEFunction(nid = 0xF2930C9C, version = 150, checkInsideInterrupt = true)
public int sceMpegAvcDecodeStopYCbCr(@CheckArgument("checkMpegHandle") int mpeg, @CanBeNull TPointer32 bufferAddr, TPointer32 gotFrameAddr) {
int buffer = 0;
if (bufferAddr.isNotNull()) {
buffer = bufferAddr.getValue();
}
// Decode any pending image
decodeImage(buffer, 0, videoPixelMode, gotFrameAddr, false);
return 0;
}
/**
* sceMpegAvcCsc
*
* sceMpegAvcDecodeYCbCr() is performing the video decoding and
* sceMpegAvcCsc() is transforming the YCbCr image into ABGR.
*
* @param mpeg -
* @param source_addr - YCbCr data.
* @param range_addr - YCbCr range.
* @param frameWidth -
* @param dest_addr - Converted data (RGB).
*
* @return
*/
@HLEFunction(nid = 0x31BD0272, version = 150, checkInsideInterrupt = true)
public int sceMpegAvcCsc(@CheckArgument("checkMpegHandle") int mpeg, TPointer sourceAddr, TPointer32 rangeAddr, int frameWidth, TPointer destAddr) {
// When frameWidth is 0, take the frameWidth specified at sceMpegCreate.
if (frameWidth == 0) {
if (defaultFrameWidth == 0) {
frameWidth = psmfHeader.getVideoWidth();
} else {
frameWidth = defaultFrameWidth;
}
}
int rangeX = rangeAddr.getValue(0);
int rangeY = rangeAddr.getValue(4);
int rangeWidth = rangeAddr.getValue(8);
int rangeHeight = rangeAddr.getValue(12);
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegAvcCsc range x=%d, y=%d, width=%d, height=%d", rangeX, rangeY, rangeWidth, rangeHeight));
}
if (((rangeX | rangeY | rangeWidth | rangeHeight) & 0xF) != 0) {
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegAvcCsc returning ERROR_MPEG_INVALID_VALUE"));
}
return SceKernelErrors.ERROR_MPEG_INVALID_VALUE;
}
if (rangeX < 0 || rangeY < 0 || rangeWidth < 0 || rangeHeight < 0) {
// Returning ERROR_INVALID_VALUE and not ERROR_MPEG_INVALID_VALUE
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegAvcCsc returning ERROR_INVALID_VALUE"));
}
return SceKernelErrors.ERROR_INVALID_VALUE;
}
int width = psmfHeader == null ? Screen.width : psmfHeader.getVideoWidth();
int height = psmfHeader == null ? Screen.height : psmfHeader.getVideoHeight();
if (rangeX + rangeWidth > width || rangeY + rangeHeight > height) {
// Returning ERROR_INVALID_VALUE and not ERROR_MPEG_INVALID_VALUE
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegAvcCsc returning ERROR_INVALID_VALUE"));
}
return SceKernelErrors.ERROR_INVALID_VALUE;
}
int width2 = width >> 1;
int height2 = height >> 1;
int length = width * height;
int length2 = width2 * height2;
// Read the YCbCr image
int[] luma = getIntBuffer(length);
int[] cb = getIntBuffer(length2);
int[] cr = getIntBuffer(length2);
int dataAddr = sourceAddr.getAddress() + YCBCR_DATA_OFFSET;
if (hasMemoryInt()) {
// Optimize the most common case
int length4 = length >> 2;
int offset = dataAddr >> 2;
int[] memoryInt = getMemoryInt();
for (int i = 0, j = 0; i < length4; i++) {
int value = memoryInt[offset++];
luma[j++] = (value ) & 0xFF;
luma[j++] = (value >> 8) & 0xFF;
luma[j++] = (value >> 16) & 0xFF;
luma[j++] = (value >> 24) & 0xFF;
}
int length16 = length2 >> 2;
for (int i = 0, j = 0; i < length16; i++) {
int value = memoryInt[offset++];
cb[j++] = (value ) & 0xFF;
cb[j++] = (value >> 8) & 0xFF;
cb[j++] = (value >> 16) & 0xFF;
cb[j++] = (value >> 24) & 0xFF;
}
for (int i = 0, j = 0; i < length16; i++) {
int value = memoryInt[offset++];
cr[j++] = (value ) & 0xFF;
cr[j++] = (value >> 8) & 0xFF;
cr[j++] = (value >> 16) & 0xFF;
cr[j++] = (value >> 24) & 0xFF;
}
} else {
IMemoryReader memoryReader = MemoryReader.getMemoryReader(dataAddr, length + length2 + length2, 1);
for (int i = 0; i < length; i++) {
luma[i] = memoryReader.readNext();
}
for (int i = 0; i < length2; i++) {
cb[i] = memoryReader.readNext();
}
for (int i = 0; i < length2; i++) {
cr[i] = memoryReader.readNext();
}
}
// Convert YCbCr to ABGR
int[] abgr = getIntBuffer(length);
H264Utils.YUV2ABGR(width, height, luma, cb, cr, abgr);
releaseIntBuffer(luma);
releaseIntBuffer(cb);
releaseIntBuffer(cr);
final int bytesPerPixel = sceDisplay.getPixelFormatBytes(videoPixelMode);
// Do not cache the video image as a texture in the VideoEngine to allow fluid rendering
VideoEngine.getInstance().addVideoTexture(destAddr.getAddress(), destAddr.getAddress() + (rangeY + rangeHeight) * frameWidth * bytesPerPixel);
// Write the ABGR image
if (videoPixelMode == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888 && hasMemoryInt()) {
// Optimize the most common case
int pixelIndex = rangeY * width + rangeX;
for (int i = 0; i < rangeHeight; i++) {
int addr = destAddr.getAddress() + (i * frameWidth) * bytesPerPixel;
System.arraycopy(abgr, pixelIndex, getMemoryInt(), addr >> 2, rangeWidth);
pixelIndex += width;
}
} else {
int addr = destAddr.getAddress();
for (int i = 0; i < rangeHeight; i++) {
IMemoryWriter memoryWriter = MemoryWriter.getMemoryWriter(addr, rangeWidth * bytesPerPixel, bytesPerPixel);
int pixelIndex = (i + rangeY) * width + rangeX;
for (int j = 0; j < rangeWidth; j++, pixelIndex++) {
int abgr8888 = abgr[pixelIndex];
int pixelColor = Debug.getPixelColor(abgr8888, videoPixelMode);
memoryWriter.writeNext(pixelColor);
}
memoryWriter.flush();
addr += frameWidth * bytesPerPixel;
}
}
releaseIntBuffer(abgr);
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegAvcCsc writing to 0x%08X-0x%08X, vcount=%d", destAddr.getAddress(), destAddr.getAddress() + (rangeY + rangeHeight) * frameWidth * bytesPerPixel, Modules.sceDisplayModule.getVcount()));
}
delayThread(avcDecodeDelay);
return 0;
}
/**
* sceMpegAtracDecode
*
* @param mpeg
* @param au_addr
* @param buffer_addr
* @param init
*
* @return
*/
@HLEFunction(nid = 0x800C44DF, version = 150, checkInsideInterrupt = true)
public int sceMpegAtracDecode(@CheckArgument("checkMpegHandle") int mpeg, TPointer auAddr, TPointer bufferAddr, int init) {
int result = hleMpegAtracDecode(auAddr, bufferAddr, MPEG_ATRAC_ES_SIZE);
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegAtracDecode currentTimestamp=%d", mpegAtracAu.pts));
}
return result;
}
protected int getPacketsFromSize(int size) {
int packets = size / (2048 + 104);
return packets;
}
private int getSizeFromPackets(int packets) {
int size = (packets * 104) + (packets * 2048);
return size;
}
/**
* sceMpegRingbufferQueryMemSize
*
* @param packets
*
* @return
*/
@HLEFunction(nid = 0xD7A29F46, version = 150, checkInsideInterrupt = true, stackUsage = 0x8)
public int sceMpegRingbufferQueryMemSize(int packets) {
return getSizeFromPackets(packets);
}
/**
* sceMpegRingbufferConstruct
*
* @param ringbuffer_addr
* @param packets
* @param data
* @param size
* @param callback_addr
* @param callback_args
*
* @return
*/
@HLEFunction(nid = 0x37295ED8, version = 150, checkInsideInterrupt = true, stackUsage = 0x38)
public int sceMpegRingbufferConstruct(TPointer ringbufferAddr, int packets, @CanBeNull TPointer data, int size, @CanBeNull TPointer callbackAddr, int callbackArgs) {
if (size < getSizeFromPackets(packets)) {
log.warn(String.format("sceMpegRingbufferConstruct insufficient space: size=%d, packets=%d", size, packets));
return SceKernelErrors.ERROR_MPEG_NO_MEMORY;
}
SceMpegRingbuffer ringbuffer = new SceMpegRingbuffer(packets, data.getAddress(), size, callbackAddr.getAddress(), callbackArgs);
ringbuffer.write(ringbufferAddr);
return 0;
}
/**
* sceMpegRingbufferDestruct
*
* @param ringbuffer_addr
*
* @return
*/
@HLEFunction(nid = 0x13407F13, version = 150, checkInsideInterrupt = true, stackUsage = 0x8)
public int sceMpegRingbufferDestruct(TPointer ringbufferAddr) {
if (mpegRingbuffer != null) {
mpegRingbuffer.read(ringbufferAddr);
resetMpegRingbuffer();
mpegRingbuffer.write(ringbufferAddr);
mpegRingbuffer = null;
mpegRingbufferAddr = null;
}
return 0;
}
/**
* sceMpegRingbufferPut
*
* @param _mpegRingbufferAddr
* @param numPackets
* @param available
*
* @return
*/
@HLEFunction(nid = 0xB240A59E, version = 150, checkInsideInterrupt = true)
public int sceMpegRingbufferPut(TPointer ringbufferAddr, int numPackets, int available) {
mpegRingbufferAddr = ringbufferAddr;
mpegRingbufferRead();
if (numPackets < 0) {
return 0;
}
int numberPackets = Math.min(available, numPackets);
if (numberPackets <= 0) {
return 0;
}
// Note: we can read more packets than available in the Mpeg stream: the application
// can loop the video by putting previous packets back into the ringbuffer.
int putNumberPackets = Math.min(numberPackets, mpegRingbuffer.getPutSequentialPackets());
int putDataAddr = mpegRingbuffer.getPutDataAddr();
AfterRingbufferPutCallback afterRingbufferPutCallback = new AfterRingbufferPutCallback(putDataAddr, numberPackets - putNumberPackets);
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegRingbufferPut executing callback 0x%08X to read 0x%X packets at 0x%08X, Ringbuffer=%s", mpegRingbuffer.getCallbackAddr(), putNumberPackets, putDataAddr, mpegRingbuffer));
}
Modules.ThreadManForUserModule.executeCallback(null, mpegRingbuffer.getCallbackAddr(), afterRingbufferPutCallback, false, putDataAddr, putNumberPackets, mpegRingbuffer.getCallbackArgs());
return afterRingbufferPutCallback.getTotalPacketsAdded();
}
/**
* sceMpegRingbufferAvailableSize
*
* @param _mpegRingbufferAddr
*
* @return
*/
@HLEFunction(nid = 0xB5F6DC87, version = 150, checkInsideInterrupt = true)
public int sceMpegRingbufferAvailableSize(TPointer ringbufferAddr) {
mpegRingbufferAddr = ringbufferAddr;
mpegRingbufferRead();
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegRingbufferAvailableSize returning 0x%X, vcount=%d", mpegRingbuffer.getFreePackets(), Modules.sceDisplayModule.getVcount()));
}
return mpegRingbuffer.getFreePackets();
}
/**
* sceMpegNextAvcRpAu - skip one video frame
*
* @param mpeg
* @param unknown
*
* @return
*/
@HLEUnimplemented
@HLEFunction(nid = 0x3C37A7A6, version = 150, checkInsideInterrupt = true)
public int sceMpegNextAvcRpAu(@CheckArgument("checkMpegHandle") int mpeg, int streamUid) {
if (!streamMap.containsKey(streamUid)) {
log.warn(String.format("sceMpegNextAvcRpAu bad stream 0x%X", streamUid));
return -1;
}
int result = hleMpegGetAvcAu(null);
if (result != 0) {
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegNextAvcRpAu returning 0x%08X", result));
}
return result;
}
videoFrameCount++;
startedMpeg = true;
return 0;
}
@HLEFunction(nid = 0x01977054, version = 150)
public int sceMpegGetUserdataAu(@CheckArgument("checkMpegHandle") int mpeg, int streamUid, TPointer auAddr, @CanBeNull TPointer headerAddr) {
if (!hasPsmfUserdataStream()) {
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegGetUserdataAu no registered user data stream, returning 0x%08X", SceKernelErrors.ERROR_MPEG_NO_DATA));
}
return SceKernelErrors.ERROR_MPEG_NO_DATA;
}
if (userDataPesHeader == null) {
userDataPesHeader = new PesHeader(getRegisteredUserDataChannel());
userDataPesHeader.setDtsPts(UNKNOWN_TIMESTAMP);
}
mpegUserDataAu.read(auAddr);
if (userDataBuffer == null) {
userDataBuffer = new UserDataBuffer(mpegUserDataAu.esBuffer, MPEG_DATA_ES_SIZE);
}
readNextUserDataFrame(userDataPesHeader);
if (userDataLength == 0) {
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegGetUserdataAu no user data available, returning 0x%08X", SceKernelErrors.ERROR_MPEG_NO_DATA));
}
return SceKernelErrors.ERROR_MPEG_NO_DATA;
}
if (userDataBuffer.getLength() < userDataLength) {
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegGetUserdataAu no enough user data available (0x%X from 0x%X), returning 0x%08X", userDataBuffer.getLength(), userDataLength, SceKernelErrors.ERROR_MPEG_NO_DATA));
}
return SceKernelErrors.ERROR_MPEG_NO_DATA;
}
mpegRingbufferNotifyRead();
Memory mem = auAddr.getMemory();
mpegUserDataAu.pts = userDataPesHeader.getPts();
mpegUserDataAu.dts = UNKNOWN_TIMESTAMP; // dts is always -1
mpegUserDataAu.esSize = userDataLength;
mpegUserDataAu.write(auAddr);
userDataBuffer.notifyRead(mem, mpegUserDataAu.esSize);
userDataLength = 0;
if (headerAddr.isNotNull()) {
// First 8 bytes of the user data header
for (int i = 0; i < userDataHeader.length; i++) {
headerAddr.setValue8(i, (byte) userDataHeader[i]);
}
}
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegGetUserdataAu returning au=%s", mpegUserDataAu));
if (log.isTraceEnabled()) {
log.trace(String.format("mpegUserDataAu.esBuffer: %s", Utilities.getMemoryDump(mpegUserDataAu.esBuffer, mpegUserDataAu.esSize)));
if (headerAddr.isNotNull()) {
log.trace(String.format("headerAddr: %s", Utilities.getMemoryDump(headerAddr.getAddress(), 8)));
}
}
}
return 0;
}
@HLEFunction(nid = 0xC45C99CC, version = 150)
public int sceMpegQueryUserdataEsSize(@CheckArgument("checkMpegHandle") int mpeg, TPointer32 esSizeAddr, TPointer32 outSizeAddr) {
esSizeAddr.setValue(MPEG_DATA_ES_SIZE);
outSizeAddr.setValue(MPEG_DATA_ES_OUTPUT_SIZE);
return 0;
}
@HLEFunction(nid = 0x0558B075, version = 150)
public int sceMpegAvcCopyYCbCr(@CheckArgument("checkMpegHandle") int mpeg, TPointer destinationAddr, TPointer sourceAddr) {
int size = getYCbCrSize() + YCBCR_DATA_OFFSET;
destinationAddr.getMemory().memcpy(destinationAddr.getAddress(), sourceAddr.getAddress(), size);
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegAvcCopyYCbCr from 0x%08X-0x%08X to 0x%08X-0x%08X", sourceAddr.getAddress(), sourceAddr.getAddress() + size, destinationAddr.getAddress(), destinationAddr.getAddress() + size));
}
return 0;
}
@HLEFunction(nid = 0x11F95CF1, version = 150)
public int sceMpegGetAvcNalAu(int mpeg, @BufferInfo(lengthInfo=LengthInfo.fixedLength, length=32, usage=Usage.in) TPointer mp4AvcNalStructAddr, @BufferInfo(lengthInfo=LengthInfo.fixedLength, length=24, usage=Usage.out) TPointer auAddr) {
// Based on information found in
// https://github.com/Rinnegatamante/lua-player-plus/blob/master/lpp-c%2B%2B/Libs/Mp4/Mp4.c
SceMp4AvcNalStruct mp4AvcNalStruct = new SceMp4AvcNalStruct();
mp4AvcNalStruct.read(mp4AvcNalStructAddr);
if (log.isTraceEnabled()) {
log.trace(String.format("sceMpegGetAvcNalAu mp4AvcNalStruct: %s", mp4AvcNalStruct));
}
SceMpegAu au = new SceMpegAu();
au.read(auAddr);
au.esBuffer = mp4AvcNalStruct.nalBuffer;
au.esSize = mp4AvcNalStruct.nalSize;
// PSP is returning 0 for pts & dts
au.pts = 0L;
au.dts = 0L;
au.write(auAddr);
if (!hasVideoCodecExtraData()) {
// Build the video codec "extradata" in the expected format
int[] videoCodecExtraData = new int[8 + mp4AvcNalStruct.spsSize + 3 + mp4AvcNalStruct.ppsSize];
int offset = 0;
videoCodecExtraData[offset++] = 0x01; // Need to start with 1
offset += 3; // Unused
videoCodecExtraData[offset++] = (mp4AvcNalStruct.nalPrefixSize - 1) & 0x03; // nal length size
videoCodecExtraData[offset++] = 0x01; // Number of sps
videoCodecExtraData[offset++] = (mp4AvcNalStruct.spsSize >> 8) & 0xFF;
videoCodecExtraData[offset++] = (mp4AvcNalStruct.spsSize ) & 0xFF;
IMemoryReader spsReader = MemoryReader.getMemoryReader(mp4AvcNalStruct.spsBuffer, mp4AvcNalStruct.spsSize, 1);
for (int i = 0; i < mp4AvcNalStruct.spsSize; i++) {
videoCodecExtraData[offset++] = spsReader.readNext();
}
videoCodecExtraData[offset++] = 0x01; // Number of pps
videoCodecExtraData[offset++] = (mp4AvcNalStruct.ppsSize >> 8) & 0xFF;
videoCodecExtraData[offset++] = (mp4AvcNalStruct.ppsSize ) & 0xFF;
IMemoryReader ppsReader = MemoryReader.getMemoryReader(mp4AvcNalStruct.ppsBuffer, mp4AvcNalStruct.ppsSize, 1);
for (int i = 0; i < mp4AvcNalStruct.ppsSize; i++) {
videoCodecExtraData[offset++] = ppsReader.readNext();
}
setVideoCodecExtraData(videoCodecExtraData);
if (log.isDebugEnabled()) {
byte[] buffer = new byte[videoCodecExtraData.length];
for (int i = 0; i < buffer.length; i++) {
buffer[i] = (byte) videoCodecExtraData[i];
}
log.debug(String.format("sceMpegGetAvcNalAu videoCodecExtraData: %s", Utilities.getMemoryDump(buffer, 0, buffer.length)));
}
}
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x921FCCCF, version = 150)
public int sceMpegGetAvcEsAu() {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x6F314410, version = 150)
public int sceMpegAvcDecodeGetDecodeSEI(int mpeg, @BufferInfo(usage=Usage.out) TPointer32 decodeSEIAddr) {
decodeSEIAddr.setValue(0);
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0xAB0E9556, version = 150)
public int sceMpegAvcDecodeDetailIndex(int mpeg, int index, @BufferInfo(lengthInfo=LengthInfo.fixedLength, length=52, usage=Usage.out) TPointer32 detail) {
detail.setValue(8, mpegAvcInfoStruct.getValue32(8)); // image width
detail.setValue(12, mpegAvcInfoStruct.getValue32(12)); // image height
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegAvcDecodeDetailIndex returning width=%d, height=%d", detail.getValue(8), detail.getValue(12)));
}
return 0;
}
@HLEFunction(nid = 0xCF3547A2, version = 150)
public int sceMpegAvcDecodeDetail2(int mpeg, @BufferInfo(usage=Usage.out) TPointer32 detail) {
detail.setValue(mpegAvcDetail2Struct.getAddress());
if (log.isTraceEnabled()) {
log.trace(String.format("sceMpegAvcDecodeDetail2 detail2 structure: %s", Utilities.getMemoryDump(mpegAvcDetail2Struct.getAddress(), 96)));
}
return 0;
}
@HLEFunction(nid = 0xF5E7EA31, version = 150)
public int sceMpegAvcConvertToYuv420(int mpeg, TPointer yuv420Buffer, TPointer yCbCrBuffer, int unknown2) {
int size = getYCbCrSize();
yCbCrBuffer.getMemory().memcpy(yuv420Buffer.getAddress(), yCbCrBuffer.getAddress() + YCBCR_DATA_OFFSET, size);
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegAvcConvertToYuv420 from 0x%08X-0x%08X to 0x%08X-0x%08X", yCbCrBuffer.getAddress(), yCbCrBuffer.getAddress() + size, yuv420Buffer.getAddress(), yuv420Buffer.getAddress() + size));
}
// The YUV420 image will be decoded and saved to memory by sceJpegCsc
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0xD1CE4950, version = 150)
public int sceMpegAvcCscMode(int mpeg, @BufferInfo(usage=Usage.in) TPointer32 modeAddr) {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0xDBB60658, version = 150)
public int sceMpegFlushAu() {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0xE95838F6, version = 150)
public int sceMpegAvcCscInfo() {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x11CAB459, version = 150)
public int sceMpeg_11CAB459() {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0xB27711A8, version = 150)
public int sceMpeg_B27711A8() {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0xD4DD6E75, version = 150)
public int sceMpeg_D4DD6E75() {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0xC345DED2, version = 150)
public int sceMpeg_C345DED2() {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x988E9E12, version = 150)
public int sceMpeg_988E9E12() {
return 0;
}
@HLEFunction(nid = 0x769BEBB6, version = 250)
public int sceMpegRingbufferQueryPackNum(int memorySize) {
return getPacketsFromSize(memorySize);
}
@HLEUnimplemented
@HLEFunction(nid = 0x63B9536A, version = 600)
public int sceMpegAvcResourceGetAvcDecTopAddr(int unknown) {
// Unknown value, passed to sceMpegCreate(ddttop)
return 0x12345678;
}
@HLEFunction(nid = 0x8160A2FE, version = 600)
public int sceMpegAvcResourceFinish() {
if (avcEsBuf != null) {
Modules.SysMemUserForUserModule.free(avcEsBuf);
avcEsBuf = null;
}
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0xAF26BB01, version = 600)
public int sceMpegAvcResourceGetAvcEsBuf() {
if (avcEsBuf == null) {
log.warn(String.format("sceMpegAvcResourceGetAvcEsBuf avcEsBuf not allocated"));
return -1;
}
if (log.isDebugEnabled()) {
log.debug(String.format("sceMpegAvcResourceGetAvcEsBuf returning 0x%08X", avcEsBuf.addr));
}
return avcEsBuf.addr;
}
@HLELogging(level="warn")
@HLEFunction(nid = 0xFCBDB5AD, version = 600)
public int sceMpegAvcResourceInit(int unknown) {
if (unknown != 1) {
return SceKernelErrors.ERROR_MPEG_INVALID_VALUE;
}
avcEsBuf = Modules.SysMemUserForUserModule.malloc(USER_PARTITION_ID, "sceMpegAvcEsBuf", PSP_SMEM_High, AVC_ES_BUF_SIZE, 0);
if (avcEsBuf != null) {
Processor.memory.memset(avcEsBuf.addr, (byte) 0, avcEsBuf.size);
}
return 0;
}
}