Initial hlsmplayer application

This commit is contained in:
root
2026-09-27 17:13:02 +08:00
commit be5a40bfe3
1214 changed files with 315753 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
plugins { id("com.android.library") }
android {
namespace = "androidx.media3.decoder"
compileSdk = 36
defaultConfig { minSdk = 23 }
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
dependencies {
api(project(":lib-common"))
implementation(libs.androidx.annotation)
compileOnly(libs.checkerframework.qual)
compileOnly(libs.errorprone.annotations)
}
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest>
<uses-sdk/>
</manifest>
@@ -0,0 +1,106 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.decoder;
import androidx.annotation.CallSuper;
import androidx.media3.common.C;
import androidx.media3.common.util.UnstableApi;
/** Base class for buffers with flags. */
@UnstableApi
public abstract class Buffer {
private @C.BufferFlags int flags;
/** Clears the buffer. */
@CallSuper
public void clear() {
flags = 0;
}
/** Returns whether the {@link C#BUFFER_FLAG_FIRST_SAMPLE} flag is set. */
public final boolean isFirstSample() {
return getFlag(C.BUFFER_FLAG_FIRST_SAMPLE);
}
/**
* Returns whether the {@link C#BUFFER_FLAG_END_OF_STREAM} flag is set.
*
* <p>If this is set, all other attributes of the buffer should be ignored.
*/
public final boolean isEndOfStream() {
return getFlag(C.BUFFER_FLAG_END_OF_STREAM);
}
/** Returns whether the {@link C#BUFFER_FLAG_KEY_FRAME} flag is set. */
public final boolean isKeyFrame() {
return getFlag(C.BUFFER_FLAG_KEY_FRAME);
}
/** Returns whether the {@link C#BUFFER_FLAG_LAST_SAMPLE} flag is set. */
public final boolean isLastSample() {
return getFlag(C.BUFFER_FLAG_LAST_SAMPLE);
}
/** Returns whether the {@link C#BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA} flag is set. */
public final boolean hasSupplementalData() {
return getFlag(C.BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA);
}
/** Returns whether the {@link C#BUFFER_FLAG_NOT_DEPENDED_ON} flag is set. */
public final boolean notDependedOn() {
return getFlag(C.BUFFER_FLAG_NOT_DEPENDED_ON);
}
/**
* Replaces this buffer's flags with {@code flags}.
*
* @param flags The flags to set, which should be a combination of the {@code C.BUFFER_FLAG_*}
* constants.
*/
public final void setFlags(@C.BufferFlags int flags) {
this.flags = flags;
}
/**
* Adds the {@code flag} to this buffer's flags.
*
* @param flag The flag to add to this buffer's flags, which should be one of the {@code
* C.BUFFER_FLAG_*} constants.
*/
public final void addFlag(@C.BufferFlags int flag) {
flags |= flag;
}
/**
* Removes the {@code flag} from this buffer's flags, if it is set.
*
* @param flag The flag to remove.
*/
public final void clearFlag(@C.BufferFlags int flag) {
flags &= ~flag;
}
/**
* Returns whether the specified flag has been set on this buffer.
*
* @param flag The flag to check.
* @return Whether the flag is set.
*/
protected final boolean getFlag(@C.BufferFlags int flag) {
return (flags & flag) == flag;
}
}
@@ -0,0 +1,26 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.decoder;
import androidx.media3.common.C;
import androidx.media3.common.util.UnstableApi;
/**
* Configuration for a decoder to allow it to decode encrypted media data. The configuration is
* {@link C.CryptoType} specific.
*/
@UnstableApi
public interface CryptoConfig {}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.decoder;
import androidx.media3.common.util.UnstableApi;
/** Thrown when a non-platform component fails to decrypt data. */
@UnstableApi
public class CryptoException extends Exception {
/** A component specific error code. */
public final int errorCode;
/**
* @param errorCode A component specific error code.
* @param message The detail message.
*/
public CryptoException(int errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
}
@@ -0,0 +1,182 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.decoder;
import static android.os.Build.VERSION.SDK_INT;
import static com.google.common.base.Preconditions.checkNotNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.C;
import androidx.media3.common.util.UnstableApi;
/**
* Metadata describing the structure of an encrypted input sample.
*
* <p>This class is a compatibility wrapper for {@link android.media.MediaCodec.CryptoInfo}.
*/
@UnstableApi
public final class CryptoInfo {
/**
* The 16 byte initialization vector. If the initialization vector of the content is shorter than
* 16 bytes, 0 byte padding is appended to extend the vector to the required 16 byte length.
*
* @see android.media.MediaCodec.CryptoInfo#iv
*/
@Nullable public byte[] iv;
/**
* The 16 byte key id.
*
* @see android.media.MediaCodec.CryptoInfo#key
*/
@Nullable public byte[] key;
/**
* The type of encryption that has been applied. Must be one of the {@link C.CryptoMode} values.
*
* @see android.media.MediaCodec.CryptoInfo#mode
*/
public @C.CryptoMode int mode;
/**
* The number of leading unencrypted bytes in each sub-sample. If null, all bytes are treated as
* encrypted and {@link #numBytesOfEncryptedData} must be specified.
*
* @see android.media.MediaCodec.CryptoInfo#numBytesOfClearData
*/
@Nullable public int[] numBytesOfClearData;
/**
* The number of trailing encrypted bytes in each sub-sample. If null, all bytes are treated as
* clear and {@link #numBytesOfClearData} must be specified.
*
* @see android.media.MediaCodec.CryptoInfo#numBytesOfEncryptedData
*/
@Nullable public int[] numBytesOfEncryptedData;
/**
* The number of subSamples that make up the buffer's contents.
*
* @see android.media.MediaCodec.CryptoInfo#numSubSamples
*/
public int numSubSamples;
/**
* @see android.media.MediaCodec.CryptoInfo.Pattern
*/
public int encryptedBlocks;
/**
* @see android.media.MediaCodec.CryptoInfo.Pattern
*/
public int clearBlocks;
private final android.media.MediaCodec.CryptoInfo frameworkCryptoInfo;
@Nullable private final PatternHolderV24 patternHolder;
public CryptoInfo() {
frameworkCryptoInfo = new android.media.MediaCodec.CryptoInfo();
patternHolder = SDK_INT >= 24 ? new PatternHolderV24(frameworkCryptoInfo) : null;
}
/**
* @see android.media.MediaCodec.CryptoInfo#set(int, int[], int[], byte[], byte[], int)
*/
public void set(
int numSubSamples,
int[] numBytesOfClearData,
int[] numBytesOfEncryptedData,
byte[] key,
byte[] iv,
@C.CryptoMode int mode,
int encryptedBlocks,
int clearBlocks) {
this.numSubSamples = numSubSamples;
this.numBytesOfClearData = numBytesOfClearData;
this.numBytesOfEncryptedData = numBytesOfEncryptedData;
this.key = key;
this.iv = iv;
this.mode = mode;
this.encryptedBlocks = encryptedBlocks;
this.clearBlocks = clearBlocks;
// Update frameworkCryptoInfo fields directly because CryptoInfo.set performs an unnecessary
// object allocation on Android N.
frameworkCryptoInfo.numSubSamples = numSubSamples;
frameworkCryptoInfo.numBytesOfClearData = numBytesOfClearData;
frameworkCryptoInfo.numBytesOfEncryptedData = numBytesOfEncryptedData;
frameworkCryptoInfo.key = key;
frameworkCryptoInfo.iv = iv;
frameworkCryptoInfo.mode = mode;
if (SDK_INT >= 24) {
checkNotNull(patternHolder).set(encryptedBlocks, clearBlocks);
}
}
/**
* Returns an equivalent {@link android.media.MediaCodec.CryptoInfo} instance.
*
* <p>Successive calls to this method on a single {@link CryptoInfo} will return the same
* instance. Changes to the {@link CryptoInfo} will be reflected in the returned object. The
* return object should not be modified directly.
*
* @return The equivalent {@link android.media.MediaCodec.CryptoInfo} instance.
*/
public android.media.MediaCodec.CryptoInfo getFrameworkCryptoInfo() {
return frameworkCryptoInfo;
}
/**
* Increases the number of clear data for the first sub sample by {@code count}.
*
* <p>If {@code count} is 0, this method is a no-op. Otherwise, it adds {@code count} to {@link
* #numBytesOfClearData}[0].
*
* <p>If {@link #numBytesOfClearData} is null (which is permitted), this method will instantiate
* it to a new {@code int[1]}.
*
* @param count The number of bytes to be added to the first subSample of {@link
* #numBytesOfClearData}.
*/
public void increaseClearDataFirstSubSampleBy(int count) {
if (count == 0) {
return;
}
if (numBytesOfClearData == null) {
numBytesOfClearData = new int[1];
frameworkCryptoInfo.numBytesOfClearData = numBytesOfClearData;
}
numBytesOfClearData[0] += count;
}
@RequiresApi(24)
private static final class PatternHolderV24 {
private final android.media.MediaCodec.CryptoInfo frameworkCryptoInfo;
private final android.media.MediaCodec.CryptoInfo.Pattern pattern;
private PatternHolderV24(android.media.MediaCodec.CryptoInfo frameworkCryptoInfo) {
this.frameworkCryptoInfo = frameworkCryptoInfo;
pattern = new android.media.MediaCodec.CryptoInfo.Pattern(0, 0);
}
private void set(int encryptedBlocks, int clearBlocks) {
pattern.set(encryptedBlocks, clearBlocks);
frameworkCryptoInfo.setPattern(pattern);
}
}
}
@@ -0,0 +1,105 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.decoder;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
import java.util.concurrent.Executor;
/**
* A media decoder.
*
* @param <I> The type of buffer input to the decoder.
* @param <O> The type of buffer output from the decoder.
* @param <E> The type of exception thrown from the decoder.
*/
@UnstableApi
public interface Decoder<I, O, E extends DecoderException> {
/**
* Returns the name of the decoder.
*
* @return The name of the decoder.
*/
String getName();
/**
* Sets the timestamp from which output buffers should be produced, in microseconds.
*
* <p>Any decoded buffer with a timestamp less than {@code outputStartTimeUs} should be skipped by
* the implementation and not made available via {@link #dequeueOutputBuffer}.
*
* <p>This method must only be called before {@linkplain #queueInputBuffer queuing the first input
* buffer} initially or after {@link #flush()}.
*
* @param outputStartTimeUs The time from which output buffer should be produced, in microseconds.
*/
void setOutputStartTimeUs(long outputStartTimeUs);
/**
* Dequeues the next input buffer to be filled and queued to the decoder.
*
* @return The input buffer, which will have been cleared, or null if a buffer isn't available.
* @throws E If a decoder error has occurred.
*/
@Nullable
I dequeueInputBuffer() throws E;
/**
* Queues an input buffer to the decoder.
*
* @param inputBuffer The input buffer.
* @throws E If a decoder error has occurred.
*/
void queueInputBuffer(I inputBuffer) throws E;
/**
* Dequeues the next output buffer from the decoder.
*
* @return The output buffer, or null if an output buffer isn't available.
* @throws E If a decoder error has occurred.
*/
@Nullable
O dequeueOutputBuffer() throws E;
/**
* Flushes the decoder. Ownership of dequeued input buffers is returned to the decoder. The caller
* is still responsible for releasing any dequeued output buffers.
*/
void flush();
/** Releases the decoder. Must be called when the decoder is no longer needed. */
void release();
/** Callback for asynchronous events. */
interface Callback {
/** Called when an input buffer becomes available to be dequeued. */
void onInputBufferAvailable();
/** Called when an output buffer becomes available to be dequeued. */
void onOutputBufferAvailable();
}
/**
* Sets the callback and executor.
*
* @param callback The callback to receive events.
* @param executor The executor on which the callback will be invoked.
*/
default void setCallback(Callback callback, Executor executor) {
// Do nothing by default.
}
}
@@ -0,0 +1,52 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.decoder;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
/** Thrown when a {@link Decoder} error occurs. */
@UnstableApi
public class DecoderException extends Exception {
/**
* Creates an instance.
*
* @param message The detail message for this exception.
*/
public DecoderException(String message) {
super(message);
}
/**
* Creates an instance.
*
* @param cause The cause of this exception, or {@code null}.
*/
public DecoderException(@Nullable Throwable cause) {
super(cause);
}
/**
* Creates an instance.
*
* @param message The detail message for this exception.
* @param cause The cause of this exception, or {@code null}.
*/
public DecoderException(String message, @Nullable Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,247 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.decoder;
import static java.lang.annotation.ElementType.TYPE_USE;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MediaLibraryInfo;
import androidx.media3.common.util.UnstableApi;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.nio.ByteBuffer;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
/** Holds input for a decoder. */
@UnstableApi
public class DecoderInputBuffer extends Buffer {
static {
MediaLibraryInfo.registerModule("media3.decoder");
}
/**
* Thrown when an attempt is made to write into a {@link DecoderInputBuffer} whose {@link
* #bufferReplacementMode} is {@link #BUFFER_REPLACEMENT_MODE_DISABLED} and who {@link #data}
* capacity is smaller than required.
*/
public static final class InsufficientCapacityException extends IllegalStateException {
/** The current capacity of the buffer. */
public final int currentCapacity;
/** The required capacity of the buffer. */
public final int requiredCapacity;
/**
* Creates an instance.
*
* @param currentCapacity The current capacity of the buffer.
* @param requiredCapacity The required capacity of the buffer.
*/
public InsufficientCapacityException(int currentCapacity, int requiredCapacity) {
super("Buffer too small (" + currentCapacity + " < " + requiredCapacity + ")");
this.currentCapacity = currentCapacity;
this.requiredCapacity = requiredCapacity;
}
}
/**
* The buffer replacement mode. This controls how {@link #ensureSpaceForWrite} generates
* replacement buffers when the capacity of the existing buffer is insufficient. One of {@link
* #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} or {@link
* #BUFFER_REPLACEMENT_MODE_DIRECT}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef({
BUFFER_REPLACEMENT_MODE_DISABLED,
BUFFER_REPLACEMENT_MODE_NORMAL,
BUFFER_REPLACEMENT_MODE_DIRECT
})
public @interface BufferReplacementMode {}
/** Disallows buffer replacement. */
public static final int BUFFER_REPLACEMENT_MODE_DISABLED = 0;
/** Allows buffer replacement using {@link ByteBuffer#allocate(int)}. */
public static final int BUFFER_REPLACEMENT_MODE_NORMAL = 1;
/** Allows buffer replacement using {@link ByteBuffer#allocateDirect(int)}. */
public static final int BUFFER_REPLACEMENT_MODE_DIRECT = 2;
/** The {@link Format}. */
@Nullable public Format format;
/** {@link CryptoInfo} for encrypted data. */
public final CryptoInfo cryptoInfo;
/** The buffer's data, or {@code null} if no data has been set. */
@Nullable public ByteBuffer data;
// TODO: Remove this temporary signaling once end-of-stream propagation for clips using content
// protection is fixed. See [Internal: b/153326944] for details.
/**
* Whether the last attempt to read a sample into this buffer failed due to not yet having the DRM
* keys associated with the next sample.
*/
public boolean waitingForKeys;
/** The time at which the sample should be presented. */
public long timeUs;
/**
* Supplemental data related to the buffer, if {@link #hasSupplementalData()} returns true. If
* present, the buffer is populated with supplemental data from position 0 to its limit.
*/
@Nullable public ByteBuffer supplementalData;
private final @BufferReplacementMode int bufferReplacementMode;
private final int paddingSize;
/** Returns a new instance that's not able to hold any data. */
public static DecoderInputBuffer newNoDataInstance() {
return new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED);
}
/**
* Creates a new instance.
*
* @param bufferReplacementMode The {@link BufferReplacementMode} replacement mode.
*/
public DecoderInputBuffer(@BufferReplacementMode int bufferReplacementMode) {
this(bufferReplacementMode, /* paddingSize= */ 0);
}
/**
* Creates a new instance.
*
* @param bufferReplacementMode The {@link BufferReplacementMode} replacement mode.
* @param paddingSize If non-zero, {@link #ensureSpaceForWrite(int)} will ensure that the buffer
* is this number of bytes larger than the requested length. This can be useful for decoders
* that consume data in fixed size blocks, for efficiency. Setting the padding size to the
* decoder's fixed read size is necessary to prevent such a decoder from trying to read beyond
* the end of the buffer.
*/
public DecoderInputBuffer(@BufferReplacementMode int bufferReplacementMode, int paddingSize) {
this.cryptoInfo = new CryptoInfo();
this.bufferReplacementMode = bufferReplacementMode;
this.paddingSize = paddingSize;
}
/**
* Clears {@link #supplementalData} and ensures that it's large enough to accommodate {@code
* length} bytes.
*
* @param length The length of the supplemental data that must be accommodated, in bytes.
*/
@EnsuresNonNull("supplementalData")
public void resetSupplementalData(int length) {
if (supplementalData == null || supplementalData.capacity() < length) {
supplementalData = ByteBuffer.allocate(length);
} else {
supplementalData.clear();
}
}
/**
* Ensures that {@link #data} is large enough to accommodate a write of a given length at its
* current position.
*
* <p>If the capacity of {@link #data} is sufficient this method does nothing. If the capacity is
* insufficient then an attempt is made to replace {@link #data} with a new {@link ByteBuffer}
* whose capacity is sufficient. Data up to the current position is copied to the new buffer.
*
* @param length The length of the write that must be accommodated, in bytes.
* @throws InsufficientCapacityException If there is insufficient capacity to accommodate the
* write and {@link #bufferReplacementMode} is {@link #BUFFER_REPLACEMENT_MODE_DISABLED}.
*/
@EnsuresNonNull("data")
public void ensureSpaceForWrite(int length) {
length += paddingSize;
@Nullable ByteBuffer currentData = data;
if (currentData == null) {
data = createReplacementByteBuffer(length);
return;
}
// Check whether the current buffer is sufficient.
int capacity = currentData.capacity();
int position = currentData.position();
int requiredCapacity = position + length;
if (capacity >= requiredCapacity) {
data = currentData;
return;
}
// Instantiate a new buffer if possible.
ByteBuffer newData = createReplacementByteBuffer(requiredCapacity);
newData.order(currentData.order());
// Copy data up to the current position from the old buffer to the new one.
if (position > 0) {
currentData.flip();
newData.put(currentData);
}
// Set the new buffer.
data = newData;
}
/** Returns whether the {@link C#BUFFER_FLAG_ENCRYPTED} flag is set. */
public final boolean isEncrypted() {
return getFlag(C.BUFFER_FLAG_ENCRYPTED);
}
/**
* Flips {@link #data} and {@link #supplementalData} in preparation for being queued to a decoder.
*
* @see java.nio.Buffer#flip()
*/
public final void flip() {
if (data != null) {
data.flip();
}
if (supplementalData != null) {
supplementalData.flip();
}
}
@Override
public void clear() {
super.clear();
if (data != null) {
data.clear();
}
if (supplementalData != null) {
supplementalData.clear();
}
waitingForKeys = false;
}
private ByteBuffer createReplacementByteBuffer(int requiredCapacity) {
if (bufferReplacementMode == BUFFER_REPLACEMENT_MODE_NORMAL) {
return ByteBuffer.allocate(requiredCapacity);
} else if (bufferReplacementMode == BUFFER_REPLACEMENT_MODE_DIRECT) {
return ByteBuffer.allocateDirect(requiredCapacity);
} else {
int currentCapacity = data == null ? 0 : data.capacity();
throw new InsufficientCapacityException(currentCapacity, requiredCapacity);
}
}
}
@@ -0,0 +1,61 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.decoder;
import androidx.annotation.CallSuper;
import androidx.media3.common.util.UnstableApi;
/** Output buffer decoded by a {@link Decoder}. */
@UnstableApi
public abstract class DecoderOutputBuffer extends Buffer {
/** Buffer owner. */
public interface Owner<S extends DecoderOutputBuffer> {
/**
* Releases the buffer.
*
* @param outputBuffer Output buffer.
*/
void releaseOutputBuffer(S outputBuffer);
}
/** The presentation timestamp for the buffer, in microseconds. */
public long timeUs;
/**
* The number of buffers immediately prior to this one that were skipped in the {@link Decoder}.
*/
public int skippedOutputBufferCount;
/**
* Whether this buffer should be skipped, usually because the decoding process generated no data
* or invalid data.
*/
public boolean shouldBeSkipped;
/** Releases the output buffer for reuse. Must be called when the buffer is no longer needed. */
public abstract void release();
@Override
@CallSuper
public void clear() {
super.clear();
timeUs = 0;
skippedOutputBufferCount = 0;
shouldBeSkipped = false;
}
}
@@ -0,0 +1,379 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.decoder;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.util.UnstableApi;
import java.util.ArrayDeque;
import java.util.concurrent.Executor;
/**
* Base class for {@link Decoder}s that use their own decode thread and decode each input buffer
* immediately into a corresponding output buffer.
*/
@SuppressWarnings("UngroupedOverloads")
@UnstableApi
public abstract class SimpleDecoder<
I extends DecoderInputBuffer, O extends DecoderOutputBuffer, E extends DecoderException>
implements Decoder<I, O, E> {
private final Thread decodeThread;
private final Object lock;
private final ArrayDeque<I> queuedInputBuffers;
private final ArrayDeque<O> queuedOutputBuffers;
private final I[] availableInputBuffers;
private final O[] availableOutputBuffers;
private int availableInputBufferCount;
private int availableOutputBufferCount;
@Nullable private I dequeuedInputBuffer;
@Nullable private E exception;
private boolean flushed;
private boolean released;
private int skippedOutputBufferCount;
private long outputStartTimeUs;
@Nullable private Executor executor;
@Nullable private Runnable onOutputBufferAvailableRunnable;
@Nullable private Runnable onInputBufferAvailableRunnable;
/**
* @param inputBuffers An array of nulls that will be used to store references to input buffers.
* @param outputBuffers An array of nulls that will be used to store references to output buffers.
*/
@SuppressWarnings("nullness:method.invocation")
protected SimpleDecoder(I[] inputBuffers, O[] outputBuffers) {
lock = new Object();
outputStartTimeUs = C.TIME_UNSET;
queuedInputBuffers = new ArrayDeque<>();
queuedOutputBuffers = new ArrayDeque<>();
availableInputBuffers = inputBuffers;
availableInputBufferCount = inputBuffers.length;
for (int i = 0; i < availableInputBufferCount; i++) {
availableInputBuffers[i] = createInputBuffer();
}
availableOutputBuffers = outputBuffers;
availableOutputBufferCount = outputBuffers.length;
for (int i = 0; i < availableOutputBufferCount; i++) {
availableOutputBuffers[i] = createOutputBuffer();
}
decodeThread =
new Thread("ExoPlayer:SimpleDecoder") {
@Override
public void run() {
SimpleDecoder.this.run();
}
};
decodeThread.start();
}
/**
* Sets the initial size of each input buffer.
*
* <p>This method should only be called before the decoder is used (i.e. before the first call to
* {@link #dequeueInputBuffer()}.
*
* @param size The required input buffer size.
*/
protected final void setInitialInputBufferSize(int size) {
checkState(availableInputBufferCount == availableInputBuffers.length);
for (I inputBuffer : availableInputBuffers) {
inputBuffer.ensureSpaceForWrite(size);
}
}
/**
* Returns whether a sample time is greater or equal to the {@link #setOutputStartTimeUs output
* start time}, if set.
*
* <p>If this method returns false, the buffer will not be made available as an output buffer.
*
* @param timeUs The buffer time, in microseconds.
* @return Whether the buffer time is greater or equal to the output start time, or {@code true}
* if the output start time is not set.
*/
protected final boolean isAtLeastOutputStartTimeUs(long timeUs) {
synchronized (lock) {
return outputStartTimeUs == C.TIME_UNSET || timeUs >= outputStartTimeUs;
}
}
@Override
public final void setOutputStartTimeUs(long outputStartTimeUs) {
synchronized (lock) {
checkState(availableInputBufferCount == availableInputBuffers.length || flushed);
this.outputStartTimeUs = outputStartTimeUs;
}
}
@Override
public final void setCallback(Callback callback, Executor executor) {
synchronized (lock) {
this.executor = executor;
this.onOutputBufferAvailableRunnable =
callback != null ? callback::onOutputBufferAvailable : null;
this.onInputBufferAvailableRunnable =
callback != null ? callback::onInputBufferAvailable : null;
}
}
@Override
@Nullable
public final I dequeueInputBuffer() throws E {
synchronized (lock) {
maybeThrowException();
checkState(dequeuedInputBuffer == null);
dequeuedInputBuffer =
availableInputBufferCount == 0
? null
: availableInputBuffers[--availableInputBufferCount];
return dequeuedInputBuffer;
}
}
@Override
public final void queueInputBuffer(I inputBuffer) throws E {
synchronized (lock) {
maybeThrowException();
checkArgument(inputBuffer == dequeuedInputBuffer);
queuedInputBuffers.addLast(inputBuffer);
maybeNotifyDecodeLoop();
dequeuedInputBuffer = null;
}
}
@Override
@Nullable
public final O dequeueOutputBuffer() throws E {
synchronized (lock) {
maybeThrowException();
if (queuedOutputBuffers.isEmpty()) {
return null;
}
return queuedOutputBuffers.removeFirst();
}
}
/**
* Releases an output buffer back to the decoder.
*
* @param outputBuffer The output buffer being released.
*/
@CallSuper
protected void releaseOutputBuffer(O outputBuffer) {
synchronized (lock) {
releaseOutputBufferInternal(outputBuffer);
maybeNotifyDecodeLoop();
}
}
@Override
public final void flush() {
synchronized (lock) {
flushed = true;
skippedOutputBufferCount = 0;
if (dequeuedInputBuffer != null) {
releaseInputBufferInternal(dequeuedInputBuffer, /* notifyCallback= */ false);
dequeuedInputBuffer = null;
}
while (!queuedInputBuffers.isEmpty()) {
releaseInputBufferInternal(queuedInputBuffers.removeFirst(), /* notifyCallback= */ false);
}
while (!queuedOutputBuffers.isEmpty()) {
queuedOutputBuffers.removeFirst().release();
}
}
}
@CallSuper
@Override
public void release() {
synchronized (lock) {
released = true;
lock.notify();
}
try {
decodeThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
/**
* Throws a decode exception, if there is one.
*
* @throws E The decode exception.
*/
private void maybeThrowException() throws E {
@Nullable E exception = this.exception;
if (exception != null) {
throw exception;
}
}
/**
* Notifies the decode loop if there exists a queued input buffer and an available output buffer
* to decode into.
*
* <p>Should only be called whilst synchronized on the lock object.
*/
private void maybeNotifyDecodeLoop() {
if (canDecodeBuffer()) {
lock.notify();
}
}
private void run() {
try {
while (decode()) {
// Do nothing.
}
} catch (InterruptedException e) {
// Not expected.
throw new IllegalStateException(e);
}
}
private boolean decode() throws InterruptedException {
I inputBuffer;
O outputBuffer;
boolean resetDecoder;
// Wait until we have an input buffer to decode, and an output buffer to decode into.
synchronized (lock) {
while (!released && !canDecodeBuffer()) {
lock.wait();
}
if (released) {
return false;
}
inputBuffer = queuedInputBuffers.removeFirst();
outputBuffer = availableOutputBuffers[--availableOutputBufferCount];
resetDecoder = flushed;
flushed = false;
}
if (inputBuffer.isEndOfStream()) {
outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);
} else {
outputBuffer.timeUs = inputBuffer.timeUs;
if (inputBuffer.isFirstSample()) {
outputBuffer.addFlag(C.BUFFER_FLAG_FIRST_SAMPLE);
}
if (!isAtLeastOutputStartTimeUs(inputBuffer.timeUs)) {
outputBuffer.shouldBeSkipped = true;
}
@Nullable E exception;
try {
exception = decode(inputBuffer, outputBuffer, resetDecoder);
} catch (RuntimeException e) {
// This can occur if a sample is malformed in a way that the decoder is not robust against.
// We don't want the process to die in this case, but we do want to propagate the error.
exception = createUnexpectedDecodeException(e);
} catch (OutOfMemoryError e) {
// This can occur if a sample is malformed in a way that causes the decoder to think it
// needs to allocate a large amount of memory. We don't want the process to die in this
// case, but we do want to propagate the error.
exception = createUnexpectedDecodeException(e);
}
if (exception != null) {
synchronized (lock) {
this.exception = exception;
}
return false;
}
}
synchronized (lock) {
if (flushed) {
outputBuffer.release();
} else if (outputBuffer.shouldBeSkipped) {
skippedOutputBufferCount++;
outputBuffer.release();
} else {
outputBuffer.skippedOutputBufferCount = skippedOutputBufferCount;
skippedOutputBufferCount = 0;
queuedOutputBuffers.addLast(outputBuffer);
Runnable currentOnOutputBufferAvailableRunnable = this.onOutputBufferAvailableRunnable;
Executor currentExecutor = this.executor;
if (currentOnOutputBufferAvailableRunnable != null && currentExecutor != null) {
currentExecutor.execute(currentOnOutputBufferAvailableRunnable);
}
}
// Make the input buffer available again.
releaseInputBufferInternal(inputBuffer, /* notifyCallback= */ true);
}
return true;
}
private boolean canDecodeBuffer() {
return !queuedInputBuffers.isEmpty() && availableOutputBufferCount > 0;
}
private void releaseInputBufferInternal(I inputBuffer, boolean notifyCallback) {
inputBuffer.clear();
availableInputBuffers[availableInputBufferCount++] = inputBuffer;
Runnable currentOnInputBufferAvailableRunnable = this.onInputBufferAvailableRunnable;
Executor currentExecutor = this.executor;
if (notifyCallback
&& currentOnInputBufferAvailableRunnable != null
&& currentExecutor != null) {
currentExecutor.execute(currentOnInputBufferAvailableRunnable);
}
}
private void releaseOutputBufferInternal(O outputBuffer) {
outputBuffer.clear();
availableOutputBuffers[availableOutputBufferCount++] = outputBuffer;
}
/** Creates a new input buffer. */
protected abstract I createInputBuffer();
/** Creates a new output buffer. */
protected abstract O createOutputBuffer();
/**
* Creates an exception to propagate for an unexpected decode error.
*
* @param error The unexpected decode error.
* @return The exception to propagate.
*/
protected abstract E createUnexpectedDecodeException(Throwable error);
/**
* Decodes the {@code inputBuffer} and stores any decoded output in {@code outputBuffer}.
*
* @param inputBuffer The buffer to decode.
* @param outputBuffer The output buffer to store decoded data. The output buffer will not be made
* available to dequeue if its {@link DecoderOutputBuffer#timeUs} is not {@linkplain
* #isAtLeastOutputStartTimeUs at least the output start time} or when it's marked with {@link
* DecoderOutputBuffer#shouldBeSkipped}. The output buffer may not have been populated in
* these cases.
* @param reset Whether the decoder must be reset before decoding.
* @return A decoder exception if an error occurred, or null if decoding was successful.
*/
@Nullable
protected abstract E decode(I inputBuffer, O outputBuffer, boolean reset);
}
@@ -0,0 +1,88 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.decoder;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/** Buffer for {@link SimpleDecoder} output. */
@UnstableApi
public class SimpleDecoderOutputBuffer extends DecoderOutputBuffer {
private final Owner<SimpleDecoderOutputBuffer> owner;
@Nullable public ByteBuffer data;
public SimpleDecoderOutputBuffer(Owner<SimpleDecoderOutputBuffer> owner) {
this.owner = owner;
}
/**
* Initializes the buffer.
*
* @param timeUs The presentation timestamp for the buffer, in microseconds.
* @param size An upper bound on the size of the data that will be written to the buffer.
* @return The {@link #data} buffer, for convenience.
*/
public ByteBuffer init(long timeUs, int size) {
this.timeUs = timeUs;
if (data == null || data.capacity() < size) {
data = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
}
data.position(0);
data.limit(size);
return data;
}
/**
* Grows the buffer to a new size.
*
* <p>Existing data is copied to the new buffer, and {@link ByteBuffer#position} is preserved.
*
* @param newSize New size of the buffer.
* @return The {@link #data} buffer, for convenience.
*/
public ByteBuffer grow(int newSize) {
ByteBuffer oldData = checkNotNull(this.data);
checkArgument(newSize >= oldData.limit());
ByteBuffer newData = ByteBuffer.allocateDirect(newSize).order(ByteOrder.nativeOrder());
int restorePosition = oldData.position();
oldData.position(0);
newData.put(oldData);
newData.position(restorePosition);
newData.limit(newSize);
this.data = newData;
return newData;
}
@Override
public void clear() {
super.clear();
if (data != null) {
data.clear();
}
}
@Override
public void release() {
owner.releaseOutputBuffer(this);
}
}
@@ -0,0 +1,231 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.decoder;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.util.UnstableApi;
import java.nio.ByteBuffer;
/** Video decoder output buffer containing video frame data. */
@UnstableApi
public class VideoDecoderOutputBuffer extends DecoderOutputBuffer {
// LINT.IfChange
public static final int COLORSPACE_UNKNOWN = 0;
public static final int COLORSPACE_BT601 = 1;
public static final int COLORSPACE_BT709 = 2;
public static final int COLORSPACE_BT2020 = 3;
// LINT.ThenChange(
// ../../../../../../../decoder_av1/src/main/jni/dav1d_jni.cc,
// ../../../../../../../decoder_vp9/src/main/jni/vpx_jni.cc
// )
/** Decoder private data. Used from native code. */
public long decoderPrivate;
/** Output mode. */
public @C.VideoOutputMode int mode;
/** RGB buffer for RGB mode. */
@Nullable public ByteBuffer data;
public int width;
public int height;
/** The format of the input from which this output buffer was decoded. */
@Nullable public Format format;
/** YUV planes for YUV mode. */
@Nullable public ByteBuffer[] yuvPlanes;
@Nullable public int[] yuvStrides;
public int colorspace;
public int yStride;
public int uvStride;
/**
* Supplemental data related to the output frame, if {@link #hasSupplementalData()} returns true.
* If present, the buffer is populated with supplemental data from position 0 to its limit.
*/
@Nullable public ByteBuffer supplementalData;
private final Owner<VideoDecoderOutputBuffer> owner;
/**
* Creates VideoDecoderOutputBuffer.
*
* @param owner Buffer owner.
*/
public VideoDecoderOutputBuffer(Owner<VideoDecoderOutputBuffer> owner) {
this.owner = owner;
}
@Override
public void release() {
owner.releaseOutputBuffer(this);
}
/**
* Initializes the buffer.
*
* @param timeUs The presentation timestamp for the buffer, in microseconds.
* @param mode The output mode. One of {@link C#VIDEO_OUTPUT_MODE_NONE}, {@link
* C#VIDEO_OUTPUT_MODE_YUV} and {@link C#VIDEO_OUTPUT_MODE_SURFACE_YUV}.
* @param supplementalData Supplemental data associated with the frame, or {@code null} if not
* present. It is safe to reuse the provided buffer after this method returns.
*/
public void init(
long timeUs, @C.VideoOutputMode int mode, @Nullable ByteBuffer supplementalData) {
this.timeUs = timeUs;
this.mode = mode;
if (supplementalData != null && supplementalData.hasRemaining()) {
addFlag(C.BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA);
int size = supplementalData.limit();
if (this.supplementalData == null || this.supplementalData.capacity() < size) {
this.supplementalData = ByteBuffer.allocate(size);
} else {
this.supplementalData.clear();
}
this.supplementalData.put(supplementalData);
this.supplementalData.flip();
supplementalData.position(0);
} else {
this.supplementalData = null;
}
}
/**
* Resizes the buffer based on the given stride. Called via JNI after decoding completes.
*
* @return Whether the buffer was resized successfully.
*/
public boolean initForYuvFrame(int width, int height, int yStride, int uvStride, int colorspace) {
this.width = width;
this.height = height;
this.colorspace = colorspace;
this.yStride = yStride;
this.uvStride = uvStride;
int uvHeight = (int) (((long) height + 1) / 2);
if (!isSafeToMultiply(yStride, height) || !isSafeToMultiply(uvStride, uvHeight)) {
return false;
}
int yLength = yStride * height;
int uvLength = uvStride * uvHeight;
int minimumYuvSize = yLength + (uvLength * 2);
if (!isSafeToMultiply(uvLength, 2) || minimumYuvSize < yLength) {
return false;
}
// Initialize data.
if (data == null || data.capacity() < minimumYuvSize) {
data = ByteBuffer.allocateDirect(minimumYuvSize);
} else {
data.position(0);
data.limit(minimumYuvSize);
}
if (yuvPlanes == null) {
yuvPlanes = new ByteBuffer[3];
}
ByteBuffer data = this.data;
ByteBuffer[] yuvPlanes = this.yuvPlanes;
// Rewrapping has to be done on every frame since the stride might have changed.
yuvPlanes[0] = data.slice();
yuvPlanes[0].limit(yLength);
data.position(yLength);
yuvPlanes[1] = data.slice();
yuvPlanes[1].limit(uvLength);
data.position(yLength + uvLength);
yuvPlanes[2] = data.slice();
yuvPlanes[2].limit(uvLength);
if (yuvStrides == null) {
yuvStrides = new int[3];
}
yuvStrides[0] = yStride;
yuvStrides[1] = uvStride;
yuvStrides[2] = uvStride;
return true;
}
public boolean initForOffsetFrames(
int offset,
int width,
int height,
int yStride,
int uvStride,
int colorspace,
int alignedHeight) {
if (yuvPlanes == null) {
yuvPlanes = new ByteBuffer[3];
}
// Data should be allocated in the native code.
if (data == null) {
return false;
}
this.width = width;
this.height = height;
this.colorspace = colorspace;
ByteBuffer[] yuvPlanes = this.yuvPlanes;
ByteBuffer data = this.data;
int yLength = yStride * height;
int uvLength = uvStride * (height >> 1);
int alignedYLength = yStride * alignedHeight;
int alignedUvLength = uvStride * (alignedHeight >> 1);
data.position(offset);
yuvPlanes[0] = data.slice();
yuvPlanes[0].limit(yLength);
data.position(alignedYLength + offset);
yuvPlanes[1] = data.slice();
yuvPlanes[1].limit(uvLength);
data.position(alignedYLength + alignedUvLength + offset);
yuvPlanes[2] = data.slice();
yuvPlanes[2].limit(uvLength);
if (yuvStrides == null) {
yuvStrides = new int[3];
}
yuvStrides[0] = yStride;
yuvStrides[1] = uvStride;
yuvStrides[2] = uvStride;
return true;
}
/**
* Configures the buffer for the given frame dimensions when passing actual frame data via {@link
* #decoderPrivate}. Called via JNI after decoding completes.
*/
public void initForPrivateFrame(int width, int height) {
this.width = width;
this.height = height;
}
/**
* Ensures that the result of multiplying individual numbers can fit into the size limit of an
* integer.
*/
private static boolean isSafeToMultiply(int a, int b) {
return a >= 0 && b >= 0 && !(b > 0 && a >= Integer.MAX_VALUE / b);
}
}
@@ -0,0 +1,19 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@NonNullApi
package androidx.media3.decoder;
import androidx.media3.common.util.NonNullApi;