Fix Lint: Inconsistent line separators
This commit is contained in:
parent
18fb0a13d7
commit
6f3dfad550
8 changed files with 1032 additions and 1032 deletions
|
|
@ -1,416 +1,416 @@
|
|||
package org.schabi.newpipe.streams;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.schabi.newpipe.streams.WebMReader.Cluster;
|
||||
import org.schabi.newpipe.streams.WebMReader.Segment;
|
||||
import org.schabi.newpipe.streams.WebMReader.SimpleBlock;
|
||||
import org.schabi.newpipe.streams.WebMReader.WebMTrack;
|
||||
import org.schabi.newpipe.streams.io.SharpStream;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* @author kapodamy
|
||||
*/
|
||||
public class OggFromWebMWriter implements Closeable {
|
||||
private static final byte FLAG_UNSET = 0x00;
|
||||
//private static final byte FLAG_CONTINUED = 0x01;
|
||||
private static final byte FLAG_FIRST = 0x02;
|
||||
private static final byte FLAG_LAST = 0x04;
|
||||
|
||||
private static final byte HEADER_CHECKSUM_OFFSET = 22;
|
||||
private static final byte HEADER_SIZE = 27;
|
||||
|
||||
private static final int TIME_SCALE_NS = 1000000000;
|
||||
|
||||
private boolean done = false;
|
||||
private boolean parsed = false;
|
||||
|
||||
private final SharpStream source;
|
||||
private final SharpStream output;
|
||||
|
||||
private int sequenceCount = 0;
|
||||
private final int streamId;
|
||||
private byte packetFlag = FLAG_FIRST;
|
||||
|
||||
private WebMReader webm = null;
|
||||
private WebMTrack webmTrack = null;
|
||||
private Segment webmSegment = null;
|
||||
private Cluster webmCluster = null;
|
||||
private SimpleBlock webmBlock = null;
|
||||
|
||||
private long webmBlockLastTimecode = 0;
|
||||
private long webmBlockNearDuration = 0;
|
||||
|
||||
private short segmentTableSize = 0;
|
||||
private final byte[] segmentTable = new byte[255];
|
||||
private long segmentTableNextTimestamp = TIME_SCALE_NS;
|
||||
|
||||
private final int[] crc32Table = new int[256];
|
||||
|
||||
public OggFromWebMWriter(@NonNull final SharpStream source, @NonNull final SharpStream target) {
|
||||
if (!source.canRead() || !source.canRewind()) {
|
||||
throw new IllegalArgumentException("source stream must be readable and allows seeking");
|
||||
}
|
||||
if (!target.canWrite() || !target.canRewind()) {
|
||||
throw new IllegalArgumentException("output stream must be writable and allows seeking");
|
||||
}
|
||||
|
||||
this.source = source;
|
||||
this.output = target;
|
||||
|
||||
this.streamId = (int) System.currentTimeMillis();
|
||||
|
||||
populateCrc32Table();
|
||||
}
|
||||
|
||||
public boolean isDone() {
|
||||
return done;
|
||||
}
|
||||
|
||||
public boolean isParsed() {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
public WebMTrack[] getTracksFromSource() throws IllegalStateException {
|
||||
if (!parsed) {
|
||||
throw new IllegalStateException("source must be parsed first");
|
||||
}
|
||||
|
||||
return webm.getAvailableTracks();
|
||||
}
|
||||
|
||||
public void parseSource() throws IOException, IllegalStateException {
|
||||
if (done) {
|
||||
throw new IllegalStateException("already done");
|
||||
}
|
||||
if (parsed) {
|
||||
throw new IllegalStateException("already parsed");
|
||||
}
|
||||
|
||||
try {
|
||||
webm = new WebMReader(source);
|
||||
webm.parse();
|
||||
webmSegment = webm.getNextSegment();
|
||||
} finally {
|
||||
parsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void selectTrack(final int trackIndex) throws IOException {
|
||||
if (!parsed) {
|
||||
throw new IllegalStateException("source must be parsed first");
|
||||
}
|
||||
if (done) {
|
||||
throw new IOException("already done");
|
||||
}
|
||||
if (webmTrack != null) {
|
||||
throw new IOException("tracks already selected");
|
||||
}
|
||||
|
||||
switch (webm.getAvailableTracks()[trackIndex].kind) {
|
||||
case Audio:
|
||||
case Video:
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("the track must an audio or video stream");
|
||||
}
|
||||
|
||||
try {
|
||||
webmTrack = webm.selectTrack(trackIndex);
|
||||
} finally {
|
||||
parsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
done = true;
|
||||
parsed = true;
|
||||
|
||||
webmTrack = null;
|
||||
webm = null;
|
||||
|
||||
if (!output.isClosed()) {
|
||||
output.flush();
|
||||
}
|
||||
|
||||
source.close();
|
||||
output.close();
|
||||
}
|
||||
|
||||
public void build() throws IOException {
|
||||
final float resolution;
|
||||
SimpleBlock bloq;
|
||||
final ByteBuffer header = ByteBuffer.allocate(27 + (255 * 255));
|
||||
final ByteBuffer page = ByteBuffer.allocate(64 * 1024);
|
||||
|
||||
header.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
/* step 1: get the amount of frames per seconds */
|
||||
switch (webmTrack.kind) {
|
||||
case Audio:
|
||||
resolution = getSampleFrequencyFromTrack(webmTrack.bMetadata);
|
||||
if (resolution == 0f) {
|
||||
throw new RuntimeException("cannot get the audio sample rate");
|
||||
}
|
||||
break;
|
||||
case Video:
|
||||
// WARNING: untested
|
||||
if (webmTrack.defaultDuration == 0) {
|
||||
throw new RuntimeException("missing default frame time");
|
||||
}
|
||||
resolution = 1000f / ((float) webmTrack.defaultDuration
|
||||
/ webmSegment.info.timecodeScale);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("not implemented");
|
||||
}
|
||||
|
||||
/* step 2: create packet with code init data */
|
||||
if (webmTrack.codecPrivate != null) {
|
||||
addPacketSegment(webmTrack.codecPrivate.length);
|
||||
makePacketheader(0x00, header, webmTrack.codecPrivate);
|
||||
write(header);
|
||||
output.write(webmTrack.codecPrivate);
|
||||
}
|
||||
|
||||
/* step 3: create packet with metadata */
|
||||
final byte[] buffer = makeMetadata();
|
||||
if (buffer != null) {
|
||||
addPacketSegment(buffer.length);
|
||||
makePacketheader(0x00, header, buffer);
|
||||
write(header);
|
||||
output.write(buffer);
|
||||
}
|
||||
|
||||
/* step 4: calculate amount of packets */
|
||||
while (webmSegment != null) {
|
||||
bloq = getNextBlock();
|
||||
|
||||
if (bloq != null && addPacketSegment(bloq)) {
|
||||
final int pos = page.position();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
bloq.data.read(page.array(), pos, bloq.dataSize);
|
||||
page.position(pos + bloq.dataSize);
|
||||
continue;
|
||||
}
|
||||
|
||||
// calculate the current packet duration using the next block
|
||||
double elapsedNs = webmTrack.codecDelay;
|
||||
|
||||
if (bloq == null) {
|
||||
packetFlag = FLAG_LAST; // note: if the flag is FLAG_CONTINUED, is changed
|
||||
elapsedNs += webmBlockLastTimecode;
|
||||
|
||||
if (webmTrack.defaultDuration > 0) {
|
||||
elapsedNs += webmTrack.defaultDuration;
|
||||
} else {
|
||||
// hardcoded way, guess the sample duration
|
||||
elapsedNs += webmBlockNearDuration;
|
||||
}
|
||||
} else {
|
||||
elapsedNs += bloq.absoluteTimeCodeNs;
|
||||
}
|
||||
|
||||
// get the sample count in the page
|
||||
elapsedNs = elapsedNs / TIME_SCALE_NS;
|
||||
elapsedNs = Math.ceil(elapsedNs * resolution);
|
||||
|
||||
// create header and calculate page checksum
|
||||
int checksum = makePacketheader((long) elapsedNs, header, null);
|
||||
checksum = calcCrc32(checksum, page.array(), page.position());
|
||||
|
||||
header.putInt(HEADER_CHECKSUM_OFFSET, checksum);
|
||||
|
||||
// dump data
|
||||
write(header);
|
||||
write(page);
|
||||
|
||||
webmBlock = bloq;
|
||||
}
|
||||
}
|
||||
|
||||
private int makePacketheader(final long granPos, @NonNull final ByteBuffer buffer,
|
||||
final byte[] immediatePage) {
|
||||
short length = HEADER_SIZE;
|
||||
|
||||
buffer.putInt(0x5367674f); // "OggS" binary string in little-endian
|
||||
buffer.put((byte) 0x00); // version
|
||||
buffer.put(packetFlag); // type
|
||||
|
||||
buffer.putLong(granPos); // granulate position
|
||||
|
||||
buffer.putInt(streamId); // bitstream serial number
|
||||
buffer.putInt(sequenceCount++); // page sequence number
|
||||
|
||||
buffer.putInt(0x00); // page checksum
|
||||
|
||||
buffer.put((byte) segmentTableSize); // segment table
|
||||
buffer.put(segmentTable, 0, segmentTableSize); // segment size
|
||||
|
||||
length += segmentTableSize;
|
||||
|
||||
clearSegmentTable(); // clear segment table for next header
|
||||
|
||||
int checksumCrc32 = calcCrc32(0x00, buffer.array(), length);
|
||||
|
||||
if (immediatePage != null) {
|
||||
checksumCrc32 = calcCrc32(checksumCrc32, immediatePage, immediatePage.length);
|
||||
buffer.putInt(HEADER_CHECKSUM_OFFSET, checksumCrc32);
|
||||
segmentTableNextTimestamp -= TIME_SCALE_NS;
|
||||
}
|
||||
|
||||
return checksumCrc32;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private byte[] makeMetadata() {
|
||||
if ("A_OPUS".equals(webmTrack.codecId)) {
|
||||
return new byte[]{
|
||||
0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73, // "OpusTags" binary string
|
||||
0x00, 0x00, 0x00, 0x00, // writing application string size (not present)
|
||||
0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags)
|
||||
};
|
||||
} else if ("A_VORBIS".equals(webmTrack.codecId)) {
|
||||
return new byte[]{
|
||||
0x03, // ¿¿¿???
|
||||
0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, // "vorbis" binary string
|
||||
0x00, 0x00, 0x00, 0x00, // writing application string size (not present)
|
||||
0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags)
|
||||
};
|
||||
}
|
||||
|
||||
// not implemented for the desired codec
|
||||
return null;
|
||||
}
|
||||
|
||||
private void write(final ByteBuffer buffer) throws IOException {
|
||||
output.write(buffer.array(), 0, buffer.position());
|
||||
buffer.position(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private SimpleBlock getNextBlock() throws IOException {
|
||||
SimpleBlock res;
|
||||
|
||||
if (webmBlock != null) {
|
||||
res = webmBlock;
|
||||
webmBlock = null;
|
||||
return res;
|
||||
}
|
||||
|
||||
if (webmSegment == null) {
|
||||
webmSegment = webm.getNextSegment();
|
||||
if (webmSegment == null) {
|
||||
return null; // no more blocks in the selected track
|
||||
}
|
||||
}
|
||||
|
||||
if (webmCluster == null) {
|
||||
webmCluster = webmSegment.getNextCluster();
|
||||
if (webmCluster == null) {
|
||||
webmSegment = null;
|
||||
return getNextBlock();
|
||||
}
|
||||
}
|
||||
|
||||
res = webmCluster.getNextSimpleBlock();
|
||||
if (res == null) {
|
||||
webmCluster = null;
|
||||
return getNextBlock();
|
||||
}
|
||||
|
||||
webmBlockNearDuration = res.absoluteTimeCodeNs - webmBlockLastTimecode;
|
||||
webmBlockLastTimecode = res.absoluteTimeCodeNs;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private float getSampleFrequencyFromTrack(final byte[] bMetadata) {
|
||||
// hardcoded way
|
||||
final ByteBuffer buffer = ByteBuffer.wrap(bMetadata);
|
||||
|
||||
while (buffer.remaining() >= 6) {
|
||||
final int id = buffer.getShort() & 0xFFFF;
|
||||
if (id == 0x0000B584) {
|
||||
return buffer.getFloat();
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
private void clearSegmentTable() {
|
||||
segmentTableNextTimestamp += TIME_SCALE_NS;
|
||||
packetFlag = FLAG_UNSET;
|
||||
segmentTableSize = 0;
|
||||
}
|
||||
|
||||
private boolean addPacketSegment(final SimpleBlock block) {
|
||||
final long timestamp = block.absoluteTimeCodeNs + webmTrack.codecDelay;
|
||||
|
||||
if (timestamp >= segmentTableNextTimestamp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return addPacketSegment(block.dataSize);
|
||||
}
|
||||
|
||||
private boolean addPacketSegment(final int size) {
|
||||
if (size > 65025) {
|
||||
throw new UnsupportedOperationException("page size cannot be larger than 65025");
|
||||
}
|
||||
|
||||
int available = (segmentTable.length - segmentTableSize) * 255;
|
||||
final boolean extra = (size % 255) == 0;
|
||||
|
||||
if (extra) {
|
||||
// add a zero byte entry in the table
|
||||
// required to indicate the sample size is multiple of 255
|
||||
available -= 255;
|
||||
}
|
||||
|
||||
// check if possible add the segment, without overflow the table
|
||||
if (available < size) {
|
||||
return false; // not enough space on the page
|
||||
}
|
||||
|
||||
for (int seg = size; seg > 0; seg -= 255) {
|
||||
segmentTable[segmentTableSize++] = (byte) Math.min(seg, 255);
|
||||
}
|
||||
|
||||
if (extra) {
|
||||
segmentTable[segmentTableSize++] = 0x00;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void populateCrc32Table() {
|
||||
for (int i = 0; i < 0x100; i++) {
|
||||
int crc = i << 24;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
final long b = crc >>> 31;
|
||||
crc <<= 1;
|
||||
crc ^= (int) (0x100000000L - b) & 0x04c11db7;
|
||||
}
|
||||
crc32Table[i] = crc;
|
||||
}
|
||||
}
|
||||
|
||||
private int calcCrc32(final int initialCrc, final byte[] buffer, final int size) {
|
||||
int crc = initialCrc;
|
||||
for (int i = 0; i < size; i++) {
|
||||
final int reg = (crc >>> 24) & 0xff;
|
||||
crc = (crc << 8) ^ crc32Table[reg ^ (buffer[i] & 0xff)];
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
}
|
||||
package org.schabi.newpipe.streams;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.schabi.newpipe.streams.WebMReader.Cluster;
|
||||
import org.schabi.newpipe.streams.WebMReader.Segment;
|
||||
import org.schabi.newpipe.streams.WebMReader.SimpleBlock;
|
||||
import org.schabi.newpipe.streams.WebMReader.WebMTrack;
|
||||
import org.schabi.newpipe.streams.io.SharpStream;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* @author kapodamy
|
||||
*/
|
||||
public class OggFromWebMWriter implements Closeable {
|
||||
private static final byte FLAG_UNSET = 0x00;
|
||||
//private static final byte FLAG_CONTINUED = 0x01;
|
||||
private static final byte FLAG_FIRST = 0x02;
|
||||
private static final byte FLAG_LAST = 0x04;
|
||||
|
||||
private static final byte HEADER_CHECKSUM_OFFSET = 22;
|
||||
private static final byte HEADER_SIZE = 27;
|
||||
|
||||
private static final int TIME_SCALE_NS = 1000000000;
|
||||
|
||||
private boolean done = false;
|
||||
private boolean parsed = false;
|
||||
|
||||
private final SharpStream source;
|
||||
private final SharpStream output;
|
||||
|
||||
private int sequenceCount = 0;
|
||||
private final int streamId;
|
||||
private byte packetFlag = FLAG_FIRST;
|
||||
|
||||
private WebMReader webm = null;
|
||||
private WebMTrack webmTrack = null;
|
||||
private Segment webmSegment = null;
|
||||
private Cluster webmCluster = null;
|
||||
private SimpleBlock webmBlock = null;
|
||||
|
||||
private long webmBlockLastTimecode = 0;
|
||||
private long webmBlockNearDuration = 0;
|
||||
|
||||
private short segmentTableSize = 0;
|
||||
private final byte[] segmentTable = new byte[255];
|
||||
private long segmentTableNextTimestamp = TIME_SCALE_NS;
|
||||
|
||||
private final int[] crc32Table = new int[256];
|
||||
|
||||
public OggFromWebMWriter(@NonNull final SharpStream source, @NonNull final SharpStream target) {
|
||||
if (!source.canRead() || !source.canRewind()) {
|
||||
throw new IllegalArgumentException("source stream must be readable and allows seeking");
|
||||
}
|
||||
if (!target.canWrite() || !target.canRewind()) {
|
||||
throw new IllegalArgumentException("output stream must be writable and allows seeking");
|
||||
}
|
||||
|
||||
this.source = source;
|
||||
this.output = target;
|
||||
|
||||
this.streamId = (int) System.currentTimeMillis();
|
||||
|
||||
populateCrc32Table();
|
||||
}
|
||||
|
||||
public boolean isDone() {
|
||||
return done;
|
||||
}
|
||||
|
||||
public boolean isParsed() {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
public WebMTrack[] getTracksFromSource() throws IllegalStateException {
|
||||
if (!parsed) {
|
||||
throw new IllegalStateException("source must be parsed first");
|
||||
}
|
||||
|
||||
return webm.getAvailableTracks();
|
||||
}
|
||||
|
||||
public void parseSource() throws IOException, IllegalStateException {
|
||||
if (done) {
|
||||
throw new IllegalStateException("already done");
|
||||
}
|
||||
if (parsed) {
|
||||
throw new IllegalStateException("already parsed");
|
||||
}
|
||||
|
||||
try {
|
||||
webm = new WebMReader(source);
|
||||
webm.parse();
|
||||
webmSegment = webm.getNextSegment();
|
||||
} finally {
|
||||
parsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void selectTrack(final int trackIndex) throws IOException {
|
||||
if (!parsed) {
|
||||
throw new IllegalStateException("source must be parsed first");
|
||||
}
|
||||
if (done) {
|
||||
throw new IOException("already done");
|
||||
}
|
||||
if (webmTrack != null) {
|
||||
throw new IOException("tracks already selected");
|
||||
}
|
||||
|
||||
switch (webm.getAvailableTracks()[trackIndex].kind) {
|
||||
case Audio:
|
||||
case Video:
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("the track must an audio or video stream");
|
||||
}
|
||||
|
||||
try {
|
||||
webmTrack = webm.selectTrack(trackIndex);
|
||||
} finally {
|
||||
parsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
done = true;
|
||||
parsed = true;
|
||||
|
||||
webmTrack = null;
|
||||
webm = null;
|
||||
|
||||
if (!output.isClosed()) {
|
||||
output.flush();
|
||||
}
|
||||
|
||||
source.close();
|
||||
output.close();
|
||||
}
|
||||
|
||||
public void build() throws IOException {
|
||||
final float resolution;
|
||||
SimpleBlock bloq;
|
||||
final ByteBuffer header = ByteBuffer.allocate(27 + (255 * 255));
|
||||
final ByteBuffer page = ByteBuffer.allocate(64 * 1024);
|
||||
|
||||
header.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
/* step 1: get the amount of frames per seconds */
|
||||
switch (webmTrack.kind) {
|
||||
case Audio:
|
||||
resolution = getSampleFrequencyFromTrack(webmTrack.bMetadata);
|
||||
if (resolution == 0f) {
|
||||
throw new RuntimeException("cannot get the audio sample rate");
|
||||
}
|
||||
break;
|
||||
case Video:
|
||||
// WARNING: untested
|
||||
if (webmTrack.defaultDuration == 0) {
|
||||
throw new RuntimeException("missing default frame time");
|
||||
}
|
||||
resolution = 1000f / ((float) webmTrack.defaultDuration
|
||||
/ webmSegment.info.timecodeScale);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("not implemented");
|
||||
}
|
||||
|
||||
/* step 2: create packet with code init data */
|
||||
if (webmTrack.codecPrivate != null) {
|
||||
addPacketSegment(webmTrack.codecPrivate.length);
|
||||
makePacketheader(0x00, header, webmTrack.codecPrivate);
|
||||
write(header);
|
||||
output.write(webmTrack.codecPrivate);
|
||||
}
|
||||
|
||||
/* step 3: create packet with metadata */
|
||||
final byte[] buffer = makeMetadata();
|
||||
if (buffer != null) {
|
||||
addPacketSegment(buffer.length);
|
||||
makePacketheader(0x00, header, buffer);
|
||||
write(header);
|
||||
output.write(buffer);
|
||||
}
|
||||
|
||||
/* step 4: calculate amount of packets */
|
||||
while (webmSegment != null) {
|
||||
bloq = getNextBlock();
|
||||
|
||||
if (bloq != null && addPacketSegment(bloq)) {
|
||||
final int pos = page.position();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
bloq.data.read(page.array(), pos, bloq.dataSize);
|
||||
page.position(pos + bloq.dataSize);
|
||||
continue;
|
||||
}
|
||||
|
||||
// calculate the current packet duration using the next block
|
||||
double elapsedNs = webmTrack.codecDelay;
|
||||
|
||||
if (bloq == null) {
|
||||
packetFlag = FLAG_LAST; // note: if the flag is FLAG_CONTINUED, is changed
|
||||
elapsedNs += webmBlockLastTimecode;
|
||||
|
||||
if (webmTrack.defaultDuration > 0) {
|
||||
elapsedNs += webmTrack.defaultDuration;
|
||||
} else {
|
||||
// hardcoded way, guess the sample duration
|
||||
elapsedNs += webmBlockNearDuration;
|
||||
}
|
||||
} else {
|
||||
elapsedNs += bloq.absoluteTimeCodeNs;
|
||||
}
|
||||
|
||||
// get the sample count in the page
|
||||
elapsedNs = elapsedNs / TIME_SCALE_NS;
|
||||
elapsedNs = Math.ceil(elapsedNs * resolution);
|
||||
|
||||
// create header and calculate page checksum
|
||||
int checksum = makePacketheader((long) elapsedNs, header, null);
|
||||
checksum = calcCrc32(checksum, page.array(), page.position());
|
||||
|
||||
header.putInt(HEADER_CHECKSUM_OFFSET, checksum);
|
||||
|
||||
// dump data
|
||||
write(header);
|
||||
write(page);
|
||||
|
||||
webmBlock = bloq;
|
||||
}
|
||||
}
|
||||
|
||||
private int makePacketheader(final long granPos, @NonNull final ByteBuffer buffer,
|
||||
final byte[] immediatePage) {
|
||||
short length = HEADER_SIZE;
|
||||
|
||||
buffer.putInt(0x5367674f); // "OggS" binary string in little-endian
|
||||
buffer.put((byte) 0x00); // version
|
||||
buffer.put(packetFlag); // type
|
||||
|
||||
buffer.putLong(granPos); // granulate position
|
||||
|
||||
buffer.putInt(streamId); // bitstream serial number
|
||||
buffer.putInt(sequenceCount++); // page sequence number
|
||||
|
||||
buffer.putInt(0x00); // page checksum
|
||||
|
||||
buffer.put((byte) segmentTableSize); // segment table
|
||||
buffer.put(segmentTable, 0, segmentTableSize); // segment size
|
||||
|
||||
length += segmentTableSize;
|
||||
|
||||
clearSegmentTable(); // clear segment table for next header
|
||||
|
||||
int checksumCrc32 = calcCrc32(0x00, buffer.array(), length);
|
||||
|
||||
if (immediatePage != null) {
|
||||
checksumCrc32 = calcCrc32(checksumCrc32, immediatePage, immediatePage.length);
|
||||
buffer.putInt(HEADER_CHECKSUM_OFFSET, checksumCrc32);
|
||||
segmentTableNextTimestamp -= TIME_SCALE_NS;
|
||||
}
|
||||
|
||||
return checksumCrc32;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private byte[] makeMetadata() {
|
||||
if ("A_OPUS".equals(webmTrack.codecId)) {
|
||||
return new byte[]{
|
||||
0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73, // "OpusTags" binary string
|
||||
0x00, 0x00, 0x00, 0x00, // writing application string size (not present)
|
||||
0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags)
|
||||
};
|
||||
} else if ("A_VORBIS".equals(webmTrack.codecId)) {
|
||||
return new byte[]{
|
||||
0x03, // ¿¿¿???
|
||||
0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, // "vorbis" binary string
|
||||
0x00, 0x00, 0x00, 0x00, // writing application string size (not present)
|
||||
0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags)
|
||||
};
|
||||
}
|
||||
|
||||
// not implemented for the desired codec
|
||||
return null;
|
||||
}
|
||||
|
||||
private void write(final ByteBuffer buffer) throws IOException {
|
||||
output.write(buffer.array(), 0, buffer.position());
|
||||
buffer.position(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private SimpleBlock getNextBlock() throws IOException {
|
||||
SimpleBlock res;
|
||||
|
||||
if (webmBlock != null) {
|
||||
res = webmBlock;
|
||||
webmBlock = null;
|
||||
return res;
|
||||
}
|
||||
|
||||
if (webmSegment == null) {
|
||||
webmSegment = webm.getNextSegment();
|
||||
if (webmSegment == null) {
|
||||
return null; // no more blocks in the selected track
|
||||
}
|
||||
}
|
||||
|
||||
if (webmCluster == null) {
|
||||
webmCluster = webmSegment.getNextCluster();
|
||||
if (webmCluster == null) {
|
||||
webmSegment = null;
|
||||
return getNextBlock();
|
||||
}
|
||||
}
|
||||
|
||||
res = webmCluster.getNextSimpleBlock();
|
||||
if (res == null) {
|
||||
webmCluster = null;
|
||||
return getNextBlock();
|
||||
}
|
||||
|
||||
webmBlockNearDuration = res.absoluteTimeCodeNs - webmBlockLastTimecode;
|
||||
webmBlockLastTimecode = res.absoluteTimeCodeNs;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private float getSampleFrequencyFromTrack(final byte[] bMetadata) {
|
||||
// hardcoded way
|
||||
final ByteBuffer buffer = ByteBuffer.wrap(bMetadata);
|
||||
|
||||
while (buffer.remaining() >= 6) {
|
||||
final int id = buffer.getShort() & 0xFFFF;
|
||||
if (id == 0x0000B584) {
|
||||
return buffer.getFloat();
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
private void clearSegmentTable() {
|
||||
segmentTableNextTimestamp += TIME_SCALE_NS;
|
||||
packetFlag = FLAG_UNSET;
|
||||
segmentTableSize = 0;
|
||||
}
|
||||
|
||||
private boolean addPacketSegment(final SimpleBlock block) {
|
||||
final long timestamp = block.absoluteTimeCodeNs + webmTrack.codecDelay;
|
||||
|
||||
if (timestamp >= segmentTableNextTimestamp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return addPacketSegment(block.dataSize);
|
||||
}
|
||||
|
||||
private boolean addPacketSegment(final int size) {
|
||||
if (size > 65025) {
|
||||
throw new UnsupportedOperationException("page size cannot be larger than 65025");
|
||||
}
|
||||
|
||||
int available = (segmentTable.length - segmentTableSize) * 255;
|
||||
final boolean extra = (size % 255) == 0;
|
||||
|
||||
if (extra) {
|
||||
// add a zero byte entry in the table
|
||||
// required to indicate the sample size is multiple of 255
|
||||
available -= 255;
|
||||
}
|
||||
|
||||
// check if possible add the segment, without overflow the table
|
||||
if (available < size) {
|
||||
return false; // not enough space on the page
|
||||
}
|
||||
|
||||
for (int seg = size; seg > 0; seg -= 255) {
|
||||
segmentTable[segmentTableSize++] = (byte) Math.min(seg, 255);
|
||||
}
|
||||
|
||||
if (extra) {
|
||||
segmentTable[segmentTableSize++] = 0x00;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void populateCrc32Table() {
|
||||
for (int i = 0; i < 0x100; i++) {
|
||||
int crc = i << 24;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
final long b = crc >>> 31;
|
||||
crc <<= 1;
|
||||
crc ^= (int) (0x100000000L - b) & 0x04c11db7;
|
||||
}
|
||||
crc32Table[i] = crc;
|
||||
}
|
||||
}
|
||||
|
||||
private int calcCrc32(final int initialCrc, final byte[] buffer, final int size) {
|
||||
int crc = initialCrc;
|
||||
for (int i = 0; i < size; i++) {
|
||||
final int reg = (crc >>> 24) & 0xff;
|
||||
crc = (crc << 8) ^ crc32Table[reg ^ (buffer[i] & 0xff)];
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue