The beginning of a filter to patch up the video metadata after the first
run.
This commit is contained in:
parent
74f08f44b0
commit
aebec22299
3 changed files with 190 additions and 0 deletions
170
src/main/java/org/forkalsrud/album/video/FlvFilter.java
Normal file
170
src/main/java/org/forkalsrud/album/video/FlvFilter.java
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
package org.forkalsrud.album.video;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Separates the FLV header boxes from the body boxes.
|
||||||
|
* After streaming a file through the header can be rewritten with metadata appropriate for streaming.
|
||||||
|
*/
|
||||||
|
public class FlvFilter extends OutputStream {
|
||||||
|
|
||||||
|
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(FlvFilter.class);
|
||||||
|
|
||||||
|
private final static int FLV_SIZE_TAGHEADER = 11;
|
||||||
|
private final static int FLV_SIZE_TAGFOOTER = 4;
|
||||||
|
private final static int FLV_SIZE_FILEHEADER = 13;
|
||||||
|
|
||||||
|
private final static int FLV_TAG_AUDIO = 8;
|
||||||
|
private final static int FLV_TAG_VIDEO = 9;
|
||||||
|
private final static int FLV_TAG_SCRIPTDATA = 18;
|
||||||
|
|
||||||
|
private OutputStream headerDst;
|
||||||
|
private OutputStream bodyDst;
|
||||||
|
|
||||||
|
private byte[] fileHeader = new byte[FLV_SIZE_FILEHEADER];
|
||||||
|
private int byteCounter = 0;
|
||||||
|
private byte[] currentBoxHeader = new byte[FLV_SIZE_TAGHEADER];
|
||||||
|
private int currentBoxWriteIdx = 0;
|
||||||
|
|
||||||
|
private int currentTagPos;
|
||||||
|
private int currentTagType;
|
||||||
|
private int currentDataSize;
|
||||||
|
private int currentTagTimestamp;
|
||||||
|
|
||||||
|
private int currentTagSize;
|
||||||
|
private byte[] currentBox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receive some bytes of FLV output
|
||||||
|
* @param b
|
||||||
|
* @param off
|
||||||
|
* @param len
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b, int off, int len) throws IOException {
|
||||||
|
|
||||||
|
// stateful
|
||||||
|
// initially we receive the header 'FLV.....'
|
||||||
|
// then we receive one box after another
|
||||||
|
// if a box can be classified as a header we write it to header dst
|
||||||
|
// else it is for body dst
|
||||||
|
int remainingInputLength = len;
|
||||||
|
int readOffset = off;
|
||||||
|
if (byteCounter < FLV_SIZE_FILEHEADER) {
|
||||||
|
int headerBytesToRead = Math.min(FLV_SIZE_FILEHEADER - byteCounter, remainingInputLength);
|
||||||
|
for (int i = 0; i < headerBytesToRead; i++) {
|
||||||
|
fileHeader[byteCounter++] = b[readOffset++];
|
||||||
|
}
|
||||||
|
remainingInputLength -= headerBytesToRead;
|
||||||
|
currentTagPos = byteCounter;
|
||||||
|
}
|
||||||
|
while (remainingInputLength > 0) {
|
||||||
|
// read the header first
|
||||||
|
if (currentBoxWriteIdx < FLV_SIZE_TAGHEADER) {
|
||||||
|
int headerBytesToRead = Math.min(FLV_SIZE_TAGHEADER - currentBoxWriteIdx, remainingInputLength);
|
||||||
|
for (int i = 0; i < headerBytesToRead; i++) {
|
||||||
|
currentBoxHeader[currentBoxWriteIdx++] = b[readOffset++];
|
||||||
|
byteCounter++;
|
||||||
|
}
|
||||||
|
remainingInputLength -= headerBytesToRead;
|
||||||
|
if (currentBoxWriteIdx < FLV_SIZE_TAGHEADER) {
|
||||||
|
// Don't have a full header yet, wait for next
|
||||||
|
if (remainingInputLength > 0) {
|
||||||
|
throw new RuntimeException("something is off in the lengths we're trying to read");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Got the header, prepare the buffer for the entire box
|
||||||
|
currentTagType = decodeUint8(currentBoxHeader, 0);
|
||||||
|
currentDataSize = decodeUint24(currentBoxHeader, 1);
|
||||||
|
currentTagTimestamp = decodeTimestamp(currentBoxHeader, 4);
|
||||||
|
currentTagSize = currentDataSize + FLV_SIZE_TAGHEADER + FLV_SIZE_TAGFOOTER;
|
||||||
|
currentBox = new byte[currentTagSize];
|
||||||
|
for (int i = 0; i < FLV_SIZE_TAGHEADER; i++) {
|
||||||
|
currentBox[i] = currentBoxHeader[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int bodyBytesToRead = Math.min(currentTagSize - currentBoxWriteIdx, remainingInputLength);
|
||||||
|
for (int i = 0; i < bodyBytesToRead; i++) {
|
||||||
|
currentBox[currentBoxWriteIdx++] = b[readOffset++];
|
||||||
|
byteCounter++;
|
||||||
|
}
|
||||||
|
remainingInputLength -= bodyBytesToRead;
|
||||||
|
if (currentBoxWriteIdx < currentTagSize) {
|
||||||
|
if (remainingInputLength > 0) {
|
||||||
|
throw new RuntimeException("something is off in the lengths we're trying to read");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
processBox();
|
||||||
|
currentTagType = 0;
|
||||||
|
currentDataSize = 0;
|
||||||
|
currentTagTimestamp = 0;
|
||||||
|
currentTagSize = 0;
|
||||||
|
currentBox = null;
|
||||||
|
currentBoxWriteIdx = 0;
|
||||||
|
Arrays.fill(currentBoxHeader, (byte)0);
|
||||||
|
currentTagPos = byteCounter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void processBox() {
|
||||||
|
|
||||||
|
switch (currentTagType) {
|
||||||
|
case FLV_TAG_VIDEO:
|
||||||
|
int flags = decodeUint8(currentBox, FLV_SIZE_TAGHEADER);
|
||||||
|
boolean isKeyFrame = ((flags >> 4) & 0xf) == 1;
|
||||||
|
if (isKeyFrame) {
|
||||||
|
log.info("Keyframe ts " + currentTagTimestamp + " @ " + currentTagPos + " (" + (currentTagPos + currentTagSize) + ")");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FLV_TAG_SCRIPTDATA:
|
||||||
|
log.info("SCRIPTDATA @ " + currentTagPos + " - len: " + currentTagSize);
|
||||||
|
break;
|
||||||
|
case FLV_TAG_AUDIO:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log.error("Unknown box type: " + currentTagType);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int decodeUint32(byte[] buf, int offset) {
|
||||||
|
int ch1 = buf[offset + 0] & 0xff;
|
||||||
|
int ch2 = buf[offset + 1] & 0xff;
|
||||||
|
int ch3 = buf[offset + 2] & 0xff;
|
||||||
|
int ch4 = buf[offset + 3] & 0xff;
|
||||||
|
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
int decodeUint24(byte[] buf, int offset) {
|
||||||
|
int ch1 = buf[offset + 0] & 0xff;
|
||||||
|
int ch2 = buf[offset + 1] & 0xff;
|
||||||
|
int ch3 = buf[offset + 2] & 0xff;
|
||||||
|
return ((ch1 << 16) + (ch2 << 8) + (ch3 << 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
int decodeUint8(byte[] buf, int offset) {
|
||||||
|
return buf[offset] & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
int decodeTimestamp(byte[] buf, int offset) {
|
||||||
|
int ch1 = buf[offset + 0] & 0xff;
|
||||||
|
int ch2 = buf[offset + 1] & 0xff;
|
||||||
|
int ch3 = buf[offset + 2] & 0xff;
|
||||||
|
int ch4 = buf[offset + 3] & 0xff;
|
||||||
|
return ((ch4 << 24) + (ch1 << 16) + (ch2 << 8) + (ch3 << 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int b) throws IOException {
|
||||||
|
write(new byte[] { (byte)b });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
20
src/test/java/org/forkalsrud/album/video/FlvFilterTest.java
Normal file
20
src/test/java/org/forkalsrud/album/video/FlvFilterTest.java
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.forkalsrud.album.video;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
|
public class FlvFilterTest extends TestCase {
|
||||||
|
|
||||||
|
public void testWrite() throws IOException {
|
||||||
|
|
||||||
|
InputStream is = getClass().getResourceAsStream("VideoAd.flv");
|
||||||
|
OutputStream os = new FlvFilter();
|
||||||
|
IOUtils.copy(is, os);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
BIN
src/test/java/org/forkalsrud/album/video/VideoAd.flv
Normal file
BIN
src/test/java/org/forkalsrud/album/video/VideoAd.flv
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue