/*
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.GUI;
import java.awt.Image;
import java.awt.image.MemoryImageSource;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import jpcsp.Emulator;
import jpcsp.HLE.modules.sceMpeg;
import jpcsp.filesystems.umdiso.UmdIsoReader;
import jpcsp.media.codec.CodecFactory;
import jpcsp.media.codec.IVideoCodec;
import jpcsp.media.codec.h264.H264Utils;
import com.twilight.h264.decoder.H264Context;
import jpcsp.util.Utilities;
public class UmdBrowserPmf {
private static org.apache.log4j.Logger log = Emulator.log;
private UmdIsoReader iso;
private String fileName;
private long startTime;
private Image image;
private boolean done;
private boolean endOfVideo;
private boolean threadExit;
private JLabel display;
private PmfDisplayThread displayThread;
private IVideoCodec videoCodec;
private int[] videoData = new int[0x10000];
private int videoDataOffset;
private InputStream is;
private int videoChannel = 0;
private int frame;
private int videoWidth;
private int videoHeight;
public UmdBrowserPmf(UmdIsoReader iso, String fileName, JLabel display) {
this.iso = iso;
this.fileName = fileName;
this.display = display;
init();
initVideo();
}
private int read8(InputStream is) {
try {
return is.read();
} catch (IOException e) {
// Ignore exception
}
return -1;
}
private int read16(InputStream is) {
return (read8(is) << 8) | read8(is);
}
private int read32(InputStream is) {
return (read8(is) << 24) | (read8(is) << 16) | (read8(is) << 8) | read8(is);
}
private void skip(InputStream is, int n) {
if (n > 0) {
try {
is.skip(n);
} catch (IOException e) {
e.printStackTrace();
}
}
}
private int skipPesHeader(InputStream is, int startCode) {
int pesLength = 0;
int c = read8(is);
pesLength++;
while (c == 0xFF) {
c = read8(is);
pesLength++;
}
if ((c & 0xC0) == 0x40) {
skip(is, 1);
c = read8(is);
pesLength += 2;
}
if ((c & 0xE0) == 0x20) {
skip(is, 4);
pesLength += 4;
if ((c & 0x10) != 0) {
skip(is, 5);
pesLength += 5;
}
} else if ((c & 0xC0) == 0x80) {
skip(is, 1);
int headerLength = read8(is);
pesLength += 2;
skip(is, headerLength);
pesLength += headerLength;
}
if (startCode == 0x1BD) { // PRIVATE_STREAM_1
int channel = read8(is);
pesLength++;
if (channel >= 0x80 && channel <= 0xCF) {
skip(is, 3);
pesLength += 3;
if (channel >= 0xB0 && channel <= 0xBF) {
skip(is, 1);
pesLength++;
}
} else {
skip(is, 3);
pesLength += 3;
}
}
return pesLength;
}
private void addVideoData(InputStream is, int length) {
if (videoDataOffset + length > videoData.length) {
// Extend the inputBuffer
int[] newVideoData = new int[videoDataOffset + length];
System.arraycopy(videoData, 0, newVideoData, 0, videoDataOffset);
videoData = newVideoData;
}
for (int i = 0; i < length; i++) {
videoData[videoDataOffset++] = read8(is);
}
}
private boolean readPsmfHeader() {
try {
is = iso.getFile(fileName);
} catch (FileNotFoundException e) {
return false;
} catch (IOException e) {
log.error("readPsmfHeader", e);
return false;
}
if (read32(is) != 0x50534D46) { // PSMF
return false;
}
skip(is, 4);
int mpegOffset = read32(is);
skip(is, jpcsp.HLE.modules.sceMpeg.PSMF_FRAME_WIDTH_OFFSET - sceMpeg.PSMF_STREAM_SIZE_OFFSET);
videoWidth = read8(is) << 4;
videoHeight = read8(is) << 4;
skip(is, mpegOffset - sceMpeg.PSMF_FRAME_HEIGHT_OFFSET - 1);
return true;
}
private boolean readPsmfPacket(int videoChannel) {
while (true) {
int startCode = read32(is);
if (startCode == -1) {
// End of file
return false;
}
int codeLength;
switch (startCode) {
case 0x1BA: // PACK_START_CODE
skip(is, 10);
break;
case 0x1BB: // SYSTEM_HEADER_START_CODE
skip(is, 14);
break;
case 0x1BE: // PADDING_STREAM
case 0x1BF: // PRIVATE_STREAM_2
case 0x1BD: // PRIVATE_STREAM_1, Audio stream
codeLength = read16(is);
skip(is, codeLength);
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(is);
if (videoChannel < 0 || startCode - 0x1E0 == videoChannel) {
int pesLength = skipPesHeader(is, startCode);
codeLength -= pesLength;
addVideoData(is, codeLength);
return true;
}
skip(is, codeLength);
break;
}
}
}
private void consumeVideoData(int length) {
if (length >= videoDataOffset) {
videoDataOffset = 0;
} else {
System.arraycopy(videoData, length, videoData, 0, videoDataOffset - length);
videoDataOffset -= length;
}
}
private int findFrameEnd() {
for (int i = 5; i < videoDataOffset; i++) {
if (videoData[i - 4] == 0x00 &&
videoData[i - 3] == 0x00 &&
videoData[i - 2] == 0x00 &&
videoData[i - 1] == 0x01) {
int naluType = videoData[i] & 0x1F;
if (naluType == H264Context.NAL_AUD) {
return i - 4;
}
}
}
return -1;
}
private void init() {
image = null;
done = false;
threadExit = false;
}
private Image getImage() {
return image;
}
final public boolean initVideo() {
if (!startVideo()) {
return false;
}
displayThread = new PmfDisplayThread();
displayThread.setDaemon(true);
displayThread.setName("UMD Browser - PMF Display Thread");
displayThread.start();
return true;
}
private boolean startVideo() {
endOfVideo = false;
if (!readPsmfHeader()) {
return false;
}
videoCodec = CodecFactory.getVideoCodec();
videoCodec.init(null);
startTime = System.currentTimeMillis();
frame = 0;
return true;
}
private void closeVideo() {
videoCodec = null;
if (is != null) {
try {
is.close();
} catch (IOException e) {
// Ignore Exception
}
is = null;
}
}
private void loopVideo() {
closeVideo();
startVideo();
}
private void stopDisplayThread() {
while (displayThread != null && !threadExit) {
done = true;
Utilities.sleep(1, 0);
}
displayThread = null;
}
public void stopVideo() {
stopDisplayThread();
closeVideo();
}
public void stepVideo() {
image = null;
int frameSize = -1;
do {
if (!readPsmfPacket(videoChannel)) {
if (videoDataOffset <= 0) {
// Enf of file reached
break;
}
frameSize = findFrameEnd();
if (frameSize < 0) {
// Process pending last frame
frameSize = videoDataOffset;
}
} else {
frameSize = findFrameEnd();
}
} while (frameSize <= 0);
if (frameSize <= 0) {
endOfVideo = true;
return;
}
int consumedLength = videoCodec.decode(videoData, 0, frameSize);
if (consumedLength < 0) {
endOfVideo = true;
return;
}
consumeVideoData(consumedLength);
if (videoCodec.hasImage()) {
int width = videoCodec.getImageWidth();
int height = videoCodec.getImageHeight();
int size = width * height;
int size2 = size >> 2;
int luma[] = new int[size];
int cr[] = new int[size2];
int cb[] = new int[size2];
if (videoCodec.getImage(luma, cb, cr) == 0) {
int abgr[] = new int[size];
H264Utils.YUV2ARGB(width, height, luma, cb, cr, abgr);
image = display.createImage(new MemoryImageSource(videoWidth, videoHeight, abgr, 0, width));
frame++;
long now = System.currentTimeMillis();
long currentDuration = now - startTime;
long videoDuration = frame * 100000L / 3003L;
if (currentDuration < videoDuration) {
Utilities.sleep((int) (videoDuration - currentDuration), 0);
}
}
}
}
private class PmfDisplayThread extends Thread {
@Override
public void run() {
while (!done) {
while (!endOfVideo && !done) {
stepVideo();
if (display != null && getImage() != null) {
display.setIcon(new ImageIcon(getImage()));
}
}
if (!done) {
loopVideo();
}
}
threadExit = true;
}
}
}