On Sat, Feb 27, 2016 at 04:28:43PM +0100, Michael Niedermayer wrote: > On Fri, Feb 26, 2016 at 04:54:47PM +0100, Matthieu Bouron wrote: > > On Tue, Feb 23, 2016 at 11:28:01AM +0100, wm4 wrote: > > > On Tue, 23 Feb 2016 09:53:43 +0100 > > > Matthieu Bouron <matthieu.bou...@gmail.com> wrote: > > > > > > > On Mon, Feb 22, 2016 at 01:08:49PM +0100, Michael Niedermayer wrote: > > > > > On Mon, Feb 22, 2016 at 12:20:36PM +0100, Matthieu Bouron wrote: > > > > > > From: Matthieu Bouron <matthieu.bou...@stupeflix.com> > > > > > [...] > > > > > > + codec = (*env)->NewObject(env, > > > > > > jfields.mediacodec_list_class, jfields.init_id, 0); > > > > > > + if (!codec) { > > > > > > + av_log(NULL, AV_LOG_ERROR, "Could not create media > > > > > > codec list\n"); > > > > > > + goto done; > > > > > > + } > > > > > > + > > > > > > + tmp = (*env)->CallObjectMethod(env, codec, > > > > > > jfields.find_decoder_for_format_id, format); > > > > > > + if (!tmp) { > > > > > > + av_log(NULL, AV_LOG_ERROR, "Could not find decoder in > > > > > > media codec list\n"); > > > > > > + goto done; > > > > > > + } > > > > > > + > > > > > > + name = ff_jni_jstring_to_utf_chars(env, tmp, NULL); > > > > > > + if (!name) { > > > > > > > > > > > + av_log(NULL, AV_LOG_ERROR, "Could not convert jstring > > > > > > to utf chars\n"); > > > > > > > > > > some non NULL context would be better, if possible, so the user knows > > > > > where an error came from > > > > > > > > It would require to pass the log_ctx (avctx) as an argument of > > > > ff_AMediaCodecList_getCodecByName. > > > > > > > > All the functions of this wrapper does not take a log_ctx as argument > > > > to be as close as possible to the original NDK MediaCodec API. The > > > > NDK MediaCodec API does not provide any wrapper around MediaCodecList. > > > > > > > > I would like the whole wrapper API to be consistent but also as close as > > > > possible to original one. > > > > If you think it's mandatory I can add a log_ctx argument to every > > > > functions of the wrapper API (so it will be used directly by av_log and > > > > ff_jni_exception_check). > > > > > > > > On another note, I fixed locally this part of the code by adding > > > > missing calls > > > > to ff_jni_exception_check. > > > > > > > > [...] > > > > > > Is it possible to store the log_ctx somewhere in the JNI? > > > > > > We might at some point in the future forbid av_log(NULL, ...) calls, and > > > then it'd get complicated. > > > > Patch updated with the following differences: > > * All logging arguments in mediacodec_wrapper functions are now non > > NULL > > * The mediacodec buffer copy functions has been moved to a separate file > > * The Mediacodec enum codec flags are properly retrieved from JNI > > > > The codec extradata conversion to annex b simplification has not been > > addressed in this update. The bitstream filter api does not seem to > > provide a way to do the extradata conversion without feeding an actual > > packet to the api (av_bitstream_filter_filter) and i would like to keep > > the codec initialization in the init function. > > > > The patchset has been pushed to a new branch: > > https://github.com/mbouron/FFmpeg/tree/feature/mediacodec-support-v3 > > > > Matthieu > > > 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 | 1674 > > ++++++++++++++++++++++++++++++++++++++ > > libavcodec/mediacodec_wrapper.h | 122 ++ > > libavcodec/mediacodecdec.c | 563 ++++++++++++ > > libavcodec/mediacodecdec.h | 82 + > > libavcodec/mediacodecdec_h264.c | 356 ++++++++ > > 10 files changed, 3207 insertions(+) > > f545068afece74d27cc04a365d9de7dcf5586a7d > > 0002-lavc-add-h264-mediacodec-decoder.patch > > From 805c7b95433f1bfbbc5d47fd59a1bbb755edb112 Mon Sep 17 00:00:00 2001 > > From: Matthieu Bouron <matthieu.bou...@stupeflix.com> > > Date: Thu, 21 Jan 2016 09:29:39 +0100 > > Subject: [PATCH 2/2] lavc: add h264 mediacodec decoder > > breaks fate > swr-resample_lin-fltp-48000-44100 > TEST swr-resample_lin-dblp-8000-44100 > TEST swr-resample_lin-dblp-8000-48000 > --- ./tests/ref/fate/source 2016-02-24 22:42:26.379879152 +0100 > +++ tests/data/fate/source 2016-02-27 14:44:09.176735216 +0100 > @@ -27,3 +27,4 @@ > compat/avisynth/windowsPorts/windows2linux.h > compat/float/float.h > compat/float/limits.h > +libavcodec/mediacodec_sw_buffer.h > Test source failed. Look at tests/data/fate/source.err for details. > make: *** [fate-source] Error 1 > make: *** Waiting for unfinished jobs....
New patch attached fixing the issue. It also fixes an issue while the binding was trying to access the field MediaCodec.BUFFER_FLAG_KEY_FRAME which is only available in android >= 5 causing the JNI code to try to call a method on a NULL field ID which caused the VM to crash on older devices. This field is now non mandatory. The codec extradata conversion is still not addressed as the bitstream API does not expose a method to do such conversion without feeding an actual packet to it. I think what is left to do at this point is more testing towards corrupted bitstream and see how to the API behaves and see how it impacts the decoder (as I rely on counters of queued and dequeued buffers, which is a bit evil). If you happen to have such h264 samples, it would be very helpful. After that, I will begin to work towards supporting surface output (as a separate patch). Matthieu
>From 4c359d8e4a20d49c4f1b0e2e1cd5daad56400760 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron <matthieu.bou...@stupeflix.com> Date: Thu, 21 Jan 2016 09:29:39 +0100 Subject: [PATCH 2/2] lavc: add h264 mediacodec decoder --- 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 | 1680 +++++++++++++++++++++++++++++++++++++ libavcodec/mediacodec_wrapper.h | 122 +++ libavcodec/mediacodecdec.c | 563 +++++++++++++ libavcodec/mediacodecdec.h | 82 ++ libavcodec/mediacodecdec_h264.c | 356 ++++++++ 10 files changed, 3213 insertions(+) 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/configure b/configure index fe94f84..0c0f18e 100755 --- a/configure +++ b/configure @@ -277,6 +277,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 @@ -2502,6 +2504,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" @@ -5660,6 +5664,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 c7a0ede..c803536 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -90,6 +90,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 @@ -305,6 +306,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 @@ -943,6 +945,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 2097db0..58bcb24 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 0000000..df75754 --- /dev/null +++ b/libavcodec/mediacodec_sw_buffer.c @@ -0,0 +1,339 @@ +/* + * Android MediaCodec software buffer copy functions + * + * Copyright (c) 2015-2016 Matthieu Bouron <matthieu.bouron stupeflix.com> + * + * 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 <string.h> +#include <sys/types.h> + +#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 <sebastian.dro...@collabora.co.uk> + * + * Copyright (C) 2012, Rafaël Carré <funman@videolanorg> + * + * Copyright (C) 2015, Sebastian Dröge <sebast...@centricular.com> + * + * Copyright (C) 2014-2015, Collabora Ltd. + * Author: Matthieu Bouron <matthieu.bou...@gcollabora.com> + * + * Copyright (C) 2015, Edward Hervey + * Author: Edward Hervey <bilb...@gmail.com> + * + * Copyright (C) 2015, Matthew Waters <matt...@centricular.com> + * + * 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é <funman@videolanorg> + * + * 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 0000000..c29de08 --- /dev/null +++ b/libavcodec/mediacodec_sw_buffer.h @@ -0,0 +1,62 @@ +/* + * Android MediaCodec software buffer copy functions + * + * Copyright (c) 2015-2016 Matthieu Bouron <matthieu.bouron stupeflix.com> + * + * 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 <sys/types.h> + +#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 0000000..6f1fcc9 --- /dev/null +++ b/libavcodec/mediacodec_wrapper.c @@ -0,0 +1,1680 @@ +/* + * Android MediaCodec Wrapper + * + * Copyright (c) 2015-2016 Matthieu Bouron <matthieu.bouron stupeflix.com> + * + * 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 <jni.h> + +#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", "<init>", "(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", "<init>", "()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 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", "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", "<init>", "()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 (!codec->object) { + 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 (!codec->object) { + 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 (!codec->object) { + 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; +} + + +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 0000000..a3461fe --- /dev/null +++ b/libavcodec/mediacodec_wrapper.h @@ -0,0 +1,122 @@ +/* + * Android MediaCodec Wrapper + * + * Copyright (c) 2015-2016 Matthieu Bouron <matthieu.bouron stupeflix.com> + * + * 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 <stdint.h> +#include <sys/types.h> + +/** + * 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 + * 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; + +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 0000000..574ece2 --- /dev/null +++ b/libavcodec/mediacodecdec.c @@ -0,0 +1,563 @@ +/* + * Android MediaCodec decoder + * + * Copyright (c) 2015-2016 Matthieu Bouron <matthieu.bouron stupeflix.com> + * + * 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 <string.h> +#include <sys/types.h> + +#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 <sebastian.dro...@collabora.co.uk> + * + * Copyright (C) 2012, Rafaël Carré <funman@videolanorg> + * + * Copyright (C) 2015, Sebastian Dröge <sebast...@centricular.com> + * + * Copyright (C) 2014-2015, Collabora Ltd. + * Author: Matthieu Bouron <matthieu.bou...@gcollabora.com> + * + * Copyright (C) 2015, Edward Hervey + * Author: Edward Hervey <bilb...@gmail.com> + * + * Copyright (C) 2015, Matthew Waters <matt...@centricular.com> + * + * 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) +{ + 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; + + av_log(avctx, AV_LOG_INFO, + "Output crop parameters top=%d bottom=%d left=%d right=%d\n", + s->crop_top, s->crop_bottom, s->crop_left, s->crop_right); + + return 0; +} + +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 0000000..bf23f85 --- /dev/null +++ b/libavcodec/mediacodecdec.h @@ -0,0 +1,82 @@ +/* + * Android MediaCodec decoder + * + * Copyright (c) 2015-2016 Matthieu Bouron <matthieu.bouron stupeflix.com> + * + * 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 <stdint.h> +#include <sys/types.h> + +#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 0000000..296768b --- /dev/null +++ b/libavcodec/mediacodecdec_h264.c @@ -0,0 +1,356 @@ +/* + * Android MediaCodec H.264 decoder + * + * Copyright (c) 2015-2016 Matthieu Bouron <matthieu.bouron stupeflix.com> + * + * 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 <stdint.h> +#include <string.h> + +#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; + + AVCodecContext *avctx_internal; + + 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); + avcodec_free_context(&s->avctx_internal); + + 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; + } + + s->avctx_internal = avcodec_alloc_context3(NULL); + if (!s->avctx_internal) { + ret = AVERROR(ENOMEM); + goto done; + } + + if (avctx->extradata) { + s->avctx_internal->extradata = av_mallocz(avctx->extradata_size + FF_INPUT_BUFFER_PADDING_SIZE); + if (!s->avctx_internal->extradata) { + ret = AVERROR(ENOMEM); + goto done; + } + memcpy(s->avctx_internal->extradata, avctx->extradata, + avctx->extradata_size); + s->avctx_internal->extradata_size = avctx->extradata_size; + } + +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, +}; -- 2.7.2
_______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel