From 4737fe6907d2693d044d29ca06510cc6fcbec544 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 21 Jan 2016 09:29:39 +0100 Subject: lavc: add h264 mediacodec decoder --- Changelog | 1 + MAINTAINERS | 1 + configure | 5 + libavcodec/Makefile | 3 + libavcodec/allcodecs.c | 1 + libavcodec/mediacodec_sw_buffer.c | 339 ++++++++ libavcodec/mediacodec_sw_buffer.h | 62 ++ libavcodec/mediacodec_wrapper.c | 1705 +++++++++++++++++++++++++++++++++++++ libavcodec/mediacodec_wrapper.h | 125 +++ libavcodec/mediacodecdec.c | 570 +++++++++++++ libavcodec/mediacodecdec.h | 82 ++ libavcodec/mediacodecdec_h264.c | 336 ++++++++ libavcodec/version.h | 4 +- 13 files changed, 3232 insertions(+), 2 deletions(-) create mode 100644 libavcodec/mediacodec_sw_buffer.c create mode 100644 libavcodec/mediacodec_sw_buffer.h create mode 100644 libavcodec/mediacodec_wrapper.c create mode 100644 libavcodec/mediacodec_wrapper.h create mode 100644 libavcodec/mediacodecdec.c create mode 100644 libavcodec/mediacodecdec.h create mode 100644 libavcodec/mediacodecdec_h264.c diff --git a/Changelog b/Changelog index 0e70724f1a..1f57f5e9e8 100644 --- a/Changelog +++ b/Changelog @@ -11,6 +11,7 @@ version : - bench and abench filters - ciescope filter - protocol blacklisting API +- MediaCodec H264 decoding version 3.0: diff --git a/MAINTAINERS b/MAINTAINERS index 6ab9f71b4d..531c21d74c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -308,6 +308,7 @@ Codecs: Hardware acceleration: crystalhd.c Philip Langdale dxva2* Hendrik Leppkes, Laurent Aimar + mediacodec* Matthieu Bouron vaapi* Gwenole Beauchesne vda* Sebastien Zwickert vdpau* Philip Langdale, Carl Eugen Hoyos diff --git a/configure b/configure index 296e03c384..3299b1bd11 100755 --- a/configure +++ b/configure @@ -276,6 +276,7 @@ External library support: --enable-libzvbi enable teletext support via libzvbi [no] --disable-lzma disable lzma [autodetect] --enable-decklink enable Blackmagic DeckLink I/O support [no] + --enable-mediacodec enable Android MediaCodec support [no] --enable-mmal enable decoding via MMAL [no] --enable-netcdf enable NetCDF, needed for sofalizer filter [no] --enable-nvenc enable NVIDIA NVENC support [no] @@ -1501,6 +1502,7 @@ EXTERNAL_LIBRARY_LIST=" libzmq libzvbi lzma + mediacodec mmal netcdf nvenc @@ -2505,6 +2507,8 @@ h264_d3d11va_hwaccel_deps="d3d11va" h264_d3d11va_hwaccel_select="h264_decoder" h264_dxva2_hwaccel_deps="dxva2" h264_dxva2_hwaccel_select="h264_decoder" +h264_mediacodec_decoder_deps="mediacodec" +h264_mediacodec_decoder_select="h264_mp4toannexb_bsf h264_parser" h264_mmal_decoder_deps="mmal" h264_mmal_decoder_select="mmal" h264_mmal_hwaccel_deps="mmal" @@ -5672,6 +5676,7 @@ enabled libzmq && require_pkg_config libzmq zmq.h zmq_ctx_new enabled libzvbi && require libzvbi libzvbi.h vbi_decoder_new -lzvbi && { check_cpp_condition libzvbi.h "VBI_VERSION_MAJOR > 0 || VBI_VERSION_MINOR > 2 || VBI_VERSION_MINOR == 2 && VBI_VERSION_MICRO >= 28" || enabled gpl || die "ERROR: libzvbi requires version 0.2.28 or --enable-gpl."; } +enabled mediacodec && { enabled jni || die "ERROR: mediacodec requires --enable-jni"; } enabled mmal && { check_lib interface/mmal/mmal.h mmal_port_connect -lmmal_core -lmmal_util -lmmal_vc_client -lbcm_host || { ! enabled cross_compile && { add_cflags -isystem/opt/vc/include/ -isystem/opt/vc/include/interface/vmcs_host/linux -isystem/opt/vc/include/interface/vcos/pthreads -fgnu89-inline ; diff --git a/libavcodec/Makefile b/libavcodec/Makefile index bb7df0f246..ee9a96267b 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -91,6 +91,7 @@ OBJS-$(CONFIG_LSP) += lsp.o OBJS-$(CONFIG_LZF) += lzf.o OBJS-$(CONFIG_MDCT) += mdct_fixed.o mdct_float.o mdct_fixed_32.o OBJS-$(CONFIG_ME_CMP) += me_cmp.o +OBJS-$(CONFIG_MEDIACODEC) += mediacodecdec.o mediacodec_wrapper.o mediacodec_sw_buffer.o OBJS-$(CONFIG_MPEG_ER) += mpeg_er.o OBJS-$(CONFIG_MPEGAUDIO) += mpegaudio.o mpegaudiodata.o \ mpegaudiodecheader.o @@ -306,6 +307,7 @@ OBJS-$(CONFIG_H264_DECODER) += h264.o h264_cabac.o h264_cavlc.o \ h264_direct.o h264_loopfilter.o \ h264_mb.o h264_picture.o h264_ps.o \ h264_refs.o h264_sei.o h264_slice.o +OBJS-$(CONFIG_H264_MEDIACODEC_DECODER) += mediacodecdec_h264.o OBJS-$(CONFIG_H264_MMAL_DECODER) += mmaldec.o OBJS-$(CONFIG_H264_VDA_DECODER) += vda_h264_dec.o OBJS-$(CONFIG_H264_QSV_DECODER) += qsvdec_h2645.o @@ -944,6 +946,7 @@ SKIPHEADERS-$(CONFIG_LIBSCHROEDINGER) += libschroedinger.h SKIPHEADERS-$(CONFIG_LIBUTVIDEO) += libutvideo.h SKIPHEADERS-$(CONFIG_LIBVPX) += libvpx.h SKIPHEADERS-$(CONFIG_LIBWEBP_ENCODER) += libwebpenc_common.h +SKIPHEADERS-$(CONFIG_MEDIACODEC) += mediacodecdec.h mediacodec_wrapper.h mediacodec_sw_buffer.h SKIPHEADERS-$(CONFIG_QSV) += qsv.h qsv_internal.h SKIPHEADERS-$(CONFIG_QSVDEC) += qsvdec.h SKIPHEADERS-$(CONFIG_QSVENC) += qsvenc.h diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index 91c1c5c9f3..3a59d13ec6 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -196,6 +196,7 @@ void avcodec_register_all(void) REGISTER_ENCDEC (H263P, h263p); REGISTER_DECODER(H264, h264); REGISTER_DECODER(H264_CRYSTALHD, h264_crystalhd); + REGISTER_DECODER(H264_MEDIACODEC, h264_mediacodec); REGISTER_DECODER(H264_MMAL, h264_mmal); REGISTER_DECODER(H264_QSV, h264_qsv); REGISTER_DECODER(H264_VDA, h264_vda); diff --git a/libavcodec/mediacodec_sw_buffer.c b/libavcodec/mediacodec_sw_buffer.c new file mode 100644 index 0000000000..df75754ba2 --- /dev/null +++ b/libavcodec/mediacodec_sw_buffer.c @@ -0,0 +1,339 @@ +/* + * Android MediaCodec software buffer copy functions + * + * Copyright (c) 2015-2016 Matthieu Bouron + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include "libavutil/frame.h" +#include "libavutil/mem.h" + +#include "avcodec.h" +#include "mediacodecdec.h" +#include "mediacodec_wrapper.h" +#include "mediacodec_sw_buffer.h" + +#define QCOM_TILE_WIDTH 64 +#define QCOM_TILE_HEIGHT 32 +#define QCOM_TILE_SIZE (QCOM_TILE_WIDTH * QCOM_TILE_HEIGHT) +#define QCOM_TILE_GROUP_SIZE (4 * QCOM_TILE_SIZE) + +/** + * The code handling the the various YUV color formats is taken from the + * GStreamer project. + * + * Gstreamer reference: + * https://cgit.freedesktop.org/gstreamer/gst-plugins-bad/tree/sys/androidmedia/ + * + * Copyright (C) 2012, Collabora Ltd. + * Author: Sebastian Dröge + * + * Copyright (C) 2012, Rafaël Carré + * + * Copyright (C) 2015, Sebastian Dröge + * + * Copyright (C) 2014-2015, Collabora Ltd. + * Author: Matthieu Bouron + * + * Copyright (C) 2015, Edward Hervey + * Author: Edward Hervey + * + * Copyright (C) 2015, Matthew Waters + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +void ff_mediacodec_sw_buffer_copy_yuv420_planar(AVCodecContext *avctx, + MediaCodecDecContext *s, + uint8_t *data, + size_t size, + FFAMediaCodecBufferInfo *info, + AVFrame *frame) +{ + int i; + uint8_t *src = NULL; + + for (i = 0; i < 3; i++) { + int stride = s->stride; + int height; + + src = data + info->offset; + if (i == 0) { + height = avctx->height; + + src += s->crop_top * s->stride; + src += s->crop_left; + } else { + height = avctx->height / 2; + stride = (s->stride + 1) / 2; + + src += s->slice_height * s->stride; + + if (i == 2) { + src += ((s->slice_height + 1) / 2) * stride; + } + + src += s->crop_top * stride; + src += (s->crop_left / 2); + } + + if (frame->linesize[i] == stride) { + memcpy(frame->data[i], src, height * stride); + } else { + int j, width; + uint8_t *dst = frame->data[i]; + + if (i == 0) { + width = avctx->width; + } else if (i == 1) { + width = FFMIN(frame->linesize[i], FFALIGN(avctx->width, 2)); + } + + for (j = 0; j < height; j++) { + memcpy(dst, src, width); + src += stride; + dst += frame->linesize[i]; + } + } + } +} + +void ff_mediacodec_sw_buffer_copy_yuv420_semi_planar(AVCodecContext *avctx, + MediaCodecDecContext *s, + uint8_t *data, + size_t size, + FFAMediaCodecBufferInfo *info, + AVFrame *frame) +{ + int i; + uint8_t *src = NULL; + + for (i = 0; i < 2; i++) { + int height; + + src = data + info->offset; + if (i == 0) { + height = avctx->height; + + src += s->crop_top * s->stride; + src += s->crop_left; + } else if (i == 1) { + height = avctx->height / 2; + + src += s->slice_height * s->stride; + src += s->crop_top * s->stride; + src += s->crop_left; + } + + if (frame->linesize[i] == s->stride) { + memcpy(frame->data[i], src, height * s->stride); + } else { + int j, width; + uint8_t *dst = frame->data[i]; + + if (i == 0) { + width = avctx->width; + } else if (i == 1) { + width = FFMIN(frame->linesize[i], FFALIGN(avctx->width, 2)); + } + + for (j = 0; j < height; j++) { + memcpy(dst, src, width); + src += s->stride; + dst += frame->linesize[i]; + } + } + } +} + + + +void ff_mediacodec_sw_buffer_copy_yuv420_packed_semi_planar(AVCodecContext *avctx, + MediaCodecDecContext *s, + uint8_t *data, + size_t size, + FFAMediaCodecBufferInfo *info, + AVFrame *frame) +{ + int i; + uint8_t *src = NULL; + + for (i = 0; i < 2; i++) { + int height; + + src = data + info->offset; + if (i == 0) { + height = avctx->height; + } else if (i == 1) { + height = avctx->height / 2; + + src += (s->slice_height - s->crop_top / 2) * s->stride; + + src += s->crop_top * s->stride; + src += s->crop_left; + } + + if (frame->linesize[i] == s->stride) { + memcpy(frame->data[i], src, height * s->stride); + } else { + int j, width; + uint8_t *dst = frame->data[i]; + + if (i == 0) { + width = avctx->width; + } else if (i == 1) { + width = FFMIN(frame->linesize[i], FFALIGN(avctx->width, 2)); + } + + for (j = 0; j < height; j++) { + memcpy(dst, src, width); + src += s->stride; + dst += frame->linesize[i]; + } + } + } +} + +/** + * The code handling the QCOM_FormatYUV420PackedSemiPlanar64x32Tile2m8ka + * color format is taken from the VLC project. + * + * VLC reference: + * http://git.videolan.org/?p=vlc.git;a=blob;f=modules/codec/omxil/qcom.c;hb=HEAD + * + * VLC copyright notice: + * + ***************************************************************************** + * qcom.c : pixel format translation for Qualcomm tiled nv12 + ***************************************************************************** + * Copyright © 2012 Rafaël Carré + * + * Authors: Rafaël Carré + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + * + */ + +static size_t qcom_tile_pos(size_t x, size_t y, size_t w, size_t h) +{ + size_t flim = x + (y & ~1) * w; + + if (y & 1) { + flim += (x & ~3) + 2; + } else if ((h & 1) == 0 || y != (h - 1)) { + flim += (x + 2) & ~3; + } + + return flim; +} + +void ff_mediacodec_sw_buffer_copy_yuv420_packed_semi_planar_64x32Tile2m8ka(AVCodecContext *avctx, + MediaCodecDecContext *s, + uint8_t *data, + size_t size, + FFAMediaCodecBufferInfo *info, + AVFrame *frame) +{ + size_t width = frame->width; + size_t linesize = frame->linesize[0]; + size_t height = frame->height; + + const size_t tile_w = (width - 1) / QCOM_TILE_WIDTH + 1; + const size_t tile_w_align = (tile_w + 1) & ~1; + const size_t tile_h_luma = (height - 1) / QCOM_TILE_HEIGHT + 1; + const size_t tile_h_chroma = (height / 2 - 1) / QCOM_TILE_HEIGHT + 1; + + size_t luma_size = tile_w_align * tile_h_luma * QCOM_TILE_SIZE; + if((luma_size % QCOM_TILE_GROUP_SIZE) != 0) + luma_size = (((luma_size - 1) / QCOM_TILE_GROUP_SIZE) + 1) * QCOM_TILE_GROUP_SIZE; + + for(size_t y = 0; y < tile_h_luma; y++) { + size_t row_width = width; + for(size_t x = 0; x < tile_w; x++) { + size_t tile_width = row_width; + size_t tile_height = height; + /* dest luma memory index for this tile */ + size_t luma_idx = y * QCOM_TILE_HEIGHT * linesize + x * QCOM_TILE_WIDTH; + /* dest chroma memory index for this tile */ + /* XXX: remove divisions */ + size_t chroma_idx = (luma_idx / linesize) * linesize / 2 + (luma_idx % linesize); + + /* luma source pointer for this tile */ + const uint8_t *src_luma = data + + qcom_tile_pos(x, y,tile_w_align, tile_h_luma) * QCOM_TILE_SIZE; + + /* chroma source pointer for this tile */ + const uint8_t *src_chroma = data + luma_size + + qcom_tile_pos(x, y/2, tile_w_align, tile_h_chroma) * QCOM_TILE_SIZE; + if (y & 1) + src_chroma += QCOM_TILE_SIZE/2; + + /* account for right columns */ + if (tile_width > QCOM_TILE_WIDTH) + tile_width = QCOM_TILE_WIDTH; + + /* account for bottom rows */ + if (tile_height > QCOM_TILE_HEIGHT) + tile_height = QCOM_TILE_HEIGHT; + + tile_height /= 2; + while (tile_height--) { + memcpy(frame->data[0] + luma_idx, src_luma, tile_width); + src_luma += QCOM_TILE_WIDTH; + luma_idx += linesize; + + memcpy(frame->data[0] + luma_idx, src_luma, tile_width); + src_luma += QCOM_TILE_WIDTH; + luma_idx += linesize; + + memcpy(frame->data[1] + chroma_idx, src_chroma, tile_width); + src_chroma += QCOM_TILE_WIDTH; + chroma_idx += linesize; + } + row_width -= QCOM_TILE_WIDTH; + } + height -= QCOM_TILE_HEIGHT; + } +} diff --git a/libavcodec/mediacodec_sw_buffer.h b/libavcodec/mediacodec_sw_buffer.h new file mode 100644 index 0000000000..c29de0842d --- /dev/null +++ b/libavcodec/mediacodec_sw_buffer.h @@ -0,0 +1,62 @@ +/* + * Android MediaCodec software buffer copy functions + * + * Copyright (c) 2015-2016 Matthieu Bouron + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_MEDIACODEC_SW_BUFFER_H +#define AVCODEC_MEDIACODEC_SW_BUFFER_H + +#include + +#include "libavutil/frame.h" + +#include "avcodec.h" +#include "mediacodecdec.h" +#include "mediacodec_wrapper.h" + +void ff_mediacodec_sw_buffer_copy_yuv420_planar(AVCodecContext *avctx, + MediaCodecDecContext *s, + uint8_t *data, + size_t size, + FFAMediaCodecBufferInfo *info, + AVFrame *frame); + +void ff_mediacodec_sw_buffer_copy_yuv420_semi_planar(AVCodecContext *avctx, + MediaCodecDecContext *s, + uint8_t *data, + size_t size, + FFAMediaCodecBufferInfo *info, + AVFrame *frame); + +void ff_mediacodec_sw_buffer_copy_yuv420_packed_semi_planar(AVCodecContext *avctx, + MediaCodecDecContext *s, + uint8_t *data, + size_t size, + FFAMediaCodecBufferInfo *info, + AVFrame *frame); + +void ff_mediacodec_sw_buffer_copy_yuv420_packed_semi_planar_64x32Tile2m8ka(AVCodecContext *avctx, + MediaCodecDecContext *s, + uint8_t *data, + size_t size, + FFAMediaCodecBufferInfo *info, + AVFrame *frame); + +#endif /* AVCODEC_MEDIACODEC_SW_BUFFER_H */ diff --git a/libavcodec/mediacodec_wrapper.c b/libavcodec/mediacodec_wrapper.c new file mode 100644 index 0000000000..3f8db64ca2 --- /dev/null +++ b/libavcodec/mediacodec_wrapper.c @@ -0,0 +1,1705 @@ +/* + * Android MediaCodec Wrapper + * + * Copyright (c) 2015-2016 Matthieu Bouron + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "libavutil/avassert.h" +#include "libavutil/mem.h" +#include "libavutil/avstring.h" + +#include "ffjni.h" +#include "version.h" +#include "mediacodec_wrapper.h" + +struct JNIAMediaCodecListFields { + + jclass mediaformat_class; + jmethodID create_video_format_id; + + jclass mediacodec_list_class; + jmethodID init_id; + jmethodID find_decoder_for_format_id; + + jmethodID get_codec_count_id; + jmethodID get_codec_info_at_id; + + jclass mediacodec_info_class; + jmethodID get_name_id; + jmethodID get_supported_types_id; + jmethodID is_encoder_id; + +} JNIAMediaCodecListFields; + +static const struct FFJniField jfields_mapping[] = { + { "android/media/MediaFormat", NULL, NULL, FF_JNI_CLASS, offsetof(struct JNIAMediaCodecListFields, mediaformat_class), 1 }, + { "android/media/MediaFormat", "createVideoFormat", "(Ljava/lang/String;II)Landroid/media/MediaFormat;", FF_JNI_STATIC_METHOD, offsetof(struct JNIAMediaCodecListFields, create_video_format_id), 1 }, + + { "android/media/MediaCodecList", NULL, NULL, FF_JNI_CLASS, offsetof(struct JNIAMediaCodecListFields, mediacodec_list_class), 1 }, + { "android/media/MediaCodecList", "", "(I)V", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecListFields, init_id), 0 }, + { "android/media/MediaCodecList", "findDecoderForFormat", "(Landroid/media/MediaFormat;)Ljava/lang/String;", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecListFields, find_decoder_for_format_id), 0 }, + + { "android/media/MediaCodecList", "getCodecCount", "()I", FF_JNI_STATIC_METHOD, offsetof(struct JNIAMediaCodecListFields, get_codec_count_id), 1 }, + { "android/media/MediaCodecList", "getCodecInfoAt", "(I)Landroid/media/MediaCodecInfo;", FF_JNI_STATIC_METHOD, offsetof(struct JNIAMediaCodecListFields, get_codec_info_at_id), 1 }, + + { "android/media/MediaCodecInfo", NULL, NULL, FF_JNI_CLASS, offsetof(struct JNIAMediaCodecListFields, mediacodec_info_class), 1 }, + { "android/media/MediaCodecInfo", "getName", "()Ljava/lang/String;", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecListFields, get_name_id), 1 }, + { "android/media/MediaCodecInfo", "getSupportedTypes", "()[Ljava/lang/String;", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecListFields, get_supported_types_id), 1 }, + { "android/media/MediaCodecInfo", "isEncoder", "()Z", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecListFields, is_encoder_id), 1 }, + + { NULL } +}; + +#define JNI_ATTACH_ENV_OR_RETURN(env, attached, log_ctx, ret) do { \ + (env) = ff_jni_attach_env(attached, log_ctx); \ + if (!(env)) { \ + return ret; \ + } \ +} while (0) + +#define JNI_ATTACH_ENV_OR_RETURN_VOID(env, attached, log_ctx) do { \ + (env) = ff_jni_attach_env(attached, log_ctx); \ + if (!(env)) { \ + return; \ + } \ +} while (0) + +#define JNI_DETACH_ENV(attached, log_ctx) do { \ + if (attached) \ + ff_jni_detach_env(log_ctx); \ +} while (0) + +char *ff_AMediaCodecList_getCodecNameByType(const char *mime, int width, int height, void *log_ctx) +{ + int ret; + char *name = NULL; + char *supported_type = NULL; + + int attached = 0; + JNIEnv *env = NULL; + struct JNIAMediaCodecListFields jfields = { 0 }; + + jobject format = NULL; + jobject codec = NULL; + jstring tmp = NULL; + + jobject info = NULL; + jobject type = NULL; + jobjectArray types = NULL; + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, log_ctx, NULL); + + if ((ret = ff_jni_init_jfields(env, &jfields, jfields_mapping, 0, log_ctx)) < 0) { + goto done; + } + + if (jfields.init_id && jfields.find_decoder_for_format_id) { + tmp = ff_jni_utf_chars_to_jstring(env, mime, log_ctx); + if (!tmp) { + goto done; + } + + format = (*env)->CallStaticObjectMethod(env, jfields.mediaformat_class, jfields.create_video_format_id, tmp, width, height); + if (ff_jni_exception_check(env, 1, log_ctx) < 0) { + goto done; + } + (*env)->DeleteLocalRef(env, tmp); + + codec = (*env)->NewObject(env, jfields.mediacodec_list_class, jfields.init_id, 0); + if (ff_jni_exception_check(env, 1, log_ctx) < 0) { + goto done; + } + + tmp = (*env)->CallObjectMethod(env, codec, jfields.find_decoder_for_format_id, format); + if (ff_jni_exception_check(env, 1, log_ctx) < 0) { + goto done; + } + if (!tmp) { + av_log(NULL, AV_LOG_ERROR, "Could not find decoder in media codec list " + "for format { mime=%s width=%d height=%d }\n", mime, width, height); + goto done; + } + + name = ff_jni_jstring_to_utf_chars(env, tmp, log_ctx); + if (!name) { + goto done; + } + + } else { + int i; + int codec_count; + + codec_count = (*env)->CallStaticIntMethod(env, jfields.mediacodec_list_class, jfields.get_codec_count_id); + if (ff_jni_exception_check(env, 1, log_ctx) < 0) { + goto done; + } + + for(i = 0; i < codec_count; i++) { + int j; + int type_count; + int is_encoder; + + info = (*env)->CallStaticObjectMethod(env, jfields.mediacodec_list_class, jfields.get_codec_info_at_id, i); + if (ff_jni_exception_check(env, 1, log_ctx) < 0) { + goto done; + } + + types = (*env)->CallObjectMethod(env, info, jfields.get_supported_types_id); + if (ff_jni_exception_check(env, 1, log_ctx) < 0) { + goto done; + } + + is_encoder = (*env)->CallBooleanMethod(env, info, jfields.is_encoder_id); + if (ff_jni_exception_check(env, 1, log_ctx) < 0) { + goto done; + } + + if (is_encoder) { + continue; + } + + type_count = (*env)->GetArrayLength(env, types); + for (j = 0; j < type_count; j++) { + + type = (*env)->GetObjectArrayElement(env, types, j); + if (ff_jni_exception_check(env, 1, log_ctx) < 0) { + goto done; + } + + supported_type = ff_jni_jstring_to_utf_chars(env, type, log_ctx); + if (!supported_type) { + goto done; + } + + if (!av_strcasecmp(supported_type, mime)) { + jobject codec_name; + + codec_name = (*env)->CallObjectMethod(env, info, jfields.get_name_id); + if (ff_jni_exception_check(env, 1, log_ctx) < 0) { + goto done; + } + + name = ff_jni_jstring_to_utf_chars(env, codec_name, log_ctx); + if (!name) { + goto done; + } + + if (strstr(name, "OMX.google")) { + av_freep(&name); + continue; + } + } + + av_freep(&supported_type); + } + + (*env)->DeleteLocalRef(env, info); + info = NULL; + + (*env)->DeleteLocalRef(env, types); + types = NULL; + + if (name) + break; + } + } + +done: + if (format) { + (*env)->DeleteLocalRef(env, format); + } + + if (codec) { + (*env)->DeleteLocalRef(env, codec); + } + + if (tmp) { + (*env)->DeleteLocalRef(env, tmp); + } + + if (info) { + (*env)->DeleteLocalRef(env, info); + } + + if (type) { + (*env)->DeleteLocalRef(env, type); + } + + if (types) { + (*env)->DeleteLocalRef(env, types); + } + + av_freep(&supported_type); + + ff_jni_reset_jfields(env, &jfields, jfields_mapping, 0, log_ctx); + + JNI_DETACH_ENV(attached, log_ctx); + + return name; +} + +struct JNIAMediaFormatFields { + + jclass clazz; + + jmethodID init_id; + + jmethodID get_integer_id; + jmethodID get_long_id; + jmethodID get_float_id; + jmethodID get_bytebuffer_id; + jmethodID get_string_id; + + jmethodID set_integer_id; + jmethodID set_long_id; + jmethodID set_float_id; + jmethodID set_bytebuffer_id; + jmethodID set_string_id; + + jmethodID to_string_id; + +} JNIAMediaFormatFields; + +static const struct FFJniField jni_amediaformat_mapping[] = { + { "android/media/MediaFormat", NULL, NULL, FF_JNI_CLASS, offsetof(struct JNIAMediaFormatFields, clazz), 1 }, + + { "android/media/MediaFormat", "", "()V", FF_JNI_METHOD, offsetof(struct JNIAMediaFormatFields, init_id), 1 }, + + { "android/media/MediaFormat", "getInteger", "(Ljava/lang/String;)I", FF_JNI_METHOD, offsetof(struct JNIAMediaFormatFields, get_integer_id), 1 }, + { "android/media/MediaFormat", "getLong", "(Ljava/lang/String;)J", FF_JNI_METHOD, offsetof(struct JNIAMediaFormatFields, get_long_id), 1 }, + { "android/media/MediaFormat", "getFloat", "(Ljava/lang/String;)F", FF_JNI_METHOD, offsetof(struct JNIAMediaFormatFields, get_float_id), 1 }, + { "android/media/MediaFormat", "getByteBuffer", "(Ljava/lang/String;)Ljava/nio/ByteBuffer;", FF_JNI_METHOD, offsetof(struct JNIAMediaFormatFields, get_bytebuffer_id), 1 }, + { "android/media/MediaFormat", "getString", "(Ljava/lang/String;)Ljava/lang/String;", FF_JNI_METHOD, offsetof(struct JNIAMediaFormatFields, get_string_id), 1 }, + + { "android/media/MediaFormat", "setInteger", "(Ljava/lang/String;I)V", FF_JNI_METHOD, offsetof(struct JNIAMediaFormatFields, set_integer_id), 1 }, + { "android/media/MediaFormat", "setLong", "(Ljava/lang/String;J)V", FF_JNI_METHOD, offsetof(struct JNIAMediaFormatFields, set_long_id), 1 }, + { "android/media/MediaFormat", "setFloat", "(Ljava/lang/String;F)V", FF_JNI_METHOD, offsetof(struct JNIAMediaFormatFields, set_float_id), 1 }, + { "android/media/MediaFormat", "setByteBuffer", "(Ljava/lang/String;Ljava/nio/ByteBuffer;)V", FF_JNI_METHOD, offsetof(struct JNIAMediaFormatFields, set_bytebuffer_id), 1 }, + { "android/media/MediaFormat", "setString", "(Ljava/lang/String;Ljava/lang/String;)V", FF_JNI_METHOD, offsetof(struct JNIAMediaFormatFields, set_string_id), 1 }, + + { "android/media/MediaFormat", "toString", "()Ljava/lang/String;", FF_JNI_METHOD, offsetof(struct JNIAMediaFormatFields, to_string_id), 1 }, + + { NULL } +}; + +static const AVClass amediaformat_class = { + .class_name = "amediaformat", + .item_name = av_default_item_name, + .version = LIBAVCODEC_VERSION_INT, +}; + +struct FFAMediaFormat { + + const AVClass *class; + struct JNIAMediaFormatFields jfields; + jobject object; +}; + +FFAMediaFormat *ff_AMediaFormat_new(void) +{ + int attached = 0; + JNIEnv *env = NULL; + FFAMediaFormat *format = NULL; + + format = av_mallocz(sizeof(FFAMediaFormat)); + if (!format) { + return NULL; + } + format->class = &amediaformat_class; + + env = ff_jni_attach_env(&attached, format); + if (!env) { + av_freep(&format); + return NULL; + } + + if (ff_jni_init_jfields(env, &format->jfields, jni_amediaformat_mapping, 1, format) < 0) { + goto fail; + } + + format->object = (*env)->NewObject(env, format->jfields.clazz, format->jfields.init_id); + if (!format->object) { + goto fail; + } + + format->object = (*env)->NewGlobalRef(env, format->object); + if (!format->object) { + goto fail; + } + + JNI_DETACH_ENV(attached, format); + + return format; +fail: + ff_jni_reset_jfields(env, &format->jfields, jni_amediaformat_mapping, 1, format); + + JNI_DETACH_ENV(attached, format); + + av_freep(&format); + + return NULL; +} + +static FFAMediaFormat *ff_AMediaFormat_newFromObject(void *object) +{ + int attached = 0; + JNIEnv *env = NULL; + FFAMediaFormat *format = NULL; + + format = av_mallocz(sizeof(FFAMediaFormat)); + if (!format) { + return NULL; + } + format->class = &amediaformat_class; + + env = ff_jni_attach_env(&attached, format); + if (!env) { + av_freep(&format); + return NULL; + } + + if (ff_jni_init_jfields(env, &format->jfields, jni_amediaformat_mapping, 1, format) < 0) { + goto fail; + } + + format->object = (*env)->NewGlobalRef(env, object); + if (!format->object) { + goto fail; + } + + JNI_DETACH_ENV(attached, format); + + return format; +fail: + ff_jni_reset_jfields(env, &format->jfields, jni_amediaformat_mapping, 1, format); + + JNI_DETACH_ENV(attached, format); + + av_freep(&format); + + return NULL; +} + + +int ff_AMediaFormat_delete(FFAMediaFormat* format) +{ + int ret = 0; + + int attached = 0; + JNIEnv *env = NULL; + + if (!format) { + return 0; + } + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, format, AVERROR_EXTERNAL); + + (*env)->DeleteGlobalRef(env, format->object); + format->object = NULL; + + ff_jni_reset_jfields(env, &format->jfields, jni_amediaformat_mapping, 1, format); + + JNI_DETACH_ENV(attached, format); + + av_freep(&format); + + return ret; +} + +char* ff_AMediaFormat_toString(FFAMediaFormat* format) +{ + char *ret = NULL; + + int attached = 0; + JNIEnv *env = NULL; + jstring description = NULL; + + av_assert0(format != NULL); + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, format, NULL); + + description = (*env)->CallObjectMethod(env, format->object, format->jfields.to_string_id); + if (ff_jni_exception_check(env, 1, NULL) < 0) { + goto fail; + } + + ret = ff_jni_jstring_to_utf_chars(env, description, format); +fail: + + if (description) { + (*env)->DeleteLocalRef(env, description); + } + + JNI_DETACH_ENV(attached, format); + + return ret; +} + +int ff_AMediaFormat_getInt32(FFAMediaFormat* format, const char *name, int32_t *out) +{ + int ret = 1; + + int attached = 0; + JNIEnv *env = NULL; + jstring key = NULL; + + av_assert0(format != NULL); + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, format, 0); + + key = ff_jni_utf_chars_to_jstring(env, name, format); + if (!key) { + ret = 0; + goto fail; + } + + *out = (*env)->CallIntMethod(env, format->object, format->jfields.get_integer_id, key); + if ((ret = ff_jni_exception_check(env, 1, format)) < 0) { + ret = 0; + goto fail; + } + + ret = 1; +fail: + if (key) { + (*env)->DeleteLocalRef(env, key); + } + + JNI_DETACH_ENV(attached, format); + + return ret; +} + +int ff_AMediaFormat_getInt64(FFAMediaFormat* format, const char *name, int64_t *out) +{ + int ret = 1; + + int attached = 0; + JNIEnv *env = NULL; + jstring key = NULL; + + av_assert0(format != NULL); + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, format, 0); + + key = ff_jni_utf_chars_to_jstring(env, name, format); + if (!key) { + ret = 0; + goto fail; + } + + *out = (*env)->CallLongMethod(env, format->object, format->jfields.get_long_id, key); + if ((ret = ff_jni_exception_check(env, 1, format)) < 0) { + ret = 0; + goto fail; + } + + ret = 1; +fail: + if (key) { + (*env)->DeleteLocalRef(env, key); + } + + JNI_DETACH_ENV(attached, format); + + return ret; +} + +int ff_AMediaFormat_getFloat(FFAMediaFormat* format, const char *name, float *out) +{ + int ret = 1; + + int attached = 0; + JNIEnv *env = NULL; + jstring key = NULL; + + av_assert0(format != NULL); + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, format, 0); + + key = ff_jni_utf_chars_to_jstring(env, name, format); + if (!key) { + ret = 0; + goto fail; + } + + *out = (*env)->CallFloatMethod(env, format->object, format->jfields.get_float_id, key); + if ((ret = ff_jni_exception_check(env, 1, format)) < 0) { + ret = 0; + goto fail; + } + + ret = 1; +fail: + if (key) { + (*env)->DeleteLocalRef(env, key); + } + + JNI_DETACH_ENV(attached, format); + + return ret; +} + +int ff_AMediaFormat_getBuffer(FFAMediaFormat* format, const char *name, void** data, size_t *size) +{ + int ret = 1; + + int attached = 0; + JNIEnv *env = NULL; + jstring key = NULL; + jobject result = NULL; + + av_assert0(format != NULL); + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, format, 0); + + key = ff_jni_utf_chars_to_jstring(env, name, format); + if (!key) { + ret = 0; + goto fail; + } + + result = (*env)->CallObjectMethod(env, format->object, format->jfields.get_bytebuffer_id, key); + if ((ret = ff_jni_exception_check(env, 1, format)) < 0) { + ret = 0; + goto fail; + } + + *data = (*env)->GetDirectBufferAddress(env, result); + *size = (*env)->GetDirectBufferCapacity(env, result); + + if (*data && *size) { + void *src = *data; + *data = av_malloc(*size); + if (!*data) { + ret = 0; + goto fail; + } + + memcpy(*data, src, *size); + } + + ret = 1; +fail: + if (key) { + (*env)->DeleteLocalRef(env, key); + } + + if (result) { + (*env)->DeleteLocalRef(env, result); + } + + JNI_DETACH_ENV(attached, format); + + return ret; +} + +int ff_AMediaFormat_getString(FFAMediaFormat* format, const char *name, const char **out) +{ + int ret = 1; + + int attached = 0; + JNIEnv *env = NULL; + jstring key = NULL; + jstring result = NULL; + + av_assert0(format != NULL); + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, format, 0); + + key = ff_jni_utf_chars_to_jstring(env, name, format); + if (!key) { + ret = 0; + goto fail; + } + + result = (*env)->CallObjectMethod(env, format->object, format->jfields.get_string_id, key); + if ((ret = ff_jni_exception_check(env, 1, format)) < 0) { + ret = 0; + goto fail; + } + + *out = ff_jni_jstring_to_utf_chars(env, result, format); + if (!*out) { + ret = 0; + goto fail; + } + + ret = 1; +fail: + if (key) { + (*env)->DeleteLocalRef(env, key); + } + + if (result) { + (*env)->DeleteLocalRef(env, result); + } + + JNI_DETACH_ENV(attached, format); + + return ret; +} + + +void ff_AMediaFormat_setInt32(FFAMediaFormat* format, const char* name, int32_t value) +{ + int attached = 0; + JNIEnv *env = NULL; + jstring key = NULL; + + av_assert0(format != NULL); + + JNI_ATTACH_ENV_OR_RETURN_VOID(env, &attached, format); + + key = ff_jni_utf_chars_to_jstring(env, name, format); + if (!key) { + goto fail; + } + + (*env)->CallVoidMethod(env, format->object, format->jfields.set_integer_id, key, value); + if (ff_jni_exception_check(env, 1, format) < 0) { + goto fail; + } + +fail: + if (key) { + (*env)->DeleteLocalRef(env, key); + } + + JNI_DETACH_ENV(attached, format); +} + +void ff_AMediaFormat_setInt64(FFAMediaFormat* format, const char* name, int64_t value) +{ + int attached = 0; + JNIEnv *env = NULL; + jstring key = NULL; + + av_assert0(format != NULL); + + JNI_ATTACH_ENV_OR_RETURN_VOID(env, &attached, format); + + key = ff_jni_utf_chars_to_jstring(env, name, format); + if (!key) { + goto fail; + } + + (*env)->CallVoidMethod(env, format->object, format->jfields.set_long_id, key, value); + if (ff_jni_exception_check(env, 1, format) < 0) { + goto fail; + } + +fail: + if (key) { + (*env)->DeleteLocalRef(env, key); + } + + JNI_DETACH_ENV(attached, NULL); +} + +void ff_AMediaFormat_setFloat(FFAMediaFormat* format, const char* name, float value) +{ + int attached = 0; + JNIEnv *env = NULL; + jstring key = NULL; + + av_assert0(format != NULL); + + JNI_ATTACH_ENV_OR_RETURN_VOID(env, &attached, format); + + key = ff_jni_utf_chars_to_jstring(env, name, format); + if (!key) { + goto fail; + } + + (*env)->CallVoidMethod(env, format->object, format->jfields.set_float_id, key, value); + if (ff_jni_exception_check(env, 1, format) < 0) { + goto fail; + } + +fail: + if (key) { + (*env)->DeleteLocalRef(env, key); + } + + JNI_DETACH_ENV(attached, NULL); +} + +void ff_AMediaFormat_setString(FFAMediaFormat* format, const char* name, const char* value) +{ + int attached = 0; + JNIEnv *env = NULL; + jstring key = NULL; + jstring string = NULL; + + av_assert0(format != NULL); + + JNI_ATTACH_ENV_OR_RETURN_VOID(env, &attached, format); + + key = ff_jni_utf_chars_to_jstring(env, name, format); + if (!key) { + goto fail; + } + + string = ff_jni_utf_chars_to_jstring(env, value, format); + if (!string) { + goto fail; + } + + (*env)->CallVoidMethod(env, format->object, format->jfields.set_string_id, key, string); + if (ff_jni_exception_check(env, 1, format) < 0) { + goto fail; + } + +fail: + if (key) { + (*env)->DeleteLocalRef(env, key); + } + + if (string) { + (*env)->DeleteLocalRef(env, string); + } + + JNI_DETACH_ENV(attached, format); +} + +void ff_AMediaFormat_setBuffer(FFAMediaFormat* format, const char* name, void* data, size_t size) +{ + int attached = 0; + JNIEnv *env = NULL; + jstring key = NULL; + jobject buffer = NULL; + void *buffer_data = NULL; + + av_assert0(format != NULL); + + JNI_ATTACH_ENV_OR_RETURN_VOID(env, &attached, format); + + key = ff_jni_utf_chars_to_jstring(env, name, format); + if (!key) { + goto fail; + } + + if (!data || !size) { + goto fail; + } + + buffer_data = av_malloc(size); + if (!buffer_data) { + goto fail; + } + + memcpy(buffer_data, data, size); + + buffer = (*env)->NewDirectByteBuffer(env, buffer_data, size); + if (!buffer) { + goto fail; + } + + (*env)->CallVoidMethod(env, format->object, format->jfields.set_bytebuffer_id, key, buffer); + if (ff_jni_exception_check(env, 1, format) < 0) { + goto fail; + } + +fail: + if (key) { + (*env)->DeleteLocalRef(env, key); + } + + if (buffer) { + (*env)->DeleteLocalRef(env, buffer); + } + + JNI_DETACH_ENV(attached, format); +} + +struct JNIAMediaCodecFields { + + jclass mediacodec_class; + + jfieldID info_try_again_later_id; + jfieldID info_output_buffers_changed_id; + jfieldID info_output_format_changed_id; + + jfieldID buffer_flag_codec_config_id; + jfieldID buffer_flag_end_of_stream_id; + jfieldID buffer_flag_key_frame_id; + + jfieldID configure_flag_encode_id; + + jmethodID create_by_codec_name_id; + jmethodID create_decoder_by_type_id; + jmethodID create_encoder_by_type_id; + + jmethodID get_name_id; + + jmethodID configure_id; + jmethodID start_id; + jmethodID flush_id; + jmethodID stop_id; + jmethodID release_id; + + jmethodID get_output_format_id; + + jmethodID dequeue_input_buffer_id; + jmethodID queue_input_buffer_id; + jmethodID get_input_buffer_id; + jmethodID get_input_buffers_id; + + jmethodID dequeue_output_buffer_id; + jmethodID get_output_buffer_id; + jmethodID get_output_buffers_id; + jmethodID release_output_buffer_id; + jmethodID release_output_buffer_at_time_id; + + jclass mediainfo_class; + + jmethodID init_id; + + jfieldID flags_id; + jfieldID offset_id; + jfieldID presentation_time_us_id; + jfieldID size_id; + +} JNIAMediaCodecFields; + +static const struct FFJniField jni_amediacodec_mapping[] = { + { "android/media/MediaCodec", NULL, NULL, FF_JNI_CLASS, offsetof(struct JNIAMediaCodecFields, mediacodec_class), 1 }, + + { "android/media/MediaCodec", "INFO_TRY_AGAIN_LATER", "I", FF_JNI_STATIC_FIELD, offsetof(struct JNIAMediaCodecFields, info_try_again_later_id), 1 }, + { "android/media/MediaCodec", "INFO_OUTPUT_BUFFERS_CHANGED", "I", FF_JNI_STATIC_FIELD, offsetof(struct JNIAMediaCodecFields, info_output_buffers_changed_id), 1 }, + { "android/media/MediaCodec", "INFO_OUTPUT_FORMAT_CHANGED", "I", FF_JNI_STATIC_FIELD, offsetof(struct JNIAMediaCodecFields, info_output_format_changed_id), 1 }, + + { "android/media/MediaCodec", "BUFFER_FLAG_CODEC_CONFIG", "I", FF_JNI_STATIC_FIELD, offsetof(struct JNIAMediaCodecFields, buffer_flag_codec_config_id), 1 }, + { "android/media/MediaCodec", "BUFFER_FLAG_END_OF_STREAM", "I", FF_JNI_STATIC_FIELD, offsetof(struct JNIAMediaCodecFields, buffer_flag_end_of_stream_id), 1 }, + { "android/media/MediaCodec", "BUFFER_FLAG_KEY_FRAME", "I", FF_JNI_STATIC_FIELD, offsetof(struct JNIAMediaCodecFields, buffer_flag_key_frame_id), 0 }, + + { "android/media/MediaCodec", "CONFIGURE_FLAG_ENCODE", "I", FF_JNI_STATIC_FIELD, offsetof(struct JNIAMediaCodecFields, configure_flag_encode_id), 1 }, + + { "android/media/MediaCodec", "createByCodecName", "(Ljava/lang/String;)Landroid/media/MediaCodec;", FF_JNI_STATIC_METHOD, offsetof(struct JNIAMediaCodecFields, create_by_codec_name_id), 1 }, + { "android/media/MediaCodec", "createDecoderByType", "(Ljava/lang/String;)Landroid/media/MediaCodec;", FF_JNI_STATIC_METHOD, offsetof(struct JNIAMediaCodecFields, create_decoder_by_type_id), 1 }, + { "android/media/MediaCodec", "createEncoderByType", "(Ljava/lang/String;)Landroid/media/MediaCodec;", FF_JNI_STATIC_METHOD, offsetof(struct JNIAMediaCodecFields, create_encoder_by_type_id), 1 }, + + { "android/media/MediaCodec", "getName", "()Ljava/lang/String;", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecFields, get_name_id), 1 }, + + { "android/media/MediaCodec", "configure", "(Landroid/media/MediaFormat;Landroid/view/Surface;Landroid/media/MediaCrypto;I)V", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecFields, configure_id), 1 }, + { "android/media/MediaCodec", "start", "()V", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecFields, start_id), 1 }, + { "android/media/MediaCodec", "flush", "()V", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecFields, flush_id), 1 }, + { "android/media/MediaCodec", "stop", "()V", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecFields, stop_id), 1 }, + { "android/media/MediaCodec", "release", "()V", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecFields, release_id), 1 }, + + { "android/media/MediaCodec", "getOutputFormat", "()Landroid/media/MediaFormat;", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecFields, get_output_format_id), 1 }, + + { "android/media/MediaCodec", "dequeueInputBuffer", "(J)I", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecFields, dequeue_input_buffer_id), 1 }, + { "android/media/MediaCodec", "queueInputBuffer", "(IIIJI)V", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecFields, queue_input_buffer_id), 1 }, + { "android/media/MediaCodec", "getInputBuffer", "(I)Ljava/nio/ByteBuffer;", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecFields, get_input_buffer_id), 0 }, + { "android/media/MediaCodec", "getInputBuffers", "()[Ljava/nio/ByteBuffer;", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecFields, get_input_buffers_id), 1 }, + + + { "android/media/MediaCodec", "dequeueOutputBuffer", "(Landroid/media/MediaCodec$BufferInfo;J)I", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecFields, dequeue_output_buffer_id), 1 }, + { "android/media/MediaCodec", "getOutputBuffer", "(I)Ljava/nio/ByteBuffer;", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecFields, get_output_buffer_id), 0 }, + { "android/media/MediaCodec", "getOutputBuffers", "()[Ljava/nio/ByteBuffer;", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecFields, get_output_buffers_id), 1 }, + { "android/media/MediaCodec", "releaseOutputBuffer", "(IZ)V", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecFields, release_output_buffer_id), 1 }, + { "android/media/MediaCodec", "releaseOutputBuffer", "(IJ)V", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecFields, release_output_buffer_at_time_id), 0 }, + + { "android/media/MediaCodec$BufferInfo", NULL, NULL, FF_JNI_CLASS, offsetof(struct JNIAMediaCodecFields, mediainfo_class), 1 }, + + { "android/media/MediaCodec.BufferInfo", "", "()V", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecFields, init_id), 1 }, + { "android/media/MediaCodec.BufferInfo", "flags", "I", FF_JNI_FIELD, offsetof(struct JNIAMediaCodecFields, flags_id), 1 }, + { "android/media/MediaCodec.BufferInfo", "offset", "I", FF_JNI_FIELD, offsetof(struct JNIAMediaCodecFields, offset_id), 1 }, + { "android/media/MediaCodec.BufferInfo", "presentationTimeUs", "J", FF_JNI_FIELD, offsetof(struct JNIAMediaCodecFields, presentation_time_us_id), 1 }, + { "android/media/MediaCodec.BufferInfo", "size", "I", FF_JNI_FIELD, offsetof(struct JNIAMediaCodecFields, size_id), 1 }, + + { NULL } +}; + + +static const AVClass amediacodec_class = { + .class_name = "amediacodec", + .item_name = av_default_item_name, + .version = LIBAVCODEC_VERSION_INT, +}; + +struct FFAMediaCodec { + + const AVClass *class; + + struct JNIAMediaCodecFields jfields; + + jobject object; + + jobject input_buffers; + jobject output_buffers; + + int INFO_TRY_AGAIN_LATER; + int INFO_OUTPUT_BUFFERS_CHANGED; + int INFO_OUTPUT_FORMAT_CHANGED; + + int BUFFER_FLAG_CODEC_CONFIG; + int BUFFER_FLAG_END_OF_STREAM; + int BUFFER_FLAG_KEY_FRAME; + + int CONFIGURE_FLAG_ENCODE; + + int has_get_i_o_buffer; +}; + +FFAMediaCodec* ff_AMediaCodec_createCodecByName(const char *name) +{ + int attached = 0; + JNIEnv *env = NULL; + FFAMediaCodec *codec = NULL; + jstring codec_name = NULL; + + codec = av_mallocz(sizeof(FFAMediaCodec)); + if (!codec) { + return NULL; + } + codec->class = &amediacodec_class; + + env = ff_jni_attach_env(&attached, codec); + if (!env) { + av_freep(&codec); + return NULL; + } + + if (ff_jni_init_jfields(env, &codec->jfields, jni_amediacodec_mapping, 1, codec) < 0) { + goto fail; + } + + codec_name = ff_jni_utf_chars_to_jstring(env, name, codec); + if (!codec_name) { + goto fail; + } + + codec->object = (*env)->CallStaticObjectMethod(env, codec->jfields.mediacodec_class, codec->jfields.create_by_codec_name_id, codec_name); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + codec->object = (*env)->NewGlobalRef(env, codec->object); + if (!codec->object) { + goto fail; + } + + codec->INFO_TRY_AGAIN_LATER = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.info_try_again_later_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + codec->BUFFER_FLAG_CODEC_CONFIG = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.buffer_flag_codec_config_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + codec->BUFFER_FLAG_END_OF_STREAM = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.buffer_flag_end_of_stream_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + if (codec->jfields.buffer_flag_key_frame_id) { + codec->BUFFER_FLAG_KEY_FRAME = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.buffer_flag_key_frame_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + } + + codec->CONFIGURE_FLAG_ENCODE = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.configure_flag_encode_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + codec->INFO_TRY_AGAIN_LATER = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.info_try_again_later_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + codec->INFO_OUTPUT_BUFFERS_CHANGED = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.info_output_buffers_changed_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + codec->INFO_OUTPUT_FORMAT_CHANGED = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.info_output_format_changed_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + JNI_DETACH_ENV(attached, codec); + + return codec; +fail: + ff_jni_reset_jfields(env, &codec->jfields, jni_amediacodec_mapping, 1, codec); + + if (codec_name) { + (*env)->DeleteLocalRef(env, codec_name); + } + + JNI_DETACH_ENV(attached, codec); + + av_freep(&codec); + + return NULL; +} + +FFAMediaCodec* ff_AMediaCodec_createDecoderByType(const char *mime) +{ + int attached = 0; + JNIEnv *env = NULL; + FFAMediaCodec *codec = NULL; + jstring mime_type = NULL; + + codec = av_mallocz(sizeof(FFAMediaCodec)); + if (!codec) { + return NULL; + } + codec->class = &amediacodec_class; + + env = ff_jni_attach_env(&attached, codec); + if (!env) { + av_freep(&codec); + return NULL; + } + + if (ff_jni_init_jfields(env, &codec->jfields, jni_amediacodec_mapping, 1, codec) < 0) { + goto fail; + } + + mime_type = ff_jni_utf_chars_to_jstring(env, mime, codec); + if (!mime_type) { + goto fail; + } + + codec->object = (*env)->CallStaticObjectMethod(env, codec->jfields.mediacodec_class, codec->jfields.create_decoder_by_type_id, mime_type); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + codec->object = (*env)->NewGlobalRef(env, codec->object); + if (!codec->object) { + goto fail; + } + + codec->BUFFER_FLAG_CODEC_CONFIG = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.buffer_flag_codec_config_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + codec->BUFFER_FLAG_END_OF_STREAM = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.buffer_flag_end_of_stream_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + if (codec->jfields.buffer_flag_key_frame_id) { + codec->BUFFER_FLAG_KEY_FRAME = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.buffer_flag_key_frame_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + } + + codec->CONFIGURE_FLAG_ENCODE = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.configure_flag_encode_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + codec->INFO_TRY_AGAIN_LATER = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.info_try_again_later_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + codec->INFO_OUTPUT_BUFFERS_CHANGED = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.info_output_buffers_changed_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + codec->INFO_OUTPUT_FORMAT_CHANGED = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.info_output_format_changed_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + if (codec->jfields.get_input_buffer_id && codec->jfields.get_output_buffer_id) { + codec->has_get_i_o_buffer = 1; + } + + JNI_DETACH_ENV(attached, codec); + + return codec; +fail: + ff_jni_reset_jfields(env, &codec->jfields, jni_amediacodec_mapping, 1, codec); + + if (mime_type) { + (*env)->DeleteLocalRef(env, mime_type); + } + + JNI_DETACH_ENV(attached, codec); + + av_freep(&codec); + + return NULL; +} + +FFAMediaCodec* ff_AMediaCodec_createEncoderByType(const char *mime) +{ + int attached = 0; + JNIEnv *env = NULL; + FFAMediaCodec *codec = NULL; + jstring mime_type = NULL; + + codec = av_mallocz(sizeof(FFAMediaCodec)); + if (!codec) { + return NULL; + } + codec->class = &amediacodec_class; + + env = ff_jni_attach_env(&attached, codec); + if (!env) { + av_freep(&codec); + return NULL; + } + + if (ff_jni_init_jfields(env, &codec->jfields, jni_amediacodec_mapping, 1, codec) < 0) { + goto fail; + } + + mime_type = ff_jni_utf_chars_to_jstring(env, mime, codec); + if (!mime_type) { + goto fail; + } + + codec->object = (*env)->CallStaticObjectMethod(env, codec->jfields.mediacodec_class, codec->jfields.create_encoder_by_type_id, mime_type); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + codec->object = (*env)->NewGlobalRef(env, codec->object); + if (!codec->object) { + goto fail; + } + + codec->INFO_TRY_AGAIN_LATER = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.info_try_again_later_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + codec->BUFFER_FLAG_CODEC_CONFIG = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.buffer_flag_codec_config_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + codec->BUFFER_FLAG_END_OF_STREAM = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.buffer_flag_end_of_stream_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + if (codec->jfields.buffer_flag_key_frame_id) { + codec->BUFFER_FLAG_KEY_FRAME = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.buffer_flag_key_frame_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + } + + codec->CONFIGURE_FLAG_ENCODE = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.configure_flag_encode_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + codec->INFO_TRY_AGAIN_LATER = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.info_try_again_later_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + codec->INFO_OUTPUT_BUFFERS_CHANGED = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.info_output_buffers_changed_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + codec->INFO_OUTPUT_FORMAT_CHANGED = (*env)->GetStaticIntField(env, codec->jfields.mediacodec_class, codec->jfields.info_output_format_changed_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + JNI_DETACH_ENV(attached, NULL); + + return codec; +fail: + ff_jni_reset_jfields(env, &codec->jfields, jni_amediacodec_mapping, 1, codec); + + if (mime_type) { + (*env)->DeleteLocalRef(env, mime_type); + } + + JNI_DETACH_ENV(attached, codec); + + av_freep(&codec); + + return NULL; +} + + +int ff_AMediaCodec_delete(FFAMediaCodec* codec) +{ + int ret = 0; + + int attached = 0; + JNIEnv *env = NULL; + + if (!codec) { + return 0; + } + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, codec, AVERROR_EXTERNAL); + + (*env)->CallVoidMethod(env, codec->object, codec->jfields.release_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + ret = AVERROR_EXTERNAL; + } + + (*env)->DeleteGlobalRef(env, codec->object); + codec->object = NULL; + + ff_jni_reset_jfields(env, &codec->jfields, jni_amediacodec_mapping, 1, codec); + + JNI_DETACH_ENV(attached, codec); + + av_freep(&codec); + + return ret; +} + +char *ff_AMediaCodec_getName(FFAMediaCodec *codec) +{ + char *ret = NULL; + int attached = 0; + JNIEnv *env = NULL; + jobject *name = NULL; + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, codec, NULL); + + name = (*env)->CallObjectMethod(env, codec->object, codec->jfields.get_name_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + ret = ff_jni_jstring_to_utf_chars(env, name, codec); + +fail: + JNI_DETACH_ENV(attached, NULL); + + return ret; +} + +int ff_AMediaCodec_configure(FFAMediaCodec* codec, const FFAMediaFormat* format, void* surface, void *crypto, uint32_t flags) +{ + int ret = 0; + int attached = 0; + JNIEnv *env = NULL; + + /* TODO: implement surface handling */ + av_assert0(surface == NULL); + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, codec, AVERROR_EXTERNAL); + + (*env)->CallVoidMethod(env, codec->object, codec->jfields.configure_id, format->object, NULL, NULL, flags); + if (ff_jni_exception_check(env, 1, codec) < 0) { + ret = AVERROR_EXTERNAL; + goto fail; + } + +fail: + JNI_DETACH_ENV(attached, NULL); + + return ret; +} + +int ff_AMediaCodec_start(FFAMediaCodec* codec) +{ + int ret = 0; + int attached = 0; + JNIEnv *env = NULL; + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, codec, AVERROR_EXTERNAL); + + (*env)->CallVoidMethod(env, codec->object, codec->jfields.start_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + ret = AVERROR_EXTERNAL; + goto fail; + } + +fail: + JNI_DETACH_ENV(attached, codec); + + return ret; +} + +int ff_AMediaCodec_stop(FFAMediaCodec* codec) +{ + int ret = 0; + int attached = 0; + JNIEnv *env = NULL; + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, codec, AVERROR_EXTERNAL); + + (*env)->CallVoidMethod(env, codec->object, codec->jfields.stop_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + ret = AVERROR_EXTERNAL; + goto fail; + } + +fail: + JNI_DETACH_ENV(attached, codec); + + return ret; +} + +int ff_AMediaCodec_flush(FFAMediaCodec* codec) +{ + int ret = 0; + int attached = 0; + JNIEnv *env = NULL; + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, codec, AVERROR_EXTERNAL); + + (*env)->CallVoidMethod(env, codec->object, codec->jfields.flush_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + ret = AVERROR_EXTERNAL; + goto fail; + } + +fail: + JNI_DETACH_ENV(attached, codec); + + return ret; +} + + +int ff_AMediaCodec_releaseOutputBuffer(FFAMediaCodec* codec, size_t idx, int render) +{ + int ret = 0; + int attached = 0; + JNIEnv *env = NULL; + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, codec, AVERROR_EXTERNAL); + + (*env)->CallVoidMethod(env, codec->object, codec->jfields.release_output_buffer_id, idx, render); + if (ff_jni_exception_check(env, 1, codec) < 0) { + ret = AVERROR_EXTERNAL; + goto fail; + } + +fail: + JNI_DETACH_ENV(attached, codec); + + return ret; +} + +int ff_AMediaCodec_releaseOutputBufferAtTime(FFAMediaCodec *codec, size_t idx, int64_t timestampNs) +{ + int ret = 0; + int attached = 0; + JNIEnv *env = NULL; + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, codec, AVERROR_EXTERNAL); + + (*env)->CallVoidMethod(env, codec->object, codec->jfields.release_output_buffer_at_time_id, idx, timestampNs); + if (ff_jni_exception_check(env, 1, codec) < 0) { + ret = AVERROR_EXTERNAL; + goto fail; + } + +fail: + JNI_DETACH_ENV(attached, codec); + + return ret; +} + + +ssize_t ff_AMediaCodec_dequeueInputBuffer(FFAMediaCodec* codec, int64_t timeoutUs) +{ + int ret = 0; + int attached = 0; + JNIEnv *env = NULL; + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, codec, AVERROR_EXTERNAL); + + ret = (*env)->CallIntMethod(env, codec->object, codec->jfields.dequeue_input_buffer_id, timeoutUs); + if (ff_jni_exception_check(env, 1, codec) < 0) { + ret = AVERROR_EXTERNAL; + goto fail; + } + +fail: + JNI_DETACH_ENV(attached, codec); + + return ret; +} + +int ff_AMediaCodec_queueInputBuffer(FFAMediaCodec* codec, size_t idx, off_t offset, size_t size, uint64_t time, uint32_t flags) +{ + int ret = 0; + int attached = 0; + JNIEnv *env = NULL; + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, codec, AVERROR_EXTERNAL); + + (*env)->CallVoidMethod(env, codec->object, codec->jfields.queue_input_buffer_id, idx, offset, size, time, flags); + if ((ret = ff_jni_exception_check(env, 1, codec)) < 0) { + ret = AVERROR_EXTERNAL; + goto fail; + } + +fail: + JNI_DETACH_ENV(attached, codec); + + return ret; +} + +ssize_t ff_AMediaCodec_dequeueOutputBuffer(FFAMediaCodec* codec, FFAMediaCodecBufferInfo *info, int64_t timeoutUs) +{ + int ret = 0; + int attached = 0; + JNIEnv *env = NULL; + + jobject mediainfo = NULL; + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, codec, AVERROR_EXTERNAL); + + mediainfo = (*env)->NewObject(env, codec->jfields.mediainfo_class, codec->jfields.init_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + ret = AVERROR_EXTERNAL; + goto fail; + } + + ret = (*env)->CallIntMethod(env, codec->object, codec->jfields.dequeue_output_buffer_id, mediainfo, timeoutUs); + if (ff_jni_exception_check(env, 1, codec) < 0) { + ret = AVERROR_EXTERNAL; + goto fail; + } + + info->flags = (*env)->GetIntField(env, mediainfo, codec->jfields.flags_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + ret = AVERROR_EXTERNAL; + goto fail; + } + + info->offset = (*env)->GetIntField(env, mediainfo, codec->jfields.offset_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + ret = AVERROR_EXTERNAL; + goto fail; + } + + info->presentationTimeUs = (*env)->GetLongField(env, mediainfo, codec->jfields.presentation_time_us_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + ret = AVERROR_EXTERNAL; + goto fail; + } + + info->size = (*env)->GetIntField(env, mediainfo, codec->jfields.size_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + ret = AVERROR_EXTERNAL; + goto fail; + } +fail: + if (mediainfo) { + (*env)->DeleteLocalRef(env, mediainfo); + } + + JNI_DETACH_ENV(attached, NULL); + + return ret; +} + +uint8_t* ff_AMediaCodec_getInputBuffer(FFAMediaCodec* codec, size_t idx, size_t *out_size) +{ + uint8_t *ret = NULL; + int attached = 0; + JNIEnv *env = NULL; + + jobject buffer = NULL; + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, codec, NULL); + + if (codec->has_get_i_o_buffer) { + buffer = (*env)->CallObjectMethod(env, codec->object, codec->jfields.get_input_buffer_id, idx); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + } else { + if (!codec->input_buffers) { + codec->input_buffers = (*env)->CallObjectMethod(env, codec->object, codec->jfields.get_input_buffers_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + codec->input_buffers = (*env)->NewGlobalRef(env, codec->input_buffers); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + } + + buffer = (*env)->GetObjectArrayElement(env, codec->input_buffers, idx); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + } + + ret = (*env)->GetDirectBufferAddress(env, buffer); + *out_size = (*env)->GetDirectBufferCapacity(env, buffer); +fail: + if (buffer) { + (*env)->DeleteLocalRef(env, buffer); + } + + JNI_DETACH_ENV(attached, codec); + + return ret; +} + +uint8_t* ff_AMediaCodec_getOutputBuffer(FFAMediaCodec* codec, size_t idx, size_t *out_size) +{ + uint8_t *ret = NULL; + int attached = 0; + JNIEnv *env = NULL; + + jobject buffer = NULL; + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, codec, NULL); + + if (codec->has_get_i_o_buffer) { + buffer = (*env)->CallObjectMethod(env, codec->object, codec->jfields.get_output_buffer_id, idx); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + } else { + if (!codec->output_buffers) { + codec->output_buffers = (*env)->CallObjectMethod(env, codec->object, codec->jfields.get_output_buffers_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + codec->output_buffers = (*env)->NewGlobalRef(env, codec->output_buffers); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + } + + buffer = (*env)->GetObjectArrayElement(env, codec->output_buffers, idx); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + } + + ret = (*env)->GetDirectBufferAddress(env, buffer); + *out_size = (*env)->GetDirectBufferCapacity(env, buffer); +fail: + if (buffer) { + (*env)->DeleteLocalRef(env, buffer); + } + + JNI_DETACH_ENV(attached, codec); + + return ret; +} + +FFAMediaFormat* ff_AMediaCodec_getOutputFormat(FFAMediaCodec* codec) +{ + FFAMediaFormat *ret = NULL; + int attached = 0; + JNIEnv *env = NULL; + + jobject mediaformat = NULL; + + JNI_ATTACH_ENV_OR_RETURN(env, &attached, codec, NULL); + + mediaformat = (*env)->CallObjectMethod(env, codec->object, codec->jfields.get_output_format_id); + if (ff_jni_exception_check(env, 1, codec) < 0) { + goto fail; + } + + ret = ff_AMediaFormat_newFromObject(mediaformat); +fail: + if (mediaformat) { + (*env)->DeleteLocalRef(env, mediaformat); + } + + JNI_DETACH_ENV(attached, codec); + + return ret; +} + +int ff_AMediaCodec_infoTryAgainLater(FFAMediaCodec *codec, ssize_t idx) +{ + return idx == codec->INFO_TRY_AGAIN_LATER; +} + +int ff_AMediaCodec_infoOutputBuffersChanged(FFAMediaCodec *codec, ssize_t idx) +{ + return idx == codec->INFO_OUTPUT_BUFFERS_CHANGED; +} + +int ff_AMediaCodec_infoOutputFormatChanged(FFAMediaCodec *codec, ssize_t idx) +{ + return idx == codec->INFO_OUTPUT_FORMAT_CHANGED; +} + +int ff_AMediaCodec_getBufferFlagCodecConfig(FFAMediaCodec *codec) +{ + return codec->BUFFER_FLAG_CODEC_CONFIG; +} + +int ff_AMediaCodec_getBufferFlagEndOfStream(FFAMediaCodec *codec) +{ + return codec->BUFFER_FLAG_END_OF_STREAM; +} + +int ff_AMediaCodec_getBufferFlagKeyFrame(FFAMediaCodec *codec) +{ + return codec->BUFFER_FLAG_KEY_FRAME; +} + +int ff_AMediaCodec_getConfigureFlagEncode(FFAMediaCodec *codec) +{ + return codec->CONFIGURE_FLAG_ENCODE; +} + +int ff_AMediaCodec_cleanOutputBuffers(FFAMediaCodec *codec) +{ + int ret = 0; + + if (!codec->has_get_i_o_buffer) { + if (codec->output_buffers) { + int attached = 0; + JNIEnv *env = NULL; + + env = ff_jni_attach_env(&attached, codec); + if (!env) { + ret = AVERROR_EXTERNAL; + goto fail; + } + + (*env)->DeleteGlobalRef(env, codec->output_buffers); + codec->output_buffers = NULL; + + JNI_DETACH_ENV(attached, codec); + } + } + +fail: + return ret; +} diff --git a/libavcodec/mediacodec_wrapper.h b/libavcodec/mediacodec_wrapper.h new file mode 100644 index 0000000000..a804b616da --- /dev/null +++ b/libavcodec/mediacodec_wrapper.h @@ -0,0 +1,125 @@ +/* + * Android MediaCodec Wrapper + * + * Copyright (c) 2015-2016 Matthieu Bouron + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_MEDIACODEC_WRAPPER_H +#define AVCODEC_MEDIACODEC_WRAPPER_H + +#include +#include + +/** + * The following API around MediaCodec and MediaFormat is based on the + * NDK one provided by Google since Android 5.0. + * + * Differences from the NDK API: + * + * Buffers returned by ff_AMediaFormat_toString and ff_AMediaFormat_getString + * are newly allocated buffer and must be freed by the user after use. + * + * The MediaCrypto API is not implemented. + * + * ff_AMediaCodec_infoTryAgainLater, ff_AMediaCodec_infoOutputBuffersChanged, + * ff_AMediaCodec_infoOutputFormatChanged, ff_AMediaCodec_cleanOutputBuffers + * ff_AMediaCodec_getName and ff_AMediaCodec_getBufferFlagEndOfStream are not + * part of the original NDK API and are convenience functions to hide JNI + * implementation. + * + * The API around MediaCodecList is not part of the NDK (and is lacking as + * we still need to retreive the codec name to work around faulty decoders + * and encoders). + * + * For documentation, please refers to NdkMediaCodec.h NdkMediaFormat.h and + * http://developer.android.com/reference/android/media/MediaCodec.html. + * + */ + +char *ff_AMediaCodecList_getCodecNameByType(const char *mime, int width, int height, void *log_ctx); + +struct FFAMediaFormat; +typedef struct FFAMediaFormat FFAMediaFormat; + +FFAMediaFormat *ff_AMediaFormat_new(void); +int ff_AMediaFormat_delete(FFAMediaFormat* format); + +char* ff_AMediaFormat_toString(FFAMediaFormat* format); + +int ff_AMediaFormat_getInt32(FFAMediaFormat* format, const char *name, int32_t *out); +int ff_AMediaFormat_getInt64(FFAMediaFormat* format, const char *name, int64_t *out); +int ff_AMediaFormat_getFloat(FFAMediaFormat* format, const char *name, float *out); +int ff_AMediaFormat_getBuffer(FFAMediaFormat* format, const char *name, void** data, size_t *size); +int ff_AMediaFormat_getString(FFAMediaFormat* format, const char *name, const char **out); + +void ff_AMediaFormat_setInt32(FFAMediaFormat* format, const char* name, int32_t value); +void ff_AMediaFormat_setInt64(FFAMediaFormat* format, const char* name, int64_t value); +void ff_AMediaFormat_setFloat(FFAMediaFormat* format, const char* name, float value); +void ff_AMediaFormat_setString(FFAMediaFormat* format, const char* name, const char* value); +void ff_AMediaFormat_setBuffer(FFAMediaFormat* format, const char* name, void* data, size_t size); + +struct FFAMediaCodec; +typedef struct FFAMediaCodec FFAMediaCodec; +typedef struct FFAMediaCodecCryptoInfo FFAMediaCodecCryptoInfo; + +struct FFAMediaCodecBufferInfo { + int32_t offset; + int32_t size; + int64_t presentationTimeUs; + uint32_t flags; +}; +typedef struct FFAMediaCodecBufferInfo FFAMediaCodecBufferInfo; + +char *ff_AMediaCodec_getName(FFAMediaCodec *codec); + +FFAMediaCodec* ff_AMediaCodec_createCodecByName(const char *name); +FFAMediaCodec* ff_AMediaCodec_createDecoderByType(const char *mime_type); +FFAMediaCodec* ff_AMediaCodec_createEncoderByType(const char *mime_type); + +int ff_AMediaCodec_configure(FFAMediaCodec* codec, const FFAMediaFormat* format, void* surface, void *crypto, uint32_t flags); +int ff_AMediaCodec_start(FFAMediaCodec* codec); +int ff_AMediaCodec_stop(FFAMediaCodec* codec); +int ff_AMediaCodec_flush(FFAMediaCodec* codec); +int ff_AMediaCodec_delete(FFAMediaCodec* codec); + +uint8_t* ff_AMediaCodec_getInputBuffer(FFAMediaCodec* codec, size_t idx, size_t *out_size); +uint8_t* ff_AMediaCodec_getOutputBuffer(FFAMediaCodec* codec, size_t idx, size_t *out_size); + +ssize_t ff_AMediaCodec_dequeueInputBuffer(FFAMediaCodec* codec, int64_t timeoutUs); +int ff_AMediaCodec_queueInputBuffer(FFAMediaCodec* codec, size_t idx, off_t offset, size_t size, uint64_t time, uint32_t flags); + +ssize_t ff_AMediaCodec_dequeueOutputBuffer(FFAMediaCodec* codec, FFAMediaCodecBufferInfo *info, int64_t timeoutUs); +FFAMediaFormat* ff_AMediaCodec_getOutputFormat(FFAMediaCodec* codec); + +int ff_AMediaCodec_releaseOutputBuffer(FFAMediaCodec* codec, size_t idx, int render); +int ff_AMediaCodec_releaseOutputBufferAtTime(FFAMediaCodec *codec, size_t idx, int64_t timestampNs); + +int ff_AMediaCodec_infoTryAgainLater(FFAMediaCodec *codec, ssize_t idx); +int ff_AMediaCodec_infoOutputBuffersChanged(FFAMediaCodec *codec, ssize_t idx); +int ff_AMediaCodec_infoOutputFormatChanged(FFAMediaCodec *codec, ssize_t indx); + +int ff_AMediaCodec_getBufferFlagCodecConfig (FFAMediaCodec *codec); +int ff_AMediaCodec_getBufferFlagEndOfStream(FFAMediaCodec *codec); +int ff_AMediaCodec_getBufferFlagKeyFrame(FFAMediaCodec *codec); + +int ff_AMediaCodec_getConfigureFlagEncode(FFAMediaCodec *codec); + +int ff_AMediaCodec_cleanOutputBuffers(FFAMediaCodec *codec); + +#endif /* AVCODEC_MEDIACODEC_WRAPPER_H */ diff --git a/libavcodec/mediacodecdec.c b/libavcodec/mediacodecdec.c new file mode 100644 index 0000000000..bd10ff7264 --- /dev/null +++ b/libavcodec/mediacodecdec.c @@ -0,0 +1,570 @@ +/* + * Android MediaCodec decoder + * + * Copyright (c) 2015-2016 Matthieu Bouron + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include "libavutil/common.h" +#include "libavutil/mem.h" +#include "libavutil/log.h" +#include "libavutil/pixfmt.h" +#include "libavutil/time.h" +#include "libavutil/timestamp.h" + +#include "avcodec.h" +#include "internal.h" + +#include "mediacodec_sw_buffer.h" +#include "mediacodec_wrapper.h" +#include "mediacodecdec.h" + +/** + * OMX.k3.video.decoder.avc, OMX.NVIDIA.* OMX.SEC.avc.dec and OMX.google + * codec workarounds used in various place are taken from the Gstreamer + * project. + * + * Gstreamer references: + * https://cgit.freedesktop.org/gstreamer/gst-plugins-bad/tree/sys/androidmedia/ + * + * Gstreamer copyright notice: + * + * Copyright (C) 2012, Collabora Ltd. + * Author: Sebastian Dröge + * + * Copyright (C) 2012, Rafaël Carré + * + * Copyright (C) 2015, Sebastian Dröge + * + * Copyright (C) 2014-2015, Collabora Ltd. + * Author: Matthieu Bouron + * + * Copyright (C) 2015, Edward Hervey + * Author: Edward Hervey + * + * Copyright (C) 2015, Matthew Waters + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define INPUT_DEQUEUE_TIMEOUT_US 8000 +#define OUTPUT_DEQUEUE_TIMEOUT_US 8000 +#define OUTPUT_DEQUEUE_BLOCK_TIMEOUT_US 1000000 + +enum { + COLOR_FormatYUV420Planar = 0x13, + COLOR_FormatYUV420SemiPlanar = 0x15, + COLOR_FormatYCbYCr = 0x19, + COLOR_FormatAndroidOpaque = 0x7F000789, + COLOR_QCOM_FormatYUV420SemiPlanar = 0x7fa30c00, + COLOR_QCOM_FormatYUV420SemiPlanar32m = 0x7fa30c04, + COLOR_QCOM_FormatYUV420PackedSemiPlanar64x32Tile2m8ka = 0x7fa30c03, + COLOR_TI_FormatYUV420PackedSemiPlanar = 0x7f000100, + COLOR_TI_FormatYUV420PackedSemiPlanarInterlaced = 0x7f000001, +}; + +static const struct { + + int color_format; + enum AVPixelFormat pix_fmt; + +} color_formats[] = { + + { COLOR_FormatYUV420Planar, AV_PIX_FMT_YUV420P }, + { COLOR_FormatYUV420SemiPlanar, AV_PIX_FMT_NV12 }, + { COLOR_QCOM_FormatYUV420SemiPlanar, AV_PIX_FMT_NV12 }, + { COLOR_QCOM_FormatYUV420SemiPlanar32m, AV_PIX_FMT_NV12 }, + { COLOR_QCOM_FormatYUV420PackedSemiPlanar64x32Tile2m8ka, AV_PIX_FMT_NV12 }, + { COLOR_TI_FormatYUV420PackedSemiPlanar, AV_PIX_FMT_NV12 }, + { COLOR_TI_FormatYUV420PackedSemiPlanarInterlaced, AV_PIX_FMT_NV12 }, + { 0 } +}; + +static enum AVPixelFormat mcdec_map_color_format(AVCodecContext *avctx, + MediaCodecDecContext *s, + int color_format) +{ + int i; + enum AVPixelFormat ret = AV_PIX_FMT_NONE; + + if (!strcmp(s->codec_name, "OMX.k3.video.decoder.avc") && color_format == COLOR_FormatYCbYCr) { + s->color_format = color_format = COLOR_TI_FormatYUV420PackedSemiPlanar; + } + + for (i = 0; i < FF_ARRAY_ELEMS(color_formats); i++) { + if (color_formats[i].color_format == color_format) { + return color_formats[i].pix_fmt; + } + } + + av_log(avctx, AV_LOG_ERROR, "Output color format 0x%x (value=%d) is not supported\n", + color_format, color_format); + + return ret; +} + +static int mediacodec_wrap_buffer(AVCodecContext *avctx, + MediaCodecDecContext *s, + uint8_t *data, + size_t size, + ssize_t index, + FFAMediaCodecBufferInfo *info, + AVFrame *frame) +{ + int ret = 0; + int status = 0; + + frame->width = avctx->width; + frame->height = avctx->height; + frame->format = avctx->pix_fmt; + + /* MediaCodec buffers needs to be copied to our own refcounted buffers + * because the flush command invalidates all input and output buffers. + */ + if ((ret = ff_get_buffer(avctx, frame, 0)) < 0) { + av_log(avctx, AV_LOG_ERROR, "Could not allocate buffer\n"); + goto done; + } + + /* Override frame->pkt_pts as ff_get_buffer will override its value based + * on the last avpacket received which is not in sync with the frame: + * * N avpackets can be pushed before 1 frame is actually returned + * * 0-sized avpackets are pushed to flush remaining frames at EOS */ + frame->pkt_pts = info->presentationTimeUs; + + av_log(avctx, AV_LOG_DEBUG, + "Frame: width=%d stride=%d height=%d slice-height=%d " + "crop-top=%d crop-bottom=%d crop-left=%d crop-right=%d encoder=%s\n" + "destination linesizes=%d,%d,%d\n" , + avctx->width, s->stride, avctx->height, s->slice_height, + s->crop_top, s->crop_bottom, s->crop_left, s->crop_right, s->codec_name, + frame->linesize[0], frame->linesize[1], frame->linesize[2]); + + switch (s->color_format) { + case COLOR_FormatYUV420Planar: + ff_mediacodec_sw_buffer_copy_yuv420_planar(avctx, s, data, size, info, frame); + break; + case COLOR_FormatYUV420SemiPlanar: + case COLOR_QCOM_FormatYUV420SemiPlanar: + case COLOR_QCOM_FormatYUV420SemiPlanar32m: + ff_mediacodec_sw_buffer_copy_yuv420_semi_planar(avctx, s, data, size, info, frame); + break; + case COLOR_TI_FormatYUV420PackedSemiPlanar: + case COLOR_TI_FormatYUV420PackedSemiPlanarInterlaced: + ff_mediacodec_sw_buffer_copy_yuv420_packed_semi_planar(avctx, s, data, size, info, frame); + break; + case COLOR_QCOM_FormatYUV420PackedSemiPlanar64x32Tile2m8ka: + ff_mediacodec_sw_buffer_copy_yuv420_packed_semi_planar_64x32Tile2m8ka(avctx, s, data, size, info, frame); + break; + default: + av_log(avctx, AV_LOG_ERROR, "Unsupported color format 0x%x (value=%d)\n", + s->color_format, s->color_format); + ret = AVERROR(EINVAL); + goto done; + } + + ret = 0; +done: + status = ff_AMediaCodec_releaseOutputBuffer(s->codec, index, 0); + if (status < 0) { + av_log(NULL, AV_LOG_ERROR, "Failed to release output buffer\n"); + ret = AVERROR_EXTERNAL; + } + + return ret; +} + +static int mediacodec_dec_parse_format(AVCodecContext *avctx, MediaCodecDecContext *s) +{ + int width = 0; + int height = 0; + int32_t value = 0; + char *format = NULL; + + if (!s->format) { + av_log(avctx, AV_LOG_ERROR, "Output MediaFormat is not set\n"); + return AVERROR(EINVAL); + } + + format = ff_AMediaFormat_toString(s->format); + if (!format) { + return AVERROR_EXTERNAL; + } + av_log(avctx, AV_LOG_DEBUG, "Parsing MediaFormat %s\n", format); + av_freep(&format); + + /* Mandatory fields */ + if (!ff_AMediaFormat_getInt32(s->format, "width", &value)) { + format = ff_AMediaFormat_toString(s->format); + av_log(avctx, AV_LOG_ERROR, "Could not get %s from format %s\n", "width", format); + av_freep(&format); + return AVERROR_EXTERNAL; + } + s->width = value; + + if (!ff_AMediaFormat_getInt32(s->format, "height", &value)) { + format = ff_AMediaFormat_toString(s->format); + av_log(avctx, AV_LOG_ERROR, "Could not get %s from format %s\n", "height", format); + av_freep(&format); + return AVERROR_EXTERNAL; + } + s->height = value; + + if (!ff_AMediaFormat_getInt32(s->format, "stride", &value)) { + format = ff_AMediaFormat_toString(s->format); + av_log(avctx, AV_LOG_ERROR, "Could not get %s from format %s\n", "stride", format); + av_freep(&format); + return AVERROR_EXTERNAL; + } + s->stride = value >= 0 ? value : s->width; + + if (!ff_AMediaFormat_getInt32(s->format, "slice-height", &value)) { + format = ff_AMediaFormat_toString(s->format); + av_log(avctx, AV_LOG_ERROR, "Could not get %s from format %s\n", "slice-height", format); + av_freep(&format); + return AVERROR_EXTERNAL; + } + if (value > 0) { + s->slice_height = value; + } else { + s->slice_height = s->height; + } + + if (strstr(s->codec_name, "OMX.Nvidia.")) { + s->slice_height = FFALIGN(s->height, 16); + } else if (strstr(s->codec_name, "OMX.SEC.avc.dec")) { + s->slice_height = avctx->height; + s->stride = avctx->width; + } + + if (!ff_AMediaFormat_getInt32(s->format, "color-format", &value)) { + format = ff_AMediaFormat_toString(s->format); + av_log(avctx, AV_LOG_ERROR, "Could not get %s from format %s\n", "color-format", format); + av_freep(&format); + return AVERROR_EXTERNAL; + } + s->color_format = value; + + s->pix_fmt = avctx->pix_fmt = mcdec_map_color_format(avctx, s, value); + if (avctx->pix_fmt == AV_PIX_FMT_NONE) { + av_log(avctx, AV_LOG_ERROR, "Output color format is not supported\n"); + return AVERROR(EINVAL); + } + + /* Optional fields */ + if (ff_AMediaFormat_getInt32(s->format, "crop-top", &value)) + s->crop_top = value; + + if (ff_AMediaFormat_getInt32(s->format, "crop-bottom", &value)) + s->crop_bottom = value; + + if (ff_AMediaFormat_getInt32(s->format, "crop-left", &value)) + s->crop_left = value; + + if (ff_AMediaFormat_getInt32(s->format, "crop-right", &value)) + s->crop_right = value; + + width = s->crop_right + 1 - s->crop_left; + height = s->crop_bottom + 1 - s->crop_top; + + av_log(avctx, AV_LOG_INFO, + "Output crop parameters top=%d bottom=%d left=%d right=%d, " + "resulting dimensions width=%d height=%d\n", + s->crop_top, s->crop_bottom, s->crop_left, s->crop_right, + width, height); + + return ff_set_dimensions(avctx, width, height); +} + +int ff_mediacodec_dec_init(AVCodecContext *avctx, MediaCodecDecContext *s, + const char *mime, FFAMediaFormat *format) +{ + int ret = 0; + int status; + + s->first_buffer_at = av_gettime(); + + s->codec_name = ff_AMediaCodecList_getCodecNameByType(mime, avctx->width, avctx->height, avctx); + if (!s->codec_name) { + ret = AVERROR_EXTERNAL; + goto fail; + } + + av_log(avctx, AV_LOG_DEBUG, "Found decoder %s\n", s->codec_name); + s->codec = ff_AMediaCodec_createCodecByName(s->codec_name); + if (!s->codec) { + av_log(avctx, AV_LOG_ERROR, "Failed to create media decoder for type %s and name %s\n", mime, s->codec_name); + ret = AVERROR_EXTERNAL; + goto fail; + } + + status = ff_AMediaCodec_configure(s->codec, format, NULL, NULL, 0); + if (status < 0) { + char *desc = ff_AMediaFormat_toString(format); + av_log(avctx, AV_LOG_ERROR, + "Failed to configure codec (status = %d) with format %s\n", + status, desc); + av_freep(&desc); + + ret = AVERROR_EXTERNAL; + goto fail; + } + + status = ff_AMediaCodec_start(s->codec); + if (status < 0) { + char *desc = ff_AMediaFormat_toString(format); + av_log(avctx, AV_LOG_ERROR, + "Failed to start codec (status = %d) with format %s\n", + status, desc); + av_freep(&desc); + ret = AVERROR_EXTERNAL; + goto fail; + } + + s->format = ff_AMediaCodec_getOutputFormat(s->codec); + if (s->format) { + if ((ret = mediacodec_dec_parse_format(avctx, s)) < 0) { + av_log(avctx, AV_LOG_ERROR, + "Failed to configure context\n"); + goto fail; + } + } + + av_log(avctx, AV_LOG_DEBUG, "MediaCodec %p started successfully\n", s->codec); + + return 0; + +fail: + av_log(avctx, AV_LOG_ERROR, "MediaCodec %p failed to start\n", s->codec); + ff_mediacodec_dec_close(avctx, s); + return ret; +} + +int ff_mediacodec_dec_decode(AVCodecContext *avctx, MediaCodecDecContext *s, + AVFrame *frame, int *got_frame, + AVPacket *pkt) +{ + int ret; + int offset = 0; + int need_flushing = 0; + uint8_t *data; + ssize_t index; + size_t size; + FFAMediaCodec *codec = s->codec; + FFAMediaCodecBufferInfo info = { 0 }; + + int status; + + int64_t input_dequeue_timeout_us = INPUT_DEQUEUE_TIMEOUT_US; + int64_t output_dequeue_timeout_us = OUTPUT_DEQUEUE_TIMEOUT_US; + + + if (pkt->size == 0) { + need_flushing = 1; + } + + if (s->flushing && need_flushing && s->queued_buffer_nb <= 0) { + return 0; + } + + while (offset < pkt->size || (need_flushing && !s->flushing)) { + int size; + + index = ff_AMediaCodec_dequeueInputBuffer(codec, input_dequeue_timeout_us); + if (ff_AMediaCodec_infoTryAgainLater(codec, index)) { + break; + } + + if (index < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to dequeue input buffer (status=%zd)\n", index); + return AVERROR_EXTERNAL; + } + + data = ff_AMediaCodec_getInputBuffer(codec, index, &size); + if (!data) { + av_log(avctx, AV_LOG_ERROR, "Failed to get input buffer\n"); + return AVERROR_EXTERNAL; + } + + if (need_flushing) { + uint32_t flags = ff_AMediaCodec_getBufferFlagEndOfStream(codec); + + av_log(avctx, AV_LOG_DEBUG, "Sending End Of Stream signal\n"); + + status = ff_AMediaCodec_queueInputBuffer(codec, index, 0, 0, pkt->pts, flags); + if (status < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to queue input empty buffer (status = %d)\n", status); + return AVERROR_EXTERNAL; + } + + s->flushing = 1; + break; + } else { + size = FFMIN(pkt->size - offset, size); + + memcpy(data, pkt->data + offset, size); + offset += size; + + status = ff_AMediaCodec_queueInputBuffer(codec, index, 0, size, pkt->pts, 0); + if (status < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to queue input buffer (status = %d)\n", status); + return AVERROR_EXTERNAL; + } + + s->queued_buffer_nb++; + if (s->queued_buffer_nb > s->queued_buffer_max) + s->queued_buffer_max = s->queued_buffer_nb; + } + } + + if (s->flushing) { + /* If the codec is flushing, block for a fair amount of time to + * ensure we got a frame */ + output_dequeue_timeout_us = OUTPUT_DEQUEUE_BLOCK_TIMEOUT_US; + } else if (s->dequeued_buffer_nb == 0) { + /* If the codec hasn't produced any frames, do not block so we + * can push data to it as fast as possible, and get the first + * frame */ + output_dequeue_timeout_us = 0; + } + + index = ff_AMediaCodec_dequeueOutputBuffer(codec, &info, output_dequeue_timeout_us); + if (index >= 0) { + int ret; + + if (!s->first_buffer++) { + av_log(avctx, AV_LOG_DEBUG, "Got first buffer after %fms\n", (av_gettime() - s->first_buffer_at) / 1000); + } + + av_log(avctx, AV_LOG_DEBUG, "Got output buffer %zd" + " offset=%" PRIi32 " size=%" PRIi32 " ts=%" PRIi64 + " flags=%" PRIu32 "\n", index, info.offset, info.size, + info.presentationTimeUs, info.flags); + + data = ff_AMediaCodec_getOutputBuffer(codec, index, &size); + if (!data) { + av_log(avctx, AV_LOG_ERROR, "Failed to get output buffer\n"); + return AVERROR_EXTERNAL; + } + + if ((ret = mediacodec_wrap_buffer(avctx, s, data, size, index, &info, frame)) < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to wrap MediaCodec buffer\n"); + return ret; + } + + *got_frame = 1; + s->queued_buffer_nb--; + s->dequeued_buffer_nb++; + + } else if (ff_AMediaCodec_infoOutputFormatChanged(codec, index)) { + char *format = NULL; + + if (s->format) { + status = ff_AMediaFormat_delete(s->format); + if (status < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to delete MediaFormat %p\n", s->format); + } + } + + s->format = ff_AMediaCodec_getOutputFormat(codec); + if (!s->format) { + av_log(avctx, AV_LOG_ERROR, "Failed to get output format\n"); + return AVERROR_EXTERNAL; + } + + format = ff_AMediaFormat_toString(s->format); + if (!format) { + return AVERROR_EXTERNAL; + } + av_log(avctx, AV_LOG_INFO, "Output MediaFormat changed to %s\n", format); + av_freep(&format); + + if ((ret = mediacodec_dec_parse_format(avctx, s)) < 0) { + return ret; + } + + } else if (ff_AMediaCodec_infoOutputBuffersChanged(codec, index)) { + ff_AMediaCodec_cleanOutputBuffers(codec); + } else if (ff_AMediaCodec_infoTryAgainLater(codec, index)) { + if (s->flushing) { + av_log(avctx, AV_LOG_ERROR, "Failed to dequeue output buffer within %" PRIi64 "ms " + "while flushing remaining frames, output will probably lack last %d frames\n", + output_dequeue_timeout_us / 1000, s->queued_buffer_nb); + } else { + av_log(avctx, AV_LOG_DEBUG, "No output buffer available, try again later\n"); + } + } else { + av_log(avctx, AV_LOG_ERROR, "Failed to dequeue output buffer (status=%zd)\n", index); + return AVERROR_EXTERNAL; + } + + return offset; +} + +int ff_mediacodec_dec_flush(AVCodecContext *avctx, MediaCodecDecContext *s) +{ + FFAMediaCodec *codec = s->codec; + int status; + + s->queued_buffer_nb = 0; + s->dequeued_buffer_nb = 0; + + s->flushing = 0; + + status = ff_AMediaCodec_flush(codec); + if (status < 0) { + av_log(NULL, AV_LOG_ERROR, "Failed to flush MediaCodec %p", codec); + return AVERROR_EXTERNAL; + } + + s->first_buffer = 0; + s->first_buffer_at = av_gettime(); + + return 0; +} + +int ff_mediacodec_dec_close(AVCodecContext *avctx, MediaCodecDecContext *s) +{ + if (s->codec) { + ff_AMediaCodec_delete(s->codec); + s->codec = NULL; + } + + if (s->format) { + ff_AMediaFormat_delete(s->format); + s->format = NULL; + } + + return 0; +} diff --git a/libavcodec/mediacodecdec.h b/libavcodec/mediacodecdec.h new file mode 100644 index 0000000000..bf23f859d9 --- /dev/null +++ b/libavcodec/mediacodecdec.h @@ -0,0 +1,82 @@ +/* + * Android MediaCodec decoder + * + * Copyright (c) 2015-2016 Matthieu Bouron + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_MEDIACODECDEC_H +#define AVCODEC_MEDIACODECDEC_H + +#include +#include + +#include "libavutil/frame.h" +#include "libavutil/pixfmt.h" + +#include "avcodec.h" +#include "mediacodec_wrapper.h" + +typedef struct MediaCodecDecContext { + + const char *codec_name; + + FFAMediaCodec *codec; + FFAMediaFormat *format; + + int started; + int flushing; + + int width; + int height; + int stride; + int slice_height; + int color_format; + enum AVPixelFormat pix_fmt; + int crop_top; + int crop_bottom; + int crop_left; + int crop_right; + + int queued_buffer_nb; + int queued_buffer_max; + uint64_t dequeued_buffer_nb; + + int first_buffer; + double first_buffer_at; + +} MediaCodecDecContext; + +int ff_mediacodec_dec_init(AVCodecContext *avctx, + MediaCodecDecContext *s, + const char *mime, + FFAMediaFormat *format); + +int ff_mediacodec_dec_decode(AVCodecContext *avctx, + MediaCodecDecContext *s, + AVFrame *frame, + int *got_frame, + AVPacket *pkt); + +int ff_mediacodec_dec_flush(AVCodecContext *avctx, + MediaCodecDecContext *s); + +int ff_mediacodec_dec_close(AVCodecContext *avctx, + MediaCodecDecContext *s); + +#endif /* AVCODEC_MEDIACODECDEC_H */ diff --git a/libavcodec/mediacodecdec_h264.c b/libavcodec/mediacodecdec_h264.c new file mode 100644 index 0000000000..2d1d52560f --- /dev/null +++ b/libavcodec/mediacodecdec_h264.c @@ -0,0 +1,336 @@ +/* + * Android MediaCodec H.264 decoder + * + * Copyright (c) 2015-2016 Matthieu Bouron + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include "libavutil/common.h" +#include "libavutil/fifo.h" +#include "libavutil/opt.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/pixfmt.h" +#include "libavutil/atomic.h" + +#include "avcodec.h" +#include "internal.h" +#include "mediacodecdec.h" +#include "mediacodec_wrapper.h" + +#define CODEC_MIME "video/avc" + +typedef struct MediaCodecH264DecContext { + + MediaCodecDecContext ctx; + + AVBitStreamFilterContext *bsf; + + AVFifoBuffer *fifo; + + AVPacket input_ref; + AVPacket filtered_pkt; + uint8_t *filtered_data; + +} MediaCodecH264DecContext; + +static int h264_extradata_to_annexb_sps_pps(AVCodecContext *avctx, + uint8_t **extradata_annexb, int *extradata_annexb_size, + int *sps_offset, int *sps_size, + int *pps_offset, int *pps_size) +{ + uint16_t unit_size; + uint64_t total_size = 0; + + uint8_t i, j, unit_nb; + uint8_t sps_seen = 0; + uint8_t pps_seen = 0; + + const uint8_t *extradata; + static const uint8_t nalu_header[4] = { 0x00, 0x00, 0x00, 0x01 }; + + if (avctx->extradata_size < 8) { + av_log(avctx, AV_LOG_ERROR, + "Too small extradata size, corrupted stream or invalid MP4/AVCC bitstream\n"); + return AVERROR(EINVAL); + } + + *extradata_annexb = NULL; + *extradata_annexb_size = 0; + + *sps_offset = *sps_size = 0; + *pps_offset = *pps_size = 0; + + extradata = avctx->extradata + 4; + + /* skip length size */ + extradata++; + + for (j = 0; j < 2; j ++) { + + if (j == 0) { + /* number of sps unit(s) */ + unit_nb = *extradata++ & 0x1f; + } else { + /* number of pps unit(s) */ + unit_nb = *extradata++; + } + + for (i = 0; i < unit_nb; i++) { + int err; + + unit_size = AV_RB16(extradata); + total_size += unit_size + 4; + + if (total_size > INT_MAX) { + av_log(avctx, AV_LOG_ERROR, + "Too big extradata size, corrupted stream or invalid MP4/AVCC bitstream\n"); + av_freep(extradata_annexb); + return AVERROR(EINVAL); + } + + if (extradata + 2 + unit_size > avctx->extradata + avctx->extradata_size) { + av_log(avctx, AV_LOG_ERROR, "Packet header is not contained in global extradata, " + "corrupted stream or invalid MP4/AVCC bitstream\n"); + av_freep(extradata_annexb); + return AVERROR(EINVAL); + } + + if ((err = av_reallocp(extradata_annexb, total_size)) < 0) { + return err; + } + + memcpy(*extradata_annexb + total_size - unit_size - 4, nalu_header, 4); + memcpy(*extradata_annexb + total_size - unit_size, extradata + 2, unit_size); + extradata += 2 + unit_size; + } + + if (unit_nb) { + if (j == 0) { + sps_seen = 1; + *sps_size = total_size; + } else { + pps_seen = 1; + *pps_size = total_size - *sps_size; + *pps_offset = *sps_size; + } + } + } + + *extradata_annexb_size = total_size; + + if (!sps_seen) + av_log(avctx, AV_LOG_WARNING, + "Warning: SPS NALU missing or invalid. " + "The resulting stream may not play.\n"); + + if (!pps_seen) + av_log(avctx, AV_LOG_WARNING, + "Warning: PPS NALU missing or invalid. " + "The resulting stream may not play.\n"); + + return 0; +} + +static av_cold int mediacodec_decode_close(AVCodecContext *avctx) +{ + MediaCodecH264DecContext *s = avctx->priv_data; + + ff_mediacodec_dec_close(avctx, &s->ctx); + + av_fifo_free(s->fifo); + + av_bitstream_filter_close(s->bsf); + + return 0; +} + +static av_cold int mediacodec_decode_init(AVCodecContext *avctx) +{ + int ret; + FFAMediaFormat *format = NULL; + MediaCodecH264DecContext *s = avctx->priv_data; + + format = ff_AMediaFormat_new(); + if (!format) { + av_log(avctx, AV_LOG_ERROR, "Failed to create media format\n"); + ret = AVERROR_EXTERNAL; + goto done; + } + + ff_AMediaFormat_setString(format, "mime", CODEC_MIME); + ff_AMediaFormat_setInt32(format, "width", avctx->width); + ff_AMediaFormat_setInt32(format, "height", avctx->height); + + if (avctx->extradata[0] == 1) { + uint8_t *extradata = NULL; + int extradata_size = 0; + + int sps_offset, sps_size; + int pps_offset, pps_size; + + if ((ret = h264_extradata_to_annexb_sps_pps(avctx, &extradata, &extradata_size, + &sps_offset, &sps_size, &pps_offset, &pps_size)) < 0) { + goto done; + } + + ff_AMediaFormat_setBuffer(format, "csd-0", extradata + sps_offset, sps_size); + ff_AMediaFormat_setBuffer(format, "csd-1", extradata + pps_offset, pps_size); + + av_freep(&extradata); + } else { + ff_AMediaFormat_setBuffer(format, "csd-0", avctx->extradata, avctx->extradata_size); + } + + if ((ret = ff_mediacodec_dec_init(avctx, &s->ctx, CODEC_MIME, format)) < 0) { + goto done; + } + + av_log(avctx, AV_LOG_INFO, "MediaCodec started successfully, ret = %d\n", ret); + + s->fifo = av_fifo_alloc(sizeof(AVPacket)); + if (!s->fifo) { + ret = AVERROR(ENOMEM); + goto done; + } + + s->bsf = av_bitstream_filter_init("h264_mp4toannexb"); + if (!s->bsf) { + ret = AVERROR(ENOMEM); + goto done; + } + +done: + if (format) { + ff_AMediaFormat_delete(format); + } + + if (ret < 0) { + mediacodec_decode_close(avctx); + } + return ret; +} + + +static int mediacodec_process_data(AVCodecContext *avctx, AVFrame *frame, + int *got_frame, AVPacket *pkt) +{ + MediaCodecH264DecContext *s = avctx->priv_data; + + return ff_mediacodec_dec_decode(avctx, &s->ctx, frame, got_frame, pkt); +} + +static int mediacodec_decode_frame(AVCodecContext *avctx, void *data, + int *got_frame, AVPacket *avpkt) +{ + MediaCodecH264DecContext *s = avctx->priv_data; + AVFrame *frame = data; + int ret; + + /* buffer the input packet */ + if (avpkt->size) { + AVPacket input_ref = { 0 }; + + if (av_fifo_space(s->fifo) < sizeof(input_ref)) { + ret = av_fifo_realloc2(s->fifo, + av_fifo_size(s->fifo) + sizeof(input_ref)); + if (ret < 0) + return ret; + } + + ret = av_packet_ref(&input_ref, avpkt); + if (ret < 0) + return ret; + av_fifo_generic_write(s->fifo, &input_ref, sizeof(input_ref), NULL); + } + + /* process buffered data */ + while (!*got_frame) { + /* prepare the input data -- convert to Annex B if needed */ + if (s->filtered_pkt.size <= 0) { + int size; + + /* no more data */ + if (av_fifo_size(s->fifo) < sizeof(AVPacket)) { + return avpkt->size ? avpkt->size : + ff_mediacodec_dec_decode(avctx, &s->ctx, frame, got_frame, avpkt); + } + + if (s->filtered_data != s->input_ref.data) + av_freep(&s->filtered_data); + s->filtered_data = NULL; + av_packet_unref(&s->input_ref); + + av_fifo_generic_read(s->fifo, &s->input_ref, sizeof(s->input_ref), NULL); + ret = av_bitstream_filter_filter(s->bsf, avctx, NULL, + &s->filtered_data, &size, + s->input_ref.data, s->input_ref.size, 0); + if (ret < 0) { + s->filtered_data = s->input_ref.data; + size = s->input_ref.size; + } + s->filtered_pkt = s->input_ref; + s->filtered_pkt.data = s->filtered_data; + s->filtered_pkt.size = size; + } + + ret = mediacodec_process_data(avctx, frame, got_frame, &s->filtered_pkt); + if (ret < 0) + return ret; + + s->filtered_pkt.size -= ret; + s->filtered_pkt.data += ret; + } + + return avpkt->size; +} + +static void mediacodec_decode_flush(AVCodecContext *avctx) +{ + MediaCodecH264DecContext *s = avctx->priv_data; + + while (av_fifo_size(s->fifo)) { + AVPacket pkt; + av_fifo_generic_read(s->fifo, &pkt, sizeof(pkt), NULL); + av_packet_unref(&pkt); + } + av_fifo_reset(s->fifo); + + av_packet_unref(&s->input_ref); + + av_init_packet(&s->filtered_pkt); + s->filtered_pkt.data = NULL; + s->filtered_pkt.size = 0; + + ff_mediacodec_dec_flush(avctx, &s->ctx); +} + +AVCodec ff_h264_mediacodec_decoder = { + .name = "h264_mediacodec", + .long_name = NULL_IF_CONFIG_SMALL("H.264 Android MediaCodec decoder"), + .type = AVMEDIA_TYPE_VIDEO, + .id = AV_CODEC_ID_H264, + .priv_data_size = sizeof(MediaCodecH264DecContext), + .init = mediacodec_decode_init, + .decode = mediacodec_decode_frame, + .flush = mediacodec_decode_flush, + .close = mediacodec_decode_close, + .capabilities = CODEC_CAP_DELAY, +}; diff --git a/libavcodec/version.h b/libavcodec/version.h index 95f4551992..bddf503009 100644 --- a/libavcodec/version.h +++ b/libavcodec/version.h @@ -28,8 +28,8 @@ #include "libavutil/version.h" #define LIBAVCODEC_VERSION_MAJOR 57 -#define LIBAVCODEC_VERSION_MINOR 27 -#define LIBAVCODEC_VERSION_MICRO 101 +#define LIBAVCODEC_VERSION_MINOR 28 +#define LIBAVCODEC_VERSION_MICRO 100 #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \ LIBAVCODEC_VERSION_MINOR, \ -- cgit v1.2.3