package org.jcodec.containers.y4m; import static org.jcodec.common.StringUtils.splitC; import static org.jcodec.platform.Platform.stringFromBytes; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import org.jcodec.common.Codec; import org.jcodec.common.Demuxer; import org.jcodec.common.DemuxerTrack; import org.jcodec.common.DemuxerTrackMeta; import org.jcodec.common.TrackType; import org.jcodec.common.VideoCodecMeta; import org.jcodec.common.io.NIOUtils; import org.jcodec.common.io.SeekableByteChannel; import org.jcodec.common.model.ColorSpace; import org.jcodec.common.model.Packet; import org.jcodec.common.model.Rational; import org.jcodec.common.model.Size; import org.jcodec.common.model.Packet.FrameType; /** * This class is part of JCodec ( www.jcodec.org ) This software is distributed * under FreeBSD License * * @author The JCodec project * */ public class Y4MDemuxer implements DemuxerTrack, Demuxer { private SeekableByteChannel is; private int width; private int height; private String invalidFormat; private Rational fps; private int bufSize; private int frameNum; private int totalFrames; private int totalDuration; public Y4MDemuxer(SeekableByteChannel _is) throws IOException { this.is = _is; ByteBuffer buf = NIOUtils.fetchFromChannel(is, 2048); String[] header = splitC(readLine(buf), ' '); if (!"YUV4MPEG2".equals(header[0])) { invalidFormat = "Not yuv4mpeg stream"; return; } String chroma = find(header, 'C'); if (chroma != null && !chroma.startsWith("420")) { invalidFormat = "Only yuv420p is supported"; return; } width = Integer.parseInt(find(header, 'W')); height = Integer.parseInt(find(header, 'H')); String fpsStr = find(header, 'F'); if (fpsStr != null) { String[] numden = splitC(fpsStr, ':'); fps = new Rational(Integer.parseInt(numden[0]), Integer.parseInt(numden[1])); } is.setPosition(buf.position()); bufSize = width * height; bufSize += bufSize / 2; long fileSize = is.size(); totalFrames = (int) (fileSize / (bufSize + 7)); totalDuration = (totalFrames * fps.getDen()) / fps.getNum(); } @Override public Packet nextFrame() throws IOException { if (invalidFormat != null) throw new RuntimeException("Invalid input: " + invalidFormat); ByteBuffer buf = NIOUtils.fetchFromChannel(is, 2048); String frame = readLine(buf); if (frame == null || !frame.startsWith("FRAME")) return null; is.setPosition(is.position() - buf.remaining()); ByteBuffer pix = NIOUtils.fetchFromChannel(is, bufSize); Packet packet = new Packet(pix, frameNum * fps.getDen(), fps.getNum(), fps.getDen(), frameNum, FrameType.KEY, null, frameNum); ++frameNum; return packet; } private static String find(String[] header, char c) { for (int i = 0; i < header.length; i++) { String string = header[i]; if (string.charAt(0) == c) return string.substring(1); } return null; } private static String readLine(ByteBuffer y4m) { ByteBuffer duplicate = y4m.duplicate(); while (y4m.hasRemaining() && y4m.get() != '\n') ; if (y4m.hasRemaining()) duplicate.limit(y4m.position() - 1); return stringFromBytes(NIOUtils.toArray(duplicate)); } public Rational getFps() { return fps; } @Override public DemuxerTrackMeta getMeta() { return new DemuxerTrackMeta(TrackType.VIDEO, Codec.RAW, totalDuration, null, totalFrames, null, new VideoCodecMeta(new Size(width, height), ColorSpace.YUV420), null); } @Override public void close() throws IOException { is.close(); } @Override public List<? extends DemuxerTrack> getTracks() { List<DemuxerTrack> list = new ArrayList<DemuxerTrack>(); list.add(this); return list; } @Override public List<? extends DemuxerTrack> getVideoTracks() { return getTracks(); } @Override public List<? extends DemuxerTrack> getAudioTracks() { return new ArrayList<DemuxerTrack>(); } }