Initial hlsmplayer application
This commit is contained in:
@@ -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);
|
||||
}
|
||||
+88
@@ -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);
|
||||
}
|
||||
}
|
||||
+231
@@ -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;
|
||||
Reference in New Issue
Block a user