mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-01-10 17:30:31 +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 '**/R.java'
|
||||||
exclude '**/BuildConfig.java'
|
exclude '**/BuildConfig.java'
|
||||||
exclude 'main/java/us/shandian/giga/**'
|
exclude 'main/java/us/shandian/giga/**'
|
||||||
exclude 'main/java/org/schabi/newpipe/streams/**'
|
|
||||||
|
|
||||||
// empty classpath
|
// empty classpath
|
||||||
classpath = files()
|
classpath = files()
|
||||||
|
@ -10,13 +10,12 @@ import java.io.InputStream;
|
|||||||
* @author kapodamy
|
* @author kapodamy
|
||||||
*/
|
*/
|
||||||
public class DataReader {
|
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;
|
private static final int BUFFER_SIZE = 128 * 1024; // 128 KiB
|
||||||
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 long position = 0;
|
private long position = 0;
|
||||||
private final SharpStream stream;
|
private final SharpStream stream;
|
||||||
@ -24,7 +23,7 @@ public class DataReader {
|
|||||||
private InputStream view;
|
private InputStream view;
|
||||||
private int viewSize;
|
private int viewSize;
|
||||||
|
|
||||||
public DataReader(SharpStream stream) {
|
public DataReader(final SharpStream stream) {
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
this.readOffset = this.readBuffer.length;
|
this.readOffset = this.readBuffer.length;
|
||||||
}
|
}
|
||||||
@ -74,6 +73,7 @@ public class DataReader {
|
|||||||
return value & 0xffffffffL;
|
return value & 0xffffffffL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public short readShort() throws IOException {
|
public short readShort() throws IOException {
|
||||||
primitiveRead(SHORT_SIZE);
|
primitiveRead(SHORT_SIZE);
|
||||||
return (short) (primitive[0] << 8 | primitive[1]);
|
return (short) (primitive[0] << 8 | primitive[1]);
|
||||||
@ -86,11 +86,11 @@ public class DataReader {
|
|||||||
return high << 32 | low;
|
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);
|
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) {
|
if (readCount < 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -135,7 +135,7 @@ public class DataReader {
|
|||||||
stream.rewind();
|
stream.rewind();
|
||||||
|
|
||||||
if ((position - viewSize) > 0) {
|
if ((position - viewSize) > 0) {
|
||||||
viewSize = 0;// drop view
|
viewSize = 0; // drop view
|
||||||
} else {
|
} else {
|
||||||
viewSize += position;
|
viewSize += position;
|
||||||
}
|
}
|
||||||
@ -157,7 +157,7 @@ public class DataReader {
|
|||||||
* @param size the size of the view
|
* @param size the size of the view
|
||||||
* @return the view
|
* @return the view
|
||||||
*/
|
*/
|
||||||
public InputStream getView(int size) {
|
public InputStream getView(final int size) {
|
||||||
if (view == null) {
|
if (view == null) {
|
||||||
view = new InputStream() {
|
view = new InputStream() {
|
||||||
@Override
|
@Override
|
||||||
@ -173,12 +173,13 @@ public class DataReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(byte[] buffer) throws IOException {
|
public int read(final byte[] buffer) throws IOException {
|
||||||
return read(buffer, 0, buffer.length);
|
return read(buffer, 0, buffer.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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) {
|
if (viewSize < 1) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -190,7 +191,7 @@ public class DataReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long skip(long amount) throws IOException {
|
public long skip(final long amount) throws IOException {
|
||||||
if (viewSize < 1) {
|
if (viewSize < 1) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -224,16 +225,18 @@ public class DataReader {
|
|||||||
|
|
||||||
private final short[] primitive = new short[LONG_SIZE];
|
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];
|
byte[] buffer = new byte[amount];
|
||||||
int read = read(buffer, 0, amount);
|
int read = read(buffer, 0, amount);
|
||||||
|
|
||||||
if (read != 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++) {
|
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;
|
return readCount < 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ import java.util.NoSuchElementException;
|
|||||||
* @author kapodamy
|
* @author kapodamy
|
||||||
*/
|
*/
|
||||||
public class Mp4DashReader {
|
public class Mp4DashReader {
|
||||||
|
|
||||||
private static final int ATOM_MOOF = 0x6D6F6F66;
|
private static final int ATOM_MOOF = 0x6D6F6F66;
|
||||||
private static final int ATOM_MFHD = 0x6D666864;
|
private static final int ATOM_MFHD = 0x6D666864;
|
||||||
private static final int ATOM_TRAF = 0x74726166;
|
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_SOUN = 0x736F756E;
|
||||||
private static final int HANDLER_SUBT = 0x73756274;
|
private static final int HANDLER_SUBT = 0x73756274;
|
||||||
|
|
||||||
|
|
||||||
private final DataReader stream;
|
private final DataReader stream;
|
||||||
|
|
||||||
private Mp4Track[] tracks = null;
|
private Mp4Track[] tracks = null;
|
||||||
@ -68,7 +66,7 @@ public class Mp4DashReader {
|
|||||||
Audio, Video, Subtitles, Other
|
Audio, Video, Subtitles, Other
|
||||||
}
|
}
|
||||||
|
|
||||||
public Mp4DashReader(SharpStream source) {
|
public Mp4DashReader(final SharpStream source) {
|
||||||
this.stream = new DataReader(source);
|
this.stream = new DataReader(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,14 +76,15 @@ public class Mp4DashReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
box = readBox(ATOM_FTYP);
|
box = readBox(ATOM_FTYP);
|
||||||
brands = parse_ftyp(box);
|
brands = parseFtyp(box);
|
||||||
switch (brands[0]) {
|
switch (brands[0]) {
|
||||||
case BRAND_DASH:
|
case BRAND_DASH:
|
||||||
case BRAND_ISO5:// ¿why not?
|
case BRAND_ISO5:// ¿why not?
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new NoSuchElementException(
|
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) {
|
switch (box.type) {
|
||||||
case ATOM_MOOV:
|
case ATOM_MOOV:
|
||||||
moov = parse_moov(box);
|
moov = parseMoov(box);
|
||||||
break;
|
break;
|
||||||
case ATOM_SIDX:
|
case ATOM_SIDX:
|
||||||
break;
|
break;
|
||||||
@ -117,10 +116,10 @@ public class Mp4DashReader {
|
|||||||
tracks[i] = new Mp4Track();
|
tracks[i] = new Mp4Track();
|
||||||
tracks[i].trak = moov.trak[i];
|
tracks[i].trak = moov.trak[i];
|
||||||
|
|
||||||
if (moov.mvex_trex != null) {
|
if (moov.mvexTrex != null) {
|
||||||
for (Trex mvex_trex : moov.mvex_trex) {
|
for (Trex mvexTrex : moov.mvexTrex) {
|
||||||
if (tracks[i].trak.tkhd.trackId == mvex_trex.trackId) {
|
if (tracks[i].trak.tkhd.trackId == mvexTrex.trackId) {
|
||||||
tracks[i].trex = mvex_trex;
|
tracks[i].trex = mvexTrex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,7 +143,7 @@ public class Mp4DashReader {
|
|||||||
backupBox = box;
|
backupBox = box;
|
||||||
}
|
}
|
||||||
|
|
||||||
Mp4Track selectTrack(int index) {
|
Mp4Track selectTrack(final int index) {
|
||||||
selectedTrack = index;
|
selectedTrack = index;
|
||||||
return tracks[index];
|
return tracks[index];
|
||||||
}
|
}
|
||||||
@ -179,7 +178,7 @@ public class Mp4DashReader {
|
|||||||
Box traf;
|
Box traf;
|
||||||
while ((traf = untilBox(tmp, ATOM_TRAF)) != null) {
|
while ((traf = untilBox(tmp, ATOM_TRAF)) != null) {
|
||||||
Box tfhd = readBox(ATOM_TFHD);
|
Box tfhd = readBox(ATOM_TFHD);
|
||||||
if (parse_tfhd(tracks[selectedTrack].trak.tkhd.trackId) != null) {
|
if (parseTfhd(tracks[selectedTrack].trak.tkhd.trackId) != null) {
|
||||||
count++;
|
count++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -196,7 +195,9 @@ public class Mp4DashReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int[] getBrands() {
|
public int[] getBrands() {
|
||||||
if (brands == null) throw new IllegalStateException("Not parsed");
|
if (brands == null) {
|
||||||
|
throw new IllegalStateException("Not parsed");
|
||||||
|
}
|
||||||
return brands;
|
return brands;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,7 +220,7 @@ public class Mp4DashReader {
|
|||||||
return tracks;
|
return tracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Mp4DashChunk getNextChunk(boolean infoOnly) throws IOException {
|
public Mp4DashChunk getNextChunk(final boolean infoOnly) throws IOException {
|
||||||
Mp4Track track = tracks[selectedTrack];
|
Mp4Track track = tracks[selectedTrack];
|
||||||
|
|
||||||
while (stream.available()) {
|
while (stream.available()) {
|
||||||
@ -240,27 +241,31 @@ public class Mp4DashReader {
|
|||||||
throw new IOException("moof found without mdat");
|
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 (moof.traf != null) {
|
||||||
|
|
||||||
if (hasFlag(moof.traf.trun.bFlags, 0x0001)) {
|
if (hasFlag(moof.traf.trun.bFlags, 0x0001)) {
|
||||||
moof.traf.trun.dataOffset -= box.size + 8;
|
moof.traf.trun.dataOffset -= box.size + 8;
|
||||||
if (moof.traf.trun.dataOffset < 0) {
|
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 (moof.traf.trun.chunkSize < 1) {
|
||||||
if (hasFlag(moof.traf.tfhd.bFlags, 0x10)) {
|
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 {
|
} else {
|
||||||
moof.traf.trun.chunkSize = (int) (box.size - 8);
|
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)) {
|
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) {
|
if (moof.traf == null) {
|
||||||
moof = null;
|
moof = null;
|
||||||
continue;// find another chunk
|
continue; // find another chunk
|
||||||
}
|
}
|
||||||
|
|
||||||
Mp4DashChunk chunk = new Mp4DashChunk();
|
Mp4DashChunk chunk = new Mp4DashChunk();
|
||||||
@ -292,17 +297,15 @@ public class Mp4DashReader {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean hasFlag(final int flags, final int mask) {
|
||||||
|
|
||||||
public static boolean hasFlag(int flags, int mask) {
|
|
||||||
return (flags & mask) == mask;
|
return (flags & mask) == mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String boxName(Box ref) {
|
private String boxName(final Box ref) {
|
||||||
return boxName(ref.type);
|
return boxName(ref.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String boxName(int type) {
|
private String boxName(final int type) {
|
||||||
try {
|
try {
|
||||||
return new String(ByteBuffer.allocate(4).putInt(type).array(), "UTF-8");
|
return new String(ByteBuffer.allocate(4).putInt(type).array(), "UTF-8");
|
||||||
} catch (UnsupportedEncodingException e) {
|
} catch (UnsupportedEncodingException e) {
|
||||||
@ -323,15 +326,16 @@ public class Mp4DashReader {
|
|||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Box readBox(int expected) throws IOException {
|
private Box readBox(final int expected) throws IOException {
|
||||||
Box b = readBox();
|
Box b = readBox();
|
||||||
if (b.type != expected) {
|
if (b.type != expected) {
|
||||||
throw new NoSuchElementException("expected " + boxName(expected) + " found " + boxName(b));
|
throw new NoSuchElementException("expected " + boxName(expected)
|
||||||
|
+ " found " + boxName(b));
|
||||||
}
|
}
|
||||||
return 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
|
// full box reading is limited to 2 GiB, and should be enough
|
||||||
int size = (int) ref.size;
|
int size = (int) ref.size;
|
||||||
|
|
||||||
@ -342,15 +346,14 @@ public class Mp4DashReader {
|
|||||||
int read = size - 8;
|
int read = size - 8;
|
||||||
|
|
||||||
if (stream.read(buffer.array(), 8, read) != read) {
|
if (stream.read(buffer.array(), 8, read) != read) {
|
||||||
throw new EOFException(
|
throw new EOFException(String.format("EOF reached in box: type=%s offset=%s size=%s",
|
||||||
String.format("EOF reached in box: type=%s offset=%s size=%s", boxName(ref.type), ref.offset, ref.size)
|
boxName(ref.type), ref.offset, ref.size));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer.array();
|
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();
|
long skip = ref.offset + ref.size - stream.position();
|
||||||
|
|
||||||
if (skip == 0) {
|
if (skip == 0) {
|
||||||
@ -365,7 +368,7 @@ public class Mp4DashReader {
|
|||||||
stream.skipBytes((int) skip);
|
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;
|
Box b;
|
||||||
while (stream.position() < (ref.offset + ref.size)) {
|
while (stream.position() < (ref.offset + ref.size)) {
|
||||||
b = readBox();
|
b = readBox();
|
||||||
@ -380,7 +383,7 @@ public class Mp4DashReader {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Box untilAnyBox(Box ref) throws IOException {
|
private Box untilAnyBox(final Box ref) throws IOException {
|
||||||
if (stream.position() >= (ref.offset + ref.size)) {
|
if (stream.position() >= (ref.offset + ref.size)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -388,17 +391,15 @@ public class Mp4DashReader {
|
|||||||
return readBox();
|
return readBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Moof parseMoof(final Box ref, final int trackId) throws IOException {
|
||||||
|
|
||||||
private Moof parse_moof(Box ref, int trackId) throws IOException {
|
|
||||||
Moof obj = new Moof();
|
Moof obj = new Moof();
|
||||||
|
|
||||||
Box b = readBox(ATOM_MFHD);
|
Box b = readBox(ATOM_MFHD);
|
||||||
obj.mfhd_SequenceNumber = parse_mfhd();
|
obj.mfhdSequenceNumber = parseMfhd();
|
||||||
ensure(b);
|
ensure(b);
|
||||||
|
|
||||||
while ((b = untilBox(ref, ATOM_TRAF)) != null) {
|
while ((b = untilBox(ref, ATOM_TRAF)) != null) {
|
||||||
obj.traf = parse_traf(b, trackId);
|
obj.traf = parseTraf(b, trackId);
|
||||||
ensure(b);
|
ensure(b);
|
||||||
|
|
||||||
if (obj.traf != null) {
|
if (obj.traf != null) {
|
||||||
@ -409,7 +410,7 @@ public class Mp4DashReader {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int parse_mfhd() throws IOException {
|
private int parseMfhd() throws IOException {
|
||||||
// version
|
// version
|
||||||
// flags
|
// flags
|
||||||
stream.skipBytes(4);
|
stream.skipBytes(4);
|
||||||
@ -417,11 +418,11 @@ public class Mp4DashReader {
|
|||||||
return stream.readInt();
|
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();
|
Traf traf = new Traf();
|
||||||
|
|
||||||
Box b = readBox(ATOM_TFHD);
|
Box b = readBox(ATOM_TFHD);
|
||||||
traf.tfhd = parse_tfhd(trackId);
|
traf.tfhd = parseTfhd(trackId);
|
||||||
ensure(b);
|
ensure(b);
|
||||||
|
|
||||||
if (traf.tfhd == null) {
|
if (traf.tfhd == null) {
|
||||||
@ -431,18 +432,18 @@ public class Mp4DashReader {
|
|||||||
b = untilBox(ref, ATOM_TRUN, ATOM_TFDT);
|
b = untilBox(ref, ATOM_TRUN, ATOM_TFDT);
|
||||||
|
|
||||||
if (b.type == ATOM_TFDT) {
|
if (b.type == ATOM_TFDT) {
|
||||||
traf.tfdt = parse_tfdt();
|
traf.tfdt = parseTfdt();
|
||||||
ensure(b);
|
ensure(b);
|
||||||
b = readBox(ATOM_TRUN);
|
b = readBox(ATOM_TRUN);
|
||||||
}
|
}
|
||||||
|
|
||||||
traf.trun = parse_trun();
|
traf.trun = parseTrun();
|
||||||
ensure(b);
|
ensure(b);
|
||||||
|
|
||||||
return traf;
|
return traf;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Tfhd parse_tfhd(int trackId) throws IOException {
|
private Tfhd parseTfhd(final int trackId) throws IOException {
|
||||||
Tfhd obj = new Tfhd();
|
Tfhd obj = new Tfhd();
|
||||||
|
|
||||||
obj.bFlags = stream.readInt();
|
obj.bFlags = stream.readInt();
|
||||||
@ -471,31 +472,31 @@ public class Mp4DashReader {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
private long parse_tfdt() throws IOException {
|
private long parseTfdt() throws IOException {
|
||||||
int version = stream.read();
|
int version = stream.read();
|
||||||
stream.skipBytes(3);// flags
|
stream.skipBytes(3); // flags
|
||||||
return version == 0 ? stream.readUnsignedInt() : stream.readLong();
|
return version == 0 ? stream.readUnsignedInt() : stream.readLong();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Trun parse_trun() throws IOException {
|
private Trun parseTrun() throws IOException {
|
||||||
Trun obj = new Trun();
|
Trun obj = new Trun();
|
||||||
obj.bFlags = stream.readInt();
|
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)) {
|
if (hasFlag(obj.bFlags, 0x0100)) {
|
||||||
obj.entries_rowSize += 4;
|
obj.entriesRowSize += 4;
|
||||||
}
|
}
|
||||||
if (hasFlag(obj.bFlags, 0x0200)) {
|
if (hasFlag(obj.bFlags, 0x0200)) {
|
||||||
obj.entries_rowSize += 4;
|
obj.entriesRowSize += 4;
|
||||||
}
|
}
|
||||||
if (hasFlag(obj.bFlags, 0x0400)) {
|
if (hasFlag(obj.bFlags, 0x0400)) {
|
||||||
obj.entries_rowSize += 4;
|
obj.entriesRowSize += 4;
|
||||||
}
|
}
|
||||||
if (hasFlag(obj.bFlags, 0x0800)) {
|
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)) {
|
if (hasFlag(obj.bFlags, 0x0001)) {
|
||||||
obj.dataOffset = stream.readInt();
|
obj.dataOffset = stream.readInt();
|
||||||
@ -524,23 +525,24 @@ public class Mp4DashReader {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int[] parse_ftyp(Box ref) throws IOException {
|
private int[] parseFtyp(final Box ref) throws IOException {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
int[] list = new int[(int) ((ref.offset + ref.size - stream.position() - 4) / 4)];
|
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++)
|
for (; i < list.length; i++) {
|
||||||
list[i] = stream.readInt();// compatible brands
|
list[i] = stream.readInt(); // compatible brands
|
||||||
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mvhd parse_mvhd() throws IOException {
|
private Mvhd parseMvhd() throws IOException {
|
||||||
int version = stream.read();
|
int version = stream.read();
|
||||||
stream.skipBytes(3);// flags
|
stream.skipBytes(3); // flags
|
||||||
|
|
||||||
// creation entries_time
|
// creation entries_time
|
||||||
// modification entries_time
|
// modification entries_time
|
||||||
@ -564,7 +566,7 @@ public class Mp4DashReader {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Tkhd parse_tkhd() throws IOException {
|
private Tkhd parseTkhd() throws IOException {
|
||||||
int version = stream.read();
|
int version = stream.read();
|
||||||
|
|
||||||
Tkhd obj = new Tkhd();
|
Tkhd obj = new Tkhd();
|
||||||
@ -576,17 +578,17 @@ public class Mp4DashReader {
|
|||||||
|
|
||||||
obj.trackId = stream.readInt();
|
obj.trackId = stream.readInt();
|
||||||
|
|
||||||
stream.skipBytes(4);// reserved
|
stream.skipBytes(4); // reserved
|
||||||
|
|
||||||
obj.duration = version == 0 ? stream.readUnsignedInt() : stream.readLong();
|
obj.duration = version == 0 ? stream.readUnsignedInt() : stream.readLong();
|
||||||
|
|
||||||
stream.skipBytes(2 * 4);// reserved
|
stream.skipBytes(2 * 4); // reserved
|
||||||
|
|
||||||
obj.bLayer = stream.readShort();
|
obj.bLayer = stream.readShort();
|
||||||
obj.bAlternateGroup = stream.readShort();
|
obj.bAlternateGroup = stream.readShort();
|
||||||
obj.bVolume = stream.readShort();
|
obj.bVolume = stream.readShort();
|
||||||
|
|
||||||
stream.skipBytes(2);// reserved
|
stream.skipBytes(2); // reserved
|
||||||
|
|
||||||
obj.matrix = new byte[9 * 4];
|
obj.matrix = new byte[9 * 4];
|
||||||
stream.read(obj.matrix);
|
stream.read(obj.matrix);
|
||||||
@ -597,20 +599,20 @@ public class Mp4DashReader {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Trak parse_trak(Box ref) throws IOException {
|
private Trak parseTrak(final Box ref) throws IOException {
|
||||||
Trak trak = new Trak();
|
Trak trak = new Trak();
|
||||||
|
|
||||||
Box b = readBox(ATOM_TKHD);
|
Box b = readBox(ATOM_TKHD);
|
||||||
trak.tkhd = parse_tkhd();
|
trak.tkhd = parseTkhd();
|
||||||
ensure(b);
|
ensure(b);
|
||||||
|
|
||||||
while ((b = untilBox(ref, ATOM_MDIA, ATOM_EDTS)) != null) {
|
while ((b = untilBox(ref, ATOM_MDIA, ATOM_EDTS)) != null) {
|
||||||
switch (b.type) {
|
switch (b.type) {
|
||||||
case ATOM_MDIA:
|
case ATOM_MDIA:
|
||||||
trak.mdia = parse_mdia(b);
|
trak.mdia = parseMdia(b);
|
||||||
break;
|
break;
|
||||||
case ATOM_EDTS:
|
case ATOM_EDTS:
|
||||||
trak.edst_elst = parse_edts(b);
|
trak.edstElst = parseEdts(b);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -620,7 +622,7 @@ public class Mp4DashReader {
|
|||||||
return trak;
|
return trak;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mdia parse_mdia(Box ref) throws IOException {
|
private Mdia parseMdia(final Box ref) throws IOException {
|
||||||
Mdia obj = new Mdia();
|
Mdia obj = new Mdia();
|
||||||
|
|
||||||
Box b;
|
Box b;
|
||||||
@ -633,13 +635,13 @@ public class Mp4DashReader {
|
|||||||
ByteBuffer buffer = ByteBuffer.wrap(obj.mdhd);
|
ByteBuffer buffer = ByteBuffer.wrap(obj.mdhd);
|
||||||
byte version = buffer.get(8);
|
byte version = buffer.get(8);
|
||||||
buffer.position(12 + ((version == 0 ? 4 : 8) * 2));
|
buffer.position(12 + ((version == 0 ? 4 : 8) * 2));
|
||||||
obj.mdhd_timeScale = buffer.getInt();
|
obj.mdhdTimeScale = buffer.getInt();
|
||||||
break;
|
break;
|
||||||
case ATOM_HDLR:
|
case ATOM_HDLR:
|
||||||
obj.hdlr = parse_hdlr(b);
|
obj.hdlr = parseHdlr(b);
|
||||||
break;
|
break;
|
||||||
case ATOM_MINF:
|
case ATOM_MINF:
|
||||||
obj.minf = parse_minf(b);
|
obj.minf = parseMinf(b);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ensure(b);
|
ensure(b);
|
||||||
@ -648,7 +650,7 @@ public class Mp4DashReader {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Hdlr parse_hdlr(Box ref) throws IOException {
|
private Hdlr parseHdlr(final Box ref) throws IOException {
|
||||||
// version
|
// version
|
||||||
// flags
|
// flags
|
||||||
stream.skipBytes(4);
|
stream.skipBytes(4);
|
||||||
@ -666,10 +668,10 @@ public class Mp4DashReader {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Moov parse_moov(Box ref) throws IOException {
|
private Moov parseMoov(final Box ref) throws IOException {
|
||||||
Box b = readBox(ATOM_MVHD);
|
Box b = readBox(ATOM_MVHD);
|
||||||
Moov moov = new Moov();
|
Moov moov = new Moov();
|
||||||
moov.mvhd = parse_mvhd();
|
moov.mvhd = parseMvhd();
|
||||||
ensure(b);
|
ensure(b);
|
||||||
|
|
||||||
ArrayList<Trak> tmp = new ArrayList<>((int) moov.mvhd.nextTrackId);
|
ArrayList<Trak> tmp = new ArrayList<>((int) moov.mvhd.nextTrackId);
|
||||||
@ -677,10 +679,10 @@ public class Mp4DashReader {
|
|||||||
|
|
||||||
switch (b.type) {
|
switch (b.type) {
|
||||||
case ATOM_TRAK:
|
case ATOM_TRAK:
|
||||||
tmp.add(parse_trak(b));
|
tmp.add(parseTrak(b));
|
||||||
break;
|
break;
|
||||||
case ATOM_MVEX:
|
case ATOM_MVEX:
|
||||||
moov.mvex_trex = parse_mvex(b, (int) moov.mvhd.nextTrackId);
|
moov.mvexTrex = parseMvex(b, (int) moov.mvhd.nextTrackId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -692,19 +694,19 @@ public class Mp4DashReader {
|
|||||||
return moov;
|
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);
|
ArrayList<Trex> tmp = new ArrayList<>(possibleTrackCount);
|
||||||
|
|
||||||
Box b;
|
Box b;
|
||||||
while ((b = untilBox(ref, ATOM_TREX)) != null) {
|
while ((b = untilBox(ref, ATOM_TREX)) != null) {
|
||||||
tmp.add(parse_trex());
|
tmp.add(parseTrex());
|
||||||
ensure(b);
|
ensure(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
return tmp.toArray(new Trex[0]);
|
return tmp.toArray(new Trex[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Trex parse_trex() throws IOException {
|
private Trex parseTrex() throws IOException {
|
||||||
// version
|
// version
|
||||||
// flags
|
// flags
|
||||||
stream.skipBytes(4);
|
stream.skipBytes(4);
|
||||||
@ -719,7 +721,7 @@ public class Mp4DashReader {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Elst parse_edts(Box ref) throws IOException {
|
private Elst parseEdts(final Box ref) throws IOException {
|
||||||
Box b = untilBox(ref, ATOM_ELST);
|
Box b = untilBox(ref, ATOM_ELST);
|
||||||
if (b == null) {
|
if (b == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -728,22 +730,22 @@ public class Mp4DashReader {
|
|||||||
Elst obj = new Elst();
|
Elst obj = new Elst();
|
||||||
|
|
||||||
boolean v1 = stream.read() == 1;
|
boolean v1 = stream.read() == 1;
|
||||||
stream.skipBytes(3);// flags
|
stream.skipBytes(3); // flags
|
||||||
|
|
||||||
int entryCount = stream.readInt();
|
int entryCount = stream.readInt();
|
||||||
if (entryCount < 1) {
|
if (entryCount < 1) {
|
||||||
obj.bMediaRate = 0x00010000;// default media rate (1.0)
|
obj.bMediaRate = 0x00010000; // default media rate (1.0)
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v1) {
|
if (v1) {
|
||||||
stream.skipBytes(DataReader.LONG_SIZE);// segment duration
|
stream.skipBytes(DataReader.LONG_SIZE); // segment duration
|
||||||
obj.MediaTime = stream.readLong();
|
obj.mediaTime = stream.readLong();
|
||||||
// ignore all remain entries
|
// ignore all remain entries
|
||||||
stream.skipBytes((entryCount - 1) * (DataReader.LONG_SIZE * 2));
|
stream.skipBytes((entryCount - 1) * (DataReader.LONG_SIZE * 2));
|
||||||
} else {
|
} else {
|
||||||
stream.skipBytes(DataReader.INTEGER_SIZE);// segment duration
|
stream.skipBytes(DataReader.INTEGER_SIZE); // segment duration
|
||||||
obj.MediaTime = stream.readInt();
|
obj.mediaTime = stream.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.bMediaRate = stream.readInt();
|
obj.bMediaRate = stream.readInt();
|
||||||
@ -751,7 +753,7 @@ public class Mp4DashReader {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Minf parse_minf(Box ref) throws IOException {
|
private Minf parseMinf(final Box ref) throws IOException {
|
||||||
Minf obj = new Minf();
|
Minf obj = new Minf();
|
||||||
|
|
||||||
Box b;
|
Box b;
|
||||||
@ -762,11 +764,11 @@ public class Mp4DashReader {
|
|||||||
obj.dinf = readFullBox(b);
|
obj.dinf = readFullBox(b);
|
||||||
break;
|
break;
|
||||||
case ATOM_STBL:
|
case ATOM_STBL:
|
||||||
obj.stbl_stsd = parse_stbl(b);
|
obj.stblStsd = parseStbl(b);
|
||||||
break;
|
break;
|
||||||
case ATOM_VMHD:
|
case ATOM_VMHD:
|
||||||
case ATOM_SMHD:
|
case ATOM_SMHD:
|
||||||
obj.$mhd = readFullBox(b);
|
obj.mhd = readFullBox(b);
|
||||||
break;
|
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);
|
Box b = untilBox(ref, ATOM_STSD);
|
||||||
|
|
||||||
if (b == null) {
|
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);
|
return readFullBox(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Box {
|
class Box {
|
||||||
|
|
||||||
int type;
|
int type;
|
||||||
long offset;
|
long offset;
|
||||||
long size;
|
long size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Moof {
|
public class Moof {
|
||||||
|
int mfhdSequenceNumber;
|
||||||
int mfhd_SequenceNumber;
|
|
||||||
public Traf traf;
|
public Traf traf;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Traf {
|
public class Traf {
|
||||||
|
|
||||||
public Tfhd tfhd;
|
public Tfhd tfhd;
|
||||||
long tfdt;
|
long tfdt;
|
||||||
public Trun trun;
|
public Trun trun;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Tfhd {
|
public class Tfhd {
|
||||||
|
|
||||||
int bFlags;
|
int bFlags;
|
||||||
public int trackId;
|
public int trackId;
|
||||||
int defaultSampleDuration;
|
int defaultSampleDuration;
|
||||||
@ -821,7 +820,6 @@ public class Mp4DashReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class TrunEntry {
|
class TrunEntry {
|
||||||
|
|
||||||
int sampleDuration;
|
int sampleDuration;
|
||||||
int sampleSize;
|
int sampleSize;
|
||||||
int sampleFlags;
|
int sampleFlags;
|
||||||
@ -833,7 +831,6 @@ public class Mp4DashReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class Trun {
|
public class Trun {
|
||||||
|
|
||||||
public int chunkDuration;
|
public int chunkDuration;
|
||||||
public int chunkSize;
|
public int chunkSize;
|
||||||
|
|
||||||
@ -843,10 +840,10 @@ public class Mp4DashReader {
|
|||||||
|
|
||||||
public int entryCount;
|
public int entryCount;
|
||||||
byte[] bEntries;
|
byte[] bEntries;
|
||||||
int entries_rowSize;
|
int entriesRowSize;
|
||||||
|
|
||||||
public TrunEntry getEntry(int i) {
|
public TrunEntry getEntry(final int i) {
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(bEntries, i * entries_rowSize, entries_rowSize);
|
ByteBuffer buffer = ByteBuffer.wrap(bEntries, i * entriesRowSize, entriesRowSize);
|
||||||
TrunEntry entry = new TrunEntry();
|
TrunEntry entry = new TrunEntry();
|
||||||
|
|
||||||
if (hasFlag(bFlags, 0x0100)) {
|
if (hasFlag(bFlags, 0x0100)) {
|
||||||
@ -868,7 +865,7 @@ public class Mp4DashReader {
|
|||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TrunEntry getAbsoluteEntry(int i, Tfhd header) {
|
public TrunEntry getAbsoluteEntry(final int i, final Tfhd header) {
|
||||||
TrunEntry entry = getEntry(i);
|
TrunEntry entry = getEntry(i);
|
||||||
|
|
||||||
if (!hasFlag(bFlags, 0x0100) && hasFlag(header.bFlags, 0x20)) {
|
if (!hasFlag(bFlags, 0x0100) && hasFlag(header.bFlags, 0x20)) {
|
||||||
@ -892,7 +889,6 @@ public class Mp4DashReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class Tkhd {
|
public class Tkhd {
|
||||||
|
|
||||||
int trackId;
|
int trackId;
|
||||||
long duration;
|
long duration;
|
||||||
short bVolume;
|
short bVolume;
|
||||||
@ -904,28 +900,24 @@ public class Mp4DashReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class Trak {
|
public class Trak {
|
||||||
|
|
||||||
public Tkhd tkhd;
|
public Tkhd tkhd;
|
||||||
public Elst edst_elst;
|
public Elst edstElst;
|
||||||
public Mdia mdia;
|
public Mdia mdia;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Mvhd {
|
class Mvhd {
|
||||||
|
|
||||||
long timeScale;
|
long timeScale;
|
||||||
long nextTrackId;
|
long nextTrackId;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Moov {
|
class Moov {
|
||||||
|
|
||||||
Mvhd mvhd;
|
Mvhd mvhd;
|
||||||
Trak[] trak;
|
Trak[] trak;
|
||||||
Trex[] mvex_trex;
|
Trex[] mvexTrex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Trex {
|
public class Trex {
|
||||||
|
|
||||||
private int trackId;
|
private int trackId;
|
||||||
int defaultSampleDescriptionIndex;
|
int defaultSampleDescriptionIndex;
|
||||||
int defaultSampleDuration;
|
int defaultSampleDuration;
|
||||||
@ -934,42 +926,36 @@ public class Mp4DashReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class Elst {
|
public class Elst {
|
||||||
|
public long mediaTime;
|
||||||
public long MediaTime;
|
|
||||||
public int bMediaRate;
|
public int bMediaRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Mdia {
|
public class Mdia {
|
||||||
|
public int mdhdTimeScale;
|
||||||
public int mdhd_timeScale;
|
|
||||||
public byte[] mdhd;
|
public byte[] mdhd;
|
||||||
public Hdlr hdlr;
|
public Hdlr hdlr;
|
||||||
public Minf minf;
|
public Minf minf;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Hdlr {
|
public class Hdlr {
|
||||||
|
|
||||||
public int type;
|
public int type;
|
||||||
public int subType;
|
public int subType;
|
||||||
public byte[] bReserved;
|
public byte[] bReserved;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Minf {
|
public class Minf {
|
||||||
|
|
||||||
public byte[] dinf;
|
public byte[] dinf;
|
||||||
public byte[] stbl_stsd;
|
public byte[] stblStsd;
|
||||||
public byte[] $mhd;
|
public byte[] mhd;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Mp4Track {
|
public class Mp4Track {
|
||||||
|
|
||||||
public TrackKind kind;
|
public TrackKind kind;
|
||||||
public Trak trak;
|
public Trak trak;
|
||||||
public Trex trex;
|
public Trex trex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Mp4DashChunk {
|
public class Mp4DashChunk {
|
||||||
|
|
||||||
public InputStream data;
|
public InputStream data;
|
||||||
public Moof moof;
|
public Moof moof;
|
||||||
private int i = 0;
|
private int i = 0;
|
||||||
@ -1002,9 +988,7 @@ public class Mp4DashReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class Mp4DashSample {
|
public class Mp4DashSample {
|
||||||
|
|
||||||
public TrunEntry info;
|
public TrunEntry info;
|
||||||
public byte[] data;
|
public byte[] data;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,13 +17,15 @@ import java.util.ArrayList;
|
|||||||
* @author kapodamy
|
* @author kapodamy
|
||||||
*/
|
*/
|
||||||
public class Mp4FromDashWriter {
|
public class Mp4FromDashWriter {
|
||||||
|
private static final int EPOCH_OFFSET = 2082844800;
|
||||||
private final static int EPOCH_OFFSET = 2082844800;
|
private static final short DEFAULT_TIMESCALE = 1000;
|
||||||
private final static short DEFAULT_TIMESCALE = 1000;
|
private static final byte SAMPLES_PER_CHUNK_INIT = 2;
|
||||||
private final static byte SAMPLES_PER_CHUNK_INIT = 2;
|
// ffmpeg uses 2, basic uses 1 (with 60fps uses 21 or 22). NewPipe will use 6
|
||||||
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 static final byte SAMPLES_PER_CHUNK = 6;
|
||||||
private final static long THRESHOLD_FOR_CO64 = 0xFFFEFFFFL;// near 3.999 GiB
|
// 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 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;
|
private final long time;
|
||||||
|
|
||||||
@ -48,7 +50,7 @@ public class Mp4FromDashWriter {
|
|||||||
|
|
||||||
private final ArrayList<Integer> compatibleBrands = new ArrayList<>(5);
|
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) {
|
for (SharpStream src : sources) {
|
||||||
if (!src.canRewind() && !src.canRead()) {
|
if (!src.canRewind() && !src.canRead()) {
|
||||||
throw new IOException("All sources must be readable and allow rewind");
|
throw new IOException("All sources must be readable and allow rewind");
|
||||||
@ -60,12 +62,12 @@ public class Mp4FromDashWriter {
|
|||||||
readersChunks = new Mp4DashChunk[readers.length];
|
readersChunks = new Mp4DashChunk[readers.length];
|
||||||
time = (System.currentTimeMillis() / 1000L) + EPOCH_OFFSET;
|
time = (System.currentTimeMillis() / 1000L) + EPOCH_OFFSET;
|
||||||
|
|
||||||
compatibleBrands.add(0x6D703431);// mp41
|
compatibleBrands.add(0x6D703431); // mp41
|
||||||
compatibleBrands.add(0x69736F6D);// isom
|
compatibleBrands.add(0x69736F6D); // isom
|
||||||
compatibleBrands.add(0x69736F32);// iso2
|
compatibleBrands.add(0x69736F32); // iso2
|
||||||
}
|
}
|
||||||
|
|
||||||
public Mp4Track[] getTracksFromSource(int sourceIndex) throws IllegalStateException {
|
public Mp4Track[] getTracksFromSource(final int sourceIndex) throws IllegalStateException {
|
||||||
if (!parsed) {
|
if (!parsed) {
|
||||||
throw new IllegalStateException("All sources must be parsed first");
|
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) {
|
if (done) {
|
||||||
throw new IOException("already 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;
|
overrideMainBrand = brand;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +142,7 @@ public class Mp4FromDashWriter {
|
|||||||
outStream = null;
|
outStream = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void build(SharpStream output) throws IOException {
|
public void build(final SharpStream output) throws IOException {
|
||||||
if (done) {
|
if (done) {
|
||||||
throw new RuntimeException("already done");
|
throw new RuntimeException("already done");
|
||||||
}
|
}
|
||||||
@ -153,7 +155,7 @@ public class Mp4FromDashWriter {
|
|||||||
// not allowed for very short tracks (less than 0.5 seconds)
|
// not allowed for very short tracks (less than 0.5 seconds)
|
||||||
//
|
//
|
||||||
outStream = output;
|
outStream = output;
|
||||||
long read = 8;// mdat box header size
|
long read = 8; // mdat box header size
|
||||||
long totalSampleSize = 0;
|
long totalSampleSize = 0;
|
||||||
int[] sampleExtra = new int[readers.length];
|
int[] sampleExtra = new int[readers.length];
|
||||||
int[] defaultMediaTime = new int[readers.length];
|
int[] defaultMediaTime = new int[readers.length];
|
||||||
@ -165,12 +167,12 @@ public class Mp4FromDashWriter {
|
|||||||
tablesInfo[i] = new TablesInfo();
|
tablesInfo[i] = new TablesInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
int single_sample_buffer;
|
int singleSampleBuffer;
|
||||||
if (tracks.length == 1 && tracks[0].kind == TrackKind.Audio) {
|
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
|
// 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 {
|
} else {
|
||||||
single_sample_buffer = -1;
|
singleSampleBuffer = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -187,7 +189,7 @@ public class Mp4FromDashWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
read += chunk.moof.traf.trun.chunkSize;
|
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;
|
TrunEntry info;
|
||||||
while ((info = chunk.getNextSampleInfo()) != null) {
|
while ((info = chunk.getNextSampleInfo()) != null) {
|
||||||
@ -222,8 +224,8 @@ public class Mp4FromDashWriter {
|
|||||||
|
|
||||||
readers[i].rewind();
|
readers[i].rewind();
|
||||||
|
|
||||||
if (single_sample_buffer > 0) {
|
if (singleSampleBuffer > 0) {
|
||||||
initChunkTables(tablesInfo[i], single_sample_buffer, single_sample_buffer);
|
initChunkTables(tablesInfo[i], singleSampleBuffer, singleSampleBuffer);
|
||||||
} else {
|
} else {
|
||||||
initChunkTables(tablesInfo[i], SAMPLES_PER_CHUNK_INIT, SAMPLES_PER_CHUNK);
|
initChunkTables(tablesInfo[i], SAMPLES_PER_CHUNK_INIT, SAMPLES_PER_CHUNK);
|
||||||
}
|
}
|
||||||
@ -232,18 +234,18 @@ public class Mp4FromDashWriter {
|
|||||||
|
|
||||||
if (sampleSizeChanges == 1) {
|
if (sampleSizeChanges == 1) {
|
||||||
tablesInfo[i].stsz = 0;
|
tablesInfo[i].stsz = 0;
|
||||||
tablesInfo[i].stsz_default = samplesSize;
|
tablesInfo[i].stszDefault = samplesSize;
|
||||||
} else {
|
} else {
|
||||||
tablesInfo[i].stsz_default = 0;
|
tablesInfo[i].stszDefault = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tablesInfo[i].stss == tablesInfo[i].stsz) {
|
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
|
// ensure track duration
|
||||||
if (tracks[i].trak.tkhd.duration < 1) {
|
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;
|
boolean is64 = read > THRESHOLD_FOR_CO64;
|
||||||
|
|
||||||
// calculate the moov size
|
// calculate the moov size
|
||||||
int auxSize = make_moov(defaultMediaTime, tablesInfo, is64);
|
int auxSize = makeMoov(defaultMediaTime, tablesInfo, is64);
|
||||||
|
|
||||||
if (auxSize < THRESHOLD_MOOV_LENGTH) {
|
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;
|
moovSimulation = false;
|
||||||
writeOffset = 0;
|
writeOffset = 0;
|
||||||
|
|
||||||
final int ftyp_size = make_ftyp();
|
final int ftypSize = makeFtyp();
|
||||||
|
|
||||||
// reserve moov space in the output stream
|
// reserve moov space in the output stream
|
||||||
if (auxSize > 0) {
|
if (auxSize > 0) {
|
||||||
int length = auxSize;
|
int length = auxSize;
|
||||||
byte[] buffer = new byte[64 * 1024];// 64 KiB
|
byte[] buffer = new byte[64 * 1024]; // 64 KiB
|
||||||
while (length > 0) {
|
while (length > 0) {
|
||||||
int count = Math.min(length, buffer.length);
|
int count = Math.min(length, buffer.length);
|
||||||
outWrite(buffer, count);
|
outWrite(buffer, count);
|
||||||
@ -274,21 +276,22 @@ public class Mp4FromDashWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (auxBuffer == null) {
|
if (auxBuffer == null) {
|
||||||
outSeek(ftyp_size);
|
outSeek(ftypSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// tablesInfo contains row counts
|
// tablesInfo contains row counts
|
||||||
// and after returning from make_moov() will contain those table offsets
|
// and after returning from makeMoov() will contain those table offsets
|
||||||
make_moov(defaultMediaTime, tablesInfo, is64);
|
makeMoov(defaultMediaTime, tablesInfo, is64);
|
||||||
|
|
||||||
// write tables: stts stsc sbgp
|
// write tables: stts stsc sbgp
|
||||||
// reset for ctts table: sampleCount sampleExtra
|
// reset for ctts table: sampleCount sampleExtra
|
||||||
for (int i = 0; i < readers.length; i++) {
|
for (int i = 0; i < readers.length; i++) {
|
||||||
writeEntryArray(tablesInfo[i].stts, 2, sampleCount[i], defaultSampleDuration[i]);
|
writeEntryArray(tablesInfo[i].stts, 2, sampleCount[i], defaultSampleDuration[i]);
|
||||||
writeEntryArray(tablesInfo[i].stsc, tablesInfo[i].stsc_bEntries.length, tablesInfo[i].stsc_bEntries);
|
writeEntryArray(tablesInfo[i].stsc, tablesInfo[i].stscBEntries.length,
|
||||||
tablesInfo[i].stsc_bEntries = null;
|
tablesInfo[i].stscBEntries);
|
||||||
|
tablesInfo[i].stscBEntries = null;
|
||||||
if (tablesInfo[i].ctts > 0) {
|
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;
|
sampleExtra[i] = -1;
|
||||||
}
|
}
|
||||||
if (tablesInfo[i].sbgp > 0) {
|
if (tablesInfo[i].sbgp > 0) {
|
||||||
@ -300,11 +303,11 @@ public class Mp4FromDashWriter {
|
|||||||
outRestore();
|
outRestore();
|
||||||
}
|
}
|
||||||
|
|
||||||
outWrite(make_mdat(totalSampleSize, is64));
|
outWrite(makeMdat(totalSampleSize, is64));
|
||||||
|
|
||||||
int[] sampleIndex = new int[readers.length];
|
int[] sampleIndex = new int[readers.length];
|
||||||
int[] sizes = 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[single_sample_buffer > 0 ? single_sample_buffer : SAMPLES_PER_CHUNK];
|
int[] sync = new int[singleSampleBuffer > 0 ? singleSampleBuffer : SAMPLES_PER_CHUNK];
|
||||||
|
|
||||||
int written = readers.length;
|
int written = readers.length;
|
||||||
while (written > 0) {
|
while (written > 0) {
|
||||||
@ -312,14 +315,14 @@ public class Mp4FromDashWriter {
|
|||||||
|
|
||||||
for (int i = 0; i < readers.length; i++) {
|
for (int i = 0; i < readers.length; i++) {
|
||||||
if (sampleIndex[i] < 0) {
|
if (sampleIndex[i] < 0) {
|
||||||
continue;// track is done
|
continue; // track is done
|
||||||
}
|
}
|
||||||
|
|
||||||
long chunkOffset = writeOffset;
|
long chunkOffset = writeOffset;
|
||||||
int syncCount = 0;
|
int syncCount = 0;
|
||||||
int limit;
|
int limit;
|
||||||
if (single_sample_buffer > 0) {
|
if (singleSampleBuffer > 0) {
|
||||||
limit = single_sample_buffer;
|
limit = singleSampleBuffer;
|
||||||
} else {
|
} else {
|
||||||
limit = sampleIndex[i] == 0 ? SAMPLES_PER_CHUNK_INIT : SAMPLES_PER_CHUNK;
|
limit = sampleIndex[i] == 0 ? SAMPLES_PER_CHUNK_INIT : SAMPLES_PER_CHUNK;
|
||||||
}
|
}
|
||||||
@ -330,7 +333,8 @@ public class Mp4FromDashWriter {
|
|||||||
|
|
||||||
if (sample == null) {
|
if (sample == null) {
|
||||||
if (tablesInfo[i].ctts > 0 && sampleExtra[i] >= 0) {
|
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();
|
outRestore();
|
||||||
}
|
}
|
||||||
sampleIndex[i] = -1;
|
sampleIndex[i] = -1;
|
||||||
@ -344,7 +348,8 @@ public class Mp4FromDashWriter {
|
|||||||
sampleCount[i]++;
|
sampleCount[i]++;
|
||||||
} else {
|
} else {
|
||||||
if (sampleExtra[i] >= 0) {
|
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();
|
outRestore();
|
||||||
}
|
}
|
||||||
sampleCount[i] = 1;
|
sampleCount[i] = 1;
|
||||||
@ -378,7 +383,8 @@ public class Mp4FromDashWriter {
|
|||||||
if (is64) {
|
if (is64) {
|
||||||
tablesInfo[i].stco = writeEntry64(tablesInfo[i].stco, chunkOffset);
|
tablesInfo[i].stco = writeEntry64(tablesInfo[i].stco, chunkOffset);
|
||||||
} else {
|
} 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) {
|
if (auxBuffer != null) {
|
||||||
// dump moov
|
// dump moov
|
||||||
outSeek(ftyp_size);
|
outSeek(ftypSize);
|
||||||
outStream.write(auxBuffer.array(), 0, auxBuffer.capacity());
|
outStream.write(auxBuffer.array(), 0, auxBuffer.capacity());
|
||||||
auxBuffer = null;
|
auxBuffer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mp4DashSample getNextSample(int track) throws IOException {
|
private Mp4DashSample getNextSample(final int track) throws IOException {
|
||||||
if (readersChunks[track] == null) {
|
if (readersChunks[track] == null) {
|
||||||
readersChunks[track] = readers[track].getNextChunk(false);
|
readersChunks[track] = readers[track].getNextChunk(false);
|
||||||
if (readersChunks[track] == null) {
|
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();
|
outBackup();
|
||||||
|
|
||||||
auxSeek(offset);
|
auxSeek(offset);
|
||||||
@ -422,7 +428,8 @@ public class Mp4FromDashWriter {
|
|||||||
return offset + 8;
|
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();
|
outBackup();
|
||||||
|
|
||||||
auxSeek(offset);
|
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)
|
// tables.stsz holds amount of samples of the track (total)
|
||||||
int totalSamples = (tables.stsz - firstCount);
|
int totalSamples = (tables.stsz - firstCount);
|
||||||
float chunkAmount = totalSamples / (float) succesiveCount;
|
float chunkAmount = totalSamples / (float) succesiveCount;
|
||||||
@ -473,36 +481,36 @@ public class Mp4FromDashWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// stsc_table_entry = [first_chunk, samples_per_chunk, sample_description_index]
|
// stsc_table_entry = [first_chunk, samples_per_chunk, sample_description_index]
|
||||||
tables.stsc_bEntries = new int[tables.stsc * 3];
|
tables.stscBEntries = new int[tables.stsc * 3];
|
||||||
tables.stco = remainChunkOffset + 1;// total entrys in chunk offset box
|
tables.stco = remainChunkOffset + 1; // total entrys in chunk offset box
|
||||||
|
|
||||||
tables.stsc_bEntries[index++] = 1;
|
tables.stscBEntries[index++] = 1;
|
||||||
tables.stsc_bEntries[index++] = firstCount;
|
tables.stscBEntries[index++] = firstCount;
|
||||||
tables.stsc_bEntries[index++] = 1;
|
tables.stscBEntries[index++] = 1;
|
||||||
|
|
||||||
if (firstCount != succesiveCount) {
|
if (firstCount != succesiveCount) {
|
||||||
tables.stsc_bEntries[index++] = 2;
|
tables.stscBEntries[index++] = 2;
|
||||||
tables.stsc_bEntries[index++] = succesiveCount;
|
tables.stscBEntries[index++] = succesiveCount;
|
||||||
tables.stsc_bEntries[index++] = 1;
|
tables.stscBEntries[index++] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remain) {
|
if (remain) {
|
||||||
tables.stsc_bEntries[index++] = remainChunkOffset + 1;
|
tables.stscBEntries[index++] = remainChunkOffset + 1;
|
||||||
tables.stsc_bEntries[index++] = totalSamples % succesiveCount;
|
tables.stscBEntries[index++] = totalSamples % succesiveCount;
|
||||||
tables.stsc_bEntries[index] = 1;
|
tables.stscBEntries[index] = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void outWrite(byte[] buffer) throws IOException {
|
private void outWrite(final byte[] buffer) throws IOException {
|
||||||
outWrite(buffer, buffer.length);
|
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;
|
writeOffset += count;
|
||||||
outStream.write(buffer, 0, count);
|
outStream.write(buffer, 0, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void outSeek(long offset) throws IOException {
|
private void outSeek(final long offset) throws IOException {
|
||||||
if (outStream.canSeek()) {
|
if (outStream.canSeek()) {
|
||||||
outStream.seek(offset);
|
outStream.seek(offset);
|
||||||
writeOffset = 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);
|
outStream.skip(amount);
|
||||||
writeOffset += amount;
|
writeOffset += amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int lengthFor(int offset) throws IOException {
|
private int lengthFor(final int offset) throws IOException {
|
||||||
int size = auxOffset() - offset;
|
int size = auxOffset() - offset;
|
||||||
|
|
||||||
if (moovSimulation) {
|
if (moovSimulation) {
|
||||||
@ -534,7 +542,8 @@ public class Mp4FromDashWriter {
|
|||||||
return size;
|
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;
|
final byte base = 16;
|
||||||
int size = columns * rows * 4;
|
int size = columns * rows * 4;
|
||||||
int total = size + base;
|
int total = size + base;
|
||||||
@ -562,14 +571,14 @@ public class Mp4FromDashWriter {
|
|||||||
return offset + base;
|
return offset + base;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void auxWrite(int value) throws IOException {
|
private void auxWrite(final int value) throws IOException {
|
||||||
auxWrite(ByteBuffer.allocate(4)
|
auxWrite(ByteBuffer.allocate(4)
|
||||||
.putInt(value)
|
.putInt(value)
|
||||||
.array()
|
.array()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void auxWrite(byte[] buffer) throws IOException {
|
private void auxWrite(final byte[] buffer) throws IOException {
|
||||||
if (moovSimulation) {
|
if (moovSimulation) {
|
||||||
writeOffset += buffer.length;
|
writeOffset += buffer.length;
|
||||||
} else if (auxBuffer == null) {
|
} 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) {
|
if (moovSimulation) {
|
||||||
writeOffset = offset;
|
writeOffset = offset;
|
||||||
} else if (auxBuffer == null) {
|
} 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) {
|
if (moovSimulation) {
|
||||||
writeOffset += amount;
|
writeOffset += amount;
|
||||||
} else if (auxBuffer == null) {
|
} else if (auxBuffer == null) {
|
||||||
@ -603,27 +612,27 @@ public class Mp4FromDashWriter {
|
|||||||
return auxBuffer == null ? (int) writeOffset : auxBuffer.position();
|
return auxBuffer == null ? (int) writeOffset : auxBuffer.position();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int makeFtyp() throws IOException {
|
||||||
|
|
||||||
private int make_ftyp() throws IOException {
|
|
||||||
int size = 16 + (compatibleBrands.size() * 4);
|
int size = 16 + (compatibleBrands.size() * 4);
|
||||||
if (overrideMainBrand != 0) size += 4;
|
if (overrideMainBrand != 0) {
|
||||||
|
size += 4;
|
||||||
|
}
|
||||||
|
|
||||||
ByteBuffer buffer = ByteBuffer.allocate(size);
|
ByteBuffer buffer = ByteBuffer.allocate(size);
|
||||||
buffer.putInt(size);
|
buffer.putInt(size);
|
||||||
buffer.putInt(0x66747970);// "ftyp"
|
buffer.putInt(0x66747970); // "ftyp"
|
||||||
|
|
||||||
if (overrideMainBrand == 0) {
|
if (overrideMainBrand == 0) {
|
||||||
buffer.putInt(0x6D703432);// mayor brand "mp42"
|
buffer.putInt(0x6D703432); // mayor brand "mp42"
|
||||||
buffer.putInt(512);// default minor version
|
buffer.putInt(512); // default minor version
|
||||||
} else {
|
} else {
|
||||||
buffer.putInt(overrideMainBrand);
|
buffer.putInt(overrideMainBrand);
|
||||||
buffer.putInt(0);
|
buffer.putInt(0);
|
||||||
buffer.putInt(0x6D703432);// "mp42" compatible brand
|
buffer.putInt(0x6D703432); // "mp42" compatible brand
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Integer brand : compatibleBrands) {
|
for (Integer brand : compatibleBrands) {
|
||||||
buffer.putInt(brand);// compatible brand
|
buffer.putInt(brand); // compatible brand
|
||||||
}
|
}
|
||||||
|
|
||||||
outWrite(buffer.array());
|
outWrite(buffer.array());
|
||||||
@ -631,7 +640,7 @@ public class Mp4FromDashWriter {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] make_mdat(long refSize, boolean is64) {
|
private byte[] makeMdat(long refSize, final boolean is64) {
|
||||||
if (is64) {
|
if (is64) {
|
||||||
refSize += 16;
|
refSize += 16;
|
||||||
} else {
|
} else {
|
||||||
@ -640,7 +649,7 @@ public class Mp4FromDashWriter {
|
|||||||
|
|
||||||
ByteBuffer buffer = ByteBuffer.allocate(is64 ? 16 : 8)
|
ByteBuffer buffer = ByteBuffer.allocate(is64 ? 16 : 8)
|
||||||
.putInt(is64 ? 0x01 : (int) refSize)
|
.putInt(is64 ? 0x01 : (int) refSize)
|
||||||
.putInt(0x6D646174);// mdat
|
.putInt(0x6D646174); // mdat
|
||||||
|
|
||||||
if (is64) {
|
if (is64) {
|
||||||
buffer.putLong(refSize);
|
buffer.putLong(refSize);
|
||||||
@ -649,7 +658,7 @@ public class Mp4FromDashWriter {
|
|||||||
return buffer.array();
|
return buffer.array();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void make_mvhd(long longestTrack) throws IOException {
|
private void makeMvhd(final long longestTrack) throws IOException {
|
||||||
auxWrite(new byte[]{
|
auxWrite(new byte[]{
|
||||||
0x00, 0x00, 0x00, 0x78, 0x6D, 0x76, 0x68, 0x64, 0x01, 0x00, 0x00, 0x00
|
0x00, 0x00, 0x00, 0x78, 0x6D, 0x76, 0x68, 0x64, 0x01, 0x00, 0x00, 0x00
|
||||||
});
|
});
|
||||||
@ -662,21 +671,22 @@ public class Mp4FromDashWriter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
auxWrite(new byte[]{
|
auxWrite(new byte[]{
|
||||||
0x00, 0x01, 0x00, 0x00, 0x01, 0x00,// default volume and rate
|
0x00, 0x01, 0x00, 0x00, 0x01, 0x00, // default volume and rate
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,// reserved values
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved values
|
||||||
// default matrix
|
// 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,
|
||||||
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
|
0x40, 0x00, 0x00, 0x00
|
||||||
});
|
});
|
||||||
auxWrite(new byte[24]);// predefined
|
auxWrite(new byte[24]); // predefined
|
||||||
auxWrite(ByteBuffer.allocate(4)
|
auxWrite(ByteBuffer.allocate(4)
|
||||||
.putInt(tracks.length + 1)
|
.putInt(tracks.length + 1)
|
||||||
.array()
|
.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();
|
int start = auxOffset();
|
||||||
|
|
||||||
auxWrite(new byte[]{
|
auxWrite(new byte[]{
|
||||||
@ -688,21 +698,21 @@ public class Mp4FromDashWriter {
|
|||||||
|
|
||||||
for (int i = 0; i < durations.length; i++) {
|
for (int i = 0; i < durations.length; i++) {
|
||||||
durations[i] = (long) Math.ceil(
|
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) {
|
if (durations[i] > longestTrack) {
|
||||||
longestTrack = durations[i];
|
longestTrack = durations[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
make_mvhd(longestTrack);
|
makeMvhd(longestTrack);
|
||||||
|
|
||||||
for (int i = 0; i < tracks.length; i++) {
|
for (int i = 0; i < tracks.length; i++) {
|
||||||
if (tracks[i].trak.tkhd.matrix.length != 36) {
|
if (tracks[i].trak.tkhd.matrix.length != 36) {
|
||||||
throw new RuntimeException("bad track matrix length (expected 36) in track n°" + i);
|
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
|
// 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,
|
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,
|
0x1F, (byte) 0xA9, 0x74, 0x6F, 0x6F, 0x00, 0x00, 0x00, 0x17, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00,
|
||||||
0x00, 0x01, 0x00, 0x00, 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);
|
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();
|
int start = auxOffset();
|
||||||
|
|
||||||
auxWrite(new byte[]{
|
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
|
0x00, 0x00, 0x00, 0x68, 0x74, 0x6B, 0x68, 0x64, 0x01, 0x00, 0x00, 0x03 // tkhd header
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -747,20 +758,20 @@ public class Mp4FromDashWriter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
auxWrite(new byte[]{
|
auxWrite(new byte[]{
|
||||||
0x00, 0x00, 0x00, 0x24, 0x65, 0x64, 0x74, 0x73,// edts 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
|
0x00, 0x00, 0x00, 0x1C, 0x65, 0x6C, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 // elst header
|
||||||
});
|
});
|
||||||
|
|
||||||
int bMediaRate;
|
int bMediaRate;
|
||||||
int mediaTime;
|
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?
|
// 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;
|
bMediaRate = 0x00010000;
|
||||||
} else {
|
} else {
|
||||||
mediaTime = (int) tracks[index].trak.edst_elst.MediaTime;
|
mediaTime = (int) tracks[index].trak.edstElst.mediaTime;
|
||||||
bMediaRate = tracks[index].trak.edst_elst.bMediaRate;
|
bMediaRate = tracks[index].trak.edstElst.bMediaRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
auxWrite(ByteBuffer
|
auxWrite(ByteBuffer
|
||||||
@ -771,32 +782,33 @@ public class Mp4FromDashWriter {
|
|||||||
.array()
|
.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);
|
lengthFor(start);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void make_mdia(Mdia mdia, TablesInfo tablesInfo, boolean is64, boolean isAudio) throws IOException {
|
private void makeMdia(final Mdia mdia, final TablesInfo tablesInfo, final boolean is64,
|
||||||
int start_mdia = auxOffset();
|
final boolean isAudio) throws IOException {
|
||||||
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x61});// mdia
|
int startMdia = auxOffset();
|
||||||
|
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x61}); // mdia
|
||||||
auxWrite(mdia.mdhd);
|
auxWrite(mdia.mdhd);
|
||||||
auxWrite(make_hdlr(mdia.hdlr));
|
auxWrite(makeHdlr(mdia.hdlr));
|
||||||
|
|
||||||
int start_minf = auxOffset();
|
int startMinf = auxOffset();
|
||||||
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x6D, 0x69, 0x6E, 0x66});// minf
|
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x6D, 0x69, 0x6E, 0x66}); // minf
|
||||||
auxWrite(mdia.minf.$mhd);
|
auxWrite(mdia.minf.mhd);
|
||||||
auxWrite(mdia.minf.dinf);
|
auxWrite(mdia.minf.dinf);
|
||||||
|
|
||||||
int start_stbl = auxOffset();
|
int startStbl = auxOffset();
|
||||||
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x73, 0x74, 0x62, 0x6C});// stbl
|
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x73, 0x74, 0x62, 0x6C}); // stbl
|
||||||
auxWrite(mdia.minf.stbl_stsd);
|
auxWrite(mdia.minf.stblStsd);
|
||||||
|
|
||||||
//
|
//
|
||||||
// In audio tracks the following tables is not required: ssts ctts
|
// In audio tracks the following tables is not required: ssts ctts
|
||||||
// And stsz can be empty if has a default sample size
|
// And stsz can be empty if has a default sample size
|
||||||
//
|
//
|
||||||
if (moovSimulation) {
|
if (moovSimulation) {
|
||||||
make(0x73747473, -1, 2, 1);// stts
|
make(0x73747473, -1, 2, 1); // stts
|
||||||
if (tablesInfo.stss > 0) {
|
if (tablesInfo.stss > 0) {
|
||||||
make(0x73747373, -1, 1, tablesInfo.stss);
|
make(0x73747373, -1, 1, tablesInfo.stss);
|
||||||
}
|
}
|
||||||
@ -804,7 +816,7 @@ public class Mp4FromDashWriter {
|
|||||||
make(0x63747473, -1, 2, tablesInfo.ctts);
|
make(0x63747473, -1, 2, tablesInfo.ctts);
|
||||||
}
|
}
|
||||||
make(0x73747363, -1, 3, tablesInfo.stsc);
|
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);
|
make(is64 ? 0x636F3634 : 0x7374636F, -1, is64 ? 2 : 1, tablesInfo.stco);
|
||||||
} else {
|
} else {
|
||||||
tablesInfo.stts = make(0x73747473, -1, 2, 1);
|
tablesInfo.stts = make(0x73747473, -1, 2, 1);
|
||||||
@ -815,23 +827,24 @@ public class Mp4FromDashWriter {
|
|||||||
tablesInfo.ctts = make(0x63747473, -1, 2, tablesInfo.ctts);
|
tablesInfo.ctts = make(0x63747473, -1, 2, tablesInfo.ctts);
|
||||||
}
|
}
|
||||||
tablesInfo.stsc = make(0x73747363, -1, 3, tablesInfo.stsc);
|
tablesInfo.stsc = make(0x73747363, -1, 3, tablesInfo.stsc);
|
||||||
tablesInfo.stsz = make(0x7374737A, tablesInfo.stsz_default, 1, tablesInfo.stsz);
|
tablesInfo.stsz = make(0x7374737A, tablesInfo.stszDefault, 1, tablesInfo.stsz);
|
||||||
tablesInfo.stco = make(is64 ? 0x636F3634 : 0x7374636F, -1, is64 ? 2 : 1, tablesInfo.stco);
|
tablesInfo.stco = make(is64 ? 0x636F3634 : 0x7374636F, -1, is64 ? 2 : 1,
|
||||||
|
tablesInfo.stco);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAudio) {
|
if (isAudio) {
|
||||||
auxWrite(make_sgpd());
|
auxWrite(makeSgpd());
|
||||||
tablesInfo.sbgp = make_sbgp();// during simulation the returned offset is ignored
|
tablesInfo.sbgp = makeSbgp(); // during simulation the returned offset is ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
lengthFor(start_stbl);
|
lengthFor(startStbl);
|
||||||
lengthFor(start_minf);
|
lengthFor(startMinf);
|
||||||
lengthFor(start_mdia);
|
lengthFor(startMdia);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] make_hdlr(Hdlr hdlr) {
|
private byte[] makeHdlr(final Hdlr hdlr) {
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(new byte[]{
|
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, 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)."
|
// 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.position(12);
|
||||||
buffer.putInt(hdlr.type);
|
buffer.putInt(hdlr.type);
|
||||||
buffer.putInt(hdlr.subType);
|
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();
|
return buffer.array();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int make_sbgp() throws IOException {
|
private int makeSbgp() throws IOException {
|
||||||
int offset = auxOffset();
|
int offset = auxOffset();
|
||||||
|
|
||||||
auxWrite(new byte[] {
|
auxWrite(new byte[] {
|
||||||
0x00, 0x00, 0x00, 0x1C,// box size
|
0x00, 0x00, 0x00, 0x1C, // box size
|
||||||
0x73, 0x62, 0x67, 0x70,// "sbpg"
|
0x73, 0x62, 0x67, 0x70, // "sbpg"
|
||||||
0x00, 0x00, 0x00, 0x00,// default box flags
|
0x00, 0x00, 0x00, 0x00, // default box flags
|
||||||
0x72, 0x6F, 0x6C, 0x6C,// group type "roll"
|
0x72, 0x6F, 0x6C, 0x6C, // group type "roll"
|
||||||
0x00, 0x00, 0x00, 0x01,// group table size
|
0x00, 0x00, 0x00, 0x01, // group table size
|
||||||
0x00, 0x00, 0x00, 0x00,// group[0] total samples (to be set later)
|
0x00, 0x00, 0x00, 0x00, // group[0] total samples (to be set later)
|
||||||
0x00, 0x00, 0x00, 0x01// group[0] description index
|
0x00, 0x00, 0x00, 0x01 // group[0] description index
|
||||||
});
|
});
|
||||||
|
|
||||||
return offset + 0x14;
|
return offset + 0x14;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] make_sgpd() {
|
private byte[] makeSgpd() {
|
||||||
/*
|
/*
|
||||||
* Sample Group Description Box
|
* Sample Group Description Box
|
||||||
*
|
*
|
||||||
@ -882,26 +895,25 @@ public class Mp4FromDashWriter {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(new byte[] {
|
ByteBuffer buffer = ByteBuffer.wrap(new byte[] {
|
||||||
0x00, 0x00, 0x00, 0x1A,// box size
|
0x00, 0x00, 0x00, 0x1A, // box size
|
||||||
0x73, 0x67, 0x70, 0x64,// "sgpd"
|
0x73, 0x67, 0x70, 0x64, // "sgpd"
|
||||||
0x01, 0x00, 0x00, 0x00,// box flags (unknown flag sets)
|
0x01, 0x00, 0x00, 0x00, // box flags (unknown flag sets)
|
||||||
0x72, 0x6F, 0x6C, 0x6C, // ¿¿group type??
|
0x72, 0x6F, 0x6C, 0x6C, // ¿¿group type??
|
||||||
0x00, 0x00, 0x00, 0x02,// ¿¿??
|
0x00, 0x00, 0x00, 0x02, // ¿¿??
|
||||||
0x00, 0x00, 0x00, 0x01,// ¿¿??
|
0x00, 0x00, 0x00, 0x01, // ¿¿??
|
||||||
(byte)0xFF, (byte)0xFF// ¿¿??
|
(byte) 0xFF, (byte) 0xFF // ¿¿??
|
||||||
});
|
});
|
||||||
|
|
||||||
return buffer.array();
|
return buffer.array();
|
||||||
}
|
}
|
||||||
|
|
||||||
class TablesInfo {
|
class TablesInfo {
|
||||||
|
|
||||||
int stts;
|
int stts;
|
||||||
int stsc;
|
int stsc;
|
||||||
int[] stsc_bEntries;
|
int[] stscBEntries;
|
||||||
int ctts;
|
int ctts;
|
||||||
int stsz;
|
int stsz;
|
||||||
int stsz_default;
|
int stszDefault;
|
||||||
int stss;
|
int stss;
|
||||||
int stco;
|
int stco;
|
||||||
int sbgp;
|
int sbgp;
|
||||||
|
@ -1,431 +1,428 @@
|
|||||||
package org.schabi.newpipe.streams;
|
package org.schabi.newpipe.streams;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
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.Cluster;
|
||||||
import org.schabi.newpipe.streams.WebMReader.SimpleBlock;
|
import org.schabi.newpipe.streams.WebMReader.Segment;
|
||||||
import org.schabi.newpipe.streams.WebMReader.WebMTrack;
|
import org.schabi.newpipe.streams.WebMReader.SimpleBlock;
|
||||||
import org.schabi.newpipe.streams.io.SharpStream;
|
import org.schabi.newpipe.streams.WebMReader.WebMTrack;
|
||||||
|
import org.schabi.newpipe.streams.io.SharpStream;
|
||||||
import java.io.Closeable;
|
|
||||||
import java.io.IOException;
|
import java.io.Closeable;
|
||||||
import java.nio.ByteBuffer;
|
import java.io.IOException;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
/**
|
||||||
/**
|
* @author kapodamy
|
||||||
* @author kapodamy
|
*/
|
||||||
*/
|
public class OggFromWebMWriter implements Closeable {
|
||||||
public class OggFromWebMWriter implements Closeable {
|
private static final byte FLAG_UNSET = 0x00;
|
||||||
|
//private static final byte FLAG_CONTINUED = 0x01;
|
||||||
private static final byte FLAG_UNSET = 0x00;
|
private static final byte FLAG_FIRST = 0x02;
|
||||||
//private static final byte FLAG_CONTINUED = 0x01;
|
private static final byte FLAG_LAST = 0x04;
|
||||||
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 final static byte HEADER_CHECKSUM_OFFSET = 22;
|
|
||||||
private final static byte HEADER_SIZE = 27;
|
private static final int TIME_SCALE_NS = 1000000000;
|
||||||
|
|
||||||
private final static int TIME_SCALE_NS = 1000000000;
|
private boolean done = false;
|
||||||
|
private boolean parsed = false;
|
||||||
private boolean done = false;
|
|
||||||
private boolean parsed = false;
|
private SharpStream source;
|
||||||
|
private SharpStream output;
|
||||||
private SharpStream source;
|
|
||||||
private SharpStream output;
|
private int sequenceCount = 0;
|
||||||
|
private final int streamId;
|
||||||
private int sequence_count = 0;
|
private byte packetFlag = FLAG_FIRST;
|
||||||
private final int STREAM_ID;
|
|
||||||
private byte packet_flag = FLAG_FIRST;
|
private WebMReader webm = null;
|
||||||
|
private WebMTrack webmTrack = null;
|
||||||
private WebMReader webm = null;
|
private Segment webmSegment = null;
|
||||||
private WebMTrack webm_track = null;
|
private Cluster webmCluster = null;
|
||||||
private Segment webm_segment = null;
|
private SimpleBlock webmBlock = null;
|
||||||
private Cluster webm_cluster = null;
|
|
||||||
private SimpleBlock webm_block = null;
|
private long webmBlockLastTimecode = 0;
|
||||||
|
private long webmBlockNearDuration = 0;
|
||||||
private long webm_block_last_timecode = 0;
|
|
||||||
private long webm_block_near_duration = 0;
|
private short segmentTableSize = 0;
|
||||||
|
private final byte[] segmentTable = new byte[255];
|
||||||
private short segment_table_size = 0;
|
private long segmentTableNextTimestamp = TIME_SCALE_NS;
|
||||||
private final byte[] segment_table = new byte[255];
|
|
||||||
private long segment_table_next_timestamp = TIME_SCALE_NS;
|
private final int[] crc32Table = new int[256];
|
||||||
|
|
||||||
private final int[] crc32_table = new int[256];
|
public OggFromWebMWriter(@NonNull final SharpStream source, @NonNull final SharpStream target) {
|
||||||
|
if (!source.canRead() || !source.canRewind()) {
|
||||||
public OggFromWebMWriter(@NonNull SharpStream source, @NonNull SharpStream target) {
|
throw new IllegalArgumentException("source stream must be readable and allows seeking");
|
||||||
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");
|
||||||
if (!target.canWrite() || !target.canRewind()) {
|
}
|
||||||
throw new IllegalArgumentException("output stream must be writable and allows seeking");
|
|
||||||
}
|
this.source = source;
|
||||||
|
this.output = target;
|
||||||
this.source = source;
|
|
||||||
this.output = target;
|
this.streamId = (int) System.currentTimeMillis();
|
||||||
|
|
||||||
this.STREAM_ID = (int) System.currentTimeMillis();
|
populateCrc32Table();
|
||||||
|
}
|
||||||
populate_crc32_table();
|
|
||||||
}
|
public boolean isDone() {
|
||||||
|
return done;
|
||||||
public boolean isDone() {
|
}
|
||||||
return done;
|
|
||||||
}
|
public boolean isParsed() {
|
||||||
|
return parsed;
|
||||||
public boolean isParsed() {
|
}
|
||||||
return parsed;
|
|
||||||
}
|
public WebMTrack[] getTracksFromSource() throws IllegalStateException {
|
||||||
|
if (!parsed) {
|
||||||
public WebMTrack[] getTracksFromSource() throws IllegalStateException {
|
throw new IllegalStateException("source must be parsed first");
|
||||||
if (!parsed) {
|
}
|
||||||
throw new IllegalStateException("source must be parsed first");
|
|
||||||
}
|
return webm.getAvailableTracks();
|
||||||
|
}
|
||||||
return webm.getAvailableTracks();
|
|
||||||
}
|
public void parseSource() throws IOException, IllegalStateException {
|
||||||
|
if (done) {
|
||||||
public void parseSource() throws IOException, IllegalStateException {
|
throw new IllegalStateException("already done");
|
||||||
if (done) {
|
}
|
||||||
throw new IllegalStateException("already done");
|
if (parsed) {
|
||||||
}
|
throw new IllegalStateException("already parsed");
|
||||||
if (parsed) {
|
}
|
||||||
throw new IllegalStateException("already parsed");
|
|
||||||
}
|
try {
|
||||||
|
webm = new WebMReader(source);
|
||||||
try {
|
webm.parse();
|
||||||
webm = new WebMReader(source);
|
webmSegment = webm.getNextSegment();
|
||||||
webm.parse();
|
} finally {
|
||||||
webm_segment = webm.getNextSegment();
|
parsed = true;
|
||||||
} finally {
|
}
|
||||||
parsed = true;
|
}
|
||||||
}
|
|
||||||
}
|
public void selectTrack(final int trackIndex) throws IOException {
|
||||||
|
if (!parsed) {
|
||||||
public void selectTrack(int trackIndex) throws IOException {
|
throw new IllegalStateException("source must be parsed first");
|
||||||
if (!parsed) {
|
}
|
||||||
throw new IllegalStateException("source must be parsed first");
|
if (done) {
|
||||||
}
|
throw new IOException("already done");
|
||||||
if (done) {
|
}
|
||||||
throw new IOException("already done");
|
if (webmTrack != null) {
|
||||||
}
|
throw new IOException("tracks already selected");
|
||||||
if (webm_track != null) {
|
}
|
||||||
throw new IOException("tracks already selected");
|
|
||||||
}
|
switch (webm.getAvailableTracks()[trackIndex].kind) {
|
||||||
|
case Audio:
|
||||||
switch (webm.getAvailableTracks()[trackIndex].kind) {
|
case Video:
|
||||||
case Audio:
|
break;
|
||||||
case Video:
|
default:
|
||||||
break;
|
throw new UnsupportedOperationException("the track must an audio or video stream");
|
||||||
default:
|
}
|
||||||
throw new UnsupportedOperationException("the track must an audio or video stream");
|
|
||||||
}
|
try {
|
||||||
|
webmTrack = webm.selectTrack(trackIndex);
|
||||||
try {
|
} finally {
|
||||||
webm_track = webm.selectTrack(trackIndex);
|
parsed = true;
|
||||||
} finally {
|
}
|
||||||
parsed = true;
|
}
|
||||||
}
|
|
||||||
}
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
@Override
|
done = true;
|
||||||
public void close() throws IOException {
|
parsed = true;
|
||||||
done = true;
|
|
||||||
parsed = true;
|
webmTrack = null;
|
||||||
|
webm = null;
|
||||||
webm_track = null;
|
|
||||||
webm = null;
|
if (!output.isClosed()) {
|
||||||
|
output.flush();
|
||||||
if (!output.isClosed()) {
|
}
|
||||||
output.flush();
|
|
||||||
}
|
source.close();
|
||||||
|
output.close();
|
||||||
source.close();
|
}
|
||||||
output.close();
|
|
||||||
}
|
public void build() throws IOException {
|
||||||
|
float resolution;
|
||||||
public void build() throws IOException {
|
SimpleBlock bloq;
|
||||||
float resolution;
|
ByteBuffer header = ByteBuffer.allocate(27 + (255 * 255));
|
||||||
SimpleBlock bloq;
|
ByteBuffer page = ByteBuffer.allocate(64 * 1024);
|
||||||
ByteBuffer header = ByteBuffer.allocate(27 + (255 * 255));
|
|
||||||
ByteBuffer page = ByteBuffer.allocate(64 * 1024);
|
header.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
header.order(ByteOrder.LITTLE_ENDIAN);
|
/* step 1: get the amount of frames per seconds */
|
||||||
|
switch (webmTrack.kind) {
|
||||||
/* step 1: get the amount of frames per seconds */
|
case Audio:
|
||||||
switch (webm_track.kind) {
|
resolution = getSampleFrequencyFromTrack(webmTrack.bMetadata);
|
||||||
case Audio:
|
if (resolution == 0f) {
|
||||||
resolution = getSampleFrequencyFromTrack(webm_track.bMetadata);
|
throw new RuntimeException("cannot get the audio sample rate");
|
||||||
if (resolution == 0f) {
|
}
|
||||||
throw new RuntimeException("cannot get the audio sample rate");
|
break;
|
||||||
}
|
case Video:
|
||||||
break;
|
// WARNING: untested
|
||||||
case Video:
|
if (webmTrack.defaultDuration == 0) {
|
||||||
// WARNING: untested
|
throw new RuntimeException("missing default frame time");
|
||||||
if (webm_track.defaultDuration == 0) {
|
}
|
||||||
throw new RuntimeException("missing default frame time");
|
resolution = 1000f / ((float) webmTrack.defaultDuration
|
||||||
}
|
/ webmSegment.info.timecodeScale);
|
||||||
resolution = 1000f / ((float) webm_track.defaultDuration / webm_segment.info.timecodeScale);
|
break;
|
||||||
break;
|
default:
|
||||||
default:
|
throw new RuntimeException("not implemented");
|
||||||
throw new RuntimeException("not implemented");
|
}
|
||||||
}
|
|
||||||
|
/* step 2: create packet with code init data */
|
||||||
/* step 2: create packet with code init data */
|
if (webmTrack.codecPrivate != null) {
|
||||||
if (webm_track.codecPrivate != null) {
|
addPacketSegment(webmTrack.codecPrivate.length);
|
||||||
addPacketSegment(webm_track.codecPrivate.length);
|
makePacketheader(0x00, header, webmTrack.codecPrivate);
|
||||||
make_packetHeader(0x00, header, webm_track.codecPrivate);
|
write(header);
|
||||||
write(header);
|
output.write(webmTrack.codecPrivate);
|
||||||
output.write(webm_track.codecPrivate);
|
}
|
||||||
}
|
|
||||||
|
/* step 3: create packet with metadata */
|
||||||
/* step 3: create packet with metadata */
|
byte[] buffer = makeMetadata();
|
||||||
byte[] buffer = make_metadata();
|
if (buffer != null) {
|
||||||
if (buffer != null) {
|
addPacketSegment(buffer.length);
|
||||||
addPacketSegment(buffer.length);
|
makePacketheader(0x00, header, buffer);
|
||||||
make_packetHeader(0x00, header, buffer);
|
write(header);
|
||||||
write(header);
|
output.write(buffer);
|
||||||
output.write(buffer);
|
}
|
||||||
}
|
|
||||||
|
/* step 4: calculate amount of packets */
|
||||||
/* step 4: calculate amount of packets */
|
while (webmSegment != null) {
|
||||||
while (webm_segment != null) {
|
bloq = getNextBlock();
|
||||||
bloq = getNextBlock();
|
|
||||||
|
if (bloq != null && addPacketSegment(bloq)) {
|
||||||
if (bloq != null && addPacketSegment(bloq)) {
|
int pos = page.position();
|
||||||
int pos = page.position();
|
//noinspection ResultOfMethodCallIgnored
|
||||||
//noinspection ResultOfMethodCallIgnored
|
bloq.data.read(page.array(), pos, bloq.dataSize);
|
||||||
bloq.data.read(page.array(), pos, bloq.dataSize);
|
page.position(pos + bloq.dataSize);
|
||||||
page.position(pos + bloq.dataSize);
|
continue;
|
||||||
continue;
|
}
|
||||||
}
|
|
||||||
|
// calculate the current packet duration using the next block
|
||||||
// calculate the current packet duration using the next block
|
double elapsedNs = webmTrack.codecDelay;
|
||||||
double elapsed_ns = webm_track.codecDelay;
|
|
||||||
|
if (bloq == null) {
|
||||||
if (bloq == null) {
|
packetFlag = FLAG_LAST; // note: if the flag is FLAG_CONTINUED, is changed
|
||||||
packet_flag = FLAG_LAST;// note: if the flag is FLAG_CONTINUED, is changed
|
elapsedNs += webmBlockLastTimecode;
|
||||||
elapsed_ns += webm_block_last_timecode;
|
|
||||||
|
if (webmTrack.defaultDuration > 0) {
|
||||||
if (webm_track.defaultDuration > 0) {
|
elapsedNs += webmTrack.defaultDuration;
|
||||||
elapsed_ns += webm_track.defaultDuration;
|
} else {
|
||||||
} else {
|
// hardcoded way, guess the sample duration
|
||||||
// hardcoded way, guess the sample duration
|
elapsedNs += webmBlockNearDuration;
|
||||||
elapsed_ns += webm_block_near_duration;
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
elapsedNs += bloq.absoluteTimeCodeNs;
|
||||||
elapsed_ns += bloq.absoluteTimeCodeNs;
|
}
|
||||||
}
|
|
||||||
|
// get the sample count in the page
|
||||||
// get the sample count in the page
|
elapsedNs = elapsedNs / TIME_SCALE_NS;
|
||||||
elapsed_ns = elapsed_ns / TIME_SCALE_NS;
|
elapsedNs = Math.ceil(elapsedNs * resolution);
|
||||||
elapsed_ns = Math.ceil(elapsed_ns * resolution);
|
|
||||||
|
// create header and calculate page checksum
|
||||||
// create header and calculate page checksum
|
int checksum = makePacketheader((long) elapsedNs, header, null);
|
||||||
int checksum = make_packetHeader((long) elapsed_ns, header, null);
|
checksum = calcCrc32(checksum, page.array(), page.position());
|
||||||
checksum = calc_crc32(checksum, page.array(), page.position());
|
|
||||||
|
header.putInt(HEADER_CHECKSUM_OFFSET, checksum);
|
||||||
header.putInt(HEADER_CHECKSUM_OFFSET, checksum);
|
|
||||||
|
// dump data
|
||||||
// dump data
|
write(header);
|
||||||
write(header);
|
write(page);
|
||||||
write(page);
|
|
||||||
|
webmBlock = bloq;
|
||||||
webm_block = bloq;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private int makePacketheader(final long granPos, @NonNull final ByteBuffer buffer,
|
||||||
private int make_packetHeader(long gran_pos, @NonNull ByteBuffer buffer, byte[] immediate_page) {
|
final byte[] immediatePage) {
|
||||||
short length = HEADER_SIZE;
|
short length = HEADER_SIZE;
|
||||||
|
|
||||||
buffer.putInt(0x5367674f);// "OggS" binary string in little-endian
|
buffer.putInt(0x5367674f); // "OggS" binary string in little-endian
|
||||||
buffer.put((byte) 0x00);// version
|
buffer.put((byte) 0x00); // version
|
||||||
buffer.put(packet_flag);// type
|
buffer.put(packetFlag); // type
|
||||||
|
|
||||||
buffer.putLong(gran_pos);// granulate position
|
buffer.putLong(granPos); // granulate position
|
||||||
|
|
||||||
buffer.putInt(STREAM_ID);// bitstream serial number
|
buffer.putInt(streamId); // bitstream serial number
|
||||||
buffer.putInt(sequence_count++);// page sequence number
|
buffer.putInt(sequenceCount++); // page sequence number
|
||||||
|
|
||||||
buffer.putInt(0x00);// page checksum
|
buffer.putInt(0x00); // page checksum
|
||||||
|
|
||||||
buffer.put((byte) segment_table_size);// segment table
|
buffer.put((byte) segmentTableSize); // segment table
|
||||||
buffer.put(segment_table, 0, segment_table_size);// segment size
|
buffer.put(segmentTable, 0, segmentTableSize); // segment size
|
||||||
|
|
||||||
length += segment_table_size;
|
length += segmentTableSize;
|
||||||
|
|
||||||
clearSegmentTable();// clear segment table for next header
|
clearSegmentTable(); // clear segment table for next header
|
||||||
|
|
||||||
int checksum_crc32 = calc_crc32(0x00, buffer.array(), length);
|
int checksumCrc32 = calcCrc32(0x00, buffer.array(), length);
|
||||||
|
|
||||||
if (immediate_page != null) {
|
if (immediatePage != null) {
|
||||||
checksum_crc32 = calc_crc32(checksum_crc32, immediate_page, immediate_page.length);
|
checksumCrc32 = calcCrc32(checksumCrc32, immediatePage, immediatePage.length);
|
||||||
buffer.putInt(HEADER_CHECKSUM_OFFSET, checksum_crc32);
|
buffer.putInt(HEADER_CHECKSUM_OFFSET, checksumCrc32);
|
||||||
segment_table_next_timestamp -= TIME_SCALE_NS;
|
segmentTableNextTimestamp -= TIME_SCALE_NS;
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum_crc32;
|
return checksumCrc32;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private byte[] make_metadata() {
|
private byte[] makeMetadata() {
|
||||||
if ("A_OPUS".equals(webm_track.codecId)) {
|
if ("A_OPUS".equals(webmTrack.codecId)) {
|
||||||
return new byte[]{
|
return new byte[]{
|
||||||
0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73,// "OpusTags" binary string
|
0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73, // "OpusTags" binary string
|
||||||
0x07, 0x00, 0x00, 0x00,// writting application string size
|
0x07, 0x00, 0x00, 0x00, // writing application string size
|
||||||
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string
|
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65, // "NewPipe" binary string
|
||||||
0x00, 0x00, 0x00, 0x00// additional tags count (zero means no tags)
|
0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags)
|
||||||
};
|
};
|
||||||
} else if ("A_VORBIS".equals(webm_track.codecId)) {
|
} else if ("A_VORBIS".equals(webmTrack.codecId)) {
|
||||||
return new byte[]{
|
return new byte[]{
|
||||||
0x03,// ????????
|
0x03, // ????????
|
||||||
0x76, 0x6f, 0x72, 0x62, 0x69, 0x73,// "vorbis" binary string
|
0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, // "vorbis" binary string
|
||||||
0x07, 0x00, 0x00, 0x00,// writting application string size
|
0x07, 0x00, 0x00, 0x00, // writting application string size
|
||||||
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string
|
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65, // "NewPipe" binary string
|
||||||
0x01, 0x00, 0x00, 0x00,// additional tags count (zero means no tags)
|
0x01, 0x00, 0x00, 0x00, // additional tags count (zero means no tags)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// whole file duration (not implemented)
|
// whole file duration (not implemented)
|
||||||
0x44,// tag string size
|
0x44,// tag string size
|
||||||
0x55, 0x52, 0x41, 0x54, 0x49, 0x4F, 0x4E, 0x3D, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x3A, 0x30,
|
0x55, 0x52, 0x41, 0x54, 0x49, 0x4F, 0x4E, 0x3D, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x3A, 0x30,
|
||||||
0x30, 0x2E, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30
|
0x30, 0x2E, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30
|
||||||
*/
|
*/
|
||||||
0x0F,// tag string size
|
0x0F, // tag string size
|
||||||
0x00, 0x00, 0x00, 0x45, 0x4E, 0x43, 0x4F, 0x44, 0x45, 0x52, 0x3D,// "ENCODER=" binary string
|
0x00, 0x00, 0x00, 0x45, 0x4E, 0x43, 0x4F, 0x44, 0x45, 0x52, 0x3D, // "ENCODER=" binary string
|
||||||
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string
|
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65, // "NewPipe" binary string
|
||||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00// ????????
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // ????????
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// not implemented for the desired codec
|
// not implemented for the desired codec
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void write(ByteBuffer buffer) throws IOException {
|
private void write(final ByteBuffer buffer) throws IOException {
|
||||||
output.write(buffer.array(), 0, buffer.position());
|
output.write(buffer.array(), 0, buffer.position());
|
||||||
buffer.position(0);
|
buffer.position(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private SimpleBlock getNextBlock() throws IOException {
|
||||||
@Nullable
|
SimpleBlock res;
|
||||||
private SimpleBlock getNextBlock() throws IOException {
|
|
||||||
SimpleBlock res;
|
if (webmBlock != null) {
|
||||||
|
res = webmBlock;
|
||||||
if (webm_block != null) {
|
webmBlock = null;
|
||||||
res = webm_block;
|
return res;
|
||||||
webm_block = null;
|
}
|
||||||
return res;
|
|
||||||
}
|
if (webmSegment == null) {
|
||||||
|
webmSegment = webm.getNextSegment();
|
||||||
if (webm_segment == null) {
|
if (webmSegment == null) {
|
||||||
webm_segment = webm.getNextSegment();
|
return null; // no more blocks in the selected track
|
||||||
if (webm_segment == null) {
|
}
|
||||||
return null;// no more blocks in the selected track
|
}
|
||||||
}
|
|
||||||
}
|
if (webmCluster == null) {
|
||||||
|
webmCluster = webmSegment.getNextCluster();
|
||||||
if (webm_cluster == null) {
|
if (webmCluster == null) {
|
||||||
webm_cluster = webm_segment.getNextCluster();
|
webmSegment = null;
|
||||||
if (webm_cluster == null) {
|
return getNextBlock();
|
||||||
webm_segment = null;
|
}
|
||||||
return getNextBlock();
|
}
|
||||||
}
|
|
||||||
}
|
res = webmCluster.getNextSimpleBlock();
|
||||||
|
if (res == null) {
|
||||||
res = webm_cluster.getNextSimpleBlock();
|
webmCluster = null;
|
||||||
if (res == null) {
|
return getNextBlock();
|
||||||
webm_cluster = null;
|
}
|
||||||
return getNextBlock();
|
|
||||||
}
|
webmBlockNearDuration = res.absoluteTimeCodeNs - webmBlockLastTimecode;
|
||||||
|
webmBlockLastTimecode = res.absoluteTimeCodeNs;
|
||||||
webm_block_near_duration = res.absoluteTimeCodeNs - webm_block_last_timecode;
|
|
||||||
webm_block_last_timecode = res.absoluteTimeCodeNs;
|
return res;
|
||||||
|
}
|
||||||
return res;
|
|
||||||
}
|
private float getSampleFrequencyFromTrack(final byte[] bMetadata) {
|
||||||
|
// hardcoded way
|
||||||
private float getSampleFrequencyFromTrack(byte[] bMetadata) {
|
ByteBuffer buffer = ByteBuffer.wrap(bMetadata);
|
||||||
// hardcoded way
|
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(bMetadata);
|
while (buffer.remaining() >= 6) {
|
||||||
|
int id = buffer.getShort() & 0xFFFF;
|
||||||
while (buffer.remaining() >= 6) {
|
if (id == 0x0000B584) {
|
||||||
int id = buffer.getShort() & 0xFFFF;
|
return buffer.getFloat();
|
||||||
if (id == 0x0000B584) {
|
}
|
||||||
return buffer.getFloat();
|
}
|
||||||
}
|
|
||||||
}
|
return 0f;
|
||||||
|
}
|
||||||
return 0f;
|
|
||||||
}
|
private void clearSegmentTable() {
|
||||||
|
segmentTableNextTimestamp += TIME_SCALE_NS;
|
||||||
private void clearSegmentTable() {
|
packetFlag = FLAG_UNSET;
|
||||||
segment_table_next_timestamp += TIME_SCALE_NS;
|
segmentTableSize = 0;
|
||||||
packet_flag = FLAG_UNSET;
|
}
|
||||||
segment_table_size = 0;
|
|
||||||
}
|
private boolean addPacketSegment(final SimpleBlock block) {
|
||||||
|
long timestamp = block.absoluteTimeCodeNs + webmTrack.codecDelay;
|
||||||
private boolean addPacketSegment(SimpleBlock block) {
|
|
||||||
long timestamp = block.absoluteTimeCodeNs + webm_track.codecDelay;
|
if (timestamp >= segmentTableNextTimestamp) {
|
||||||
|
return false;
|
||||||
if (timestamp >= segment_table_next_timestamp) {
|
}
|
||||||
return false;
|
|
||||||
}
|
return addPacketSegment(block.dataSize);
|
||||||
|
}
|
||||||
return addPacketSegment(block.dataSize);
|
|
||||||
}
|
private boolean addPacketSegment(int size) {
|
||||||
|
if (size > 65025) {
|
||||||
private boolean addPacketSegment(int size) {
|
throw new UnsupportedOperationException("page size cannot be larger than 65025");
|
||||||
if (size > 65025) {
|
}
|
||||||
throw new UnsupportedOperationException("page size cannot be larger than 65025");
|
|
||||||
}
|
int available = (segmentTable.length - segmentTableSize) * 255;
|
||||||
|
boolean extra = (size % 255) == 0;
|
||||||
int available = (segment_table.length - segment_table_size) * 255;
|
|
||||||
boolean extra = (size % 255) == 0;
|
if (extra) {
|
||||||
|
// add a zero byte entry in the table
|
||||||
if (extra) {
|
// required to indicate the sample size is multiple of 255
|
||||||
// add a zero byte entry in the table
|
available -= 255;
|
||||||
// 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) {
|
||||||
// check if possible add the segment, without overflow the table
|
return false; // not enough space on the page
|
||||||
if (available < size) {
|
}
|
||||||
return false;// not enough space on the page
|
|
||||||
}
|
for (; size > 0; size -= 255) {
|
||||||
|
segmentTable[segmentTableSize++] = (byte) Math.min(size, 255);
|
||||||
for (; size > 0; size -= 255) {
|
}
|
||||||
segment_table[segment_table_size++] = (byte) Math.min(size, 255);
|
|
||||||
}
|
if (extra) {
|
||||||
|
segmentTable[segmentTableSize++] = 0x00;
|
||||||
if (extra) {
|
}
|
||||||
segment_table[segment_table_size++] = 0x00;
|
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
return true;
|
|
||||||
}
|
private void populateCrc32Table() {
|
||||||
|
for (int i = 0; i < 0x100; i++) {
|
||||||
private void populate_crc32_table() {
|
int crc = i << 24;
|
||||||
for (int i = 0; i < 0x100; i++) {
|
for (int j = 0; j < 8; j++) {
|
||||||
int crc = i << 24;
|
long b = crc >>> 31;
|
||||||
for (int j = 0; j < 8; j++) {
|
crc <<= 1;
|
||||||
long b = crc >>> 31;
|
crc ^= (int) (0x100000000L - b) & 0x04c11db7;
|
||||||
crc <<= 1;
|
}
|
||||||
crc ^= (int) (0x100000000L - b) & 0x04c11db7;
|
crc32Table[i] = crc;
|
||||||
}
|
}
|
||||||
crc32_table[i] = crc;
|
}
|
||||||
}
|
|
||||||
}
|
private int calcCrc32(int initialCrc, final byte[] buffer, final int size) {
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
private int calc_crc32(int initial_crc, byte[] buffer, int size) {
|
int reg = (initialCrc >>> 24) & 0xff;
|
||||||
for (int i = 0; i < size; i++) {
|
initialCrc = (initialCrc << 8) ^ crc32Table[reg ^ (buffer[i] & 0xff)];
|
||||||
int reg = (initial_crc >>> 24) & 0xff;
|
}
|
||||||
initial_crc = (initial_crc << 8) ^ crc32_table[reg ^ (buffer[i] & 0xff)];
|
|
||||||
}
|
return initialCrc;
|
||||||
|
}
|
||||||
return initial_crc;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -26,18 +26,19 @@ public class SrtFromTtmlWriter {
|
|||||||
|
|
||||||
private int frameIndex = 0;
|
private int frameIndex = 0;
|
||||||
|
|
||||||
public SrtFromTtmlWriter(SharpStream out, boolean ignoreEmptyFrames) {
|
public SrtFromTtmlWriter(final SharpStream out, final boolean ignoreEmptyFrames) {
|
||||||
this.out = out;
|
this.out = out;
|
||||||
this.ignoreEmptyFrames = ignoreEmptyFrames;
|
this.ignoreEmptyFrames = ignoreEmptyFrames;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getTimestamp(Element frame, String attr) {
|
private static String getTimestamp(final Element frame, final String attr) {
|
||||||
return frame
|
return frame
|
||||||
.attr(attr)
|
.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(String.valueOf(frameIndex++));
|
||||||
writeString(NEW_LINE);
|
writeString(NEW_LINE);
|
||||||
writeString(begin);
|
writeString(begin);
|
||||||
@ -49,11 +50,11 @@ public class SrtFromTtmlWriter {
|
|||||||
writeString(NEW_LINE);
|
writeString(NEW_LINE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeString(String text) throws IOException {
|
private void writeString(final String text) throws IOException {
|
||||||
out.write(text.getBytes(charset));
|
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
|
* TTML parser with BASIC support
|
||||||
* multiple CUE is not supported
|
* multiple CUE is not supported
|
||||||
@ -66,25 +67,32 @@ public class SrtFromTtmlWriter {
|
|||||||
// parse XML
|
// parse XML
|
||||||
byte[] buffer = new byte[(int) ttml.available()];
|
byte[] buffer = new byte[(int) ttml.available()];
|
||||||
ttml.read(buffer);
|
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);
|
StringBuilder text = new StringBuilder(128);
|
||||||
Elements paragraph_list = doc.select("body > div > p");
|
Elements paragraphList = doc.select("body > div > p");
|
||||||
|
|
||||||
// check if has frames
|
// 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);
|
text.setLength(0);
|
||||||
|
|
||||||
for (Node children : paragraph.childNodes()) {
|
for (Node children : paragraph.childNodes()) {
|
||||||
if (children instanceof TextNode)
|
if (children instanceof TextNode) {
|
||||||
text.append(((TextNode) children).text());
|
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);
|
text.append(NEW_LINE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ignoreEmptyFrames && text.length() < 1) continue;
|
if (ignoreEmptyFrames && text.length() < 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
String begin = getTimestamp(paragraph, "begin");
|
String begin = getTimestamp(paragraph, "begin");
|
||||||
String end = getTimestamp(paragraph, "end");
|
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;
|
package org.schabi.newpipe.streams.io;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* based on c#
|
* Based on C#'s Stream class.
|
||||||
*/
|
*/
|
||||||
public abstract class SharpStream implements Closeable {
|
public abstract class SharpStream implements Closeable {
|
||||||
|
public abstract int read() throws IOException;
|
||||||
public abstract int read() throws IOException;
|
|
||||||
|
public abstract int read(byte[] buffer) throws IOException;
|
||||||
public abstract int read(byte buffer[]) throws IOException;
|
|
||||||
|
public abstract int read(byte[] buffer, int offset, int count) throws IOException;
|
||||||
public abstract int read(byte buffer[], int offset, int count) throws IOException;
|
|
||||||
|
public abstract long skip(long amount) throws IOException;
|
||||||
public abstract long skip(long amount) throws IOException;
|
|
||||||
|
public abstract long available();
|
||||||
public abstract long available();
|
|
||||||
|
public abstract void rewind() throws IOException;
|
||||||
public abstract void rewind() throws IOException;
|
|
||||||
|
public abstract boolean isClosed();
|
||||||
public abstract boolean isClosed();
|
|
||||||
|
@Override
|
||||||
@Override
|
public abstract void close();
|
||||||
public abstract void close();
|
|
||||||
|
public abstract boolean canRewind();
|
||||||
public abstract boolean canRewind();
|
|
||||||
|
public abstract boolean canRead();
|
||||||
public abstract boolean canRead();
|
|
||||||
|
public abstract boolean canWrite();
|
||||||
public abstract boolean canWrite();
|
|
||||||
|
public boolean canSetLength() {
|
||||||
public boolean canSetLength() {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
public boolean canSeek() {
|
||||||
public boolean canSeek() {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
public abstract void write(byte value) throws IOException;
|
||||||
public abstract void write(byte value) throws IOException;
|
|
||||||
|
public abstract void write(byte[] buffer) throws IOException;
|
||||||
public abstract void write(byte[] buffer) throws IOException;
|
|
||||||
|
public abstract void write(byte[] buffer, int offset, int count) throws IOException;
|
||||||
public abstract void write(byte[] buffer, int offset, int count) throws IOException;
|
|
||||||
|
public void flush() throws IOException {
|
||||||
public void flush() throws IOException {
|
// STUB
|
||||||
// STUB
|
}
|
||||||
}
|
|
||||||
|
public void setLength(final long length) throws IOException {
|
||||||
public void setLength(long length) throws IOException {
|
throw new IOException("Not implemented");
|
||||||
throw new IOException("Not implemented");
|
}
|
||||||
}
|
|
||||||
|
public void seek(final long offset) throws IOException {
|
||||||
public void seek(long offset) throws IOException {
|
throw new IOException("Not implemented");
|
||||||
throw new IOException("Not implemented");
|
}
|
||||||
}
|
|
||||||
|
public long length() throws IOException {
|
||||||
public long length() throws IOException {
|
throw new UnsupportedOperationException("Unsupported operation");
|
||||||
throw new UnsupportedOperationException("Unsupported operation");
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -6,7 +6,38 @@
|
|||||||
<suppress checks="FinalParameters"
|
<suppress checks="FinalParameters"
|
||||||
files="LocalItemListAdapter.java"
|
files="LocalItemListAdapter.java"
|
||||||
lines="220,292"/>
|
lines="220,292"/>
|
||||||
|
|
||||||
<suppress checks="FinalParameters"
|
<suppress checks="FinalParameters"
|
||||||
files="InfoListAdapter.java"
|
files="InfoListAdapter.java"
|
||||||
lines="253,325"/>
|
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>
|
</suppressions>
|
||||||
|
Loading…
Reference in New Issue
Block a user