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