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