package org.jcodec.samples.mashup;
import static org.jcodec.common.io.NIOUtils.readableChannel;
import static org.jcodec.common.io.NIOUtils.writableChannel;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.jcodec.codecs.h264.H264Utils;
import org.jcodec.codecs.h264.decode.SliceHeaderReader;
import org.jcodec.codecs.h264.io.model.NALUnit;
import org.jcodec.codecs.h264.io.model.NALUnitType;
import org.jcodec.codecs.h264.io.model.PictureParameterSet;
import org.jcodec.codecs.h264.io.model.SeqParameterSet;
import org.jcodec.codecs.h264.io.model.SliceHeader;
import org.jcodec.codecs.h264.io.write.SliceHeaderWriter;
import org.jcodec.common.Assert;
import org.jcodec.common.Codec;
import org.jcodec.common.DemuxerTrack;
import org.jcodec.common.DemuxerTrackMeta;
import org.jcodec.common.MuxerTrack;
import org.jcodec.common.io.BitReader;
import org.jcodec.common.io.BitWriter;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.model.Packet;
import org.jcodec.containers.mp4.Brand;
import org.jcodec.containers.mp4.MP4Packet;
import org.jcodec.containers.mp4.demuxer.MP4Demuxer;
import org.jcodec.containers.mp4.muxer.MP4Muxer;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* @author The JCodec project
*
*/
public class MovStitch2 {
public static void main(String[] args) throws Exception {
if (args.length < 3) {
System.out.println("Syntax: <in1.mov> <in2.mov> <out.mov>");
return;
}
File in1 = new File(args[0]);
File in2 = new File(args[1]);
File out = new File(args[2]);
changePPS(in1, in2, out);
}
public static void changePPS(File in1, File in2, File out) throws IOException {
MP4Muxer muxer = MP4Muxer.createMP4Muxer(writableChannel(out), Brand.MOV);
MP4Demuxer demuxer1 = new MP4Demuxer(readableChannel(in1));
DemuxerTrack vt1 = demuxer1.getVideoTrack();
MP4Demuxer demuxer2 = new MP4Demuxer(readableChannel(in2));
DemuxerTrack vt2 = demuxer2.getVideoTrack();
checkCompatible(vt1, vt2);
DemuxerTrackMeta meta1 = vt1.getMeta();
DemuxerTrackMeta meta2 = vt2.getMeta();
MuxerTrack outTrack = muxer.addVideoTrack(Codec.H264, meta1.getVideoCodecMeta());
for (int i = 0; i < meta1.getTotalFrames(); i++) {
outTrack.addFrame((MP4Packet) vt1.nextFrame());
}
List<ByteBuffer> spsList = new ArrayList<ByteBuffer>();
List<ByteBuffer> ppsList = new ArrayList<ByteBuffer>();
ByteBuffer codecPrivate2 = updateCodecPrivate(vt2.getMeta().getCodecPrivate(), spsList, ppsList);
SeqParameterSet sps = SeqParameterSet.read(spsList.get(0).duplicate());
PictureParameterSet pps = PictureParameterSet.read(ppsList.get(0).duplicate());
for (int i = 0; i < meta2.getTotalFrames(); i++) {
Packet packet = vt2.nextFrame();
ByteBuffer frm;
if(codecPrivate2 != null) {
frm = ByteBuffer.allocate(packet.getData().remaining() + 24 + codecPrivate2.remaining());
frm.put(codecPrivate2);
codecPrivate2 = null;
} else {
frm = ByteBuffer.allocate(packet.getData().remaining() + 24);
}
doFrame(packet.getData(), frm, sps, pps);
outTrack.addFrame(Packet.createPacketWithData(packet, frm));
}
muxer.finish();
}
private static void checkCompatible(DemuxerTrack vt1, DemuxerTrack vt2) {
DemuxerTrackMeta meta1 = vt1.getMeta();
DemuxerTrackMeta meta2 = vt2.getMeta();
Assert.assertTrue(meta1.getCodec() == Codec.H264);
Assert.assertTrue(meta2.getCodec() == Codec.H264);
Assert.assertEquals(meta1.getVideoCodecMeta().getSize().getWidth(),
meta2.getVideoCodecMeta().getSize().getWidth());
Assert.assertEquals(meta1.getVideoCodecMeta().getSize().getHeight(),
meta2.getVideoCodecMeta().getSize().getHeight());
}
public static void doFrame(ByteBuffer data, ByteBuffer dst, SeqParameterSet sps, PictureParameterSet pps)
throws IOException {
SliceHeaderWriter shw = new SliceHeaderWriter();
SliceHeaderReader shr = new SliceHeaderReader();
while (data.remaining() > 0) {
ByteBuffer nalUnit = H264Utils.nextNALUnit(data);
NALUnit nu = NALUnit.read(nalUnit);
if (nu.type == NALUnitType.IDR_SLICE || nu.type == NALUnitType.NON_IDR_SLICE) {
dst.getInt(1);
copyNU(shr, shw, nu, nalUnit, dst, sps, pps);
} else {
dst.putInt(1);
nu.write(dst);
NIOUtils.write(dst, nalUnit);
}
}
dst.flip();
}
public static void copyNU(SliceHeaderReader shr, SliceHeaderWriter shw, NALUnit nu, ByteBuffer is, ByteBuffer os,
SeqParameterSet sps, PictureParameterSet pps) {
BitReader reader = BitReader.createBitReader(is);
BitWriter writer = new BitWriter(os);
SliceHeader sh = shr.readPart1(reader);
shr.readPart2(sh, nu, sps, pps, reader);
sh.pic_parameter_set_id = 1;
nu.write(os);
shw.write(sh, nu.type == NALUnitType.IDR_SLICE, nu.nal_ref_idc, writer);
copyCABAC(writer, reader);
}
private static ByteBuffer updateCodecPrivate(ByteBuffer codecPrivate, List<ByteBuffer> sps, List<ByteBuffer> pps) {
H264Utils.wipePS(codecPrivate, null, sps, pps);
for (int i = 0; i < pps.size(); i++) {
pps.set(i, updatePps(pps.get(i)));
}
for (int i = 0; i < sps.size(); i++) {
sps.set(i, updateSps(sps.get(i)));
}
return H264Utils.saveCodecPrivate(sps, pps);
}
private static void copyCABAC(BitWriter w, BitReader r) {
long bp = r.curBit();
long rem = r.readNBit(8 - (int) bp);
Assert.assertEquals((1 << (8 - bp)) - 1, rem);
if (w.curBit() != 0)
w.writeNBit(0xff, 8 - w.curBit());
int b;
while ((b = r.readNBit(8)) != -1)
w.writeNBit(b, 8);
}
static ByteBuffer updateSps(ByteBuffer bb) {
SeqParameterSet sps = SeqParameterSet.read(bb);
sps.seq_parameter_set_id = 1;
ByteBuffer out = ByteBuffer.allocate(bb.capacity() + 10);
sps.write(out);
out.flip();
return out;
}
static ByteBuffer updatePps(ByteBuffer bb) {
PictureParameterSet pps = PictureParameterSet.read(bb);
Assert.assertTrue(pps.entropy_coding_mode_flag);
pps.seq_parameter_set_id = 1;
pps.pic_parameter_set_id = 1;
ByteBuffer out = ByteBuffer.allocate(bb.capacity() + 10);
pps.write(out);
out.flip();
return out;
}
}