Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
package org.mozilla.gecko.media;
import android.annotation.SuppressLint;
import android.media.MediaCodec;
import android.media.MediaCodec.BufferInfo;
import android.media.MediaCodec.CryptoInfo;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.ChecksSdkIntAtLeast;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import org.mozilla.gecko.annotation.WrapForJNI;
// Parcelable carrying input/output sample data and info cross process.
public final class Sample implements Parcelable {
public static final Sample EOS;
static {
final BufferInfo eosInfo = new BufferInfo();
EOS = new Sample();
EOS.info.set(0, 0, Long.MIN_VALUE, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
}
@WrapForJNI public long session;
public static final int NO_BUFFER = -1;
public int bufferId = NO_BUFFER;
@WrapForJNI public BufferInfo info = new BufferInfo();
public CryptoInfo cryptoInfo;
// Simple Linked list for recycling objects.
// Used to nodify Sample objects. Do not marshal/unmarshal.
private Sample mNext;
private static Sample sPool = new Sample();
private static int sPoolSize = 1;
private Sample() {}
private void readInfo(final Parcel in) {
final int offset = in.readInt();
final int size = in.readInt();
final long pts = in.readLong();
final int flags = in.readInt();
info.set(offset, size, pts, flags);
}
private void readCrypto(final Parcel in) {
final int hasCryptoInfo = in.readInt();
if (hasCryptoInfo == 0) {
cryptoInfo = null;
return;
}
final byte[] iv = in.createByteArray();
final byte[] key = in.createByteArray();
final int mode = in.readInt();
final int[] numBytesOfClearData = in.createIntArray();
final int[] numBytesOfEncryptedData = in.createIntArray();
final int numSubSamples = in.readInt();
if (cryptoInfo == null) {
cryptoInfo = new CryptoInfo();
}
cryptoInfo.set(numSubSamples, numBytesOfClearData, numBytesOfEncryptedData, key, iv, mode);
if (supportsCryptoPattern()) {
final int numEncryptBlocks = in.readInt();
final int numSkipBlocks = in.readInt();
cryptoInfo.setPattern(new CryptoInfo.Pattern(numEncryptBlocks, numSkipBlocks));
}
}
public Sample set(final BufferInfo info, final CryptoInfo cryptoInfo) {
setBufferInfo(info);
setCryptoInfo(cryptoInfo);
return this;
}
public void setBufferInfo(final BufferInfo info) {
this.info.set(0, info.size, info.presentationTimeUs, info.flags);
}
public void setCryptoInfo(final CryptoInfo crypto) {
if (crypto == null) {
cryptoInfo = null;
return;
}
if (cryptoInfo == null) {
cryptoInfo = new CryptoInfo();
}
if (crypto.numSubSamples > 0) {
cryptoInfo.set(
crypto.numSubSamples,
crypto.numBytesOfClearData,
crypto.numBytesOfEncryptedData,
crypto.key,
crypto.iv,
crypto.mode);
} else {
// MediaCodec expects us to provide at least one subsample, and at least one non-null array
// between numBytesOfClearData and numBytesOfEncryptedData.
// Whole-block full sample encryption does not carry subsample information, so we need to
// synthesize it by creating a subsample as big as the sample itself.
cryptoInfo.set(1, new int[] {0}, new int[] {info.size}, crypto.key, crypto.iv, crypto.mode);
}
if (supportsCryptoPattern()) {
final CryptoInfo.Pattern pattern = getCryptoPatternCompat(crypto);
if (pattern == null) {
return;
}
cryptoInfo.setPattern(pattern);
}
}
@WrapForJNI
public void dispose() {
if (isEOS()) {
return;
}
bufferId = NO_BUFFER;
info.set(0, 0, 0, 0);
if (cryptoInfo != null) {
cryptoInfo.set(0, null, null, null, null, 0);
}
// Recycle it.
synchronized (CREATOR) {
this.mNext = sPool;
sPool = this;
sPoolSize++;
}
}
public boolean isEOS() {
return (this == EOS) || ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0);
}
public static Sample obtain() {
synchronized (CREATOR) {
Sample s = null;
if (sPoolSize > 0) {
s = sPool;
sPool = s.mNext;
s.mNext = null;
sPoolSize--;
} else {
s = new Sample();
}
return s;
}
}
public static final Creator<Sample> CREATOR =
new Creator<Sample>() {
@Override
public Sample createFromParcel(final Parcel in) {
return obtainSample(in);
}
@Override
public Sample[] newArray(final int size) {
return new Sample[size];
}
private Sample obtainSample(final Parcel in) {
final Sample s = obtain();
s.session = in.readLong();
s.bufferId = in.readInt();
s.readInfo(in);
s.readCrypto(in);
return s;
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel dest, final int parcelableFlags) {
dest.writeLong(session);
dest.writeInt(bufferId);
writeInfo(dest);
writeCrypto(dest);
}
private void writeInfo(final Parcel dest) {
dest.writeInt(info.offset);
dest.writeInt(info.size);
dest.writeLong(info.presentationTimeUs);
dest.writeInt(info.flags);
}
private void writeCrypto(final Parcel dest) {
if (cryptoInfo != null) {
dest.writeInt(1);
dest.writeByteArray(cryptoInfo.iv);
dest.writeByteArray(cryptoInfo.key);
dest.writeInt(cryptoInfo.mode);
dest.writeIntArray(cryptoInfo.numBytesOfClearData);
dest.writeIntArray(cryptoInfo.numBytesOfEncryptedData);
dest.writeInt(cryptoInfo.numSubSamples);
if (supportsCryptoPattern()) {
final CryptoInfo.Pattern pattern = getCryptoPatternCompat(cryptoInfo);
if (pattern != null) {
dest.writeInt(pattern.getEncryptBlocks());
dest.writeInt(pattern.getSkipBlocks());
} else {
// Couldn't get pattern - write default values
dest.writeInt(0);
dest.writeInt(0);
}
}
} else {
dest.writeInt(0);
}
}
public static byte[] byteArrayFromBuffer(
final ByteBuffer buffer, final int offset, final int size) {
if (buffer == null || buffer.capacity() == 0 || size == 0) {
return null;
}
if (buffer.hasArray() && offset == 0 && buffer.array().length == size) {
return buffer.array();
}
final int length = Math.min(offset + size, buffer.capacity()) - offset;
final byte[] bytes = new byte[length];
buffer.position(offset);
buffer.get(bytes);
return bytes;
}
@Override
public String toString() {
if (isEOS()) {
return "EOS sample";
}
final StringBuilder str = new StringBuilder();
str.append("{ session#:")
.append(session)
.append(", buffer#")
.append(bufferId)
.append(", info=")
.append("{ offset=")
.append(info.offset)
.append(", size=")
.append(info.size)
.append(", pts=")
.append(info.presentationTimeUs)
.append(", flags=")
.append(Integer.toHexString(info.flags))
.append(" }")
.append(" }");
return str.toString();
}
@ChecksSdkIntAtLeast(api = android.os.Build.VERSION_CODES.N)
public static boolean supportsCryptoPattern() {
return Build.VERSION.SDK_INT >= 24;
}
@SuppressLint("DiscouragedPrivateApi")
public static CryptoInfo.Pattern getCryptoPatternCompat(final CryptoInfo cryptoInfo) {
if (!supportsCryptoPattern()) {
return null;
}
// getPattern() added in API 31:
if (Build.VERSION.SDK_INT >= 31) {
return cryptoInfo.getPattern();
}
// CryptoInfo.Pattern added in API 24:
if (Build.VERSION.SDK_INT >= 24) {
try {
// Without getPattern(), no way to access the pattern without reflection.
final Field patternField = CryptoInfo.class.getDeclaredField("pattern");
patternField.setAccessible(true);
return (CryptoInfo.Pattern) patternField.get(cryptoInfo);
} catch (final NoSuchFieldException | IllegalAccessException e) {
return null;
}
}
return null;
}
}