Prepend Ogg ID and Comment Header Pages to offloaded Opus stream
Add Ogg ID Header and Comment Header Pages to the Ogg encapsulated Opus for offload playback. This further matches the RFC 7845 spec and provides initialization data to decoders. PiperOrigin-RevId: 548080222
This commit is contained in:
parent
11e3219e6d
commit
847f6f24d3
@ -75,6 +75,8 @@
|
||||
in offload, then no track will be selected.
|
||||
* Disabling gapless support for offload when pre-API level 33 due to
|
||||
playback position issue after track transition.
|
||||
* Prepend Ogg ID Header and Comment Header Pages to bitstream for
|
||||
offloaded Opus playback in accordance with RFC 7845.
|
||||
* Remove parameter `enableOffload` from
|
||||
`DefaultRenderersFactory.buildAudioSink` method signature.
|
||||
* Remove method `DefaultAudioSink.Builder.setOffloadMode`.
|
||||
|
@ -18,19 +18,37 @@ package androidx.media3.exoplayer.audio;
|
||||
import static androidx.media3.common.audio.AudioProcessor.EMPTY_BUFFER;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.decoder.DecoderInputBuffer;
|
||||
import androidx.media3.extractor.OpusUtil;
|
||||
import com.google.common.primitives.UnsignedBytes;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.List;
|
||||
|
||||
/** A packetizer that encapsulates OPUS audio encodings in OGG packets. */
|
||||
/** A packetizer that encapsulates Opus audio encodings in Ogg packets. */
|
||||
@UnstableApi
|
||||
public final class OggOpusAudioPacketizer {
|
||||
|
||||
private static final int CHECKSUM_INDEX = 22;
|
||||
|
||||
/** ID Header and Comment Header pages are 0 and 1 respectively */
|
||||
private static final int FIRST_AUDIO_SAMPLE_PAGE_SEQUENCE = 2;
|
||||
private static final int FIRST_AUDIO_SAMPLE_PAGE_SEQUENCE_NUMBER = 2;
|
||||
|
||||
private static final int OGG_PACKET_HEADER_LENGTH = 28;
|
||||
private static final int SERIAL_NUMBER = 0;
|
||||
private static final byte[] OGG_DEFAULT_ID_HEADER_PAGE =
|
||||
new byte[] {
|
||||
79, 103, 103, 83, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, -43, -59, -9, 1,
|
||||
19, 79, 112, 117, 115, 72, 101, 97, 100, 1, 2, 56, 1, -128, -69, 0, 0, 0, 0, 0
|
||||
};
|
||||
private static final byte[] OGG_DEFAULT_COMMENT_HEADER_PAGE =
|
||||
new byte[] {
|
||||
79, 103, 103, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 11, -103, 87, 83, 1,
|
||||
16, 79, 112, 117, 115, 84, 97, 103, 115, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
private ByteBuffer outputBuffer;
|
||||
private int pageSequenceNumber;
|
||||
@ -40,7 +58,7 @@ public final class OggOpusAudioPacketizer {
|
||||
public OggOpusAudioPacketizer() {
|
||||
outputBuffer = EMPTY_BUFFER;
|
||||
granulePosition = 0;
|
||||
pageSequenceNumber = FIRST_AUDIO_SAMPLE_PAGE_SEQUENCE;
|
||||
pageSequenceNumber = FIRST_AUDIO_SAMPLE_PAGE_SEQUENCE_NUMBER;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -49,13 +67,23 @@ public final class OggOpusAudioPacketizer {
|
||||
* @param inputBuffer The input buffer to packetize. It must be a direct {@link ByteBuffer} with
|
||||
* LITTLE_ENDIAN order. The contents will be overwritten with the Ogg packet. The caller
|
||||
* retains ownership of the provided buffer.
|
||||
* @param initializationData contains set-up data for the Opus Decoder. The data will be provided
|
||||
* in an Ogg ID Header Page prepended to the bitstream. The list should contain either one or
|
||||
* three byte arrays. The first item is the payload for the Ogg ID Header Page. If three
|
||||
* items, then it also contains the Opus pre-skip and seek pre-roll values in that order.
|
||||
*/
|
||||
public void packetize(DecoderInputBuffer inputBuffer) {
|
||||
public void packetize(DecoderInputBuffer inputBuffer, List<byte[]> initializationData) {
|
||||
checkNotNull(inputBuffer.data);
|
||||
if (inputBuffer.data.limit() - inputBuffer.data.position() == 0) {
|
||||
return;
|
||||
}
|
||||
outputBuffer = packetizeInternal(inputBuffer.data);
|
||||
@Nullable
|
||||
byte[] providedOggIdHeaderPayloadBytes =
|
||||
pageSequenceNumber == FIRST_AUDIO_SAMPLE_PAGE_SEQUENCE_NUMBER
|
||||
&& (initializationData.size() == 1 || initializationData.size() == 3)
|
||||
? initializationData.get(0)
|
||||
: null;
|
||||
outputBuffer = packetizeInternal(inputBuffer.data, providedOggIdHeaderPayloadBytes);
|
||||
inputBuffer.clear();
|
||||
inputBuffer.ensureSpaceForWrite(outputBuffer.remaining());
|
||||
inputBuffer.data.put(outputBuffer);
|
||||
@ -66,16 +94,24 @@ public final class OggOpusAudioPacketizer {
|
||||
public void reset() {
|
||||
outputBuffer = EMPTY_BUFFER;
|
||||
granulePosition = 0;
|
||||
pageSequenceNumber = FIRST_AUDIO_SAMPLE_PAGE_SEQUENCE;
|
||||
pageSequenceNumber = FIRST_AUDIO_SAMPLE_PAGE_SEQUENCE_NUMBER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill outputBuffer with an Ogg packet encapsulating the inputBuffer.
|
||||
*
|
||||
* @param inputBuffer contains Opus to wrap in Ogg packet
|
||||
* <p>If {@code providedOggIdHeaderPayloadBytes} is {@code null} and {@link #pageSequenceNumber}
|
||||
* is {@link #FIRST_AUDIO_SAMPLE_PAGE_SEQUENCE_NUMBER}, then {@link #OGG_DEFAULT_ID_HEADER_PAGE}
|
||||
* will be prepended to the Ogg Opus Audio packets for the Ogg ID Header Page.
|
||||
*
|
||||
* @param inputBuffer contains Opus to wrap in Ogg packet.
|
||||
* @param providedOggIdHeaderPayloadBytes containing the Ogg ID Header Page payload. Expected to
|
||||
* be {@code null} if {@link #pageSequenceNumber} is not {@link
|
||||
* #FIRST_AUDIO_SAMPLE_PAGE_SEQUENCE_NUMBER}.
|
||||
* @return {@link ByteBuffer} containing Ogg packet
|
||||
*/
|
||||
private ByteBuffer packetizeInternal(ByteBuffer inputBuffer) {
|
||||
private ByteBuffer packetizeInternal(
|
||||
ByteBuffer inputBuffer, @Nullable byte[] providedOggIdHeaderPayloadBytes) {
|
||||
int position = inputBuffer.position();
|
||||
int limit = inputBuffer.limit();
|
||||
int inputBufferSize = limit - position;
|
||||
@ -86,38 +122,37 @@ public final class OggOpusAudioPacketizer {
|
||||
|
||||
int outputPacketSize = headerSize + inputBufferSize;
|
||||
|
||||
// If first audio sample in stream, then the packetizer will add Ogg ID Header and Comment
|
||||
// Header Pages. Include additional page lengths in buffer size calculation.
|
||||
int oggIdHeaderPageSize = 0;
|
||||
if (pageSequenceNumber == FIRST_AUDIO_SAMPLE_PAGE_SEQUENCE_NUMBER) {
|
||||
oggIdHeaderPageSize =
|
||||
providedOggIdHeaderPayloadBytes != null
|
||||
? OGG_PACKET_HEADER_LENGTH + providedOggIdHeaderPayloadBytes.length
|
||||
: OGG_DEFAULT_ID_HEADER_PAGE.length;
|
||||
outputPacketSize += oggIdHeaderPageSize + OGG_DEFAULT_COMMENT_HEADER_PAGE.length;
|
||||
}
|
||||
|
||||
// Resample the little endian input and update the output buffers.
|
||||
ByteBuffer buffer = replaceOutputBuffer(outputPacketSize);
|
||||
|
||||
// Capture Pattern for Page [OggS]
|
||||
buffer.put((byte) 'O');
|
||||
buffer.put((byte) 'g');
|
||||
buffer.put((byte) 'g');
|
||||
buffer.put((byte) 'S');
|
||||
|
||||
// StreamStructure Version
|
||||
buffer.put((byte) 0);
|
||||
|
||||
// header_type_flag
|
||||
buffer.put((byte) 0x00);
|
||||
// If first audio sample in stream then insert Ogg ID Header and Comment Header Pages
|
||||
if (pageSequenceNumber == FIRST_AUDIO_SAMPLE_PAGE_SEQUENCE_NUMBER) {
|
||||
if (providedOggIdHeaderPayloadBytes != null) {
|
||||
writeOggIdHeaderPage(buffer, /* idHeaderPayloadBytes= */ providedOggIdHeaderPayloadBytes);
|
||||
} else {
|
||||
// Write default Ogg ID Header Payload
|
||||
buffer.put(OGG_DEFAULT_ID_HEADER_PAGE);
|
||||
}
|
||||
buffer.put(OGG_DEFAULT_COMMENT_HEADER_PAGE);
|
||||
}
|
||||
|
||||
// granule_position
|
||||
int numSamples = OpusUtil.parsePacketAudioSampleCount(inputBuffer);
|
||||
granulePosition += numSamples;
|
||||
buffer.putLong(granulePosition);
|
||||
|
||||
// bitstream_serial_number
|
||||
buffer.putInt(0);
|
||||
|
||||
// page_sequence_number
|
||||
buffer.putInt(pageSequenceNumber);
|
||||
pageSequenceNumber++;
|
||||
|
||||
// CRC_checksum
|
||||
buffer.putInt(0);
|
||||
|
||||
// number_page_segments
|
||||
buffer.put((byte) numSegments);
|
||||
writeOggPacketHeader(
|
||||
buffer, granulePosition, pageSequenceNumber, numSegments, /* isIdHeaderPacket= */ false);
|
||||
|
||||
// Segment_table
|
||||
int bytesLeft = inputBufferSize;
|
||||
@ -131,6 +166,7 @@ public final class OggOpusAudioPacketizer {
|
||||
}
|
||||
}
|
||||
|
||||
// Write Opus audio data
|
||||
for (int i = position; i < limit; i++) {
|
||||
buffer.put(inputBuffer.get(i));
|
||||
}
|
||||
@ -138,16 +174,103 @@ public final class OggOpusAudioPacketizer {
|
||||
inputBuffer.position(inputBuffer.limit());
|
||||
buffer.flip();
|
||||
|
||||
int checksum;
|
||||
if (pageSequenceNumber == FIRST_AUDIO_SAMPLE_PAGE_SEQUENCE_NUMBER) {
|
||||
checksum =
|
||||
Util.crc32(
|
||||
buffer.array(),
|
||||
/* start= */ buffer.arrayOffset()
|
||||
+ oggIdHeaderPageSize
|
||||
+ OGG_DEFAULT_COMMENT_HEADER_PAGE.length,
|
||||
/* end= */ buffer.limit() - buffer.position(),
|
||||
/* initialValue= */ 0);
|
||||
buffer.putInt(
|
||||
oggIdHeaderPageSize + OGG_DEFAULT_COMMENT_HEADER_PAGE.length + CHECKSUM_INDEX, checksum);
|
||||
} else {
|
||||
checksum =
|
||||
Util.crc32(
|
||||
buffer.array(),
|
||||
/* start= */ buffer.arrayOffset(),
|
||||
/* end= */ buffer.limit() - buffer.position(),
|
||||
/* initialValue= */ 0);
|
||||
buffer.putInt(CHECKSUM_INDEX, checksum);
|
||||
}
|
||||
|
||||
// Increase pageSequenceNumber for next packet
|
||||
pageSequenceNumber++;
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write Ogg ID Header Page packet to {@link ByteBuffer}.
|
||||
*
|
||||
* @param buffer to write into.
|
||||
* @param idHeaderPayloadBytes containing the Ogg ID Header Page payload.
|
||||
*/
|
||||
private void writeOggIdHeaderPage(ByteBuffer buffer, byte[] idHeaderPayloadBytes) {
|
||||
// TODO(b/290195621): Use starting position to calculate correct 'pre-skip' value
|
||||
writeOggPacketHeader(
|
||||
buffer,
|
||||
/* granulePosition= */ 0,
|
||||
/* pageSequenceNumber= */ 0,
|
||||
/* numberPageSegments= */ 1,
|
||||
/* isIdHeaderPacket= */ true);
|
||||
buffer.put(UnsignedBytes.checkedCast(idHeaderPayloadBytes.length));
|
||||
buffer.put(idHeaderPayloadBytes);
|
||||
int checksum =
|
||||
Util.crc32(
|
||||
buffer.array(),
|
||||
buffer.arrayOffset(),
|
||||
buffer.limit() - buffer.position(),
|
||||
/* start= */ buffer.arrayOffset(),
|
||||
/* end= */ OGG_PACKET_HEADER_LENGTH + idHeaderPayloadBytes.length,
|
||||
/* initialValue= */ 0);
|
||||
buffer.putInt(22, checksum);
|
||||
buffer.position(0);
|
||||
buffer.putInt(/* index= */ CHECKSUM_INDEX, checksum);
|
||||
buffer.position(OGG_PACKET_HEADER_LENGTH + idHeaderPayloadBytes.length);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
/**
|
||||
* Write header for an Ogg Page Packet to {@link ByteBuffer}.
|
||||
*
|
||||
* @param byteBuffer to write unto.
|
||||
* @param granulePosition is the number of audio samples in the stream up to and including this
|
||||
* packet.
|
||||
* @param pageSequenceNumber of the page this header is for.
|
||||
* @param numberPageSegments the data of this Ogg page will span.
|
||||
* @param isIdHeaderPacket where if this header is start of the bitstream.
|
||||
*/
|
||||
private void writeOggPacketHeader(
|
||||
ByteBuffer byteBuffer,
|
||||
long granulePosition,
|
||||
int pageSequenceNumber,
|
||||
int numberPageSegments,
|
||||
boolean isIdHeaderPacket) {
|
||||
// Capture Pattern for Ogg Page [OggS]
|
||||
byteBuffer.put((byte) 'O');
|
||||
byteBuffer.put((byte) 'g');
|
||||
byteBuffer.put((byte) 'g');
|
||||
byteBuffer.put((byte) 'S');
|
||||
|
||||
// StreamStructure Version
|
||||
byteBuffer.put((byte) 0);
|
||||
|
||||
// Header-type
|
||||
byteBuffer.put(isIdHeaderPacket ? (byte) 0x02 : (byte) 0x00);
|
||||
|
||||
// Granule_position
|
||||
byteBuffer.putLong(granulePosition);
|
||||
|
||||
// bitstream_serial_number
|
||||
byteBuffer.putInt(SERIAL_NUMBER);
|
||||
|
||||
// Page_sequence_number
|
||||
byteBuffer.putInt(pageSequenceNumber);
|
||||
|
||||
// CRC_checksum
|
||||
// Will be overwritten with calculated checksum after rest of page is written to buffer.
|
||||
byteBuffer.putInt(0);
|
||||
|
||||
// Number_page_segments
|
||||
byteBuffer.put(UnsignedBytes.checkedCast(numberPageSegments));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -720,6 +720,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||
bypassBatchBuffer.clear();
|
||||
bypassSampleBuffer.clear();
|
||||
bypassSampleBufferPending = false;
|
||||
oggOpusAudioPacketizer.reset();
|
||||
} else {
|
||||
flushOrReinitializeCodec();
|
||||
}
|
||||
@ -2349,7 +2350,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||
if (inputFormat != null
|
||||
&& inputFormat.sampleMimeType != null
|
||||
&& inputFormat.sampleMimeType.equals(MimeTypes.AUDIO_OPUS)) {
|
||||
oggOpusAudioPacketizer.packetize(bypassSampleBuffer);
|
||||
oggOpusAudioPacketizer.packetize(bypassSampleBuffer, inputFormat.initializationData);
|
||||
}
|
||||
if (!bypassBatchBuffer.append(bypassSampleBuffer)) {
|
||||
bypassSampleBufferPending = true;
|
||||
|
@ -76,15 +76,45 @@ public class OpusUtil {
|
||||
*/
|
||||
public static int parseOggPacketAudioSampleCount(ByteBuffer buffer) {
|
||||
// RFC 3433 section 6 - The Ogg page format.
|
||||
int numPageSegments = buffer.get(/* index= */ 26);
|
||||
int indexFirstOpusPacket = 27 + numPageSegments; // Skip Ogg header and segment table.
|
||||
int preAudioPacketByteCount = parseOggPacketForPreAudioSampleByteCount(buffer);
|
||||
int numPageSegments = buffer.get(/* index= */ 26 + preAudioPacketByteCount);
|
||||
// Skip Ogg header + segment table.
|
||||
int indexFirstOpusPacket = 27 + numPageSegments + preAudioPacketByteCount;
|
||||
long packetDurationUs =
|
||||
getPacketDurationUs(
|
||||
buffer.get(indexFirstOpusPacket),
|
||||
buffer.limit() > 1 ? buffer.get(indexFirstOpusPacket + 1) : 0);
|
||||
buffer.limit() - indexFirstOpusPacket > 1 ? buffer.get(indexFirstOpusPacket + 1) : 0);
|
||||
return (int) (packetDurationUs * SAMPLE_RATE / C.MICROS_PER_SECOND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the offset from the start of the buffer to audio sample Ogg packets.
|
||||
*
|
||||
* @param buffer containing the Ogg Encapsulated Opus audio bitstream.
|
||||
* @return the offset before the Ogg packet containing audio samples.
|
||||
*/
|
||||
public static int parseOggPacketForPreAudioSampleByteCount(ByteBuffer buffer) {
|
||||
// Parse Ogg Packet Type from Header at index 5
|
||||
if ((buffer.get(/* index= */ 5) & 0x02) == 0) {
|
||||
// Ogg Page packet header type is not beginning of logical stream. Must be an Audio page.
|
||||
return 0;
|
||||
}
|
||||
// ID Header Page size is Ogg packet header size + sum(lacing values: 1..number_page_segments).
|
||||
int idHeaderPageSize = 28;
|
||||
int idHeaderPageNumOfSegments = buffer.get(/* index= */ 26);
|
||||
for (int i = 0; i < idHeaderPageNumOfSegments; i++) {
|
||||
idHeaderPageSize += buffer.get(/* index= */ 27 + i);
|
||||
}
|
||||
// Comment Header Page size is Ogg packet header size + sum(lacing values:
|
||||
// 1..number_page_segments).
|
||||
int commentHeaderPageSize = 28;
|
||||
int commentHeaderPageSizeNumOfSegments = buffer.get(/* index= */ idHeaderPageSize + 26);
|
||||
for (int i = 0; i < commentHeaderPageSizeNumOfSegments; i++) {
|
||||
commentHeaderPageSize += buffer.get(/* index= */ idHeaderPageSize + 27 + i);
|
||||
}
|
||||
return idHeaderPageSize + commentHeaderPageSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of audio samples in the given audio packet.
|
||||
*
|
||||
|
@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.media3.common.C;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.List;
|
||||
@ -30,34 +31,140 @@ import org.junit.runner.RunWith;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class OpusUtilTest {
|
||||
|
||||
private static final byte[] HEADER =
|
||||
new byte[] {79, 112, 117, 115, 72, 101, 97, 100, 0, 2, 1, 56, 0, 0, -69, -128, 0, 0, 0};
|
||||
/** Ogg Packet Header in accordance with RFC 3533 for an Ogg ID Header Page. */
|
||||
private static final byte[] OGG_ID_HEADER_PACKET_HEADER =
|
||||
new byte[] {
|
||||
79, 103, 103, 83, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, -43, -59, -9, 1,
|
||||
19
|
||||
};
|
||||
|
||||
private static final int HEADER_PRE_SKIP_SAMPLES = 14337;
|
||||
private static final byte[] HEADER_PRE_SKIP_BYTES =
|
||||
buildNativeOrderByteArray(sampleCountToNanoseconds(HEADER_PRE_SKIP_SAMPLES));
|
||||
/** Payload for Ogg ID Header Page in accordance with RFC 7845. */
|
||||
private static final byte[] OGG_ID_HEADER_PAYLOAD =
|
||||
new byte[] {79, 112, 117, 115, 72, 101, 97, 100, 1, 2, 56, 1, -128, -69, 0, 0, 0, 0, 0};
|
||||
|
||||
private static final int OGG_ID_HEADER_PRE_SKIP_SAMPLES = 312;
|
||||
private static final byte[] OGG_ID_HEADER_PRE_SKIP_BYTES =
|
||||
buildNativeOrderByteArray(sampleCountToNanoseconds(OGG_ID_HEADER_PRE_SKIP_SAMPLES));
|
||||
private static final int DEFAULT_SEEK_PRE_ROLL_SAMPLES = 3840;
|
||||
private static final byte[] DEFAULT_SEEK_PRE_ROLL_BYTES =
|
||||
buildNativeOrderByteArray(sampleCountToNanoseconds(DEFAULT_SEEK_PRE_ROLL_SAMPLES));
|
||||
|
||||
/** Ogg Packet Header in accordance with RFC 3533 for an Ogg Comment Header Page. */
|
||||
private static final byte[] OGG_COMMENT_HEADER_PACKET_HEADER =
|
||||
new byte[] {
|
||||
79, 103, 103, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 11, -103, 87, 83, 1,
|
||||
16
|
||||
};
|
||||
|
||||
/** Payload for Ogg Comment Header Page with empty vendor and comment sections. */
|
||||
private static final byte[] OGG_COMMENT_HEADER_PACKET_PAYLOAD =
|
||||
new byte[] {79, 112, 117, 115, 84, 97, 103, 115, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
/** Ogg Packet Header for an page of an Opus audio sample contained in a single segment. */
|
||||
private static final byte[] OGG_OPUS_PACKET_HEADER_SINGLE_SEGMENT =
|
||||
new byte[] {
|
||||
79, 103, 103, 83, 0, 0, -32, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 54, -31, -124, 22,
|
||||
1, -22
|
||||
};
|
||||
|
||||
/** Ogg Packet Header for an page of an Opus audio sample that takes up multiple segments. */
|
||||
private static final byte[] OGG_OPUS_PACKET_HEADER_MULTIPLE_SEGMENTS =
|
||||
new byte[] {
|
||||
79, 103, 103, 83, 0, 0, -32, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 54, -31, -124, 22,
|
||||
2, -1, -22
|
||||
};
|
||||
|
||||
private static final byte[] OGG_OPUS_PACKET_PAYLOAD_CODE_ZERO_TOC = getBytesFromHexString("04");
|
||||
private static final byte[] OGG_OPUS_PACKET_PAYLOAD_CODE_THREE_TOC =
|
||||
getBytesFromHexString("078C");
|
||||
|
||||
@Test
|
||||
public void buildInitializationData_returnsExpectedHeaderWithPreSkipAndPreRoll() {
|
||||
List<byte[]> initializationData = OpusUtil.buildInitializationData(HEADER);
|
||||
List<byte[]> initializationData = OpusUtil.buildInitializationData(OGG_ID_HEADER_PAYLOAD);
|
||||
|
||||
assertThat(initializationData).hasSize(3);
|
||||
assertThat(initializationData.get(0)).isEqualTo(HEADER);
|
||||
assertThat(initializationData.get(1)).isEqualTo(HEADER_PRE_SKIP_BYTES);
|
||||
assertThat(initializationData.get(0)).isEqualTo(OGG_ID_HEADER_PAYLOAD);
|
||||
assertThat(initializationData.get(1)).isEqualTo(OGG_ID_HEADER_PRE_SKIP_BYTES);
|
||||
assertThat(initializationData.get(2)).isEqualTo(DEFAULT_SEEK_PRE_ROLL_BYTES);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getChannelCount_returnsChannelCount() {
|
||||
int channelCount = OpusUtil.getChannelCount(HEADER);
|
||||
int channelCount = OpusUtil.getChannelCount(OGG_ID_HEADER_PAYLOAD);
|
||||
|
||||
assertThat(channelCount).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseOggPacketForPreAudioSampleByteCount_returnsExpectedByteCount() {
|
||||
byte[] packetData =
|
||||
Bytes.concat(
|
||||
OGG_ID_HEADER_PACKET_HEADER,
|
||||
OGG_ID_HEADER_PAYLOAD,
|
||||
OGG_COMMENT_HEADER_PACKET_HEADER,
|
||||
OGG_COMMENT_HEADER_PACKET_PAYLOAD,
|
||||
OGG_OPUS_PACKET_HEADER_SINGLE_SEGMENT,
|
||||
OGG_OPUS_PACKET_PAYLOAD_CODE_ZERO_TOC);
|
||||
ByteBuffer preAudioOggPacketsByteBuffer = ByteBuffer.wrap(packetData);
|
||||
|
||||
int preAudioSampleByteCount =
|
||||
OpusUtil.parseOggPacketForPreAudioSampleByteCount(preAudioOggPacketsByteBuffer);
|
||||
|
||||
assertThat(preAudioSampleByteCount).isEqualTo(91);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseOggPacketAudioSampleCount_withCodeZeroToc_returnsExpectedAudioSampleCount() {
|
||||
byte[] packetData =
|
||||
Bytes.concat(
|
||||
OGG_ID_HEADER_PACKET_HEADER,
|
||||
OGG_ID_HEADER_PAYLOAD,
|
||||
OGG_COMMENT_HEADER_PACKET_HEADER,
|
||||
OGG_COMMENT_HEADER_PACKET_PAYLOAD,
|
||||
OGG_OPUS_PACKET_HEADER_SINGLE_SEGMENT,
|
||||
OGG_OPUS_PACKET_PAYLOAD_CODE_ZERO_TOC);
|
||||
ByteBuffer oggPacketsByteBuffer = ByteBuffer.wrap(packetData);
|
||||
|
||||
int audioSampleCount = OpusUtil.parseOggPacketAudioSampleCount(oggPacketsByteBuffer);
|
||||
|
||||
assertThat(audioSampleCount).isEqualTo(480);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
parseOggPacketAudioSampleCount_withMultipleOggPageSegments_returnsExpectedAudioSampleCount() {
|
||||
byte[] packetData =
|
||||
Bytes.concat(
|
||||
OGG_ID_HEADER_PACKET_HEADER,
|
||||
OGG_ID_HEADER_PAYLOAD,
|
||||
OGG_COMMENT_HEADER_PACKET_HEADER,
|
||||
OGG_COMMENT_HEADER_PACKET_PAYLOAD,
|
||||
OGG_OPUS_PACKET_HEADER_MULTIPLE_SEGMENTS,
|
||||
OGG_OPUS_PACKET_PAYLOAD_CODE_ZERO_TOC);
|
||||
ByteBuffer oggPacketsByteBuffer = ByteBuffer.wrap(packetData);
|
||||
|
||||
int audioSampleCount = OpusUtil.parseOggPacketAudioSampleCount(oggPacketsByteBuffer);
|
||||
|
||||
assertThat(audioSampleCount).isEqualTo(480);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseOggPacketAudioSampleCount_withCodeThreeToc_returnsExpectedAudioSampleCount() {
|
||||
byte[] packetData =
|
||||
Bytes.concat(
|
||||
OGG_ID_HEADER_PACKET_HEADER,
|
||||
OGG_ID_HEADER_PAYLOAD,
|
||||
OGG_COMMENT_HEADER_PACKET_HEADER,
|
||||
OGG_COMMENT_HEADER_PACKET_PAYLOAD,
|
||||
OGG_OPUS_PACKET_HEADER_SINGLE_SEGMENT,
|
||||
OGG_OPUS_PACKET_PAYLOAD_CODE_THREE_TOC);
|
||||
ByteBuffer oggPacketsByteBuffer = ByteBuffer.wrap(packetData);
|
||||
|
||||
int audioSampleCount = OpusUtil.parseOggPacketAudioSampleCount(oggPacketsByteBuffer);
|
||||
|
||||
assertThat(audioSampleCount).isEqualTo(5760);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPacketDurationUs_code0_returnsExpectedDuration() {
|
||||
long config0DurationUs = OpusUtil.getPacketDurationUs(getBytesFromHexString("04"));
|
||||
|
@ -1,6 +1,6 @@
|
||||
SinkDump (OggOpus):
|
||||
buffers.length = 9
|
||||
buffers[0] = length 4046, hash 68FA8318
|
||||
buffers[0] = length 4137, hash 9776A1C3
|
||||
buffers[1] = length 3848, hash B3105060
|
||||
buffers[2] = length 3747, hash 63B6648B
|
||||
buffers[3] = length 3752, hash B5C28B9D
|
||||
|
Loading…
x
Reference in New Issue
Block a user