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,133 @@
package org.jcodec.containers.flv;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.Map;
import org.jcodec.platform.Platform;
public class FLVMetadata {
private double duration;
private double width;
private double height;
private double framerate;
private String audiocodecid;
private double videokeyframe_frequency;
private String videodevice;
private double avclevel;
private double audiosamplerate;
private double audiochannels;
private String presetname;
private double videodatarate;
private double audioinputvolume;
private Date creationdate;
private String videocodecid;
private double avcprofile;
private String audiodevice;
private double audiodatarate;
public FLVMetadata(Map<String, Object> md) {
Field[] declaredFields = Platform.getDeclaredFields(getClass());
for (int i = 0; i < declaredFields.length; i++) {
Field field = declaredFields[i];
Object object = md.get(field.getName());
try {
if (object instanceof Double) {
field.setDouble(this, ((Double)object).doubleValue());
} else if (object instanceof Boolean) {
field.setBoolean(this, ((Boolean)object).booleanValue());
} else {
field.set(this, object);
}
} catch (Exception e) {}
}
}
public double getDuration() {
return this.duration;
}
public double getWidth() {
return this.width;
}
public double getHeight() {
return this.height;
}
public double getFramerate() {
return this.framerate;
}
public String getAudiocodecid() {
return this.audiocodecid;
}
public double getVideokeyframe_frequency() {
return this.videokeyframe_frequency;
}
public String getVideodevice() {
return this.videodevice;
}
public double getAvclevel() {
return this.avclevel;
}
public double getAudiosamplerate() {
return this.audiosamplerate;
}
public double getAudiochannels() {
return this.audiochannels;
}
public String getPresetname() {
return this.presetname;
}
public double getVideodatarate() {
return this.videodatarate;
}
public double getAudioinputvolume() {
return this.audioinputvolume;
}
public Date getCreationdate() {
return this.creationdate;
}
public String getVideocodecid() {
return this.videocodecid;
}
public double getAvcprofile() {
return this.avcprofile;
}
public String getAudiodevice() {
return this.audiodevice;
}
public double getAudiodatarate() {
return this.audiodatarate;
}
}

View file

@ -0,0 +1,348 @@
package org.jcodec.containers.flv;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.ReadableByteChannel;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.jcodec.common.AudioFormat;
import org.jcodec.common.Codec;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.common.logging.Logger;
import org.jcodec.common.tools.MathUtil;
import org.jcodec.platform.Platform;
public class FLVReader {
private static final int REPOSITION_BUFFER_READS = 10;
private static final int TAG_HEADER_SIZE = 15;
private static final int READ_BUFFER_SIZE = 1024;
private int frameNo;
private ByteBuffer readBuf;
private SeekableByteChannel ch;
private boolean eof;
private static boolean platformBigEndian = (ByteBuffer.allocate(0).order() == ByteOrder.BIG_ENDIAN);
public static Codec[] audioCodecMapping = new Codec[] {
Codec.PCM, Codec.ADPCM, Codec.MP3, Codec.PCM, Codec.NELLYMOSER, Codec.NELLYMOSER, Codec.NELLYMOSER, Codec.G711, Codec.G711, null,
Codec.AAC, Codec.SPEEX, Codec.MP3, null };
public static Codec[] videoCodecMapping = new Codec[] { null, null, Codec.SORENSON, Codec.FLASH_SCREEN_VIDEO, Codec.VP6, Codec.VP6, Codec.FLASH_SCREEN_V2, Codec.H264 };
public static int[] sampleRates = new int[] { 5500, 11000, 22000, 44100 };
public FLVReader(SeekableByteChannel ch) throws IOException {
this.ch = ch;
this.readBuf = ByteBuffer.allocate(1024);
this.readBuf.order(ByteOrder.BIG_ENDIAN);
initialRead(ch);
if (!readHeader(this.readBuf)) {
this.readBuf.position(0);
if (!repositionFile())
throw new RuntimeException("Invalid FLV file");
Logger.warn(String.format("Parsing a corrupt FLV file, first tag found at %d. %s", this.readBuf.position(),
(this.readBuf.position() == 0) ? "Did you forget the FLV 9-byte header?" : ""));
}
}
private void initialRead(ReadableByteChannel ch) throws IOException {
this.readBuf.clear();
if (ch.read(this.readBuf) == -1)
this.eof = true;
this.readBuf.flip();
}
public FLVTag readNextPacket() throws IOException {
if (this.eof)
return null;
FLVTag pkt = parsePacket(this.readBuf);
if (pkt == null && !this.eof) {
moveRemainderToTheStart(this.readBuf);
if (this.ch.read(this.readBuf) == -1) {
this.eof = true;
return null;
}
while (MathUtil.log2(this.readBuf.capacity()) <= 22) {
this.readBuf.flip();
pkt = parsePacket(this.readBuf);
if (pkt != null || this.readBuf.position() > 0)
break;
ByteBuffer newBuf = ByteBuffer.allocate(this.readBuf.capacity() << 2);
newBuf.put(this.readBuf);
this.readBuf = newBuf;
if (this.ch.read(this.readBuf) == -1) {
this.eof = true;
return null;
}
}
}
return pkt;
}
public FLVTag readPrevPacket() throws IOException {
int startOfLastPacket = this.readBuf.getInt();
this.readBuf.position(this.readBuf.position() - 4);
if (this.readBuf.position() > startOfLastPacket) {
this.readBuf.position(this.readBuf.position() - startOfLastPacket);
return parsePacket(this.readBuf);
}
long oldPos = this.ch.position() - (long)this.readBuf.remaining();
if (oldPos <= 9L)
return null;
long newPos = Math.max(0L, oldPos - (long)(this.readBuf.capacity() / 2));
this.ch.setPosition(newPos);
this.readBuf.clear();
this.ch.read(this.readBuf);
this.readBuf.flip();
this.readBuf.position((int)(oldPos - newPos));
return readPrevPacket();
}
private static void moveRemainderToTheStart(ByteBuffer readBuf) {
int rem = readBuf.remaining();
for (int i = 0; i < rem; i++)
readBuf.put(i, readBuf.get());
readBuf.clear();
readBuf.position(rem);
}
public FLVTag parsePacket(ByteBuffer readBuf) throws IOException {
long packetPos;
int prevPacketSize, packetType, timestamp, streamId;
ByteBuffer payload;
FLVTag.Type type;
FLVTag.TagHeader tagHeader;
while (true) {
if (readBuf.remaining() < 15)
return null;
int pos = readBuf.position();
packetPos = this.ch.position() - (long)readBuf.remaining();
prevPacketSize = readBuf.getInt();
packetType = readBuf.get() & 0xFF;
int payloadSize = (readBuf.getShort() & 0xFFFF) << 8 | readBuf.get() & 0xFF;
timestamp = (readBuf.getShort() & 0xFFFF) << 8 | readBuf.get() & 0xFF | (readBuf.get() & 0xFF) << 24;
streamId = (readBuf.getShort() & 0xFFFF) << 8 | readBuf.get() & 0xFF;
if (readBuf.remaining() >= payloadSize + 4) {
int thisPacketSize = readBuf.getInt(readBuf.position() + payloadSize);
if (thisPacketSize != payloadSize + 11) {
readBuf.position(readBuf.position() - 15);
if (!repositionFile()) {
Logger.error(String.format("Corrupt FLV stream at %d, failed to reposition!", packetPos));
this.ch.setPosition(this.ch.size());
this.eof = true;
return null;
}
Logger.warn(String.format("Corrupt FLV stream at %d, repositioned to %d.", packetPos, this.ch.position() -
(long)readBuf.remaining()));
continue;
}
}
if (readBuf.remaining() < payloadSize) {
readBuf.position(pos);
return null;
}
if (packetType != 8 && packetType != 9 && packetType != 18) {
NIOUtils.skip(readBuf, payloadSize);
continue;
}
payload = NIOUtils.clone(NIOUtils.read(readBuf, payloadSize));
if (packetType == 8) {
type = FLVTag.Type.AUDIO;
tagHeader = parseAudioTagHeader(payload.duplicate());
break;
}
if (packetType == 9) {
type = FLVTag.Type.VIDEO;
tagHeader = parseVideoTagHeader(payload.duplicate());
break;
}
if (packetType == 18) {
type = FLVTag.Type.SCRIPT;
tagHeader = null;
break;
}
System.out.println("NON AV packet");
}
boolean keyFrame = false;
if (tagHeader != null && tagHeader instanceof FLVTag.VideoTagHeader) {
FLVTag.VideoTagHeader vth = (FLVTag.VideoTagHeader)tagHeader;
keyFrame &= (vth.getFrameType() == 1) ? true : false;
}
keyFrame &= (packetType == 8 || packetType == 9) ? true : false;
return new FLVTag(type, packetPos, tagHeader, timestamp, payload, keyFrame, (long)this.frameNo++, streamId, prevPacketSize);
}
public static boolean readHeader(ByteBuffer readBuf) {
if (readBuf.remaining() < 9 || readBuf.get() != 70 || readBuf.get() != 76 || readBuf.get() != 86 ||
readBuf.get() != 1 || (readBuf.get() & 0x5) == 0 || readBuf.getInt() != 9)
return false;
return true;
}
public static FLVMetadata parseMetadata(ByteBuffer bb) {
if ("onMetaData".equals(readAMFData(bb, -1)))
return new FLVMetadata((Map<String, Object>)readAMFData(bb, -1));
return null;
}
private static Object readAMFData(ByteBuffer input, int type) {
Date date;
if (type == -1)
type = input.get() & 0xFF;
switch (type) {
case 0:
return input.getDouble();
case 1:
return (input.get() == 1);
case 2:
return readAMFString(input);
case 3:
return readAMFObject(input);
case 8:
return readAMFEcmaArray(input);
case 10:
return readAMFStrictArray(input);
case 11:
date = new Date((long)input.getDouble());
input.getShort();
return date;
case 13:
return "UNDEFINED";
}
return null;
}
private static Object readAMFStrictArray(ByteBuffer input) {
int count = input.getInt();
Object[] result = new Object[count];
for (int i = 0; i < count; i++)
result[i] = readAMFData(input, -1);
return result;
}
private static String readAMFString(ByteBuffer input) {
int size = input.getShort() & 0xFFFF;
return Platform.stringFromCharset(NIOUtils.toArray(NIOUtils.read(input, size)), "UTF-8");
}
private static Object readAMFObject(ByteBuffer input) {
Map<String, Object> array = new HashMap<>();
while (true) {
String key = readAMFString(input);
int dataType = input.get() & 0xFF;
if (dataType == 9)
break;
array.put(key, readAMFData(input, dataType));
}
return array;
}
private static Object readAMFEcmaArray(ByteBuffer input) {
long size = (long)input.getInt();
Map<String, Object> array = new HashMap<>();
for (int i = 0; (long)i < size; i++) {
String key = readAMFString(input);
int dataType = input.get() & 0xFF;
array.put(key, readAMFData(input, dataType));
}
return array;
}
public static FLVTag.VideoTagHeader parseVideoTagHeader(ByteBuffer dup) {
byte b0 = dup.get();
int frameType = (b0 & 0xFF) >> 4;
int codecId = b0 & 0xF;
Codec codec = videoCodecMapping[codecId];
if (codecId == 7) {
byte avcPacketType = dup.get();
int compOffset = dup.getShort() << 8 | dup.get() & 0xFF;
return new FLVTag.AvcVideoTagHeader(codec, frameType, avcPacketType, compOffset);
}
return new FLVTag.VideoTagHeader(codec, frameType);
}
public static FLVTag.TagHeader parseAudioTagHeader(ByteBuffer dup) {
byte b = dup.get();
int codecId = (b & 0xFF) >> 4;
int sampleRate = sampleRates[b >> 2 & 0x3];
if (codecId == 4 || codecId == 11)
sampleRate = 16000;
if (codecId == 5 || codecId == 14)
sampleRate = 8000;
int sampleSizeInBits = ((b & 0x2) == 0) ? 8 : 16;
boolean signed = ((codecId != 3 && codecId != 0) || sampleSizeInBits == 16);
int channelCount = 1 + (b & 0x1);
if (codecId == 11)
channelCount = 1;
AudioFormat audioFormat = new AudioFormat(sampleRate, sampleSizeInBits, channelCount, signed,
(codecId == 3) ? false : platformBigEndian);
Codec codec = audioCodecMapping[codecId];
if (codecId == 10) {
byte packetType = dup.get();
return new FLVTag.AacAudioTagHeader(codec, audioFormat, packetType);
}
return new FLVTag.AudioTagHeader(codec, audioFormat);
}
public static int probe(ByteBuffer buf) {
try {
readHeader(buf);
return 100;
} catch (RuntimeException e) {
return 0;
}
}
public void reset() throws IOException {
initialRead(this.ch);
}
public void reposition() throws IOException {
reset();
if (!positionAtPacket(this.readBuf))
throw new RuntimeException("Could not find at FLV tag start");
}
public static boolean positionAtPacket(ByteBuffer readBuf) {
ByteBuffer dup = readBuf.duplicate();
int payloadSize = 0;
NIOUtils.skip(dup, 5);
while (dup.hasRemaining()) {
payloadSize = (payloadSize & 0xFFFF) << 8 | dup.get() & 0xFF;
int pointerPos = dup.position() + 7 + payloadSize;
if (dup.position() >= 8 && pointerPos < dup.limit() - 4 && dup.getInt(pointerPos) - payloadSize == 11) {
readBuf.position(dup.position() - 8);
return true;
}
}
return false;
}
public boolean repositionFile() throws IOException {
int payloadSize = 0;
for (int i = 0; i < 10; i++) {
while (this.readBuf.hasRemaining()) {
payloadSize = (payloadSize & 0xFFFF) << 8 | this.readBuf.get() & 0xFF;
int pointerPos = this.readBuf.position() + 7 + payloadSize;
if (this.readBuf.position() >= 8 && pointerPos < this.readBuf.limit() - 4 &&
this.readBuf.getInt(pointerPos) - payloadSize == 11) {
this.readBuf.position(this.readBuf.position() - 8);
return true;
}
}
initialRead(this.ch);
if (!this.readBuf.hasRemaining())
break;
}
return false;
}
}

View file

@ -0,0 +1,164 @@
package org.jcodec.containers.flv;
import java.nio.ByteBuffer;
import org.jcodec.common.AudioFormat;
import org.jcodec.common.Codec;
public class FLVTag {
private Type type;
private long position;
private TagHeader tagHeader;
private int pts;
private ByteBuffer data;
private boolean keyFrame;
private long frameNo;
private int streamId;
private int prevPacketSize;
public FLVTag(Type type, long position, TagHeader tagHeader, int pts, ByteBuffer data, boolean keyFrame, long frameNo, int streamId, int prevPacketSize) {
this.type = type;
this.position = position;
this.tagHeader = tagHeader;
this.pts = pts;
this.data = data;
this.keyFrame = keyFrame;
this.frameNo = frameNo;
this.streamId = streamId;
this.prevPacketSize = prevPacketSize;
}
public enum Type {
VIDEO, AUDIO, SCRIPT;
}
public Type getType() {
return this.type;
}
public long getPosition() {
return this.position;
}
public TagHeader getTagHeader() {
return this.tagHeader;
}
public int getPts() {
return this.pts;
}
public void setPts(int pts) {
this.pts = pts;
}
public int getStreamId() {
return this.streamId;
}
public void setStreamId(int streamId) {
this.streamId = streamId;
}
public int getPrevPacketSize() {
return this.prevPacketSize;
}
public void setPrevPacketSize(int prevPacketSize) {
this.prevPacketSize = prevPacketSize;
}
public ByteBuffer getData() {
return this.data;
}
public double getPtsD() {
return (double)this.pts / 1000.0D;
}
public boolean isKeyFrame() {
return this.keyFrame;
}
public long getFrameNo() {
return this.frameNo;
}
public static class TagHeader {
private Codec codec;
public TagHeader(Codec codec) {
this.codec = codec;
}
public Codec getCodec() {
return this.codec;
}
}
public static class VideoTagHeader extends TagHeader {
private int frameType;
public VideoTagHeader(Codec codec, int frameType) {
super(codec);
this.frameType = frameType;
}
public int getFrameType() {
return this.frameType;
}
}
public static class AvcVideoTagHeader extends VideoTagHeader {
private int compOffset;
private byte avcPacketType;
public AvcVideoTagHeader(Codec codec, int frameType, byte avcPacketType, int compOffset) {
super(codec, frameType);
this.avcPacketType = avcPacketType;
this.compOffset = compOffset;
}
public int getCompOffset() {
return this.compOffset;
}
public byte getAvcPacketType() {
return this.avcPacketType;
}
}
public static class AudioTagHeader extends TagHeader {
private AudioFormat audioFormat;
public AudioTagHeader(Codec codec, AudioFormat audioFormat) {
super(codec);
this.audioFormat = audioFormat;
}
public AudioFormat getAudioFormat() {
return this.audioFormat;
}
}
public static class AacAudioTagHeader extends AudioTagHeader {
private int packetType;
public AacAudioTagHeader(Codec codec, AudioFormat audioFormat, int packetType) {
super(codec, audioFormat);
this.packetType = packetType;
}
public int getPacketType() {
return this.packetType;
}
}
}

View file

@ -0,0 +1,496 @@
package org.jcodec.containers.flv;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.jcodec.codecs.h264.H264Utils;
import org.jcodec.codecs.h264.io.model.PictureParameterSet;
import org.jcodec.codecs.h264.io.model.SeqParameterSet;
import org.jcodec.codecs.h264.mp4.AvcCBox;
import org.jcodec.common.AudioFormat;
import org.jcodec.common.Codec;
import org.jcodec.common.StringUtils;
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.tools.MainUtils;
import org.jcodec.platform.Platform;
public class FLVTool {
private static Map<String, PacketProcessorFactory> processors = new HashMap<>();
static {
processors.put("clip", new ClipPacketProcessor.Factory());
processors.put("fix_pts", new FixPtsProcessor.Factory());
processors.put("info", new InfoPacketProcessor.Factory());
processors.put("shift_pts", new ShiftPtsProcessor.Factory());
}
private static final MainUtils.Flag FLAG_MAX_PACKETS = MainUtils.Flag.flag("max-packets", "m", "Maximum number of packets to process");
public static void main1(String[] args) throws IOException {
if (args.length < 1) {
printGenericHelp();
return;
}
String command = args[0];
PacketProcessorFactory processorFactory = processors.get(command);
if (processorFactory == null) {
System.err.println("Unknown command: " + command);
printGenericHelp();
return;
}
MainUtils.Cmd cmd = MainUtils.parseArguments(Platform.<String>copyOfRangeO(args, 1, args.length), processorFactory.getFlags());
if (cmd.args.length < 1) {
MainUtils.printHelpCmd(command, processorFactory.getFlags(), Arrays.asList("file in", "?file out"));
return;
}
PacketProcessor processor = processorFactory.newPacketProcessor(cmd);
int maxPackets = cmd.getIntegerFlagD(FLAG_MAX_PACKETS, Integer.valueOf(Integer.MAX_VALUE));
SeekableByteChannel _in = null;
SeekableByteChannel out = null;
try {
_in = NIOUtils.readableChannel(new File(cmd.getArg(0)));
if (processor.hasOutput())
out = NIOUtils.writableChannel(new File(cmd.getArg(1)));
FLVReader demuxer = new FLVReader(_in);
FLVWriter muxer = new FLVWriter(out);
FLVTag pkt = null;
for (int i = 0; i < maxPackets && (pkt = demuxer.readNextPacket()) != null &&
processor.processPacket(pkt, muxer); i++);
processor.finish(muxer);
if (processor.hasOutput())
muxer.finish();
} finally {
IOUtils.closeQuietly(_in);
IOUtils.closeQuietly(out);
}
}
private static void printGenericHelp() {
System.err.println("Syntax: <command> [flags] <file in> [file out]\nWhere command is: [" +
StringUtils.joinS(processors.keySet().toArray(new String[0]), ", ") + "].");
}
private static PacketProcessor getProcessor(String command, MainUtils.Cmd cmd) {
PacketProcessorFactory factory = processors.get(command);
if (factory == null)
return null;
return factory.newPacketProcessor(cmd);
}
public static class ClipPacketProcessor implements PacketProcessor {
private static FLVTag h264Config;
private boolean copying = false;
private Double from;
private Double to;
private static final MainUtils.Flag FLAG_FROM = MainUtils.Flag.flag("from", null, "From timestamp (in seconds, i.e 67.49)");
private static final MainUtils.Flag FLAG_TO = MainUtils.Flag.flag("to", null, "To timestamp");
public static class Factory implements FLVTool.PacketProcessorFactory {
public FLVTool.PacketProcessor newPacketProcessor(MainUtils.Cmd flags) {
return new FLVTool.ClipPacketProcessor(flags.getDoubleFlag(FLVTool.ClipPacketProcessor.FLAG_FROM), flags.getDoubleFlag(FLVTool.ClipPacketProcessor.FLAG_TO));
}
public MainUtils.Flag[] getFlags() {
return new MainUtils.Flag[] { FLVTool.ClipPacketProcessor.FLAG_FROM, FLVTool.ClipPacketProcessor.FLAG_TO };
}
}
public ClipPacketProcessor(Double from, Double to) {
this.from = from;
this.to = to;
}
public boolean processPacket(FLVTag pkt, FLVWriter writer) throws IOException {
if (pkt.getType() == FLVTag.Type.VIDEO && pkt.getTagHeader().getCodec() == Codec.H264 && (
(FLVTag.AvcVideoTagHeader)pkt.getTagHeader()).getAvcPacketType() == 0) {
h264Config = pkt;
System.out.println("GOT AVCC");
}
if (!this.copying && (this.from == null || pkt.getPtsD() > this.from) && pkt.getType() == FLVTag.Type.VIDEO && pkt.isKeyFrame() && h264Config != null) {
System.out.println("Starting at packet: " + Platform.toJSON(pkt));
this.copying = true;
h264Config.setPts(pkt.getPts());
writer.addPacket(h264Config);
}
if (this.to != null && pkt.getPtsD() >= this.to) {
System.out.println("Stopping at packet: " + Platform.toJSON(pkt));
return false;
}
if (this.copying)
writer.addPacket(pkt);
return true;
}
public void finish(FLVWriter muxer) {}
public boolean hasOutput() {
return true;
}
}
public static class Factory implements PacketProcessorFactory {
public FLVTool.PacketProcessor newPacketProcessor(MainUtils.Cmd flags) {
return new FLVTool.ClipPacketProcessor(flags.getDoubleFlag(FLVTool.ClipPacketProcessor.FLAG_FROM), flags.getDoubleFlag(FLVTool.ClipPacketProcessor.FLAG_TO));
}
public MainUtils.Flag[] getFlags() {
return new MainUtils.Flag[] { FLVTool.ClipPacketProcessor.FLAG_FROM, FLVTool.ClipPacketProcessor.FLAG_TO };
}
}
public static class FixPtsProcessor implements PacketProcessor {
private double lastPtsAudio = 0.0D;
private double lastPtsVideo = 0.0D;
private List<FLVTag> tags;
private int audioTagsInQueue;
private int videoTagsInQueue;
private static final double CORRECTION_PACE = 0.33D;
public static class Factory implements FLVTool.PacketProcessorFactory {
public FLVTool.PacketProcessor newPacketProcessor(MainUtils.Cmd flags) {
return new FLVTool.FixPtsProcessor();
}
public MainUtils.Flag[] getFlags() {
return new MainUtils.Flag[0];
}
}
public FixPtsProcessor() {
this.tags = new ArrayList<>();
}
public boolean processPacket(FLVTag pkt, FLVWriter writer) throws IOException {
this.tags.add(pkt);
if (pkt.getType() == FLVTag.Type.AUDIO) {
this.audioTagsInQueue++;
} else if (pkt.getType() == FLVTag.Type.VIDEO) {
this.videoTagsInQueue++;
}
if (this.tags.size() < 600)
return true;
processOneTag(writer);
return true;
}
private void processOneTag(FLVWriter writer) throws IOException {
FLVTag tag = (FLVTag)this.tags.remove(0);
if (tag.getType() == FLVTag.Type.AUDIO) {
tag.setPts((int)Math.round(this.lastPtsAudio * 1000.0D));
this.lastPtsAudio += audioFrameDuration((FLVTag.AudioTagHeader)tag.getTagHeader());
this.audioTagsInQueue--;
} else if (tag.getType() == FLVTag.Type.VIDEO) {
double duration = 1024.0D * (double)this.audioTagsInQueue / (double)(48000 * this.videoTagsInQueue);
tag.setPts((int)Math.round(this.lastPtsVideo * 1000.0D));
this.lastPtsVideo += Math.min(1.33D * duration, Math.max(0.6699999999999999D * duration, duration +
Math.min(1.0D, Math.abs(this.lastPtsAudio - this.lastPtsVideo)) * (this.lastPtsAudio - this.lastPtsVideo)));
this.videoTagsInQueue--;
System.out.println("" + this.lastPtsVideo + " - " + this.lastPtsVideo);
} else {
tag.setPts((int)Math.round(this.lastPtsVideo * 1000.0D));
}
writer.addPacket(tag);
}
private double audioFrameDuration(FLVTag.AudioTagHeader audioTagHeader) {
if (Codec.AAC == audioTagHeader.getCodec())
return 1024.0D / (double)audioTagHeader.getAudioFormat().getSampleRate();
if (Codec.MP3 == audioTagHeader.getCodec())
return 1152.0D / (double)audioTagHeader.getAudioFormat().getSampleRate();
throw new RuntimeException("Audio codec:" + String.valueOf(audioTagHeader.getCodec()) + " is not supported.");
}
public void finish(FLVWriter muxer) throws IOException {
while (this.tags.size() > 0)
processOneTag(muxer);
}
public boolean hasOutput() {
return true;
}
}
public static class Factory implements PacketProcessorFactory {
public FLVTool.PacketProcessor newPacketProcessor(MainUtils.Cmd flags) {
return new FLVTool.FixPtsProcessor();
}
public MainUtils.Flag[] getFlags() {
return new MainUtils.Flag[0];
}
}
public static class InfoPacketProcessor implements PacketProcessor {
private FLVTag prevVideoTag;
private FLVTag prevAudioTag;
private boolean checkOnly;
private FLVTag.Type streamType;
public static class Factory implements FLVTool.PacketProcessorFactory {
private static final MainUtils.Flag FLAG_CHECK = MainUtils.Flag.flag("check", null, "Check sanity and report errors only, no packet dump will be generated.");
private static final MainUtils.Flag FLAG_STREAM = MainUtils.Flag.flag("stream", null, "Stream selector, can be one of: ['video', 'audio', 'script'].");
public FLVTool.PacketProcessor newPacketProcessor(MainUtils.Cmd flags) {
return new FLVTool.InfoPacketProcessor(flags.getBooleanFlagD(FLAG_CHECK, Boolean.valueOf(false)),
flags.<FLVTag.Type>getEnumFlagD(FLAG_STREAM, null, FLVTag.Type.class));
}
public MainUtils.Flag[] getFlags() {
return new MainUtils.Flag[] { FLAG_CHECK, FLAG_STREAM };
}
}
public InfoPacketProcessor(boolean checkOnly, FLVTag.Type streamType) {
this.checkOnly = checkOnly;
this.streamType = streamType;
}
public boolean processPacket(FLVTag pkt, FLVWriter writer) throws IOException {
if (this.checkOnly)
return true;
if (pkt.getType() == FLVTag.Type.VIDEO) {
if (this.streamType == FLVTag.Type.VIDEO || this.streamType == null) {
if (this.prevVideoTag != null)
dumpOnePacket(this.prevVideoTag, pkt.getPts() - this.prevVideoTag.getPts());
this.prevVideoTag = pkt;
}
} else if (pkt.getType() == FLVTag.Type.AUDIO) {
if (this.streamType == FLVTag.Type.AUDIO || this.streamType == null) {
if (this.prevAudioTag != null)
dumpOnePacket(this.prevAudioTag, pkt.getPts() - this.prevAudioTag.getPts());
this.prevAudioTag = pkt;
}
} else {
dumpOnePacket(pkt, 0);
}
return true;
}
private void dumpOnePacket(FLVTag pkt, int duration) {
System.out.print("T=" + typeString(pkt.getType()) + "|PTS=" + pkt.getPts() + "|DUR=" + duration + "|" + (
pkt.isKeyFrame() ? "K" : " ") + "|POS=" + pkt.getPosition());
if (pkt.getTagHeader() instanceof FLVTag.VideoTagHeader) {
FLVTag.VideoTagHeader vt = (FLVTag.VideoTagHeader)pkt.getTagHeader();
System.out.print("|C=" + String.valueOf(vt.getCodec()) + "|FT=" + vt.getFrameType());
if (vt instanceof FLVTag.AvcVideoTagHeader) {
FLVTag.AvcVideoTagHeader avct = (FLVTag.AvcVideoTagHeader)vt;
System.out.print("|PKT_TYPE=" + avct.getAvcPacketType() + "|COMP_OFF=" + avct.getCompOffset());
if (avct.getAvcPacketType() == 0) {
ByteBuffer frameData = pkt.getData().duplicate();
FLVReader.parseVideoTagHeader(frameData);
AvcCBox avcc = H264Utils.parseAVCCFromBuffer(frameData);
for (SeqParameterSet sps : H264Utils.readSPSFromBufferList(avcc.getSpsList())) {
System.out.println();
System.out.print(" SPS[" + sps.getSeqParameterSetId() + "]:" + Platform.toJSON(sps));
}
for (PictureParameterSet pps : H264Utils.readPPSFromBufferList(avcc.getPpsList())) {
System.out.println();
System.out.print(" PPS[" + pps.getPicParameterSetId() + "]:" + Platform.toJSON(pps));
}
}
}
} else if (pkt.getTagHeader() instanceof FLVTag.AudioTagHeader) {
FLVTag.AudioTagHeader at = (FLVTag.AudioTagHeader)pkt.getTagHeader();
AudioFormat format = at.getAudioFormat();
System.out.print("|C=" + String.valueOf(at.getCodec()) + "|SR=" + format.getSampleRate() + "|SS=" + (
format.getSampleSizeInBits() >> 3) + "|CH=" + format.getChannels());
} else if (pkt.getType() == FLVTag.Type.SCRIPT) {
FLVMetadata metadata = FLVReader.parseMetadata(pkt.getData().duplicate());
if (metadata != null) {
System.out.println();
System.out.print(" Metadata:" + Platform.toJSON(metadata));
}
}
System.out.println();
}
private String typeString(FLVTag.Type type) {
return type.toString().substring(0, 1);
}
public void finish(FLVWriter muxer) throws IOException {
if (this.prevVideoTag != null)
dumpOnePacket(this.prevVideoTag, 0);
if (this.prevAudioTag != null)
dumpOnePacket(this.prevAudioTag, 0);
}
public boolean hasOutput() {
return false;
}
}
public static class Factory implements PacketProcessorFactory {
private static final MainUtils.Flag FLAG_CHECK = MainUtils.Flag.flag("check", null, "Check sanity and report errors only, no packet dump will be generated.");
private static final MainUtils.Flag FLAG_STREAM = MainUtils.Flag.flag("stream", null, "Stream selector, can be one of: ['video', 'audio', 'script'].");
public FLVTool.PacketProcessor newPacketProcessor(MainUtils.Cmd flags) {
return new FLVTool.InfoPacketProcessor(flags.getBooleanFlagD(FLAG_CHECK, Boolean.valueOf(false)), flags.<FLVTag.Type>getEnumFlagD(FLAG_STREAM, null, FLVTag.Type.class));
}
public MainUtils.Flag[] getFlags() {
return new MainUtils.Flag[] { FLAG_CHECK, FLAG_STREAM };
}
}
public static class ShiftPtsProcessor implements PacketProcessor {
private static final long WRAP_AROUND_VALUE = 2147483648L;
private static final int HALF_WRAP_AROUND_VALUE = 1073741824;
private static final MainUtils.Flag FLAG_TO = MainUtils.Flag.flag("to", null, "Shift first pts to this value, and all subsequent pts accordingly.");
private static final MainUtils.Flag FLAG_BY = MainUtils.Flag.flag("by", null, "Shift all pts by this value.");
private static final MainUtils.Flag FLAG_WRAP_AROUND = MainUtils.Flag.flag("wrap-around", null, "Expect wrap around of timestamps.");
private int shiftTo;
private Integer shiftBy;
private long ptsDelta;
private boolean firstPtsSeen;
private List<FLVTag> savedTags;
private boolean expectWrapAround;
private int prevPts;
public static class Factory implements FLVTool.PacketProcessorFactory {
public FLVTool.PacketProcessor newPacketProcessor(MainUtils.Cmd flags) {
return new FLVTool.ShiftPtsProcessor(flags.getIntegerFlagD(FLVTool.ShiftPtsProcessor.FLAG_TO, Integer.valueOf(0)), flags.getIntegerFlag(FLVTool.ShiftPtsProcessor.FLAG_BY),
flags.getBooleanFlagD(FLVTool.ShiftPtsProcessor.FLAG_WRAP_AROUND, Boolean.valueOf(false)));
}
public MainUtils.Flag[] getFlags() {
return new MainUtils.Flag[] { FLVTool.ShiftPtsProcessor.FLAG_TO, FLVTool.ShiftPtsProcessor.FLAG_BY, FLVTool.ShiftPtsProcessor.FLAG_WRAP_AROUND };
}
}
public ShiftPtsProcessor(int shiftTo, Integer shiftBy, boolean expectWrapAround) {
this.savedTags = new LinkedList<>();
this.shiftTo = shiftTo;
this.shiftBy = shiftBy;
this.expectWrapAround = true;
}
public boolean processPacket(FLVTag pkt, FLVWriter writer) throws IOException {
boolean avcPrivatePacket = (pkt.getType() == FLVTag.Type.VIDEO && ((FLVTag.VideoTagHeader)
pkt.getTagHeader()).getCodec() == Codec.H264 && ((FLVTag.AvcVideoTagHeader)
pkt.getTagHeader()).getAvcPacketType() == 0);
boolean aacPrivatePacket = (pkt.getType() == FLVTag.Type.AUDIO && ((FLVTag.AudioTagHeader)
pkt.getTagHeader()).getCodec() == Codec.AAC && ((FLVTag.AacAudioTagHeader)
pkt.getTagHeader()).getPacketType() == 0);
boolean validPkt = (pkt.getType() != FLVTag.Type.SCRIPT && !avcPrivatePacket && !aacPrivatePacket);
if (this.expectWrapAround && validPkt && pkt.getPts() < this.prevPts && (long)this.prevPts -
(long)pkt.getPts() > 1073741824L) {
Logger.warn("Wrap around detected: " + this.prevPts + " -> " + pkt.getPts());
if (pkt.getPts() < -1073741824) {
this.ptsDelta += 4294967296L;
} else if (pkt.getPts() >= 0) {
this.ptsDelta += 2147483648L;
}
}
if (validPkt)
this.prevPts = pkt.getPts();
if (this.firstPtsSeen) {
writePacket(pkt, writer);
} else if (!validPkt) {
this.savedTags.add(pkt);
} else {
if (this.shiftBy != null) {
this.ptsDelta = (long)this.shiftBy;
if (this.ptsDelta + (long)pkt.getPts() < 0L)
this.ptsDelta = (long)-pkt.getPts();
} else {
this.ptsDelta = (long)(this.shiftTo - pkt.getPts());
}
this.firstPtsSeen = true;
emptySavedTags(writer);
writePacket(pkt, writer);
}
return true;
}
private void writePacket(FLVTag pkt, FLVWriter writer) throws IOException {
long newPts = (long)pkt.getPts() + this.ptsDelta;
if (newPts < 0L) {
Logger.warn("Preventing negative pts for tag @" + pkt.getPosition());
if (this.shiftBy != null) {
newPts = 0L;
} else {
newPts = (long)this.shiftTo;
}
} else if (newPts >= 2147483648L) {
Logger.warn("PTS wrap around @" + pkt.getPosition());
newPts -= 2147483648L;
this.ptsDelta = newPts - (long)pkt.getPts();
}
pkt.setPts((int)newPts);
writer.addPacket(pkt);
}
private void emptySavedTags(FLVWriter muxer) throws IOException {
while (this.savedTags.size() > 0)
writePacket((FLVTag)this.savedTags.remove(0), muxer);
}
public void finish(FLVWriter muxer) throws IOException {
emptySavedTags(muxer);
}
public boolean hasOutput() {
return true;
}
}
public static class Factory implements PacketProcessorFactory {
public FLVTool.PacketProcessor newPacketProcessor(MainUtils.Cmd flags) {
return new FLVTool.ShiftPtsProcessor(flags.getIntegerFlagD(FLVTool.ShiftPtsProcessor.FLAG_TO, Integer.valueOf(0)), flags.getIntegerFlag(FLVTool.ShiftPtsProcessor.FLAG_BY), flags.getBooleanFlagD(FLVTool.ShiftPtsProcessor.FLAG_WRAP_AROUND, Boolean.valueOf(false)));
}
public MainUtils.Flag[] getFlags() {
return new MainUtils.Flag[] { FLVTool.ShiftPtsProcessor.FLAG_TO, FLVTool.ShiftPtsProcessor.FLAG_BY, FLVTool.ShiftPtsProcessor.FLAG_WRAP_AROUND };
}
}
public static interface PacketProcessorFactory {
FLVTool.PacketProcessor newPacketProcessor(MainUtils.Cmd param1Cmd);
MainUtils.Flag[] getFlags();
}
public static interface PacketProcessor {
boolean processPacket(FLVTag param1FLVTag, FLVWriter param1FLVWriter) throws IOException;
boolean hasOutput();
void finish(FLVWriter param1FLVWriter) throws IOException;
}
}

View file

@ -0,0 +1,188 @@
package org.jcodec.containers.flv;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import org.jcodec.common.Codec;
import org.jcodec.common.DemuxerTrack;
import org.jcodec.common.DemuxerTrackMeta;
import org.jcodec.common.LongArrayList;
import org.jcodec.common.SeekableDemuxerTrack;
import org.jcodec.common.TrackType;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.common.model.Packet;
public class FLVTrackDemuxer {
private static final int MAX_CRAWL_DISTANCE_SEC = 10;
private FLVReader demuxer;
private FLVDemuxerTrack video;
private FLVDemuxerTrack audio;
private LinkedList<FLVTag> packets;
private SeekableByteChannel _in;
public static class FLVDemuxerTrack implements SeekableDemuxerTrack {
private FLVTag.Type type;
private int curFrame;
private Codec codec;
private LongArrayList framePositions;
private byte[] codecPrivate;
private FLVTrackDemuxer demuxer;
public FLVDemuxerTrack(FLVTrackDemuxer demuxer, FLVTag.Type type) throws IOException {
this.framePositions = LongArrayList.createLongArrayList();
this.demuxer = demuxer;
this.type = type;
FLVTag frame = demuxer.nextFrameI(type, false);
this.codec = frame.getTagHeader().getCodec();
}
public Packet nextFrame() throws IOException {
FLVTag frame = this.demuxer.nextFrameI(this.type, true);
this.framePositions.add(frame.getPosition());
return toPacket(frame);
}
public Packet prevFrame() throws IOException {
FLVTag frame = this.demuxer.prevFrameI(this.type, true);
return toPacket(frame);
}
public Packet pickFrame() throws IOException {
FLVTag frame = this.demuxer.nextFrameI(this.type, false);
return toPacket(frame);
}
private Packet toPacket(FLVTag frame) {
return null;
}
public DemuxerTrackMeta getMeta() {
TrackType t = (this.type == FLVTag.Type.VIDEO) ? TrackType.VIDEO : TrackType.AUDIO;
return new DemuxerTrackMeta(t, this.codec, 0.0D, null, 0, ByteBuffer.wrap(this.codecPrivate), null, null);
}
public boolean gotoFrame(long i) throws IOException {
if (i >= (long)this.framePositions.size())
return false;
this.demuxer.resetToPosition(this.framePositions.get((int)i));
return true;
}
public boolean gotoSyncFrame(long i) {
throw new RuntimeException();
}
public long getCurFrame() {
return (long)this.curFrame;
}
public void seek(double second) throws IOException {
this.demuxer.seekI(second);
}
}
public FLVTrackDemuxer(SeekableByteChannel _in) throws IOException {
this.packets = new LinkedList<>();
this._in = _in;
_in.setPosition(0L);
this.demuxer = new FLVReader(_in);
this.video = new FLVDemuxerTrack(this, FLVTag.Type.VIDEO);
this.audio = new FLVDemuxerTrack(this, FLVTag.Type.AUDIO);
}
private void resetToPosition(long position) throws IOException {
this._in.setPosition(position);
this.demuxer.reset();
this.packets.clear();
}
private void seekI(double second) throws IOException {
this.packets.clear();
FLVTag base;
while ((base = this.demuxer.readNextPacket()) != null && base.getPtsD() == 0.0D);
if (base == null)
return;
this._in.setPosition(base.getPosition() + 1048576L);
this.demuxer.reposition();
FLVTag off = this.demuxer.readNextPacket();
int byteRate = (int)((double)(off.getPosition() - base.getPosition()) / (off.getPtsD() - base.getPtsD()));
long offset = base.getPosition() + (long)((second - base.getPtsD()) * (double)byteRate);
this._in.setPosition(offset);
this.demuxer.reposition();
for (int i = 0; i < 5; i++) {
FLVTag pkt = this.demuxer.readNextPacket();
double distance = second - pkt.getPtsD();
if (distance > 0.0D && distance < 10.0D) {
System.out.println("Crawling forward: " + distance);
FLVTag testPkt;
while ((testPkt = this.demuxer.readNextPacket()) != null && testPkt.getPtsD() < second);
if (testPkt != null)
this.packets.add(pkt);
return;
}
if (distance < 0.0D && distance > -10.0D) {
System.out.println("Overshoot by: " + -distance);
this._in.setPosition(pkt.getPosition() + (long)((distance - 1.0D) * (double)byteRate));
this.demuxer.reposition();
}
}
}
private FLVTag nextFrameI(FLVTag.Type type, boolean remove) throws IOException {
for (Iterator<FLVTag> it = this.packets.iterator(); it.hasNext(); ) {
FLVTag fLVTag = it.next();
if (fLVTag.getType() == type) {
if (remove)
it.remove();
return fLVTag;
}
}
FLVTag pkt;
while ((pkt = this.demuxer.readNextPacket()) != null && pkt.getType() != type)
this.packets.add(pkt);
if (!remove)
this.packets.add(pkt);
return pkt;
}
private FLVTag prevFrameI(FLVTag.Type type, boolean remove) throws IOException {
for (ListIterator<FLVTag> it = this.packets.listIterator(); it.hasPrevious(); ) {
FLVTag fLVTag = it.previous();
if (fLVTag.getType() == type) {
if (remove)
it.remove();
return fLVTag;
}
}
FLVTag pkt;
while ((pkt = this.demuxer.readPrevPacket()) != null && pkt.getType() != type)
this.packets.add(0, pkt);
if (!remove)
this.packets.add(0, pkt);
return pkt;
}
public DemuxerTrack[] getTracks() {
return new DemuxerTrack[] { this.video, this.audio };
}
public DemuxerTrack getVideoTrack() {
return this.video;
}
public DemuxerTrack getAudioTrack() {
return this.video;
}
}

View file

@ -0,0 +1,65 @@
package org.jcodec.containers.flv;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.io.SeekableByteChannel;
public class FLVWriter {
private static final int WRITE_BUFFER_SIZE = 1048576;
private int startOfLastPacket = 9;
private SeekableByteChannel out;
private ByteBuffer writeBuf;
public FLVWriter(SeekableByteChannel out) {
this.out = out;
this.writeBuf = ByteBuffer.allocate(1048576);
writeHeader(this.writeBuf);
}
public void addPacket(FLVTag pkt) throws IOException {
if (!writePacket(this.writeBuf, pkt)) {
this.writeBuf.flip();
this.startOfLastPacket -= this.out.write(this.writeBuf);
this.writeBuf.clear();
if (!writePacket(this.writeBuf, pkt))
throw new RuntimeException("Unexpected");
}
}
public void finish() throws IOException {
this.writeBuf.flip();
this.out.write(this.writeBuf);
}
private boolean writePacket(ByteBuffer writeBuf, FLVTag pkt) {
int pktType = (pkt.getType() == FLVTag.Type.VIDEO) ? 9 : ((pkt.getType() == FLVTag.Type.SCRIPT) ? 18 : 8);
int dataLen = pkt.getData().remaining();
if (writeBuf.remaining() < 15 + dataLen)
return false;
writeBuf.putInt(writeBuf.position() - this.startOfLastPacket);
this.startOfLastPacket = writeBuf.position();
writeBuf.put((byte)pktType);
writeBuf.putShort((short)(dataLen >> 8));
writeBuf.put((byte)(dataLen & 0xFF));
writeBuf.putShort((short)(pkt.getPts() >> 8 & 0xFFFF));
writeBuf.put((byte)(pkt.getPts() & 0xFF));
writeBuf.put((byte)(pkt.getPts() >> 24 & 0xFF));
writeBuf.putShort((short)0);
writeBuf.put((byte)0);
NIOUtils.write(writeBuf, pkt.getData().duplicate());
return true;
}
private static void writeHeader(ByteBuffer writeBuf) {
writeBuf.put((byte)70);
writeBuf.put((byte)76);
writeBuf.put((byte)86);
writeBuf.put((byte)1);
writeBuf.put((byte)5);
writeBuf.putInt(9);
}
}