mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2024-12-23 00:20:32 +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,431 +1,428 @@
|
||||
package org.schabi.newpipe.streams;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.schabi.newpipe.streams.WebMReader.Cluster;
|
||||
import org.schabi.newpipe.streams.WebMReader.Segment;
|
||||
import org.schabi.newpipe.streams.WebMReader.SimpleBlock;
|
||||
import org.schabi.newpipe.streams.WebMReader.WebMTrack;
|
||||
import org.schabi.newpipe.streams.io.SharpStream;
|
||||
|
||||
import java.io.Closeable;
|
||||
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 final static int TIME_SCALE_NS = 1000000000;
|
||||
|
||||
private boolean done = false;
|
||||
private boolean parsed = false;
|
||||
|
||||
private SharpStream source;
|
||||
private SharpStream output;
|
||||
|
||||
private int sequence_count = 0;
|
||||
private final int STREAM_ID;
|
||||
private byte packet_flag = 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 long webm_block_last_timecode = 0;
|
||||
private long webm_block_near_duration = 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 final int[] crc32_table = new int[256];
|
||||
|
||||
public OggFromWebMWriter(@NonNull SharpStream source, @NonNull SharpStream target) {
|
||||
if (!source.canRead() || !source.canRewind()) {
|
||||
throw new IllegalArgumentException("source stream must be readable and allows seeking");
|
||||
}
|
||||
if (!target.canWrite() || !target.canRewind()) {
|
||||
throw new IllegalArgumentException("output stream must be writable and allows seeking");
|
||||
}
|
||||
|
||||
this.source = source;
|
||||
this.output = target;
|
||||
|
||||
this.STREAM_ID = (int) System.currentTimeMillis();
|
||||
|
||||
populate_crc32_table();
|
||||
}
|
||||
|
||||
public boolean isDone() {
|
||||
return done;
|
||||
}
|
||||
|
||||
public boolean isParsed() {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
public WebMTrack[] getTracksFromSource() throws IllegalStateException {
|
||||
if (!parsed) {
|
||||
throw new IllegalStateException("source must be parsed first");
|
||||
}
|
||||
|
||||
return webm.getAvailableTracks();
|
||||
}
|
||||
|
||||
public void parseSource() throws IOException, IllegalStateException {
|
||||
if (done) {
|
||||
throw new IllegalStateException("already done");
|
||||
}
|
||||
if (parsed) {
|
||||
throw new IllegalStateException("already parsed");
|
||||
}
|
||||
|
||||
try {
|
||||
webm = new WebMReader(source);
|
||||
webm.parse();
|
||||
webm_segment = webm.getNextSegment();
|
||||
} finally {
|
||||
parsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void selectTrack(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) {
|
||||
throw new IOException("tracks already selected");
|
||||
}
|
||||
|
||||
switch (webm.getAvailableTracks()[trackIndex].kind) {
|
||||
case Audio:
|
||||
case Video:
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("the track must an audio or video stream");
|
||||
}
|
||||
|
||||
try {
|
||||
webm_track = webm.selectTrack(trackIndex);
|
||||
} finally {
|
||||
parsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
done = true;
|
||||
parsed = true;
|
||||
|
||||
webm_track = null;
|
||||
webm = null;
|
||||
|
||||
if (!output.isClosed()) {
|
||||
output.flush();
|
||||
}
|
||||
|
||||
source.close();
|
||||
output.close();
|
||||
}
|
||||
|
||||
public void build() throws IOException {
|
||||
float resolution;
|
||||
SimpleBlock bloq;
|
||||
ByteBuffer header = ByteBuffer.allocate(27 + (255 * 255));
|
||||
ByteBuffer page = ByteBuffer.allocate(64 * 1024);
|
||||
|
||||
header.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
/* step 1: get the amount of frames per seconds */
|
||||
switch (webm_track.kind) {
|
||||
case Audio:
|
||||
resolution = getSampleFrequencyFromTrack(webm_track.bMetadata);
|
||||
if (resolution == 0f) {
|
||||
throw new RuntimeException("cannot get the audio sample rate");
|
||||
}
|
||||
break;
|
||||
case Video:
|
||||
// WARNING: untested
|
||||
if (webm_track.defaultDuration == 0) {
|
||||
throw new RuntimeException("missing default frame time");
|
||||
}
|
||||
resolution = 1000f / ((float) webm_track.defaultDuration / webm_segment.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);
|
||||
write(header);
|
||||
output.write(webm_track.codecPrivate);
|
||||
}
|
||||
|
||||
/* step 3: create packet with metadata */
|
||||
byte[] buffer = make_metadata();
|
||||
if (buffer != null) {
|
||||
addPacketSegment(buffer.length);
|
||||
make_packetHeader(0x00, header, buffer);
|
||||
write(header);
|
||||
output.write(buffer);
|
||||
}
|
||||
|
||||
/* step 4: calculate amount of packets */
|
||||
while (webm_segment != null) {
|
||||
bloq = getNextBlock();
|
||||
|
||||
if (bloq != null && addPacketSegment(bloq)) {
|
||||
int pos = page.position();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
bloq.data.read(page.array(), pos, bloq.dataSize);
|
||||
page.position(pos + bloq.dataSize);
|
||||
continue;
|
||||
}
|
||||
|
||||
// calculate the current packet duration using the next block
|
||||
double elapsed_ns = webm_track.codecDelay;
|
||||
|
||||
if (bloq == null) {
|
||||
packet_flag = FLAG_LAST;// note: if the flag is FLAG_CONTINUED, is changed
|
||||
elapsed_ns += webm_block_last_timecode;
|
||||
|
||||
if (webm_track.defaultDuration > 0) {
|
||||
elapsed_ns += webm_track.defaultDuration;
|
||||
} else {
|
||||
// hardcoded way, guess the sample duration
|
||||
elapsed_ns += webm_block_near_duration;
|
||||
}
|
||||
} else {
|
||||
elapsed_ns += bloq.absoluteTimeCodeNs;
|
||||
}
|
||||
|
||||
// get the sample count in the page
|
||||
elapsed_ns = elapsed_ns / TIME_SCALE_NS;
|
||||
elapsed_ns = Math.ceil(elapsed_ns * resolution);
|
||||
|
||||
// create header and calculate page checksum
|
||||
int checksum = make_packetHeader((long) elapsed_ns, header, null);
|
||||
checksum = calc_crc32(checksum, page.array(), page.position());
|
||||
|
||||
header.putInt(HEADER_CHECKSUM_OFFSET, checksum);
|
||||
|
||||
// dump data
|
||||
write(header);
|
||||
write(page);
|
||||
|
||||
webm_block = bloq;
|
||||
}
|
||||
}
|
||||
|
||||
private int make_packetHeader(long gran_pos, @NonNull ByteBuffer buffer, byte[] immediate_page) {
|
||||
short length = HEADER_SIZE;
|
||||
|
||||
buffer.putInt(0x5367674f);// "OggS" binary string in little-endian
|
||||
buffer.put((byte) 0x00);// version
|
||||
buffer.put(packet_flag);// type
|
||||
|
||||
buffer.putLong(gran_pos);// granulate position
|
||||
|
||||
buffer.putInt(STREAM_ID);// bitstream serial number
|
||||
buffer.putInt(sequence_count++);// page sequence number
|
||||
|
||||
buffer.putInt(0x00);// page checksum
|
||||
|
||||
buffer.put((byte) segment_table_size);// segment table
|
||||
buffer.put(segment_table, 0, segment_table_size);// segment size
|
||||
|
||||
length += segment_table_size;
|
||||
|
||||
clearSegmentTable();// clear segment table for next header
|
||||
|
||||
int checksum_crc32 = calc_crc32(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;
|
||||
}
|
||||
|
||||
return checksum_crc32;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private byte[] make_metadata() {
|
||||
if ("A_OPUS".equals(webm_track.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)
|
||||
};
|
||||
} else if ("A_VORBIS".equals(webm_track.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)
|
||||
|
||||
/*
|
||||
// whole file duration (not implemented)
|
||||
0x44,// tag string size
|
||||
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// ????????
|
||||
};
|
||||
}
|
||||
|
||||
// not implemented for the desired codec
|
||||
return null;
|
||||
}
|
||||
|
||||
private void write(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;
|
||||
return res;
|
||||
}
|
||||
|
||||
if (webm_segment == null) {
|
||||
webm_segment = webm.getNextSegment();
|
||||
if (webm_segment == 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;
|
||||
return getNextBlock();
|
||||
}
|
||||
}
|
||||
|
||||
res = webm_cluster.getNextSimpleBlock();
|
||||
if (res == null) {
|
||||
webm_cluster = null;
|
||||
return getNextBlock();
|
||||
}
|
||||
|
||||
webm_block_near_duration = res.absoluteTimeCodeNs - webm_block_last_timecode;
|
||||
webm_block_last_timecode = res.absoluteTimeCodeNs;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private float getSampleFrequencyFromTrack(byte[] bMetadata) {
|
||||
// hardcoded way
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bMetadata);
|
||||
|
||||
while (buffer.remaining() >= 6) {
|
||||
int id = buffer.getShort() & 0xFFFF;
|
||||
if (id == 0x0000B584) {
|
||||
return buffer.getFloat();
|
||||
}
|
||||
}
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
private void clearSegmentTable() {
|
||||
segment_table_next_timestamp += TIME_SCALE_NS;
|
||||
packet_flag = FLAG_UNSET;
|
||||
segment_table_size = 0;
|
||||
}
|
||||
|
||||
private boolean addPacketSegment(SimpleBlock block) {
|
||||
long timestamp = block.absoluteTimeCodeNs + webm_track.codecDelay;
|
||||
|
||||
if (timestamp >= segment_table_next_timestamp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return addPacketSegment(block.dataSize);
|
||||
}
|
||||
|
||||
private boolean addPacketSegment(int size) {
|
||||
if (size > 65025) {
|
||||
throw new UnsupportedOperationException("page size cannot be larger than 65025");
|
||||
}
|
||||
|
||||
int available = (segment_table.length - segment_table_size) * 255;
|
||||
boolean extra = (size % 255) == 0;
|
||||
|
||||
if (extra) {
|
||||
// add a zero byte entry in the table
|
||||
// required to indicate the sample size is multiple of 255
|
||||
available -= 255;
|
||||
}
|
||||
|
||||
// check if possible add the segment, without overflow the table
|
||||
if (available < size) {
|
||||
return false;// not enough space on the page
|
||||
}
|
||||
|
||||
for (; size > 0; size -= 255) {
|
||||
segment_table[segment_table_size++] = (byte) Math.min(size, 255);
|
||||
}
|
||||
|
||||
if (extra) {
|
||||
segment_table[segment_table_size++] = 0x00;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void populate_crc32_table() {
|
||||
for (int i = 0; i < 0x100; i++) {
|
||||
int crc = i << 24;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
long b = crc >>> 31;
|
||||
crc <<= 1;
|
||||
crc ^= (int) (0x100000000L - b) & 0x04c11db7;
|
||||
}
|
||||
crc32_table[i] = crc;
|
||||
}
|
||||
}
|
||||
|
||||
private int calc_crc32(int initial_crc, byte[] buffer, 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)];
|
||||
}
|
||||
|
||||
return initial_crc;
|
||||
}
|
||||
|
||||
}
|
||||
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;
|
||||
import org.schabi.newpipe.streams.WebMReader.SimpleBlock;
|
||||
import org.schabi.newpipe.streams.WebMReader.WebMTrack;
|
||||
import org.schabi.newpipe.streams.io.SharpStream;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* @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 static final byte HEADER_CHECKSUM_OFFSET = 22;
|
||||
private static final byte HEADER_SIZE = 27;
|
||||
|
||||
private static final int TIME_SCALE_NS = 1000000000;
|
||||
|
||||
private boolean done = false;
|
||||
private boolean parsed = false;
|
||||
|
||||
private SharpStream source;
|
||||
private SharpStream output;
|
||||
|
||||
private int sequenceCount = 0;
|
||||
private final int streamId;
|
||||
private byte packetFlag = FLAG_FIRST;
|
||||
|
||||
private WebMReader webm = null;
|
||||
private WebMTrack webmTrack = null;
|
||||
private Segment webmSegment = null;
|
||||
private Cluster webmCluster = null;
|
||||
private SimpleBlock webmBlock = null;
|
||||
|
||||
private long webmBlockLastTimecode = 0;
|
||||
private long webmBlockNearDuration = 0;
|
||||
|
||||
private short segmentTableSize = 0;
|
||||
private final byte[] segmentTable = new byte[255];
|
||||
private long segmentTableNextTimestamp = TIME_SCALE_NS;
|
||||
|
||||
private final int[] crc32Table = new int[256];
|
||||
|
||||
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");
|
||||
}
|
||||
if (!target.canWrite() || !target.canRewind()) {
|
||||
throw new IllegalArgumentException("output stream must be writable and allows seeking");
|
||||
}
|
||||
|
||||
this.source = source;
|
||||
this.output = target;
|
||||
|
||||
this.streamId = (int) System.currentTimeMillis();
|
||||
|
||||
populateCrc32Table();
|
||||
}
|
||||
|
||||
public boolean isDone() {
|
||||
return done;
|
||||
}
|
||||
|
||||
public boolean isParsed() {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
public WebMTrack[] getTracksFromSource() throws IllegalStateException {
|
||||
if (!parsed) {
|
||||
throw new IllegalStateException("source must be parsed first");
|
||||
}
|
||||
|
||||
return webm.getAvailableTracks();
|
||||
}
|
||||
|
||||
public void parseSource() throws IOException, IllegalStateException {
|
||||
if (done) {
|
||||
throw new IllegalStateException("already done");
|
||||
}
|
||||
if (parsed) {
|
||||
throw new IllegalStateException("already parsed");
|
||||
}
|
||||
|
||||
try {
|
||||
webm = new WebMReader(source);
|
||||
webm.parse();
|
||||
webmSegment = webm.getNextSegment();
|
||||
} finally {
|
||||
parsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
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 (webmTrack != null) {
|
||||
throw new IOException("tracks already selected");
|
||||
}
|
||||
|
||||
switch (webm.getAvailableTracks()[trackIndex].kind) {
|
||||
case Audio:
|
||||
case Video:
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("the track must an audio or video stream");
|
||||
}
|
||||
|
||||
try {
|
||||
webmTrack = webm.selectTrack(trackIndex);
|
||||
} finally {
|
||||
parsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
done = true;
|
||||
parsed = true;
|
||||
|
||||
webmTrack = null;
|
||||
webm = null;
|
||||
|
||||
if (!output.isClosed()) {
|
||||
output.flush();
|
||||
}
|
||||
|
||||
source.close();
|
||||
output.close();
|
||||
}
|
||||
|
||||
public void build() throws IOException {
|
||||
float resolution;
|
||||
SimpleBlock bloq;
|
||||
ByteBuffer header = ByteBuffer.allocate(27 + (255 * 255));
|
||||
ByteBuffer page = ByteBuffer.allocate(64 * 1024);
|
||||
|
||||
header.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
/* step 1: get the amount of frames per seconds */
|
||||
switch (webmTrack.kind) {
|
||||
case Audio:
|
||||
resolution = getSampleFrequencyFromTrack(webmTrack.bMetadata);
|
||||
if (resolution == 0f) {
|
||||
throw new RuntimeException("cannot get the audio sample rate");
|
||||
}
|
||||
break;
|
||||
case Video:
|
||||
// WARNING: untested
|
||||
if (webmTrack.defaultDuration == 0) {
|
||||
throw new RuntimeException("missing default frame time");
|
||||
}
|
||||
resolution = 1000f / ((float) webmTrack.defaultDuration
|
||||
/ webmSegment.info.timecodeScale);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("not implemented");
|
||||
}
|
||||
|
||||
/* step 2: create packet with code init data */
|
||||
if (webmTrack.codecPrivate != null) {
|
||||
addPacketSegment(webmTrack.codecPrivate.length);
|
||||
makePacketheader(0x00, header, webmTrack.codecPrivate);
|
||||
write(header);
|
||||
output.write(webmTrack.codecPrivate);
|
||||
}
|
||||
|
||||
/* step 3: create packet with metadata */
|
||||
byte[] buffer = makeMetadata();
|
||||
if (buffer != null) {
|
||||
addPacketSegment(buffer.length);
|
||||
makePacketheader(0x00, header, buffer);
|
||||
write(header);
|
||||
output.write(buffer);
|
||||
}
|
||||
|
||||
/* step 4: calculate amount of packets */
|
||||
while (webmSegment != null) {
|
||||
bloq = getNextBlock();
|
||||
|
||||
if (bloq != null && addPacketSegment(bloq)) {
|
||||
int pos = page.position();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
bloq.data.read(page.array(), pos, bloq.dataSize);
|
||||
page.position(pos + bloq.dataSize);
|
||||
continue;
|
||||
}
|
||||
|
||||
// calculate the current packet duration using the next block
|
||||
double elapsedNs = webmTrack.codecDelay;
|
||||
|
||||
if (bloq == null) {
|
||||
packetFlag = FLAG_LAST; // note: if the flag is FLAG_CONTINUED, is changed
|
||||
elapsedNs += webmBlockLastTimecode;
|
||||
|
||||
if (webmTrack.defaultDuration > 0) {
|
||||
elapsedNs += webmTrack.defaultDuration;
|
||||
} else {
|
||||
// hardcoded way, guess the sample duration
|
||||
elapsedNs += webmBlockNearDuration;
|
||||
}
|
||||
} else {
|
||||
elapsedNs += bloq.absoluteTimeCodeNs;
|
||||
}
|
||||
|
||||
// get the sample count in the page
|
||||
elapsedNs = elapsedNs / TIME_SCALE_NS;
|
||||
elapsedNs = Math.ceil(elapsedNs * resolution);
|
||||
|
||||
// create header and calculate page checksum
|
||||
int checksum = makePacketheader((long) elapsedNs, header, null);
|
||||
checksum = calcCrc32(checksum, page.array(), page.position());
|
||||
|
||||
header.putInt(HEADER_CHECKSUM_OFFSET, checksum);
|
||||
|
||||
// dump data
|
||||
write(header);
|
||||
write(page);
|
||||
|
||||
webmBlock = bloq;
|
||||
}
|
||||
}
|
||||
|
||||
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(packetFlag); // type
|
||||
|
||||
buffer.putLong(granPos); // granulate position
|
||||
|
||||
buffer.putInt(streamId); // bitstream serial number
|
||||
buffer.putInt(sequenceCount++); // page sequence number
|
||||
|
||||
buffer.putInt(0x00); // page checksum
|
||||
|
||||
buffer.put((byte) segmentTableSize); // segment table
|
||||
buffer.put(segmentTable, 0, segmentTableSize); // segment size
|
||||
|
||||
length += segmentTableSize;
|
||||
|
||||
clearSegmentTable(); // clear segment table for next header
|
||||
|
||||
int checksumCrc32 = calcCrc32(0x00, buffer.array(), length);
|
||||
|
||||
if (immediatePage != null) {
|
||||
checksumCrc32 = calcCrc32(checksumCrc32, immediatePage, immediatePage.length);
|
||||
buffer.putInt(HEADER_CHECKSUM_OFFSET, checksumCrc32);
|
||||
segmentTableNextTimestamp -= TIME_SCALE_NS;
|
||||
}
|
||||
|
||||
return checksumCrc32;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
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, // 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(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)
|
||||
|
||||
/*
|
||||
// whole file duration (not implemented)
|
||||
0x44,// tag string size
|
||||
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 // ????????
|
||||
};
|
||||
}
|
||||
|
||||
// not implemented for the desired codec
|
||||
return null;
|
||||
}
|
||||
|
||||
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 (webmBlock != null) {
|
||||
res = webmBlock;
|
||||
webmBlock = null;
|
||||
return res;
|
||||
}
|
||||
|
||||
if (webmSegment == null) {
|
||||
webmSegment = webm.getNextSegment();
|
||||
if (webmSegment == null) {
|
||||
return null; // no more blocks in the selected track
|
||||
}
|
||||
}
|
||||
|
||||
if (webmCluster == null) {
|
||||
webmCluster = webmSegment.getNextCluster();
|
||||
if (webmCluster == null) {
|
||||
webmSegment = null;
|
||||
return getNextBlock();
|
||||
}
|
||||
}
|
||||
|
||||
res = webmCluster.getNextSimpleBlock();
|
||||
if (res == null) {
|
||||
webmCluster = null;
|
||||
return getNextBlock();
|
||||
}
|
||||
|
||||
webmBlockNearDuration = res.absoluteTimeCodeNs - webmBlockLastTimecode;
|
||||
webmBlockLastTimecode = res.absoluteTimeCodeNs;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private float getSampleFrequencyFromTrack(final byte[] bMetadata) {
|
||||
// hardcoded way
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bMetadata);
|
||||
|
||||
while (buffer.remaining() >= 6) {
|
||||
int id = buffer.getShort() & 0xFFFF;
|
||||
if (id == 0x0000B584) {
|
||||
return buffer.getFloat();
|
||||
}
|
||||
}
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
private void clearSegmentTable() {
|
||||
segmentTableNextTimestamp += TIME_SCALE_NS;
|
||||
packetFlag = FLAG_UNSET;
|
||||
segmentTableSize = 0;
|
||||
}
|
||||
|
||||
private boolean addPacketSegment(final SimpleBlock block) {
|
||||
long timestamp = block.absoluteTimeCodeNs + webmTrack.codecDelay;
|
||||
|
||||
if (timestamp >= segmentTableNextTimestamp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return addPacketSegment(block.dataSize);
|
||||
}
|
||||
|
||||
private boolean addPacketSegment(int size) {
|
||||
if (size > 65025) {
|
||||
throw new UnsupportedOperationException("page size cannot be larger than 65025");
|
||||
}
|
||||
|
||||
int available = (segmentTable.length - segmentTableSize) * 255;
|
||||
boolean extra = (size % 255) == 0;
|
||||
|
||||
if (extra) {
|
||||
// add a zero byte entry in the table
|
||||
// required to indicate the sample size is multiple of 255
|
||||
available -= 255;
|
||||
}
|
||||
|
||||
// check if possible add the segment, without overflow the table
|
||||
if (available < size) {
|
||||
return false; // not enough space on the page
|
||||
}
|
||||
|
||||
for (; size > 0; size -= 255) {
|
||||
segmentTable[segmentTableSize++] = (byte) Math.min(size, 255);
|
||||
}
|
||||
|
||||
if (extra) {
|
||||
segmentTable[segmentTableSize++] = 0x00;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void populateCrc32Table() {
|
||||
for (int i = 0; i < 0x100; i++) {
|
||||
int crc = i << 24;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
long b = crc >>> 31;
|
||||
crc <<= 1;
|
||||
crc ^= (int) (0x100000000L - b) & 0x04c11db7;
|
||||
}
|
||||
crc32Table[i] = crc;
|
||||
}
|
||||
}
|
||||
|
||||
private int calcCrc32(int initialCrc, final byte[] buffer, final int size) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
int reg = (initialCrc >>> 24) & 0xff;
|
||||
initialCrc = (initialCrc << 8) ^ crc32Table[reg ^ (buffer[i] & 0xff)];
|
||||
}
|
||||
|
||||
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");
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,63 +1,62 @@
|
||||
package org.schabi.newpipe.streams.io;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* based on c#
|
||||
*/
|
||||
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[], int offset, int count) throws IOException;
|
||||
|
||||
public abstract long skip(long amount) throws IOException;
|
||||
|
||||
public abstract long available();
|
||||
|
||||
public abstract void rewind() throws IOException;
|
||||
|
||||
public abstract boolean isClosed();
|
||||
|
||||
@Override
|
||||
public abstract void close();
|
||||
|
||||
public abstract boolean canRewind();
|
||||
|
||||
public abstract boolean canRead();
|
||||
|
||||
public abstract boolean canWrite();
|
||||
|
||||
public boolean canSetLength() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean canSeek() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract void write(byte value) throws IOException;
|
||||
|
||||
public abstract void write(byte[] buffer) throws IOException;
|
||||
|
||||
public abstract void write(byte[] buffer, int offset, int count) throws IOException;
|
||||
|
||||
public void flush() throws IOException {
|
||||
// STUB
|
||||
}
|
||||
|
||||
public void setLength(long length) throws IOException {
|
||||
throw new IOException("Not implemented");
|
||||
}
|
||||
|
||||
public void seek(long offset) throws IOException {
|
||||
throw new IOException("Not implemented");
|
||||
}
|
||||
|
||||
public long length() throws IOException {
|
||||
throw new UnsupportedOperationException("Unsupported operation");
|
||||
}
|
||||
}
|
||||
package org.schabi.newpipe.streams.io;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 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, int offset, int count) throws IOException;
|
||||
|
||||
public abstract long skip(long amount) throws IOException;
|
||||
|
||||
public abstract long available();
|
||||
|
||||
public abstract void rewind() throws IOException;
|
||||
|
||||
public abstract boolean isClosed();
|
||||
|
||||
@Override
|
||||
public abstract void close();
|
||||
|
||||
public abstract boolean canRewind();
|
||||
|
||||
public abstract boolean canRead();
|
||||
|
||||
public abstract boolean canWrite();
|
||||
|
||||
public boolean canSetLength() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean canSeek() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract void write(byte value) throws IOException;
|
||||
|
||||
public abstract void write(byte[] buffer) throws IOException;
|
||||
|
||||
public abstract void write(byte[] buffer, int offset, int count) throws IOException;
|
||||
|
||||
public void flush() throws IOException {
|
||||
// STUB
|
||||
}
|
||||
|
||||
public void setLength(final long length) throws IOException {
|
||||
throw new IOException("Not implemented");
|
||||
}
|
||||
|
||||
public void seek(final long offset) throws IOException {
|
||||
throw new IOException("Not implemented");
|
||||
}
|
||||
|
||||
public long length() throws IOException {
|
||||
throw new UnsupportedOperationException("Unsupported operation");
|
||||
}
|
||||
}
|
||||
|
@ -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