Skip to content

Commit ed9d2bc

Browse files
rabbitknightzhuo.chen
authored andcommitted
Merge actual implementation in google/ExoPlayer#7132.
1 parent b01c6ff commit ed9d2bc

File tree

10 files changed

+1139
-29
lines changed

10 files changed

+1139
-29
lines changed

libraries/decoder_ffmpeg/README.md

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# FFmpeg decoder module
22

3-
The FFmpeg module provides `FfmpegAudioRenderer`, which uses FFmpeg for decoding
4-
and can render audio encoded in a variety of formats.
3+
The FFmpeg module provides `FfmpegAudioRenderer` and `ExperimentalFfmpegVideoRenderer`, which uses FFmpeg for decoding
4+
and can render audio & video encoded in a variety of formats.
55

66
## License note
77

@@ -65,7 +65,7 @@ FFMPEG_PATH="$(pwd)"
6565
details of the available decoders, and which formats they support.
6666

6767
```
68-
ENABLED_DECODERS=(vorbis opus flac)
68+
ENABLED_DECODERS=(vorbis opus flac h264 hevc)
6969
```
7070

7171
* Add a link to the FFmpeg source code in the FFmpeg module `jni` directory.
@@ -85,6 +85,34 @@ cd "${FFMPEG_MODULE_PATH}/jni" && \
8585
"${FFMPEG_MODULE_PATH}" "${NDK_PATH}" "${HOST_PLATFORM}" "${ANDROID_ABI}" "${ENABLED_DECODERS[@]}"
8686
```
8787

88+
89+
Attempt to Rotate ``AVPixelFormat::AV_PIX_FMT_YUV420P`` & Copy the Pixels to ``ANativeWindow`` Buffer. The `libyuv` is also required.
90+
91+
* Fetch `libyuv` and checkout an appropriate branch:
92+
93+
```
94+
cd "<preferred location for libyuv>" && \
95+
git clone https://chromium.googlesource.com/libyuv/libyuv && \
96+
YUV_PATH="$(pwd)"
97+
```
98+
99+
* Add a link to the `libyuv` source code in the `libyuv` module `jni` directory.
100+
101+
```
102+
cd "${FFMPEG_MODULE_PATH}/jni" && \
103+
ln -s "$YUV_PATH" libyuv
104+
```
105+
106+
* Execute `build_yuv.sh` to build libyuv for `armeabi-v7a`, `arm64-v8a`,
107+
`x86` and `x86_64`. The script can be edited if you need to build for
108+
different architectures:
109+
110+
```
111+
cd "${FFMPEG_MODULE_PATH}/jni" && \
112+
./build_yuv.sh \
113+
"${FFMPEG_MODULE_PATH}" "${NDK_PATH}" "${ANDROID_ABI}"
114+
```
115+
88116
## Build instructions (Windows)
89117

90118
We do not provide support for building this module on Windows, however it should

libraries/decoder_ffmpeg/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ android.namespace = 'androidx.media3.decoder.ffmpeg'
1717

1818
// Configure the native build only if ffmpeg is present to avoid gradle sync
1919
// failures if ffmpeg hasn't been built according to the README instructions.
20-
if (project.file('src/main/jni/ffmpeg').exists()) {
20+
if (project.file('src/main/jni/ffmpeg').exists() && project.file('src/main/jni/libyuv').exists()) {
2121
android.externalNativeBuild.cmake.path = 'src/main/jni/CMakeLists.txt'
2222
// Should match cmake_minimum_required.
2323
android.externalNativeBuild.cmake.version = '3.21.0+'
@@ -28,6 +28,7 @@ dependencies {
2828
// TODO(b/203752526): Remove this dependency.
2929
implementation project(modulePrefix + 'lib-exoplayer')
3030
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
31+
implementation project(modulePrefix + 'lib-common')
3132
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
3233
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
3334
testImplementation project(modulePrefix + 'test-utils')
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
/*
2+
* Copyright (C) 2019 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package androidx.media3.decoder.ffmpeg;
17+
18+
import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
19+
20+
import android.view.Surface;
21+
import androidx.annotation.Nullable;
22+
import androidx.annotation.VisibleForTesting;
23+
import androidx.media3.common.C;
24+
import androidx.media3.common.Format;
25+
import androidx.media3.common.util.Assertions;
26+
import androidx.media3.common.util.UnstableApi;
27+
import androidx.media3.common.util.Util;
28+
import androidx.media3.decoder.DecoderInputBuffer;
29+
import androidx.media3.decoder.SimpleDecoder;
30+
import androidx.media3.decoder.VideoDecoderOutputBuffer;
31+
import java.nio.ByteBuffer;
32+
import java.util.List;
33+
34+
/**
35+
* Ffmpeg Video decoder.
36+
*/
37+
@VisibleForTesting(otherwise = PACKAGE_PRIVATE)
38+
@UnstableApi
39+
/* package */ final class ExperimentalFfmpegVideoDecoder
40+
extends SimpleDecoder<DecoderInputBuffer, VideoDecoderOutputBuffer, FfmpegDecoderException> {
41+
42+
private static final String TAG = "FfmpegVideoDecoder";
43+
44+
// LINT.IfChange
45+
private static final int VIDEO_DECODER_SUCCESS = 0;
46+
private static final int VIDEO_DECODER_ERROR_INVALID_DATA = -1;
47+
private static final int VIDEO_DECODER_ERROR_OTHER = -2;
48+
private static final int VIDEO_DECODER_ERROR_READ_FRAME = -3;
49+
// LINT.ThenChange(../../../../../../../jni/ffmpeg_jni.cc)
50+
51+
private final String codecName;
52+
private long nativeContext;
53+
@Nullable
54+
private final byte[] extraData;
55+
@C.VideoOutputMode
56+
private volatile int outputMode;
57+
58+
private int degree = 0;
59+
60+
/**
61+
* Creates a Ffmpeg video Decoder.
62+
*
63+
* @param numInputBuffers Number of input buffers.
64+
* @param numOutputBuffers Number of output buffers.
65+
* @param initialInputBufferSize The initial size of each input buffer, in bytes.
66+
* @param threads Number of threads libffmpeg will use to decode.
67+
* @throws FfmpegDecoderException Thrown if an exception occurs when initializing the decoder.
68+
*/
69+
public ExperimentalFfmpegVideoDecoder(
70+
int numInputBuffers, int numOutputBuffers, int initialInputBufferSize, int threads,
71+
Format format)
72+
throws FfmpegDecoderException {
73+
super(
74+
new DecoderInputBuffer[numInputBuffers],
75+
new VideoDecoderOutputBuffer[numOutputBuffers]);
76+
if (!FfmpegLibrary.isAvailable()) {
77+
throw new FfmpegDecoderException("Failed to load decoder native library.");
78+
}
79+
codecName = Assertions.checkNotNull(FfmpegLibrary.getCodecName(format.sampleMimeType));
80+
extraData = getExtraData(format.sampleMimeType, format.initializationData);
81+
degree = format.rotationDegrees;
82+
nativeContext = ffmpegInitialize(codecName, extraData, threads, degree);
83+
if (nativeContext == 0) {
84+
throw new FfmpegDecoderException("Failed to initialize decoder.");
85+
}
86+
setInitialInputBufferSize(initialInputBufferSize);
87+
}
88+
89+
/**
90+
* Returns FFmpeg-compatible codec-specific initialization data ("extra data"), or {@code null} if
91+
* not required.
92+
*/
93+
@Nullable
94+
private static byte[] getExtraData(String mimeType, List<byte[]> initializationData) {
95+
int size = 0;
96+
for (int i = 0; i < initializationData.size(); i++) {
97+
size += initializationData.get(i).length;
98+
}
99+
if (size > 0) {
100+
byte[] extra = new byte[size];
101+
ByteBuffer wrapper = ByteBuffer.wrap(extra);
102+
for (int i = 0; i < initializationData.size(); i++) {
103+
wrapper.put(initializationData.get(i));
104+
}
105+
return extra;
106+
}
107+
return null;
108+
}
109+
110+
@Override
111+
public String getName() {
112+
return "ffmpeg" + FfmpegLibrary.getVersion() + "-" + codecName;
113+
}
114+
115+
/**
116+
* Sets the output mode for frames rendered by the decoder.
117+
*
118+
* @param outputMode The output mode.
119+
*/
120+
public void setOutputMode(@C.VideoOutputMode int outputMode) {
121+
this.outputMode = outputMode;
122+
}
123+
124+
@Override
125+
protected DecoderInputBuffer createInputBuffer() {
126+
return new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
127+
}
128+
129+
@Override
130+
protected VideoDecoderOutputBuffer createOutputBuffer() {
131+
return new VideoDecoderOutputBuffer(this::releaseOutputBuffer);
132+
}
133+
134+
@Override
135+
@Nullable
136+
protected FfmpegDecoderException decode(
137+
DecoderInputBuffer inputBuffer, VideoDecoderOutputBuffer outputBuffer, boolean reset) {
138+
if (reset) {
139+
140+
nativeContext = ffmpegReset(nativeContext);
141+
if (nativeContext == 0) {
142+
return new FfmpegDecoderException("Error resetting (see logcat).");
143+
}
144+
}
145+
146+
// send packet
147+
ByteBuffer inputData = Util.castNonNull(inputBuffer.data);
148+
int inputSize = inputData.limit();
149+
// enqueue origin data
150+
int sendPacketResult = ffmpegSendPacket(nativeContext, inputData, inputSize,
151+
inputBuffer.timeUs);
152+
153+
if (sendPacketResult == VIDEO_DECODER_ERROR_INVALID_DATA) {
154+
outputBuffer.shouldBeSkipped = true;
155+
return null;
156+
} else if (sendPacketResult == VIDEO_DECODER_ERROR_READ_FRAME) {
157+
// need read frame
158+
} else if (sendPacketResult == VIDEO_DECODER_ERROR_OTHER) {
159+
return new FfmpegDecoderException("ffmpegDecode error: (see logcat)");
160+
}
161+
162+
// receive frame
163+
boolean decodeOnly = !isAtLeastOutputStartTimeUs(inputBuffer.timeUs);
164+
// We need to dequeue the decoded frame from the decoder even when the input data is
165+
// decode-only.
166+
if (!decodeOnly) {
167+
outputBuffer.init(inputBuffer.timeUs, outputMode, null);
168+
}
169+
int getFrameResult = ffmpegReceiveFrame(nativeContext, outputMode, outputBuffer, decodeOnly);
170+
if (getFrameResult == VIDEO_DECODER_ERROR_OTHER) {
171+
return new FfmpegDecoderException("ffmpegDecode error: (see logcat)");
172+
}
173+
174+
if (getFrameResult == VIDEO_DECODER_ERROR_INVALID_DATA) {
175+
outputBuffer.shouldBeSkipped = true;
176+
}
177+
178+
if (!decodeOnly) {
179+
outputBuffer.format = inputBuffer.format;
180+
}
181+
182+
return null;
183+
}
184+
185+
@Override
186+
protected FfmpegDecoderException createUnexpectedDecodeException(Throwable error) {
187+
return new FfmpegDecoderException("Unexpected decode error", error);
188+
}
189+
190+
@Override
191+
public void release() {
192+
super.release();
193+
ffmpegRelease(nativeContext);
194+
nativeContext = 0;
195+
}
196+
197+
/**
198+
* Renders output buffer to the given surface. Must only be called when in {@link
199+
* C#VIDEO_OUTPUT_MODE_SURFACE_YUV} mode.
200+
*
201+
* @param outputBuffer Output buffer.
202+
* @param surface Output surface.
203+
* @throws FfmpegDecoderException Thrown if called with invalid output mode or frame rendering
204+
* fails.
205+
*/
206+
public void renderToSurface(VideoDecoderOutputBuffer outputBuffer, Surface surface)
207+
throws FfmpegDecoderException {
208+
if (outputBuffer.mode != C.VIDEO_OUTPUT_MODE_SURFACE_YUV) {
209+
throw new FfmpegDecoderException("Invalid output mode.");
210+
}
211+
int rst = ffmpegRenderFrame(nativeContext, surface, outputBuffer, outputBuffer.width,
212+
outputBuffer.height);
213+
// Log.d(TAG, "renderToSurface: rst = " + rst + ",surface = " + surface + ",buffer = " + outputBuffer.timeUs);
214+
if (rst == VIDEO_DECODER_ERROR_OTHER) {
215+
throw new FfmpegDecoderException(
216+
"Buffer render error: ");
217+
}
218+
}
219+
220+
private native long ffmpegInitialize(String codecName, @Nullable byte[] extraData, int threads,
221+
int degree);
222+
223+
private native long ffmpegReset(long context);
224+
225+
private native void ffmpegRelease(long context);
226+
227+
private native int ffmpegRenderFrame(
228+
long context, Surface surface, VideoDecoderOutputBuffer outputBuffer,
229+
int displayedWidth,
230+
int displayedHeight);
231+
232+
/**
233+
* Decodes the encoded data passed.
234+
*
235+
* @param context Decoder context.
236+
* @param encodedData Encoded data.
237+
* @param length Length of the data buffer.
238+
* @return {@link #VIDEO_DECODER_SUCCESS} if successful, {@link #VIDEO_DECODER_ERROR_OTHER} if an
239+
* error occurred.
240+
*/
241+
private native int ffmpegSendPacket(long context, ByteBuffer encodedData, int length,
242+
long inputTime);
243+
244+
/**
245+
* Gets the decoded frame.
246+
*
247+
* @param context Decoder context.
248+
* @param outputBuffer Output buffer for the decoded frame.
249+
* @return {@link #VIDEO_DECODER_SUCCESS} if successful, {@link #VIDEO_DECODER_ERROR_INVALID_DATA}
250+
* if successful but the frame is decode-only, {@link #VIDEO_DECODER_ERROR_OTHER} if an error
251+
* occurred.
252+
*/
253+
private native int ffmpegReceiveFrame(
254+
long context, int outputMode, VideoDecoderOutputBuffer outputBuffer, boolean decodeOnly);
255+
256+
}

0 commit comments

Comments
 (0)