mirror of
https://github.com/androidx/media.git
synced 2025-05-10 00:59:51 +08:00
webm_extractor: Add support for parsing BlockGroup element
This CL adds support for parsing BlockGroup elements for all codecs (not just opus). It also adds a test to verify the new behavior.
This commit is contained in:
parent
99304eb44e
commit
7ad55dbf2c
@ -82,6 +82,7 @@ public final class WebmExtractor implements Extractor {
|
|||||||
private static final int ID_SIMPLE_BLOCK = 0xA3;
|
private static final int ID_SIMPLE_BLOCK = 0xA3;
|
||||||
private static final int ID_BLOCK_GROUP = 0xA0;
|
private static final int ID_BLOCK_GROUP = 0xA0;
|
||||||
private static final int ID_BLOCK = 0xA1;
|
private static final int ID_BLOCK = 0xA1;
|
||||||
|
private static final int ID_REFERENCE_BLOCK = 0xFB;
|
||||||
private static final int ID_TRACKS = 0x1654AE6B;
|
private static final int ID_TRACKS = 0x1654AE6B;
|
||||||
private static final int ID_TRACK_ENTRY = 0xAE;
|
private static final int ID_TRACK_ENTRY = 0xAE;
|
||||||
private static final int ID_TRACK_NUMBER = 0xD7;
|
private static final int ID_TRACK_NUMBER = 0xD7;
|
||||||
@ -152,6 +153,7 @@ public final class WebmExtractor implements Extractor {
|
|||||||
private int sampleFlags;
|
private int sampleFlags;
|
||||||
private long sampleTimeUs;
|
private long sampleTimeUs;
|
||||||
private boolean sampleRead;
|
private boolean sampleRead;
|
||||||
|
private boolean sampleSeenReferenceBlock;
|
||||||
|
|
||||||
// Extractor outputs.
|
// Extractor outputs.
|
||||||
private ExtractorOutput extractorOutput;
|
private ExtractorOutput extractorOutput;
|
||||||
@ -236,6 +238,7 @@ public final class WebmExtractor implements Extractor {
|
|||||||
case ID_CONTENT_ENCRYPTION_AES_SETTINGS_CIPHER_MODE:
|
case ID_CONTENT_ENCRYPTION_AES_SETTINGS_CIPHER_MODE:
|
||||||
case ID_CUE_TIME:
|
case ID_CUE_TIME:
|
||||||
case ID_CUE_CLUSTER_POSITION:
|
case ID_CUE_CLUSTER_POSITION:
|
||||||
|
case ID_REFERENCE_BLOCK:
|
||||||
return EbmlReader.TYPE_UNSIGNED_INT;
|
return EbmlReader.TYPE_UNSIGNED_INT;
|
||||||
case ID_DOC_TYPE:
|
case ID_DOC_TYPE:
|
||||||
case ID_CODEC_ID:
|
case ID_CODEC_ID:
|
||||||
@ -282,6 +285,9 @@ public final class WebmExtractor implements Extractor {
|
|||||||
seekForCues = true;
|
seekForCues = true;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
case ID_BLOCK_GROUP:
|
||||||
|
sampleSeenReferenceBlock = false;
|
||||||
|
return;
|
||||||
case ID_CONTENT_ENCODING:
|
case ID_CONTENT_ENCODING:
|
||||||
// TODO: check and fail if more than one content encoding is present.
|
// TODO: check and fail if more than one content encoding is present.
|
||||||
return;
|
return;
|
||||||
@ -314,6 +320,19 @@ public final class WebmExtractor implements Extractor {
|
|||||||
// We have already built the cues. Ignore.
|
// We have already built the cues. Ignore.
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
case ID_BLOCK_GROUP:
|
||||||
|
if (sampleState != SAMPLE_STATE_DATA) {
|
||||||
|
// We've skipped this sample (due to incompatible track number).
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If the ReferenceBlock element was not found for this sample, then it is a keyframe.
|
||||||
|
if (!sampleSeenReferenceBlock) {
|
||||||
|
sampleFlags |= C.SAMPLE_FLAG_SYNC;
|
||||||
|
}
|
||||||
|
outputSampleMetadata(
|
||||||
|
(audioTrackFormat != null && sampleTrackNumber == audioTrackFormat.number)
|
||||||
|
? audioTrackFormat.trackOutput : videoTrackFormat.trackOutput);
|
||||||
|
return;
|
||||||
case ID_CONTENT_ENCODING:
|
case ID_CONTENT_ENCODING:
|
||||||
if (!trackFormat.hasContentEncryption) {
|
if (!trackFormat.hasContentEncryption) {
|
||||||
// We found a ContentEncoding other than Encryption.
|
// We found a ContentEncoding other than Encryption.
|
||||||
@ -405,6 +424,9 @@ public final class WebmExtractor implements Extractor {
|
|||||||
case ID_CHANNELS:
|
case ID_CHANNELS:
|
||||||
trackFormat.channelCount = (int) value;
|
trackFormat.channelCount = (int) value;
|
||||||
return;
|
return;
|
||||||
|
case ID_REFERENCE_BLOCK:
|
||||||
|
sampleSeenReferenceBlock = true;
|
||||||
|
return;
|
||||||
case ID_CONTENT_ENCODING_ORDER:
|
case ID_CONTENT_ENCODING_ORDER:
|
||||||
// This extractor only supports one ContentEncoding element and hence the order has to be 0.
|
// This extractor only supports one ContentEncoding element and hence the order has to be 0.
|
||||||
if (value != 0) {
|
if (value != 0) {
|
||||||
@ -551,16 +573,8 @@ public final class WebmExtractor implements Extractor {
|
|||||||
throw new ParserException("Lacing mode not supported: " + lacing);
|
throw new ParserException("Lacing mode not supported: " + lacing);
|
||||||
}
|
}
|
||||||
boolean isInvisible = (sampleHeaderScratchData[2] & 0x08) == 0x08;
|
boolean isInvisible = (sampleHeaderScratchData[2] & 0x08) == 0x08;
|
||||||
boolean isKeyframe;
|
boolean isKeyframe =
|
||||||
if (id == ID_BLOCK) {
|
(id == ID_SIMPLE_BLOCK && (sampleHeaderScratchData[2] & 0x80) == 0x80);
|
||||||
// Matroska Block element does not self-sufficiently say whether it is a keyframe. It
|
|
||||||
// depends on the existence of another element (ReferenceBlock) which may occur after
|
|
||||||
// the Block element. Since this extractor uses Block element only for Opus, we set the
|
|
||||||
// keyframe to be true always since all Opus frames are key frames.
|
|
||||||
isKeyframe = true;
|
|
||||||
} else {
|
|
||||||
isKeyframe = (sampleHeaderScratchData[2] & 0x80) == 0x80;
|
|
||||||
}
|
|
||||||
boolean isEncrypted = false;
|
boolean isEncrypted = false;
|
||||||
|
|
||||||
// If encrypted, the fourth byte is an encryption signal byte.
|
// If encrypted, the fourth byte is an encryption signal byte.
|
||||||
@ -601,15 +615,24 @@ public final class WebmExtractor implements Extractor {
|
|||||||
sampleSize += 4;
|
sampleSize += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
trackOutput.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, null);
|
// For SimpleBlock, we send the metadata here as we have all the information. For Block, we
|
||||||
sampleState = SAMPLE_STATE_START;
|
// send the metadata at the end of the BlockGroup element since we'll know if the frame is a
|
||||||
sampleRead = true;
|
// keyframe or not only at that point.
|
||||||
|
if (id == ID_SIMPLE_BLOCK) {
|
||||||
|
outputSampleMetadata(trackOutput);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException("Unexpected id: " + id);
|
throw new IllegalStateException("Unexpected id: " + id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void outputSampleMetadata(TrackOutput trackOutput) {
|
||||||
|
trackOutput.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, null);
|
||||||
|
sampleState = SAMPLE_STATE_START;
|
||||||
|
sampleRead = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds an video {@link MediaFormat} containing recently gathered Video information.
|
* Builds an video {@link MediaFormat} containing recently gathered Video information.
|
||||||
*
|
*
|
||||||
|
@ -334,6 +334,19 @@ public class WebmExtractorTest extends InstrumentationTestCase {
|
|||||||
assertSample(mediaSegment, 0, true, false, false, audioOutput);
|
assertSample(mediaSegment, 0, true, false, false, audioOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testReadBlockNonKeyframe() throws IOException, InterruptedException {
|
||||||
|
MediaSegment mediaSegment =
|
||||||
|
createMediaSegment(100, 0, 0, false, false, false, false, false, 1);
|
||||||
|
byte[] testInputData = joinByteArrays(
|
||||||
|
createInitializationSegment(
|
||||||
|
1, mediaSegment.clusterBytes.length, true, DEFAULT_TIMECODE_SCALE,
|
||||||
|
new int[] { ID_VP9 }, null),
|
||||||
|
mediaSegment.clusterBytes);
|
||||||
|
consume(testInputData);
|
||||||
|
assertVideoFormat();
|
||||||
|
assertSample(mediaSegment, 0, false, false, false, videoOutput);
|
||||||
|
}
|
||||||
|
|
||||||
public void testReadEncryptedFrame() throws IOException, InterruptedException {
|
public void testReadEncryptedFrame() throws IOException, InterruptedException {
|
||||||
MediaSegment mediaSegment = createMediaSegment(100, 0, 0, true, false, true, true, true, 1);
|
MediaSegment mediaSegment = createMediaSegment(100, 0, 0, true, false, true, true, true, 1);
|
||||||
ContentEncodingSettings settings = new ContentEncodingSettings(0, 1, 1, 5, 1);
|
ContentEncodingSettings settings = new ContentEncodingSettings(0, 1, 1, 5, 1);
|
||||||
@ -466,7 +479,6 @@ public class WebmExtractorTest extends InstrumentationTestCase {
|
|||||||
assertEquals(keyframe, (output.sampleFlags & C.SAMPLE_FLAG_SYNC) != 0);
|
assertEquals(keyframe, (output.sampleFlags & C.SAMPLE_FLAG_SYNC) != 0);
|
||||||
assertEquals(invisible, (output.sampleFlags & C.SAMPLE_FLAG_DECODE_ONLY) != 0);
|
assertEquals(invisible, (output.sampleFlags & C.SAMPLE_FLAG_DECODE_ONLY) != 0);
|
||||||
assertEquals(encrypted, (output.sampleFlags & C.SAMPLE_FLAG_ENCRYPTED) != 0);
|
assertEquals(encrypted, (output.sampleFlags & C.SAMPLE_FLAG_ENCRYPTED) != 0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] createInitializationSegment(int cuePoints, int mediaSegmentSize,
|
private byte[] createInitializationSegment(int cuePoints, int mediaSegmentSize,
|
||||||
@ -522,7 +534,8 @@ public class WebmExtractorTest extends InstrumentationTestCase {
|
|||||||
blockBytes = createSimpleBlockElement(data.length, blockTimecode,
|
blockBytes = createSimpleBlockElement(data.length, blockTimecode,
|
||||||
keyframe, invisible, true, encrypted, validSignalByte, trackNumber);
|
keyframe, invisible, true, encrypted, validSignalByte, trackNumber);
|
||||||
} else {
|
} else {
|
||||||
blockBytes = createBlockElement(data.length, blockTimecode, invisible, true, trackNumber);
|
blockBytes = createBlockElement(data.length, blockTimecode,
|
||||||
|
keyframe, invisible, true, trackNumber);
|
||||||
}
|
}
|
||||||
byte[] clusterBytes =
|
byte[] clusterBytes =
|
||||||
createClusterElement(blockBytes.length + data.length, clusterTimecode);
|
createClusterElement(blockBytes.length + data.length, clusterTimecode);
|
||||||
@ -762,22 +775,29 @@ public class WebmExtractorTest extends InstrumentationTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] createBlockElement(
|
private static byte[] createBlockElement(
|
||||||
int size, int timecode, boolean invisible, boolean noLacing, int trackNumber) {
|
int size, int timecode, boolean keyframe, boolean invisible, boolean noLacing,
|
||||||
|
int trackNumber) {
|
||||||
int blockSize = size + 5;
|
int blockSize = size + 5;
|
||||||
byte[] blockSizeBytes = getIntegerBytes(blockSize);
|
byte[] blockSizeBytes = getIntegerBytes(blockSize);
|
||||||
byte[] timeBytes = getIntegerBytes(timecode);
|
byte[] timeBytes = getIntegerBytes(timecode);
|
||||||
byte[] trackNumberBytes = getIntegerBytes(trackNumber);
|
byte[] trackNumberBytes = getIntegerBytes(trackNumber);
|
||||||
int blockElementSize = 1 + 8 + blockSize; // id + size + length of data
|
// Size of blockgroup = id + size + size of reference block + length of data.
|
||||||
byte[] sizeBytes = getIntegerBytes(blockElementSize);
|
int blockGroupElementSize = 1 + 8 + (keyframe ? 0 : 3) + blockSize;
|
||||||
|
byte[] sizeBytes = getIntegerBytes(blockGroupElementSize);
|
||||||
byte flags = (byte) ((invisible ? 0x08 : 0x00) | (noLacing ? 0x00 : 0x06));
|
byte flags = (byte) ((invisible ? 0x08 : 0x00) | (noLacing ? 0x00 : 0x06));
|
||||||
return createByteArray(
|
byte[] blockGroupHeader = createByteArray(
|
||||||
0xA0, // BlockGroup
|
0xA0, // BlockGroup
|
||||||
0x01, 0x00, 0x00, 0x00, sizeBytes[0], sizeBytes[1], sizeBytes[2], sizeBytes[3],
|
0x01, 0x00, 0x00, 0x00, sizeBytes[0], sizeBytes[1], sizeBytes[2], sizeBytes[3]);
|
||||||
|
byte[] referenceBlock = keyframe ? new byte[0] : createByteArray(
|
||||||
|
0xFB, // ReferenceBlock
|
||||||
|
0x81, 0x00); // size=1 value=0
|
||||||
|
byte[] blockData = createByteArray(
|
||||||
0xA1, // Block
|
0xA1, // Block
|
||||||
0x01, 0x00, 0x00, 0x00,
|
0x01, 0x00, 0x00, 0x00,
|
||||||
blockSizeBytes[0], blockSizeBytes[1], blockSizeBytes[2], blockSizeBytes[3],
|
blockSizeBytes[0], blockSizeBytes[1], blockSizeBytes[2], blockSizeBytes[3],
|
||||||
0x40, trackNumberBytes[3], // Track number size=2
|
0x40, trackNumberBytes[3], // Track number size=2
|
||||||
timeBytes[2], timeBytes[3], flags); // Timecode and flags
|
timeBytes[2], timeBytes[3], flags); // Timecode and flags
|
||||||
|
return joinByteArrays(blockGroupHeader, referenceBlock, blockData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] createFrameData(int size) {
|
private static byte[] createFrameData(int size) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user