The beginning of a filter to patch up the video metadata after the first

run.
This commit is contained in:
Knut Forkalsrud 2011-07-02 00:58:49 -07:00
parent 74f08f44b0
commit aebec22299
3 changed files with 190 additions and 0 deletions

View 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 });
}
}

View 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);
}
}

Binary file not shown.