www in docker support
This commit is contained in:
parent
539a848e95
commit
c227fce036
2145 changed files with 399596 additions and 58 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue