Created
July 3, 2017 08:39
-
-
Save enginebai/711dbcdccd78c7cef8c128921ba55a21 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.machipopo.swag.utils; | |
/** | |
* Created by engine on 2017/7/3. | |
*/ | |
import java.io.Closeable; | |
import java.io.File; | |
import java.io.FileInputStream; | |
import java.io.FileOutputStream; | |
import java.io.IOException; | |
import java.nio.ByteBuffer; | |
import java.nio.ByteOrder; | |
import java.nio.channels.FileChannel; | |
public class FastStart { | |
public static boolean sDEBUG = true; | |
private static void safeClose(Closeable closeable) { | |
if (closeable != null) { | |
try { | |
closeable.close(); | |
} | |
catch (IOException e) { | |
printe(e, "Failed to close file: "); | |
} | |
} | |
} | |
/* package */ | |
static long uint32ToLong(int int32) { | |
return int32 & 0x00000000ffffffffL; | |
} | |
/** | |
* Ensures passed uint32 value in long can be represented as Java int. | |
*/ | |
/* package */ | |
static int uint32ToInt(int uint32) throws UnsupportedFileException { | |
if (uint32 < 0) { | |
throw new UnsupportedFileException("uint32 value is too large"); | |
} | |
return uint32; | |
} | |
/** | |
* Ensures passed uint32 value in long can be represented as Java int. | |
*/ | |
/* package */ | |
static int uint32ToInt(long uint32) throws UnsupportedFileException { | |
if (uint32 > Integer.MAX_VALUE || uint32 < 0) { | |
throw new UnsupportedFileException("uint32 value is too large"); | |
} | |
return (int) uint32; | |
} | |
/** | |
* Ensures passed uint64 value can be represented as Java long. | |
*/ | |
/* package */ | |
static long uint64ToLong(long uint64) throws UnsupportedFileException { | |
if (uint64 < 0) throw new UnsupportedFileException("uint64 value is too large"); | |
return uint64; | |
} | |
private static int fourCcToInt(byte[] byteArray) { | |
return ByteBuffer.wrap(byteArray).order(ByteOrder.BIG_ENDIAN).getInt(); | |
} | |
private static void printf(String format, Object... args) { | |
if (sDEBUG) System.err.println("QtFastStart: " + String.format(format, args)); | |
} | |
private static void printe(Throwable e, String format, Object... args) { | |
printf(format, args); | |
if (sDEBUG) e.printStackTrace(); | |
} | |
private static boolean readAndFill(FileChannel infile, ByteBuffer buffer) throws IOException { | |
buffer.clear(); | |
int size = infile.read(buffer); | |
buffer.flip(); | |
return size == buffer.capacity(); | |
} | |
private static boolean readAndFill(FileChannel infile, ByteBuffer buffer, long position) throws IOException { | |
buffer.clear(); | |
int size = infile.read(buffer, position); | |
buffer.flip(); | |
return size == buffer.capacity(); | |
} | |
/* top level atoms */ | |
private static final int FREE_ATOM = fourCcToInt(new byte[]{'f', 'r', 'e', 'e'}); | |
private static final int JUNK_ATOM = fourCcToInt(new byte[]{'j', 'u', 'n', 'k'}); | |
private static final int MDAT_ATOM = fourCcToInt(new byte[]{'m', 'd', 'a', 't'}); | |
private static final int MOOV_ATOM = fourCcToInt(new byte[]{'m', 'o', 'o', 'v'}); | |
private static final int PNOT_ATOM = fourCcToInt(new byte[]{'p', 'n', 'o', 't'}); | |
private static final int SKIP_ATOM = fourCcToInt(new byte[]{'s', 'k', 'i', 'p'}); | |
private static final int WIDE_ATOM = fourCcToInt(new byte[]{'w', 'i', 'd', 'e'}); | |
private static final int PICT_ATOM = fourCcToInt(new byte[]{'P', 'I', 'C', 'T'}); | |
private static final int FTYP_ATOM = fourCcToInt(new byte[]{'f', 't', 'y', 'p'}); | |
private static final int UUID_ATOM = fourCcToInt(new byte[]{'u', 'u', 'i', 'd'}); | |
private static final int CMOV_ATOM = fourCcToInt(new byte[]{'c', 'm', 'o', 'v'}); | |
private static final int STCO_ATOM = fourCcToInt(new byte[]{'s', 't', 'c', 'o'}); | |
private static final int CO64_ATOM = fourCcToInt(new byte[]{'c', 'o', '6', '4'}); | |
private static final int ATOM_PREAMBLE_SIZE = 8; | |
/** | |
* @param in Input file. | |
* @param out Output file. | |
* @return false if input file is already fast start. | |
* @throws IOException | |
*/ | |
public static boolean fastStart(File in, File out) throws IOException, MalformedFileException, UnsupportedFileException { | |
boolean ret = false; | |
FileInputStream inStream = null; | |
FileOutputStream outStream = null; | |
try { | |
inStream = new FileInputStream(in); | |
FileChannel infile = inStream.getChannel(); | |
outStream = new FileOutputStream(out); | |
FileChannel outfile = outStream.getChannel(); | |
return ret = fastStartImpl(infile, outfile); | |
} | |
finally { | |
safeClose(inStream); | |
safeClose(outStream); | |
if (!ret) { | |
out.delete(); | |
} | |
} | |
} | |
private static boolean fastStartImpl(FileChannel infile, FileChannel outfile) throws IOException, MalformedFileException, UnsupportedFileException { | |
ByteBuffer atomBytes = ByteBuffer.allocate(ATOM_PREAMBLE_SIZE).order(ByteOrder.BIG_ENDIAN); | |
int atomType = 0; | |
long atomSize = 0; // uint64_t | |
long lastOffset; | |
ByteBuffer moovAtom; | |
ByteBuffer ftypAtom = null; | |
// uint64_t, but assuming it is in int32 range. It is reasonable as int max is around 2GB. Such large moov is unlikely, yet unallocatable :). | |
int moovAtomSize; | |
long startOffset = 0; | |
// traverse through the atoms in the file to make sure that 'moov' is at the end | |
while (readAndFill(infile, atomBytes)) { | |
atomSize = uint32ToLong(atomBytes.getInt()); // uint32 | |
atomType = atomBytes.getInt(); // representing uint32_t in signed int | |
// keep ftyp atom | |
if (atomType == FTYP_ATOM) { | |
int ftypAtomSize = uint32ToInt(atomSize); // XXX: assume in range of int32_t | |
ftypAtom = ByteBuffer.allocate(ftypAtomSize).order(ByteOrder.BIG_ENDIAN); | |
atomBytes.rewind(); | |
ftypAtom.put(atomBytes); | |
if (infile.read(ftypAtom) < ftypAtomSize - ATOM_PREAMBLE_SIZE) break; | |
ftypAtom.flip(); | |
startOffset = infile.position(); // after ftyp atom | |
} else { | |
if (atomSize == 1) { | |
/* 64-bit special case */ | |
atomBytes.clear(); | |
if (!readAndFill(infile, atomBytes)) break; | |
atomSize = uint64ToLong(atomBytes.getLong()); // XXX: assume in range of int64_t | |
infile.position(infile.position() + atomSize - ATOM_PREAMBLE_SIZE * 2); // seek | |
} else { | |
infile.position(infile.position() + atomSize - ATOM_PREAMBLE_SIZE); // seek | |
} | |
} | |
if (sDEBUG) printf("%c%c%c%c %10d %d", | |
(atomType >> 24) & 255, | |
(atomType >> 16) & 255, | |
(atomType >> 8) & 255, | |
(atomType >> 0) & 255, | |
infile.position() - atomSize, | |
atomSize); | |
if ((atomType != FREE_ATOM) | |
&& (atomType != JUNK_ATOM) | |
&& (atomType != MDAT_ATOM) | |
&& (atomType != MOOV_ATOM) | |
&& (atomType != PNOT_ATOM) | |
&& (atomType != SKIP_ATOM) | |
&& (atomType != WIDE_ATOM) | |
&& (atomType != PICT_ATOM) | |
&& (atomType != UUID_ATOM) | |
&& (atomType != FTYP_ATOM)) { | |
printf("encountered non-QT top-level atom (is this a QuickTime file?)"); | |
break; | |
} | |
/* The atom header is 8 (or 16 bytes), if the atom size (which | |
* includes these 8 or 16 bytes) is less than that, we won't be | |
* able to continue scanning sensibly after this atom, so break. */ | |
if (atomSize < 8) | |
break; | |
} | |
// if (atomType != MOOV_ATOM) { | |
// printf("last atom in file was not a moov atom"); | |
// return false; | |
// } | |
// moov atom was, in fact, the last atom in the chunk; load the whole moov atom | |
// atomSize is uint64, but for moov uint32 should be stored. | |
// XXX: assuming moov atomSize <= max vaue of int32 | |
moovAtomSize = uint32ToInt(atomSize); | |
lastOffset = infile.size() - moovAtomSize; // NOTE: assuming no extra data after moov, as qt-faststart.c | |
moovAtom = ByteBuffer.allocate(moovAtomSize).order(ByteOrder.BIG_ENDIAN); | |
if (!readAndFill(infile, moovAtom, lastOffset)) { | |
throw new MalformedFileException("failed to read moov atom"); | |
} | |
// this utility does not support compressed atoms yet, so disqualify files with compressed QT atoms | |
if (moovAtom.getInt(12) == CMOV_ATOM) { | |
throw new UnsupportedFileException("this utility does not support compressed moov atoms yet"); | |
} | |
// crawl through the moov chunk in search of stco or co64 atoms | |
while (moovAtom.remaining() >= 8) { | |
int atomHead = moovAtom.position(); | |
atomType = moovAtom.getInt(atomHead + 4); // representing uint32_t in signed int | |
if (!(atomType == STCO_ATOM || atomType == CO64_ATOM)) { | |
moovAtom.position(moovAtom.position() + 1); | |
continue; | |
} | |
atomSize = uint32ToLong(moovAtom.getInt(atomHead)); // uint32 | |
if (atomSize > moovAtom.remaining()) { | |
throw new MalformedFileException("bad atom size"); | |
} | |
moovAtom.position(atomHead + 12); // skip size (4 bytes), type (4 bytes), version (1 byte) and flags (3 bytes) | |
if (moovAtom.remaining() < 4) { | |
throw new MalformedFileException("malformed atom"); | |
} | |
// uint32_t, but assuming moovAtomSize is in int32 range, so this will be in int32 range | |
int offsetCount = uint32ToInt(moovAtom.getInt()); | |
if (atomType == STCO_ATOM) { | |
printf("patching stco atom..."); | |
if (moovAtom.remaining() < offsetCount * 4) { | |
throw new MalformedFileException("bad atom size/element count"); | |
} | |
for (int i = 0; i < offsetCount; i++) { | |
int currentOffset = moovAtom.getInt(moovAtom.position()); | |
int newOffset = currentOffset + moovAtomSize; // calculate uint32 in int, bitwise addition | |
// current 0xffffffff => new 0x00000000 (actual >= 0x0000000100000000L) | |
if (currentOffset < 0 && newOffset >= 0) { | |
throw new UnsupportedFileException("This is bug in original qt-faststart.c: " | |
+ "stco atom should be extended to co64 atom as new offset value overflows uint32, " | |
+ "but is not implemented."); | |
} | |
moovAtom.putInt(newOffset); | |
} | |
} else if (atomType == CO64_ATOM) { | |
printf("patching co64 atom..."); | |
if (moovAtom.remaining() < offsetCount * 8) { | |
throw new MalformedFileException("bad atom size/element count"); | |
} | |
for (int i = 0; i < offsetCount; i++) { | |
long currentOffset = moovAtom.getLong(moovAtom.position()); | |
moovAtom.putLong(currentOffset + moovAtomSize); // calculate uint64 in long, bitwise addition | |
} | |
} | |
} | |
infile.position(startOffset); // seek after ftyp atom | |
if (ftypAtom != null) { | |
// dump the same ftyp atom | |
printf("writing ftyp atom..."); | |
ftypAtom.rewind(); | |
outfile.write(ftypAtom); | |
} | |
// dump the new moov atom | |
printf("writing moov atom..."); | |
moovAtom.rewind(); | |
outfile.write(moovAtom); | |
// copy the remainder of the infile, from offset 0 -> (lastOffset - startOffset) - 1 | |
printf("copying rest of file..."); | |
infile.transferTo(startOffset, lastOffset - startOffset, outfile); | |
return true; | |
} | |
public static class QtFastStartException extends Exception { | |
private QtFastStartException(String detailMessage) { | |
super(detailMessage); | |
} | |
} | |
public static class MalformedFileException extends QtFastStartException { | |
private MalformedFileException(String detailMessage) { | |
super(detailMessage); | |
} | |
} | |
public static class UnsupportedFileException extends QtFastStartException { | |
private UnsupportedFileException(String detailMessage) { | |
super(detailMessage); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment