1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2024-06-27 07:33:20 +00:00

Use Checkstyle for org.schabi.newpipe.streams as well

This commit is contained in:
wb9688 2020-04-02 15:42:28 +02:00
parent 55480c8290
commit b6c6dc7282
10 changed files with 2131 additions and 2098 deletions

View File

@ -99,7 +99,6 @@ task runCheckstyle(type: Checkstyle) {
exclude '**/R.java'
exclude '**/BuildConfig.java'
exclude 'main/java/us/shandian/giga/**'
exclude 'main/java/org/schabi/newpipe/streams/**'
// empty classpath
classpath = files()

View File

@ -10,13 +10,12 @@ import java.io.InputStream;
* @author kapodamy
*/
public class DataReader {
public static final int SHORT_SIZE = 2;
public static final int LONG_SIZE = 8;
public static final int INTEGER_SIZE = 4;
public static final int FLOAT_SIZE = 4;
public final static int SHORT_SIZE = 2;
public final static int LONG_SIZE = 8;
public final static int INTEGER_SIZE = 4;
public final static int FLOAT_SIZE = 4;
private final static int BUFFER_SIZE = 128 * 1024;// 128 KiB
private static final int BUFFER_SIZE = 128 * 1024; // 128 KiB
private long position = 0;
private final SharpStream stream;
@ -24,7 +23,7 @@ public class DataReader {
private InputStream view;
private int viewSize;
public DataReader(SharpStream stream) {
public DataReader(final SharpStream stream) {
this.stream = stream;
this.readOffset = this.readBuffer.length;
}
@ -74,6 +73,7 @@ public class DataReader {
return value & 0xffffffffL;
}
public short readShort() throws IOException {
primitiveRead(SHORT_SIZE);
return (short) (primitive[0] << 8 | primitive[1]);
@ -86,11 +86,11 @@ public class DataReader {
return high << 32 | low;
}
public int read(byte[] buffer) throws IOException {
public int read(final byte[] buffer) throws IOException {
return read(buffer, 0, buffer.length);
}
public int read(byte[] buffer, int offset, int count) throws IOException {
public int read(final byte[] buffer, int offset, int count) throws IOException {
if (readCount < 0) {
return -1;
}
@ -157,7 +157,7 @@ public class DataReader {
* @param size the size of the view
* @return the view
*/
public InputStream getView(int size) {
public InputStream getView(final int size) {
if (view == null) {
view = new InputStream() {
@Override
@ -173,12 +173,13 @@ public class DataReader {
}
@Override
public int read(byte[] buffer) throws IOException {
public int read(final byte[] buffer) throws IOException {
return read(buffer, 0, buffer.length);
}
@Override
public int read(byte[] buffer, int offset, int count) throws IOException {
public int read(final byte[] buffer, final int offset, final int count)
throws IOException {
if (viewSize < 1) {
return -1;
}
@ -190,7 +191,7 @@ public class DataReader {
}
@Override
public long skip(long amount) throws IOException {
public long skip(final long amount) throws IOException {
if (viewSize < 1) {
return 0;
}
@ -224,16 +225,18 @@ public class DataReader {
private final short[] primitive = new short[LONG_SIZE];
private void primitiveRead(int amount) throws IOException {
private void primitiveRead(final int amount) throws IOException {
byte[] buffer = new byte[amount];
int read = read(buffer, 0, amount);
if (read != amount) {
throw new EOFException("Truncated stream, missing " + String.valueOf(amount - read) + " bytes");
throw new EOFException("Truncated stream, missing "
+ String.valueOf(amount - read) + " bytes");
}
for (int i = 0; i < amount; i++) {
primitive[i] = (short) (buffer[i] & 0xFF);// the "byte" data type in java is signed and is very annoying
// the "byte" data type in java is signed and is very annoying
primitive[i] = (short) (buffer[i] & 0xFF);
}
}
@ -256,5 +259,4 @@ public class DataReader {
return readCount < 1;
}
}

View File

@ -14,7 +14,6 @@ import java.util.NoSuchElementException;
* @author kapodamy
*/
public class Mp4DashReader {
private static final int ATOM_MOOF = 0x6D6F6F66;
private static final int ATOM_MFHD = 0x6D666864;
private static final int ATOM_TRAF = 0x74726166;
@ -50,7 +49,6 @@ public class Mp4DashReader {
private static final int HANDLER_SOUN = 0x736F756E;
private static final int HANDLER_SUBT = 0x73756274;
private final DataReader stream;
private Mp4Track[] tracks = null;
@ -68,7 +66,7 @@ public class Mp4DashReader {
Audio, Video, Subtitles, Other
}
public Mp4DashReader(SharpStream source) {
public Mp4DashReader(final SharpStream source) {
this.stream = new DataReader(source);
}
@ -78,14 +76,15 @@ public class Mp4DashReader {
}
box = readBox(ATOM_FTYP);
brands = parse_ftyp(box);
brands = parseFtyp(box);
switch (brands[0]) {
case BRAND_DASH:
case BRAND_ISO5:// ¿why not?
break;
default:
throw new NoSuchElementException(
"Not a MPEG-4 DASH container, major brand is not 'dash' or 'iso5' is " + boxName(brands[0])
"Not a MPEG-4 DASH container, major brand is not 'dash' or 'iso5' is "
+ boxName(brands[0])
);
}
@ -98,7 +97,7 @@ public class Mp4DashReader {
switch (box.type) {
case ATOM_MOOV:
moov = parse_moov(box);
moov = parseMoov(box);
break;
case ATOM_SIDX:
break;
@ -117,10 +116,10 @@ public class Mp4DashReader {
tracks[i] = new Mp4Track();
tracks[i].trak = moov.trak[i];
if (moov.mvex_trex != null) {
for (Trex mvex_trex : moov.mvex_trex) {
if (tracks[i].trak.tkhd.trackId == mvex_trex.trackId) {
tracks[i].trex = mvex_trex;
if (moov.mvexTrex != null) {
for (Trex mvexTrex : moov.mvexTrex) {
if (tracks[i].trak.tkhd.trackId == mvexTrex.trackId) {
tracks[i].trex = mvexTrex;
}
}
}
@ -144,7 +143,7 @@ public class Mp4DashReader {
backupBox = box;
}
Mp4Track selectTrack(int index) {
Mp4Track selectTrack(final int index) {
selectedTrack = index;
return tracks[index];
}
@ -179,7 +178,7 @@ public class Mp4DashReader {
Box traf;
while ((traf = untilBox(tmp, ATOM_TRAF)) != null) {
Box tfhd = readBox(ATOM_TFHD);
if (parse_tfhd(tracks[selectedTrack].trak.tkhd.trackId) != null) {
if (parseTfhd(tracks[selectedTrack].trak.tkhd.trackId) != null) {
count++;
break;
}
@ -196,7 +195,9 @@ public class Mp4DashReader {
}
public int[] getBrands() {
if (brands == null) throw new IllegalStateException("Not parsed");
if (brands == null) {
throw new IllegalStateException("Not parsed");
}
return brands;
}
@ -219,7 +220,7 @@ public class Mp4DashReader {
return tracks;
}
public Mp4DashChunk getNextChunk(boolean infoOnly) throws IOException {
public Mp4DashChunk getNextChunk(final boolean infoOnly) throws IOException {
Mp4Track track = tracks[selectedTrack];
while (stream.available()) {
@ -240,27 +241,31 @@ public class Mp4DashReader {
throw new IOException("moof found without mdat");
}
moof = parse_moof(box, track.trak.tkhd.trackId);
moof = parseMoof(box, track.trak.tkhd.trackId);
if (moof.traf != null) {
if (hasFlag(moof.traf.trun.bFlags, 0x0001)) {
moof.traf.trun.dataOffset -= box.size + 8;
if (moof.traf.trun.dataOffset < 0) {
throw new IOException("trun box has wrong data offset, points outside of concurrent mdat box");
throw new IOException("trun box has wrong data offset, "
+ "points outside of concurrent mdat box");
}
}
if (moof.traf.trun.chunkSize < 1) {
if (hasFlag(moof.traf.tfhd.bFlags, 0x10)) {
moof.traf.trun.chunkSize = moof.traf.tfhd.defaultSampleSize * moof.traf.trun.entryCount;
moof.traf.trun.chunkSize = moof.traf.tfhd.defaultSampleSize
* moof.traf.trun.entryCount;
} else {
moof.traf.trun.chunkSize = (int) (box.size - 8);
}
}
if (!hasFlag(moof.traf.trun.bFlags, 0x900) && moof.traf.trun.chunkDuration == 0) {
if (!hasFlag(moof.traf.trun.bFlags, 0x900)
&& moof.traf.trun.chunkDuration == 0) {
if (hasFlag(moof.traf.tfhd.bFlags, 0x20)) {
moof.traf.trun.chunkDuration = moof.traf.tfhd.defaultSampleDuration * moof.traf.trun.entryCount;
moof.traf.trun.chunkDuration = moof.traf.tfhd.defaultSampleDuration
* moof.traf.trun.entryCount;
}
}
}
@ -292,17 +297,15 @@ public class Mp4DashReader {
return null;
}
public static boolean hasFlag(int flags, int mask) {
public static boolean hasFlag(final int flags, final int mask) {
return (flags & mask) == mask;
}
private String boxName(Box ref) {
private String boxName(final Box ref) {
return boxName(ref.type);
}
private String boxName(int type) {
private String boxName(final int type) {
try {
return new String(ByteBuffer.allocate(4).putInt(type).array(), "UTF-8");
} catch (UnsupportedEncodingException e) {
@ -323,15 +326,16 @@ public class Mp4DashReader {
return b;
}
private Box readBox(int expected) throws IOException {
private Box readBox(final int expected) throws IOException {
Box b = readBox();
if (b.type != expected) {
throw new NoSuchElementException("expected " + boxName(expected) + " found " + boxName(b));
throw new NoSuchElementException("expected " + boxName(expected)
+ " found " + boxName(b));
}
return b;
}
private byte[] readFullBox(Box ref) throws IOException {
private byte[] readFullBox(final Box ref) throws IOException {
// full box reading is limited to 2 GiB, and should be enough
int size = (int) ref.size;
@ -342,15 +346,14 @@ public class Mp4DashReader {
int read = size - 8;
if (stream.read(buffer.array(), 8, read) != read) {
throw new EOFException(
String.format("EOF reached in box: type=%s offset=%s size=%s", boxName(ref.type), ref.offset, ref.size)
);
throw new EOFException(String.format("EOF reached in box: type=%s offset=%s size=%s",
boxName(ref.type), ref.offset, ref.size));
}
return buffer.array();
}
private void ensure(Box ref) throws IOException {
private void ensure(final Box ref) throws IOException {
long skip = ref.offset + ref.size - stream.position();
if (skip == 0) {
@ -365,7 +368,7 @@ public class Mp4DashReader {
stream.skipBytes((int) skip);
}
private Box untilBox(Box ref, int... expected) throws IOException {
private Box untilBox(final Box ref, final int... expected) throws IOException {
Box b;
while (stream.position() < (ref.offset + ref.size)) {
b = readBox();
@ -380,7 +383,7 @@ public class Mp4DashReader {
return null;
}
private Box untilAnyBox(Box ref) throws IOException {
private Box untilAnyBox(final Box ref) throws IOException {
if (stream.position() >= (ref.offset + ref.size)) {
return null;
}
@ -388,17 +391,15 @@ public class Mp4DashReader {
return readBox();
}
private Moof parse_moof(Box ref, int trackId) throws IOException {
private Moof parseMoof(final Box ref, final int trackId) throws IOException {
Moof obj = new Moof();
Box b = readBox(ATOM_MFHD);
obj.mfhd_SequenceNumber = parse_mfhd();
obj.mfhdSequenceNumber = parseMfhd();
ensure(b);
while ((b = untilBox(ref, ATOM_TRAF)) != null) {
obj.traf = parse_traf(b, trackId);
obj.traf = parseTraf(b, trackId);
ensure(b);
if (obj.traf != null) {
@ -409,7 +410,7 @@ public class Mp4DashReader {
return obj;
}
private int parse_mfhd() throws IOException {
private int parseMfhd() throws IOException {
// version
// flags
stream.skipBytes(4);
@ -417,11 +418,11 @@ public class Mp4DashReader {
return stream.readInt();
}
private Traf parse_traf(Box ref, int trackId) throws IOException {
private Traf parseTraf(final Box ref, final int trackId) throws IOException {
Traf traf = new Traf();
Box b = readBox(ATOM_TFHD);
traf.tfhd = parse_tfhd(trackId);
traf.tfhd = parseTfhd(trackId);
ensure(b);
if (traf.tfhd == null) {
@ -431,18 +432,18 @@ public class Mp4DashReader {
b = untilBox(ref, ATOM_TRUN, ATOM_TFDT);
if (b.type == ATOM_TFDT) {
traf.tfdt = parse_tfdt();
traf.tfdt = parseTfdt();
ensure(b);
b = readBox(ATOM_TRUN);
}
traf.trun = parse_trun();
traf.trun = parseTrun();
ensure(b);
return traf;
}
private Tfhd parse_tfhd(int trackId) throws IOException {
private Tfhd parseTfhd(final int trackId) throws IOException {
Tfhd obj = new Tfhd();
obj.bFlags = stream.readInt();
@ -471,31 +472,31 @@ public class Mp4DashReader {
return obj;
}
private long parse_tfdt() throws IOException {
private long parseTfdt() throws IOException {
int version = stream.read();
stream.skipBytes(3); // flags
return version == 0 ? stream.readUnsignedInt() : stream.readLong();
}
private Trun parse_trun() throws IOException {
private Trun parseTrun() throws IOException {
Trun obj = new Trun();
obj.bFlags = stream.readInt();
obj.entryCount = stream.readInt(); // unsigned int
obj.entries_rowSize = 0;
obj.entriesRowSize = 0;
if (hasFlag(obj.bFlags, 0x0100)) {
obj.entries_rowSize += 4;
obj.entriesRowSize += 4;
}
if (hasFlag(obj.bFlags, 0x0200)) {
obj.entries_rowSize += 4;
obj.entriesRowSize += 4;
}
if (hasFlag(obj.bFlags, 0x0400)) {
obj.entries_rowSize += 4;
obj.entriesRowSize += 4;
}
if (hasFlag(obj.bFlags, 0x0800)) {
obj.entries_rowSize += 4;
obj.entriesRowSize += 4;
}
obj.bEntries = new byte[obj.entries_rowSize * obj.entryCount];
obj.bEntries = new byte[obj.entriesRowSize * obj.entryCount];
if (hasFlag(obj.bFlags, 0x0001)) {
obj.dataOffset = stream.readInt();
@ -524,7 +525,7 @@ public class Mp4DashReader {
return obj;
}
private int[] parse_ftyp(Box ref) throws IOException {
private int[] parseFtyp(final Box ref) throws IOException {
int i = 0;
int[] list = new int[(int) ((ref.offset + ref.size - stream.position() - 4) / 4)];
@ -532,13 +533,14 @@ public class Mp4DashReader {
stream.skipBytes(4); // minor version
for (; i < list.length; i++)
for (; i < list.length; i++) {
list[i] = stream.readInt(); // compatible brands
}
return list;
}
private Mvhd parse_mvhd() throws IOException {
private Mvhd parseMvhd() throws IOException {
int version = stream.read();
stream.skipBytes(3); // flags
@ -564,7 +566,7 @@ public class Mp4DashReader {
return obj;
}
private Tkhd parse_tkhd() throws IOException {
private Tkhd parseTkhd() throws IOException {
int version = stream.read();
Tkhd obj = new Tkhd();
@ -597,20 +599,20 @@ public class Mp4DashReader {
return obj;
}
private Trak parse_trak(Box ref) throws IOException {
private Trak parseTrak(final Box ref) throws IOException {
Trak trak = new Trak();
Box b = readBox(ATOM_TKHD);
trak.tkhd = parse_tkhd();
trak.tkhd = parseTkhd();
ensure(b);
while ((b = untilBox(ref, ATOM_MDIA, ATOM_EDTS)) != null) {
switch (b.type) {
case ATOM_MDIA:
trak.mdia = parse_mdia(b);
trak.mdia = parseMdia(b);
break;
case ATOM_EDTS:
trak.edst_elst = parse_edts(b);
trak.edstElst = parseEdts(b);
break;
}
@ -620,7 +622,7 @@ public class Mp4DashReader {
return trak;
}
private Mdia parse_mdia(Box ref) throws IOException {
private Mdia parseMdia(final Box ref) throws IOException {
Mdia obj = new Mdia();
Box b;
@ -633,13 +635,13 @@ public class Mp4DashReader {
ByteBuffer buffer = ByteBuffer.wrap(obj.mdhd);
byte version = buffer.get(8);
buffer.position(12 + ((version == 0 ? 4 : 8) * 2));
obj.mdhd_timeScale = buffer.getInt();
obj.mdhdTimeScale = buffer.getInt();
break;
case ATOM_HDLR:
obj.hdlr = parse_hdlr(b);
obj.hdlr = parseHdlr(b);
break;
case ATOM_MINF:
obj.minf = parse_minf(b);
obj.minf = parseMinf(b);
break;
}
ensure(b);
@ -648,7 +650,7 @@ public class Mp4DashReader {
return obj;
}
private Hdlr parse_hdlr(Box ref) throws IOException {
private Hdlr parseHdlr(final Box ref) throws IOException {
// version
// flags
stream.skipBytes(4);
@ -666,10 +668,10 @@ public class Mp4DashReader {
return obj;
}
private Moov parse_moov(Box ref) throws IOException {
private Moov parseMoov(final Box ref) throws IOException {
Box b = readBox(ATOM_MVHD);
Moov moov = new Moov();
moov.mvhd = parse_mvhd();
moov.mvhd = parseMvhd();
ensure(b);
ArrayList<Trak> tmp = new ArrayList<>((int) moov.mvhd.nextTrackId);
@ -677,10 +679,10 @@ public class Mp4DashReader {
switch (b.type) {
case ATOM_TRAK:
tmp.add(parse_trak(b));
tmp.add(parseTrak(b));
break;
case ATOM_MVEX:
moov.mvex_trex = parse_mvex(b, (int) moov.mvhd.nextTrackId);
moov.mvexTrex = parseMvex(b, (int) moov.mvhd.nextTrackId);
break;
}
@ -692,19 +694,19 @@ public class Mp4DashReader {
return moov;
}
private Trex[] parse_mvex(Box ref, int possibleTrackCount) throws IOException {
private Trex[] parseMvex(final Box ref, final int possibleTrackCount) throws IOException {
ArrayList<Trex> tmp = new ArrayList<>(possibleTrackCount);
Box b;
while ((b = untilBox(ref, ATOM_TREX)) != null) {
tmp.add(parse_trex());
tmp.add(parseTrex());
ensure(b);
}
return tmp.toArray(new Trex[0]);
}
private Trex parse_trex() throws IOException {
private Trex parseTrex() throws IOException {
// version
// flags
stream.skipBytes(4);
@ -719,7 +721,7 @@ public class Mp4DashReader {
return obj;
}
private Elst parse_edts(Box ref) throws IOException {
private Elst parseEdts(final Box ref) throws IOException {
Box b = untilBox(ref, ATOM_ELST);
if (b == null) {
return null;
@ -738,12 +740,12 @@ public class Mp4DashReader {
if (v1) {
stream.skipBytes(DataReader.LONG_SIZE); // segment duration
obj.MediaTime = stream.readLong();
obj.mediaTime = stream.readLong();
// ignore all remain entries
stream.skipBytes((entryCount - 1) * (DataReader.LONG_SIZE * 2));
} else {
stream.skipBytes(DataReader.INTEGER_SIZE); // segment duration
obj.MediaTime = stream.readInt();
obj.mediaTime = stream.readInt();
}
obj.bMediaRate = stream.readInt();
@ -751,7 +753,7 @@ public class Mp4DashReader {
return obj;
}
private Minf parse_minf(Box ref) throws IOException {
private Minf parseMinf(final Box ref) throws IOException {
Minf obj = new Minf();
Box b;
@ -762,11 +764,11 @@ public class Mp4DashReader {
obj.dinf = readFullBox(b);
break;
case ATOM_STBL:
obj.stbl_stsd = parse_stbl(b);
obj.stblStsd = parseStbl(b);
break;
case ATOM_VMHD:
case ATOM_SMHD:
obj.$mhd = readFullBox(b);
obj.mhd = readFullBox(b);
break;
}
@ -777,9 +779,12 @@ public class Mp4DashReader {
}
/**
* this only read the "stsd" box inside
* This only reads the "stsd" box inside.
*
* @param ref stbl box
* @return stsd box inside
*/
private byte[] parse_stbl(Box ref) throws IOException {
private byte[] parseStbl(final Box ref) throws IOException {
Box b = untilBox(ref, ATOM_STSD);
if (b == null) {
@ -789,30 +794,24 @@ public class Mp4DashReader {
return readFullBox(b);
}
class Box {
int type;
long offset;
long size;
}
public class Moof {
int mfhd_SequenceNumber;
int mfhdSequenceNumber;
public Traf traf;
}
public class Traf {
public Tfhd tfhd;
long tfdt;
public Trun trun;
}
public class Tfhd {
int bFlags;
public int trackId;
int defaultSampleDuration;
@ -821,7 +820,6 @@ public class Mp4DashReader {
}
class TrunEntry {
int sampleDuration;
int sampleSize;
int sampleFlags;
@ -833,7 +831,6 @@ public class Mp4DashReader {
}
public class Trun {
public int chunkDuration;
public int chunkSize;
@ -843,10 +840,10 @@ public class Mp4DashReader {
public int entryCount;
byte[] bEntries;
int entries_rowSize;
int entriesRowSize;
public TrunEntry getEntry(int i) {
ByteBuffer buffer = ByteBuffer.wrap(bEntries, i * entries_rowSize, entries_rowSize);
public TrunEntry getEntry(final int i) {
ByteBuffer buffer = ByteBuffer.wrap(bEntries, i * entriesRowSize, entriesRowSize);
TrunEntry entry = new TrunEntry();
if (hasFlag(bFlags, 0x0100)) {
@ -868,7 +865,7 @@ public class Mp4DashReader {
return entry;
}
public TrunEntry getAbsoluteEntry(int i, Tfhd header) {
public TrunEntry getAbsoluteEntry(final int i, final Tfhd header) {
TrunEntry entry = getEntry(i);
if (!hasFlag(bFlags, 0x0100) && hasFlag(header.bFlags, 0x20)) {
@ -892,7 +889,6 @@ public class Mp4DashReader {
}
public class Tkhd {
int trackId;
long duration;
short bVolume;
@ -904,28 +900,24 @@ public class Mp4DashReader {
}
public class Trak {
public Tkhd tkhd;
public Elst edst_elst;
public Elst edstElst;
public Mdia mdia;
}
class Mvhd {
long timeScale;
long nextTrackId;
}
class Moov {
Mvhd mvhd;
Trak[] trak;
Trex[] mvex_trex;
Trex[] mvexTrex;
}
public class Trex {
private int trackId;
int defaultSampleDescriptionIndex;
int defaultSampleDuration;
@ -934,42 +926,36 @@ public class Mp4DashReader {
}
public class Elst {
public long MediaTime;
public long mediaTime;
public int bMediaRate;
}
public class Mdia {
public int mdhd_timeScale;
public int mdhdTimeScale;
public byte[] mdhd;
public Hdlr hdlr;
public Minf minf;
}
public class Hdlr {
public int type;
public int subType;
public byte[] bReserved;
}
public class Minf {
public byte[] dinf;
public byte[] stbl_stsd;
public byte[] $mhd;
public byte[] stblStsd;
public byte[] mhd;
}
public class Mp4Track {
public TrackKind kind;
public Trak trak;
public Trex trex;
}
public class Mp4DashChunk {
public InputStream data;
public Moof moof;
private int i = 0;
@ -1002,9 +988,7 @@ public class Mp4DashReader {
}
public class Mp4DashSample {
public TrunEntry info;
public byte[] data;
}
}

View File

@ -17,13 +17,15 @@ import java.util.ArrayList;
* @author kapodamy
*/
public class Mp4FromDashWriter {
private final static int EPOCH_OFFSET = 2082844800;
private final static short DEFAULT_TIMESCALE = 1000;
private final static byte SAMPLES_PER_CHUNK_INIT = 2;
private final static byte SAMPLES_PER_CHUNK = 6;// ffmpeg uses 2, basic uses 1 (with 60fps uses 21 or 22). NewPipe will use 6
private final static long THRESHOLD_FOR_CO64 = 0xFFFEFFFFL;// near 3.999 GiB
private final static int THRESHOLD_MOOV_LENGTH = (256 * 1024) + (2048 * 1024); // 2.2 MiB enough for: 1080p 60fps 00h35m00s
private static final int EPOCH_OFFSET = 2082844800;
private static final short DEFAULT_TIMESCALE = 1000;
private static final byte SAMPLES_PER_CHUNK_INIT = 2;
// ffmpeg uses 2, basic uses 1 (with 60fps uses 21 or 22). NewPipe will use 6
private static final byte SAMPLES_PER_CHUNK = 6;
// near 3.999 GiB
private static final long THRESHOLD_FOR_CO64 = 0xFFFEFFFFL;
// 2.2 MiB enough for: 1080p 60fps 00h35m00s
private static final int THRESHOLD_MOOV_LENGTH = (256 * 1024) + (2048 * 1024);
private final long time;
@ -48,7 +50,7 @@ public class Mp4FromDashWriter {
private final ArrayList<Integer> compatibleBrands = new ArrayList<>(5);
public Mp4FromDashWriter(SharpStream... sources) throws IOException {
public Mp4FromDashWriter(final SharpStream... sources) throws IOException {
for (SharpStream src : sources) {
if (!src.canRewind() && !src.canRead()) {
throw new IOException("All sources must be readable and allow rewind");
@ -65,7 +67,7 @@ public class Mp4FromDashWriter {
compatibleBrands.add(0x69736F32); // iso2
}
public Mp4Track[] getTracksFromSource(int sourceIndex) throws IllegalStateException {
public Mp4Track[] getTracksFromSource(final int sourceIndex) throws IllegalStateException {
if (!parsed) {
throw new IllegalStateException("All sources must be parsed first");
}
@ -92,7 +94,7 @@ public class Mp4FromDashWriter {
}
}
public void selectTracks(int... trackIndex) throws IOException {
public void selectTracks(final int... trackIndex) throws IOException {
if (done) {
throw new IOException("already done");
}
@ -110,7 +112,7 @@ public class Mp4FromDashWriter {
}
}
public void setMainBrand(int brand) {
public void setMainBrand(final int brand) {
overrideMainBrand = brand;
}
@ -140,7 +142,7 @@ public class Mp4FromDashWriter {
outStream = null;
}
public void build(SharpStream output) throws IOException {
public void build(final SharpStream output) throws IOException {
if (done) {
throw new RuntimeException("already done");
}
@ -165,12 +167,12 @@ public class Mp4FromDashWriter {
tablesInfo[i] = new TablesInfo();
}
int single_sample_buffer;
int singleSampleBuffer;
if (tracks.length == 1 && tracks[0].kind == TrackKind.Audio) {
// near 1 second of audio data per chunk, avoid split the audio stream in large chunks
single_sample_buffer = tracks[0].trak.mdia.mdhd_timeScale / 1000;
singleSampleBuffer = tracks[0].trak.mdia.mdhdTimeScale / 1000;
} else {
single_sample_buffer = -1;
singleSampleBuffer = -1;
}
@ -222,8 +224,8 @@ public class Mp4FromDashWriter {
readers[i].rewind();
if (single_sample_buffer > 0) {
initChunkTables(tablesInfo[i], single_sample_buffer, single_sample_buffer);
if (singleSampleBuffer > 0) {
initChunkTables(tablesInfo[i], singleSampleBuffer, singleSampleBuffer);
} else {
initChunkTables(tablesInfo[i], SAMPLES_PER_CHUNK_INIT, SAMPLES_PER_CHUNK);
}
@ -232,9 +234,9 @@ public class Mp4FromDashWriter {
if (sampleSizeChanges == 1) {
tablesInfo[i].stsz = 0;
tablesInfo[i].stsz_default = samplesSize;
tablesInfo[i].stszDefault = samplesSize;
} else {
tablesInfo[i].stsz_default = 0;
tablesInfo[i].stszDefault = 0;
}
if (tablesInfo[i].stss == tablesInfo[i].stsz) {
@ -251,7 +253,7 @@ public class Mp4FromDashWriter {
boolean is64 = read > THRESHOLD_FOR_CO64;
// calculate the moov size
int auxSize = make_moov(defaultMediaTime, tablesInfo, is64);
int auxSize = makeMoov(defaultMediaTime, tablesInfo, is64);
if (auxSize < THRESHOLD_MOOV_LENGTH) {
auxBuffer = ByteBuffer.allocate(auxSize); // cache moov in the memory
@ -260,7 +262,7 @@ public class Mp4FromDashWriter {
moovSimulation = false;
writeOffset = 0;
final int ftyp_size = make_ftyp();
final int ftypSize = makeFtyp();
// reserve moov space in the output stream
if (auxSize > 0) {
@ -274,19 +276,20 @@ public class Mp4FromDashWriter {
}
if (auxBuffer == null) {
outSeek(ftyp_size);
outSeek(ftypSize);
}
// tablesInfo contains row counts
// and after returning from make_moov() will contain those table offsets
make_moov(defaultMediaTime, tablesInfo, is64);
// and after returning from makeMoov() will contain those table offsets
makeMoov(defaultMediaTime, tablesInfo, is64);
// write tables: stts stsc sbgp
// reset for ctts table: sampleCount sampleExtra
for (int i = 0; i < readers.length; i++) {
writeEntryArray(tablesInfo[i].stts, 2, sampleCount[i], defaultSampleDuration[i]);
writeEntryArray(tablesInfo[i].stsc, tablesInfo[i].stsc_bEntries.length, tablesInfo[i].stsc_bEntries);
tablesInfo[i].stsc_bEntries = null;
writeEntryArray(tablesInfo[i].stsc, tablesInfo[i].stscBEntries.length,
tablesInfo[i].stscBEntries);
tablesInfo[i].stscBEntries = null;
if (tablesInfo[i].ctts > 0) {
sampleCount[i] = 1; // the index is not base zero
sampleExtra[i] = -1;
@ -300,11 +303,11 @@ public class Mp4FromDashWriter {
outRestore();
}
outWrite(make_mdat(totalSampleSize, is64));
outWrite(makeMdat(totalSampleSize, is64));
int[] sampleIndex = new int[readers.length];
int[] sizes = new int[single_sample_buffer > 0 ? single_sample_buffer : SAMPLES_PER_CHUNK];
int[] sync = new int[single_sample_buffer > 0 ? single_sample_buffer : SAMPLES_PER_CHUNK];
int[] sizes = new int[singleSampleBuffer > 0 ? singleSampleBuffer : SAMPLES_PER_CHUNK];
int[] sync = new int[singleSampleBuffer > 0 ? singleSampleBuffer : SAMPLES_PER_CHUNK];
int written = readers.length;
while (written > 0) {
@ -318,8 +321,8 @@ public class Mp4FromDashWriter {
long chunkOffset = writeOffset;
int syncCount = 0;
int limit;
if (single_sample_buffer > 0) {
limit = single_sample_buffer;
if (singleSampleBuffer > 0) {
limit = singleSampleBuffer;
} else {
limit = sampleIndex[i] == 0 ? SAMPLES_PER_CHUNK_INIT : SAMPLES_PER_CHUNK;
}
@ -330,7 +333,8 @@ public class Mp4FromDashWriter {
if (sample == null) {
if (tablesInfo[i].ctts > 0 && sampleExtra[i] >= 0) {
writeEntryArray(tablesInfo[i].ctts, 1, sampleCount[i], sampleExtra[i]);// flush last entries
writeEntryArray(tablesInfo[i].ctts, 1, sampleCount[i],
sampleExtra[i]); // flush last entries
outRestore();
}
sampleIndex[i] = -1;
@ -344,7 +348,8 @@ public class Mp4FromDashWriter {
sampleCount[i]++;
} else {
if (sampleExtra[i] >= 0) {
tablesInfo[i].ctts = writeEntryArray(tablesInfo[i].ctts, 2, sampleCount[i], sampleExtra[i]);
tablesInfo[i].ctts = writeEntryArray(tablesInfo[i].ctts, 2,
sampleCount[i], sampleExtra[i]);
outRestore();
}
sampleCount[i] = 1;
@ -378,7 +383,8 @@ public class Mp4FromDashWriter {
if (is64) {
tablesInfo[i].stco = writeEntry64(tablesInfo[i].stco, chunkOffset);
} else {
tablesInfo[i].stco = writeEntryArray(tablesInfo[i].stco, 1, (int) chunkOffset);
tablesInfo[i].stco = writeEntryArray(tablesInfo[i].stco, 1,
(int) chunkOffset);
}
}
@ -389,13 +395,13 @@ public class Mp4FromDashWriter {
if (auxBuffer != null) {
// dump moov
outSeek(ftyp_size);
outSeek(ftypSize);
outStream.write(auxBuffer.array(), 0, auxBuffer.capacity());
auxBuffer = null;
}
}
private Mp4DashSample getNextSample(int track) throws IOException {
private Mp4DashSample getNextSample(final int track) throws IOException {
if (readersChunks[track] == null) {
readersChunks[track] = readers[track].getNextChunk(false);
if (readersChunks[track] == null) {
@ -413,7 +419,7 @@ public class Mp4FromDashWriter {
}
private int writeEntry64(int offset, long value) throws IOException {
private int writeEntry64(final int offset, final long value) throws IOException {
outBackup();
auxSeek(offset);
@ -422,7 +428,8 @@ public class Mp4FromDashWriter {
return offset + 8;
}
private int writeEntryArray(int offset, int count, int... values) throws IOException {
private int writeEntryArray(final int offset, final int count, final int... values)
throws IOException {
outBackup();
auxSeek(offset);
@ -456,7 +463,8 @@ public class Mp4FromDashWriter {
}
}
private void initChunkTables(TablesInfo tables, int firstCount, int succesiveCount) {
private void initChunkTables(final TablesInfo tables, final int firstCount,
final int succesiveCount) {
// tables.stsz holds amount of samples of the track (total)
int totalSamples = (tables.stsz - firstCount);
float chunkAmount = totalSamples / (float) succesiveCount;
@ -473,36 +481,36 @@ public class Mp4FromDashWriter {
}
// stsc_table_entry = [first_chunk, samples_per_chunk, sample_description_index]
tables.stsc_bEntries = new int[tables.stsc * 3];
tables.stscBEntries = new int[tables.stsc * 3];
tables.stco = remainChunkOffset + 1; // total entrys in chunk offset box
tables.stsc_bEntries[index++] = 1;
tables.stsc_bEntries[index++] = firstCount;
tables.stsc_bEntries[index++] = 1;
tables.stscBEntries[index++] = 1;
tables.stscBEntries[index++] = firstCount;
tables.stscBEntries[index++] = 1;
if (firstCount != succesiveCount) {
tables.stsc_bEntries[index++] = 2;
tables.stsc_bEntries[index++] = succesiveCount;
tables.stsc_bEntries[index++] = 1;
tables.stscBEntries[index++] = 2;
tables.stscBEntries[index++] = succesiveCount;
tables.stscBEntries[index++] = 1;
}
if (remain) {
tables.stsc_bEntries[index++] = remainChunkOffset + 1;
tables.stsc_bEntries[index++] = totalSamples % succesiveCount;
tables.stsc_bEntries[index] = 1;
tables.stscBEntries[index++] = remainChunkOffset + 1;
tables.stscBEntries[index++] = totalSamples % succesiveCount;
tables.stscBEntries[index] = 1;
}
}
private void outWrite(byte[] buffer) throws IOException {
private void outWrite(final byte[] buffer) throws IOException {
outWrite(buffer, buffer.length);
}
private void outWrite(byte[] buffer, int count) throws IOException {
private void outWrite(final byte[] buffer, final int count) throws IOException {
writeOffset += count;
outStream.write(buffer, 0, count);
}
private void outSeek(long offset) throws IOException {
private void outSeek(final long offset) throws IOException {
if (outStream.canSeek()) {
outStream.seek(offset);
writeOffset = offset;
@ -515,12 +523,12 @@ public class Mp4FromDashWriter {
}
}
private void outSkip(long amount) throws IOException {
private void outSkip(final long amount) throws IOException {
outStream.skip(amount);
writeOffset += amount;
}
private int lengthFor(int offset) throws IOException {
private int lengthFor(final int offset) throws IOException {
int size = auxOffset() - offset;
if (moovSimulation) {
@ -534,7 +542,8 @@ public class Mp4FromDashWriter {
return size;
}
private int make(int type, int extra, int columns, int rows) throws IOException {
private int make(final int type, final int extra, final int columns, final int rows)
throws IOException {
final byte base = 16;
int size = columns * rows * 4;
int total = size + base;
@ -562,14 +571,14 @@ public class Mp4FromDashWriter {
return offset + base;
}
private void auxWrite(int value) throws IOException {
private void auxWrite(final int value) throws IOException {
auxWrite(ByteBuffer.allocate(4)
.putInt(value)
.array()
);
}
private void auxWrite(byte[] buffer) throws IOException {
private void auxWrite(final byte[] buffer) throws IOException {
if (moovSimulation) {
writeOffset += buffer.length;
} else if (auxBuffer == null) {
@ -579,7 +588,7 @@ public class Mp4FromDashWriter {
}
}
private void auxSeek(int offset) throws IOException {
private void auxSeek(final int offset) throws IOException {
if (moovSimulation) {
writeOffset = offset;
} else if (auxBuffer == null) {
@ -589,7 +598,7 @@ public class Mp4FromDashWriter {
}
}
private void auxSkip(int amount) throws IOException {
private void auxSkip(final int amount) throws IOException {
if (moovSimulation) {
writeOffset += amount;
} else if (auxBuffer == null) {
@ -603,11 +612,11 @@ public class Mp4FromDashWriter {
return auxBuffer == null ? (int) writeOffset : auxBuffer.position();
}
private int make_ftyp() throws IOException {
private int makeFtyp() throws IOException {
int size = 16 + (compatibleBrands.size() * 4);
if (overrideMainBrand != 0) size += 4;
if (overrideMainBrand != 0) {
size += 4;
}
ByteBuffer buffer = ByteBuffer.allocate(size);
buffer.putInt(size);
@ -631,7 +640,7 @@ public class Mp4FromDashWriter {
return size;
}
private byte[] make_mdat(long refSize, boolean is64) {
private byte[] makeMdat(long refSize, final boolean is64) {
if (is64) {
refSize += 16;
} else {
@ -649,7 +658,7 @@ public class Mp4FromDashWriter {
return buffer.array();
}
private void make_mvhd(long longestTrack) throws IOException {
private void makeMvhd(final long longestTrack) throws IOException {
auxWrite(new byte[]{
0x00, 0x00, 0x00, 0x78, 0x6D, 0x76, 0x68, 0x64, 0x01, 0x00, 0x00, 0x00
});
@ -676,7 +685,8 @@ public class Mp4FromDashWriter {
);
}
private int make_moov(int[] defaultMediaTime, TablesInfo[] tablesInfo, boolean is64) throws RuntimeException, IOException {
private int makeMoov(final int[] defaultMediaTime, final TablesInfo[] tablesInfo,
final boolean is64) throws RuntimeException, IOException {
int start = auxOffset();
auxWrite(new byte[]{
@ -688,21 +698,21 @@ public class Mp4FromDashWriter {
for (int i = 0; i < durations.length; i++) {
durations[i] = (long) Math.ceil(
((double) tracks[i].trak.tkhd.duration / tracks[i].trak.mdia.mdhd_timeScale) * DEFAULT_TIMESCALE
);
((double) tracks[i].trak.tkhd.duration / tracks[i].trak.mdia.mdhdTimeScale)
* DEFAULT_TIMESCALE);
if (durations[i] > longestTrack) {
longestTrack = durations[i];
}
}
make_mvhd(longestTrack);
makeMvhd(longestTrack);
for (int i = 0; i < tracks.length; i++) {
if (tracks[i].trak.tkhd.matrix.length != 36) {
throw new RuntimeException("bad track matrix length (expected 36) in track n°" + i);
}
make_trak(i, durations[i], defaultMediaTime[i], tablesInfo[i], is64);
makeTrak(i, durations[i], defaultMediaTime[i], tablesInfo[i], is64);
}
// udta/meta/ilst/©too
@ -719,7 +729,8 @@ public class Mp4FromDashWriter {
return lengthFor(start);
}
private void make_trak(int index, long duration, int defaultMediaTime, TablesInfo tables, boolean is64) throws IOException {
private void makeTrak(final int index, final long duration, final int defaultMediaTime,
final TablesInfo tables, final boolean is64) throws IOException {
int start = auxOffset();
auxWrite(new byte[]{
@ -754,13 +765,13 @@ public class Mp4FromDashWriter {
int bMediaRate;
int mediaTime;
if (tracks[index].trak.edst_elst == null) {
if (tracks[index].trak.edstElst == null) {
// is a audio track ¿is edst/elst optional for audio tracks?
mediaTime = 0x00; // ffmpeg set this value as zero, instead of defaultMediaTime
bMediaRate = 0x00010000;
} else {
mediaTime = (int) tracks[index].trak.edst_elst.MediaTime;
bMediaRate = tracks[index].trak.edst_elst.bMediaRate;
mediaTime = (int) tracks[index].trak.edstElst.mediaTime;
bMediaRate = tracks[index].trak.edstElst.bMediaRate;
}
auxWrite(ByteBuffer
@ -771,25 +782,26 @@ public class Mp4FromDashWriter {
.array()
);
make_mdia(tracks[index].trak.mdia, tables, is64, tracks[index].kind == TrackKind.Audio);
makeMdia(tracks[index].trak.mdia, tables, is64, tracks[index].kind == TrackKind.Audio);
lengthFor(start);
}
private void make_mdia(Mdia mdia, TablesInfo tablesInfo, boolean is64, boolean isAudio) throws IOException {
int start_mdia = auxOffset();
private void makeMdia(final Mdia mdia, final TablesInfo tablesInfo, final boolean is64,
final boolean isAudio) throws IOException {
int startMdia = auxOffset();
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x61}); // mdia
auxWrite(mdia.mdhd);
auxWrite(make_hdlr(mdia.hdlr));
auxWrite(makeHdlr(mdia.hdlr));
int start_minf = auxOffset();
int startMinf = auxOffset();
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x6D, 0x69, 0x6E, 0x66}); // minf
auxWrite(mdia.minf.$mhd);
auxWrite(mdia.minf.mhd);
auxWrite(mdia.minf.dinf);
int start_stbl = auxOffset();
int startStbl = auxOffset();
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
@ -804,7 +816,7 @@ public class Mp4FromDashWriter {
make(0x63747473, -1, 2, tablesInfo.ctts);
}
make(0x73747363, -1, 3, tablesInfo.stsc);
make(0x7374737A, tablesInfo.stsz_default, 1, tablesInfo.stsz);
make(0x7374737A, tablesInfo.stszDefault, 1, tablesInfo.stsz);
make(is64 ? 0x636F3634 : 0x7374636F, -1, is64 ? 2 : 1, tablesInfo.stco);
} else {
tablesInfo.stts = make(0x73747473, -1, 2, 1);
@ -815,21 +827,22 @@ public class Mp4FromDashWriter {
tablesInfo.ctts = make(0x63747473, -1, 2, tablesInfo.ctts);
}
tablesInfo.stsc = make(0x73747363, -1, 3, tablesInfo.stsc);
tablesInfo.stsz = make(0x7374737A, tablesInfo.stsz_default, 1, tablesInfo.stsz);
tablesInfo.stco = make(is64 ? 0x636F3634 : 0x7374636F, -1, is64 ? 2 : 1, tablesInfo.stco);
tablesInfo.stsz = make(0x7374737A, tablesInfo.stszDefault, 1, tablesInfo.stsz);
tablesInfo.stco = make(is64 ? 0x636F3634 : 0x7374636F, -1, is64 ? 2 : 1,
tablesInfo.stco);
}
if (isAudio) {
auxWrite(make_sgpd());
tablesInfo.sbgp = make_sbgp();// during simulation the returned offset is ignored
auxWrite(makeSgpd());
tablesInfo.sbgp = makeSbgp(); // during simulation the returned offset is ignored
}
lengthFor(start_stbl);
lengthFor(start_minf);
lengthFor(start_mdia);
lengthFor(startStbl);
lengthFor(startMinf);
lengthFor(startMdia);
}
private byte[] make_hdlr(Hdlr hdlr) {
private byte[] makeHdlr(final Hdlr hdlr) {
ByteBuffer buffer = ByteBuffer.wrap(new byte[]{
0x00, 0x00, 0x00, 0x77, 0x68, 0x64, 0x6C, 0x72, // hdlr
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -851,7 +864,7 @@ public class Mp4FromDashWriter {
return buffer.array();
}
private int make_sbgp() throws IOException {
private int makeSbgp() throws IOException {
int offset = auxOffset();
auxWrite(new byte[] {
@ -867,7 +880,7 @@ public class Mp4FromDashWriter {
return offset + 0x14;
}
private byte[] make_sgpd() {
private byte[] makeSgpd() {
/*
* Sample Group Description Box
*
@ -895,13 +908,12 @@ public class Mp4FromDashWriter {
}
class TablesInfo {
int stts;
int stsc;
int[] stsc_bEntries;
int[] stscBEntries;
int ctts;
int stsz;
int stsz_default;
int stszDefault;
int stss;
int stco;
int sbgp;

View File

@ -1,6 +1,7 @@
package org.schabi.newpipe.streams;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.schabi.newpipe.streams.WebMReader.Cluster;
import org.schabi.newpipe.streams.WebMReader.Segment;
@ -13,22 +14,19 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import androidx.annotation.Nullable;
/**
* @author kapodamy
*/
public class OggFromWebMWriter implements Closeable {
private static final byte FLAG_UNSET = 0x00;
//private static final byte FLAG_CONTINUED = 0x01;
private static final byte FLAG_FIRST = 0x02;
private static final byte FLAG_LAST = 0x04;
private final static byte HEADER_CHECKSUM_OFFSET = 22;
private final static byte HEADER_SIZE = 27;
private static final byte HEADER_CHECKSUM_OFFSET = 22;
private static final byte HEADER_SIZE = 27;
private final static int TIME_SCALE_NS = 1000000000;
private static final int TIME_SCALE_NS = 1000000000;
private boolean done = false;
private boolean parsed = false;
@ -36,26 +34,26 @@ public class OggFromWebMWriter implements Closeable {
private SharpStream source;
private SharpStream output;
private int sequence_count = 0;
private final int STREAM_ID;
private byte packet_flag = FLAG_FIRST;
private int sequenceCount = 0;
private final int streamId;
private byte packetFlag = FLAG_FIRST;
private WebMReader webm = null;
private WebMTrack webm_track = null;
private Segment webm_segment = null;
private Cluster webm_cluster = null;
private SimpleBlock webm_block = null;
private WebMTrack webmTrack = null;
private Segment webmSegment = null;
private Cluster webmCluster = null;
private SimpleBlock webmBlock = null;
private long webm_block_last_timecode = 0;
private long webm_block_near_duration = 0;
private long webmBlockLastTimecode = 0;
private long webmBlockNearDuration = 0;
private short segment_table_size = 0;
private final byte[] segment_table = new byte[255];
private long segment_table_next_timestamp = TIME_SCALE_NS;
private short segmentTableSize = 0;
private final byte[] segmentTable = new byte[255];
private long segmentTableNextTimestamp = TIME_SCALE_NS;
private final int[] crc32_table = new int[256];
private final int[] crc32Table = new int[256];
public OggFromWebMWriter(@NonNull SharpStream source, @NonNull SharpStream target) {
public OggFromWebMWriter(@NonNull final SharpStream source, @NonNull final SharpStream target) {
if (!source.canRead() || !source.canRewind()) {
throw new IllegalArgumentException("source stream must be readable and allows seeking");
}
@ -66,9 +64,9 @@ public class OggFromWebMWriter implements Closeable {
this.source = source;
this.output = target;
this.STREAM_ID = (int) System.currentTimeMillis();
this.streamId = (int) System.currentTimeMillis();
populate_crc32_table();
populateCrc32Table();
}
public boolean isDone() {
@ -98,20 +96,20 @@ public class OggFromWebMWriter implements Closeable {
try {
webm = new WebMReader(source);
webm.parse();
webm_segment = webm.getNextSegment();
webmSegment = webm.getNextSegment();
} finally {
parsed = true;
}
}
public void selectTrack(int trackIndex) throws IOException {
public void selectTrack(final int trackIndex) throws IOException {
if (!parsed) {
throw new IllegalStateException("source must be parsed first");
}
if (done) {
throw new IOException("already done");
}
if (webm_track != null) {
if (webmTrack != null) {
throw new IOException("tracks already selected");
}
@ -124,7 +122,7 @@ public class OggFromWebMWriter implements Closeable {
}
try {
webm_track = webm.selectTrack(trackIndex);
webmTrack = webm.selectTrack(trackIndex);
} finally {
parsed = true;
}
@ -135,7 +133,7 @@ public class OggFromWebMWriter implements Closeable {
done = true;
parsed = true;
webm_track = null;
webmTrack = null;
webm = null;
if (!output.isClosed()) {
@ -155,43 +153,44 @@ public class OggFromWebMWriter implements Closeable {
header.order(ByteOrder.LITTLE_ENDIAN);
/* step 1: get the amount of frames per seconds */
switch (webm_track.kind) {
switch (webmTrack.kind) {
case Audio:
resolution = getSampleFrequencyFromTrack(webm_track.bMetadata);
resolution = getSampleFrequencyFromTrack(webmTrack.bMetadata);
if (resolution == 0f) {
throw new RuntimeException("cannot get the audio sample rate");
}
break;
case Video:
// WARNING: untested
if (webm_track.defaultDuration == 0) {
if (webmTrack.defaultDuration == 0) {
throw new RuntimeException("missing default frame time");
}
resolution = 1000f / ((float) webm_track.defaultDuration / webm_segment.info.timecodeScale);
resolution = 1000f / ((float) webmTrack.defaultDuration
/ webmSegment.info.timecodeScale);
break;
default:
throw new RuntimeException("not implemented");
}
/* step 2: create packet with code init data */
if (webm_track.codecPrivate != null) {
addPacketSegment(webm_track.codecPrivate.length);
make_packetHeader(0x00, header, webm_track.codecPrivate);
if (webmTrack.codecPrivate != null) {
addPacketSegment(webmTrack.codecPrivate.length);
makePacketheader(0x00, header, webmTrack.codecPrivate);
write(header);
output.write(webm_track.codecPrivate);
output.write(webmTrack.codecPrivate);
}
/* step 3: create packet with metadata */
byte[] buffer = make_metadata();
byte[] buffer = makeMetadata();
if (buffer != null) {
addPacketSegment(buffer.length);
make_packetHeader(0x00, header, buffer);
makePacketheader(0x00, header, buffer);
write(header);
output.write(buffer);
}
/* step 4: calculate amount of packets */
while (webm_segment != null) {
while (webmSegment != null) {
bloq = getNextBlock();
if (bloq != null && addPacketSegment(bloq)) {
@ -203,29 +202,29 @@ public class OggFromWebMWriter implements Closeable {
}
// calculate the current packet duration using the next block
double elapsed_ns = webm_track.codecDelay;
double elapsedNs = webmTrack.codecDelay;
if (bloq == null) {
packet_flag = FLAG_LAST;// note: if the flag is FLAG_CONTINUED, is changed
elapsed_ns += webm_block_last_timecode;
packetFlag = FLAG_LAST; // note: if the flag is FLAG_CONTINUED, is changed
elapsedNs += webmBlockLastTimecode;
if (webm_track.defaultDuration > 0) {
elapsed_ns += webm_track.defaultDuration;
if (webmTrack.defaultDuration > 0) {
elapsedNs += webmTrack.defaultDuration;
} else {
// hardcoded way, guess the sample duration
elapsed_ns += webm_block_near_duration;
elapsedNs += webmBlockNearDuration;
}
} else {
elapsed_ns += bloq.absoluteTimeCodeNs;
elapsedNs += bloq.absoluteTimeCodeNs;
}
// get the sample count in the page
elapsed_ns = elapsed_ns / TIME_SCALE_NS;
elapsed_ns = Math.ceil(elapsed_ns * resolution);
elapsedNs = elapsedNs / TIME_SCALE_NS;
elapsedNs = Math.ceil(elapsedNs * resolution);
// create header and calculate page checksum
int checksum = make_packetHeader((long) elapsed_ns, header, null);
checksum = calc_crc32(checksum, page.array(), page.position());
int checksum = makePacketheader((long) elapsedNs, header, null);
checksum = calcCrc32(checksum, page.array(), page.position());
header.putInt(HEADER_CHECKSUM_OFFSET, checksum);
@ -233,52 +232,53 @@ public class OggFromWebMWriter implements Closeable {
write(header);
write(page);
webm_block = bloq;
webmBlock = bloq;
}
}
private int make_packetHeader(long gran_pos, @NonNull ByteBuffer buffer, byte[] immediate_page) {
private int makePacketheader(final long granPos, @NonNull final ByteBuffer buffer,
final byte[] immediatePage) {
short length = HEADER_SIZE;
buffer.putInt(0x5367674f); // "OggS" binary string in little-endian
buffer.put((byte) 0x00); // version
buffer.put(packet_flag);// type
buffer.put(packetFlag); // type
buffer.putLong(gran_pos);// granulate position
buffer.putLong(granPos); // granulate position
buffer.putInt(STREAM_ID);// bitstream serial number
buffer.putInt(sequence_count++);// page sequence number
buffer.putInt(streamId); // bitstream serial number
buffer.putInt(sequenceCount++); // page sequence number
buffer.putInt(0x00); // page checksum
buffer.put((byte) segment_table_size);// segment table
buffer.put(segment_table, 0, segment_table_size);// segment size
buffer.put((byte) segmentTableSize); // segment table
buffer.put(segmentTable, 0, segmentTableSize); // segment size
length += segment_table_size;
length += segmentTableSize;
clearSegmentTable(); // clear segment table for next header
int checksum_crc32 = calc_crc32(0x00, buffer.array(), length);
int checksumCrc32 = calcCrc32(0x00, buffer.array(), length);
if (immediate_page != null) {
checksum_crc32 = calc_crc32(checksum_crc32, immediate_page, immediate_page.length);
buffer.putInt(HEADER_CHECKSUM_OFFSET, checksum_crc32);
segment_table_next_timestamp -= TIME_SCALE_NS;
if (immediatePage != null) {
checksumCrc32 = calcCrc32(checksumCrc32, immediatePage, immediatePage.length);
buffer.putInt(HEADER_CHECKSUM_OFFSET, checksumCrc32);
segmentTableNextTimestamp -= TIME_SCALE_NS;
}
return checksum_crc32;
return checksumCrc32;
}
@Nullable
private byte[] make_metadata() {
if ("A_OPUS".equals(webm_track.codecId)) {
private byte[] makeMetadata() {
if ("A_OPUS".equals(webmTrack.codecId)) {
return new byte[]{
0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73, // "OpusTags" binary string
0x07, 0x00, 0x00, 0x00,// writting application string size
0x07, 0x00, 0x00, 0x00, // writing application string size
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65, // "NewPipe" binary string
0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags)
};
} else if ("A_VORBIS".equals(webm_track.codecId)) {
} else if ("A_VORBIS".equals(webmTrack.codecId)) {
return new byte[]{
0x03, // ????????
0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, // "vorbis" binary string
@ -303,51 +303,49 @@ public class OggFromWebMWriter implements Closeable {
return null;
}
private void write(ByteBuffer buffer) throws IOException {
private void write(final ByteBuffer buffer) throws IOException {
output.write(buffer.array(), 0, buffer.position());
buffer.position(0);
}
@Nullable
private SimpleBlock getNextBlock() throws IOException {
SimpleBlock res;
if (webm_block != null) {
res = webm_block;
webm_block = null;
if (webmBlock != null) {
res = webmBlock;
webmBlock = null;
return res;
}
if (webm_segment == null) {
webm_segment = webm.getNextSegment();
if (webm_segment == null) {
if (webmSegment == null) {
webmSegment = webm.getNextSegment();
if (webmSegment == null) {
return null; // no more blocks in the selected track
}
}
if (webm_cluster == null) {
webm_cluster = webm_segment.getNextCluster();
if (webm_cluster == null) {
webm_segment = null;
if (webmCluster == null) {
webmCluster = webmSegment.getNextCluster();
if (webmCluster == null) {
webmSegment = null;
return getNextBlock();
}
}
res = webm_cluster.getNextSimpleBlock();
res = webmCluster.getNextSimpleBlock();
if (res == null) {
webm_cluster = null;
webmCluster = null;
return getNextBlock();
}
webm_block_near_duration = res.absoluteTimeCodeNs - webm_block_last_timecode;
webm_block_last_timecode = res.absoluteTimeCodeNs;
webmBlockNearDuration = res.absoluteTimeCodeNs - webmBlockLastTimecode;
webmBlockLastTimecode = res.absoluteTimeCodeNs;
return res;
}
private float getSampleFrequencyFromTrack(byte[] bMetadata) {
private float getSampleFrequencyFromTrack(final byte[] bMetadata) {
// hardcoded way
ByteBuffer buffer = ByteBuffer.wrap(bMetadata);
@ -362,15 +360,15 @@ public class OggFromWebMWriter implements Closeable {
}
private void clearSegmentTable() {
segment_table_next_timestamp += TIME_SCALE_NS;
packet_flag = FLAG_UNSET;
segment_table_size = 0;
segmentTableNextTimestamp += TIME_SCALE_NS;
packetFlag = FLAG_UNSET;
segmentTableSize = 0;
}
private boolean addPacketSegment(SimpleBlock block) {
long timestamp = block.absoluteTimeCodeNs + webm_track.codecDelay;
private boolean addPacketSegment(final SimpleBlock block) {
long timestamp = block.absoluteTimeCodeNs + webmTrack.codecDelay;
if (timestamp >= segment_table_next_timestamp) {
if (timestamp >= segmentTableNextTimestamp) {
return false;
}
@ -382,7 +380,7 @@ public class OggFromWebMWriter implements Closeable {
throw new UnsupportedOperationException("page size cannot be larger than 65025");
}
int available = (segment_table.length - segment_table_size) * 255;
int available = (segmentTable.length - segmentTableSize) * 255;
boolean extra = (size % 255) == 0;
if (extra) {
@ -397,17 +395,17 @@ public class OggFromWebMWriter implements Closeable {
}
for (; size > 0; size -= 255) {
segment_table[segment_table_size++] = (byte) Math.min(size, 255);
segmentTable[segmentTableSize++] = (byte) Math.min(size, 255);
}
if (extra) {
segment_table[segment_table_size++] = 0x00;
segmentTable[segmentTableSize++] = 0x00;
}
return true;
}
private void populate_crc32_table() {
private void populateCrc32Table() {
for (int i = 0; i < 0x100; i++) {
int crc = i << 24;
for (int j = 0; j < 8; j++) {
@ -415,17 +413,16 @@ public class OggFromWebMWriter implements Closeable {
crc <<= 1;
crc ^= (int) (0x100000000L - b) & 0x04c11db7;
}
crc32_table[i] = crc;
crc32Table[i] = crc;
}
}
private int calc_crc32(int initial_crc, byte[] buffer, int size) {
private int calcCrc32(int initialCrc, final byte[] buffer, final int size) {
for (int i = 0; i < size; i++) {
int reg = (initial_crc >>> 24) & 0xff;
initial_crc = (initial_crc << 8) ^ crc32_table[reg ^ (buffer[i] & 0xff)];
int reg = (initialCrc >>> 24) & 0xff;
initialCrc = (initialCrc << 8) ^ crc32Table[reg ^ (buffer[i] & 0xff)];
}
return initial_crc;
return initialCrc;
}
}

View File

@ -26,18 +26,19 @@ public class SrtFromTtmlWriter {
private int frameIndex = 0;
public SrtFromTtmlWriter(SharpStream out, boolean ignoreEmptyFrames) {
public SrtFromTtmlWriter(final SharpStream out, final boolean ignoreEmptyFrames) {
this.out = out;
this.ignoreEmptyFrames = ignoreEmptyFrames;
}
private static String getTimestamp(Element frame, String attr) {
private static String getTimestamp(final Element frame, final String attr) {
return frame
.attr(attr)
.replace('.', ','); // SRT subtitles uses comma as decimal separator
}
private void writeFrame(String begin, String end, StringBuilder text) throws IOException {
private void writeFrame(final String begin, final String end, final StringBuilder text)
throws IOException {
writeString(String.valueOf(frameIndex++));
writeString(NEW_LINE);
writeString(begin);
@ -49,11 +50,11 @@ public class SrtFromTtmlWriter {
writeString(NEW_LINE);
}
private void writeString(String text) throws IOException {
private void writeString(final String text) throws IOException {
out.write(text.getBytes(charset));
}
public void build(SharpStream ttml) throws IOException {
public void build(final SharpStream ttml) throws IOException {
/*
* TTML parser with BASIC support
* multiple CUE is not supported
@ -66,25 +67,32 @@ public class SrtFromTtmlWriter {
// parse XML
byte[] buffer = new byte[(int) ttml.available()];
ttml.read(buffer);
Document doc = Jsoup.parse(new ByteArrayInputStream(buffer), "UTF-8", "", Parser.xmlParser());
Document doc = Jsoup.parse(new ByteArrayInputStream(buffer), "UTF-8", "",
Parser.xmlParser());
StringBuilder text = new StringBuilder(128);
Elements paragraph_list = doc.select("body > div > p");
Elements paragraphList = doc.select("body > div > p");
// check if has frames
if (paragraph_list.size() < 1) return;
if (paragraphList.size() < 1) {
return;
}
for (Element paragraph : paragraph_list) {
for (Element paragraph : paragraphList) {
text.setLength(0);
for (Node children : paragraph.childNodes()) {
if (children instanceof TextNode)
if (children instanceof TextNode) {
text.append(((TextNode) children).text());
else if (children instanceof Element && ((Element) children).tagName().equalsIgnoreCase("br"))
} else if (children instanceof Element
&& ((Element) children).tagName().equalsIgnoreCase("br")) {
text.append(NEW_LINE);
}
}
if (ignoreEmptyFrames && text.length() < 1) continue;
if (ignoreEmptyFrames && text.length() < 1) {
continue;
}
String begin = getTimestamp(paragraph, "begin");
String end = getTimestamp(paragraph, "end");

View File

@ -14,36 +14,35 @@ import java.util.NoSuchElementException;
* @author kapodamy
*/
public class WebMReader {
private static final int ID_EMBL = 0x0A45DFA3;
private static final int ID_EMBL_READ_VERSION = 0x02F7;
private static final int ID_EMBL_DOC_TYPE = 0x0282;
private static final int ID_EMBL_DOC_TYPE_READ_VERSION = 0x0285;
private final static int ID_EMBL = 0x0A45DFA3;
private final static int ID_EMBLReadVersion = 0x02F7;
private final static int ID_EMBLDocType = 0x0282;
private final static int ID_EMBLDocTypeReadVersion = 0x0285;
private static final int ID_SEGMENT = 0x08538067;
private final static int ID_Segment = 0x08538067;
private static final int ID_INFO = 0x0549A966;
private static final int ID_TIMECODE_SCALE = 0x0AD7B1;
private static final int ID_DURATION = 0x489;
private final static int ID_Info = 0x0549A966;
private final static int ID_TimecodeScale = 0x0AD7B1;
private final static int ID_Duration = 0x489;
private static final int ID_TRACKS = 0x0654AE6B;
private static final int ID_TRACK_ENTRY = 0x2E;
private static final int ID_TRACK_NUMBER = 0x57;
private static final int ID_TRACK_TYPE = 0x03;
private static final int ID_CODEC_ID = 0x06;
private static final int ID_CODEC_PRIVATE = 0x23A2;
private static final int ID_VIDEO = 0x60;
private static final int ID_AUDIO = 0x61;
private static final int ID_DEFAULT_DURATION = 0x3E383;
private static final int ID_FLAG_LACING = 0x1C;
private static final int ID_CODEC_DELAY = 0x16AA;
private static final int ID_SEEK_PRE_ROLL = 0x16BB;
private final static int ID_Tracks = 0x0654AE6B;
private final static int ID_TrackEntry = 0x2E;
private final static int ID_TrackNumber = 0x57;
private final static int ID_TrackType = 0x03;
private final static int ID_CodecID = 0x06;
private final static int ID_CodecPrivate = 0x23A2;
private final static int ID_Video = 0x60;
private final static int ID_Audio = 0x61;
private final static int ID_DefaultDuration = 0x3E383;
private final static int ID_FlagLacing = 0x1C;
private final static int ID_CodecDelay = 0x16AA;
private final static int ID_SeekPreRoll = 0x16BB;
private final static int ID_Cluster = 0x0F43B675;
private final static int ID_Timecode = 0x67;
private final static int ID_SimpleBlock = 0x23;
private final static int ID_Block = 0x21;
private final static int ID_GroupBlock = 0x20;
private static final int ID_CLUSTER = 0x0F43B675;
private static final int ID_TIMECODE = 0x67;
private static final int ID_SIMPLE_BLOCK = 0x23;
private static final int ID_BLOCK = 0x21;
private static final int ID_GROUP_BLOCK = 0x20;
public enum TrackKind {
@ -57,7 +56,7 @@ public class WebMReader {
private boolean done;
private boolean firstSegment;
public WebMReader(SharpStream source) {
public WebMReader(final SharpStream source) {
this.stream = new DataReader(source);
}
@ -68,7 +67,7 @@ public class WebMReader {
}
ensure(elem);
elem = untilElement(null, ID_Segment);
elem = untilElement(null, ID_SEGMENT);
if (elem == null) {
throw new IOException("Fragment element not found");
}
@ -83,7 +82,7 @@ public class WebMReader {
return tracks;
}
public WebMTrack selectTrack(int index) {
public WebMTrack selectTrack(final int index) {
selectedTrack = index;
return tracks[index];
}
@ -100,7 +99,7 @@ public class WebMReader {
ensure(segment.ref);
// WARNING: track cannot be the same or have different index in new segments
Element elem = untilElement(null, ID_Segment);
Element elem = untilElement(null, ID_SEGMENT);
if (elem == null) {
done = true;
return null;
@ -110,9 +109,7 @@ public class WebMReader {
return segment;
}
private long readNumber(Element parent) throws IOException {
private long readNumber(final Element parent) throws IOException {
int length = (int) parent.contentSize;
long value = 0;
while (length-- > 0) {
@ -125,11 +122,11 @@ public class WebMReader {
return value;
}
private String readString(Element parent) throws IOException {
private String readString(final Element parent) throws IOException {
return new String(readBlob(parent), StandardCharsets.UTF_8); // or use "utf-8"
}
private byte[] readBlob(Element parent) throws IOException {
private byte[] readBlob(final Element parent) throws IOException {
long length = parent.contentSize;
byte[] buffer = new byte[(int) length];
int read = stream.read(buffer);
@ -180,16 +177,17 @@ public class WebMReader {
return elem;
}
private Element readElement(int expected) throws IOException {
private Element readElement(final int expected) throws IOException {
Element elem = readElement();
if (expected != 0 && elem.type != expected) {
throw new NoSuchElementException("expected " + elementID(expected) + " found " + elementID(elem.type));
throw new NoSuchElementException("expected " + elementID(expected)
+ " found " + elementID(elem.type));
}
return elem;
}
private Element untilElement(Element ref, int... expected) throws IOException {
private Element untilElement(final Element ref, final int... expected) throws IOException {
Element elem;
while (ref == null ? stream.available() : (stream.position() < (ref.offset + ref.size))) {
elem = readElement();
@ -208,11 +206,11 @@ public class WebMReader {
return null;
}
private String elementID(long type) {
private String elementID(final long type) {
return "0x".concat(Long.toHexString(type));
}
private void ensure(Element ref) throws IOException {
private void ensure(final Element ref) throws IOException {
long skip = (ref.offset + ref.size) - stream.position();
if (skip == 0) {
@ -227,10 +225,9 @@ public class WebMReader {
stream.skipBytes(skip);
}
private boolean readEbml(Element ref, int minReadVersion, int minDocTypeVersion) throws IOException {
Element elem = untilElement(ref, ID_EMBLReadVersion);
private boolean readEbml(final Element ref, final int minReadVersion,
final int minDocTypeVersion) throws IOException {
Element elem = untilElement(ref, ID_EMBL_READ_VERSION);
if (elem == null) {
return false;
}
@ -238,28 +235,28 @@ public class WebMReader {
return false;
}
elem = untilElement(ref, ID_EMBLDocType);
elem = untilElement(ref, ID_EMBL_DOC_TYPE);
if (elem == null) {
return false;
}
if (!readString(elem).equals("webm")) {
return false;
}
elem = untilElement(ref, ID_EMBLDocTypeReadVersion);
elem = untilElement(ref, ID_EMBL_DOC_TYPE_READ_VERSION);
return elem != null && readNumber(elem) <= minDocTypeVersion;
}
private Info readInfo(Element ref) throws IOException {
private Info readInfo(final Element ref) throws IOException {
Element elem;
Info info = new Info();
while ((elem = untilElement(ref, ID_TimecodeScale, ID_Duration)) != null) {
while ((elem = untilElement(ref, ID_TIMECODE_SCALE, ID_DURATION)) != null) {
switch (elem.type) {
case ID_TimecodeScale:
case ID_TIMECODE_SCALE:
info.timecodeScale = readNumber(elem);
break;
case ID_Duration:
case ID_DURATION:
info.duration = readNumber(elem);
break;
}
@ -273,19 +270,20 @@ public class WebMReader {
return info;
}
private Segment readSegment(Element ref, int trackLacingExpected, boolean metadataExpected) throws IOException {
private Segment readSegment(final Element ref, final int trackLacingExpected,
final boolean metadataExpected) throws IOException {
Segment obj = new Segment(ref);
Element elem;
while ((elem = untilElement(ref, ID_Info, ID_Tracks, ID_Cluster)) != null) {
if (elem.type == ID_Cluster) {
while ((elem = untilElement(ref, ID_INFO, ID_TRACKS, ID_CLUSTER)) != null) {
if (elem.type == ID_CLUSTER) {
obj.currentCluster = elem;
break;
}
switch (elem.type) {
case ID_Info:
case ID_INFO:
obj.info = readInfo(elem);
break;
case ID_Tracks:
case ID_TRACKS:
obj.tracks = readTracks(elem, trackLacingExpected);
break;
}
@ -293,48 +291,50 @@ public class WebMReader {
}
if (metadataExpected && (obj.info == null || obj.tracks == null)) {
throw new RuntimeException("Cluster element found without Info and/or Tracks element at position " + String.valueOf(ref.offset));
throw new RuntimeException(
"Cluster element found without Info and/or Tracks element at position "
+ String.valueOf(ref.offset));
}
return obj;
}
private WebMTrack[] readTracks(Element ref, int lacingExpected) throws IOException {
private WebMTrack[] readTracks(final Element ref, final int lacingExpected) throws IOException {
ArrayList<WebMTrack> trackEntries = new ArrayList<>(2);
Element elem_trackEntry;
Element elemTrackEntry;
while ((elem_trackEntry = untilElement(ref, ID_TrackEntry)) != null) {
while ((elemTrackEntry = untilElement(ref, ID_TRACK_ENTRY)) != null) {
WebMTrack entry = new WebMTrack();
boolean drop = false;
Element elem;
while ((elem = untilElement(elem_trackEntry)) != null) {
while ((elem = untilElement(elemTrackEntry)) != null) {
switch (elem.type) {
case ID_TrackNumber:
case ID_TRACK_NUMBER:
entry.trackNumber = readNumber(elem);
break;
case ID_TrackType:
case ID_TRACK_TYPE:
entry.trackType = (int) readNumber(elem);
break;
case ID_CodecID:
case ID_CODEC_ID:
entry.codecId = readString(elem);
break;
case ID_CodecPrivate:
case ID_CODEC_PRIVATE:
entry.codecPrivate = readBlob(elem);
break;
case ID_Audio:
case ID_Video:
case ID_AUDIO:
case ID_VIDEO:
entry.bMetadata = readBlob(elem);
break;
case ID_DefaultDuration:
case ID_DEFAULT_DURATION:
entry.defaultDuration = readNumber(elem);
break;
case ID_FlagLacing:
case ID_FLAG_LACING:
drop = readNumber(elem) != lacingExpected;
break;
case ID_CodecDelay:
case ID_CODEC_DELAY:
entry.codecDelay = readNumber(elem);
break;
case ID_SeekPreRoll:
case ID_SEEK_PRE_ROLL:
entry.seekPreRoll = readNumber(elem);
break;
default:
@ -345,7 +345,7 @@ public class WebMReader {
if (!drop) {
trackEntries.add(entry);
}
ensure(elem_trackEntry);
ensure(elemTrackEntry);
}
WebMTrack[] entries = new WebMTrack[trackEntries.size()];
@ -368,37 +368,36 @@ public class WebMReader {
return entries;
}
private SimpleBlock readSimpleBlock(Element ref) throws IOException {
private SimpleBlock readSimpleBlock(final Element ref) throws IOException {
SimpleBlock obj = new SimpleBlock(ref);
obj.trackNumber = readEncodedNumber();
obj.relativeTimeCode = stream.readShort();
obj.flags = (byte) stream.read();
obj.dataSize = (int) ((ref.offset + ref.size) - stream.position());
obj.createdFromBlock = ref.type == ID_Block;
obj.createdFromBlock = ref.type == ID_BLOCK;
// NOTE: lacing is not implemented, and will be mixed with the stream data
if (obj.dataSize < 0) {
throw new IOException(String.format("Unexpected SimpleBlock element size, missing %s bytes", -obj.dataSize));
throw new IOException(String.format(
"Unexpected SimpleBlock element size, missing %s bytes", -obj.dataSize));
}
return obj;
}
private Cluster readCluster(Element ref) throws IOException {
private Cluster readCluster(final Element ref) throws IOException {
Cluster obj = new Cluster(ref);
Element elem = untilElement(ref, ID_Timecode);
Element elem = untilElement(ref, ID_TIMECODE);
if (elem == null) {
throw new NoSuchElementException("Cluster at " + String.valueOf(ref.offset) + " without Timecode element");
throw new NoSuchElementException("Cluster at " + String.valueOf(ref.offset)
+ " without Timecode element");
}
obj.timecode = readNumber(elem);
return obj;
}
class Element {
int type;
long offset;
long contentSize;
@ -406,13 +405,11 @@ public class WebMReader {
}
public class Info {
public long timecodeScale;
public long duration;
}
public class WebMTrack {
public long trackNumber;
protected int trackType;
public String codecId;
@ -425,8 +422,7 @@ public class WebMReader {
}
public class Segment {
Segment(Element ref) {
Segment(final Element ref) {
this.ref = ref;
this.firstClusterInSegment = true;
}
@ -447,7 +443,7 @@ public class WebMReader {
}
ensure(segment.currentCluster);
Element elem = untilElement(segment.ref, ID_Cluster);
Element elem = untilElement(segment.ref, ID_CLUSTER);
if (elem == null) {
return null;
}
@ -459,11 +455,10 @@ public class WebMReader {
}
public class SimpleBlock {
public InputStream data;
public boolean createdFromBlock;
SimpleBlock(Element ref) {
SimpleBlock(final Element ref) {
this.ref = ref;
}
@ -480,13 +475,12 @@ public class WebMReader {
}
public class Cluster {
Element ref;
SimpleBlock currentSimpleBlock = null;
Element currentBlockGroup = null;
public long timecode;
Cluster(Element ref) {
Cluster(final Element ref) {
this.ref = ref;
}
@ -508,14 +502,14 @@ public class WebMReader {
}
while (!insideClusterBounds()) {
Element elem = untilElement(ref, ID_SimpleBlock, ID_GroupBlock);
Element elem = untilElement(ref, ID_SIMPLE_BLOCK, ID_GROUP_BLOCK);
if (elem == null) {
return null;
}
if (elem.type == ID_GroupBlock) {
if (elem.type == ID_GROUP_BLOCK) {
currentBlockGroup = elem;
elem = untilElement(currentBlockGroup, ID_Block);
elem = untilElement(currentBlockGroup, ID_BLOCK);
if (elem == null) {
ensure(currentBlockGroup);
@ -529,7 +523,8 @@ public class WebMReader {
currentSimpleBlock.data = stream.getView((int) currentSimpleBlock.dataSize);
// calculate the timestamp in nanoseconds
currentSimpleBlock.absoluteTimeCodeNs = currentSimpleBlock.relativeTimeCode + this.timecode;
currentSimpleBlock.absoluteTimeCodeNs = currentSimpleBlock.relativeTimeCode
+ this.timecode;
currentSimpleBlock.absoluteTimeCodeNs *= segment.info.timecodeScale;
return currentSimpleBlock;
@ -537,10 +532,7 @@ public class WebMReader {
ensure(elem);
}
return null;
}
}
}

View File

@ -19,14 +19,13 @@ import java.util.ArrayList;
* @author kapodamy
*/
public class WebMWriter implements Closeable {
private final static int BUFFER_SIZE = 8 * 1024;
private final static int DEFAULT_TIMECODE_SCALE = 1000000;
private final static int INTERV = 100;// 100ms on 1000000us timecode scale
private final static int DEFAULT_CUES_EACH_MS = 5000;// 5000ms on 1000000us timecode scale
private final static byte CLUSTER_HEADER_SIZE = 8;
private final static int CUE_RESERVE_SIZE = 65535;
private final static byte MINIMUM_EBML_VOID_SIZE = 4;
private static final int BUFFER_SIZE = 8 * 1024;
private static final int DEFAULT_TIMECODE_SCALE = 1000000;
private static final int INTERV = 100; // 100ms on 1000000us timecode scale
private static final int DEFAULT_CUES_EACH_MS = 5000; // 5000ms on 1000000us timecode scale
private static final byte CLUSTER_HEADER_SIZE = 8;
private static final int CUE_RESERVE_SIZE = 65535;
private static final byte MINIMUM_EBML_VOID_SIZE = 4;
private WebMReader.WebMTrack[] infoTracks;
private SharpStream[] sourceTracks;
@ -46,7 +45,7 @@ public class WebMWriter implements Closeable {
private byte[] outBuffer;
private ByteBuffer outByteBuffer;
public WebMWriter(SharpStream... source) {
public WebMWriter(final SharpStream... source) {
sourceTracks = source;
readers = new WebMReader[sourceTracks.length];
infoTracks = new WebMTrack[sourceTracks.length];
@ -55,7 +54,7 @@ public class WebMWriter implements Closeable {
clustersOffsetsSizes = new ArrayList<>(256);
}
public WebMTrack[] getTracksFromSource(int sourceIndex) throws IllegalStateException {
public WebMTrack[] getTracksFromSource(final int sourceIndex) throws IllegalStateException {
if (done) {
throw new IllegalStateException("already done");
}
@ -85,7 +84,7 @@ public class WebMWriter implements Closeable {
}
}
public void selectTracks(int... trackIndex) throws IOException {
public void selectTracks(final int... trackIndex) throws IOException {
try {
readersSegment = new Segment[readers.length];
readersCluster = new Cluster[readers.length];
@ -126,7 +125,7 @@ public class WebMWriter implements Closeable {
clustersOffsetsSizes = null;
}
public void build(SharpStream out) throws IOException, RuntimeException {
public void build(final SharpStream out) throws IOException, RuntimeException {
if (!out.canRewind()) {
throw new IOException("The output stream must be allow seek");
}
@ -187,7 +186,7 @@ public class WebMWriter implements Closeable {
// reserve space for Cues element
long cueOffset = written;
make_EBML_void(out, CUE_RESERVE_SIZE, true);
makeEbmlVoid(out, CUE_RESERVE_SIZE, true);
int[] defaultSampleDuration = new int[infoTracks.length];
long[] duration = new long[infoTracks.length];
@ -196,7 +195,8 @@ public class WebMWriter implements Closeable {
if (infoTracks[i].defaultDuration < 0) {
defaultSampleDuration[i] = -1; // not available
} else {
defaultSampleDuration[i] = (int) Math.ceil(infoTracks[i].defaultDuration / (float) DEFAULT_TIMECODE_SCALE);
defaultSampleDuration[i] = (int) Math.ceil(infoTracks[i].defaultDuration
/ (float) DEFAULT_TIMECODE_SCALE);
}
duration[i] = -1;
}
@ -239,15 +239,18 @@ public class WebMWriter implements Closeable {
newClusterByTrackId = -1;
baseTimecode = bloq.absoluteTimecode;
limitTimecode = baseTimecode + INTERV;
currentClusterOffset = makeCluster(out, baseTimecode, currentClusterOffset, true);
currentClusterOffset = makeCluster(out, baseTimecode, currentClusterOffset,
true);
}
if (cuesForTrackId == i) {
if ((nextCueTime > -1 && bloq.absoluteTimecode >= nextCueTime) || (nextCueTime < 0 && bloq.isKeyframe())) {
if ((nextCueTime > -1 && bloq.absoluteTimecode >= nextCueTime)
|| (nextCueTime < 0 && bloq.isKeyframe())) {
if (nextCueTime > -1) {
nextCueTime += DEFAULT_CUES_EACH_MS;
}
keyFrames.add(new KeyFrame(segmentOffset, currentClusterOffset, written, bloq.absoluteTimecode));
keyFrames.add(new KeyFrame(segmentOffset, currentClusterOffset, written,
bloq.absoluteTimecode));
}
}
@ -255,7 +258,8 @@ public class WebMWriter implements Closeable {
blockWritten++;
if (defaultSampleDuration[i] < 0 && duration[i] >= 0) {
// if the sample duration in unknown, calculate using current_duration - previous_duration
// if the sample duration in unknown,
// calculate using current_duration - previous_duration
defaultSampleDuration[i] = (int) (bloq.absoluteTimecode - duration[i]);
}
duration[i] = bloq.absoluteTimecode;
@ -318,7 +322,7 @@ public class WebMWriter implements Closeable {
dump(outBuffer, size, out);
}
make_EBML_void(out, CUE_RESERVE_SIZE - cueSize - 7, false);
makeEbmlVoid(out, CUE_RESERVE_SIZE - cueSize - 7, false);
seekTo(out, cueOffset + 5);
outByteBuffer.putShort(0, cueSize);
@ -332,7 +336,7 @@ public class WebMWriter implements Closeable {
}
}
private Block getNextBlockFrom(int internalTrackId) throws IOException {
private Block getNextBlockFrom(final int internalTrackId) throws IOException {
if (readersSegment[internalTrackId] == null) {
readersSegment[internalTrackId] = readers[internalTrackId].getNextSegment();
if (readersSegment[internalTrackId] == null) {
@ -364,7 +368,7 @@ public class WebMWriter implements Closeable {
return bloq;
}
private void seekTo(SharpStream stream, long offset) throws IOException {
private void seekTo(final SharpStream stream, final long offset) throws IOException {
if (stream.canSeek()) {
stream.seek(offset);
} else {
@ -379,13 +383,15 @@ public class WebMWriter implements Closeable {
written = offset;
}
private void writeInt(SharpStream stream, long offset, int number) throws IOException {
private void writeInt(final SharpStream stream, final long offset, final int number)
throws IOException {
seekTo(stream, offset);
outByteBuffer.putInt(0, number);
dump(outBuffer, DataReader.INTEGER_SIZE, stream);
}
private void writeBlock(SharpStream stream, Block bloq, long clusterTimecode) throws IOException {
private void writeBlock(final SharpStream stream, final Block bloq, final long clusterTimecode)
throws IOException {
long relativeTimeCode = bloq.absoluteTimecode - clusterTimecode;
if (relativeTimeCode < Short.MIN_VALUE || relativeTimeCode > Short.MAX_VALUE) {
@ -396,7 +402,8 @@ public class WebMWriter implements Closeable {
listBuffer.add(new byte[]{(byte) 0xa3});
listBuffer.add(null); // block size
listBuffer.add(encode(bloq.trackNumber + 1, false));
listBuffer.add(ByteBuffer.allocate(DataReader.SHORT_SIZE).putShort((short) relativeTimeCode).array());
listBuffer.add(ByteBuffer.allocate(DataReader.SHORT_SIZE).putShort((short) relativeTimeCode)
.array());
listBuffer.add(new byte[]{bloq.flags});
int blockSize = bloq.dataSize;
@ -413,7 +420,8 @@ public class WebMWriter implements Closeable {
}
}
private long makeCluster(SharpStream stream, long timecode, long offset, boolean create) throws IOException {
private long makeCluster(final SharpStream stream, final long timecode, long offset,
final boolean create) throws IOException {
ClusterInfo cluster;
if (offset > 0) {
@ -444,7 +452,7 @@ public class WebMWriter implements Closeable {
return offset;
}
private void makeEBML(SharpStream stream) throws IOException {
private void makeEBML(final SharpStream stream) throws IOException {
// deafult values
dump(new byte[]{
0x1A, 0x45, (byte) 0xDF, (byte) 0xA3, 0x01, 0x00, 0x00, 0x00,
@ -468,7 +476,7 @@ public class WebMWriter implements Closeable {
return lengthFor(buffer);
}
private ArrayList<byte[]> makeTrackEntry(int internalTrackId, WebMTrack track) {
private ArrayList<byte[]> makeTrackEntry(final int internalTrackId, final WebMTrack track) {
byte[] id = encode(internalTrackId + 1, true);
ArrayList<byte[]> buffer = new ArrayList<>(12);
@ -531,10 +539,10 @@ public class WebMWriter implements Closeable {
}
return lengthFor(buffer);
}
private int makeCuePoint(int internalTrackId, KeyFrame keyFrame, byte[] buffer) {
private int makeCuePoint(final int internalTrackId, final KeyFrame keyFrame,
final byte[] buffer) {
ArrayList<byte[]> cue = new ArrayList<>(5);
/* CuePoint */
@ -559,7 +567,8 @@ public class WebMWriter implements Closeable {
return size;
}
private ArrayList<byte[]> makeCueTrackPosition(int internalTrackId, KeyFrame keyFrame) {
private ArrayList<byte[]> makeCueTrackPosition(final int internalTrackId,
final KeyFrame keyFrame) {
ArrayList<byte[]> buffer = new ArrayList<>(8);
/* CueTrackPositions */
@ -583,7 +592,8 @@ public class WebMWriter implements Closeable {
return lengthFor(buffer);
}
private void make_EBML_void(SharpStream out, int size, boolean wipe) throws IOException {
private void makeEbmlVoid(final SharpStream out, int size, final boolean wipe)
throws IOException {
/* ebml void */
outByteBuffer.putShort(0, (short) 0xec20);
outByteBuffer.putShort(2, (short) (size - 4));
@ -600,23 +610,25 @@ public class WebMWriter implements Closeable {
}
}
private void dump(byte[] buffer, SharpStream stream) throws IOException {
private void dump(final byte[] buffer, final SharpStream stream) throws IOException {
dump(buffer, buffer.length, stream);
}
private void dump(byte[] buffer, int count, SharpStream stream) throws IOException {
private void dump(final byte[] buffer, final int count, final SharpStream stream)
throws IOException {
stream.write(buffer, 0, count);
written += count;
}
private void dump(ArrayList<byte[]> buffers, SharpStream stream) throws IOException {
private void dump(final ArrayList<byte[]> buffers, final SharpStream stream)
throws IOException {
for (byte[] buffer : buffers) {
stream.write(buffer);
written += buffer.length;
}
}
private ArrayList<byte[]> lengthFor(ArrayList<byte[]> buffer) {
private ArrayList<byte[]> lengthFor(final ArrayList<byte[]> buffer) {
long size = 0;
for (int i = 2; i < buffer.size(); i++) {
size += buffer.get(i).length;
@ -625,7 +637,7 @@ public class WebMWriter implements Closeable {
return buffer;
}
private byte[] encode(long number, boolean withLength) {
private byte[] encode(final long number, final boolean withLength) {
int length = -1;
for (int i = 1; i <= 7; i++) {
if (number < Math.pow(2, 7 * i)) {
@ -662,7 +674,7 @@ public class WebMWriter implements Closeable {
return buffer;
}
private ArrayList<byte[]> encode(String value) {
private ArrayList<byte[]> encode(final String value) {
byte[] str;
str = value.getBytes(StandardCharsets.UTF_8); // or use "utf-8"
@ -673,7 +685,7 @@ public class WebMWriter implements Closeable {
return buffer;
}
private boolean valid(byte[] buffer) {
private boolean valid(final byte[] buffer) {
return buffer != null && buffer.length > 0;
}
@ -717,8 +729,7 @@ public class WebMWriter implements Closeable {
}
class KeyFrame {
KeyFrame(long segment, long cluster, long block, long timecode) {
KeyFrame(final long segment, final long cluster, final long block, final long timecode) {
clusterPosition = cluster - segment;
relativePosition = (int) (block - cluster - CLUSTER_HEADER_SIZE);
duration = timecode;
@ -730,7 +741,6 @@ public class WebMWriter implements Closeable {
}
class Block {
InputStream data;
int trackNumber;
byte flags;
@ -744,14 +754,13 @@ public class WebMWriter implements Closeable {
@NonNull
@Override
public String toString() {
return String.format("trackNumber=%s isKeyFrame=%S absoluteTimecode=%s", trackNumber, isKeyframe(), absoluteTimecode);
return String.format("trackNumber=%s isKeyFrame=%S absoluteTimecode=%s", trackNumber,
isKeyframe(), absoluteTimecode);
}
}
class ClusterInfo {
long offset;
int size;
}
}

View File

@ -4,15 +4,14 @@ import java.io.Closeable;
import java.io.IOException;
/**
* based on c#
* Based on C#'s Stream class.
*/
public abstract class SharpStream implements Closeable {
public abstract int read() throws IOException;
public abstract int read(byte buffer[]) throws IOException;
public abstract int read(byte[] buffer) throws IOException;
public abstract int read(byte buffer[], int offset, int count) throws IOException;
public abstract int read(byte[] buffer, int offset, int count) throws IOException;
public abstract long skip(long amount) throws IOException;
@ -49,11 +48,11 @@ public abstract class SharpStream implements Closeable {
// STUB
}
public void setLength(long length) throws IOException {
public void setLength(final long length) throws IOException {
throw new IOException("Not implemented");
}
public void seek(long offset) throws IOException {
public void seek(final long offset) throws IOException {
throw new IOException("Not implemented");
}

View File

@ -6,7 +6,38 @@
<suppress checks="FinalParameters"
files="LocalItemListAdapter.java"
lines="220,292"/>
<suppress checks="FinalParameters"
files="InfoListAdapter.java"
lines="253,325"/>
<!-- org.schabi.newpipe.streams -->
<suppress checks="FinalParameters"
files="WebMWriter.java"
lines="423,595"/>
<suppress checks="LineLength"
files="WebMWriter.java"
lines="160,162"/>
<suppress checks="FinalParameters"
files="OggFromWebMWriter.java"
lines="378,420"/>
<suppress checks="LineLength"
files="OggFromWebMWriter.java"
lines="292,296"/>
<suppress checks="FinalParameters"
files="Mp4FromDashWriter.java"
lines="643"/>
<suppress checks="LineLength"
files="Mp4FromDashWriter.java"
lines="677,678,720-724,738,762,848,850-855"/>
<suppress checks="InnerAssignment"
files="Mp4DashReader.java"
lines="190"/>
<suppress checks="FinalParameters"
files="DataReader.java"
lines="46,93"/>
</suppressions>