www in docker support

This commit is contained in:
MaddoScientisto 2026-04-22 18:41:37 +02:00
commit c227fce036
2145 changed files with 399596 additions and 58 deletions

View file

@ -0,0 +1,23 @@
package org.jcodec.api.transcode;
import org.jcodec.common.model.AudioBuffer;
import org.jcodec.common.model.Packet;
public class AudioFrameWithPacket {
private AudioBuffer audio;
private Packet packet;
public AudioFrameWithPacket(AudioBuffer audio, Packet packet) {
this.audio = audio;
this.packet = packet;
}
public AudioBuffer getAudio() {
return this.audio;
}
public Packet getPacket() {
return this.packet;
}
}

View file

@ -0,0 +1,12 @@
package org.jcodec.api.transcode;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Picture;
public interface Filter {
PixelStore.LoanerPicture filter(Picture paramPicture, PixelStore paramPixelStore);
ColorSpace getInputColor();
ColorSpace getOutputColor();
}

View file

@ -0,0 +1,5 @@
package org.jcodec.api.transcode;
public enum Options {
PROFILE, INTERLACED, DOWNSCALE;
}

View file

@ -0,0 +1,12 @@
package org.jcodec.api.transcode;
import java.io.IOException;
import org.jcodec.common.AudioCodecMeta;
import org.jcodec.common.VideoCodecMeta;
import org.jcodec.common.model.Packet;
public interface PacketSink {
void outputVideoPacket(Packet paramPacket, VideoCodecMeta paramVideoCodecMeta) throws IOException;
void outputAudioPacket(Packet paramPacket, AudioCodecMeta paramAudioCodecMeta) throws IOException;
}

View file

@ -0,0 +1,10 @@
package org.jcodec.api.transcode;
import java.io.IOException;
import org.jcodec.common.model.Packet;
public interface PacketSource {
Packet inputVideoPacket() throws IOException;
Packet inputAudioPacket() throws IOException;
}

View file

@ -0,0 +1,43 @@
package org.jcodec.api.transcode;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Picture;
public interface PixelStore {
LoanerPicture getPicture(int paramInt1, int paramInt2, ColorSpace paramColorSpace);
void putBack(LoanerPicture paramLoanerPicture);
void retake(LoanerPicture paramLoanerPicture);
public static class LoanerPicture {
private Picture picture;
private int refCnt;
public LoanerPicture(Picture picture, int refCnt) {
this.picture = picture;
this.refCnt = refCnt;
}
public Picture getPicture() {
return this.picture;
}
public int getRefCnt() {
return this.refCnt;
}
public void decRefCnt() {
this.refCnt--;
}
public boolean unused() {
return (this.refCnt <= 0);
}
public void incRefCnt() {
this.refCnt++;
}
}
}

View file

@ -0,0 +1,34 @@
package org.jcodec.api.transcode;
import java.util.ArrayList;
import java.util.List;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Picture;
public class PixelStoreImpl implements PixelStore {
private List<Picture> buffers = new ArrayList<>();
public PixelStore.LoanerPicture getPicture(int width, int height, ColorSpace color) {
for (Picture picture : this.buffers) {
if (picture.getWidth() == width && picture.getHeight() == height &&
picture.getColor() == color) {
this.buffers.remove(picture);
return new PixelStore.LoanerPicture(picture, 1);
}
}
return new PixelStore.LoanerPicture(Picture.create(width, height, color), 1);
}
public void putBack(PixelStore.LoanerPicture frame) {
frame.decRefCnt();
if (frame.unused()) {
Picture pixels = frame.getPicture();
pixels.setCrop(null);
this.buffers.add(pixels);
}
}
public void retake(PixelStore.LoanerPicture frame) {
frame.incRefCnt();
}
}

View file

@ -0,0 +1,22 @@
package org.jcodec.api.transcode;
import java.io.IOException;
import org.jcodec.common.model.ColorSpace;
public interface Sink {
void init() throws IOException;
void outputVideoFrame(VideoFrameWithPacket paramVideoFrameWithPacket) throws IOException;
void outputAudioFrame(AudioFrameWithPacket paramAudioFrameWithPacket) throws IOException;
void finish() throws IOException;
ColorSpace getInputColor();
void setOption(Options paramOptions, Object paramObject);
boolean isVideo();
boolean isAudio();
}

View file

@ -0,0 +1,231 @@
package org.jcodec.api.transcode;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.jcodec.codecs.h264.H264Encoder;
import org.jcodec.codecs.png.PNGEncoder;
import org.jcodec.codecs.prores.ProresEncoder;
import org.jcodec.codecs.raw.RAWVideoEncoder;
import org.jcodec.codecs.vpx.IVFMuxer;
import org.jcodec.codecs.vpx.VP8Encoder;
import org.jcodec.codecs.wav.WavMuxer;
import org.jcodec.codecs.y4m.Y4MMuxer;
import org.jcodec.common.AudioCodecMeta;
import org.jcodec.common.AudioEncoder;
import org.jcodec.common.AudioFormat;
import org.jcodec.common.Codec;
import org.jcodec.common.Format;
import org.jcodec.common.Muxer;
import org.jcodec.common.MuxerTrack;
import org.jcodec.common.VideoCodecMeta;
import org.jcodec.common.VideoEncoder;
import org.jcodec.common.io.IOUtils;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.common.logging.Logger;
import org.jcodec.common.model.AudioBuffer;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Packet;
import org.jcodec.common.model.Picture;
import org.jcodec.common.model.Size;
import org.jcodec.containers.imgseq.ImageSequenceMuxer;
import org.jcodec.containers.mkv.muxer.MKVMuxer;
import org.jcodec.containers.mp4.muxer.MP4Muxer;
import org.jcodec.containers.raw.RawMuxer;
public class SinkImpl implements Sink, PacketSink {
private String destName;
private SeekableByteChannel destStream;
private Muxer muxer;
private MuxerTrack videoOutputTrack;
private MuxerTrack audioOutputTrack;
private boolean framesOutput;
private Codec outputVideoCodec;
private Codec outputAudioCodec;
private Format outputFormat;
private final ThreadLocal<ByteBuffer> bufferStore;
private AudioEncoder audioEncoder;
private VideoEncoder videoEncoder;
private String profile;
private boolean interlaced;
public void outputVideoPacket(Packet packet, VideoCodecMeta codecMeta) throws IOException {
if (!this.outputFormat.isVideo())
return;
if (this.videoOutputTrack == null)
this.videoOutputTrack = this.muxer.addVideoTrack(this.outputVideoCodec, codecMeta);
this.videoOutputTrack.addFrame(packet);
this.framesOutput = true;
}
public void outputAudioPacket(Packet audioPkt, AudioCodecMeta audioCodecMeta) throws IOException {
if (!this.outputFormat.isAudio())
return;
if (this.audioOutputTrack == null)
this.audioOutputTrack = this.muxer.addAudioTrack(this.outputAudioCodec, audioCodecMeta);
this.audioOutputTrack.addFrame(audioPkt);
this.framesOutput = true;
}
public void initMuxer() throws IOException {
if (this.destStream == null && this.outputFormat != Format.IMG)
this.destStream = NIOUtils.writableFileChannel(this.destName);
if (Format.MKV == this.outputFormat) {
this.muxer = new MKVMuxer(this.destStream);
} else if (Format.MOV == this.outputFormat) {
this.muxer = MP4Muxer.createMP4MuxerToChannel(this.destStream);
} else if (Format.IVF == this.outputFormat) {
this.muxer = new IVFMuxer(this.destStream);
} else if (Format.IMG == this.outputFormat) {
this.muxer = new ImageSequenceMuxer(this.destName);
} else if (Format.WAV == this.outputFormat) {
this.muxer = new WavMuxer(this.destStream);
} else if (Format.Y4M == this.outputFormat) {
this.muxer = new Y4MMuxer(this.destStream);
} else if (Format.RAW == this.outputFormat) {
this.muxer = new RawMuxer(this.destStream);
} else {
throw new RuntimeException("The output format " + String.valueOf(this.outputFormat) + " is not supported.");
}
}
public void finish() throws IOException {
if (this.framesOutput) {
this.muxer.finish();
} else {
Logger.warn("No frames output.");
}
if (this.destStream != null)
IOUtils.closeQuietly(this.destStream);
}
public SinkImpl(String destName, Format outputFormat, Codec outputVideoCodec, Codec outputAudioCodec) {
if (destName == null && outputFormat == Format.IMG)
throw new IllegalArgumentException("A destination file should be specified for the image muxer.");
this.destName = destName;
this.outputFormat = outputFormat;
this.outputVideoCodec = outputVideoCodec;
this.outputAudioCodec = outputAudioCodec;
this.outputFormat = outputFormat;
this.bufferStore = new ThreadLocal<>();
}
public static SinkImpl createWithStream(SeekableByteChannel destStream, Format outputFormat, Codec outputVideoCodec, Codec outputAudioCodec) {
SinkImpl result = new SinkImpl(null, outputFormat, outputVideoCodec, outputAudioCodec);
result.destStream = destStream;
return result;
}
public void init() throws IOException {
initMuxer();
if (this.outputFormat.isVideo() && this.outputVideoCodec != null)
if (Codec.PRORES == this.outputVideoCodec) {
this.videoEncoder = ProresEncoder.createProresEncoder(this.profile, this.interlaced);
} else if (Codec.H264 == this.outputVideoCodec) {
this.videoEncoder = H264Encoder.createH264Encoder();
} else if (Codec.VP8 == this.outputVideoCodec) {
this.videoEncoder = VP8Encoder.createVP8Encoder(10);
} else if (Codec.PNG == this.outputVideoCodec) {
this.videoEncoder = new PNGEncoder();
} else if (Codec.RAW == this.outputVideoCodec) {
this.videoEncoder = new RAWVideoEncoder();
} else {
throw new RuntimeException("Could not find encoder for the codec: " + String.valueOf(this.outputVideoCodec));
}
}
protected VideoEncoder.EncodedFrame encodeVideo(Picture frame, ByteBuffer _out) {
if (!this.outputFormat.isVideo())
return null;
return this.videoEncoder.encodeFrame(frame, _out);
}
private AudioEncoder createAudioEncoder(Codec codec, AudioFormat format) {
if (codec != Codec.PCM)
throw new RuntimeException("Only PCM audio encoding (RAW audio) is supported.");
return new RawAudioEncoder();
}
private static class RawAudioEncoder implements AudioEncoder {
public ByteBuffer encode(ByteBuffer audioPkt, ByteBuffer buf) {
return audioPkt;
}
}
protected ByteBuffer encodeAudio(AudioBuffer audioBuffer) {
if (this.audioEncoder == null) {
AudioFormat format = audioBuffer.getFormat();
this.audioEncoder = createAudioEncoder(this.outputAudioCodec, format);
}
return this.audioEncoder.encode(audioBuffer.getData(), null);
}
public void setProfile(String profile) {
this.profile = profile;
}
public void setInterlaced(Boolean interlaced) {
this.interlaced = interlaced;
}
public void outputVideoFrame(VideoFrameWithPacket videoFrame) throws IOException {
if (!this.outputFormat.isVideo() || this.outputVideoCodec == null)
return;
ByteBuffer buffer = this.bufferStore.get();
int bufferSize = this.videoEncoder.estimateBufferSize(videoFrame.getFrame().getPicture());
if (buffer == null || bufferSize < buffer.capacity()) {
buffer = ByteBuffer.allocate(bufferSize);
this.bufferStore.set(buffer);
}
buffer.clear();
Picture frame = videoFrame.getFrame().getPicture();
VideoEncoder.EncodedFrame enc = encodeVideo(frame, buffer);
Packet outputVideoPacket = Packet.createPacketWithData(videoFrame.getPacket(), NIOUtils.clone(enc.getData()));
outputVideoPacket.setFrameType(enc.isKeyFrame() ? Packet.FrameType.KEY : Packet.FrameType.INTER);
outputVideoPacket(outputVideoPacket,
VideoCodecMeta.createSimpleVideoCodecMeta(new Size(frame.getWidth(), frame.getHeight()), frame.getColor()));
}
public void outputAudioFrame(AudioFrameWithPacket audioFrame) throws IOException {
if (!this.outputFormat.isAudio() || this.outputAudioCodec == null)
return;
outputAudioPacket(Packet.createPacketWithData(audioFrame.getPacket(), encodeAudio(audioFrame.getAudio())),
AudioCodecMeta.fromAudioFormat(audioFrame.getAudio().getFormat()));
}
public ColorSpace getInputColor() {
if (this.videoEncoder == null)
throw new IllegalStateException("Video encoder has not been initialized, init() must be called before using this class.");
ColorSpace[] colorSpaces = this.videoEncoder.getSupportedColorSpaces();
return (colorSpaces == null) ? null : colorSpaces[0];
}
public void setOption(Options option, Object value) {
if (option == Options.PROFILE) {
this.profile = (String)value;
} else if (option == Options.INTERLACED) {
this.interlaced = (Boolean)value;
}
}
public boolean isVideo() {
return this.outputFormat.isVideo();
}
public boolean isAudio() {
return this.outputFormat.isAudio();
}
}

View file

@ -0,0 +1,29 @@
package org.jcodec.api.transcode;
import java.io.IOException;
import org.jcodec.common.AudioCodecMeta;
import org.jcodec.common.VideoCodecMeta;
public interface Source {
void init(PixelStore paramPixelStore) throws IOException;
void seekFrames(int paramInt) throws IOException;
VideoFrameWithPacket getNextVideoFrame() throws IOException;
AudioFrameWithPacket getNextAudioFrame() throws IOException;
void finish();
boolean haveAudio();
void setOption(Options paramOptions, Object paramObject);
VideoCodecMeta getVideoCodecMeta();
AudioCodecMeta getAudioCodecMeta();
boolean isVideo();
boolean isAudio();
}

View file

@ -0,0 +1,463 @@
package org.jcodec.api.transcode;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import net.sourceforge.jaad.aac.AACException;
import org.jcodec.codecs.aac.AACDecoder;
import org.jcodec.codecs.h264.BufferH264ES;
import org.jcodec.codecs.h264.H264Decoder;
import org.jcodec.codecs.h264.H264Utils;
import org.jcodec.codecs.mjpeg.JpegDecoder;
import org.jcodec.codecs.mjpeg.JpegToThumb2x2;
import org.jcodec.codecs.mjpeg.JpegToThumb4x4;
import org.jcodec.codecs.mpeg12.MPEGDecoder;
import org.jcodec.codecs.mpeg12.Mpeg2Thumb2x2;
import org.jcodec.codecs.mpeg12.Mpeg2Thumb4x4;
import org.jcodec.codecs.mpeg4.MPEG4Decoder;
import org.jcodec.codecs.png.PNGDecoder;
import org.jcodec.codecs.prores.ProresDecoder;
import org.jcodec.codecs.prores.ProresToThumb;
import org.jcodec.codecs.prores.ProresToThumb2x2;
import org.jcodec.codecs.prores.ProresToThumb4x4;
import org.jcodec.codecs.raw.RAWVideoDecoder;
import org.jcodec.codecs.vpx.VP8Decoder;
import org.jcodec.codecs.wav.WavDemuxer;
import org.jcodec.common.AudioCodecMeta;
import org.jcodec.common.AudioDecoder;
import org.jcodec.common.AudioFormat;
import org.jcodec.common.Codec;
import org.jcodec.common.Demuxer;
import org.jcodec.common.DemuxerTrack;
import org.jcodec.common.DemuxerTrackMeta;
import org.jcodec.common.Format;
import org.jcodec.common.SeekableDemuxerTrack;
import org.jcodec.common.Tuple;
import org.jcodec.common.VideoCodecMeta;
import org.jcodec.common.VideoDecoder;
import org.jcodec.common.io.IOUtils;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.common.logging.Logger;
import org.jcodec.common.model.AudioBuffer;
import org.jcodec.common.model.Packet;
import org.jcodec.common.model.Picture;
import org.jcodec.common.model.Size;
import org.jcodec.containers.imgseq.ImageSequenceDemuxer;
import org.jcodec.containers.mkv.demuxer.MKVDemuxer;
import org.jcodec.containers.mp3.MPEGAudioDemuxer;
import org.jcodec.containers.mp4.demuxer.MP4Demuxer;
import org.jcodec.containers.mps.MPEGDemuxer;
import org.jcodec.containers.mps.MPSDemuxer;
import org.jcodec.containers.mps.MTSDemuxer;
import org.jcodec.containers.webp.WebpDemuxer;
import org.jcodec.containers.y4m.Y4MDemuxer;
public class SourceImpl implements Source, PacketSource {
private String sourceName;
private SeekableByteChannel sourceStream;
private Demuxer demuxVideo;
private Demuxer demuxAudio;
private Format inputFormat;
private DemuxerTrack videoInputTrack;
private DemuxerTrack audioInputTrack;
private Tuple._3<Integer, Integer, Codec> inputVideoCodec;
private Tuple._3<Integer, Integer, Codec> inputAudioCodec;
private List<VideoFrameWithPacket> frameReorderBuffer;
private List<Packet> videoPacketReorderBuffer;
private PixelStore pixelStore;
private VideoCodecMeta videoCodecMeta;
private AudioCodecMeta audioCodecMeta;
private AudioDecoder audioDecoder;
private VideoDecoder videoDecoder;
private int downscale = 1;
public static MPEGDecoder createMpegDecoder(int downscale) {
if (downscale == 2)
return new Mpeg2Thumb4x4();
if (downscale == 4)
return new Mpeg2Thumb2x2();
return new MPEGDecoder();
}
public static ProresDecoder createProresDecoder(int downscale) {
if (2 == downscale)
return new ProresToThumb4x4();
if (4 == downscale)
return new ProresToThumb2x2();
if (8 == downscale)
return new ProresToThumb();
return new ProresDecoder();
}
public void initDemuxer() throws FileNotFoundException, IOException {
if (this.inputFormat != Format.IMG)
this.sourceStream = NIOUtils.readableFileChannel(this.sourceName);
if (Format.MOV == this.inputFormat) {
this.demuxVideo = this.demuxAudio = MP4Demuxer.createMP4Demuxer(this.sourceStream);
} else if (Format.MKV == this.inputFormat) {
this.demuxVideo = this.demuxAudio = new MKVDemuxer(this.sourceStream);
} else if (Format.IMG == this.inputFormat) {
this.demuxVideo = new ImageSequenceDemuxer(this.sourceName, Integer.MAX_VALUE);
} else if (Format.WEBP == this.inputFormat) {
this.demuxVideo = new WebpDemuxer(this.sourceStream);
} else if (Format.MPEG_PS == this.inputFormat) {
this.demuxVideo = this.demuxAudio = new MPSDemuxer(this.sourceStream);
} else if (Format.Y4M == this.inputFormat) {
Y4MDemuxer y4mDemuxer = new Y4MDemuxer(this.sourceStream);
this.demuxVideo = this.demuxAudio = y4mDemuxer;
this.videoInputTrack = y4mDemuxer;
} else if (Format.H264 == this.inputFormat) {
this.demuxVideo = new BufferH264ES(NIOUtils.fetchAllFromChannel(this.sourceStream));
} else if (Format.WAV == this.inputFormat) {
this.demuxAudio = new WavDemuxer(this.sourceStream);
} else if (Format.MPEG_AUDIO == this.inputFormat) {
this.demuxAudio = new MPEGAudioDemuxer(this.sourceStream);
} else if (Format.MPEG_TS == this.inputFormat) {
MTSDemuxer mtsDemuxer = new MTSDemuxer(this.sourceStream);
MPSDemuxer mpsDemuxer = null;
if (this.inputVideoCodec != null) {
mpsDemuxer = new MPSDemuxer(mtsDemuxer.getProgram(((Integer)this.inputVideoCodec.v0).intValue()));
this.videoInputTrack = openTSTrack(mpsDemuxer, (Integer)this.inputVideoCodec.v1);
this.demuxVideo = mpsDemuxer;
}
if (this.inputAudioCodec != null) {
if (this.inputVideoCodec == null || this.inputVideoCodec.v0 != this.inputAudioCodec.v0)
mpsDemuxer = new MPSDemuxer(mtsDemuxer.getProgram(((Integer)this.inputAudioCodec.v0).intValue()));
this.audioInputTrack = openTSTrack(mpsDemuxer, (Integer)this.inputAudioCodec.v1);
this.demuxAudio = mpsDemuxer;
}
for (Iterator<Integer> iterator = mtsDemuxer.getPrograms().iterator(); iterator.hasNext(); ) {
int pid = iterator.next();
if ((this.inputVideoCodec == null || pid != (Integer)this.inputVideoCodec.v0) && (this.inputAudioCodec == null || pid != (Integer)this.inputAudioCodec.v0)) {
Logger.info("Unused program: " + pid);
mtsDemuxer.getProgram(pid).close();
}
}
} else {
throw new RuntimeException("Input format: " + String.valueOf(this.inputFormat) + " is not supported.");
}
if (this.demuxVideo != null && this.inputVideoCodec != null) {
List<? extends DemuxerTrack> videoTracks = this.demuxVideo.getVideoTracks();
if (videoTracks.size() > 0)
this.videoInputTrack = videoTracks.get(((Integer)this.inputVideoCodec.v1).intValue());
}
if (this.demuxAudio != null && this.inputAudioCodec != null) {
List<? extends DemuxerTrack> audioTracks = this.demuxAudio.getAudioTracks();
if (audioTracks.size() > 0)
this.audioInputTrack = audioTracks.get(((Integer)this.inputAudioCodec.v1).intValue());
}
}
protected int seekToKeyFrame(int frame) throws IOException {
if (this.videoInputTrack instanceof SeekableDemuxerTrack) {
SeekableDemuxerTrack seekable = (SeekableDemuxerTrack)this.videoInputTrack;
seekable.gotoSyncFrame((long)frame);
return (int)seekable.getCurFrame();
}
Logger.warn("Can not seek in " + String.valueOf(this.videoInputTrack) + " container.");
return -1;
}
private MPEGDemuxer.MPEGDemuxerTrack openTSTrack(MPSDemuxer demuxerVideo, Integer selectedTrack) {
int trackNo = 0;
for (MPEGDemuxer.MPEGDemuxerTrack track : demuxerVideo.getTracks()) {
if (trackNo == selectedTrack)
return track;
track.ignore();
trackNo++;
}
return null;
}
public Packet inputVideoPacket() throws IOException {
Packet packet;
do {
packet = getNextVideoPacket();
if (packet == null)
continue;
this.videoPacketReorderBuffer.add(packet);
} while (packet != null && this.videoPacketReorderBuffer.size() <= 7);
if (this.videoPacketReorderBuffer.size() == 0)
return null;
Packet out = (Packet)this.videoPacketReorderBuffer.remove(0);
int duration = Integer.MAX_VALUE;
for (Packet packet2 : this.videoPacketReorderBuffer) {
int cand = (int)(packet2.getPts() - out.getPts());
if (cand > 0 && cand < duration)
duration = cand;
}
if (duration != Integer.MAX_VALUE)
out.setDuration((long)duration);
return out;
}
private Packet getNextVideoPacket() throws IOException {
if (this.videoInputTrack == null)
return null;
Packet nextFrame = this.videoInputTrack.nextFrame();
if (this.videoDecoder == null) {
this.videoDecoder = createVideoDecoder((Codec)this.inputVideoCodec.v2, this.downscale, nextFrame.getData(), null);
if (this.videoDecoder != null)
this.videoCodecMeta = this.videoDecoder.getCodecMeta(nextFrame.getData());
}
return nextFrame;
}
public Packet inputAudioPacket() throws IOException {
if (this.audioInputTrack == null)
return null;
Packet audioPkt = this.audioInputTrack.nextFrame();
if (this.audioDecoder == null && audioPkt != null) {
this.audioDecoder = createAudioDecoder(audioPkt.getData());
if (this.audioDecoder != null)
this.audioCodecMeta = this.audioDecoder.getCodecMeta(audioPkt.getData());
}
return audioPkt;
}
public DemuxerTrackMeta getTrackVideoMeta() {
if (this.videoInputTrack == null)
return null;
return this.videoInputTrack.getMeta();
}
public DemuxerTrackMeta getAudioMeta() {
if (this.audioInputTrack == null)
return null;
return this.audioInputTrack.getMeta();
}
public boolean haveAudio() {
return (this.audioInputTrack != null);
}
public void finish() {
if (this.sourceStream != null)
IOUtils.closeQuietly(this.sourceStream);
}
public SourceImpl(String sourceName, Format inputFormat, Tuple._3<Integer, Integer, Codec> inputVideoCodec, Tuple._3<Integer, Integer, Codec> inputAudioCodec) {
this.sourceName = sourceName;
this.inputFormat = inputFormat;
this.inputVideoCodec = inputVideoCodec;
this.inputAudioCodec = inputAudioCodec;
this.frameReorderBuffer = new ArrayList<>();
this.videoPacketReorderBuffer = new ArrayList<>();
}
public void init(PixelStore pixelStore) throws IOException {
this.pixelStore = pixelStore;
initDemuxer();
}
private AudioDecoder createAudioDecoder(ByteBuffer codecPrivate) throws AACException {
if (Codec.AAC == this.inputAudioCodec.v2)
return new AACDecoder(codecPrivate);
if (Codec.PCM == this.inputAudioCodec.v2)
return new RawAudioDecoder(getAudioMeta().getAudioCodecMeta().getFormat());
return null;
}
private VideoDecoder createVideoDecoder(Codec codec, int downscale, ByteBuffer codecPrivate, VideoCodecMeta videoCodecMeta) {
if (Codec.H264 == codec)
return H264Decoder.createH264DecoderFromCodecPrivate(codecPrivate);
if (Codec.PNG == codec)
return new PNGDecoder();
if (Codec.MPEG2 == codec)
return createMpegDecoder(downscale);
if (Codec.PRORES == codec)
return createProresDecoder(downscale);
if (Codec.VP8 == codec)
return new VP8Decoder();
if (Codec.JPEG == codec)
return createJpegDecoder(downscale);
if (Codec.MPEG4 == codec)
return new MPEG4Decoder();
if (Codec.RAW == codec) {
Size dim = videoCodecMeta.getSize();
return new RAWVideoDecoder(dim.getWidth(), dim.getHeight());
}
return null;
}
public Picture decodeVideo(ByteBuffer data, Picture target1) {
return this.videoDecoder.decodeFrame(data, target1.getData());
}
protected ByteBuffer decodeAudio(ByteBuffer audioPkt) throws IOException {
if (this.inputAudioCodec.v2 == Codec.PCM)
return audioPkt;
AudioBuffer decodeFrame = this.audioDecoder.decodeFrame(audioPkt, null);
return decodeFrame.getData();
}
private static class RawAudioDecoder implements AudioDecoder {
private AudioFormat format;
public RawAudioDecoder(AudioFormat format) {
this.format = format;
}
public AudioBuffer decodeFrame(ByteBuffer frame, ByteBuffer dst) throws IOException {
return new AudioBuffer(frame, this.format, frame.remaining() / this.format.getFrameSize());
}
public AudioCodecMeta getCodecMeta(ByteBuffer data) throws IOException {
return AudioCodecMeta.fromAudioFormat(this.format);
}
}
public void seekFrames(int seekFrames) throws IOException {
if (seekFrames == 0)
return;
int skipFrames = seekFrames - seekToKeyFrame(seekFrames);
Packet inVideoPacket;
while (skipFrames > 0 && (inVideoPacket = getNextVideoPacket()) != null) {
PixelStore.LoanerPicture loanerBuffer = getPixelBuffer(inVideoPacket.getData());
Picture decodedFrame = decodeVideo(inVideoPacket.getData(), loanerBuffer.getPicture());
if (decodedFrame == null) {
this.pixelStore.putBack(loanerBuffer);
continue;
}
this.frameReorderBuffer.add(new VideoFrameWithPacket(inVideoPacket, new PixelStore.LoanerPicture(decodedFrame, 1)));
if (this.frameReorderBuffer.size() > 7) {
Collections.sort(this.frameReorderBuffer);
VideoFrameWithPacket removed = (VideoFrameWithPacket)this.frameReorderBuffer.remove(0);
skipFrames--;
if (removed.getFrame() != null)
this.pixelStore.putBack(removed.getFrame());
}
}
}
private void detectFrameType(Packet inVideoPacket) {
if (this.inputVideoCodec.v2 != Codec.H264)
return;
inVideoPacket.setFrameType(
H264Utils.isByteBufferIDRSlice(inVideoPacket.getData()) ? Packet.FrameType.KEY : Packet.FrameType.INTER);
}
protected PixelStore.LoanerPicture getPixelBuffer(ByteBuffer firstFrame) {
VideoCodecMeta videoMeta = getVideoCodecMeta();
Size size = videoMeta.getSize();
return this.pixelStore.getPicture(size.getWidth() + 15 & 0xFFFFFFF0, size.getHeight() + 15 & 0xFFFFFFF0,
videoMeta.getColor());
}
public VideoCodecMeta getVideoCodecMeta() {
if (this.videoCodecMeta != null)
return this.videoCodecMeta;
DemuxerTrackMeta meta = getTrackVideoMeta();
if (meta != null && meta.getVideoCodecMeta() != null)
this.videoCodecMeta = meta.getVideoCodecMeta();
return this.videoCodecMeta;
}
public VideoFrameWithPacket getNextVideoFrame() throws IOException {
Packet inVideoPacket;
while ((inVideoPacket = getNextVideoPacket()) != null) {
if (inVideoPacket.getFrameType() == Packet.FrameType.UNKNOWN)
detectFrameType(inVideoPacket);
Picture decodedFrame = null;
PixelStore.LoanerPicture pixelBuffer = getPixelBuffer(inVideoPacket.getData());
decodedFrame = decodeVideo(inVideoPacket.getData(), pixelBuffer.getPicture());
if (decodedFrame == null) {
this.pixelStore.putBack(pixelBuffer);
continue;
}
this.frameReorderBuffer.add(new VideoFrameWithPacket(inVideoPacket, new PixelStore.LoanerPicture(decodedFrame, 1)));
if (this.frameReorderBuffer.size() > 7)
return removeFirstFixDuration(this.frameReorderBuffer);
}
if (this.frameReorderBuffer.size() > 0)
return removeFirstFixDuration(this.frameReorderBuffer);
return null;
}
private VideoFrameWithPacket removeFirstFixDuration(List<VideoFrameWithPacket> reorderBuffer) {
Collections.sort(reorderBuffer);
VideoFrameWithPacket frame = (VideoFrameWithPacket)reorderBuffer.remove(0);
if (!reorderBuffer.isEmpty()) {
VideoFrameWithPacket nextFrame = reorderBuffer.get(0);
frame.getPacket().setDuration(nextFrame.getPacket().getPts() - frame.getPacket().getPts());
}
return frame;
}
public AudioFrameWithPacket getNextAudioFrame() throws IOException {
AudioBuffer audioBuffer;
Packet audioPkt = inputAudioPacket();
if (audioPkt == null)
return null;
if (this.inputAudioCodec.v2 == Codec.PCM) {
DemuxerTrackMeta audioMeta = getAudioMeta();
audioBuffer = new AudioBuffer(audioPkt.getData(), audioMeta.getAudioCodecMeta().getFormat(), audioMeta.getTotalFrames());
} else {
audioBuffer = this.audioDecoder.decodeFrame(audioPkt.getData(), null);
}
return new AudioFrameWithPacket(audioBuffer, audioPkt);
}
public Tuple._3<Integer, Integer, Codec> getIntputVideoCodec() {
return this.inputVideoCodec;
}
public Tuple._3<Integer, Integer, Codec> getInputAudioCode() {
return this.inputAudioCodec;
}
public void setOption(Options option, Object value) {
if (option == Options.DOWNSCALE)
this.downscale = (Integer)value;
}
public AudioCodecMeta getAudioCodecMeta() {
if (this.audioInputTrack != null && this.audioInputTrack.getMeta() != null &&
this.audioInputTrack.getMeta().getAudioCodecMeta() != null)
return this.audioInputTrack.getMeta().getAudioCodecMeta();
return this.audioCodecMeta;
}
public boolean isVideo() {
if (!this.inputFormat.isVideo())
return false;
List<? extends DemuxerTrack> tracks = this.demuxVideo.getVideoTracks();
return (tracks != null && tracks.size() > 0);
}
public boolean isAudio() {
if (!this.inputFormat.isAudio())
return false;
List<? extends DemuxerTrack> tracks = this.demuxAudio.getAudioTracks();
return (tracks != null && tracks.size() > 0);
}
public static JpegDecoder createJpegDecoder(int downscale) {
if (downscale == 2)
return new JpegToThumb4x4();
if (downscale == 4)
return new JpegToThumb2x2();
return new JpegDecoder();
}
}

View file

@ -0,0 +1,396 @@
package org.jcodec.api.transcode;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jcodec.api.transcode.filters.DumpMvFilter;
import org.jcodec.api.transcode.filters.ScaleFilter;
import org.jcodec.common.Codec;
import org.jcodec.common.Demuxer;
import org.jcodec.common.DemuxerTrack;
import org.jcodec.common.DemuxerTrackMeta;
import org.jcodec.common.Format;
import org.jcodec.common.JCodecUtil;
import org.jcodec.common.TrackType;
import org.jcodec.common.Tuple;
import org.jcodec.common.logging.LogLevel;
import org.jcodec.common.logging.Logger;
import org.jcodec.common.logging.OutLogSink;
import org.jcodec.common.model.Packet;
import org.jcodec.common.tools.MainUtils;
import org.jcodec.common.tools.MathUtil;
import org.jcodec.platform.Platform;
public class TranscodeMain {
private static final MainUtils.Flag FLAG_INPUT = new MainUtils.Flag("input", "i", "Designates an input argument", MainUtils.FlagType.VOID);
private static final MainUtils.Flag FLAG_MAP_VIDEO = MainUtils.Flag.flag("map:v", "mv", "Map a video from a specified input into this output");
private static final MainUtils.Flag FLAG_MAP_AUDIO = MainUtils.Flag.flag("map:a", "ma", "Map a audio from a specified input into this output");
private static final MainUtils.Flag FLAG_SEEK_FRAMES = MainUtils.Flag.flag("seek-frames", null, "Seek frames");
private static final MainUtils.Flag FLAG_MAX_FRAMES = MainUtils.Flag.flag("max-frames", "limit", "Max frames");
private static final MainUtils.Flag FLAG_AUDIO_CODEC = MainUtils.Flag.flag("codec:audio", "acodec", "Audio codec [default=auto].");
private static final MainUtils.Flag FLAG_VIDEO_CODEC = MainUtils.Flag.flag("codec:video", "vcodec", "Video codec [default=auto].");
private static final MainUtils.Flag FLAG_FORMAT = MainUtils.Flag.flag("format", "f", "Format [default=auto].");
private static final MainUtils.Flag FLAG_PROFILE = MainUtils.Flag.flag("profile", null, "Profile to use (supported by some encoders).");
private static final MainUtils.Flag FLAG_INTERLACED = MainUtils.Flag.flag("interlaced", null, "Encode output as interlaced (supported by Prores encoder).");
private static final MainUtils.Flag FLAG_DUMPMV = MainUtils.Flag.flag("dumpMv", null, "Dump motion vectors (supported by h.264 decoder).");
private static final MainUtils.Flag FLAG_DUMPMVJS = MainUtils.Flag.flag("dumpMvJs", null, "Dump motion vectors in form of JASON file (supported by h.264 decoder).");
private static final MainUtils.Flag FLAG_DOWNSCALE = MainUtils.Flag.flag("downscale", null, "Decode frames in downscale (supported by MPEG, Prores and Jpeg decoders).");
private static final MainUtils.Flag FLAG_VIDEO_FILTER = MainUtils.Flag.flag("videoFilter", "vf", "Contains a comma separated list of video filters with arguments.");
private static final MainUtils.Flag[] ALL_FLAGS = new MainUtils.Flag[] {
FLAG_INPUT, FLAG_FORMAT, FLAG_VIDEO_CODEC, FLAG_AUDIO_CODEC, FLAG_SEEK_FRAMES, FLAG_MAX_FRAMES, FLAG_PROFILE, FLAG_INTERLACED, FLAG_DUMPMV, FLAG_DUMPMVJS,
FLAG_DOWNSCALE, FLAG_MAP_VIDEO, FLAG_MAP_AUDIO, FLAG_VIDEO_FILTER };
private static Map<String, Format> extensionToF = new HashMap<>();
private static Map<String, Codec> extensionToC = new HashMap<>();
private static Map<Format, Codec> videoCodecsForF = new HashMap<>();
private static Map<Format, Codec> audioCodecsForF = new HashMap<>();
private static Set<Codec> supportedDecoders = new HashSet<>();
private static Map<String, Class<? extends Filter>> knownFilters = new HashMap<>();
static {
extensionToF.put("mp3", Format.MPEG_AUDIO);
extensionToF.put("mp2", Format.MPEG_AUDIO);
extensionToF.put("mp1", Format.MPEG_AUDIO);
extensionToF.put("mpg", Format.MPEG_PS);
extensionToF.put("mpeg", Format.MPEG_PS);
extensionToF.put("m2p", Format.MPEG_PS);
extensionToF.put("ps", Format.MPEG_PS);
extensionToF.put("vob", Format.MPEG_PS);
extensionToF.put("evo", Format.MPEG_PS);
extensionToF.put("mod", Format.MPEG_PS);
extensionToF.put("tod", Format.MPEG_PS);
extensionToF.put("ts", Format.MPEG_TS);
extensionToF.put("m2t", Format.MPEG_TS);
extensionToF.put("mp4", Format.MOV);
extensionToF.put("m4a", Format.MOV);
extensionToF.put("m4v", Format.MOV);
extensionToF.put("mov", Format.MOV);
extensionToF.put("3gp", Format.MOV);
extensionToF.put("mkv", Format.MKV);
extensionToF.put("webm", Format.MKV);
extensionToF.put("264", Format.H264);
extensionToF.put("jsv", Format.H264);
extensionToF.put("h264", Format.H264);
extensionToF.put("raw", Format.RAW);
extensionToF.put("", Format.RAW);
extensionToF.put("flv", Format.FLV);
extensionToF.put("avi", Format.AVI);
extensionToF.put("jpg", Format.IMG);
extensionToF.put("jpeg", Format.IMG);
extensionToF.put("png", Format.IMG);
extensionToF.put("mjp", Format.MJPEG);
extensionToF.put("ivf", Format.IVF);
extensionToF.put("y4m", Format.Y4M);
extensionToF.put("wav", Format.WAV);
extensionToC.put("mpg", Codec.MPEG2);
extensionToC.put("mpeg", Codec.MPEG2);
extensionToC.put("m2p", Codec.MPEG2);
extensionToC.put("ps", Codec.MPEG2);
extensionToC.put("vob", Codec.MPEG2);
extensionToC.put("evo", Codec.MPEG2);
extensionToC.put("mod", Codec.MPEG2);
extensionToC.put("tod", Codec.MPEG2);
extensionToC.put("ts", Codec.MPEG2);
extensionToC.put("m2t", Codec.MPEG2);
extensionToC.put("m4a", Codec.AAC);
extensionToC.put("mkv", Codec.H264);
extensionToC.put("webm", Codec.VP8);
extensionToC.put("264", Codec.H264);
extensionToC.put("raw", Codec.RAW);
extensionToC.put("jpg", Codec.JPEG);
extensionToC.put("jpeg", Codec.JPEG);
extensionToC.put("png", Codec.PNG);
extensionToC.put("mjp", Codec.JPEG);
extensionToC.put("y4m", Codec.RAW);
videoCodecsForF.put(Format.MPEG_PS, Codec.MPEG2);
audioCodecsForF.put(Format.MPEG_PS, Codec.MP2);
videoCodecsForF.put(Format.MOV, Codec.H264);
audioCodecsForF.put(Format.MOV, Codec.AAC);
videoCodecsForF.put(Format.MKV, Codec.VP8);
audioCodecsForF.put(Format.MKV, Codec.VORBIS);
audioCodecsForF.put(Format.WAV, Codec.PCM);
videoCodecsForF.put(Format.H264, Codec.H264);
videoCodecsForF.put(Format.RAW, Codec.RAW);
videoCodecsForF.put(Format.FLV, Codec.H264);
videoCodecsForF.put(Format.AVI, Codec.MPEG4);
videoCodecsForF.put(Format.IMG, Codec.PNG);
videoCodecsForF.put(Format.MJPEG, Codec.JPEG);
videoCodecsForF.put(Format.IVF, Codec.VP8);
videoCodecsForF.put(Format.Y4M, Codec.RAW);
supportedDecoders.add(Codec.AAC);
supportedDecoders.add(Codec.H264);
supportedDecoders.add(Codec.JPEG);
supportedDecoders.add(Codec.MPEG2);
supportedDecoders.add(Codec.PCM);
supportedDecoders.add(Codec.PNG);
supportedDecoders.add(Codec.MPEG4);
supportedDecoders.add(Codec.PRORES);
supportedDecoders.add(Codec.RAW);
supportedDecoders.add(Codec.VP8);
supportedDecoders.add(Codec.MP3);
supportedDecoders.add(Codec.MP2);
supportedDecoders.add(Codec.MP1);
knownFilters.put("scale", ScaleFilter.class);
}
public static void main(String[] args) throws Exception {
Logger.addSink(new OutLogSink(System.out, new OutLogSink.SimpleFormat("#message"), LogLevel.INFO));
MainUtils.Cmd cmd = MainUtils.parseArguments(args, ALL_FLAGS);
Transcoder.TranscoderBuilder builder = Transcoder.newTranscoder();
List<Source> sources = new ArrayList<>();
List<Tuple._3<Integer, Integer, Codec>> inputCodecsVideo = new ArrayList<>();
List<Tuple._3<Integer, Integer, Codec>> inputCodecsAudio = new ArrayList<>();
for (int index = 0; index < cmd.argsLength(); index++) {
if (cmd.getBooleanFlagI(index, FLAG_INPUT)) {
Format inputFormat;
Tuple._3<Integer, Integer, Codec> inputCodecVideo = null, inputCodecAudio = null;
String input = cmd.getArg(index);
String inputFormatRaw = cmd.getStringFlagI(index, FLAG_FORMAT);
if (inputFormatRaw == null) {
inputFormat = getFormatFromExtension(input);
if (inputFormat != Format.IMG) {
Format detectFormat = JCodecUtil.detectFormat(new File(input));
if (detectFormat != null)
inputFormat = detectFormat;
}
} else {
inputFormat = Format.valueOf(inputFormatRaw.toUpperCase());
}
if (inputFormat == null) {
Logger.error("Input format could not be detected");
return;
}
Logger.info(String.format("Input stream %d: %s", index, String.valueOf(inputFormat)));
int videoTrackNo = -1;
String inputCodecVideoRaw = cmd.getStringFlagI(index, FLAG_VIDEO_CODEC);
if (inputCodecVideoRaw == null) {
if (inputFormat == Format.IMG) {
inputCodecVideo = Tuple.triple(Integer.valueOf(0), Integer.valueOf(0), getCodecFromExtension(input));
} else if (inputFormat.isVideo()) {
inputCodecVideo = selectSuitableTrack(input, inputFormat, TrackType.VIDEO);
}
} else {
inputCodecVideo = Tuple.triple(Integer.valueOf(0), Integer.valueOf(0), Codec.valueOf(inputCodecVideoRaw.toUpperCase()));
}
if (inputCodecVideo != null)
if (inputFormat == Format.MPEG_TS) {
Logger.info(String.format("Video codec: %s[pid=%d,stream=%d]", String.valueOf(inputCodecVideo.v2), inputCodecVideo.v0, inputCodecVideo.v1));
} else {
Logger.info(String.format("Video codec: %s", String.valueOf(inputCodecVideo.v2)));
}
String inputCodecAudioRaw = cmd.getStringFlagI(index, FLAG_AUDIO_CODEC);
if (inputCodecAudioRaw == null) {
if (inputFormat.isAudio())
inputCodecAudio = selectSuitableTrack(input, inputFormat, TrackType.AUDIO);
} else {
inputCodecAudio = Tuple.triple(Integer.valueOf(0), Integer.valueOf(0), Codec.valueOf(inputCodecAudioRaw.toUpperCase()));
}
if (inputCodecAudio != null)
if (inputFormat == Format.MPEG_TS) {
Logger.info(String.format("Audio codec: %s[pid=%d,stream=%d]", String.valueOf(inputCodecAudio.v2), inputCodecAudio.v0, inputCodecAudio.v1));
} else {
Logger.info(String.format("Audio codec: %s", String.valueOf(inputCodecAudio.v2)));
}
Source source = new SourceImpl(input, inputFormat, inputCodecVideo, inputCodecAudio);
Integer downscale = cmd.getIntegerFlagID(index, FLAG_DOWNSCALE, Integer.valueOf(1));
if (downscale != null && 1 << MathUtil.log2(downscale.intValue()) != downscale) {
Logger.error("Only values [2, 4, 8] are supported for " + String.valueOf(FLAG_DOWNSCALE) + ", the option will have no effect.");
} else {
source.setOption(Options.DOWNSCALE, downscale);
}
source.setOption(Options.PROFILE, cmd.getStringFlagI(index, FLAG_PROFILE));
source.setOption(Options.INTERLACED, cmd.getBooleanFlagID(index, FLAG_INTERLACED, Boolean.valueOf(false)));
sources.add(source);
inputCodecsVideo.add(inputCodecVideo);
inputCodecsAudio.add(inputCodecAudio);
builder.addSource(source);
builder.setSeekFrames(sources.size() - 1, cmd.getIntegerFlagID(index, FLAG_SEEK_FRAMES, Integer.valueOf(0)).intValue())
.setMaxFrames(sources.size() - 1, cmd.getIntegerFlagID(index, FLAG_MAX_FRAMES, Integer.valueOf(Integer.MAX_VALUE)).intValue());
}
}
if (sources.isEmpty()) {
MainUtils.printHelpArgs(ALL_FLAGS, new String[] { "input", "output" });
return;
}
List<Sink> sinks = new ArrayList<>();
for (int i = 0; i < cmd.argsLength(); i++) {
if (!cmd.getBooleanFlagI(i, FLAG_INPUT)) {
Format outputFormat;
String output = cmd.getArg(i);
String outputFormatRaw = cmd.getStringFlagI(i, FLAG_FORMAT);
if (outputFormatRaw == null) {
outputFormat = getFormatFromExtension(output);
} else {
outputFormat = Format.valueOf(outputFormatRaw.toUpperCase());
}
String outputCodecVideoRaw = cmd.getStringFlagI(i, FLAG_VIDEO_CODEC);
Codec outputCodecVideo = null;
boolean videoCopy = false;
if (outputCodecVideoRaw == null) {
outputCodecVideo = getCodecFromExtension(output);
if (outputCodecVideo == null)
outputCodecVideo = getFirstVideoCodecForFormat(outputFormat);
} else if ("copy".equalsIgnoreCase(outputCodecVideoRaw)) {
videoCopy = true;
} else if ("none".equalsIgnoreCase(outputCodecVideoRaw)) {
outputCodecVideo = null;
} else {
outputCodecVideo = Codec.valueOf(outputCodecVideoRaw.toUpperCase());
}
String outputCodecAudioRaw = cmd.getStringFlagI(i, FLAG_AUDIO_CODEC);
Codec outputCodecAudio = null;
boolean audioCopy = false;
if (outputCodecAudioRaw == null) {
if (outputFormat.isAudio())
outputCodecAudio = getFirstAudioCodecForFormat(outputFormat);
} else if ("copy".equalsIgnoreCase(outputCodecAudioRaw)) {
audioCopy = true;
} else if ("none".equalsIgnoreCase(outputCodecVideoRaw)) {
outputCodecAudio = null;
} else {
outputCodecAudio = Codec.valueOf(outputCodecAudioRaw.toUpperCase());
}
int audioMap = cmd.getIntegerFlagID(i, FLAG_MAP_AUDIO, Integer.valueOf(0));
if (audioMap > sources.size())
Logger.error("Can not map audio from source " + audioMap + ", " +
sources.size() + " sources specified.");
int videoMap = cmd.getIntegerFlagID(i, FLAG_MAP_VIDEO, Integer.valueOf(0));
if (videoMap > sources.size())
Logger.error("Can not map video from source " + videoMap + ", " +
sources.size() + " sources specified.");
if (videoCopy) {
Tuple._3<Integer, Integer, Codec> inputCodecVideo = inputCodecsVideo.get(videoMap);
outputCodecVideo = (inputCodecVideo != null) ? (Codec)inputCodecVideo.v2 : null;
}
if (audioCopy) {
Tuple._3<Integer, Integer, Codec> inputCodecAudio = inputCodecsAudio.get(audioMap);
outputCodecAudio = (inputCodecAudio != null) ? (Codec)inputCodecAudio.v2 : null;
}
Sink sink = new SinkImpl(output, outputFormat, outputCodecVideo, outputCodecAudio);
sinks.add(sink);
builder.addSink(sink);
builder.setAudioMapping(audioMap, sinks.size() - 1, audioCopy);
builder.setVideoMapping(videoMap, sinks.size() - 1, videoCopy);
if (cmd.getBooleanFlagI(i, FLAG_DUMPMV)) {
builder.addFilter(sinks.size() - 1, new DumpMvFilter(false));
} else if (cmd.getBooleanFlagI(i, FLAG_DUMPMVJS)) {
builder.addFilter(sinks.size() - 1, new DumpMvFilter(true));
}
String vf = cmd.getStringFlagI(i, FLAG_VIDEO_FILTER);
if (vf != null)
addVideoFilters(vf, builder, sinks.size() - 1);
}
}
if (sources.isEmpty() || sinks.isEmpty()) {
MainUtils.printHelpArgs(ALL_FLAGS, new String[] { "input", "output" });
return;
}
Transcoder transcoder = builder.create();
transcoder.transcode();
}
private static void addVideoFilters(String vf, Transcoder.TranscoderBuilder builder, int sinkIndex) {
if (vf == null)
return;
for (String filter : vf.split(",")) {
String[] parts = filter.split("=");
String filterName = parts[0];
Class<? extends Filter> filterClass = knownFilters.get(filterName);
if (filterClass == null) {
Logger.error("Unknown filter: " + filterName);
throw new RuntimeException("Unknown filter: " + filterName);
}
if (parts.length > 1) {
String filterArgs = parts[1];
String[] split = filterArgs.split(":");
Integer[] params = new Integer[split.length];
for (int i = 0; i < split.length; i++)
params[i] = Integer.parseInt(split[i]);
try {
Filter f = Platform.newInstance(filterClass, params);
builder.addFilter(sinkIndex, f);
} catch (Exception e) {
String message = "The filter " + filterName + " doesn't take " + split.length + " arguments.";
Logger.error(message);
throw new RuntimeException(message);
}
}
}
}
private static Codec getFirstAudioCodecForFormat(Format inputFormat) {
return audioCodecsForF.get(inputFormat);
}
private static Codec getFirstVideoCodecForFormat(Format inputFormat) {
return videoCodecsForF.get(inputFormat);
}
private static Codec detectVideoDecoder(DemuxerTrack track) throws IOException {
DemuxerTrackMeta meta = track.getMeta();
if (meta != null) {
Codec codec = meta.getCodec();
if (codec != null)
return codec;
}
Packet packet = track.nextFrame();
if (packet == null)
return null;
return JCodecUtil.detectDecoder(packet.getData());
}
private static Tuple._3<Integer, Integer, Codec> selectSuitableTrack(String input, Format format, TrackType targetType) throws IOException {
Tuple._2<Integer, Demuxer> demuxerPid;
if (format == Format.MPEG_TS) {
demuxerPid = JCodecUtil.createM2TSDemuxer(new File(input), targetType);
} else {
demuxerPid = Tuple.pair(Integer.valueOf(0), JCodecUtil.createDemuxer(format, new File(input)));
}
if (demuxerPid == null || demuxerPid.v1 == null)
return null;
int trackNo = 0;
List<? extends DemuxerTrack> tracks = (targetType == TrackType.VIDEO) ? ((Demuxer)demuxerPid.v1).getVideoTracks() : (
(Demuxer)demuxerPid.v1).getAudioTracks();
for (DemuxerTrack demuxerTrack : tracks) {
Codec codec = detectVideoDecoder(demuxerTrack);
if (supportedDecoders.contains(codec))
return Tuple.triple((Integer)demuxerPid.v0, Integer.valueOf(trackNo), codec);
trackNo++;
}
return null;
}
private static Format getFormatFromExtension(String output) {
String extension = output.replaceFirst(".*\\.([^\\.]+$)", "$1");
return extensionToF.get(extension);
}
private static Codec getCodecFromExtension(String output) {
String extension = output.replaceFirst(".*\\.([^\\.]+$)", "$1");
return extensionToC.get(extension);
}
}

View file

@ -0,0 +1,413 @@
package org.jcodec.api.transcode;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.jcodec.api.transcode.filters.ColorTransformFilter;
import org.jcodec.common.AudioCodecMeta;
import org.jcodec.common.IntArrayList;
import org.jcodec.common.VideoCodecMeta;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Packet;
public class Transcoder {
static final int REORDER_BUFFER_SIZE = 7;
private Source[] sources;
private Sink[] sinks;
private List<Filter>[] extraFilters;
private int[] seekFrames;
private int[] maxFrames;
private Mapping[] videoMappings;
private Mapping[] audioMappings;
private Transcoder(Source[] source, Sink[] sink, Mapping[] videoMappings, Mapping[] audioMappings, List<Filter>[] extraFilters, int[] seekFrames, int[] maxFrames) {
this.extraFilters = extraFilters;
this.videoMappings = videoMappings;
this.audioMappings = audioMappings;
this.seekFrames = seekFrames;
this.maxFrames = maxFrames;
this.sources = source;
this.sinks = sink;
}
private static class Mapping {
private int source;
private boolean copy;
public Mapping(int source, boolean copy) {
this.source = source;
this.copy = copy;
}
}
private static class Stream {
private static final double AUDIO_LEADING_TIME = 0.2D;
private LinkedList<VideoFrameWithPacket> videoQueue;
private LinkedList<AudioFrameWithPacket> audioQueue;
private List<Filter> filters;
private List<Filter> extraFilters;
private Sink sink;
private boolean videoCopy;
private boolean audioCopy;
private PixelStore pixelStore;
private VideoCodecMeta videoCodecMeta;
private AudioCodecMeta audioCodecMeta;
private static final int REORDER_LENGTH = 5;
public Stream(Sink sink, boolean videoCopy, boolean audioCopy, List<Filter> extraFilters, PixelStore pixelStore) {
this.sink = sink;
this.videoCopy = videoCopy;
this.audioCopy = audioCopy;
this.extraFilters = extraFilters;
this.pixelStore = pixelStore;
this.videoQueue = new LinkedList<>();
this.audioQueue = new LinkedList<>();
}
private List<Filter> initColorTransform(ColorSpace sourceColor, List<Filter> extraFilters, Sink sink) {
List<Filter> filters = new ArrayList<>();
for (Filter filter : extraFilters) {
ColorSpace colorSpace = filter.getInputColor();
if (!sourceColor.matches(colorSpace))
filters.add(new ColorTransformFilter(colorSpace));
filters.add(filter);
if (filter.getOutputColor() != ColorSpace.SAME)
sourceColor = filter.getOutputColor();
}
ColorSpace inputColor = sink.getInputColor();
if (inputColor != null && inputColor != sourceColor)
filters.add(new ColorTransformFilter(inputColor));
return filters;
}
public void tryFlushQueues() throws IOException {
if (this.videoQueue.size() <= 0)
return;
if (this.videoCopy && this.videoQueue.size() < 5)
return;
if (!hasLeadingAudio())
return;
VideoFrameWithPacket firstVideoFrame = this.videoQueue.get(0);
if (this.videoCopy)
for (VideoFrameWithPacket videoFrame : this.videoQueue) {
if (videoFrame.getPacket().getFrameNo() < firstVideoFrame.getPacket().getFrameNo())
firstVideoFrame = videoFrame;
}
int aqSize = this.audioQueue.size();
for (int af = 0; af < aqSize; af++) {
AudioFrameWithPacket audioFrame = this.audioQueue.get(0);
if (audioFrame.getPacket().getPtsD() >= firstVideoFrame.getPacket().getPtsD() + 0.2D)
break;
this.audioQueue.remove(0);
if (this.audioCopy && this.sink instanceof PacketSink) {
((PacketSink)this.sink).outputAudioPacket(audioFrame.getPacket(), this.audioCodecMeta);
} else {
this.sink.outputAudioFrame(audioFrame);
}
}
this.videoQueue.remove(firstVideoFrame);
if (this.videoCopy && this.sink instanceof PacketSink) {
((PacketSink)this.sink).outputVideoPacket(firstVideoFrame.getPacket(), this.videoCodecMeta);
} else {
PixelStore.LoanerPicture frame = filterFrame(firstVideoFrame);
this.sink.outputVideoFrame(new VideoFrameWithPacket(firstVideoFrame.getPacket(), frame));
this.pixelStore.putBack(frame);
}
}
private PixelStore.LoanerPicture filterFrame(VideoFrameWithPacket firstVideoFrame) {
PixelStore.LoanerPicture frame = firstVideoFrame.getFrame();
for (Filter filter : this.filters) {
PixelStore.LoanerPicture old = frame;
frame = filter.filter(frame.getPicture(), this.pixelStore);
if (frame == null) {
frame = old;
continue;
}
this.pixelStore.putBack(old);
}
return frame;
}
public void finalFlushQueues() throws IOException {
VideoFrameWithPacket lastVideoFrame = null;
for (VideoFrameWithPacket videoFrame : this.videoQueue) {
if (lastVideoFrame == null || videoFrame.getPacket().getPtsD() >= lastVideoFrame.getPacket().getPtsD())
lastVideoFrame = videoFrame;
}
if (lastVideoFrame != null) {
for (AudioFrameWithPacket audioFrame : this.audioQueue) {
if (audioFrame.getPacket().getPtsD() > lastVideoFrame.getPacket().getPtsD())
break;
if (this.audioCopy && this.sink instanceof PacketSink) {
((PacketSink)this.sink).outputAudioPacket(audioFrame.getPacket(), this.audioCodecMeta);
continue;
}
this.sink.outputAudioFrame(audioFrame);
}
for (VideoFrameWithPacket videoFrame : this.videoQueue) {
if (videoFrame != null) {
if (this.videoCopy && this.sink instanceof PacketSink) {
((PacketSink)this.sink).outputVideoPacket(videoFrame.getPacket(), this.videoCodecMeta);
continue;
}
PixelStore.LoanerPicture frame = filterFrame(videoFrame);
this.sink.outputVideoFrame(new VideoFrameWithPacket(videoFrame.getPacket(), frame));
this.pixelStore.putBack(frame);
}
}
} else {
for (AudioFrameWithPacket audioFrame : this.audioQueue) {
if (this.audioCopy && this.sink instanceof PacketSink) {
((PacketSink)this.sink).outputAudioPacket(audioFrame.getPacket(), this.audioCodecMeta);
continue;
}
this.sink.outputAudioFrame(audioFrame);
}
}
}
public void addVideoPacket(VideoFrameWithPacket videoFrame, VideoCodecMeta meta) {
if (videoFrame.getFrame() != null)
this.pixelStore.retake(videoFrame.getFrame());
this.videoQueue.add(videoFrame);
this.videoCodecMeta = meta;
if (this.filters == null)
this.filters = initColorTransform(this.videoCodecMeta.getColor(), this.extraFilters, this.sink);
}
public void addAudioPacket(AudioFrameWithPacket videoFrame, AudioCodecMeta meta) {
this.audioQueue.add(videoFrame);
this.audioCodecMeta = meta;
}
public boolean needsVideoFrame() {
if (this.videoQueue.size() <= 0)
return true;
if (this.videoCopy && this.videoQueue.size() < 5)
return true;
return false;
}
public boolean hasLeadingAudio() {
VideoFrameWithPacket firstVideoFrame = this.videoQueue.get(0);
for (AudioFrameWithPacket audioFrame : this.audioQueue) {
if (audioFrame.getPacket().getPtsD() >= firstVideoFrame.getPacket().getPtsD() + 0.2D)
return true;
}
return false;
}
}
public void transcode() throws IOException {
PixelStore pixelStore = new PixelStoreImpl();
List<Stream>[] videoStreams = new List[this.sources.length];
List<Stream>[] audioStreams = new List[this.sources.length];
boolean[] decodeVideo = new boolean[this.sources.length];
boolean[] decodeAudio = new boolean[this.sources.length];
boolean[] finishedVideo = new boolean[this.sources.length];
boolean[] finishedAudio = new boolean[this.sources.length];
Stream[] allStreams = new Stream[this.sinks.length];
int[] videoFramesRead = new int[this.sources.length];
for (int k = 0; k < this.sources.length; k++) {
videoStreams[k] = new ArrayList<>();
audioStreams[k] = new ArrayList<>();
}
for (int j = 0; j < this.sinks.length; j++)
this.sinks[j].init();
for (int i = 0; i < this.sources.length; i++) {
this.sources[i].init(pixelStore);
this.sources[i].seekFrames(this.seekFrames[i]);
}
for (int s = 0; s < this.sinks.length; s++) {
Stream stream = new Stream(this.sinks[s], (this.videoMappings[s]).copy, (this.audioMappings[s]).copy, this.extraFilters[s], pixelStore);
allStreams[s] = stream;
if (this.sources[(this.videoMappings[s]).source].isVideo()) {
videoStreams[(this.videoMappings[s]).source].add(stream);
if (!(this.videoMappings[s]).copy)
decodeVideo[(this.videoMappings[s]).source] = true;
} else {
finishedVideo[(this.videoMappings[s]).source] = true;
}
if (this.sources[(this.audioMappings[s]).source].isAudio()) {
audioStreams[(this.audioMappings[s]).source].add(stream);
if (!(this.audioMappings[s]).copy)
decodeAudio[(this.audioMappings[s]).source] = true;
} else {
finishedAudio[(this.audioMappings[s]).source] = true;
}
}
try {
boolean allFinished;
do {
for (int i1 = 0; i1 < this.sources.length; i1++) {
Source source = this.sources[i1];
boolean needsVideoFrame = !finishedVideo[i1];
for (Stream stream : videoStreams[i1])
needsVideoFrame &= (stream.needsVideoFrame() || stream.hasLeadingAudio() || finishedAudio[i1]) ? true : false;
if (needsVideoFrame) {
VideoFrameWithPacket nextVideoFrame;
if (videoFramesRead[i1] >= this.maxFrames[i1]) {
nextVideoFrame = null;
finishedVideo[i1] = true;
} else if (decodeVideo[i1] || !(source instanceof PacketSource)) {
nextVideoFrame = source.getNextVideoFrame();
if (nextVideoFrame == null) {
finishedVideo[i1] = true;
} else {
videoFramesRead[i1] = videoFramesRead[i1] + 1;
printLegend((int)nextVideoFrame.getPacket().getFrameNo(), 0,
nextVideoFrame.getPacket());
}
} else {
Packet packet = ((PacketSource)source).inputVideoPacket();
if (packet == null) {
finishedVideo[i1] = true;
} else {
videoFramesRead[i1] = videoFramesRead[i1] + 1;
}
nextVideoFrame = new VideoFrameWithPacket(packet, null);
}
if (finishedVideo[i1]) {
for (Stream stream : videoStreams[i1]) {
for (int ss = 0; ss < audioStreams.length; ss++)
audioStreams[ss].remove(stream);
}
videoStreams[i1].clear();
}
if (nextVideoFrame != null) {
for (Stream stream : videoStreams[i1])
stream.addVideoPacket(nextVideoFrame, source.getVideoCodecMeta());
if (nextVideoFrame.getFrame() != null)
pixelStore.putBack(nextVideoFrame.getFrame());
}
}
if (!audioStreams[i1].isEmpty()) {
AudioFrameWithPacket nextAudioFrame;
if (decodeAudio[i1] || !(source instanceof PacketSource)) {
nextAudioFrame = source.getNextAudioFrame();
if (nextAudioFrame == null)
finishedAudio[i1] = true;
} else {
Packet packet = ((PacketSource)source).inputAudioPacket();
if (packet == null) {
finishedAudio[i1] = true;
nextAudioFrame = null;
} else {
nextAudioFrame = new AudioFrameWithPacket(null, packet);
}
}
if (nextAudioFrame != null)
for (Stream stream : audioStreams[i1])
stream.addAudioPacket(nextAudioFrame, source.getAudioCodecMeta());
} else {
finishedAudio[i1] = true;
}
}
for (int i2 = 0; i2 < allStreams.length; i2++)
allStreams[i2].tryFlushQueues();
allFinished = true;
for (int i3 = 0; i3 < this.sources.length; i3++)
allFinished &= finishedVideo[i3] & finishedAudio[i3];
} while (!allFinished);
for (int n = 0; n < allStreams.length; n++)
allStreams[n].finalFlushQueues();
} finally {
for (int n = 0; n < this.sources.length; n++)
this.sources[0].finish();
for (int m = 0; m < this.sinks.length; m++)
this.sinks[m].finish();
}
}
private void printLegend(int frameNo, int maxFrames, Packet inVideoPacket) {
if (frameNo % 100 == 0)
System.out.print(String.format("[%6d]\r", frameNo));
}
public static class TranscoderBuilder {
private List<Source> source = new ArrayList<>();
private List<Sink> sink = new ArrayList<>();
private List<List<Filter>> filters = new ArrayList<>();
private IntArrayList seekFrames = new IntArrayList(20);
private IntArrayList maxFrames = new IntArrayList(20);
private List<Transcoder.Mapping> videoMappings = new ArrayList<>();
private List<Transcoder.Mapping> audioMappings = new ArrayList<>();
public TranscoderBuilder addFilter(int sink, Filter filter) {
this.filters.get(sink).add(filter);
return this;
}
public TranscoderBuilder setSeekFrames(int source, int seekFrames) {
this.seekFrames.set(source, seekFrames);
return this;
}
public TranscoderBuilder setMaxFrames(int source, int maxFrames) {
this.maxFrames.set(source, maxFrames);
return this;
}
public TranscoderBuilder addSource(Source source) {
this.source.add(source);
this.seekFrames.add(0);
this.maxFrames.add(Integer.MAX_VALUE);
return this;
}
public TranscoderBuilder addSink(Sink sink) {
this.sink.add(sink);
this.videoMappings.add(new Transcoder.Mapping(0, false));
this.audioMappings.add(new Transcoder.Mapping(0, false));
this.filters.add(new ArrayList<>());
return this;
}
public TranscoderBuilder setVideoMapping(int src, int sink, boolean copy) {
this.videoMappings.set(sink, new Transcoder.Mapping(src, copy));
return this;
}
public TranscoderBuilder setAudioMapping(int src, int sink, boolean copy) {
this.audioMappings.set(sink, new Transcoder.Mapping(src, copy));
return this;
}
public Transcoder create() {
return new Transcoder(this.source.<Source>toArray(new Source[0]), this.sink.<Sink>toArray(new Sink[0]),
this.videoMappings.<Transcoder.Mapping>toArray(new Transcoder.Mapping[0]), this.audioMappings.<Transcoder.Mapping>toArray(new Transcoder.Mapping[0]),
this.filters.<List<Filter>>toArray(new List[0]), this.seekFrames.toArray(), this.maxFrames.toArray());
}
}
public static TranscoderBuilder newTranscoder() {
return new TranscoderBuilder();
}
}

View file

@ -0,0 +1,30 @@
package org.jcodec.api.transcode;
import org.jcodec.common.model.Packet;
public class VideoFrameWithPacket implements Comparable<VideoFrameWithPacket> {
private Packet packet;
private PixelStore.LoanerPicture frame;
public VideoFrameWithPacket(Packet inFrame, PixelStore.LoanerPicture dec2) {
this.packet = inFrame;
this.frame = dec2;
}
public int compareTo(VideoFrameWithPacket arg) {
if (arg == null)
return -1;
long pts1 = this.packet.getPts();
long pts2 = arg.packet.getPts();
return (pts1 > pts2) ? 1 : ((pts1 == pts2) ? 0 : -1);
}
public Packet getPacket() {
return this.packet;
}
public PixelStore.LoanerPicture getFrame() {
return this.frame;
}
}

View file

@ -0,0 +1,38 @@
package org.jcodec.api.transcode.filters;
import org.jcodec.api.transcode.Filter;
import org.jcodec.api.transcode.PixelStore;
import org.jcodec.common.logging.Logger;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Picture;
import org.jcodec.scale.ColorUtil;
import org.jcodec.scale.Transform;
public class ColorTransformFilter implements Filter {
private Transform transform;
private ColorSpace outputColor;
public ColorTransformFilter(ColorSpace outputColor) {
this.outputColor = outputColor;
}
public PixelStore.LoanerPicture filter(Picture picture, PixelStore store) {
if (this.transform == null) {
this.transform = ColorUtil.getTransform(picture.getColor(), this.outputColor);
Logger.debug("Creating transform: " + String.valueOf(this.transform));
}
PixelStore.LoanerPicture outFrame = store.getPicture(picture.getWidth(), picture.getHeight(), this.outputColor);
outFrame.getPicture().setCrop(picture.getCrop());
this.transform.transform(picture, outFrame.getPicture());
return outFrame;
}
public ColorSpace getInputColor() {
return ColorSpace.ANY_PLANAR;
}
public ColorSpace getOutputColor() {
return this.outputColor;
}
}

View file

@ -0,0 +1,20 @@
package org.jcodec.api.transcode.filters;
import org.jcodec.api.transcode.Filter;
import org.jcodec.api.transcode.PixelStore;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Picture;
public class CropFilter implements Filter {
public PixelStore.LoanerPicture filter(Picture picture, PixelStore store) {
return null;
}
public ColorSpace getInputColor() {
return null;
}
public ColorSpace getOutputColor() {
return null;
}
}

View file

@ -0,0 +1,86 @@
package org.jcodec.api.transcode.filters;
import org.jcodec.api.transcode.Filter;
import org.jcodec.api.transcode.PixelStore;
import org.jcodec.codecs.h264.H264Utils;
import org.jcodec.codecs.h264.io.model.Frame;
import org.jcodec.codecs.h264.io.model.SliceType;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Picture;
public class DumpMvFilter implements Filter {
private boolean js;
public DumpMvFilter(boolean js) {
this.js = js;
}
public PixelStore.LoanerPicture filter(Picture picture, PixelStore pixelStore) {
Frame dec = (Frame)picture;
if (!this.js) {
dumpMvTxt(dec);
} else {
dumpMvJs(dec);
}
return null;
}
private void dumpMvTxt(Frame dec) {
System.err.println("FRAME ================================================================");
if (dec.getFrameType() == SliceType.I)
return;
H264Utils.MvList2D mvs = dec.getMvs();
for (int i = 0; i < 2; i++) {
System.err.println(((i == 0) ? "BCK" : "FWD") + " ===========================================================================");
for (int blkY = 0; blkY < mvs.getHeight(); blkY++) {
StringBuilder line0 = new StringBuilder();
StringBuilder line1 = new StringBuilder();
StringBuilder line2 = new StringBuilder();
StringBuilder line3 = new StringBuilder();
line0.append("+");
line1.append("|");
line2.append("|");
line3.append("|");
for (int blkX = 0; blkX < mvs.getWidth(); blkX++) {
line0.append("------+");
line1.append(String.format("%6d|", H264Utils.Mv.mvX(mvs.getMv(blkX, blkY, i))));
line2.append(String.format("%6d|", H264Utils.Mv.mvY(mvs.getMv(blkX, blkY, i))));
line3.append(String.format(" %2d|", H264Utils.Mv.mvRef(mvs.getMv(blkX, blkY, i))));
}
System.err.println(line0.toString());
System.err.println(line1.toString());
System.err.println(line2.toString());
System.err.println(line3.toString());
}
if (dec.getFrameType() != SliceType.B)
break;
}
}
private void dumpMvJs(Frame dec) {
System.err.println("{");
if (dec.getFrameType() == SliceType.I)
return;
H264Utils.MvList2D mvs = dec.getMvs();
for (int i = 0; i < 2; i++) {
System.err.println(((i == 0) ? "backRef" : "forwardRef") + ": [");
for (int blkY = 0; blkY < mvs.getHeight(); blkY++) {
for (int blkX = 0; blkX < mvs.getWidth(); blkX++)
System.err.println("{x: " + blkX + ", y: " + blkY + ", mx: " + H264Utils.Mv.mvX(mvs.getMv(blkX, blkY, i)) + ", my: " +
H264Utils.Mv.mvY(mvs.getMv(blkX, blkY, i)) + ", ridx:" + H264Utils.Mv.mvRef(mvs.getMv(blkX, blkY, i)) + "},");
}
System.err.println("],");
if (dec.getFrameType() != SliceType.B)
break;
}
System.err.println("}");
}
public ColorSpace getInputColor() {
return null;
}
public ColorSpace getOutputColor() {
return null;
}
}

View file

@ -0,0 +1,53 @@
package org.jcodec.api.transcode.filters;
import org.jcodec.api.transcode.Filter;
import org.jcodec.api.transcode.PixelStore;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Picture;
import org.jcodec.common.model.Size;
import org.jcodec.scale.BaseResampler;
import org.jcodec.scale.LanczosResampler;
public class ScaleFilter implements Filter {
private BaseResampler resampler;
private ColorSpace currentColor;
private Size currentSize;
private Size targetSize;
private int width;
private int height;
public ScaleFilter(int width, int height) {
this.width = width;
this.height = height;
}
public Size getTarget() {
return new Size(this.width, this.height);
}
public PixelStore.LoanerPicture filter(Picture picture, PixelStore store) {
Size pictureSize = picture.getSize();
if (this.resampler == null || this.currentColor != picture.getColor() || !pictureSize.equals(this.currentSize)) {
this.currentColor = picture.getColor();
this.currentSize = picture.getSize();
this.targetSize = new Size(this.width & this.currentColor.getWidthMask(), this.height & this.currentColor.getHeightMask());
this.resampler = new LanczosResampler(this.currentSize, this.targetSize);
}
PixelStore.LoanerPicture dest = store.getPicture(this.targetSize.getWidth(), this.targetSize.getHeight(), this.currentColor);
this.resampler.resample(picture, dest.getPicture());
return dest;
}
public ColorSpace getInputColor() {
return ColorSpace.ANY_PLANAR;
}
public ColorSpace getOutputColor() {
return ColorSpace.SAME;
}
}