Skip to content

Commit ec6243d

Browse files
committed
Span metadata about multiple pages if needed
1 parent cb71440 commit ec6243d

1 file changed

Lines changed: 99 additions & 5 deletions

File tree

app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java

Lines changed: 99 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.Base64;
2828
import java.util.List;
2929
import java.util.stream.Collectors;
30+
import java.util.Arrays;
3031

3132
/**
3233
* <p>
@@ -68,7 +69,7 @@ public class OggFromWebMWriter implements Closeable {
6869
/**
6970
* EOS (end of stream).
7071
*/
71-
private static final byte FLAG_LAST = 0x04;;
72+
private static final byte FLAG_LAST = 0x04;
7273

7374
private static final byte HEADER_CHECKSUM_OFFSET = 22;
7475
private static final byte HEADER_SIZE = 27;
@@ -257,10 +258,7 @@ public void build() throws IOException {
257258
/* step 3: create packet with metadata */
258259
final byte[] buffer = makeCommentHeader();
259260
if (buffer != null) {
260-
addPacketSegment(buffer.length);
261-
makePacketHeader(0x00, header, buffer);
262-
write(header);
263-
output.write(buffer);
261+
addPacketSegmentMultiPage(buffer, header);
264262
}
265263

266264
/* step 4: calculate amount of packets */
@@ -626,6 +624,102 @@ private boolean addPacketSegment(final int size) {
626624
return true;
627625
}
628626

627+
/**
628+
* Like {@link #addPacketSegment(SimpleBlock)} for large metadata blobs
629+
* splits the provided data into multiple pages if necessary
630+
* and writes them immediately (header + data).
631+
* This method is intended to be used only for metadata (e.g. large thumbnails).
632+
*
633+
* @param data the metadata to add as a packet segment
634+
* @param header a reusable ByteBuffer for writing page headers; this method will write
635+
* the header for each page as needed
636+
*/
637+
private void addPacketSegmentMultiPage(@NonNull final byte[] data,
638+
@NonNull final ByteBuffer header) throws IOException {
639+
int offset = 0;
640+
boolean first = true;
641+
642+
while (offset < data.length) {
643+
final int remaining = data.length - offset;
644+
final boolean finalChunkCandidate = remaining <= OPUS_MAX_PACKETS_PAGE_SIZE;
645+
final int chunkSize;
646+
if (finalChunkCandidate) {
647+
chunkSize = remaining; // final chunk can be any size
648+
} else {
649+
// For intermediate (non-final) chunks, make the chunk size a multiple
650+
// of OGG_SEGMENT_SIZE so that the last lacing value is 255 and the
651+
// decoder won't treat the packet as finished on that page.
652+
final int maxFullSegments = OPUS_MAX_PACKETS_PAGE_SIZE / OGG_SEGMENT_SIZE;
653+
chunkSize = maxFullSegments * OGG_SEGMENT_SIZE;
654+
}
655+
656+
final boolean isFinalChunk = (offset + chunkSize) >= data.length;
657+
658+
// We must reserve appropriate number of lacing values in the segment table.
659+
// For chunks that are exact multiples of OGG_SEGMENT_SIZE and are the final
660+
// chunk of the packet, a trailing 0 lacing entry is required to indicate
661+
// the packet ends exactly on a segment boundary. For intermediate chunks
662+
// (continued across pages) we MUST NOT write that trailing 0 because then
663+
// the packet would appear complete on that page. Instead intermediate
664+
// chunks should end with only 255-valued lacing entries (no trailing 0).
665+
final int fullSegments = chunkSize / OGG_SEGMENT_SIZE; // may be 0
666+
final int lastSegSize = chunkSize % OGG_SEGMENT_SIZE; // 0..254
667+
final boolean chunkIsMultiple = (lastSegSize == 0);
668+
669+
int requiredEntries = fullSegments + (lastSegSize > 0 ? 1 : 0);
670+
if (chunkIsMultiple && isFinalChunk) {
671+
// need an extra zero entry to mark packet end
672+
requiredEntries += 1;
673+
}
674+
675+
// If the segment table doesn't have enough room, flush the current page
676+
// by writing a header without immediate data. This clears the segment table.
677+
if (requiredEntries > (segmentTable.length - segmentTableSize)) {
678+
// flush current page
679+
int checksum = makePacketHeader(0x00, header, null);
680+
checksum = calcCrc32(checksum, new byte[0], 0);
681+
header.putInt(HEADER_CHECKSUM_OFFSET, checksum);
682+
write(header);
683+
}
684+
685+
// After ensuring space, if still not enough (edge case), throw
686+
if (requiredEntries > (segmentTable.length - segmentTableSize)) {
687+
throw new IOException("Unable to reserve segment table entries for metadata chunk");
688+
}
689+
690+
// Fill the segment table entries for this chunk. For intermediate chunks
691+
// that are an exact multiple of OGG_SEGMENT_SIZE we must NOT append a
692+
// trailing zero entry (that would incorrectly signal packet end).
693+
final int remainingToAssign = chunkSize;
694+
for (int seg = remainingToAssign; seg > 0; seg -= OGG_SEGMENT_SIZE) {
695+
segmentTable[segmentTableSize++] = (byte) Math.min(seg, OGG_SEGMENT_SIZE);
696+
}
697+
698+
if (chunkIsMultiple && isFinalChunk) {
699+
// Only append the zero terminator for a final chunk that has an exact
700+
// multiple of OGG_SEGMENT_SIZE bytes.
701+
segmentTable[segmentTableSize++] = 0x00;
702+
}
703+
704+
// For continuation pages (after the first), mark the page as continued.
705+
if (!first) {
706+
packetFlag = FLAG_CONTINUED;
707+
}
708+
709+
final byte[] chunk = Arrays.copyOfRange(data, offset, offset + chunkSize);
710+
711+
// Now create header (which will consume and clear the segment table) and write
712+
// header + chunk data. makePacketHeader will compute checksum including chunk
713+
// when an immediatePage is provided.
714+
makePacketHeader(0x00, header, chunk);
715+
write(header);
716+
output.write(chunk);
717+
718+
offset += chunkSize;
719+
first = false;
720+
}
721+
}
722+
629723
private void populateCrc32Table() {
630724
for (int i = 0; i < 0x100; i++) {
631725
int crc = i << 24;

0 commit comments

Comments
 (0)