blob: fde0a73d8bd2ac3afb49a0cee9494887b5117417 [file] [log] [blame]
/*
* 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 com.google.android.exoplayer2.decoder;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import java.nio.ByteBuffer;
/** Video decoder output buffer containing video frame data. */
public class VideoDecoderOutputBuffer extends DecoderOutputBuffer {
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;
/** Decoder private data. Used from native code. */
public int decoderPrivate;
/** Output mode. */
@C.VideoOutputMode public 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;
/**
* 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;
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;
}
/**
* 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);
}
}