Use MediaCodec Hardware Decoder with FFmpeg on Android
Integrating Android’s native MediaCodec hardware decoder into an FFmpeg workflow allows developers to leverage hardware-accelerated video decoding for improved performance, lower latency, and reduced CPU and battery consumption. This article provides a direct, technical guide on how to compile FFmpeg with MediaCodec support, initialize the Java Virtual Machine (JVM) within your C/C++ codebase, select the appropriate hardware decoder, and retrieve decoded frames efficiently.
1. Compile FFmpeg with MediaCodec Support
To use MediaCodec within FFmpeg, you must compile FFmpeg with the appropriate JNI (Java Native Interface) and MediaCodec flags enabled. When configuring your FFmpeg build script for Android, ensure you include the following flags:
--enable-jni \
--enable-mediacodec \
--enable-decoder=h264_mediacodec \
--enable-decoder=hevc_mediacodec \
--enable-decoder=vp9_mediacodecThese flags instruct FFmpeg to compile the JNI helper utilities and
map Android’s system MediaCodec APIs to FFmpeg’s decoder interface under
the *_mediacodec names.
2. Initialize the JVM in Your Native Code
Because MediaCodec is a Java-based API in Android, FFmpeg’s native C/C++ code needs access to the Java Virtual Machine (JVM) to invoke it. You must register the JVM pointer with FFmpeg before performing any decoding operations.
In your JNI initialization function (usually
JNI_OnLoad), pass the JavaVM pointer to
FFmpeg:
#include <libavcodec/jni.h>
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
// Provide the JVM pointer to FFmpeg
av_jni_set_java_vm(vm, NULL);
return JNI_VERSION_1_6;
}3. Locate and Open the MediaCodec Decoder
Instead of looking up standard software decoders (like
h264 or hevc), you must explicitly search for
the MediaCodec variant using
avcodec_find_decoder_by_name.
#include <libavcodec/avcodec.h>
// Find the H.264 MediaCodec hardware decoder
const AVCodec *codec = avcodec_find_decoder_by_name("h264_mediacodec");
if (!codec) {
// Fallback to software decoder if hardware decoder is unavailable
codec = avcodec_find_decoder(AV_CODEC_ID_H264);
}
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
// Handle allocation failure
}
// Open the codec context
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
// Handle initialization failure
}4. Feed and Retrieve Frames in the Loop
Once the decoder is initialized, the standard FFmpeg packet-to-frame loop handles the decoding. The hardware acceleration happens transparently behind the scenes.
AVPacket *packet = av_packet_alloc();
AVFrame *frame = av_frame_alloc();
// Read packets from your AVFormatContext
while (av_read_frame(format_ctx, packet) >= 0) {
if (packet->stream_index == video_stream_idx) {
// Send the compressed packet to the hardware decoder
int response = avcodec_send_packet(codec_ctx, packet);
if (response < 0) break;
while (response >= 0) {
// Retrieve decoded raw frame from the hardware decoder
response = avcodec_receive_frame(codec_ctx, frame);
if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
break;
} else if (response < 0) {
// Handle error
break;
}
// Frame is now ready.
// Note: By default, the frame data is copied back to CPU memory (YUV420P format).
process_decoded_frame(frame);
}
}
av_packet_unref(packet);
}
av_frame_free(&frame);
av_packet_free(&packet);5. Advanced: Direct Surface Rendering (Zero-Copy)
By default, FFmpeg copies decoded frame data from the GPU back into
CPU memory (YUV buffers). While simple to handle, this transfer limits
performance. For maximum efficiency, you can render directly to an
Android Surface (e.g., a SurfaceView or
TextureView) without copying the pixels back to CPU
memory.
To enable direct rendering:
- Retrieve the
ANativeWindowassociated with your Android Surface in C/C++. - Initialize the FFmpeg decoder context’s
hw_device_ctxusingAVHWDeviceTypeset toAV_HWDEVICE_TYPE_MEDIACODEC. - Pass the surface handle to the codec context before calling
avcodec_open2using theAVMediaCodecContextstructure:
#include <libavcodec/mediacodec.h>
AVMediaCodecContext *media_codec_ctx = av_mediacodec_alloc_context();
// surface is a jobject pointing to your Android Surface
media_codec_ctx->surface = surface;
codec_ctx->hwaccel_context = media_codec_ctx;When using direct Surface rendering, calling
avcodec_receive_frame will not output raw pixel data.
Instead, it triggers the hardware decoder to render the decoded frame
directly to the attached screen surface.