mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2024-12-23 08:30:44 +00:00
Use Checkstyle for org.schabi.newpipe.streams as well
This commit is contained in:
parent
55480c8290
commit
b6c6dc7282
@ -99,7 +99,6 @@ task runCheckstyle(type: Checkstyle) {
|
||||
exclude '**/R.java'
|
||||
exclude '**/BuildConfig.java'
|
||||
exclude 'main/java/us/shandian/giga/**'
|
||||
exclude 'main/java/org/schabi/newpipe/streams/**'
|
||||
|
||||
// empty classpath
|
||||
classpath = files()
|
||||
|
@ -10,13 +10,12 @@ import java.io.InputStream;
|
||||
* @author kapodamy
|
||||
*/
|
||||
public class DataReader {
|
||||
public static final int SHORT_SIZE = 2;
|
||||
public static final int LONG_SIZE = 8;
|
||||
public static final int INTEGER_SIZE = 4;
|
||||
public static final int FLOAT_SIZE = 4;
|
||||
|
||||
public final static int SHORT_SIZE = 2;
|
||||
public final static int LONG_SIZE = 8;
|
||||
public final static int INTEGER_SIZE = 4;
|
||||
public final static int FLOAT_SIZE = 4;
|
||||
|
||||
private final static int BUFFER_SIZE = 128 * 1024;// 128 KiB
|
||||
private static final int BUFFER_SIZE = 128 * 1024; // 128 KiB
|
||||
|
||||
private long position = 0;
|
||||
private final SharpStream stream;
|
||||
@ -24,7 +23,7 @@ public class DataReader {
|
||||
private InputStream view;
|
||||
private int viewSize;
|
||||
|
||||
public DataReader(SharpStream stream) {
|
||||
public DataReader(final SharpStream stream) {
|
||||
this.stream = stream;
|
||||
this.readOffset = this.readBuffer.length;
|
||||
}
|
||||
@ -74,6 +73,7 @@ public class DataReader {
|
||||
return value & 0xffffffffL;
|
||||
}
|
||||
|
||||
|
||||
public short readShort() throws IOException {
|
||||
primitiveRead(SHORT_SIZE);
|
||||
return (short) (primitive[0] << 8 | primitive[1]);
|
||||
@ -86,11 +86,11 @@ public class DataReader {
|
||||
return high << 32 | low;
|
||||
}
|
||||
|
||||
public int read(byte[] buffer) throws IOException {
|
||||
public int read(final byte[] buffer) throws IOException {
|
||||
return read(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
public int read(byte[] buffer, int offset, int count) throws IOException {
|
||||
public int read(final byte[] buffer, int offset, int count) throws IOException {
|
||||
if (readCount < 0) {
|
||||
return -1;
|
||||
}
|
||||
@ -135,7 +135,7 @@ public class DataReader {
|
||||
stream.rewind();
|
||||
|
||||
if ((position - viewSize) > 0) {
|
||||
viewSize = 0;// drop view
|
||||
viewSize = 0; // drop view
|
||||
} else {
|
||||
viewSize += position;
|
||||
}
|
||||
@ -157,7 +157,7 @@ public class DataReader {
|
||||
* @param size the size of the view
|
||||
* @return the view
|
||||
*/
|
||||
public InputStream getView(int size) {
|
||||
public InputStream getView(final int size) {
|
||||
if (view == null) {
|
||||
view = new InputStream() {
|
||||
@Override
|
||||
@ -173,12 +173,13 @@ public class DataReader {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buffer) throws IOException {
|
||||
public int read(final byte[] buffer) throws IOException {
|
||||
return read(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buffer, int offset, int count) throws IOException {
|
||||
public int read(final byte[] buffer, final int offset, final int count)
|
||||
throws IOException {
|
||||
if (viewSize < 1) {
|
||||
return -1;
|
||||
}
|
||||
@ -190,7 +191,7 @@ public class DataReader {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long amount) throws IOException {
|
||||
public long skip(final long amount) throws IOException {
|
||||
if (viewSize < 1) {
|
||||
return 0;
|
||||
}
|
||||
@ -224,16 +225,18 @@ public class DataReader {
|
||||
|
||||
private final short[] primitive = new short[LONG_SIZE];
|
||||
|
||||
private void primitiveRead(int amount) throws IOException {
|
||||
private void primitiveRead(final int amount) throws IOException {
|
||||
byte[] buffer = new byte[amount];
|
||||
int read = read(buffer, 0, amount);
|
||||
|
||||
if (read != amount) {
|
||||
throw new EOFException("Truncated stream, missing " + String.valueOf(amount - read) + " bytes");
|
||||
throw new EOFException("Truncated stream, missing "
|
||||
+ String.valueOf(amount - read) + " bytes");
|
||||
}
|
||||
|
||||
for (int i = 0; i < amount; i++) {
|
||||
primitive[i] = (short) (buffer[i] & 0xFF);// the "byte" data type in java is signed and is very annoying
|
||||
// the "byte" data type in java is signed and is very annoying
|
||||
primitive[i] = (short) (buffer[i] & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
@ -256,5 +259,4 @@ public class DataReader {
|
||||
|
||||
return readCount < 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import java.util.NoSuchElementException;
|
||||
* @author kapodamy
|
||||
*/
|
||||
public class Mp4DashReader {
|
||||
|
||||
private static final int ATOM_MOOF = 0x6D6F6F66;
|
||||
private static final int ATOM_MFHD = 0x6D666864;
|
||||
private static final int ATOM_TRAF = 0x74726166;
|
||||
@ -50,7 +49,6 @@ public class Mp4DashReader {
|
||||
private static final int HANDLER_SOUN = 0x736F756E;
|
||||
private static final int HANDLER_SUBT = 0x73756274;
|
||||
|
||||
|
||||
private final DataReader stream;
|
||||
|
||||
private Mp4Track[] tracks = null;
|
||||
@ -68,7 +66,7 @@ public class Mp4DashReader {
|
||||
Audio, Video, Subtitles, Other
|
||||
}
|
||||
|
||||
public Mp4DashReader(SharpStream source) {
|
||||
public Mp4DashReader(final SharpStream source) {
|
||||
this.stream = new DataReader(source);
|
||||
}
|
||||
|
||||
@ -78,14 +76,15 @@ public class Mp4DashReader {
|
||||
}
|
||||
|
||||
box = readBox(ATOM_FTYP);
|
||||
brands = parse_ftyp(box);
|
||||
brands = parseFtyp(box);
|
||||
switch (brands[0]) {
|
||||
case BRAND_DASH:
|
||||
case BRAND_ISO5:// ¿why not?
|
||||
break;
|
||||
default:
|
||||
throw new NoSuchElementException(
|
||||
"Not a MPEG-4 DASH container, major brand is not 'dash' or 'iso5' is " + boxName(brands[0])
|
||||
"Not a MPEG-4 DASH container, major brand is not 'dash' or 'iso5' is "
|
||||
+ boxName(brands[0])
|
||||
);
|
||||
}
|
||||
|
||||
@ -98,7 +97,7 @@ public class Mp4DashReader {
|
||||
|
||||
switch (box.type) {
|
||||
case ATOM_MOOV:
|
||||
moov = parse_moov(box);
|
||||
moov = parseMoov(box);
|
||||
break;
|
||||
case ATOM_SIDX:
|
||||
break;
|
||||
@ -117,10 +116,10 @@ public class Mp4DashReader {
|
||||
tracks[i] = new Mp4Track();
|
||||
tracks[i].trak = moov.trak[i];
|
||||
|
||||
if (moov.mvex_trex != null) {
|
||||
for (Trex mvex_trex : moov.mvex_trex) {
|
||||
if (tracks[i].trak.tkhd.trackId == mvex_trex.trackId) {
|
||||
tracks[i].trex = mvex_trex;
|
||||
if (moov.mvexTrex != null) {
|
||||
for (Trex mvexTrex : moov.mvexTrex) {
|
||||
if (tracks[i].trak.tkhd.trackId == mvexTrex.trackId) {
|
||||
tracks[i].trex = mvexTrex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -144,7 +143,7 @@ public class Mp4DashReader {
|
||||
backupBox = box;
|
||||
}
|
||||
|
||||
Mp4Track selectTrack(int index) {
|
||||
Mp4Track selectTrack(final int index) {
|
||||
selectedTrack = index;
|
||||
return tracks[index];
|
||||
}
|
||||
@ -179,7 +178,7 @@ public class Mp4DashReader {
|
||||
Box traf;
|
||||
while ((traf = untilBox(tmp, ATOM_TRAF)) != null) {
|
||||
Box tfhd = readBox(ATOM_TFHD);
|
||||
if (parse_tfhd(tracks[selectedTrack].trak.tkhd.trackId) != null) {
|
||||
if (parseTfhd(tracks[selectedTrack].trak.tkhd.trackId) != null) {
|
||||
count++;
|
||||
break;
|
||||
}
|
||||
@ -196,7 +195,9 @@ public class Mp4DashReader {
|
||||
}
|
||||
|
||||
public int[] getBrands() {
|
||||
if (brands == null) throw new IllegalStateException("Not parsed");
|
||||
if (brands == null) {
|
||||
throw new IllegalStateException("Not parsed");
|
||||
}
|
||||
return brands;
|
||||
}
|
||||
|
||||
@ -219,7 +220,7 @@ public class Mp4DashReader {
|
||||
return tracks;
|
||||
}
|
||||
|
||||
public Mp4DashChunk getNextChunk(boolean infoOnly) throws IOException {
|
||||
public Mp4DashChunk getNextChunk(final boolean infoOnly) throws IOException {
|
||||
Mp4Track track = tracks[selectedTrack];
|
||||
|
||||
while (stream.available()) {
|
||||
@ -240,27 +241,31 @@ public class Mp4DashReader {
|
||||
throw new IOException("moof found without mdat");
|
||||
}
|
||||
|
||||
moof = parse_moof(box, track.trak.tkhd.trackId);
|
||||
moof = parseMoof(box, track.trak.tkhd.trackId);
|
||||
|
||||
if (moof.traf != null) {
|
||||
|
||||
if (hasFlag(moof.traf.trun.bFlags, 0x0001)) {
|
||||
moof.traf.trun.dataOffset -= box.size + 8;
|
||||
if (moof.traf.trun.dataOffset < 0) {
|
||||
throw new IOException("trun box has wrong data offset, points outside of concurrent mdat box");
|
||||
throw new IOException("trun box has wrong data offset, "
|
||||
+ "points outside of concurrent mdat box");
|
||||
}
|
||||
}
|
||||
|
||||
if (moof.traf.trun.chunkSize < 1) {
|
||||
if (hasFlag(moof.traf.tfhd.bFlags, 0x10)) {
|
||||
moof.traf.trun.chunkSize = moof.traf.tfhd.defaultSampleSize * moof.traf.trun.entryCount;
|
||||
moof.traf.trun.chunkSize = moof.traf.tfhd.defaultSampleSize
|
||||
* moof.traf.trun.entryCount;
|
||||
} else {
|
||||
moof.traf.trun.chunkSize = (int) (box.size - 8);
|
||||
}
|
||||
}
|
||||
if (!hasFlag(moof.traf.trun.bFlags, 0x900) && moof.traf.trun.chunkDuration == 0) {
|
||||
if (!hasFlag(moof.traf.trun.bFlags, 0x900)
|
||||
&& moof.traf.trun.chunkDuration == 0) {
|
||||
if (hasFlag(moof.traf.tfhd.bFlags, 0x20)) {
|
||||
moof.traf.trun.chunkDuration = moof.traf.tfhd.defaultSampleDuration * moof.traf.trun.entryCount;
|
||||
moof.traf.trun.chunkDuration = moof.traf.tfhd.defaultSampleDuration
|
||||
* moof.traf.trun.entryCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -272,7 +277,7 @@ public class Mp4DashReader {
|
||||
|
||||
if (moof.traf == null) {
|
||||
moof = null;
|
||||
continue;// find another chunk
|
||||
continue; // find another chunk
|
||||
}
|
||||
|
||||
Mp4DashChunk chunk = new Mp4DashChunk();
|
||||
@ -292,17 +297,15 @@ public class Mp4DashReader {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static boolean hasFlag(int flags, int mask) {
|
||||
public static boolean hasFlag(final int flags, final int mask) {
|
||||
return (flags & mask) == mask;
|
||||
}
|
||||
|
||||
private String boxName(Box ref) {
|
||||
private String boxName(final Box ref) {
|
||||
return boxName(ref.type);
|
||||
}
|
||||
|
||||
private String boxName(int type) {
|
||||
private String boxName(final int type) {
|
||||
try {
|
||||
return new String(ByteBuffer.allocate(4).putInt(type).array(), "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
@ -323,15 +326,16 @@ public class Mp4DashReader {
|
||||
return b;
|
||||
}
|
||||
|
||||
private Box readBox(int expected) throws IOException {
|
||||
private Box readBox(final int expected) throws IOException {
|
||||
Box b = readBox();
|
||||
if (b.type != expected) {
|
||||
throw new NoSuchElementException("expected " + boxName(expected) + " found " + boxName(b));
|
||||
throw new NoSuchElementException("expected " + boxName(expected)
|
||||
+ " found " + boxName(b));
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
private byte[] readFullBox(Box ref) throws IOException {
|
||||
private byte[] readFullBox(final Box ref) throws IOException {
|
||||
// full box reading is limited to 2 GiB, and should be enough
|
||||
int size = (int) ref.size;
|
||||
|
||||
@ -342,15 +346,14 @@ public class Mp4DashReader {
|
||||
int read = size - 8;
|
||||
|
||||
if (stream.read(buffer.array(), 8, read) != read) {
|
||||
throw new EOFException(
|
||||
String.format("EOF reached in box: type=%s offset=%s size=%s", boxName(ref.type), ref.offset, ref.size)
|
||||
);
|
||||
throw new EOFException(String.format("EOF reached in box: type=%s offset=%s size=%s",
|
||||
boxName(ref.type), ref.offset, ref.size));
|
||||
}
|
||||
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
private void ensure(Box ref) throws IOException {
|
||||
private void ensure(final Box ref) throws IOException {
|
||||
long skip = ref.offset + ref.size - stream.position();
|
||||
|
||||
if (skip == 0) {
|
||||
@ -365,7 +368,7 @@ public class Mp4DashReader {
|
||||
stream.skipBytes((int) skip);
|
||||
}
|
||||
|
||||
private Box untilBox(Box ref, int... expected) throws IOException {
|
||||
private Box untilBox(final Box ref, final int... expected) throws IOException {
|
||||
Box b;
|
||||
while (stream.position() < (ref.offset + ref.size)) {
|
||||
b = readBox();
|
||||
@ -380,7 +383,7 @@ public class Mp4DashReader {
|
||||
return null;
|
||||
}
|
||||
|
||||
private Box untilAnyBox(Box ref) throws IOException {
|
||||
private Box untilAnyBox(final Box ref) throws IOException {
|
||||
if (stream.position() >= (ref.offset + ref.size)) {
|
||||
return null;
|
||||
}
|
||||
@ -388,17 +391,15 @@ public class Mp4DashReader {
|
||||
return readBox();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Moof parse_moof(Box ref, int trackId) throws IOException {
|
||||
private Moof parseMoof(final Box ref, final int trackId) throws IOException {
|
||||
Moof obj = new Moof();
|
||||
|
||||
Box b = readBox(ATOM_MFHD);
|
||||
obj.mfhd_SequenceNumber = parse_mfhd();
|
||||
obj.mfhdSequenceNumber = parseMfhd();
|
||||
ensure(b);
|
||||
|
||||
while ((b = untilBox(ref, ATOM_TRAF)) != null) {
|
||||
obj.traf = parse_traf(b, trackId);
|
||||
obj.traf = parseTraf(b, trackId);
|
||||
ensure(b);
|
||||
|
||||
if (obj.traf != null) {
|
||||
@ -409,7 +410,7 @@ public class Mp4DashReader {
|
||||
return obj;
|
||||
}
|
||||
|
||||
private int parse_mfhd() throws IOException {
|
||||
private int parseMfhd() throws IOException {
|
||||
// version
|
||||
// flags
|
||||
stream.skipBytes(4);
|
||||
@ -417,11 +418,11 @@ public class Mp4DashReader {
|
||||
return stream.readInt();
|
||||
}
|
||||
|
||||
private Traf parse_traf(Box ref, int trackId) throws IOException {
|
||||
private Traf parseTraf(final Box ref, final int trackId) throws IOException {
|
||||
Traf traf = new Traf();
|
||||
|
||||
Box b = readBox(ATOM_TFHD);
|
||||
traf.tfhd = parse_tfhd(trackId);
|
||||
traf.tfhd = parseTfhd(trackId);
|
||||
ensure(b);
|
||||
|
||||
if (traf.tfhd == null) {
|
||||
@ -431,18 +432,18 @@ public class Mp4DashReader {
|
||||
b = untilBox(ref, ATOM_TRUN, ATOM_TFDT);
|
||||
|
||||
if (b.type == ATOM_TFDT) {
|
||||
traf.tfdt = parse_tfdt();
|
||||
traf.tfdt = parseTfdt();
|
||||
ensure(b);
|
||||
b = readBox(ATOM_TRUN);
|
||||
}
|
||||
|
||||
traf.trun = parse_trun();
|
||||
traf.trun = parseTrun();
|
||||
ensure(b);
|
||||
|
||||
return traf;
|
||||
}
|
||||
|
||||
private Tfhd parse_tfhd(int trackId) throws IOException {
|
||||
private Tfhd parseTfhd(final int trackId) throws IOException {
|
||||
Tfhd obj = new Tfhd();
|
||||
|
||||
obj.bFlags = stream.readInt();
|
||||
@ -471,31 +472,31 @@ public class Mp4DashReader {
|
||||
return obj;
|
||||
}
|
||||
|
||||
private long parse_tfdt() throws IOException {
|
||||
private long parseTfdt() throws IOException {
|
||||
int version = stream.read();
|
||||
stream.skipBytes(3);// flags
|
||||
stream.skipBytes(3); // flags
|
||||
return version == 0 ? stream.readUnsignedInt() : stream.readLong();
|
||||
}
|
||||
|
||||
private Trun parse_trun() throws IOException {
|
||||
private Trun parseTrun() throws IOException {
|
||||
Trun obj = new Trun();
|
||||
obj.bFlags = stream.readInt();
|
||||
obj.entryCount = stream.readInt();// unsigned int
|
||||
obj.entryCount = stream.readInt(); // unsigned int
|
||||
|
||||
obj.entries_rowSize = 0;
|
||||
obj.entriesRowSize = 0;
|
||||
if (hasFlag(obj.bFlags, 0x0100)) {
|
||||
obj.entries_rowSize += 4;
|
||||
obj.entriesRowSize += 4;
|
||||
}
|
||||
if (hasFlag(obj.bFlags, 0x0200)) {
|
||||
obj.entries_rowSize += 4;
|
||||
obj.entriesRowSize += 4;
|
||||
}
|
||||
if (hasFlag(obj.bFlags, 0x0400)) {
|
||||
obj.entries_rowSize += 4;
|
||||
obj.entriesRowSize += 4;
|
||||
}
|
||||
if (hasFlag(obj.bFlags, 0x0800)) {
|
||||
obj.entries_rowSize += 4;
|
||||
obj.entriesRowSize += 4;
|
||||
}
|
||||
obj.bEntries = new byte[obj.entries_rowSize * obj.entryCount];
|
||||
obj.bEntries = new byte[obj.entriesRowSize * obj.entryCount];
|
||||
|
||||
if (hasFlag(obj.bFlags, 0x0001)) {
|
||||
obj.dataOffset = stream.readInt();
|
||||
@ -524,23 +525,24 @@ public class Mp4DashReader {
|
||||
return obj;
|
||||
}
|
||||
|
||||
private int[] parse_ftyp(Box ref) throws IOException {
|
||||
private int[] parseFtyp(final Box ref) throws IOException {
|
||||
int i = 0;
|
||||
int[] list = new int[(int) ((ref.offset + ref.size - stream.position() - 4) / 4)];
|
||||
|
||||
list[i++] = stream.readInt();// major brand
|
||||
list[i++] = stream.readInt(); // major brand
|
||||
|
||||
stream.skipBytes(4);// minor version
|
||||
stream.skipBytes(4); // minor version
|
||||
|
||||
for (; i < list.length; i++)
|
||||
list[i] = stream.readInt();// compatible brands
|
||||
for (; i < list.length; i++) {
|
||||
list[i] = stream.readInt(); // compatible brands
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private Mvhd parse_mvhd() throws IOException {
|
||||
private Mvhd parseMvhd() throws IOException {
|
||||
int version = stream.read();
|
||||
stream.skipBytes(3);// flags
|
||||
stream.skipBytes(3); // flags
|
||||
|
||||
// creation entries_time
|
||||
// modification entries_time
|
||||
@ -564,7 +566,7 @@ public class Mp4DashReader {
|
||||
return obj;
|
||||
}
|
||||
|
||||
private Tkhd parse_tkhd() throws IOException {
|
||||
private Tkhd parseTkhd() throws IOException {
|
||||
int version = stream.read();
|
||||
|
||||
Tkhd obj = new Tkhd();
|
||||
@ -576,17 +578,17 @@ public class Mp4DashReader {
|
||||
|
||||
obj.trackId = stream.readInt();
|
||||
|
||||
stream.skipBytes(4);// reserved
|
||||
stream.skipBytes(4); // reserved
|
||||
|
||||
obj.duration = version == 0 ? stream.readUnsignedInt() : stream.readLong();
|
||||
|
||||
stream.skipBytes(2 * 4);// reserved
|
||||
stream.skipBytes(2 * 4); // reserved
|
||||
|
||||
obj.bLayer = stream.readShort();
|
||||
obj.bAlternateGroup = stream.readShort();
|
||||
obj.bVolume = stream.readShort();
|
||||
|
||||
stream.skipBytes(2);// reserved
|
||||
stream.skipBytes(2); // reserved
|
||||
|
||||
obj.matrix = new byte[9 * 4];
|
||||
stream.read(obj.matrix);
|
||||
@ -597,20 +599,20 @@ public class Mp4DashReader {
|
||||
return obj;
|
||||
}
|
||||
|
||||
private Trak parse_trak(Box ref) throws IOException {
|
||||
private Trak parseTrak(final Box ref) throws IOException {
|
||||
Trak trak = new Trak();
|
||||
|
||||
Box b = readBox(ATOM_TKHD);
|
||||
trak.tkhd = parse_tkhd();
|
||||
trak.tkhd = parseTkhd();
|
||||
ensure(b);
|
||||
|
||||
while ((b = untilBox(ref, ATOM_MDIA, ATOM_EDTS)) != null) {
|
||||
switch (b.type) {
|
||||
case ATOM_MDIA:
|
||||
trak.mdia = parse_mdia(b);
|
||||
trak.mdia = parseMdia(b);
|
||||
break;
|
||||
case ATOM_EDTS:
|
||||
trak.edst_elst = parse_edts(b);
|
||||
trak.edstElst = parseEdts(b);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -620,7 +622,7 @@ public class Mp4DashReader {
|
||||
return trak;
|
||||
}
|
||||
|
||||
private Mdia parse_mdia(Box ref) throws IOException {
|
||||
private Mdia parseMdia(final Box ref) throws IOException {
|
||||
Mdia obj = new Mdia();
|
||||
|
||||
Box b;
|
||||
@ -633,13 +635,13 @@ public class Mp4DashReader {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(obj.mdhd);
|
||||
byte version = buffer.get(8);
|
||||
buffer.position(12 + ((version == 0 ? 4 : 8) * 2));
|
||||
obj.mdhd_timeScale = buffer.getInt();
|
||||
obj.mdhdTimeScale = buffer.getInt();
|
||||
break;
|
||||
case ATOM_HDLR:
|
||||
obj.hdlr = parse_hdlr(b);
|
||||
obj.hdlr = parseHdlr(b);
|
||||
break;
|
||||
case ATOM_MINF:
|
||||
obj.minf = parse_minf(b);
|
||||
obj.minf = parseMinf(b);
|
||||
break;
|
||||
}
|
||||
ensure(b);
|
||||
@ -648,7 +650,7 @@ public class Mp4DashReader {
|
||||
return obj;
|
||||
}
|
||||
|
||||
private Hdlr parse_hdlr(Box ref) throws IOException {
|
||||
private Hdlr parseHdlr(final Box ref) throws IOException {
|
||||
// version
|
||||
// flags
|
||||
stream.skipBytes(4);
|
||||
@ -666,10 +668,10 @@ public class Mp4DashReader {
|
||||
return obj;
|
||||
}
|
||||
|
||||
private Moov parse_moov(Box ref) throws IOException {
|
||||
private Moov parseMoov(final Box ref) throws IOException {
|
||||
Box b = readBox(ATOM_MVHD);
|
||||
Moov moov = new Moov();
|
||||
moov.mvhd = parse_mvhd();
|
||||
moov.mvhd = parseMvhd();
|
||||
ensure(b);
|
||||
|
||||
ArrayList<Trak> tmp = new ArrayList<>((int) moov.mvhd.nextTrackId);
|
||||
@ -677,10 +679,10 @@ public class Mp4DashReader {
|
||||
|
||||
switch (b.type) {
|
||||
case ATOM_TRAK:
|
||||
tmp.add(parse_trak(b));
|
||||
tmp.add(parseTrak(b));
|
||||
break;
|
||||
case ATOM_MVEX:
|
||||
moov.mvex_trex = parse_mvex(b, (int) moov.mvhd.nextTrackId);
|
||||
moov.mvexTrex = parseMvex(b, (int) moov.mvhd.nextTrackId);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -692,19 +694,19 @@ public class Mp4DashReader {
|
||||
return moov;
|
||||
}
|
||||
|
||||
private Trex[] parse_mvex(Box ref, int possibleTrackCount) throws IOException {
|
||||
private Trex[] parseMvex(final Box ref, final int possibleTrackCount) throws IOException {
|
||||
ArrayList<Trex> tmp = new ArrayList<>(possibleTrackCount);
|
||||
|
||||
Box b;
|
||||
while ((b = untilBox(ref, ATOM_TREX)) != null) {
|
||||
tmp.add(parse_trex());
|
||||
tmp.add(parseTrex());
|
||||
ensure(b);
|
||||
}
|
||||
|
||||
return tmp.toArray(new Trex[0]);
|
||||
}
|
||||
|
||||
private Trex parse_trex() throws IOException {
|
||||
private Trex parseTrex() throws IOException {
|
||||
// version
|
||||
// flags
|
||||
stream.skipBytes(4);
|
||||
@ -719,7 +721,7 @@ public class Mp4DashReader {
|
||||
return obj;
|
||||
}
|
||||
|
||||
private Elst parse_edts(Box ref) throws IOException {
|
||||
private Elst parseEdts(final Box ref) throws IOException {
|
||||
Box b = untilBox(ref, ATOM_ELST);
|
||||
if (b == null) {
|
||||
return null;
|
||||
@ -728,22 +730,22 @@ public class Mp4DashReader {
|
||||
Elst obj = new Elst();
|
||||
|
||||
boolean v1 = stream.read() == 1;
|
||||
stream.skipBytes(3);// flags
|
||||
stream.skipBytes(3); // flags
|
||||
|
||||
int entryCount = stream.readInt();
|
||||
if (entryCount < 1) {
|
||||
obj.bMediaRate = 0x00010000;// default media rate (1.0)
|
||||
obj.bMediaRate = 0x00010000; // default media rate (1.0)
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (v1) {
|
||||
stream.skipBytes(DataReader.LONG_SIZE);// segment duration
|
||||
obj.MediaTime = stream.readLong();
|
||||
stream.skipBytes(DataReader.LONG_SIZE); // segment duration
|
||||
obj.mediaTime = stream.readLong();
|
||||
// ignore all remain entries
|
||||
stream.skipBytes((entryCount - 1) * (DataReader.LONG_SIZE * 2));
|
||||
} else {
|
||||
stream.skipBytes(DataReader.INTEGER_SIZE);// segment duration
|
||||
obj.MediaTime = stream.readInt();
|
||||
stream.skipBytes(DataReader.INTEGER_SIZE); // segment duration
|
||||
obj.mediaTime = stream.readInt();
|
||||
}
|
||||
|
||||
obj.bMediaRate = stream.readInt();
|
||||
@ -751,7 +753,7 @@ public class Mp4DashReader {
|
||||
return obj;
|
||||
}
|
||||
|
||||
private Minf parse_minf(Box ref) throws IOException {
|
||||
private Minf parseMinf(final Box ref) throws IOException {
|
||||
Minf obj = new Minf();
|
||||
|
||||
Box b;
|
||||
@ -762,11 +764,11 @@ public class Mp4DashReader {
|
||||
obj.dinf = readFullBox(b);
|
||||
break;
|
||||
case ATOM_STBL:
|
||||
obj.stbl_stsd = parse_stbl(b);
|
||||
obj.stblStsd = parseStbl(b);
|
||||
break;
|
||||
case ATOM_VMHD:
|
||||
case ATOM_SMHD:
|
||||
obj.$mhd = readFullBox(b);
|
||||
obj.mhd = readFullBox(b);
|
||||
break;
|
||||
|
||||
}
|
||||
@ -777,42 +779,39 @@ public class Mp4DashReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* this only read the "stsd" box inside
|
||||
* This only reads the "stsd" box inside.
|
||||
*
|
||||
* @param ref stbl box
|
||||
* @return stsd box inside
|
||||
*/
|
||||
private byte[] parse_stbl(Box ref) throws IOException {
|
||||
private byte[] parseStbl(final Box ref) throws IOException {
|
||||
Box b = untilBox(ref, ATOM_STSD);
|
||||
|
||||
if (b == null) {
|
||||
return new byte[0];// this never should happens (missing codec startup data)
|
||||
return new byte[0]; // this never should happens (missing codec startup data)
|
||||
}
|
||||
|
||||
return readFullBox(b);
|
||||
}
|
||||
|
||||
|
||||
|
||||
class Box {
|
||||
|
||||
int type;
|
||||
long offset;
|
||||
long size;
|
||||
}
|
||||
|
||||
public class Moof {
|
||||
|
||||
int mfhd_SequenceNumber;
|
||||
int mfhdSequenceNumber;
|
||||
public Traf traf;
|
||||
}
|
||||
|
||||
public class Traf {
|
||||
|
||||
public Tfhd tfhd;
|
||||
long tfdt;
|
||||
public Trun trun;
|
||||
}
|
||||
|
||||
public class Tfhd {
|
||||
|
||||
int bFlags;
|
||||
public int trackId;
|
||||
int defaultSampleDuration;
|
||||
@ -821,7 +820,6 @@ public class Mp4DashReader {
|
||||
}
|
||||
|
||||
class TrunEntry {
|
||||
|
||||
int sampleDuration;
|
||||
int sampleSize;
|
||||
int sampleFlags;
|
||||
@ -833,7 +831,6 @@ public class Mp4DashReader {
|
||||
}
|
||||
|
||||
public class Trun {
|
||||
|
||||
public int chunkDuration;
|
||||
public int chunkSize;
|
||||
|
||||
@ -843,10 +840,10 @@ public class Mp4DashReader {
|
||||
|
||||
public int entryCount;
|
||||
byte[] bEntries;
|
||||
int entries_rowSize;
|
||||
int entriesRowSize;
|
||||
|
||||
public TrunEntry getEntry(int i) {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bEntries, i * entries_rowSize, entries_rowSize);
|
||||
public TrunEntry getEntry(final int i) {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bEntries, i * entriesRowSize, entriesRowSize);
|
||||
TrunEntry entry = new TrunEntry();
|
||||
|
||||
if (hasFlag(bFlags, 0x0100)) {
|
||||
@ -868,7 +865,7 @@ public class Mp4DashReader {
|
||||
return entry;
|
||||
}
|
||||
|
||||
public TrunEntry getAbsoluteEntry(int i, Tfhd header) {
|
||||
public TrunEntry getAbsoluteEntry(final int i, final Tfhd header) {
|
||||
TrunEntry entry = getEntry(i);
|
||||
|
||||
if (!hasFlag(bFlags, 0x0100) && hasFlag(header.bFlags, 0x20)) {
|
||||
@ -892,7 +889,6 @@ public class Mp4DashReader {
|
||||
}
|
||||
|
||||
public class Tkhd {
|
||||
|
||||
int trackId;
|
||||
long duration;
|
||||
short bVolume;
|
||||
@ -904,28 +900,24 @@ public class Mp4DashReader {
|
||||
}
|
||||
|
||||
public class Trak {
|
||||
|
||||
public Tkhd tkhd;
|
||||
public Elst edst_elst;
|
||||
public Elst edstElst;
|
||||
public Mdia mdia;
|
||||
|
||||
}
|
||||
|
||||
class Mvhd {
|
||||
|
||||
long timeScale;
|
||||
long nextTrackId;
|
||||
}
|
||||
|
||||
class Moov {
|
||||
|
||||
Mvhd mvhd;
|
||||
Trak[] trak;
|
||||
Trex[] mvex_trex;
|
||||
Trex[] mvexTrex;
|
||||
}
|
||||
|
||||
public class Trex {
|
||||
|
||||
private int trackId;
|
||||
int defaultSampleDescriptionIndex;
|
||||
int defaultSampleDuration;
|
||||
@ -934,42 +926,36 @@ public class Mp4DashReader {
|
||||
}
|
||||
|
||||
public class Elst {
|
||||
|
||||
public long MediaTime;
|
||||
public long mediaTime;
|
||||
public int bMediaRate;
|
||||
}
|
||||
|
||||
public class Mdia {
|
||||
|
||||
public int mdhd_timeScale;
|
||||
public int mdhdTimeScale;
|
||||
public byte[] mdhd;
|
||||
public Hdlr hdlr;
|
||||
public Minf minf;
|
||||
}
|
||||
|
||||
public class Hdlr {
|
||||
|
||||
public int type;
|
||||
public int subType;
|
||||
public byte[] bReserved;
|
||||
}
|
||||
|
||||
public class Minf {
|
||||
|
||||
public byte[] dinf;
|
||||
public byte[] stbl_stsd;
|
||||
public byte[] $mhd;
|
||||
public byte[] stblStsd;
|
||||
public byte[] mhd;
|
||||
}
|
||||
|
||||
public class Mp4Track {
|
||||
|
||||
public TrackKind kind;
|
||||
public Trak trak;
|
||||
public Trex trex;
|
||||
}
|
||||
|
||||
public class Mp4DashChunk {
|
||||
|
||||
public InputStream data;
|
||||
public Moof moof;
|
||||
private int i = 0;
|
||||
@ -1002,9 +988,7 @@ public class Mp4DashReader {
|
||||
}
|
||||
|
||||
public class Mp4DashSample {
|
||||
|
||||
public TrunEntry info;
|
||||
public byte[] data;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,13 +17,15 @@ import java.util.ArrayList;
|
||||
* @author kapodamy
|
||||
*/
|
||||
public class Mp4FromDashWriter {
|
||||
|
||||
private final static int EPOCH_OFFSET = 2082844800;
|
||||
private final static short DEFAULT_TIMESCALE = 1000;
|
||||
private final static byte SAMPLES_PER_CHUNK_INIT = 2;
|
||||
private final static byte SAMPLES_PER_CHUNK = 6;// ffmpeg uses 2, basic uses 1 (with 60fps uses 21 or 22). NewPipe will use 6
|
||||
private final static long THRESHOLD_FOR_CO64 = 0xFFFEFFFFL;// near 3.999 GiB
|
||||
private final static int THRESHOLD_MOOV_LENGTH = (256 * 1024) + (2048 * 1024); // 2.2 MiB enough for: 1080p 60fps 00h35m00s
|
||||
private static final int EPOCH_OFFSET = 2082844800;
|
||||
private static final short DEFAULT_TIMESCALE = 1000;
|
||||
private static final byte SAMPLES_PER_CHUNK_INIT = 2;
|
||||
// ffmpeg uses 2, basic uses 1 (with 60fps uses 21 or 22). NewPipe will use 6
|
||||
private static final byte SAMPLES_PER_CHUNK = 6;
|
||||
// near 3.999 GiB
|
||||
private static final long THRESHOLD_FOR_CO64 = 0xFFFEFFFFL;
|
||||
// 2.2 MiB enough for: 1080p 60fps 00h35m00s
|
||||
private static final int THRESHOLD_MOOV_LENGTH = (256 * 1024) + (2048 * 1024);
|
||||
|
||||
private final long time;
|
||||
|
||||
@ -48,7 +50,7 @@ public class Mp4FromDashWriter {
|
||||
|
||||
private final ArrayList<Integer> compatibleBrands = new ArrayList<>(5);
|
||||
|
||||
public Mp4FromDashWriter(SharpStream... sources) throws IOException {
|
||||
public Mp4FromDashWriter(final SharpStream... sources) throws IOException {
|
||||
for (SharpStream src : sources) {
|
||||
if (!src.canRewind() && !src.canRead()) {
|
||||
throw new IOException("All sources must be readable and allow rewind");
|
||||
@ -60,12 +62,12 @@ public class Mp4FromDashWriter {
|
||||
readersChunks = new Mp4DashChunk[readers.length];
|
||||
time = (System.currentTimeMillis() / 1000L) + EPOCH_OFFSET;
|
||||
|
||||
compatibleBrands.add(0x6D703431);// mp41
|
||||
compatibleBrands.add(0x69736F6D);// isom
|
||||
compatibleBrands.add(0x69736F32);// iso2
|
||||
compatibleBrands.add(0x6D703431); // mp41
|
||||
compatibleBrands.add(0x69736F6D); // isom
|
||||
compatibleBrands.add(0x69736F32); // iso2
|
||||
}
|
||||
|
||||
public Mp4Track[] getTracksFromSource(int sourceIndex) throws IllegalStateException {
|
||||
public Mp4Track[] getTracksFromSource(final int sourceIndex) throws IllegalStateException {
|
||||
if (!parsed) {
|
||||
throw new IllegalStateException("All sources must be parsed first");
|
||||
}
|
||||
@ -92,7 +94,7 @@ public class Mp4FromDashWriter {
|
||||
}
|
||||
}
|
||||
|
||||
public void selectTracks(int... trackIndex) throws IOException {
|
||||
public void selectTracks(final int... trackIndex) throws IOException {
|
||||
if (done) {
|
||||
throw new IOException("already done");
|
||||
}
|
||||
@ -110,7 +112,7 @@ public class Mp4FromDashWriter {
|
||||
}
|
||||
}
|
||||
|
||||
public void setMainBrand(int brand) {
|
||||
public void setMainBrand(final int brand) {
|
||||
overrideMainBrand = brand;
|
||||
}
|
||||
|
||||
@ -140,7 +142,7 @@ public class Mp4FromDashWriter {
|
||||
outStream = null;
|
||||
}
|
||||
|
||||
public void build(SharpStream output) throws IOException {
|
||||
public void build(final SharpStream output) throws IOException {
|
||||
if (done) {
|
||||
throw new RuntimeException("already done");
|
||||
}
|
||||
@ -153,7 +155,7 @@ public class Mp4FromDashWriter {
|
||||
// not allowed for very short tracks (less than 0.5 seconds)
|
||||
//
|
||||
outStream = output;
|
||||
long read = 8;// mdat box header size
|
||||
long read = 8; // mdat box header size
|
||||
long totalSampleSize = 0;
|
||||
int[] sampleExtra = new int[readers.length];
|
||||
int[] defaultMediaTime = new int[readers.length];
|
||||
@ -165,12 +167,12 @@ public class Mp4FromDashWriter {
|
||||
tablesInfo[i] = new TablesInfo();
|
||||
}
|
||||
|
||||
int single_sample_buffer;
|
||||
int singleSampleBuffer;
|
||||
if (tracks.length == 1 && tracks[0].kind == TrackKind.Audio) {
|
||||
// near 1 second of audio data per chunk, avoid split the audio stream in large chunks
|
||||
single_sample_buffer = tracks[0].trak.mdia.mdhd_timeScale / 1000;
|
||||
singleSampleBuffer = tracks[0].trak.mdia.mdhdTimeScale / 1000;
|
||||
} else {
|
||||
single_sample_buffer = -1;
|
||||
singleSampleBuffer = -1;
|
||||
}
|
||||
|
||||
|
||||
@ -187,7 +189,7 @@ public class Mp4FromDashWriter {
|
||||
}
|
||||
|
||||
read += chunk.moof.traf.trun.chunkSize;
|
||||
sampleExtra[i] += chunk.moof.traf.trun.chunkDuration;// calculate track duration
|
||||
sampleExtra[i] += chunk.moof.traf.trun.chunkDuration; // calculate track duration
|
||||
|
||||
TrunEntry info;
|
||||
while ((info = chunk.getNextSampleInfo()) != null) {
|
||||
@ -222,8 +224,8 @@ public class Mp4FromDashWriter {
|
||||
|
||||
readers[i].rewind();
|
||||
|
||||
if (single_sample_buffer > 0) {
|
||||
initChunkTables(tablesInfo[i], single_sample_buffer, single_sample_buffer);
|
||||
if (singleSampleBuffer > 0) {
|
||||
initChunkTables(tablesInfo[i], singleSampleBuffer, singleSampleBuffer);
|
||||
} else {
|
||||
initChunkTables(tablesInfo[i], SAMPLES_PER_CHUNK_INIT, SAMPLES_PER_CHUNK);
|
||||
}
|
||||
@ -232,18 +234,18 @@ public class Mp4FromDashWriter {
|
||||
|
||||
if (sampleSizeChanges == 1) {
|
||||
tablesInfo[i].stsz = 0;
|
||||
tablesInfo[i].stsz_default = samplesSize;
|
||||
tablesInfo[i].stszDefault = samplesSize;
|
||||
} else {
|
||||
tablesInfo[i].stsz_default = 0;
|
||||
tablesInfo[i].stszDefault = 0;
|
||||
}
|
||||
|
||||
if (tablesInfo[i].stss == tablesInfo[i].stsz) {
|
||||
tablesInfo[i].stss = -1;// for audio tracks (all samples are keyframes)
|
||||
tablesInfo[i].stss = -1; // for audio tracks (all samples are keyframes)
|
||||
}
|
||||
|
||||
// ensure track duration
|
||||
if (tracks[i].trak.tkhd.duration < 1) {
|
||||
tracks[i].trak.tkhd.duration = sampleExtra[i];// this never should happen
|
||||
tracks[i].trak.tkhd.duration = sampleExtra[i]; // this never should happen
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,21 +253,21 @@ public class Mp4FromDashWriter {
|
||||
boolean is64 = read > THRESHOLD_FOR_CO64;
|
||||
|
||||
// calculate the moov size
|
||||
int auxSize = make_moov(defaultMediaTime, tablesInfo, is64);
|
||||
int auxSize = makeMoov(defaultMediaTime, tablesInfo, is64);
|
||||
|
||||
if (auxSize < THRESHOLD_MOOV_LENGTH) {
|
||||
auxBuffer = ByteBuffer.allocate(auxSize);// cache moov in the memory
|
||||
auxBuffer = ByteBuffer.allocate(auxSize); // cache moov in the memory
|
||||
}
|
||||
|
||||
moovSimulation = false;
|
||||
writeOffset = 0;
|
||||
|
||||
final int ftyp_size = make_ftyp();
|
||||
final int ftypSize = makeFtyp();
|
||||
|
||||
// reserve moov space in the output stream
|
||||
if (auxSize > 0) {
|
||||
int length = auxSize;
|
||||
byte[] buffer = new byte[64 * 1024];// 64 KiB
|
||||
byte[] buffer = new byte[64 * 1024]; // 64 KiB
|
||||
while (length > 0) {
|
||||
int count = Math.min(length, buffer.length);
|
||||
outWrite(buffer, count);
|
||||
@ -274,21 +276,22 @@ public class Mp4FromDashWriter {
|
||||
}
|
||||
|
||||
if (auxBuffer == null) {
|
||||
outSeek(ftyp_size);
|
||||
outSeek(ftypSize);
|
||||
}
|
||||
|
||||
// tablesInfo contains row counts
|
||||
// and after returning from make_moov() will contain those table offsets
|
||||
make_moov(defaultMediaTime, tablesInfo, is64);
|
||||
// and after returning from makeMoov() will contain those table offsets
|
||||
makeMoov(defaultMediaTime, tablesInfo, is64);
|
||||
|
||||
// write tables: stts stsc sbgp
|
||||
// reset for ctts table: sampleCount sampleExtra
|
||||
for (int i = 0; i < readers.length; i++) {
|
||||
writeEntryArray(tablesInfo[i].stts, 2, sampleCount[i], defaultSampleDuration[i]);
|
||||
writeEntryArray(tablesInfo[i].stsc, tablesInfo[i].stsc_bEntries.length, tablesInfo[i].stsc_bEntries);
|
||||
tablesInfo[i].stsc_bEntries = null;
|
||||
writeEntryArray(tablesInfo[i].stsc, tablesInfo[i].stscBEntries.length,
|
||||
tablesInfo[i].stscBEntries);
|
||||
tablesInfo[i].stscBEntries = null;
|
||||
if (tablesInfo[i].ctts > 0) {
|
||||
sampleCount[i] = 1;// the index is not base zero
|
||||
sampleCount[i] = 1; // the index is not base zero
|
||||
sampleExtra[i] = -1;
|
||||
}
|
||||
if (tablesInfo[i].sbgp > 0) {
|
||||
@ -300,11 +303,11 @@ public class Mp4FromDashWriter {
|
||||
outRestore();
|
||||
}
|
||||
|
||||
outWrite(make_mdat(totalSampleSize, is64));
|
||||
outWrite(makeMdat(totalSampleSize, is64));
|
||||
|
||||
int[] sampleIndex = new int[readers.length];
|
||||
int[] sizes = new int[single_sample_buffer > 0 ? single_sample_buffer : SAMPLES_PER_CHUNK];
|
||||
int[] sync = new int[single_sample_buffer > 0 ? single_sample_buffer : SAMPLES_PER_CHUNK];
|
||||
int[] sizes = new int[singleSampleBuffer > 0 ? singleSampleBuffer : SAMPLES_PER_CHUNK];
|
||||
int[] sync = new int[singleSampleBuffer > 0 ? singleSampleBuffer : SAMPLES_PER_CHUNK];
|
||||
|
||||
int written = readers.length;
|
||||
while (written > 0) {
|
||||
@ -312,14 +315,14 @@ public class Mp4FromDashWriter {
|
||||
|
||||
for (int i = 0; i < readers.length; i++) {
|
||||
if (sampleIndex[i] < 0) {
|
||||
continue;// track is done
|
||||
continue; // track is done
|
||||
}
|
||||
|
||||
long chunkOffset = writeOffset;
|
||||
int syncCount = 0;
|
||||
int limit;
|
||||
if (single_sample_buffer > 0) {
|
||||
limit = single_sample_buffer;
|
||||
if (singleSampleBuffer > 0) {
|
||||
limit = singleSampleBuffer;
|
||||
} else {
|
||||
limit = sampleIndex[i] == 0 ? SAMPLES_PER_CHUNK_INIT : SAMPLES_PER_CHUNK;
|
||||
}
|
||||
@ -330,7 +333,8 @@ public class Mp4FromDashWriter {
|
||||
|
||||
if (sample == null) {
|
||||
if (tablesInfo[i].ctts > 0 && sampleExtra[i] >= 0) {
|
||||
writeEntryArray(tablesInfo[i].ctts, 1, sampleCount[i], sampleExtra[i]);// flush last entries
|
||||
writeEntryArray(tablesInfo[i].ctts, 1, sampleCount[i],
|
||||
sampleExtra[i]); // flush last entries
|
||||
outRestore();
|
||||
}
|
||||
sampleIndex[i] = -1;
|
||||
@ -344,7 +348,8 @@ public class Mp4FromDashWriter {
|
||||
sampleCount[i]++;
|
||||
} else {
|
||||
if (sampleExtra[i] >= 0) {
|
||||
tablesInfo[i].ctts = writeEntryArray(tablesInfo[i].ctts, 2, sampleCount[i], sampleExtra[i]);
|
||||
tablesInfo[i].ctts = writeEntryArray(tablesInfo[i].ctts, 2,
|
||||
sampleCount[i], sampleExtra[i]);
|
||||
outRestore();
|
||||
}
|
||||
sampleCount[i] = 1;
|
||||
@ -378,7 +383,8 @@ public class Mp4FromDashWriter {
|
||||
if (is64) {
|
||||
tablesInfo[i].stco = writeEntry64(tablesInfo[i].stco, chunkOffset);
|
||||
} else {
|
||||
tablesInfo[i].stco = writeEntryArray(tablesInfo[i].stco, 1, (int) chunkOffset);
|
||||
tablesInfo[i].stco = writeEntryArray(tablesInfo[i].stco, 1,
|
||||
(int) chunkOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@ -389,17 +395,17 @@ public class Mp4FromDashWriter {
|
||||
|
||||
if (auxBuffer != null) {
|
||||
// dump moov
|
||||
outSeek(ftyp_size);
|
||||
outSeek(ftypSize);
|
||||
outStream.write(auxBuffer.array(), 0, auxBuffer.capacity());
|
||||
auxBuffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private Mp4DashSample getNextSample(int track) throws IOException {
|
||||
private Mp4DashSample getNextSample(final int track) throws IOException {
|
||||
if (readersChunks[track] == null) {
|
||||
readersChunks[track] = readers[track].getNextChunk(false);
|
||||
if (readersChunks[track] == null) {
|
||||
return null;// EOF reached
|
||||
return null; // EOF reached
|
||||
}
|
||||
}
|
||||
|
||||
@ -413,7 +419,7 @@ public class Mp4FromDashWriter {
|
||||
}
|
||||
|
||||
|
||||
private int writeEntry64(int offset, long value) throws IOException {
|
||||
private int writeEntry64(final int offset, final long value) throws IOException {
|
||||
outBackup();
|
||||
|
||||
auxSeek(offset);
|
||||
@ -422,7 +428,8 @@ public class Mp4FromDashWriter {
|
||||
return offset + 8;
|
||||
}
|
||||
|
||||
private int writeEntryArray(int offset, int count, int... values) throws IOException {
|
||||
private int writeEntryArray(final int offset, final int count, final int... values)
|
||||
throws IOException {
|
||||
outBackup();
|
||||
|
||||
auxSeek(offset);
|
||||
@ -456,7 +463,8 @@ public class Mp4FromDashWriter {
|
||||
}
|
||||
}
|
||||
|
||||
private void initChunkTables(TablesInfo tables, int firstCount, int succesiveCount) {
|
||||
private void initChunkTables(final TablesInfo tables, final int firstCount,
|
||||
final int succesiveCount) {
|
||||
// tables.stsz holds amount of samples of the track (total)
|
||||
int totalSamples = (tables.stsz - firstCount);
|
||||
float chunkAmount = totalSamples / (float) succesiveCount;
|
||||
@ -473,36 +481,36 @@ public class Mp4FromDashWriter {
|
||||
}
|
||||
|
||||
// stsc_table_entry = [first_chunk, samples_per_chunk, sample_description_index]
|
||||
tables.stsc_bEntries = new int[tables.stsc * 3];
|
||||
tables.stco = remainChunkOffset + 1;// total entrys in chunk offset box
|
||||
tables.stscBEntries = new int[tables.stsc * 3];
|
||||
tables.stco = remainChunkOffset + 1; // total entrys in chunk offset box
|
||||
|
||||
tables.stsc_bEntries[index++] = 1;
|
||||
tables.stsc_bEntries[index++] = firstCount;
|
||||
tables.stsc_bEntries[index++] = 1;
|
||||
tables.stscBEntries[index++] = 1;
|
||||
tables.stscBEntries[index++] = firstCount;
|
||||
tables.stscBEntries[index++] = 1;
|
||||
|
||||
if (firstCount != succesiveCount) {
|
||||
tables.stsc_bEntries[index++] = 2;
|
||||
tables.stsc_bEntries[index++] = succesiveCount;
|
||||
tables.stsc_bEntries[index++] = 1;
|
||||
tables.stscBEntries[index++] = 2;
|
||||
tables.stscBEntries[index++] = succesiveCount;
|
||||
tables.stscBEntries[index++] = 1;
|
||||
}
|
||||
|
||||
if (remain) {
|
||||
tables.stsc_bEntries[index++] = remainChunkOffset + 1;
|
||||
tables.stsc_bEntries[index++] = totalSamples % succesiveCount;
|
||||
tables.stsc_bEntries[index] = 1;
|
||||
tables.stscBEntries[index++] = remainChunkOffset + 1;
|
||||
tables.stscBEntries[index++] = totalSamples % succesiveCount;
|
||||
tables.stscBEntries[index] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void outWrite(byte[] buffer) throws IOException {
|
||||
private void outWrite(final byte[] buffer) throws IOException {
|
||||
outWrite(buffer, buffer.length);
|
||||
}
|
||||
|
||||
private void outWrite(byte[] buffer, int count) throws IOException {
|
||||
private void outWrite(final byte[] buffer, final int count) throws IOException {
|
||||
writeOffset += count;
|
||||
outStream.write(buffer, 0, count);
|
||||
}
|
||||
|
||||
private void outSeek(long offset) throws IOException {
|
||||
private void outSeek(final long offset) throws IOException {
|
||||
if (outStream.canSeek()) {
|
||||
outStream.seek(offset);
|
||||
writeOffset = offset;
|
||||
@ -515,12 +523,12 @@ public class Mp4FromDashWriter {
|
||||
}
|
||||
}
|
||||
|
||||
private void outSkip(long amount) throws IOException {
|
||||
private void outSkip(final long amount) throws IOException {
|
||||
outStream.skip(amount);
|
||||
writeOffset += amount;
|
||||
}
|
||||
|
||||
private int lengthFor(int offset) throws IOException {
|
||||
private int lengthFor(final int offset) throws IOException {
|
||||
int size = auxOffset() - offset;
|
||||
|
||||
if (moovSimulation) {
|
||||
@ -534,7 +542,8 @@ public class Mp4FromDashWriter {
|
||||
return size;
|
||||
}
|
||||
|
||||
private int make(int type, int extra, int columns, int rows) throws IOException {
|
||||
private int make(final int type, final int extra, final int columns, final int rows)
|
||||
throws IOException {
|
||||
final byte base = 16;
|
||||
int size = columns * rows * 4;
|
||||
int total = size + base;
|
||||
@ -562,14 +571,14 @@ public class Mp4FromDashWriter {
|
||||
return offset + base;
|
||||
}
|
||||
|
||||
private void auxWrite(int value) throws IOException {
|
||||
private void auxWrite(final int value) throws IOException {
|
||||
auxWrite(ByteBuffer.allocate(4)
|
||||
.putInt(value)
|
||||
.array()
|
||||
);
|
||||
}
|
||||
|
||||
private void auxWrite(byte[] buffer) throws IOException {
|
||||
private void auxWrite(final byte[] buffer) throws IOException {
|
||||
if (moovSimulation) {
|
||||
writeOffset += buffer.length;
|
||||
} else if (auxBuffer == null) {
|
||||
@ -579,7 +588,7 @@ public class Mp4FromDashWriter {
|
||||
}
|
||||
}
|
||||
|
||||
private void auxSeek(int offset) throws IOException {
|
||||
private void auxSeek(final int offset) throws IOException {
|
||||
if (moovSimulation) {
|
||||
writeOffset = offset;
|
||||
} else if (auxBuffer == null) {
|
||||
@ -589,7 +598,7 @@ public class Mp4FromDashWriter {
|
||||
}
|
||||
}
|
||||
|
||||
private void auxSkip(int amount) throws IOException {
|
||||
private void auxSkip(final int amount) throws IOException {
|
||||
if (moovSimulation) {
|
||||
writeOffset += amount;
|
||||
} else if (auxBuffer == null) {
|
||||
@ -603,27 +612,27 @@ public class Mp4FromDashWriter {
|
||||
return auxBuffer == null ? (int) writeOffset : auxBuffer.position();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private int make_ftyp() throws IOException {
|
||||
private int makeFtyp() throws IOException {
|
||||
int size = 16 + (compatibleBrands.size() * 4);
|
||||
if (overrideMainBrand != 0) size += 4;
|
||||
if (overrideMainBrand != 0) {
|
||||
size += 4;
|
||||
}
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(size);
|
||||
buffer.putInt(size);
|
||||
buffer.putInt(0x66747970);// "ftyp"
|
||||
buffer.putInt(0x66747970); // "ftyp"
|
||||
|
||||
if (overrideMainBrand == 0) {
|
||||
buffer.putInt(0x6D703432);// mayor brand "mp42"
|
||||
buffer.putInt(512);// default minor version
|
||||
buffer.putInt(0x6D703432); // mayor brand "mp42"
|
||||
buffer.putInt(512); // default minor version
|
||||
} else {
|
||||
buffer.putInt(overrideMainBrand);
|
||||
buffer.putInt(0);
|
||||
buffer.putInt(0x6D703432);// "mp42" compatible brand
|
||||
buffer.putInt(0x6D703432); // "mp42" compatible brand
|
||||
}
|
||||
|
||||
for (Integer brand : compatibleBrands) {
|
||||
buffer.putInt(brand);// compatible brand
|
||||
buffer.putInt(brand); // compatible brand
|
||||
}
|
||||
|
||||
outWrite(buffer.array());
|
||||
@ -631,7 +640,7 @@ public class Mp4FromDashWriter {
|
||||
return size;
|
||||
}
|
||||
|
||||
private byte[] make_mdat(long refSize, boolean is64) {
|
||||
private byte[] makeMdat(long refSize, final boolean is64) {
|
||||
if (is64) {
|
||||
refSize += 16;
|
||||
} else {
|
||||
@ -640,7 +649,7 @@ public class Mp4FromDashWriter {
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(is64 ? 16 : 8)
|
||||
.putInt(is64 ? 0x01 : (int) refSize)
|
||||
.putInt(0x6D646174);// mdat
|
||||
.putInt(0x6D646174); // mdat
|
||||
|
||||
if (is64) {
|
||||
buffer.putLong(refSize);
|
||||
@ -649,7 +658,7 @@ public class Mp4FromDashWriter {
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
private void make_mvhd(long longestTrack) throws IOException {
|
||||
private void makeMvhd(final long longestTrack) throws IOException {
|
||||
auxWrite(new byte[]{
|
||||
0x00, 0x00, 0x00, 0x78, 0x6D, 0x76, 0x68, 0x64, 0x01, 0x00, 0x00, 0x00
|
||||
});
|
||||
@ -662,21 +671,22 @@ public class Mp4FromDashWriter {
|
||||
);
|
||||
|
||||
auxWrite(new byte[]{
|
||||
0x00, 0x01, 0x00, 0x00, 0x01, 0x00,// default volume and rate
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,// reserved values
|
||||
0x00, 0x01, 0x00, 0x00, 0x01, 0x00, // default volume and rate
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved values
|
||||
// default matrix
|
||||
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x40, 0x00, 0x00, 0x00
|
||||
});
|
||||
auxWrite(new byte[24]);// predefined
|
||||
auxWrite(new byte[24]); // predefined
|
||||
auxWrite(ByteBuffer.allocate(4)
|
||||
.putInt(tracks.length + 1)
|
||||
.array()
|
||||
);
|
||||
}
|
||||
|
||||
private int make_moov(int[] defaultMediaTime, TablesInfo[] tablesInfo, boolean is64) throws RuntimeException, IOException {
|
||||
private int makeMoov(final int[] defaultMediaTime, final TablesInfo[] tablesInfo,
|
||||
final boolean is64) throws RuntimeException, IOException {
|
||||
int start = auxOffset();
|
||||
|
||||
auxWrite(new byte[]{
|
||||
@ -688,21 +698,21 @@ public class Mp4FromDashWriter {
|
||||
|
||||
for (int i = 0; i < durations.length; i++) {
|
||||
durations[i] = (long) Math.ceil(
|
||||
((double) tracks[i].trak.tkhd.duration / tracks[i].trak.mdia.mdhd_timeScale) * DEFAULT_TIMESCALE
|
||||
);
|
||||
((double) tracks[i].trak.tkhd.duration / tracks[i].trak.mdia.mdhdTimeScale)
|
||||
* DEFAULT_TIMESCALE);
|
||||
|
||||
if (durations[i] > longestTrack) {
|
||||
longestTrack = durations[i];
|
||||
}
|
||||
}
|
||||
|
||||
make_mvhd(longestTrack);
|
||||
makeMvhd(longestTrack);
|
||||
|
||||
for (int i = 0; i < tracks.length; i++) {
|
||||
if (tracks[i].trak.tkhd.matrix.length != 36) {
|
||||
throw new RuntimeException("bad track matrix length (expected 36) in track n°" + i);
|
||||
}
|
||||
make_trak(i, durations[i], defaultMediaTime[i], tablesInfo[i], is64);
|
||||
makeTrak(i, durations[i], defaultMediaTime[i], tablesInfo[i], is64);
|
||||
}
|
||||
|
||||
// udta/meta/ilst/©too
|
||||
@ -713,17 +723,18 @@ public class Mp4FromDashWriter {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x69, 0x6C, 0x73, 0x74, 0x00, 0x00, 0x00,
|
||||
0x1F, (byte) 0xA9, 0x74, 0x6F, 0x6F, 0x00, 0x00, 0x00, 0x17, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00,
|
||||
0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65// "NewPipe" binary string
|
||||
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65 // "NewPipe" binary string
|
||||
});
|
||||
|
||||
return lengthFor(start);
|
||||
}
|
||||
|
||||
private void make_trak(int index, long duration, int defaultMediaTime, TablesInfo tables, boolean is64) throws IOException {
|
||||
private void makeTrak(final int index, final long duration, final int defaultMediaTime,
|
||||
final TablesInfo tables, final boolean is64) throws IOException {
|
||||
int start = auxOffset();
|
||||
|
||||
auxWrite(new byte[]{
|
||||
0x00, 0x00, 0x00, 0x00, 0x74, 0x72, 0x61, 0x6B,// trak header
|
||||
0x00, 0x00, 0x00, 0x00, 0x74, 0x72, 0x61, 0x6B, // trak header
|
||||
0x00, 0x00, 0x00, 0x68, 0x74, 0x6B, 0x68, 0x64, 0x01, 0x00, 0x00, 0x03 // tkhd header
|
||||
});
|
||||
|
||||
@ -747,20 +758,20 @@ public class Mp4FromDashWriter {
|
||||
);
|
||||
|
||||
auxWrite(new byte[]{
|
||||
0x00, 0x00, 0x00, 0x24, 0x65, 0x64, 0x74, 0x73,// edts header
|
||||
0x00, 0x00, 0x00, 0x1C, 0x65, 0x6C, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01// elst header
|
||||
0x00, 0x00, 0x00, 0x24, 0x65, 0x64, 0x74, 0x73, // edts header
|
||||
0x00, 0x00, 0x00, 0x1C, 0x65, 0x6C, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 // elst header
|
||||
});
|
||||
|
||||
int bMediaRate;
|
||||
int mediaTime;
|
||||
|
||||
if (tracks[index].trak.edst_elst == null) {
|
||||
if (tracks[index].trak.edstElst == null) {
|
||||
// is a audio track ¿is edst/elst optional for audio tracks?
|
||||
mediaTime = 0x00;// ffmpeg set this value as zero, instead of defaultMediaTime
|
||||
mediaTime = 0x00; // ffmpeg set this value as zero, instead of defaultMediaTime
|
||||
bMediaRate = 0x00010000;
|
||||
} else {
|
||||
mediaTime = (int) tracks[index].trak.edst_elst.MediaTime;
|
||||
bMediaRate = tracks[index].trak.edst_elst.bMediaRate;
|
||||
mediaTime = (int) tracks[index].trak.edstElst.mediaTime;
|
||||
bMediaRate = tracks[index].trak.edstElst.bMediaRate;
|
||||
}
|
||||
|
||||
auxWrite(ByteBuffer
|
||||
@ -771,32 +782,33 @@ public class Mp4FromDashWriter {
|
||||
.array()
|
||||
);
|
||||
|
||||
make_mdia(tracks[index].trak.mdia, tables, is64, tracks[index].kind == TrackKind.Audio);
|
||||
makeMdia(tracks[index].trak.mdia, tables, is64, tracks[index].kind == TrackKind.Audio);
|
||||
|
||||
lengthFor(start);
|
||||
}
|
||||
|
||||
private void make_mdia(Mdia mdia, TablesInfo tablesInfo, boolean is64, boolean isAudio) throws IOException {
|
||||
int start_mdia = auxOffset();
|
||||
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x61});// mdia
|
||||
private void makeMdia(final Mdia mdia, final TablesInfo tablesInfo, final boolean is64,
|
||||
final boolean isAudio) throws IOException {
|
||||
int startMdia = auxOffset();
|
||||
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x61}); // mdia
|
||||
auxWrite(mdia.mdhd);
|
||||
auxWrite(make_hdlr(mdia.hdlr));
|
||||
auxWrite(makeHdlr(mdia.hdlr));
|
||||
|
||||
int start_minf = auxOffset();
|
||||
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x6D, 0x69, 0x6E, 0x66});// minf
|
||||
auxWrite(mdia.minf.$mhd);
|
||||
int startMinf = auxOffset();
|
||||
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x6D, 0x69, 0x6E, 0x66}); // minf
|
||||
auxWrite(mdia.minf.mhd);
|
||||
auxWrite(mdia.minf.dinf);
|
||||
|
||||
int start_stbl = auxOffset();
|
||||
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x73, 0x74, 0x62, 0x6C});// stbl
|
||||
auxWrite(mdia.minf.stbl_stsd);
|
||||
int startStbl = auxOffset();
|
||||
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x73, 0x74, 0x62, 0x6C}); // stbl
|
||||
auxWrite(mdia.minf.stblStsd);
|
||||
|
||||
//
|
||||
// In audio tracks the following tables is not required: ssts ctts
|
||||
// And stsz can be empty if has a default sample size
|
||||
//
|
||||
if (moovSimulation) {
|
||||
make(0x73747473, -1, 2, 1);// stts
|
||||
make(0x73747473, -1, 2, 1); // stts
|
||||
if (tablesInfo.stss > 0) {
|
||||
make(0x73747373, -1, 1, tablesInfo.stss);
|
||||
}
|
||||
@ -804,7 +816,7 @@ public class Mp4FromDashWriter {
|
||||
make(0x63747473, -1, 2, tablesInfo.ctts);
|
||||
}
|
||||
make(0x73747363, -1, 3, tablesInfo.stsc);
|
||||
make(0x7374737A, tablesInfo.stsz_default, 1, tablesInfo.stsz);
|
||||
make(0x7374737A, tablesInfo.stszDefault, 1, tablesInfo.stsz);
|
||||
make(is64 ? 0x636F3634 : 0x7374636F, -1, is64 ? 2 : 1, tablesInfo.stco);
|
||||
} else {
|
||||
tablesInfo.stts = make(0x73747473, -1, 2, 1);
|
||||
@ -815,23 +827,24 @@ public class Mp4FromDashWriter {
|
||||
tablesInfo.ctts = make(0x63747473, -1, 2, tablesInfo.ctts);
|
||||
}
|
||||
tablesInfo.stsc = make(0x73747363, -1, 3, tablesInfo.stsc);
|
||||
tablesInfo.stsz = make(0x7374737A, tablesInfo.stsz_default, 1, tablesInfo.stsz);
|
||||
tablesInfo.stco = make(is64 ? 0x636F3634 : 0x7374636F, -1, is64 ? 2 : 1, tablesInfo.stco);
|
||||
tablesInfo.stsz = make(0x7374737A, tablesInfo.stszDefault, 1, tablesInfo.stsz);
|
||||
tablesInfo.stco = make(is64 ? 0x636F3634 : 0x7374636F, -1, is64 ? 2 : 1,
|
||||
tablesInfo.stco);
|
||||
}
|
||||
|
||||
if (isAudio) {
|
||||
auxWrite(make_sgpd());
|
||||
tablesInfo.sbgp = make_sbgp();// during simulation the returned offset is ignored
|
||||
auxWrite(makeSgpd());
|
||||
tablesInfo.sbgp = makeSbgp(); // during simulation the returned offset is ignored
|
||||
}
|
||||
|
||||
lengthFor(start_stbl);
|
||||
lengthFor(start_minf);
|
||||
lengthFor(start_mdia);
|
||||
lengthFor(startStbl);
|
||||
lengthFor(startMinf);
|
||||
lengthFor(startMdia);
|
||||
}
|
||||
|
||||
private byte[] make_hdlr(Hdlr hdlr) {
|
||||
private byte[] makeHdlr(final Hdlr hdlr) {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(new byte[]{
|
||||
0x00, 0x00, 0x00, 0x77, 0x68, 0x64, 0x6C, 0x72,// hdlr
|
||||
0x00, 0x00, 0x00, 0x77, 0x68, 0x64, 0x6C, 0x72, // hdlr
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// binary string "ISO Media file created in NewPipe (A libre lightweight streaming frontend for Android)."
|
||||
@ -846,28 +859,28 @@ public class Mp4FromDashWriter {
|
||||
buffer.position(12);
|
||||
buffer.putInt(hdlr.type);
|
||||
buffer.putInt(hdlr.subType);
|
||||
buffer.put(hdlr.bReserved);// always is a zero array
|
||||
buffer.put(hdlr.bReserved); // always is a zero array
|
||||
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
private int make_sbgp() throws IOException {
|
||||
private int makeSbgp() throws IOException {
|
||||
int offset = auxOffset();
|
||||
|
||||
auxWrite(new byte[] {
|
||||
0x00, 0x00, 0x00, 0x1C,// box size
|
||||
0x73, 0x62, 0x67, 0x70,// "sbpg"
|
||||
0x00, 0x00, 0x00, 0x00,// default box flags
|
||||
0x72, 0x6F, 0x6C, 0x6C,// group type "roll"
|
||||
0x00, 0x00, 0x00, 0x01,// group table size
|
||||
0x00, 0x00, 0x00, 0x00,// group[0] total samples (to be set later)
|
||||
0x00, 0x00, 0x00, 0x01// group[0] description index
|
||||
0x00, 0x00, 0x00, 0x1C, // box size
|
||||
0x73, 0x62, 0x67, 0x70, // "sbpg"
|
||||
0x00, 0x00, 0x00, 0x00, // default box flags
|
||||
0x72, 0x6F, 0x6C, 0x6C, // group type "roll"
|
||||
0x00, 0x00, 0x00, 0x01, // group table size
|
||||
0x00, 0x00, 0x00, 0x00, // group[0] total samples (to be set later)
|
||||
0x00, 0x00, 0x00, 0x01 // group[0] description index
|
||||
});
|
||||
|
||||
return offset + 0x14;
|
||||
}
|
||||
|
||||
private byte[] make_sgpd() {
|
||||
private byte[] makeSgpd() {
|
||||
/*
|
||||
* Sample Group Description Box
|
||||
*
|
||||
@ -882,26 +895,25 @@ public class Mp4FromDashWriter {
|
||||
*/
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(new byte[] {
|
||||
0x00, 0x00, 0x00, 0x1A,// box size
|
||||
0x73, 0x67, 0x70, 0x64,// "sgpd"
|
||||
0x01, 0x00, 0x00, 0x00,// box flags (unknown flag sets)
|
||||
0x00, 0x00, 0x00, 0x1A, // box size
|
||||
0x73, 0x67, 0x70, 0x64, // "sgpd"
|
||||
0x01, 0x00, 0x00, 0x00, // box flags (unknown flag sets)
|
||||
0x72, 0x6F, 0x6C, 0x6C, // ¿¿group type??
|
||||
0x00, 0x00, 0x00, 0x02,// ¿¿??
|
||||
0x00, 0x00, 0x00, 0x01,// ¿¿??
|
||||
(byte)0xFF, (byte)0xFF// ¿¿??
|
||||
0x00, 0x00, 0x00, 0x02, // ¿¿??
|
||||
0x00, 0x00, 0x00, 0x01, // ¿¿??
|
||||
(byte) 0xFF, (byte) 0xFF // ¿¿??
|
||||
});
|
||||
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
class TablesInfo {
|
||||
|
||||
int stts;
|
||||
int stsc;
|
||||
int[] stsc_bEntries;
|
||||
int[] stscBEntries;
|
||||
int ctts;
|
||||
int stsz;
|
||||
int stsz_default;
|
||||
int stszDefault;
|
||||
int stss;
|
||||
int stco;
|
||||
int sbgp;
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.schabi.newpipe.streams;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.schabi.newpipe.streams.WebMReader.Cluster;
|
||||
import org.schabi.newpipe.streams.WebMReader.Segment;
|
||||
@ -13,22 +14,19 @@ import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* @author kapodamy
|
||||
*/
|
||||
public class OggFromWebMWriter implements Closeable {
|
||||
|
||||
private static final byte FLAG_UNSET = 0x00;
|
||||
//private static final byte FLAG_CONTINUED = 0x01;
|
||||
private static final byte FLAG_FIRST = 0x02;
|
||||
private static final byte FLAG_LAST = 0x04;
|
||||
|
||||
private final static byte HEADER_CHECKSUM_OFFSET = 22;
|
||||
private final static byte HEADER_SIZE = 27;
|
||||
private static final byte HEADER_CHECKSUM_OFFSET = 22;
|
||||
private static final byte HEADER_SIZE = 27;
|
||||
|
||||
private final static int TIME_SCALE_NS = 1000000000;
|
||||
private static final int TIME_SCALE_NS = 1000000000;
|
||||
|
||||
private boolean done = false;
|
||||
private boolean parsed = false;
|
||||
@ -36,26 +34,26 @@ public class OggFromWebMWriter implements Closeable {
|
||||
private SharpStream source;
|
||||
private SharpStream output;
|
||||
|
||||
private int sequence_count = 0;
|
||||
private final int STREAM_ID;
|
||||
private byte packet_flag = FLAG_FIRST;
|
||||
private int sequenceCount = 0;
|
||||
private final int streamId;
|
||||
private byte packetFlag = FLAG_FIRST;
|
||||
|
||||
private WebMReader webm = null;
|
||||
private WebMTrack webm_track = null;
|
||||
private Segment webm_segment = null;
|
||||
private Cluster webm_cluster = null;
|
||||
private SimpleBlock webm_block = null;
|
||||
private WebMTrack webmTrack = null;
|
||||
private Segment webmSegment = null;
|
||||
private Cluster webmCluster = null;
|
||||
private SimpleBlock webmBlock = null;
|
||||
|
||||
private long webm_block_last_timecode = 0;
|
||||
private long webm_block_near_duration = 0;
|
||||
private long webmBlockLastTimecode = 0;
|
||||
private long webmBlockNearDuration = 0;
|
||||
|
||||
private short segment_table_size = 0;
|
||||
private final byte[] segment_table = new byte[255];
|
||||
private long segment_table_next_timestamp = TIME_SCALE_NS;
|
||||
private short segmentTableSize = 0;
|
||||
private final byte[] segmentTable = new byte[255];
|
||||
private long segmentTableNextTimestamp = TIME_SCALE_NS;
|
||||
|
||||
private final int[] crc32_table = new int[256];
|
||||
private final int[] crc32Table = new int[256];
|
||||
|
||||
public OggFromWebMWriter(@NonNull SharpStream source, @NonNull SharpStream target) {
|
||||
public OggFromWebMWriter(@NonNull final SharpStream source, @NonNull final SharpStream target) {
|
||||
if (!source.canRead() || !source.canRewind()) {
|
||||
throw new IllegalArgumentException("source stream must be readable and allows seeking");
|
||||
}
|
||||
@ -66,9 +64,9 @@ public class OggFromWebMWriter implements Closeable {
|
||||
this.source = source;
|
||||
this.output = target;
|
||||
|
||||
this.STREAM_ID = (int) System.currentTimeMillis();
|
||||
this.streamId = (int) System.currentTimeMillis();
|
||||
|
||||
populate_crc32_table();
|
||||
populateCrc32Table();
|
||||
}
|
||||
|
||||
public boolean isDone() {
|
||||
@ -98,20 +96,20 @@ public class OggFromWebMWriter implements Closeable {
|
||||
try {
|
||||
webm = new WebMReader(source);
|
||||
webm.parse();
|
||||
webm_segment = webm.getNextSegment();
|
||||
webmSegment = webm.getNextSegment();
|
||||
} finally {
|
||||
parsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void selectTrack(int trackIndex) throws IOException {
|
||||
public void selectTrack(final int trackIndex) throws IOException {
|
||||
if (!parsed) {
|
||||
throw new IllegalStateException("source must be parsed first");
|
||||
}
|
||||
if (done) {
|
||||
throw new IOException("already done");
|
||||
}
|
||||
if (webm_track != null) {
|
||||
if (webmTrack != null) {
|
||||
throw new IOException("tracks already selected");
|
||||
}
|
||||
|
||||
@ -124,7 +122,7 @@ public class OggFromWebMWriter implements Closeable {
|
||||
}
|
||||
|
||||
try {
|
||||
webm_track = webm.selectTrack(trackIndex);
|
||||
webmTrack = webm.selectTrack(trackIndex);
|
||||
} finally {
|
||||
parsed = true;
|
||||
}
|
||||
@ -135,7 +133,7 @@ public class OggFromWebMWriter implements Closeable {
|
||||
done = true;
|
||||
parsed = true;
|
||||
|
||||
webm_track = null;
|
||||
webmTrack = null;
|
||||
webm = null;
|
||||
|
||||
if (!output.isClosed()) {
|
||||
@ -155,43 +153,44 @@ public class OggFromWebMWriter implements Closeable {
|
||||
header.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
/* step 1: get the amount of frames per seconds */
|
||||
switch (webm_track.kind) {
|
||||
switch (webmTrack.kind) {
|
||||
case Audio:
|
||||
resolution = getSampleFrequencyFromTrack(webm_track.bMetadata);
|
||||
resolution = getSampleFrequencyFromTrack(webmTrack.bMetadata);
|
||||
if (resolution == 0f) {
|
||||
throw new RuntimeException("cannot get the audio sample rate");
|
||||
}
|
||||
break;
|
||||
case Video:
|
||||
// WARNING: untested
|
||||
if (webm_track.defaultDuration == 0) {
|
||||
if (webmTrack.defaultDuration == 0) {
|
||||
throw new RuntimeException("missing default frame time");
|
||||
}
|
||||
resolution = 1000f / ((float) webm_track.defaultDuration / webm_segment.info.timecodeScale);
|
||||
resolution = 1000f / ((float) webmTrack.defaultDuration
|
||||
/ webmSegment.info.timecodeScale);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("not implemented");
|
||||
}
|
||||
|
||||
/* step 2: create packet with code init data */
|
||||
if (webm_track.codecPrivate != null) {
|
||||
addPacketSegment(webm_track.codecPrivate.length);
|
||||
make_packetHeader(0x00, header, webm_track.codecPrivate);
|
||||
if (webmTrack.codecPrivate != null) {
|
||||
addPacketSegment(webmTrack.codecPrivate.length);
|
||||
makePacketheader(0x00, header, webmTrack.codecPrivate);
|
||||
write(header);
|
||||
output.write(webm_track.codecPrivate);
|
||||
output.write(webmTrack.codecPrivate);
|
||||
}
|
||||
|
||||
/* step 3: create packet with metadata */
|
||||
byte[] buffer = make_metadata();
|
||||
byte[] buffer = makeMetadata();
|
||||
if (buffer != null) {
|
||||
addPacketSegment(buffer.length);
|
||||
make_packetHeader(0x00, header, buffer);
|
||||
makePacketheader(0x00, header, buffer);
|
||||
write(header);
|
||||
output.write(buffer);
|
||||
}
|
||||
|
||||
/* step 4: calculate amount of packets */
|
||||
while (webm_segment != null) {
|
||||
while (webmSegment != null) {
|
||||
bloq = getNextBlock();
|
||||
|
||||
if (bloq != null && addPacketSegment(bloq)) {
|
||||
@ -203,29 +202,29 @@ public class OggFromWebMWriter implements Closeable {
|
||||
}
|
||||
|
||||
// calculate the current packet duration using the next block
|
||||
double elapsed_ns = webm_track.codecDelay;
|
||||
double elapsedNs = webmTrack.codecDelay;
|
||||
|
||||
if (bloq == null) {
|
||||
packet_flag = FLAG_LAST;// note: if the flag is FLAG_CONTINUED, is changed
|
||||
elapsed_ns += webm_block_last_timecode;
|
||||
packetFlag = FLAG_LAST; // note: if the flag is FLAG_CONTINUED, is changed
|
||||
elapsedNs += webmBlockLastTimecode;
|
||||
|
||||
if (webm_track.defaultDuration > 0) {
|
||||
elapsed_ns += webm_track.defaultDuration;
|
||||
if (webmTrack.defaultDuration > 0) {
|
||||
elapsedNs += webmTrack.defaultDuration;
|
||||
} else {
|
||||
// hardcoded way, guess the sample duration
|
||||
elapsed_ns += webm_block_near_duration;
|
||||
elapsedNs += webmBlockNearDuration;
|
||||
}
|
||||
} else {
|
||||
elapsed_ns += bloq.absoluteTimeCodeNs;
|
||||
elapsedNs += bloq.absoluteTimeCodeNs;
|
||||
}
|
||||
|
||||
// get the sample count in the page
|
||||
elapsed_ns = elapsed_ns / TIME_SCALE_NS;
|
||||
elapsed_ns = Math.ceil(elapsed_ns * resolution);
|
||||
elapsedNs = elapsedNs / TIME_SCALE_NS;
|
||||
elapsedNs = Math.ceil(elapsedNs * resolution);
|
||||
|
||||
// create header and calculate page checksum
|
||||
int checksum = make_packetHeader((long) elapsed_ns, header, null);
|
||||
checksum = calc_crc32(checksum, page.array(), page.position());
|
||||
int checksum = makePacketheader((long) elapsedNs, header, null);
|
||||
checksum = calcCrc32(checksum, page.array(), page.position());
|
||||
|
||||
header.putInt(HEADER_CHECKSUM_OFFSET, checksum);
|
||||
|
||||
@ -233,58 +232,59 @@ public class OggFromWebMWriter implements Closeable {
|
||||
write(header);
|
||||
write(page);
|
||||
|
||||
webm_block = bloq;
|
||||
webmBlock = bloq;
|
||||
}
|
||||
}
|
||||
|
||||
private int make_packetHeader(long gran_pos, @NonNull ByteBuffer buffer, byte[] immediate_page) {
|
||||
private int makePacketheader(final long granPos, @NonNull final ByteBuffer buffer,
|
||||
final byte[] immediatePage) {
|
||||
short length = HEADER_SIZE;
|
||||
|
||||
buffer.putInt(0x5367674f);// "OggS" binary string in little-endian
|
||||
buffer.put((byte) 0x00);// version
|
||||
buffer.put(packet_flag);// type
|
||||
buffer.putInt(0x5367674f); // "OggS" binary string in little-endian
|
||||
buffer.put((byte) 0x00); // version
|
||||
buffer.put(packetFlag); // type
|
||||
|
||||
buffer.putLong(gran_pos);// granulate position
|
||||
buffer.putLong(granPos); // granulate position
|
||||
|
||||
buffer.putInt(STREAM_ID);// bitstream serial number
|
||||
buffer.putInt(sequence_count++);// page sequence number
|
||||
buffer.putInt(streamId); // bitstream serial number
|
||||
buffer.putInt(sequenceCount++); // page sequence number
|
||||
|
||||
buffer.putInt(0x00);// page checksum
|
||||
buffer.putInt(0x00); // page checksum
|
||||
|
||||
buffer.put((byte) segment_table_size);// segment table
|
||||
buffer.put(segment_table, 0, segment_table_size);// segment size
|
||||
buffer.put((byte) segmentTableSize); // segment table
|
||||
buffer.put(segmentTable, 0, segmentTableSize); // segment size
|
||||
|
||||
length += segment_table_size;
|
||||
length += segmentTableSize;
|
||||
|
||||
clearSegmentTable();// clear segment table for next header
|
||||
clearSegmentTable(); // clear segment table for next header
|
||||
|
||||
int checksum_crc32 = calc_crc32(0x00, buffer.array(), length);
|
||||
int checksumCrc32 = calcCrc32(0x00, buffer.array(), length);
|
||||
|
||||
if (immediate_page != null) {
|
||||
checksum_crc32 = calc_crc32(checksum_crc32, immediate_page, immediate_page.length);
|
||||
buffer.putInt(HEADER_CHECKSUM_OFFSET, checksum_crc32);
|
||||
segment_table_next_timestamp -= TIME_SCALE_NS;
|
||||
if (immediatePage != null) {
|
||||
checksumCrc32 = calcCrc32(checksumCrc32, immediatePage, immediatePage.length);
|
||||
buffer.putInt(HEADER_CHECKSUM_OFFSET, checksumCrc32);
|
||||
segmentTableNextTimestamp -= TIME_SCALE_NS;
|
||||
}
|
||||
|
||||
return checksum_crc32;
|
||||
return checksumCrc32;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private byte[] make_metadata() {
|
||||
if ("A_OPUS".equals(webm_track.codecId)) {
|
||||
private byte[] makeMetadata() {
|
||||
if ("A_OPUS".equals(webmTrack.codecId)) {
|
||||
return new byte[]{
|
||||
0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73,// "OpusTags" binary string
|
||||
0x07, 0x00, 0x00, 0x00,// writting application string size
|
||||
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string
|
||||
0x00, 0x00, 0x00, 0x00// additional tags count (zero means no tags)
|
||||
0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73, // "OpusTags" binary string
|
||||
0x07, 0x00, 0x00, 0x00, // writing application string size
|
||||
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65, // "NewPipe" binary string
|
||||
0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags)
|
||||
};
|
||||
} else if ("A_VORBIS".equals(webm_track.codecId)) {
|
||||
} else if ("A_VORBIS".equals(webmTrack.codecId)) {
|
||||
return new byte[]{
|
||||
0x03,// ????????
|
||||
0x76, 0x6f, 0x72, 0x62, 0x69, 0x73,// "vorbis" binary string
|
||||
0x07, 0x00, 0x00, 0x00,// writting application string size
|
||||
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string
|
||||
0x01, 0x00, 0x00, 0x00,// additional tags count (zero means no tags)
|
||||
0x03, // ????????
|
||||
0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, // "vorbis" binary string
|
||||
0x07, 0x00, 0x00, 0x00, // writting application string size
|
||||
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65, // "NewPipe" binary string
|
||||
0x01, 0x00, 0x00, 0x00, // additional tags count (zero means no tags)
|
||||
|
||||
/*
|
||||
// whole file duration (not implemented)
|
||||
@ -292,10 +292,10 @@ public class OggFromWebMWriter implements Closeable {
|
||||
0x55, 0x52, 0x41, 0x54, 0x49, 0x4F, 0x4E, 0x3D, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x3A, 0x30,
|
||||
0x30, 0x2E, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30
|
||||
*/
|
||||
0x0F,// tag string size
|
||||
0x00, 0x00, 0x00, 0x45, 0x4E, 0x43, 0x4F, 0x44, 0x45, 0x52, 0x3D,// "ENCODER=" binary string
|
||||
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00// ????????
|
||||
0x0F, // tag string size
|
||||
0x00, 0x00, 0x00, 0x45, 0x4E, 0x43, 0x4F, 0x44, 0x45, 0x52, 0x3D, // "ENCODER=" binary string
|
||||
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65, // "NewPipe" binary string
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // ????????
|
||||
};
|
||||
}
|
||||
|
||||
@ -303,51 +303,49 @@ public class OggFromWebMWriter implements Closeable {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void write(ByteBuffer buffer) throws IOException {
|
||||
private void write(final ByteBuffer buffer) throws IOException {
|
||||
output.write(buffer.array(), 0, buffer.position());
|
||||
buffer.position(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Nullable
|
||||
private SimpleBlock getNextBlock() throws IOException {
|
||||
SimpleBlock res;
|
||||
|
||||
if (webm_block != null) {
|
||||
res = webm_block;
|
||||
webm_block = null;
|
||||
if (webmBlock != null) {
|
||||
res = webmBlock;
|
||||
webmBlock = null;
|
||||
return res;
|
||||
}
|
||||
|
||||
if (webm_segment == null) {
|
||||
webm_segment = webm.getNextSegment();
|
||||
if (webm_segment == null) {
|
||||
return null;// no more blocks in the selected track
|
||||
if (webmSegment == null) {
|
||||
webmSegment = webm.getNextSegment();
|
||||
if (webmSegment == null) {
|
||||
return null; // no more blocks in the selected track
|
||||
}
|
||||
}
|
||||
|
||||
if (webm_cluster == null) {
|
||||
webm_cluster = webm_segment.getNextCluster();
|
||||
if (webm_cluster == null) {
|
||||
webm_segment = null;
|
||||
if (webmCluster == null) {
|
||||
webmCluster = webmSegment.getNextCluster();
|
||||
if (webmCluster == null) {
|
||||
webmSegment = null;
|
||||
return getNextBlock();
|
||||
}
|
||||
}
|
||||
|
||||
res = webm_cluster.getNextSimpleBlock();
|
||||
res = webmCluster.getNextSimpleBlock();
|
||||
if (res == null) {
|
||||
webm_cluster = null;
|
||||
webmCluster = null;
|
||||
return getNextBlock();
|
||||
}
|
||||
|
||||
webm_block_near_duration = res.absoluteTimeCodeNs - webm_block_last_timecode;
|
||||
webm_block_last_timecode = res.absoluteTimeCodeNs;
|
||||
webmBlockNearDuration = res.absoluteTimeCodeNs - webmBlockLastTimecode;
|
||||
webmBlockLastTimecode = res.absoluteTimeCodeNs;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private float getSampleFrequencyFromTrack(byte[] bMetadata) {
|
||||
private float getSampleFrequencyFromTrack(final byte[] bMetadata) {
|
||||
// hardcoded way
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bMetadata);
|
||||
|
||||
@ -362,15 +360,15 @@ public class OggFromWebMWriter implements Closeable {
|
||||
}
|
||||
|
||||
private void clearSegmentTable() {
|
||||
segment_table_next_timestamp += TIME_SCALE_NS;
|
||||
packet_flag = FLAG_UNSET;
|
||||
segment_table_size = 0;
|
||||
segmentTableNextTimestamp += TIME_SCALE_NS;
|
||||
packetFlag = FLAG_UNSET;
|
||||
segmentTableSize = 0;
|
||||
}
|
||||
|
||||
private boolean addPacketSegment(SimpleBlock block) {
|
||||
long timestamp = block.absoluteTimeCodeNs + webm_track.codecDelay;
|
||||
private boolean addPacketSegment(final SimpleBlock block) {
|
||||
long timestamp = block.absoluteTimeCodeNs + webmTrack.codecDelay;
|
||||
|
||||
if (timestamp >= segment_table_next_timestamp) {
|
||||
if (timestamp >= segmentTableNextTimestamp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -382,7 +380,7 @@ public class OggFromWebMWriter implements Closeable {
|
||||
throw new UnsupportedOperationException("page size cannot be larger than 65025");
|
||||
}
|
||||
|
||||
int available = (segment_table.length - segment_table_size) * 255;
|
||||
int available = (segmentTable.length - segmentTableSize) * 255;
|
||||
boolean extra = (size % 255) == 0;
|
||||
|
||||
if (extra) {
|
||||
@ -393,21 +391,21 @@ public class OggFromWebMWriter implements Closeable {
|
||||
|
||||
// check if possible add the segment, without overflow the table
|
||||
if (available < size) {
|
||||
return false;// not enough space on the page
|
||||
return false; // not enough space on the page
|
||||
}
|
||||
|
||||
for (; size > 0; size -= 255) {
|
||||
segment_table[segment_table_size++] = (byte) Math.min(size, 255);
|
||||
segmentTable[segmentTableSize++] = (byte) Math.min(size, 255);
|
||||
}
|
||||
|
||||
if (extra) {
|
||||
segment_table[segment_table_size++] = 0x00;
|
||||
segmentTable[segmentTableSize++] = 0x00;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void populate_crc32_table() {
|
||||
private void populateCrc32Table() {
|
||||
for (int i = 0; i < 0x100; i++) {
|
||||
int crc = i << 24;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
@ -415,17 +413,16 @@ public class OggFromWebMWriter implements Closeable {
|
||||
crc <<= 1;
|
||||
crc ^= (int) (0x100000000L - b) & 0x04c11db7;
|
||||
}
|
||||
crc32_table[i] = crc;
|
||||
crc32Table[i] = crc;
|
||||
}
|
||||
}
|
||||
|
||||
private int calc_crc32(int initial_crc, byte[] buffer, int size) {
|
||||
private int calcCrc32(int initialCrc, final byte[] buffer, final int size) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
int reg = (initial_crc >>> 24) & 0xff;
|
||||
initial_crc = (initial_crc << 8) ^ crc32_table[reg ^ (buffer[i] & 0xff)];
|
||||
int reg = (initialCrc >>> 24) & 0xff;
|
||||
initialCrc = (initialCrc << 8) ^ crc32Table[reg ^ (buffer[i] & 0xff)];
|
||||
}
|
||||
|
||||
return initial_crc;
|
||||
return initialCrc;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,18 +26,19 @@ public class SrtFromTtmlWriter {
|
||||
|
||||
private int frameIndex = 0;
|
||||
|
||||
public SrtFromTtmlWriter(SharpStream out, boolean ignoreEmptyFrames) {
|
||||
public SrtFromTtmlWriter(final SharpStream out, final boolean ignoreEmptyFrames) {
|
||||
this.out = out;
|
||||
this.ignoreEmptyFrames = ignoreEmptyFrames;
|
||||
}
|
||||
|
||||
private static String getTimestamp(Element frame, String attr) {
|
||||
private static String getTimestamp(final Element frame, final String attr) {
|
||||
return frame
|
||||
.attr(attr)
|
||||
.replace('.', ',');// SRT subtitles uses comma as decimal separator
|
||||
.replace('.', ','); // SRT subtitles uses comma as decimal separator
|
||||
}
|
||||
|
||||
private void writeFrame(String begin, String end, StringBuilder text) throws IOException {
|
||||
private void writeFrame(final String begin, final String end, final StringBuilder text)
|
||||
throws IOException {
|
||||
writeString(String.valueOf(frameIndex++));
|
||||
writeString(NEW_LINE);
|
||||
writeString(begin);
|
||||
@ -49,11 +50,11 @@ public class SrtFromTtmlWriter {
|
||||
writeString(NEW_LINE);
|
||||
}
|
||||
|
||||
private void writeString(String text) throws IOException {
|
||||
private void writeString(final String text) throws IOException {
|
||||
out.write(text.getBytes(charset));
|
||||
}
|
||||
|
||||
public void build(SharpStream ttml) throws IOException {
|
||||
public void build(final SharpStream ttml) throws IOException {
|
||||
/*
|
||||
* TTML parser with BASIC support
|
||||
* multiple CUE is not supported
|
||||
@ -66,25 +67,32 @@ public class SrtFromTtmlWriter {
|
||||
// parse XML
|
||||
byte[] buffer = new byte[(int) ttml.available()];
|
||||
ttml.read(buffer);
|
||||
Document doc = Jsoup.parse(new ByteArrayInputStream(buffer), "UTF-8", "", Parser.xmlParser());
|
||||
Document doc = Jsoup.parse(new ByteArrayInputStream(buffer), "UTF-8", "",
|
||||
Parser.xmlParser());
|
||||
|
||||
StringBuilder text = new StringBuilder(128);
|
||||
Elements paragraph_list = doc.select("body > div > p");
|
||||
Elements paragraphList = doc.select("body > div > p");
|
||||
|
||||
// check if has frames
|
||||
if (paragraph_list.size() < 1) return;
|
||||
if (paragraphList.size() < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Element paragraph : paragraph_list) {
|
||||
for (Element paragraph : paragraphList) {
|
||||
text.setLength(0);
|
||||
|
||||
for (Node children : paragraph.childNodes()) {
|
||||
if (children instanceof TextNode)
|
||||
if (children instanceof TextNode) {
|
||||
text.append(((TextNode) children).text());
|
||||
else if (children instanceof Element && ((Element) children).tagName().equalsIgnoreCase("br"))
|
||||
} else if (children instanceof Element
|
||||
&& ((Element) children).tagName().equalsIgnoreCase("br")) {
|
||||
text.append(NEW_LINE);
|
||||
}
|
||||
}
|
||||
|
||||
if (ignoreEmptyFrames && text.length() < 1) continue;
|
||||
if (ignoreEmptyFrames && text.length() < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String begin = getTimestamp(paragraph, "begin");
|
||||
String end = getTimestamp(paragraph, "end");
|
||||
|
@ -14,36 +14,35 @@ import java.util.NoSuchElementException;
|
||||
* @author kapodamy
|
||||
*/
|
||||
public class WebMReader {
|
||||
private static final int ID_EMBL = 0x0A45DFA3;
|
||||
private static final int ID_EMBL_READ_VERSION = 0x02F7;
|
||||
private static final int ID_EMBL_DOC_TYPE = 0x0282;
|
||||
private static final int ID_EMBL_DOC_TYPE_READ_VERSION = 0x0285;
|
||||
|
||||
private final static int ID_EMBL = 0x0A45DFA3;
|
||||
private final static int ID_EMBLReadVersion = 0x02F7;
|
||||
private final static int ID_EMBLDocType = 0x0282;
|
||||
private final static int ID_EMBLDocTypeReadVersion = 0x0285;
|
||||
private static final int ID_SEGMENT = 0x08538067;
|
||||
|
||||
private final static int ID_Segment = 0x08538067;
|
||||
private static final int ID_INFO = 0x0549A966;
|
||||
private static final int ID_TIMECODE_SCALE = 0x0AD7B1;
|
||||
private static final int ID_DURATION = 0x489;
|
||||
|
||||
private final static int ID_Info = 0x0549A966;
|
||||
private final static int ID_TimecodeScale = 0x0AD7B1;
|
||||
private final static int ID_Duration = 0x489;
|
||||
private static final int ID_TRACKS = 0x0654AE6B;
|
||||
private static final int ID_TRACK_ENTRY = 0x2E;
|
||||
private static final int ID_TRACK_NUMBER = 0x57;
|
||||
private static final int ID_TRACK_TYPE = 0x03;
|
||||
private static final int ID_CODEC_ID = 0x06;
|
||||
private static final int ID_CODEC_PRIVATE = 0x23A2;
|
||||
private static final int ID_VIDEO = 0x60;
|
||||
private static final int ID_AUDIO = 0x61;
|
||||
private static final int ID_DEFAULT_DURATION = 0x3E383;
|
||||
private static final int ID_FLAG_LACING = 0x1C;
|
||||
private static final int ID_CODEC_DELAY = 0x16AA;
|
||||
private static final int ID_SEEK_PRE_ROLL = 0x16BB;
|
||||
|
||||
private final static int ID_Tracks = 0x0654AE6B;
|
||||
private final static int ID_TrackEntry = 0x2E;
|
||||
private final static int ID_TrackNumber = 0x57;
|
||||
private final static int ID_TrackType = 0x03;
|
||||
private final static int ID_CodecID = 0x06;
|
||||
private final static int ID_CodecPrivate = 0x23A2;
|
||||
private final static int ID_Video = 0x60;
|
||||
private final static int ID_Audio = 0x61;
|
||||
private final static int ID_DefaultDuration = 0x3E383;
|
||||
private final static int ID_FlagLacing = 0x1C;
|
||||
private final static int ID_CodecDelay = 0x16AA;
|
||||
private final static int ID_SeekPreRoll = 0x16BB;
|
||||
|
||||
private final static int ID_Cluster = 0x0F43B675;
|
||||
private final static int ID_Timecode = 0x67;
|
||||
private final static int ID_SimpleBlock = 0x23;
|
||||
private final static int ID_Block = 0x21;
|
||||
private final static int ID_GroupBlock = 0x20;
|
||||
private static final int ID_CLUSTER = 0x0F43B675;
|
||||
private static final int ID_TIMECODE = 0x67;
|
||||
private static final int ID_SIMPLE_BLOCK = 0x23;
|
||||
private static final int ID_BLOCK = 0x21;
|
||||
private static final int ID_GROUP_BLOCK = 0x20;
|
||||
|
||||
|
||||
public enum TrackKind {
|
||||
@ -57,7 +56,7 @@ public class WebMReader {
|
||||
private boolean done;
|
||||
private boolean firstSegment;
|
||||
|
||||
public WebMReader(SharpStream source) {
|
||||
public WebMReader(final SharpStream source) {
|
||||
this.stream = new DataReader(source);
|
||||
}
|
||||
|
||||
@ -68,7 +67,7 @@ public class WebMReader {
|
||||
}
|
||||
ensure(elem);
|
||||
|
||||
elem = untilElement(null, ID_Segment);
|
||||
elem = untilElement(null, ID_SEGMENT);
|
||||
if (elem == null) {
|
||||
throw new IOException("Fragment element not found");
|
||||
}
|
||||
@ -83,7 +82,7 @@ public class WebMReader {
|
||||
return tracks;
|
||||
}
|
||||
|
||||
public WebMTrack selectTrack(int index) {
|
||||
public WebMTrack selectTrack(final int index) {
|
||||
selectedTrack = index;
|
||||
return tracks[index];
|
||||
}
|
||||
@ -100,7 +99,7 @@ public class WebMReader {
|
||||
|
||||
ensure(segment.ref);
|
||||
// WARNING: track cannot be the same or have different index in new segments
|
||||
Element elem = untilElement(null, ID_Segment);
|
||||
Element elem = untilElement(null, ID_SEGMENT);
|
||||
if (elem == null) {
|
||||
done = true;
|
||||
return null;
|
||||
@ -110,9 +109,7 @@ public class WebMReader {
|
||||
return segment;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private long readNumber(Element parent) throws IOException {
|
||||
private long readNumber(final Element parent) throws IOException {
|
||||
int length = (int) parent.contentSize;
|
||||
long value = 0;
|
||||
while (length-- > 0) {
|
||||
@ -125,11 +122,11 @@ public class WebMReader {
|
||||
return value;
|
||||
}
|
||||
|
||||
private String readString(Element parent) throws IOException {
|
||||
return new String(readBlob(parent), StandardCharsets.UTF_8);// or use "utf-8"
|
||||
private String readString(final Element parent) throws IOException {
|
||||
return new String(readBlob(parent), StandardCharsets.UTF_8); // or use "utf-8"
|
||||
}
|
||||
|
||||
private byte[] readBlob(Element parent) throws IOException {
|
||||
private byte[] readBlob(final Element parent) throws IOException {
|
||||
long length = parent.contentSize;
|
||||
byte[] buffer = new byte[(int) length];
|
||||
int read = stream.read(buffer);
|
||||
@ -180,16 +177,17 @@ public class WebMReader {
|
||||
return elem;
|
||||
}
|
||||
|
||||
private Element readElement(int expected) throws IOException {
|
||||
private Element readElement(final int expected) throws IOException {
|
||||
Element elem = readElement();
|
||||
if (expected != 0 && elem.type != expected) {
|
||||
throw new NoSuchElementException("expected " + elementID(expected) + " found " + elementID(elem.type));
|
||||
throw new NoSuchElementException("expected " + elementID(expected)
|
||||
+ " found " + elementID(elem.type));
|
||||
}
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
private Element untilElement(Element ref, int... expected) throws IOException {
|
||||
private Element untilElement(final Element ref, final int... expected) throws IOException {
|
||||
Element elem;
|
||||
while (ref == null ? stream.available() : (stream.position() < (ref.offset + ref.size))) {
|
||||
elem = readElement();
|
||||
@ -208,11 +206,11 @@ public class WebMReader {
|
||||
return null;
|
||||
}
|
||||
|
||||
private String elementID(long type) {
|
||||
private String elementID(final long type) {
|
||||
return "0x".concat(Long.toHexString(type));
|
||||
}
|
||||
|
||||
private void ensure(Element ref) throws IOException {
|
||||
private void ensure(final Element ref) throws IOException {
|
||||
long skip = (ref.offset + ref.size) - stream.position();
|
||||
|
||||
if (skip == 0) {
|
||||
@ -227,10 +225,9 @@ public class WebMReader {
|
||||
stream.skipBytes(skip);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private boolean readEbml(Element ref, int minReadVersion, int minDocTypeVersion) throws IOException {
|
||||
Element elem = untilElement(ref, ID_EMBLReadVersion);
|
||||
private boolean readEbml(final Element ref, final int minReadVersion,
|
||||
final int minDocTypeVersion) throws IOException {
|
||||
Element elem = untilElement(ref, ID_EMBL_READ_VERSION);
|
||||
if (elem == null) {
|
||||
return false;
|
||||
}
|
||||
@ -238,28 +235,28 @@ public class WebMReader {
|
||||
return false;
|
||||
}
|
||||
|
||||
elem = untilElement(ref, ID_EMBLDocType);
|
||||
elem = untilElement(ref, ID_EMBL_DOC_TYPE);
|
||||
if (elem == null) {
|
||||
return false;
|
||||
}
|
||||
if (!readString(elem).equals("webm")) {
|
||||
return false;
|
||||
}
|
||||
elem = untilElement(ref, ID_EMBLDocTypeReadVersion);
|
||||
elem = untilElement(ref, ID_EMBL_DOC_TYPE_READ_VERSION);
|
||||
|
||||
return elem != null && readNumber(elem) <= minDocTypeVersion;
|
||||
}
|
||||
|
||||
private Info readInfo(Element ref) throws IOException {
|
||||
private Info readInfo(final Element ref) throws IOException {
|
||||
Element elem;
|
||||
Info info = new Info();
|
||||
|
||||
while ((elem = untilElement(ref, ID_TimecodeScale, ID_Duration)) != null) {
|
||||
while ((elem = untilElement(ref, ID_TIMECODE_SCALE, ID_DURATION)) != null) {
|
||||
switch (elem.type) {
|
||||
case ID_TimecodeScale:
|
||||
case ID_TIMECODE_SCALE:
|
||||
info.timecodeScale = readNumber(elem);
|
||||
break;
|
||||
case ID_Duration:
|
||||
case ID_DURATION:
|
||||
info.duration = readNumber(elem);
|
||||
break;
|
||||
}
|
||||
@ -273,19 +270,20 @@ public class WebMReader {
|
||||
return info;
|
||||
}
|
||||
|
||||
private Segment readSegment(Element ref, int trackLacingExpected, boolean metadataExpected) throws IOException {
|
||||
private Segment readSegment(final Element ref, final int trackLacingExpected,
|
||||
final boolean metadataExpected) throws IOException {
|
||||
Segment obj = new Segment(ref);
|
||||
Element elem;
|
||||
while ((elem = untilElement(ref, ID_Info, ID_Tracks, ID_Cluster)) != null) {
|
||||
if (elem.type == ID_Cluster) {
|
||||
while ((elem = untilElement(ref, ID_INFO, ID_TRACKS, ID_CLUSTER)) != null) {
|
||||
if (elem.type == ID_CLUSTER) {
|
||||
obj.currentCluster = elem;
|
||||
break;
|
||||
}
|
||||
switch (elem.type) {
|
||||
case ID_Info:
|
||||
case ID_INFO:
|
||||
obj.info = readInfo(elem);
|
||||
break;
|
||||
case ID_Tracks:
|
||||
case ID_TRACKS:
|
||||
obj.tracks = readTracks(elem, trackLacingExpected);
|
||||
break;
|
||||
}
|
||||
@ -293,48 +291,50 @@ public class WebMReader {
|
||||
}
|
||||
|
||||
if (metadataExpected && (obj.info == null || obj.tracks == null)) {
|
||||
throw new RuntimeException("Cluster element found without Info and/or Tracks element at position " + String.valueOf(ref.offset));
|
||||
throw new RuntimeException(
|
||||
"Cluster element found without Info and/or Tracks element at position "
|
||||
+ String.valueOf(ref.offset));
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
private WebMTrack[] readTracks(Element ref, int lacingExpected) throws IOException {
|
||||
private WebMTrack[] readTracks(final Element ref, final int lacingExpected) throws IOException {
|
||||
ArrayList<WebMTrack> trackEntries = new ArrayList<>(2);
|
||||
Element elem_trackEntry;
|
||||
Element elemTrackEntry;
|
||||
|
||||
while ((elem_trackEntry = untilElement(ref, ID_TrackEntry)) != null) {
|
||||
while ((elemTrackEntry = untilElement(ref, ID_TRACK_ENTRY)) != null) {
|
||||
WebMTrack entry = new WebMTrack();
|
||||
boolean drop = false;
|
||||
Element elem;
|
||||
while ((elem = untilElement(elem_trackEntry)) != null) {
|
||||
while ((elem = untilElement(elemTrackEntry)) != null) {
|
||||
switch (elem.type) {
|
||||
case ID_TrackNumber:
|
||||
case ID_TRACK_NUMBER:
|
||||
entry.trackNumber = readNumber(elem);
|
||||
break;
|
||||
case ID_TrackType:
|
||||
case ID_TRACK_TYPE:
|
||||
entry.trackType = (int) readNumber(elem);
|
||||
break;
|
||||
case ID_CodecID:
|
||||
case ID_CODEC_ID:
|
||||
entry.codecId = readString(elem);
|
||||
break;
|
||||
case ID_CodecPrivate:
|
||||
case ID_CODEC_PRIVATE:
|
||||
entry.codecPrivate = readBlob(elem);
|
||||
break;
|
||||
case ID_Audio:
|
||||
case ID_Video:
|
||||
case ID_AUDIO:
|
||||
case ID_VIDEO:
|
||||
entry.bMetadata = readBlob(elem);
|
||||
break;
|
||||
case ID_DefaultDuration:
|
||||
case ID_DEFAULT_DURATION:
|
||||
entry.defaultDuration = readNumber(elem);
|
||||
break;
|
||||
case ID_FlagLacing:
|
||||
case ID_FLAG_LACING:
|
||||
drop = readNumber(elem) != lacingExpected;
|
||||
break;
|
||||
case ID_CodecDelay:
|
||||
case ID_CODEC_DELAY:
|
||||
entry.codecDelay = readNumber(elem);
|
||||
break;
|
||||
case ID_SeekPreRoll:
|
||||
case ID_SEEK_PRE_ROLL:
|
||||
entry.seekPreRoll = readNumber(elem);
|
||||
break;
|
||||
default:
|
||||
@ -345,7 +345,7 @@ public class WebMReader {
|
||||
if (!drop) {
|
||||
trackEntries.add(entry);
|
||||
}
|
||||
ensure(elem_trackEntry);
|
||||
ensure(elemTrackEntry);
|
||||
}
|
||||
|
||||
WebMTrack[] entries = new WebMTrack[trackEntries.size()];
|
||||
@ -368,37 +368,36 @@ public class WebMReader {
|
||||
return entries;
|
||||
}
|
||||
|
||||
private SimpleBlock readSimpleBlock(Element ref) throws IOException {
|
||||
private SimpleBlock readSimpleBlock(final Element ref) throws IOException {
|
||||
SimpleBlock obj = new SimpleBlock(ref);
|
||||
obj.trackNumber = readEncodedNumber();
|
||||
obj.relativeTimeCode = stream.readShort();
|
||||
obj.flags = (byte) stream.read();
|
||||
obj.dataSize = (int) ((ref.offset + ref.size) - stream.position());
|
||||
obj.createdFromBlock = ref.type == ID_Block;
|
||||
obj.createdFromBlock = ref.type == ID_BLOCK;
|
||||
|
||||
// NOTE: lacing is not implemented, and will be mixed with the stream data
|
||||
if (obj.dataSize < 0) {
|
||||
throw new IOException(String.format("Unexpected SimpleBlock element size, missing %s bytes", -obj.dataSize));
|
||||
throw new IOException(String.format(
|
||||
"Unexpected SimpleBlock element size, missing %s bytes", -obj.dataSize));
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
private Cluster readCluster(Element ref) throws IOException {
|
||||
private Cluster readCluster(final Element ref) throws IOException {
|
||||
Cluster obj = new Cluster(ref);
|
||||
|
||||
Element elem = untilElement(ref, ID_Timecode);
|
||||
Element elem = untilElement(ref, ID_TIMECODE);
|
||||
if (elem == null) {
|
||||
throw new NoSuchElementException("Cluster at " + String.valueOf(ref.offset) + " without Timecode element");
|
||||
throw new NoSuchElementException("Cluster at " + String.valueOf(ref.offset)
|
||||
+ " without Timecode element");
|
||||
}
|
||||
obj.timecode = readNumber(elem);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
|
||||
class Element {
|
||||
|
||||
int type;
|
||||
long offset;
|
||||
long contentSize;
|
||||
@ -406,13 +405,11 @@ public class WebMReader {
|
||||
}
|
||||
|
||||
public class Info {
|
||||
|
||||
public long timecodeScale;
|
||||
public long duration;
|
||||
}
|
||||
|
||||
public class WebMTrack {
|
||||
|
||||
public long trackNumber;
|
||||
protected int trackType;
|
||||
public String codecId;
|
||||
@ -425,8 +422,7 @@ public class WebMReader {
|
||||
}
|
||||
|
||||
public class Segment {
|
||||
|
||||
Segment(Element ref) {
|
||||
Segment(final Element ref) {
|
||||
this.ref = ref;
|
||||
this.firstClusterInSegment = true;
|
||||
}
|
||||
@ -447,7 +443,7 @@ public class WebMReader {
|
||||
}
|
||||
ensure(segment.currentCluster);
|
||||
|
||||
Element elem = untilElement(segment.ref, ID_Cluster);
|
||||
Element elem = untilElement(segment.ref, ID_CLUSTER);
|
||||
if (elem == null) {
|
||||
return null;
|
||||
}
|
||||
@ -459,11 +455,10 @@ public class WebMReader {
|
||||
}
|
||||
|
||||
public class SimpleBlock {
|
||||
|
||||
public InputStream data;
|
||||
public boolean createdFromBlock;
|
||||
|
||||
SimpleBlock(Element ref) {
|
||||
SimpleBlock(final Element ref) {
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
@ -480,13 +475,12 @@ public class WebMReader {
|
||||
}
|
||||
|
||||
public class Cluster {
|
||||
|
||||
Element ref;
|
||||
SimpleBlock currentSimpleBlock = null;
|
||||
Element currentBlockGroup = null;
|
||||
public long timecode;
|
||||
|
||||
Cluster(Element ref) {
|
||||
Cluster(final Element ref) {
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
@ -508,14 +502,14 @@ public class WebMReader {
|
||||
}
|
||||
|
||||
while (!insideClusterBounds()) {
|
||||
Element elem = untilElement(ref, ID_SimpleBlock, ID_GroupBlock);
|
||||
Element elem = untilElement(ref, ID_SIMPLE_BLOCK, ID_GROUP_BLOCK);
|
||||
if (elem == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (elem.type == ID_GroupBlock) {
|
||||
if (elem.type == ID_GROUP_BLOCK) {
|
||||
currentBlockGroup = elem;
|
||||
elem = untilElement(currentBlockGroup, ID_Block);
|
||||
elem = untilElement(currentBlockGroup, ID_BLOCK);
|
||||
|
||||
if (elem == null) {
|
||||
ensure(currentBlockGroup);
|
||||
@ -529,7 +523,8 @@ public class WebMReader {
|
||||
currentSimpleBlock.data = stream.getView((int) currentSimpleBlock.dataSize);
|
||||
|
||||
// calculate the timestamp in nanoseconds
|
||||
currentSimpleBlock.absoluteTimeCodeNs = currentSimpleBlock.relativeTimeCode + this.timecode;
|
||||
currentSimpleBlock.absoluteTimeCodeNs = currentSimpleBlock.relativeTimeCode
|
||||
+ this.timecode;
|
||||
currentSimpleBlock.absoluteTimeCodeNs *= segment.info.timecodeScale;
|
||||
|
||||
return currentSimpleBlock;
|
||||
@ -537,10 +532,7 @@ public class WebMReader {
|
||||
|
||||
ensure(elem);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,14 +19,13 @@ import java.util.ArrayList;
|
||||
* @author kapodamy
|
||||
*/
|
||||
public class WebMWriter implements Closeable {
|
||||
|
||||
private final static int BUFFER_SIZE = 8 * 1024;
|
||||
private final static int DEFAULT_TIMECODE_SCALE = 1000000;
|
||||
private final static int INTERV = 100;// 100ms on 1000000us timecode scale
|
||||
private final static int DEFAULT_CUES_EACH_MS = 5000;// 5000ms on 1000000us timecode scale
|
||||
private final static byte CLUSTER_HEADER_SIZE = 8;
|
||||
private final static int CUE_RESERVE_SIZE = 65535;
|
||||
private final static byte MINIMUM_EBML_VOID_SIZE = 4;
|
||||
private static final int BUFFER_SIZE = 8 * 1024;
|
||||
private static final int DEFAULT_TIMECODE_SCALE = 1000000;
|
||||
private static final int INTERV = 100; // 100ms on 1000000us timecode scale
|
||||
private static final int DEFAULT_CUES_EACH_MS = 5000; // 5000ms on 1000000us timecode scale
|
||||
private static final byte CLUSTER_HEADER_SIZE = 8;
|
||||
private static final int CUE_RESERVE_SIZE = 65535;
|
||||
private static final byte MINIMUM_EBML_VOID_SIZE = 4;
|
||||
|
||||
private WebMReader.WebMTrack[] infoTracks;
|
||||
private SharpStream[] sourceTracks;
|
||||
@ -46,7 +45,7 @@ public class WebMWriter implements Closeable {
|
||||
private byte[] outBuffer;
|
||||
private ByteBuffer outByteBuffer;
|
||||
|
||||
public WebMWriter(SharpStream... source) {
|
||||
public WebMWriter(final SharpStream... source) {
|
||||
sourceTracks = source;
|
||||
readers = new WebMReader[sourceTracks.length];
|
||||
infoTracks = new WebMTrack[sourceTracks.length];
|
||||
@ -55,7 +54,7 @@ public class WebMWriter implements Closeable {
|
||||
clustersOffsetsSizes = new ArrayList<>(256);
|
||||
}
|
||||
|
||||
public WebMTrack[] getTracksFromSource(int sourceIndex) throws IllegalStateException {
|
||||
public WebMTrack[] getTracksFromSource(final int sourceIndex) throws IllegalStateException {
|
||||
if (done) {
|
||||
throw new IllegalStateException("already done");
|
||||
}
|
||||
@ -85,7 +84,7 @@ public class WebMWriter implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
public void selectTracks(int... trackIndex) throws IOException {
|
||||
public void selectTracks(final int... trackIndex) throws IOException {
|
||||
try {
|
||||
readersSegment = new Segment[readers.length];
|
||||
readersCluster = new Cluster[readers.length];
|
||||
@ -126,7 +125,7 @@ public class WebMWriter implements Closeable {
|
||||
clustersOffsetsSizes = null;
|
||||
}
|
||||
|
||||
public void build(SharpStream out) throws IOException, RuntimeException {
|
||||
public void build(final SharpStream out) throws IOException, RuntimeException {
|
||||
if (!out.canRewind()) {
|
||||
throw new IOException("The output stream must be allow seek");
|
||||
}
|
||||
@ -167,9 +166,9 @@ public class WebMWriter implements Closeable {
|
||||
listBuffer.add(new byte[]{
|
||||
0x15, 0x49, (byte) 0xa9, 0x66, (byte) 0xa2, 0x2a, (byte) 0xd7, (byte) 0xb1
|
||||
});
|
||||
listBuffer.add(encode(DEFAULT_TIMECODE_SCALE, true));// this value MUST NOT exceed 4 bytes
|
||||
listBuffer.add(encode(DEFAULT_TIMECODE_SCALE, true)); // this value MUST NOT exceed 4 bytes
|
||||
listBuffer.add(new byte[]{0x44, (byte) 0x89, (byte) 0x84,
|
||||
0x00, 0x00, 0x00, 0x00,// info.duration
|
||||
0x00, 0x00, 0x00, 0x00, // info.duration
|
||||
|
||||
/* MuxingApp */
|
||||
0x4d, (byte) 0x80, (byte) 0x87, 0x4E,
|
||||
@ -187,16 +186,17 @@ public class WebMWriter implements Closeable {
|
||||
|
||||
// reserve space for Cues element
|
||||
long cueOffset = written;
|
||||
make_EBML_void(out, CUE_RESERVE_SIZE, true);
|
||||
makeEbmlVoid(out, CUE_RESERVE_SIZE, true);
|
||||
|
||||
int[] defaultSampleDuration = new int[infoTracks.length];
|
||||
long[] duration = new long[infoTracks.length];
|
||||
|
||||
for (int i = 0; i < infoTracks.length; i++) {
|
||||
if (infoTracks[i].defaultDuration < 0) {
|
||||
defaultSampleDuration[i] = -1;// not available
|
||||
defaultSampleDuration[i] = -1; // not available
|
||||
} else {
|
||||
defaultSampleDuration[i] = (int) Math.ceil(infoTracks[i].defaultDuration / (float) DEFAULT_TIMECODE_SCALE);
|
||||
defaultSampleDuration[i] = (int) Math.ceil(infoTracks[i].defaultDuration
|
||||
/ (float) DEFAULT_TIMECODE_SCALE);
|
||||
}
|
||||
duration[i] = -1;
|
||||
}
|
||||
@ -228,7 +228,7 @@ public class WebMWriter implements Closeable {
|
||||
}
|
||||
|
||||
if (bloq.data == null) {
|
||||
blockWritten = 1;// fake block
|
||||
blockWritten = 1; // fake block
|
||||
newClusterByTrackId = i;
|
||||
i++;
|
||||
continue;
|
||||
@ -239,15 +239,18 @@ public class WebMWriter implements Closeable {
|
||||
newClusterByTrackId = -1;
|
||||
baseTimecode = bloq.absoluteTimecode;
|
||||
limitTimecode = baseTimecode + INTERV;
|
||||
currentClusterOffset = makeCluster(out, baseTimecode, currentClusterOffset, true);
|
||||
currentClusterOffset = makeCluster(out, baseTimecode, currentClusterOffset,
|
||||
true);
|
||||
}
|
||||
|
||||
if (cuesForTrackId == i) {
|
||||
if ((nextCueTime > -1 && bloq.absoluteTimecode >= nextCueTime) || (nextCueTime < 0 && bloq.isKeyframe())) {
|
||||
if ((nextCueTime > -1 && bloq.absoluteTimecode >= nextCueTime)
|
||||
|| (nextCueTime < 0 && bloq.isKeyframe())) {
|
||||
if (nextCueTime > -1) {
|
||||
nextCueTime += DEFAULT_CUES_EACH_MS;
|
||||
}
|
||||
keyFrames.add(new KeyFrame(segmentOffset, currentClusterOffset, written, bloq.absoluteTimecode));
|
||||
keyFrames.add(new KeyFrame(segmentOffset, currentClusterOffset, written,
|
||||
bloq.absoluteTimecode));
|
||||
}
|
||||
}
|
||||
|
||||
@ -255,7 +258,8 @@ public class WebMWriter implements Closeable {
|
||||
blockWritten++;
|
||||
|
||||
if (defaultSampleDuration[i] < 0 && duration[i] >= 0) {
|
||||
// if the sample duration in unknown, calculate using current_duration - previous_duration
|
||||
// if the sample duration in unknown,
|
||||
// calculate using current_duration - previous_duration
|
||||
defaultSampleDuration[i] = (int) (bloq.absoluteTimecode - duration[i]);
|
||||
}
|
||||
duration[i] = bloq.absoluteTimecode;
|
||||
@ -305,20 +309,20 @@ public class WebMWriter implements Closeable {
|
||||
|
||||
/* Cue */
|
||||
short cueSize = 0;
|
||||
dump(new byte[]{0x1c, 0x53, (byte) 0xbb, 0x6b, 0x20, 0x00, 0x00}, out);// header size is 7
|
||||
dump(new byte[]{0x1c, 0x53, (byte) 0xbb, 0x6b, 0x20, 0x00, 0x00}, out); // header size is 7
|
||||
|
||||
for (KeyFrame keyFrame : keyFrames) {
|
||||
int size = makeCuePoint(cuesForTrackId, keyFrame, outBuffer);
|
||||
|
||||
if ((cueSize + size + 7 + MINIMUM_EBML_VOID_SIZE) > CUE_RESERVE_SIZE) {
|
||||
break;// no space left
|
||||
break; // no space left
|
||||
}
|
||||
|
||||
cueSize += size;
|
||||
dump(outBuffer, size, out);
|
||||
}
|
||||
|
||||
make_EBML_void(out, CUE_RESERVE_SIZE - cueSize - 7, false);
|
||||
makeEbmlVoid(out, CUE_RESERVE_SIZE - cueSize - 7, false);
|
||||
|
||||
seekTo(out, cueOffset + 5);
|
||||
outByteBuffer.putShort(0, cueSize);
|
||||
@ -332,11 +336,11 @@ public class WebMWriter implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private Block getNextBlockFrom(int internalTrackId) throws IOException {
|
||||
private Block getNextBlockFrom(final int internalTrackId) throws IOException {
|
||||
if (readersSegment[internalTrackId] == null) {
|
||||
readersSegment[internalTrackId] = readers[internalTrackId].getNextSegment();
|
||||
if (readersSegment[internalTrackId] == null) {
|
||||
return null;// no more blocks in the selected track
|
||||
return null; // no more blocks in the selected track
|
||||
}
|
||||
}
|
||||
|
||||
@ -351,7 +355,7 @@ public class WebMWriter implements Closeable {
|
||||
SimpleBlock res = readersCluster[internalTrackId].getNextSimpleBlock();
|
||||
if (res == null) {
|
||||
readersCluster[internalTrackId] = null;
|
||||
return new Block();// fake block to indicate the end of the cluster
|
||||
return new Block(); // fake block to indicate the end of the cluster
|
||||
}
|
||||
|
||||
Block bloq = new Block();
|
||||
@ -364,7 +368,7 @@ public class WebMWriter implements Closeable {
|
||||
return bloq;
|
||||
}
|
||||
|
||||
private void seekTo(SharpStream stream, long offset) throws IOException {
|
||||
private void seekTo(final SharpStream stream, final long offset) throws IOException {
|
||||
if (stream.canSeek()) {
|
||||
stream.seek(offset);
|
||||
} else {
|
||||
@ -379,13 +383,15 @@ public class WebMWriter implements Closeable {
|
||||
written = offset;
|
||||
}
|
||||
|
||||
private void writeInt(SharpStream stream, long offset, int number) throws IOException {
|
||||
private void writeInt(final SharpStream stream, final long offset, final int number)
|
||||
throws IOException {
|
||||
seekTo(stream, offset);
|
||||
outByteBuffer.putInt(0, number);
|
||||
dump(outBuffer, DataReader.INTEGER_SIZE, stream);
|
||||
}
|
||||
|
||||
private void writeBlock(SharpStream stream, Block bloq, long clusterTimecode) throws IOException {
|
||||
private void writeBlock(final SharpStream stream, final Block bloq, final long clusterTimecode)
|
||||
throws IOException {
|
||||
long relativeTimeCode = bloq.absoluteTimecode - clusterTimecode;
|
||||
|
||||
if (relativeTimeCode < Short.MIN_VALUE || relativeTimeCode > Short.MAX_VALUE) {
|
||||
@ -394,9 +400,10 @@ public class WebMWriter implements Closeable {
|
||||
|
||||
ArrayList<byte[]> listBuffer = new ArrayList<>(5);
|
||||
listBuffer.add(new byte[]{(byte) 0xa3});
|
||||
listBuffer.add(null);// block size
|
||||
listBuffer.add(null); // block size
|
||||
listBuffer.add(encode(bloq.trackNumber + 1, false));
|
||||
listBuffer.add(ByteBuffer.allocate(DataReader.SHORT_SIZE).putShort((short) relativeTimeCode).array());
|
||||
listBuffer.add(ByteBuffer.allocate(DataReader.SHORT_SIZE).putShort((short) relativeTimeCode)
|
||||
.array());
|
||||
listBuffer.add(new byte[]{bloq.flags});
|
||||
|
||||
int blockSize = bloq.dataSize;
|
||||
@ -413,7 +420,8 @@ public class WebMWriter implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private long makeCluster(SharpStream stream, long timecode, long offset, boolean create) throws IOException {
|
||||
private long makeCluster(final SharpStream stream, final long timecode, long offset,
|
||||
final boolean create) throws IOException {
|
||||
ClusterInfo cluster;
|
||||
|
||||
if (offset > 0) {
|
||||
@ -444,7 +452,7 @@ public class WebMWriter implements Closeable {
|
||||
return offset;
|
||||
}
|
||||
|
||||
private void makeEBML(SharpStream stream) throws IOException {
|
||||
private void makeEBML(final SharpStream stream) throws IOException {
|
||||
// deafult values
|
||||
dump(new byte[]{
|
||||
0x1A, 0x45, (byte) 0xDF, (byte) 0xA3, 0x01, 0x00, 0x00, 0x00,
|
||||
@ -468,7 +476,7 @@ public class WebMWriter implements Closeable {
|
||||
return lengthFor(buffer);
|
||||
}
|
||||
|
||||
private ArrayList<byte[]> makeTrackEntry(int internalTrackId, WebMTrack track) {
|
||||
private ArrayList<byte[]> makeTrackEntry(final int internalTrackId, final WebMTrack track) {
|
||||
byte[] id = encode(internalTrackId + 1, true);
|
||||
ArrayList<byte[]> buffer = new ArrayList<>(12);
|
||||
|
||||
@ -531,10 +539,10 @@ public class WebMWriter implements Closeable {
|
||||
}
|
||||
|
||||
return lengthFor(buffer);
|
||||
|
||||
}
|
||||
|
||||
private int makeCuePoint(int internalTrackId, KeyFrame keyFrame, byte[] buffer) {
|
||||
private int makeCuePoint(final int internalTrackId, final KeyFrame keyFrame,
|
||||
final byte[] buffer) {
|
||||
ArrayList<byte[]> cue = new ArrayList<>(5);
|
||||
|
||||
/* CuePoint */
|
||||
@ -559,7 +567,8 @@ public class WebMWriter implements Closeable {
|
||||
return size;
|
||||
}
|
||||
|
||||
private ArrayList<byte[]> makeCueTrackPosition(int internalTrackId, KeyFrame keyFrame) {
|
||||
private ArrayList<byte[]> makeCueTrackPosition(final int internalTrackId,
|
||||
final KeyFrame keyFrame) {
|
||||
ArrayList<byte[]> buffer = new ArrayList<>(8);
|
||||
|
||||
/* CueTrackPositions */
|
||||
@ -583,7 +592,8 @@ public class WebMWriter implements Closeable {
|
||||
return lengthFor(buffer);
|
||||
}
|
||||
|
||||
private void make_EBML_void(SharpStream out, int size, boolean wipe) throws IOException {
|
||||
private void makeEbmlVoid(final SharpStream out, int size, final boolean wipe)
|
||||
throws IOException {
|
||||
/* ebml void */
|
||||
outByteBuffer.putShort(0, (short) 0xec20);
|
||||
outByteBuffer.putShort(2, (short) (size - 4));
|
||||
@ -600,23 +610,25 @@ public class WebMWriter implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private void dump(byte[] buffer, SharpStream stream) throws IOException {
|
||||
private void dump(final byte[] buffer, final SharpStream stream) throws IOException {
|
||||
dump(buffer, buffer.length, stream);
|
||||
}
|
||||
|
||||
private void dump(byte[] buffer, int count, SharpStream stream) throws IOException {
|
||||
private void dump(final byte[] buffer, final int count, final SharpStream stream)
|
||||
throws IOException {
|
||||
stream.write(buffer, 0, count);
|
||||
written += count;
|
||||
}
|
||||
|
||||
private void dump(ArrayList<byte[]> buffers, SharpStream stream) throws IOException {
|
||||
private void dump(final ArrayList<byte[]> buffers, final SharpStream stream)
|
||||
throws IOException {
|
||||
for (byte[] buffer : buffers) {
|
||||
stream.write(buffer);
|
||||
written += buffer.length;
|
||||
}
|
||||
}
|
||||
|
||||
private ArrayList<byte[]> lengthFor(ArrayList<byte[]> buffer) {
|
||||
private ArrayList<byte[]> lengthFor(final ArrayList<byte[]> buffer) {
|
||||
long size = 0;
|
||||
for (int i = 2; i < buffer.size(); i++) {
|
||||
size += buffer.get(i).length;
|
||||
@ -625,7 +637,7 @@ public class WebMWriter implements Closeable {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private byte[] encode(long number, boolean withLength) {
|
||||
private byte[] encode(final long number, final boolean withLength) {
|
||||
int length = -1;
|
||||
for (int i = 1; i <= 7; i++) {
|
||||
if (number < Math.pow(2, 7 * i)) {
|
||||
@ -662,9 +674,9 @@ public class WebMWriter implements Closeable {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private ArrayList<byte[]> encode(String value) {
|
||||
private ArrayList<byte[]> encode(final String value) {
|
||||
byte[] str;
|
||||
str = value.getBytes(StandardCharsets.UTF_8);// or use "utf-8"
|
||||
str = value.getBytes(StandardCharsets.UTF_8); // or use "utf-8"
|
||||
|
||||
ArrayList<byte[]> buffer = new ArrayList<>(2);
|
||||
buffer.add(encode(str.length, false));
|
||||
@ -673,7 +685,7 @@ public class WebMWriter implements Closeable {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private boolean valid(byte[] buffer) {
|
||||
private boolean valid(final byte[] buffer) {
|
||||
return buffer != null && buffer.length > 0;
|
||||
}
|
||||
|
||||
@ -717,8 +729,7 @@ public class WebMWriter implements Closeable {
|
||||
}
|
||||
|
||||
class KeyFrame {
|
||||
|
||||
KeyFrame(long segment, long cluster, long block, long timecode) {
|
||||
KeyFrame(final long segment, final long cluster, final long block, final long timecode) {
|
||||
clusterPosition = cluster - segment;
|
||||
relativePosition = (int) (block - cluster - CLUSTER_HEADER_SIZE);
|
||||
duration = timecode;
|
||||
@ -730,7 +741,6 @@ public class WebMWriter implements Closeable {
|
||||
}
|
||||
|
||||
class Block {
|
||||
|
||||
InputStream data;
|
||||
int trackNumber;
|
||||
byte flags;
|
||||
@ -744,14 +754,13 @@ public class WebMWriter implements Closeable {
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("trackNumber=%s isKeyFrame=%S absoluteTimecode=%s", trackNumber, isKeyframe(), absoluteTimecode);
|
||||
return String.format("trackNumber=%s isKeyFrame=%S absoluteTimecode=%s", trackNumber,
|
||||
isKeyframe(), absoluteTimecode);
|
||||
}
|
||||
}
|
||||
|
||||
class ClusterInfo {
|
||||
|
||||
long offset;
|
||||
int size;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,15 +4,14 @@ import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* based on c#
|
||||
* Based on C#'s Stream class.
|
||||
*/
|
||||
public abstract class SharpStream implements Closeable {
|
||||
|
||||
public abstract int read() throws IOException;
|
||||
|
||||
public abstract int read(byte buffer[]) throws IOException;
|
||||
public abstract int read(byte[] buffer) throws IOException;
|
||||
|
||||
public abstract int read(byte buffer[], int offset, int count) throws IOException;
|
||||
public abstract int read(byte[] buffer, int offset, int count) throws IOException;
|
||||
|
||||
public abstract long skip(long amount) throws IOException;
|
||||
|
||||
@ -49,11 +48,11 @@ public abstract class SharpStream implements Closeable {
|
||||
// STUB
|
||||
}
|
||||
|
||||
public void setLength(long length) throws IOException {
|
||||
public void setLength(final long length) throws IOException {
|
||||
throw new IOException("Not implemented");
|
||||
}
|
||||
|
||||
public void seek(long offset) throws IOException {
|
||||
public void seek(final long offset) throws IOException {
|
||||
throw new IOException("Not implemented");
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,38 @@
|
||||
<suppress checks="FinalParameters"
|
||||
files="LocalItemListAdapter.java"
|
||||
lines="220,292"/>
|
||||
|
||||
<suppress checks="FinalParameters"
|
||||
files="InfoListAdapter.java"
|
||||
lines="253,325"/>
|
||||
|
||||
<!-- org.schabi.newpipe.streams -->
|
||||
<suppress checks="FinalParameters"
|
||||
files="WebMWriter.java"
|
||||
lines="423,595"/>
|
||||
<suppress checks="LineLength"
|
||||
files="WebMWriter.java"
|
||||
lines="160,162"/>
|
||||
|
||||
<suppress checks="FinalParameters"
|
||||
files="OggFromWebMWriter.java"
|
||||
lines="378,420"/>
|
||||
<suppress checks="LineLength"
|
||||
files="OggFromWebMWriter.java"
|
||||
lines="292,296"/>
|
||||
|
||||
<suppress checks="FinalParameters"
|
||||
files="Mp4FromDashWriter.java"
|
||||
lines="643"/>
|
||||
<suppress checks="LineLength"
|
||||
files="Mp4FromDashWriter.java"
|
||||
lines="677,678,720-724,738,762,848,850-855"/>
|
||||
|
||||
<suppress checks="InnerAssignment"
|
||||
files="Mp4DashReader.java"
|
||||
lines="190"/>
|
||||
|
||||
<suppress checks="FinalParameters"
|
||||
files="DataReader.java"
|
||||
lines="46,93"/>
|
||||
</suppressions>
|
||||
|
Loading…
Reference in New Issue
Block a user