package org.jcodec.common;
import static org.jcodec.common.Tuple._2;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.jcodec.codecs.aac.AACDecoder;
import org.jcodec.codecs.h264.BufferH264ES;
import org.jcodec.codecs.h264.H264Decoder;
import org.jcodec.codecs.mjpeg.JpegDecoder;
import org.jcodec.codecs.mpeg12.MPEGDecoder;
import org.jcodec.codecs.ppm.PPMEncoder;
import org.jcodec.codecs.prores.ProresDecoder;
import org.jcodec.codecs.vpx.VP8Decoder;
import org.jcodec.codecs.wav.WavDemuxer;
import org.jcodec.common.io.FileChannelWrapper;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.logging.Logger;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Picture8Bit;
import org.jcodec.common.tools.MathUtil;
import org.jcodec.containers.imgseq.ImageSequenceDemuxer;
import org.jcodec.containers.mkv.demuxer.MKVDemuxer;
import org.jcodec.containers.mp4.demuxer.MP4Demuxer;
import org.jcodec.containers.mps.MPSDemuxer;
import org.jcodec.containers.mps.MTSDemuxer;
import org.jcodec.containers.webp.WebpDemuxer;
import org.jcodec.containers.y4m.Y4MDemuxer;
import org.jcodec.scale.ColorUtil;
import org.jcodec.scale.Transform8Bit;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* @author The JCodec project
*
*/
public class JCodecUtil {
private static final Map<Codec, Class<?>> decoders = new HashMap<Codec, Class<?>>();
private static final Map<Format, Class<?>> demuxers = new HashMap<Format, Class<?>>();
static {
decoders.put(Codec.VP8, VP8Decoder.class);
decoders.put(Codec.PRORES, ProresDecoder.class);
decoders.put(Codec.MPEG2, MPEGDecoder.class);
decoders.put(Codec.H264, H264Decoder.class);
decoders.put(Codec.AAC, AACDecoder.class);
demuxers.put(Format.MPEG_TS, MTSDemuxer.class);
demuxers.put(Format.MPEG_PS, MPSDemuxer.class);
demuxers.put(Format.MOV, MP4Demuxer.class);
demuxers.put(Format.WEBP, WebpDemuxer.class);
};
public static Format detectFormat(File f) throws IOException {
return detectFormatBuffer(NIOUtils.fetchFromFileL(f, 200 * 1024));
}
public static Format detectFormatChannel(ReadableByteChannel f) throws IOException {
return detectFormatBuffer(NIOUtils.fetchFromChannel(f, 200 * 1024));
}
public static Format detectFormatBuffer(ByteBuffer b) {
int maxScore = 0;
Format selected = null;
for (Map.Entry<Format, Class<?>> vd : demuxers.entrySet()) {
int score = probe(b.duplicate(), vd.getValue());
if (score > maxScore) {
selected = vd.getKey();
maxScore = score;
}
}
return selected;
}
public static Codec detectDecoder(ByteBuffer b) {
int maxScore = 0;
Codec selected = null;
for (Map.Entry<Codec, Class<?>> vd : decoders.entrySet()) {
int score = probe(b.duplicate(), vd.getValue());
if (score > maxScore) {
selected = vd.getKey();
maxScore = score;
}
}
return selected;
}
private static int probe(ByteBuffer b, Class<?> vd) {
try {
Method method = vd.getDeclaredMethod("probe", ByteBuffer.class);
if (method != null) {
return (Integer) method.invoke(null, b);
}
} catch (Exception e) {
}
return 0;
}
public static VideoDecoder getVideoDecoder(String fourcc) {
if ("apch".equals(fourcc) || "apcs".equals(fourcc) || "apco".equals(fourcc) || "apcn".equals(fourcc)
|| "ap4h".equals(fourcc))
return new ProresDecoder();
else if ("m2v1".equals(fourcc))
return new MPEGDecoder();
else
return null;
}
public static void savePictureAsPPM(Picture8Bit pic, File file) throws IOException {
Transform8Bit transform = ColorUtil.getTransform8Bit(pic.getColor(), ColorSpace.RGB);
Picture8Bit rgb = Picture8Bit.create(pic.getWidth(), pic.getHeight(), ColorSpace.RGB);
transform.transform(pic, rgb);
NIOUtils.writeTo(new PPMEncoder().encodeFrame8Bit(rgb), file);
}
public static byte[] asciiString(String fourcc) {
char[] ch = fourcc.toCharArray();
byte[] result = new byte[ch.length];
for (int i = 0; i < ch.length; i++) {
result[i] = (byte) ch[i];
}
return result;
}
public static void writeBER32(ByteBuffer buffer, int value) {
buffer.put((byte) ((value >> 21) | 0x80));
buffer.put((byte) ((value >> 14) | 0x80));
buffer.put((byte) ((value >> 7) | 0x80));
buffer.put((byte) (value & 0x7F));
}
public static void writeBER32Var(ByteBuffer bb, int value) {
for (int i = 0, bits = MathUtil.log2(value); i < 4 && bits > 0; i++) {
bits -= 7;
int out = value >> bits;
if (bits > 0)
out |= 0x80;
bb.put((byte) out);
}
}
public static int readBER32(ByteBuffer input) {
int size = 0;
for (int i = 0; i < 4; i++) {
byte b = input.get();
size = (size << 7) | (b & 0x7f);
if (((b & 0xff) >> 7) == 0)
break;
}
return size;
}
public static int[] getAsIntArray(ByteBuffer yuv, int size) {
byte[] b = new byte[size];
int[] result = new int[size];
yuv.get(b);
for (int i = 0; i < b.length; i++) {
result[i] = b[i] & 0xff;
}
return result;
}
public static ThreadPoolExecutor getPriorityExecutor(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
new PriorityBlockingQueue<Runnable>(10, PriorityFuture.COMP)) {
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
RunnableFuture<T> newTaskFor = super.newTaskFor(callable);
return new PriorityFuture<T>(newTaskFor, ((PriorityCallable<T>) callable).getPriority());
}
};
}
public static String removeExtension(String name) {
if (name == null)
return null;
return name.replaceAll("\\.[^\\.]+$", "");
}
public static Demuxer createDemuxer(Format format, File input) throws IOException {
FileChannelWrapper ch = null;
if (format != Format.IMG) {
ch = NIOUtils.readableChannel(input);
}
switch (format) {
case MOV:
return new MP4Demuxer(ch);
case MPEG_PS:
return new MPSDemuxer(ch);
case MKV:
return new MKVDemuxer(ch);
case IMG:
return new ImageSequenceDemuxer(input.getAbsolutePath(), Integer.MAX_VALUE);
case Y4M:
return new Y4MDemuxer(ch);
case WEBP:
return new WebpDemuxer(ch);
case H264:
return new BufferH264ES(NIOUtils.fetchFromChannel(ch));
case WAV:
return new WavDemuxer(ch);
default:
Logger.error("Format " + format + " is not supported");
}
return null;
}
public static _2<Integer, Demuxer> createM2TSDemuxer(File input, TrackType targetTrack) throws IOException {
FileChannelWrapper ch = NIOUtils.readableChannel(input);
MTSDemuxer mts = new MTSDemuxer(ch);
Set<Integer> programs = mts.getPrograms();
if (programs.size() == 0) {
Logger.error("The MPEG TS stream contains no programs");
return null;
}
Tuple._2<Integer, Demuxer> found = null;
for (Integer pid : programs) {
ReadableByteChannel program = mts.getProgram(pid);
if (found != null) {
program.close();
continue;
}
MPSDemuxer demuxer = new MPSDemuxer(program);
if (targetTrack == TrackType.AUDIO && demuxer.getAudioTracks().size() > 0
|| targetTrack == TrackType.VIDEO && demuxer.getVideoTracks().size() > 0) {
found = _2(pid, (Demuxer) demuxer);
Logger.info("Using M2TS program: " + pid + " for " + targetTrack + " track.");
} else {
program.close();
}
}
return found;
}
public static AudioDecoder createAudioDecoder(Codec codec, ByteBuffer decoderSpecific) throws IOException {
switch (codec) {
case AAC:
return new AACDecoder(decoderSpecific);
default:
Logger.error("Codec " + codec + " is not supported");
}
return null;
}
public static VideoDecoder createVideoDecoder(Codec codec, ByteBuffer decoderSpecific) {
switch (codec) {
case H264:
return decoderSpecific != null ? H264Decoder.createH264DecoderFromCodecPrivate(decoderSpecific)
: new H264Decoder();
case MPEG2:
return new MPEGDecoder();
case VP8:
return new VP8Decoder();
case JPEG:
return new JpegDecoder();
default:
Logger.error("Codec " + codec + " is not supported");
}
return null;
}
public static String dwToFourCC(int fourCC) {
char[] ch = new char[4];
ch[0] = (char)((fourCC >> 24) & 0xff);
ch[1] = (char)((fourCC >> 16) & 0xff);
ch[2] = (char)((fourCC >> 8) & 0xff);
ch[3] = (char)((fourCC >> 0) & 0xff);
return new String(ch);
}
}