On Thu, Apr 07, 2016 at 02:51:44PM +0200, Matthieu Bouron wrote: > On Wed, Mar 23, 2016 at 6:16 PM, Matthieu Bouron <matthieu.bou...@gmail.com> > wrote: > > > > > > > On Tue, Mar 22, 2016 at 10:04 AM, Matthieu Bouron < > > matthieu.bou...@gmail.com> wrote: > > > >> > >> > >> On Fri, Mar 18, 2016 at 5:50 PM, Matthieu Bouron < > >> matthieu.bou...@gmail.com> wrote: > >> > >>> From: Matthieu Bouron <matthieu.bou...@stupeflix.com> > >>> > >>> --- > >>> > >>> Hello, > >>> > >>> The following patch add hwaccel support to the mediacodec (h264) decoder > >>> by allowing > >>> the user to render the output frames directly on a surface. > >>> > >>> In order to do so the user needs to initialize the hwaccel through the > >>> use of > >>> av_mediacodec_alloc_context and av_mediacodec_default_init functions. > >>> The later > >>> takes a reference to an android/view/Surface as parameter. > >>> > >>> If the hwaccel successfully initialize, the decoder output frames pix > >>> fmt will be > >>> AV_PIX_FMT_MEDIACODEC. The following snippet of code demonstrate how to > >>> render > >>> the frames on the surface: > >>> > >>> AVMediaCodecBuffer *buffer = (AVMediaCodecBuffer *)frame->data[3]; > >>> av_mediacodec_release_buffer(buffer, 1); > >>> > >>> The last argument of av_mediacodec_release_buffer enable rendering of the > >>> buffer on the surface (or not if set to 0). > >>> > >>> Regarding the internal changes in the mediacodec decoder: > >>> > >>> MediaCodec.flush() discards both input and output buffers meaning that if > >>> MediaCodec.flush() is called all output buffers the user has a reference > >>> on are > >>> now invalid (and cannot be used). > >>> This behaviour does not fit well in the avcodec API. > >>> > >>> When the decoder is configured to output software buffers, there is no > >>> issue as > >>> the buffers are copied. > >>> > >>> Now when the decoder is configured to output to a surface, the user > >>> might not > >>> want to render all the frames as fast as the decoder can go and might > >>> want to > >>> control *when* the frame are rendered, so we need to make sure that the > >>> MediaCodec.flush() call is delayed until all the frames the user retains > >>> has > >>> been released or rendered. > >>> > >>> Delaying the call to MediaCodec.flush() means buffering any inputs that > >>> come > >>> the decoder until the user has released/renderer the frame he retains. > >>> > >>> This is a limitation of this hwaccel implementation, if the user retains > >>> a > >>> frame (a), then issue a flush command to the decoder, the packets he > >>> feeds to > >>> the decoder at that point will be queued in the internal decoder packet > >>> queue > >>> (until he releases the frame (a)). This scenario leads to a memory usage > >>> increase to say the least. > >>> > >>> Currently there is no limitation on the size of the internal decoder > >>> packet > >>> queue but this is something that can be added easily. Then, if the queue > >>> is > >>> full, what would be the behaviour of the decoder ? Can it block ? Or > >>> should it > >>> returns something like AVERROR(EAGAIN) ? > >>> > >>> About the other internal decoder changes I introduced: > >>> > >>> The MediaCodecDecContext is now refcounted (using the lavu/atomic api) > >>> since > >>> the (hwaccel) frames can be retained by the user, we need to delay the > >>> destruction of the codec until the user has released all the frames he > >>> has a > >>> reference on. > >>> The reference counter of the MediaCodecDecContext is incremented each > >>> time an > >>> (hwaccel) frame is outputted by the decoder and decremented each time a > >>> (hwaccel) frame is released. > >>> > >>> Also, when the decoder is configured to output to a surface the pts that > >>> are > >>> given to the MediaCodec API are now rescaled based on the codec_timebase > >>> as > >>> those timestamps values are propagated to the frames rendered on the > >>> surface > >>> since Android M. Not sure if it's really useful though. > >>> > >>> On the performance side: > >>> > >>> On a nexus 5, decoding an h264 stream (main profile) 1080p@60fps: > >>> - software output + rgba conversion goes at 59~60fps > >>> - surface output + render on a surface goes at 100~110fps > >>> > >>> > >> [...] > >> > >> Patch updated with the following differences: > >> * the public mediacodec api is now always built (not only when > >> mediacodec is available) (and the build when mediacodec is not available > >> has been fixed) > >> * the documentation of av_mediacodec_release_buffer has been improved a > >> bit > >> > > > > Patch updated with the following differences: > > MediaCodecBuffer->released type is now a volatile int (instead of a int*) > > MediaCodecContext->refcount type is now a volatile int (instead of a > > int*) > > > > Ping.
Rebased patch attached. Matthieu
>From b8a63f18eeecc4f52524db75bc5a2cf1d13cbab2 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron <matthieu.bou...@stupeflix.com> Date: Fri, 11 Mar 2016 17:21:04 +0100 Subject: [PATCH] lavc: add mediacodec hwaccel support --- configure | 1 + libavcodec/Makefile | 6 +- libavcodec/allcodecs.c | 1 + libavcodec/mediacodec.c | 133 ++++++++++++++++++++ libavcodec/mediacodec.h | 88 +++++++++++++ libavcodec/mediacodec_surface.c | 66 ++++++++++ libavcodec/mediacodec_surface.h | 31 +++++ libavcodec/mediacodec_wrapper.c | 5 +- libavcodec/mediacodecdec.c | 265 +++++++++++++++++++++++++++++++++------- libavcodec/mediacodecdec.h | 17 +++ libavcodec/mediacodecdec_h264.c | 44 ++++++- libavcodec/version.h | 2 +- libavutil/pixdesc.c | 4 + libavutil/pixfmt.h | 2 + libavutil/version.h | 2 +- 15 files changed, 607 insertions(+), 60 deletions(-) create mode 100644 libavcodec/mediacodec.c create mode 100644 libavcodec/mediacodec.h create mode 100644 libavcodec/mediacodec_surface.c create mode 100644 libavcodec/mediacodec_surface.h diff --git a/configure b/configure index 9ce377c..49d6071 100755 --- a/configure +++ b/configure @@ -2547,6 +2547,7 @@ h264_d3d11va_hwaccel_select="h264_decoder" h264_dxva2_hwaccel_deps="dxva2" h264_dxva2_hwaccel_select="h264_decoder" h264_mediacodec_decoder_deps="mediacodec" +h264_mediacodec_hwaccel_deps="mediacodec" h264_mediacodec_decoder_select="h264_mp4toannexb_bsf h264_parser" h264_mmal_decoder_deps="mmal" h264_mmal_decoder_select="mmal" diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 3e3fb7b..c09362a 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -10,6 +10,7 @@ HEADERS = avcodec.h \ dv_profile.h \ dxva2.h \ jni.h \ + mediacodec.h \ qsv.h \ vaapi.h \ vda.h \ @@ -37,6 +38,7 @@ OBJS = allcodecs.o \ imgconvert.o \ jni.o \ mathtables.o \ + mediacodec.o \ options.o \ parser.o \ profiles.o \ @@ -95,7 +97,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_MEDIACODEC) += mediacodecdec.o mediacodec_surface.o mediacodec_wrapper.o mediacodec_sw_buffer.o OBJS-$(CONFIG_MPEG_ER) += mpeg_er.o OBJS-$(CONFIG_MPEGAUDIO) += mpegaudio.o mpegaudiodata.o \ mpegaudiodecheader.o @@ -995,7 +997,7 @@ SKIPHEADERS-$(CONFIG_JNI) += ffjni.h SKIPHEADERS-$(CONFIG_LIBSCHROEDINGER) += libschroedinger.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_MEDIACODEC) += mediacodecdec.h mediacodec_surface.h mediacodec_wrapper.h mediacodec_sw_buffer.h SKIPHEADERS-$(CONFIG_NVENC) += nvenc.h SKIPHEADERS-$(CONFIG_QSV) += qsv.h qsv_internal.h SKIPHEADERS-$(CONFIG_QSVDEC) += qsvdec.h diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index 36a0de0..2f2cf59 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -72,6 +72,7 @@ void avcodec_register_all(void) REGISTER_HWACCEL(H264_CUVID, h264_cuvid); REGISTER_HWACCEL(H264_D3D11VA, h264_d3d11va); REGISTER_HWACCEL(H264_DXVA2, h264_dxva2); + REGISTER_HWACCEL(H264_MEDIACODEC, h264_mediacodec); REGISTER_HWACCEL(H264_MMAL, h264_mmal); REGISTER_HWACCEL(H264_QSV, h264_qsv); REGISTER_HWACCEL(H264_VAAPI, h264_vaapi); diff --git a/libavcodec/mediacodec.c b/libavcodec/mediacodec.c new file mode 100644 index 0000000..5b79798 --- /dev/null +++ b/libavcodec/mediacodec.c @@ -0,0 +1,133 @@ +/* + * Android MediaCodec public API functions + * + * Copyright (c) 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 "config.h" + +#if CONFIG_H264_MEDIACODEC_HWACCEL + +#include <jni.h> + +#include "libavcodec/avcodec.h" +#include "libavutil/atomic.h" +#include "libavutil/mem.h" + +#include "ffjni.h" +#include "mediacodec.h" +#include "mediacodecdec.h" + +AVMediaCodecContext *av_mediacodec_alloc_context(void) +{ + return av_mallocz(sizeof(AVMediaCodecContext)); +} + +int av_mediacodec_default_init(AVCodecContext *avctx, AVMediaCodecContext *ctx, void *surface) +{ + int ret = 0; + JNIEnv *env = NULL; + int attached = 0; + + env = ff_jni_attach_env(&attached, avctx); + if (!env) { + return AVERROR_EXTERNAL; + } + + ctx->surface = (*env)->NewGlobalRef(env, surface); + if (ctx->surface) { + avctx->hwaccel_context = ctx; + } else { + av_log(avctx, AV_LOG_ERROR, "Could not create new global reference\n"); + ret = AVERROR_EXTERNAL; + } + + if (attached) { + ff_jni_detach_env(avctx); + } + + return ret; +} + +void av_mediacodec_default_free(AVCodecContext *avctx) +{ + JNIEnv *env = NULL; + int attached = 0; + + AVMediaCodecContext *ctx = avctx->hwaccel_context; + + if (!ctx) { + return; + } + + env = ff_jni_attach_env(&attached, avctx); + if (!env) { + return; + } + + if (ctx->surface) { + (*env)->DeleteGlobalRef(env, ctx->surface); + ctx->surface = NULL; + } + + if (attached) { + ff_jni_detach_env(avctx); + } + + av_freep(&avctx->hwaccel_context); +} + +int av_mediacodec_release_buffer(AVMediaCodecBuffer *buffer, int render) +{ + MediaCodecDecContext *ctx = buffer->ctx; + int released = avpriv_atomic_int_add_and_fetch(&buffer->released, 1); + + if (released == 1) { + return ff_AMediaCodec_releaseOutputBuffer(ctx->codec, buffer->index, render); + } + + return 0; +} + +#else + +#include <stdlib.h> + +#include "mediacodec.h" + +AVMediaCodecContext *av_mediacodec_alloc_context(void) +{ + return NULL; +} + +int av_mediacodec_default_init(AVCodecContext *avctx, AVMediaCodecContext *ctx, void *surface) +{ + return 0; +} + +void av_mediacodec_default_free(AVCodecContext *avctx) +{ +} + +int av_mediacodec_release_buffer(AVMediaCodecBuffer *buffer, int render) +{ + return 0; +} + +#endif diff --git a/libavcodec/mediacodec.h b/libavcodec/mediacodec.h new file mode 100644 index 0000000..f755bd1 --- /dev/null +++ b/libavcodec/mediacodec.h @@ -0,0 +1,88 @@ +/* + * Android MediaCodec public API + * + * Copyright (c) 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_H +#define AVCODEC_MEDIACODEC_H + +#include "libavcodec/avcodec.h" + +/** + * This structure holds a reference to a android/view/Surface object that will + * be used as output by the decoder. + * + */ +typedef struct AVMediaCodecContext { + + /** + * android/view/Surface object reference. + */ + void *surface; + +} AVMediaCodecContext; + +/** + * Allocate and initialize a MediaCodec context. + * + * When decoding with MediaCodec is finished, the caller must free the + * MediaCodec context with av_mediacodec_default_free. + * + * @return a pointer to a newly allocated AVMediaCodecContext on success, NULL otherwise + */ +AVMediaCodecContext *av_mediacodec_alloc_context(void); + +/** + * Convenience function that sets up the MediaCodec context. + * + * @param avctx codec context + * @param ctx MediaCodec context to initialize + * @param surface reference to an android/view/Surface + * @return 0 on success, < 0 otherwise + */ +int av_mediacodec_default_init(AVCodecContext *avctx, AVMediaCodecContext *ctx, void *surface); + +/** + * This function must be called to free the MediaCodec context initialized with + * av_mediacodec_default_init(). + * + * @param avctx codec context + */ +void av_mediacodec_default_free(AVCodecContext *avctx); + +/** + * Opaque structure representing a MediaCodec buffer to render. + */ +typedef struct MediaCodecBuffer AVMediaCodecBuffer; + +/** + * Release a MediaCodec buffer and render it on the surface that is associated + * with the decoder. This function should only be called once on a given + * buffer, once released the underlying buffer returns to the codec, thus + * subsequent calls to this function will have no effect. + * + * @param buffer the buffer to render + * @param render 1 to release and render the buffer on the surface or 0 to + * only release the buffer + * @return 0 on success, < 0 otherwise + */ +int av_mediacodec_release_buffer(AVMediaCodecBuffer *buffer, int render); + +#endif /* AVCODEC_MEDIACODEC_H */ diff --git a/libavcodec/mediacodec_surface.c b/libavcodec/mediacodec_surface.c new file mode 100644 index 0000000..903ebe4 --- /dev/null +++ b/libavcodec/mediacodec_surface.c @@ -0,0 +1,66 @@ +/* + * Android MediaCodec Surface functions + * + * Copyright (c) 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 "ffjni.h" +#include "mediacodec_surface.h" + +void *ff_mediacodec_surface_ref(void *surface, void *log_ctx) +{ + int attached = 0; + JNIEnv *env = NULL; + + void *reference = NULL; + + env = ff_jni_attach_env(&attached, log_ctx); + if (!env) { + return NULL; + } + + reference = (*env)->NewGlobalRef(env, surface); + + if (attached) { + ff_jni_detach_env(log_ctx); + } + + return reference; +} + +int ff_mediacodec_surface_unref(void *surface, void *log_ctx) +{ + int attached = 0; + JNIEnv *env = NULL; + + env = ff_jni_attach_env(&attached, log_ctx); + if (!env) { + return AVERROR_EXTERNAL; + } + + (*env)->DeleteGlobalRef(env, surface); + + if (attached) { + ff_jni_detach_env(log_ctx); + } + + return 0; +} diff --git a/libavcodec/mediacodec_surface.h b/libavcodec/mediacodec_surface.h new file mode 100644 index 0000000..0178b8a --- /dev/null +++ b/libavcodec/mediacodec_surface.h @@ -0,0 +1,31 @@ +/* + * Android MediaCodec Surface functions + * + * Copyright (c) 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_SURFACE_H +#define AVCODEC_MEDIACODEC_SURFACE_H + +#include "libavcodec/avcodec.h" + +void *ff_mediacodec_surface_ref(void *surface, void *log_ctx); +int ff_mediacodec_surface_unref(void *surface, void *log_ctx); + +#endif /* AVCODEC_MEDIACODEC_SURFACE_H */ diff --git a/libavcodec/mediacodec_wrapper.c b/libavcodec/mediacodec_wrapper.c index 546768f..cd933d6 100644 --- a/libavcodec/mediacodec_wrapper.c +++ b/libavcodec/mediacodec_wrapper.c @@ -1416,12 +1416,9 @@ int ff_AMediaCodec_configure(FFAMediaCodec* codec, const FFAMediaFormat* format, 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); + (*env)->CallVoidMethod(env, codec->object, codec->jfields.configure_id, format->object, surface, NULL, flags); if (ff_jni_exception_check(env, 1, codec) < 0) { ret = AVERROR_EXTERNAL; goto fail; diff --git a/libavcodec/mediacodecdec.c b/libavcodec/mediacodecdec.c index e29637e..4675de4 100644 --- a/libavcodec/mediacodecdec.c +++ b/libavcodec/mediacodecdec.c @@ -23,6 +23,7 @@ #include <string.h> #include <sys/types.h> +#include "libavutil/atomic.h" #include "libavutil/common.h" #include "libavutil/mem.h" #include "libavutil/log.h" @@ -33,6 +34,8 @@ #include "avcodec.h" #include "internal.h" +#include "mediacodec.h" +#include "mediacodec_surface.h" #include "mediacodec_sw_buffer.h" #include "mediacodec_wrapper.h" #include "mediacodecdec.h" @@ -118,6 +121,10 @@ static enum AVPixelFormat mcdec_map_color_format(AVCodecContext *avctx, int i; enum AVPixelFormat ret = AV_PIX_FMT_NONE; + if (s->surface) { + return AV_PIX_FMT_MEDIACODEC; + } + if (!strcmp(s->codec_name, "OMX.k3.video.decoder.avc") && color_format == COLOR_FormatYCbYCr) { s->color_format = color_format = COLOR_TI_FormatYUV420PackedSemiPlanar; } @@ -134,7 +141,112 @@ static enum AVPixelFormat mcdec_map_color_format(AVCodecContext *avctx, return ret; } -static int mediacodec_wrap_buffer(AVCodecContext *avctx, +static void ff_mediacodec_dec_ref(MediaCodecDecContext *s) +{ + avpriv_atomic_int_add_and_fetch(&s->refcount, 1); +} + +static void ff_mediacodec_dec_unref(MediaCodecDecContext *s) +{ + if (!s) + return; + + if (!avpriv_atomic_int_add_and_fetch(&s->refcount, -1)) { + if (s->codec) { + ff_AMediaCodec_delete(s->codec); + s->codec = NULL; + } + + if (s->format) { + ff_AMediaFormat_delete(s->format); + s->format = NULL; + } + + if (s->surface) { + ff_mediacodec_surface_unref(s->surface, NULL); + s->surface = NULL; + } + + av_freep(&s->codec_name); + av_freep(&s); + } +} + +static void mediacodec_buffer_release(void *opaque, uint8_t *data) +{ + AVMediaCodecBuffer *buffer = opaque; + MediaCodecDecContext *ctx = buffer->ctx; + int released = avpriv_atomic_int_get(&buffer->released); + + if (!released) { + ff_AMediaCodec_releaseOutputBuffer(ctx->codec, buffer->index, 0); + } + + ff_mediacodec_dec_unref(ctx); + av_freep(&buffer); +} + +static int mediacodec_wrap_hw_buffer(AVCodecContext *avctx, + MediaCodecDecContext *s, + ssize_t index, + FFAMediaCodecBufferInfo *info, + AVFrame *frame) +{ + int ret = 0; + int status = 0; + AVMediaCodecBuffer *buffer = NULL; + + frame->buf[0] = NULL; + frame->width = avctx->width; + frame->height = avctx->height; + frame->format = avctx->pix_fmt; + frame->pkt_pts = av_rescale_q(info->presentationTimeUs, + av_make_q(1, 1000000), + avctx->pkt_timebase); + frame->pkt_dts = AV_NOPTS_VALUE; + + buffer = av_mallocz(sizeof(AVMediaCodecBuffer)); + if (!buffer) { + ret = AVERROR(ENOMEM); + goto fail; + } + + buffer->released = 0; + + frame->buf[0] = av_buffer_create(NULL, + 0, + mediacodec_buffer_release, + buffer, + AV_BUFFER_FLAG_READONLY); + + if (!frame->buf[0]) { + ret = AVERROR(ENOMEM); + goto fail; + + } + + buffer->ctx = s; + ff_mediacodec_dec_ref(s); + + buffer->index = index; + buffer->pts = info->presentationTimeUs; + + frame->data[3] = (uint8_t *)buffer; + + return 0; +fail: + av_freep(buffer); + av_buffer_unref(&frame->buf[0]); + status = ff_AMediaCodec_releaseOutputBuffer(s->codec, index, 0); + if (status < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to release output buffer\n"); + ret = AVERROR_EXTERNAL; + } + + return ret; +} + +static int mediacodec_wrap_sw_buffer(AVCodecContext *avctx, MediaCodecDecContext *s, uint8_t *data, size_t size, @@ -304,6 +416,30 @@ static int mediacodec_dec_parse_format(AVCodecContext *avctx, MediaCodecDecConte return ff_set_dimensions(avctx, width, height); } + +static int mediacodec_dec_flush_codec(AVCodecContext *avctx, MediaCodecDecContext *s) +{ + FFAMediaCodec *codec = s->codec; + int status; + + s->dequeued_buffer_nb = 0; + + s->draining = 0; + s->flushing = 0; + s->eos = 0; + + status = ff_AMediaCodec_flush(codec); + if (status < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to flush codec\n"); + return AVERROR_EXTERNAL; + } + + s->first_buffer = 0; + s->first_buffer_at = av_gettime(); + + return 0; +} + int ff_mediacodec_dec_init(AVCodecContext *avctx, MediaCodecDecContext *s, const char *mime, FFAMediaFormat *format) { @@ -311,7 +447,24 @@ int ff_mediacodec_dec_init(AVCodecContext *avctx, MediaCodecDecContext *s, int status; int profile; + enum AVPixelFormat pix_fmt; + enum AVPixelFormat pix_fmts[3] = { + AV_PIX_FMT_MEDIACODEC, + AV_PIX_FMT_NONE, + }; + s->first_buffer_at = av_gettime(); + s->refcount = 1; + + pix_fmt = ff_get_format(avctx, pix_fmts); + if (pix_fmt == AV_PIX_FMT_MEDIACODEC) { + AVMediaCodecContext *user_ctx = avctx->hwaccel_context; + + if (user_ctx && user_ctx->surface) { + s->surface = ff_mediacodec_surface_ref(user_ctx->surface, avctx); + av_log(avctx, AV_LOG_INFO, "Using surface %p\n", s->surface); + } + } profile = ff_AMediaCodecProfile_getProfileFromAVCodecContext(avctx); if (profile < 0) { @@ -332,7 +485,7 @@ int ff_mediacodec_dec_init(AVCodecContext *avctx, MediaCodecDecContext *s, goto fail; } - status = ff_AMediaCodec_configure(s->codec, format, NULL, NULL, 0); + status = ff_AMediaCodec_configure(s->codec, format, s->surface, NULL, 0); if (status < 0) { char *desc = ff_AMediaFormat_toString(format); av_log(avctx, AV_LOG_ERROR, @@ -380,7 +533,7 @@ int ff_mediacodec_dec_decode(AVCodecContext *avctx, MediaCodecDecContext *s, { int ret; int offset = 0; - int need_flushing = 0; + int need_draining = 0; uint8_t *data; ssize_t index; size_t size; @@ -392,15 +545,21 @@ int ff_mediacodec_dec_decode(AVCodecContext *avctx, MediaCodecDecContext *s, int64_t input_dequeue_timeout_us = INPUT_DEQUEUE_TIMEOUT_US; int64_t output_dequeue_timeout_us = OUTPUT_DEQUEUE_TIMEOUT_US; + if (s->flushing) { + av_log(avctx, AV_LOG_ERROR, "Decoder is flushing and cannot accept new buffer " + "until all output buffers have been released\n"); + return AVERROR_EXTERNAL; + } + if (pkt->size == 0) { - need_flushing = 1; + need_draining = 1; } - if (s->flushing && s->eos) { + if (s->draining && s->eos) { return 0; } - while (offset < pkt->size || (need_flushing && !s->flushing)) { + while (offset < pkt->size || (need_draining && !s->draining)) { int size; index = ff_AMediaCodec_dequeueInputBuffer(codec, input_dequeue_timeout_us); @@ -419,26 +578,37 @@ int ff_mediacodec_dec_decode(AVCodecContext *avctx, MediaCodecDecContext *s, return AVERROR_EXTERNAL; } - if (need_flushing) { + if (need_draining) { + int64_t pts = pkt->pts; uint32_t flags = ff_AMediaCodec_getBufferFlagEndOfStream(codec); + if (s->surface) { + pts = av_rescale_q(pts, avctx->pkt_timebase, av_make_q(1, 1000000)); + } + av_log(avctx, AV_LOG_DEBUG, "Sending End Of Stream signal\n"); - status = ff_AMediaCodec_queueInputBuffer(codec, index, 0, 0, pkt->pts, flags); + status = ff_AMediaCodec_queueInputBuffer(codec, index, 0, 0, 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; + s->draining = 1; break; } else { + int64_t pts = pkt->pts; + 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 (s->surface) { + pts = av_rescale_q(pts, avctx->pkt_timebase, av_make_q(1, 1000000)); + } + + status = ff_AMediaCodec_queueInputBuffer(codec, index, 0, size, pts, 0); if (status < 0) { av_log(avctx, AV_LOG_ERROR, "Failed to queue input buffer (status = %d)\n", status); return AVERROR_EXTERNAL; @@ -446,7 +616,7 @@ int ff_mediacodec_dec_decode(AVCodecContext *avctx, MediaCodecDecContext *s, } } - if (need_flushing || s->flushing) { + if (need_draining || s->draining) { /* If the codec is flushing or need to be flushed, block for a fair * amount of time to ensure we got a frame */ output_dequeue_timeout_us = OUTPUT_DEQUEUE_BLOCK_TIMEOUT_US; @@ -475,15 +645,22 @@ int ff_mediacodec_dec_decode(AVCodecContext *avctx, MediaCodecDecContext *s, } if (info.size) { - 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; + if (s->surface) { + if ((ret = mediacodec_wrap_hw_buffer(avctx, s, index, &info, frame)) < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to wrap MediaCodec buffer\n"); + return ret; + } + } else { + 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_sw_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; @@ -525,9 +702,9 @@ int ff_mediacodec_dec_decode(AVCodecContext *avctx, MediaCodecDecContext *s, } else if (ff_AMediaCodec_infoOutputBuffersChanged(codec, index)) { ff_AMediaCodec_cleanOutputBuffers(codec); } else if (ff_AMediaCodec_infoTryAgainLater(codec, index)) { - if (s->flushing) { + if (s->draining) { av_log(avctx, AV_LOG_ERROR, "Failed to dequeue output buffer within %" PRIi64 "ms " - "while flushing remaining frames, output will probably lack frames\n", + "while draining remaining frames, output will probably lack frames\n", output_dequeue_timeout_us / 1000); } else { av_log(avctx, AV_LOG_DEBUG, "No output buffer available, try again later\n"); @@ -542,39 +719,35 @@ int ff_mediacodec_dec_decode(AVCodecContext *avctx, MediaCodecDecContext *s, int ff_mediacodec_dec_flush(AVCodecContext *avctx, MediaCodecDecContext *s) { - FFAMediaCodec *codec = s->codec; - int status; - - s->dequeued_buffer_nb = 0; + if (!s->surface || avpriv_atomic_int_get(&s->refcount) == 1) { + int ret; - s->flushing = 0; - s->eos = 0; + if ((ret = mediacodec_dec_flush_codec(avctx, s)) < 0) { + return ret; + } - status = ff_AMediaCodec_flush(codec); - if (status < 0) { - av_log(avctx, AV_LOG_ERROR, "Failed to flush codec\n"); - return AVERROR_EXTERNAL; + return 1; } - s->first_buffer = 0; - s->first_buffer_at = av_gettime(); - + s->flushing = 1; 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; - } - - av_freep(&s->codec_name); + ff_mediacodec_dec_unref(s); return 0; } + +int ff_mediacodec_dec_is_flushing(AVCodecContext *avctx, MediaCodecDecContext *s) +{ + return s->flushing; +} + +AVHWAccel ff_h264_mediacodec_hwaccel = { + .name = "mediacodec", + .type = AVMEDIA_TYPE_VIDEO, + .id = AV_CODEC_ID_H264, + .pix_fmt = AV_PIX_FMT_MEDIACODEC, +}; diff --git a/libavcodec/mediacodecdec.h b/libavcodec/mediacodecdec.h index 646b628..8613352 100644 --- a/libavcodec/mediacodecdec.h +++ b/libavcodec/mediacodecdec.h @@ -34,12 +34,17 @@ typedef struct MediaCodecDecContext { + volatile int refcount; + char *codec_name; FFAMediaCodec *codec; FFAMediaFormat *format; + void *surface; + int started; + int draining; int flushing; int eos; @@ -78,4 +83,16 @@ int ff_mediacodec_dec_flush(AVCodecContext *avctx, int ff_mediacodec_dec_close(AVCodecContext *avctx, MediaCodecDecContext *s); +int ff_mediacodec_dec_is_flushing(AVCodecContext *avctx, + MediaCodecDecContext *s); + +typedef struct MediaCodecBuffer { + + MediaCodecDecContext *ctx; + ssize_t index; + int64_t pts; + volatile int released; + +} MediaCodecBuffer; + #endif /* AVCODEC_MEDIACODECDEC_H */ diff --git a/libavcodec/mediacodecdec_h264.c b/libavcodec/mediacodecdec_h264.c index eb63ab5..b7de77e 100644 --- a/libavcodec/mediacodecdec_h264.c +++ b/libavcodec/mediacodecdec_h264.c @@ -41,7 +41,7 @@ typedef struct MediaCodecH264DecContext { - MediaCodecDecContext ctx; + MediaCodecDecContext *ctx; AVBSFContext *bsf; @@ -55,7 +55,8 @@ static av_cold int mediacodec_decode_close(AVCodecContext *avctx) { MediaCodecH264DecContext *s = avctx->priv_data; - ff_mediacodec_dec_close(avctx, &s->ctx); + ff_mediacodec_dec_close(avctx, s->ctx); + s->ctx = NULL; av_fifo_free(s->fifo); @@ -120,7 +121,15 @@ static av_cold int mediacodec_decode_init(AVCodecContext *avctx) goto done; } - if ((ret = ff_mediacodec_dec_init(avctx, &s->ctx, CODEC_MIME, format)) < 0) { + s->ctx = av_mallocz(sizeof(*s->ctx)); + if (!s->ctx) { + av_log(avctx, AV_LOG_ERROR, "Failed to allocate MediaCodecDecContext\n"); + ret = AVERROR(ENOMEM); + goto done; + } + + if ((ret = ff_mediacodec_dec_init(avctx, s->ctx, CODEC_MIME, format)) < 0) { + s->ctx = NULL; goto done; } @@ -169,7 +178,7 @@ static int mediacodec_process_data(AVCodecContext *avctx, AVFrame *frame, { MediaCodecH264DecContext *s = avctx->priv_data; - return ff_mediacodec_dec_decode(avctx, &s->ctx, frame, got_frame, pkt); + return ff_mediacodec_dec_decode(avctx, s->ctx, frame, got_frame, pkt); } static int mediacodec_decode_frame(AVCodecContext *avctx, void *data, @@ -196,6 +205,29 @@ static int mediacodec_decode_frame(AVCodecContext *avctx, void *data, av_fifo_generic_write(s->fifo, &input_pkt, sizeof(input_pkt), NULL); } + /* + * MediaCodec.flush() discards both input and output buffers, thus we + * need to delay the call to this function until the user has released or + * renderered the frames he retains. + * + * After we have buffered an input packet, check if the codec is in the + * flushing state. If it is, we need to call ff_mediacodec_dec_flush. + * + * ff_mediacodec_dec_flush returns 0 if the flush cannot be performed on + * the codec (because the user retains frames). The codec stays in the + * flushing state. + * + * ff_mediacodec_dec_flush returns 1 if the flush can actually be + * performed on the codec. The codec leaves the flushing state and can + * process again packets. + * + */ + if (ff_mediacodec_dec_is_flushing(avctx, s->ctx)) { + if (!ff_mediacodec_dec_flush(avctx, s->ctx)) { + return avpkt->size; + } + } + /* process buffered data */ while (!*got_frame) { /* prepare the input data -- convert to Annex B if needed */ @@ -207,7 +239,7 @@ static int mediacodec_decode_frame(AVCodecContext *avctx, void *data, /* 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); + ff_mediacodec_dec_decode(avctx, s->ctx, frame, got_frame, avpkt); } av_fifo_generic_read(s->fifo, &input_pkt, sizeof(input_pkt), NULL); @@ -254,7 +286,7 @@ static void mediacodec_decode_flush(AVCodecContext *avctx) av_packet_unref(&s->filtered_pkt); - ff_mediacodec_dec_flush(avctx, &s->ctx); + ff_mediacodec_dec_flush(avctx, s->ctx); } AVCodec ff_h264_mediacodec_decoder = { diff --git a/libavcodec/version.h b/libavcodec/version.h index 3d52086..b2ccbd2 100644 --- a/libavcodec/version.h +++ b/libavcodec/version.h @@ -28,7 +28,7 @@ #include "libavutil/version.h" #define LIBAVCODEC_VERSION_MAJOR 57 -#define LIBAVCODEC_VERSION_MINOR 46 +#define LIBAVCODEC_VERSION_MINOR 47 #define LIBAVCODEC_VERSION_MICRO 200 #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \ diff --git a/libavutil/pixdesc.c b/libavutil/pixdesc.c index 0dffa4d..d88aaf7 100644 --- a/libavutil/pixdesc.c +++ b/libavutil/pixdesc.c @@ -1974,6 +1974,10 @@ static const AVPixFmtDescriptor av_pix_fmt_descriptors[AV_PIX_FMT_NB] = { .name = "qsv", .flags = AV_PIX_FMT_FLAG_HWACCEL, }, + [AV_PIX_FMT_MEDIACODEC] = { + .name = "mediacodec", + .flags = AV_PIX_FMT_FLAG_HWACCEL, + }, [AV_PIX_FMT_MMAL] = { .name = "mmal", .flags = AV_PIX_FMT_FLAG_HWACCEL, diff --git a/libavutil/pixfmt.h b/libavutil/pixfmt.h index 0ed01c4..6f71ac0 100644 --- a/libavutil/pixfmt.h +++ b/libavutil/pixfmt.h @@ -303,6 +303,8 @@ enum AVPixelFormat { AV_PIX_FMT_GBRAP10BE, ///< planar GBR 4:4:4:4 40bpp, big-endian AV_PIX_FMT_GBRAP10LE, ///< planar GBR 4:4:4:4 40bpp, little-endian + AV_PIX_FMT_MEDIACODEC, ///< hardware decoding through MediaCodec + AV_PIX_FMT_NB, ///< number of pixel formats, DO NOT USE THIS if you want to link with shared libav* because the number of formats might differ between versions }; diff --git a/libavutil/version.h b/libavutil/version.h index a282fa8..aa10622 100644 --- a/libavutil/version.h +++ b/libavutil/version.h @@ -64,7 +64,7 @@ */ #define LIBAVUTIL_VERSION_MAJOR 55 -#define LIBAVUTIL_VERSION_MINOR 26 +#define LIBAVUTIL_VERSION_MINOR 27 #define LIBAVUTIL_VERSION_MICRO 100 #define LIBAVUTIL_VERSION_INT AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \ -- 2.9.0
_______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel