| @@ -11,6 +11,7 @@ version <next>: | |||
| - bench and abench filters | |||
| - ciescope filter | |||
| - protocol blacklisting API | |||
| - MediaCodec H264 decoding | |||
| version 3.0: | |||
| @@ -308,6 +308,7 @@ Codecs: | |||
| Hardware acceleration: | |||
| crystalhd.c Philip Langdale | |||
| dxva2* Hendrik Leppkes, Laurent Aimar | |||
| mediacodec* Matthieu Bouron | |||
| vaapi* Gwenole Beauchesne | |||
| vda* Sebastien Zwickert | |||
| vdpau* Philip Langdale, Carl Eugen Hoyos | |||
| @@ -276,6 +276,7 @@ External library support: | |||
| --enable-libzvbi enable teletext support via libzvbi [no] | |||
| --disable-lzma disable lzma [autodetect] | |||
| --enable-decklink enable Blackmagic DeckLink I/O support [no] | |||
| --enable-mediacodec enable Android MediaCodec support [no] | |||
| --enable-mmal enable decoding via MMAL [no] | |||
| --enable-netcdf enable NetCDF, needed for sofalizer filter [no] | |||
| --enable-nvenc enable NVIDIA NVENC support [no] | |||
| @@ -1501,6 +1502,7 @@ EXTERNAL_LIBRARY_LIST=" | |||
| libzmq | |||
| libzvbi | |||
| lzma | |||
| mediacodec | |||
| mmal | |||
| netcdf | |||
| nvenc | |||
| @@ -2505,6 +2507,8 @@ h264_d3d11va_hwaccel_deps="d3d11va" | |||
| h264_d3d11va_hwaccel_select="h264_decoder" | |||
| h264_dxva2_hwaccel_deps="dxva2" | |||
| h264_dxva2_hwaccel_select="h264_decoder" | |||
| h264_mediacodec_decoder_deps="mediacodec" | |||
| h264_mediacodec_decoder_select="h264_mp4toannexb_bsf h264_parser" | |||
| h264_mmal_decoder_deps="mmal" | |||
| h264_mmal_decoder_select="mmal" | |||
| h264_mmal_hwaccel_deps="mmal" | |||
| @@ -5672,6 +5676,7 @@ enabled libzmq && require_pkg_config libzmq zmq.h zmq_ctx_new | |||
| enabled libzvbi && require libzvbi libzvbi.h vbi_decoder_new -lzvbi && | |||
| { check_cpp_condition libzvbi.h "VBI_VERSION_MAJOR > 0 || VBI_VERSION_MINOR > 2 || VBI_VERSION_MINOR == 2 && VBI_VERSION_MICRO >= 28" || | |||
| enabled gpl || die "ERROR: libzvbi requires version 0.2.28 or --enable-gpl."; } | |||
| enabled mediacodec && { enabled jni || die "ERROR: mediacodec requires --enable-jni"; } | |||
| enabled mmal && { check_lib interface/mmal/mmal.h mmal_port_connect -lmmal_core -lmmal_util -lmmal_vc_client -lbcm_host || | |||
| { ! enabled cross_compile && { | |||
| add_cflags -isystem/opt/vc/include/ -isystem/opt/vc/include/interface/vmcs_host/linux -isystem/opt/vc/include/interface/vcos/pthreads -fgnu89-inline ; | |||
| @@ -91,6 +91,7 @@ OBJS-$(CONFIG_LSP) += lsp.o | |||
| OBJS-$(CONFIG_LZF) += lzf.o | |||
| OBJS-$(CONFIG_MDCT) += mdct_fixed.o mdct_float.o mdct_fixed_32.o | |||
| OBJS-$(CONFIG_ME_CMP) += me_cmp.o | |||
| OBJS-$(CONFIG_MEDIACODEC) += mediacodecdec.o mediacodec_wrapper.o mediacodec_sw_buffer.o | |||
| OBJS-$(CONFIG_MPEG_ER) += mpeg_er.o | |||
| OBJS-$(CONFIG_MPEGAUDIO) += mpegaudio.o mpegaudiodata.o \ | |||
| mpegaudiodecheader.o | |||
| @@ -306,6 +307,7 @@ OBJS-$(CONFIG_H264_DECODER) += h264.o h264_cabac.o h264_cavlc.o \ | |||
| h264_direct.o h264_loopfilter.o \ | |||
| h264_mb.o h264_picture.o h264_ps.o \ | |||
| h264_refs.o h264_sei.o h264_slice.o | |||
| OBJS-$(CONFIG_H264_MEDIACODEC_DECODER) += mediacodecdec_h264.o | |||
| OBJS-$(CONFIG_H264_MMAL_DECODER) += mmaldec.o | |||
| OBJS-$(CONFIG_H264_VDA_DECODER) += vda_h264_dec.o | |||
| OBJS-$(CONFIG_H264_QSV_DECODER) += qsvdec_h2645.o | |||
| @@ -944,6 +946,7 @@ SKIPHEADERS-$(CONFIG_LIBSCHROEDINGER) += libschroedinger.h | |||
| SKIPHEADERS-$(CONFIG_LIBUTVIDEO) += libutvideo.h | |||
| SKIPHEADERS-$(CONFIG_LIBVPX) += libvpx.h | |||
| SKIPHEADERS-$(CONFIG_LIBWEBP_ENCODER) += libwebpenc_common.h | |||
| SKIPHEADERS-$(CONFIG_MEDIACODEC) += mediacodecdec.h mediacodec_wrapper.h mediacodec_sw_buffer.h | |||
| SKIPHEADERS-$(CONFIG_QSV) += qsv.h qsv_internal.h | |||
| SKIPHEADERS-$(CONFIG_QSVDEC) += qsvdec.h | |||
| SKIPHEADERS-$(CONFIG_QSVENC) += qsvenc.h | |||
| @@ -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); | |||
| @@ -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.droege@collabora.co.uk> | |||
| * | |||
| * Copyright (C) 2012, Rafaël Carré <funman@videolanorg> | |||
| * | |||
| * Copyright (C) 2015, Sebastian Dröge <sebastian@centricular.com> | |||
| * | |||
| * Copyright (C) 2014-2015, Collabora Ltd. | |||
| * Author: Matthieu Bouron <matthieu.bouron@gcollabora.com> | |||
| * | |||
| * Copyright (C) 2015, Edward Hervey | |||
| * Author: Edward Hervey <bilboed@gmail.com> | |||
| * | |||
| * Copyright (C) 2015, Matthew Waters <matthew@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; | |||
| } | |||
| } | |||
| @@ -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 */ | |||
| @@ -0,0 +1,125 @@ | |||
| /* | |||
| * 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 | |||
| * ff_AMediaCodec_getName and ff_AMediaCodec_getBufferFlagEndOfStream are not | |||
| * part of the original NDK API and are convenience functions to hide JNI | |||
| * implementation. | |||
| * | |||
| * The API around MediaCodecList is not part of the NDK (and is lacking as | |||
| * we still need to retreive the codec name to work around faulty decoders | |||
| * and encoders). | |||
| * | |||
| * For documentation, please refers to NdkMediaCodec.h NdkMediaFormat.h and | |||
| * http://developer.android.com/reference/android/media/MediaCodec.html. | |||
| * | |||
| */ | |||
| char *ff_AMediaCodecList_getCodecNameByType(const char *mime, int width, int height, void *log_ctx); | |||
| struct FFAMediaFormat; | |||
| typedef struct FFAMediaFormat FFAMediaFormat; | |||
| FFAMediaFormat *ff_AMediaFormat_new(void); | |||
| int ff_AMediaFormat_delete(FFAMediaFormat* format); | |||
| char* ff_AMediaFormat_toString(FFAMediaFormat* format); | |||
| int ff_AMediaFormat_getInt32(FFAMediaFormat* format, const char *name, int32_t *out); | |||
| int ff_AMediaFormat_getInt64(FFAMediaFormat* format, const char *name, int64_t *out); | |||
| int ff_AMediaFormat_getFloat(FFAMediaFormat* format, const char *name, float *out); | |||
| int ff_AMediaFormat_getBuffer(FFAMediaFormat* format, const char *name, void** data, size_t *size); | |||
| int ff_AMediaFormat_getString(FFAMediaFormat* format, const char *name, const char **out); | |||
| void ff_AMediaFormat_setInt32(FFAMediaFormat* format, const char* name, int32_t value); | |||
| void ff_AMediaFormat_setInt64(FFAMediaFormat* format, const char* name, int64_t value); | |||
| void ff_AMediaFormat_setFloat(FFAMediaFormat* format, const char* name, float value); | |||
| void ff_AMediaFormat_setString(FFAMediaFormat* format, const char* name, const char* value); | |||
| void ff_AMediaFormat_setBuffer(FFAMediaFormat* format, const char* name, void* data, size_t size); | |||
| struct FFAMediaCodec; | |||
| typedef struct FFAMediaCodec FFAMediaCodec; | |||
| typedef struct FFAMediaCodecCryptoInfo FFAMediaCodecCryptoInfo; | |||
| struct FFAMediaCodecBufferInfo { | |||
| int32_t offset; | |||
| int32_t size; | |||
| int64_t presentationTimeUs; | |||
| uint32_t flags; | |||
| }; | |||
| typedef struct FFAMediaCodecBufferInfo FFAMediaCodecBufferInfo; | |||
| char *ff_AMediaCodec_getName(FFAMediaCodec *codec); | |||
| FFAMediaCodec* ff_AMediaCodec_createCodecByName(const char *name); | |||
| FFAMediaCodec* ff_AMediaCodec_createDecoderByType(const char *mime_type); | |||
| FFAMediaCodec* ff_AMediaCodec_createEncoderByType(const char *mime_type); | |||
| int ff_AMediaCodec_configure(FFAMediaCodec* codec, const FFAMediaFormat* format, void* surface, void *crypto, uint32_t flags); | |||
| int ff_AMediaCodec_start(FFAMediaCodec* codec); | |||
| int ff_AMediaCodec_stop(FFAMediaCodec* codec); | |||
| int ff_AMediaCodec_flush(FFAMediaCodec* codec); | |||
| int ff_AMediaCodec_delete(FFAMediaCodec* codec); | |||
| uint8_t* ff_AMediaCodec_getInputBuffer(FFAMediaCodec* codec, size_t idx, size_t *out_size); | |||
| uint8_t* ff_AMediaCodec_getOutputBuffer(FFAMediaCodec* codec, size_t idx, size_t *out_size); | |||
| ssize_t ff_AMediaCodec_dequeueInputBuffer(FFAMediaCodec* codec, int64_t timeoutUs); | |||
| int ff_AMediaCodec_queueInputBuffer(FFAMediaCodec* codec, size_t idx, off_t offset, size_t size, uint64_t time, uint32_t flags); | |||
| ssize_t ff_AMediaCodec_dequeueOutputBuffer(FFAMediaCodec* codec, FFAMediaCodecBufferInfo *info, int64_t timeoutUs); | |||
| FFAMediaFormat* ff_AMediaCodec_getOutputFormat(FFAMediaCodec* codec); | |||
| int ff_AMediaCodec_releaseOutputBuffer(FFAMediaCodec* codec, size_t idx, int render); | |||
| int ff_AMediaCodec_releaseOutputBufferAtTime(FFAMediaCodec *codec, size_t idx, int64_t timestampNs); | |||
| int ff_AMediaCodec_infoTryAgainLater(FFAMediaCodec *codec, ssize_t idx); | |||
| int ff_AMediaCodec_infoOutputBuffersChanged(FFAMediaCodec *codec, ssize_t idx); | |||
| int ff_AMediaCodec_infoOutputFormatChanged(FFAMediaCodec *codec, ssize_t indx); | |||
| int ff_AMediaCodec_getBufferFlagCodecConfig (FFAMediaCodec *codec); | |||
| int ff_AMediaCodec_getBufferFlagEndOfStream(FFAMediaCodec *codec); | |||
| int ff_AMediaCodec_getBufferFlagKeyFrame(FFAMediaCodec *codec); | |||
| int ff_AMediaCodec_getConfigureFlagEncode(FFAMediaCodec *codec); | |||
| int ff_AMediaCodec_cleanOutputBuffers(FFAMediaCodec *codec); | |||
| #endif /* AVCODEC_MEDIACODEC_WRAPPER_H */ | |||
| @@ -0,0 +1,570 @@ | |||
| /* | |||
| * 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.droege@collabora.co.uk> | |||
| * | |||
| * Copyright (C) 2012, Rafaël Carré <funman@videolanorg> | |||
| * | |||
| * Copyright (C) 2015, Sebastian Dröge <sebastian@centricular.com> | |||
| * | |||
| * Copyright (C) 2014-2015, Collabora Ltd. | |||
| * Author: Matthieu Bouron <matthieu.bouron@gcollabora.com> | |||
| * | |||
| * Copyright (C) 2015, Edward Hervey | |||
| * Author: Edward Hervey <bilboed@gmail.com> | |||
| * | |||
| * Copyright (C) 2015, Matthew Waters <matthew@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) | |||
| { | |||
| int width = 0; | |||
| int height = 0; | |||
| int32_t value = 0; | |||
| char *format = NULL; | |||
| if (!s->format) { | |||
| av_log(avctx, AV_LOG_ERROR, "Output MediaFormat is not set\n"); | |||
| return AVERROR(EINVAL); | |||
| } | |||
| format = ff_AMediaFormat_toString(s->format); | |||
| if (!format) { | |||
| return AVERROR_EXTERNAL; | |||
| } | |||
| av_log(avctx, AV_LOG_DEBUG, "Parsing MediaFormat %s\n", format); | |||
| av_freep(&format); | |||
| /* Mandatory fields */ | |||
| if (!ff_AMediaFormat_getInt32(s->format, "width", &value)) { | |||
| format = ff_AMediaFormat_toString(s->format); | |||
| av_log(avctx, AV_LOG_ERROR, "Could not get %s from format %s\n", "width", format); | |||
| av_freep(&format); | |||
| return AVERROR_EXTERNAL; | |||
| } | |||
| s->width = value; | |||
| if (!ff_AMediaFormat_getInt32(s->format, "height", &value)) { | |||
| format = ff_AMediaFormat_toString(s->format); | |||
| av_log(avctx, AV_LOG_ERROR, "Could not get %s from format %s\n", "height", format); | |||
| av_freep(&format); | |||
| return AVERROR_EXTERNAL; | |||
| } | |||
| s->height = value; | |||
| if (!ff_AMediaFormat_getInt32(s->format, "stride", &value)) { | |||
| format = ff_AMediaFormat_toString(s->format); | |||
| av_log(avctx, AV_LOG_ERROR, "Could not get %s from format %s\n", "stride", format); | |||
| av_freep(&format); | |||
| return AVERROR_EXTERNAL; | |||
| } | |||
| s->stride = value >= 0 ? value : s->width; | |||
| if (!ff_AMediaFormat_getInt32(s->format, "slice-height", &value)) { | |||
| format = ff_AMediaFormat_toString(s->format); | |||
| av_log(avctx, AV_LOG_ERROR, "Could not get %s from format %s\n", "slice-height", format); | |||
| av_freep(&format); | |||
| return AVERROR_EXTERNAL; | |||
| } | |||
| if (value > 0) { | |||
| s->slice_height = value; | |||
| } else { | |||
| s->slice_height = s->height; | |||
| } | |||
| if (strstr(s->codec_name, "OMX.Nvidia.")) { | |||
| s->slice_height = FFALIGN(s->height, 16); | |||
| } else if (strstr(s->codec_name, "OMX.SEC.avc.dec")) { | |||
| s->slice_height = avctx->height; | |||
| s->stride = avctx->width; | |||
| } | |||
| if (!ff_AMediaFormat_getInt32(s->format, "color-format", &value)) { | |||
| format = ff_AMediaFormat_toString(s->format); | |||
| av_log(avctx, AV_LOG_ERROR, "Could not get %s from format %s\n", "color-format", format); | |||
| av_freep(&format); | |||
| return AVERROR_EXTERNAL; | |||
| } | |||
| s->color_format = value; | |||
| s->pix_fmt = avctx->pix_fmt = mcdec_map_color_format(avctx, s, value); | |||
| if (avctx->pix_fmt == AV_PIX_FMT_NONE) { | |||
| av_log(avctx, AV_LOG_ERROR, "Output color format is not supported\n"); | |||
| return AVERROR(EINVAL); | |||
| } | |||
| /* Optional fields */ | |||
| if (ff_AMediaFormat_getInt32(s->format, "crop-top", &value)) | |||
| s->crop_top = value; | |||
| if (ff_AMediaFormat_getInt32(s->format, "crop-bottom", &value)) | |||
| s->crop_bottom = value; | |||
| if (ff_AMediaFormat_getInt32(s->format, "crop-left", &value)) | |||
| s->crop_left = value; | |||
| if (ff_AMediaFormat_getInt32(s->format, "crop-right", &value)) | |||
| s->crop_right = value; | |||
| width = s->crop_right + 1 - s->crop_left; | |||
| height = s->crop_bottom + 1 - s->crop_top; | |||
| av_log(avctx, AV_LOG_INFO, | |||
| "Output crop parameters top=%d bottom=%d left=%d right=%d, " | |||
| "resulting dimensions width=%d height=%d\n", | |||
| s->crop_top, s->crop_bottom, s->crop_left, s->crop_right, | |||
| width, height); | |||
| return ff_set_dimensions(avctx, width, height); | |||
| } | |||
| int ff_mediacodec_dec_init(AVCodecContext *avctx, MediaCodecDecContext *s, | |||
| const char *mime, FFAMediaFormat *format) | |||
| { | |||
| int ret = 0; | |||
| int status; | |||
| s->first_buffer_at = av_gettime(); | |||
| s->codec_name = ff_AMediaCodecList_getCodecNameByType(mime, avctx->width, avctx->height, avctx); | |||
| if (!s->codec_name) { | |||
| ret = AVERROR_EXTERNAL; | |||
| goto fail; | |||
| } | |||
| av_log(avctx, AV_LOG_DEBUG, "Found decoder %s\n", s->codec_name); | |||
| s->codec = ff_AMediaCodec_createCodecByName(s->codec_name); | |||
| if (!s->codec) { | |||
| av_log(avctx, AV_LOG_ERROR, "Failed to create media decoder for type %s and name %s\n", mime, s->codec_name); | |||
| ret = AVERROR_EXTERNAL; | |||
| goto fail; | |||
| } | |||
| status = ff_AMediaCodec_configure(s->codec, format, NULL, NULL, 0); | |||
| if (status < 0) { | |||
| char *desc = ff_AMediaFormat_toString(format); | |||
| av_log(avctx, AV_LOG_ERROR, | |||
| "Failed to configure codec (status = %d) with format %s\n", | |||
| status, desc); | |||
| av_freep(&desc); | |||
| ret = AVERROR_EXTERNAL; | |||
| goto fail; | |||
| } | |||
| status = ff_AMediaCodec_start(s->codec); | |||
| if (status < 0) { | |||
| char *desc = ff_AMediaFormat_toString(format); | |||
| av_log(avctx, AV_LOG_ERROR, | |||
| "Failed to start codec (status = %d) with format %s\n", | |||
| status, desc); | |||
| av_freep(&desc); | |||
| ret = AVERROR_EXTERNAL; | |||
| goto fail; | |||
| } | |||
| s->format = ff_AMediaCodec_getOutputFormat(s->codec); | |||
| if (s->format) { | |||
| if ((ret = mediacodec_dec_parse_format(avctx, s)) < 0) { | |||
| av_log(avctx, AV_LOG_ERROR, | |||
| "Failed to configure context\n"); | |||
| goto fail; | |||
| } | |||
| } | |||
| av_log(avctx, AV_LOG_DEBUG, "MediaCodec %p started successfully\n", s->codec); | |||
| return 0; | |||
| fail: | |||
| av_log(avctx, AV_LOG_ERROR, "MediaCodec %p failed to start\n", s->codec); | |||
| ff_mediacodec_dec_close(avctx, s); | |||
| return ret; | |||
| } | |||
| int ff_mediacodec_dec_decode(AVCodecContext *avctx, MediaCodecDecContext *s, | |||
| AVFrame *frame, int *got_frame, | |||
| AVPacket *pkt) | |||
| { | |||
| int ret; | |||
| int offset = 0; | |||
| int need_flushing = 0; | |||
| uint8_t *data; | |||
| ssize_t index; | |||
| size_t size; | |||
| FFAMediaCodec *codec = s->codec; | |||
| FFAMediaCodecBufferInfo info = { 0 }; | |||
| int status; | |||
| int64_t input_dequeue_timeout_us = INPUT_DEQUEUE_TIMEOUT_US; | |||
| int64_t output_dequeue_timeout_us = OUTPUT_DEQUEUE_TIMEOUT_US; | |||
| if (pkt->size == 0) { | |||
| need_flushing = 1; | |||
| } | |||
| if (s->flushing && need_flushing && s->queued_buffer_nb <= 0) { | |||
| return 0; | |||
| } | |||
| while (offset < pkt->size || (need_flushing && !s->flushing)) { | |||
| int size; | |||
| index = ff_AMediaCodec_dequeueInputBuffer(codec, input_dequeue_timeout_us); | |||
| if (ff_AMediaCodec_infoTryAgainLater(codec, index)) { | |||
| break; | |||
| } | |||
| if (index < 0) { | |||
| av_log(avctx, AV_LOG_ERROR, "Failed to dequeue input buffer (status=%zd)\n", index); | |||
| return AVERROR_EXTERNAL; | |||
| } | |||
| data = ff_AMediaCodec_getInputBuffer(codec, index, &size); | |||
| if (!data) { | |||
| av_log(avctx, AV_LOG_ERROR, "Failed to get input buffer\n"); | |||
| return AVERROR_EXTERNAL; | |||
| } | |||
| if (need_flushing) { | |||
| uint32_t flags = ff_AMediaCodec_getBufferFlagEndOfStream(codec); | |||
| av_log(avctx, AV_LOG_DEBUG, "Sending End Of Stream signal\n"); | |||
| status = ff_AMediaCodec_queueInputBuffer(codec, index, 0, 0, pkt->pts, flags); | |||
| if (status < 0) { | |||
| av_log(avctx, AV_LOG_ERROR, "Failed to queue input empty buffer (status = %d)\n", status); | |||
| return AVERROR_EXTERNAL; | |||
| } | |||
| s->flushing = 1; | |||
| break; | |||
| } else { | |||
| size = FFMIN(pkt->size - offset, size); | |||
| memcpy(data, pkt->data + offset, size); | |||
| offset += size; | |||
| status = ff_AMediaCodec_queueInputBuffer(codec, index, 0, size, pkt->pts, 0); | |||
| if (status < 0) { | |||
| av_log(avctx, AV_LOG_ERROR, "Failed to queue input buffer (status = %d)\n", status); | |||
| return AVERROR_EXTERNAL; | |||
| } | |||
| s->queued_buffer_nb++; | |||
| if (s->queued_buffer_nb > s->queued_buffer_max) | |||
| s->queued_buffer_max = s->queued_buffer_nb; | |||
| } | |||
| } | |||
| if (s->flushing) { | |||
| /* If the codec is flushing, block for a fair amount of time to | |||
| * ensure we got a frame */ | |||
| output_dequeue_timeout_us = OUTPUT_DEQUEUE_BLOCK_TIMEOUT_US; | |||
| } else if (s->dequeued_buffer_nb == 0) { | |||
| /* If the codec hasn't produced any frames, do not block so we | |||
| * can push data to it as fast as possible, and get the first | |||
| * frame */ | |||
| output_dequeue_timeout_us = 0; | |||
| } | |||
| index = ff_AMediaCodec_dequeueOutputBuffer(codec, &info, output_dequeue_timeout_us); | |||
| if (index >= 0) { | |||
| int ret; | |||
| if (!s->first_buffer++) { | |||
| av_log(avctx, AV_LOG_DEBUG, "Got first buffer after %fms\n", (av_gettime() - s->first_buffer_at) / 1000); | |||
| } | |||
| av_log(avctx, AV_LOG_DEBUG, "Got output buffer %zd" | |||
| " offset=%" PRIi32 " size=%" PRIi32 " ts=%" PRIi64 | |||
| " flags=%" PRIu32 "\n", index, info.offset, info.size, | |||
| info.presentationTimeUs, info.flags); | |||
| data = ff_AMediaCodec_getOutputBuffer(codec, index, &size); | |||
| if (!data) { | |||
| av_log(avctx, AV_LOG_ERROR, "Failed to get output buffer\n"); | |||
| return AVERROR_EXTERNAL; | |||
| } | |||
| if ((ret = mediacodec_wrap_buffer(avctx, s, data, size, index, &info, frame)) < 0) { | |||
| av_log(avctx, AV_LOG_ERROR, "Failed to wrap MediaCodec buffer\n"); | |||
| return ret; | |||
| } | |||
| *got_frame = 1; | |||
| s->queued_buffer_nb--; | |||
| s->dequeued_buffer_nb++; | |||
| } else if (ff_AMediaCodec_infoOutputFormatChanged(codec, index)) { | |||
| char *format = NULL; | |||
| if (s->format) { | |||
| status = ff_AMediaFormat_delete(s->format); | |||
| if (status < 0) { | |||
| av_log(avctx, AV_LOG_ERROR, "Failed to delete MediaFormat %p\n", s->format); | |||
| } | |||
| } | |||
| s->format = ff_AMediaCodec_getOutputFormat(codec); | |||
| if (!s->format) { | |||
| av_log(avctx, AV_LOG_ERROR, "Failed to get output format\n"); | |||
| return AVERROR_EXTERNAL; | |||
| } | |||
| format = ff_AMediaFormat_toString(s->format); | |||
| if (!format) { | |||
| return AVERROR_EXTERNAL; | |||
| } | |||
| av_log(avctx, AV_LOG_INFO, "Output MediaFormat changed to %s\n", format); | |||
| av_freep(&format); | |||
| if ((ret = mediacodec_dec_parse_format(avctx, s)) < 0) { | |||
| return ret; | |||
| } | |||
| } else if (ff_AMediaCodec_infoOutputBuffersChanged(codec, index)) { | |||
| ff_AMediaCodec_cleanOutputBuffers(codec); | |||
| } else if (ff_AMediaCodec_infoTryAgainLater(codec, index)) { | |||
| if (s->flushing) { | |||
| av_log(avctx, AV_LOG_ERROR, "Failed to dequeue output buffer within %" PRIi64 "ms " | |||
| "while flushing remaining frames, output will probably lack last %d frames\n", | |||
| output_dequeue_timeout_us / 1000, s->queued_buffer_nb); | |||
| } else { | |||
| av_log(avctx, AV_LOG_DEBUG, "No output buffer available, try again later\n"); | |||
| } | |||
| } else { | |||
| av_log(avctx, AV_LOG_ERROR, "Failed to dequeue output buffer (status=%zd)\n", index); | |||
| return AVERROR_EXTERNAL; | |||
| } | |||
| return offset; | |||
| } | |||
| int ff_mediacodec_dec_flush(AVCodecContext *avctx, MediaCodecDecContext *s) | |||
| { | |||
| FFAMediaCodec *codec = s->codec; | |||
| int status; | |||
| s->queued_buffer_nb = 0; | |||
| s->dequeued_buffer_nb = 0; | |||
| s->flushing = 0; | |||
| status = ff_AMediaCodec_flush(codec); | |||
| if (status < 0) { | |||
| av_log(NULL, AV_LOG_ERROR, "Failed to flush MediaCodec %p", codec); | |||
| return AVERROR_EXTERNAL; | |||
| } | |||
| s->first_buffer = 0; | |||
| s->first_buffer_at = av_gettime(); | |||
| return 0; | |||
| } | |||
| int ff_mediacodec_dec_close(AVCodecContext *avctx, MediaCodecDecContext *s) | |||
| { | |||
| if (s->codec) { | |||
| ff_AMediaCodec_delete(s->codec); | |||
| s->codec = NULL; | |||
| } | |||
| if (s->format) { | |||
| ff_AMediaFormat_delete(s->format); | |||
| s->format = NULL; | |||
| } | |||
| return 0; | |||
| } | |||
| @@ -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 */ | |||
| @@ -0,0 +1,336 @@ | |||
| /* | |||
| * 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; | |||
| AVBitStreamFilterContext *bsf; | |||
| AVFifoBuffer *fifo; | |||
| AVPacket input_ref; | |||
| AVPacket filtered_pkt; | |||
| uint8_t *filtered_data; | |||
| } MediaCodecH264DecContext; | |||
| static int h264_extradata_to_annexb_sps_pps(AVCodecContext *avctx, | |||
| uint8_t **extradata_annexb, int *extradata_annexb_size, | |||
| int *sps_offset, int *sps_size, | |||
| int *pps_offset, int *pps_size) | |||
| { | |||
| uint16_t unit_size; | |||
| uint64_t total_size = 0; | |||
| uint8_t i, j, unit_nb; | |||
| uint8_t sps_seen = 0; | |||
| uint8_t pps_seen = 0; | |||
| const uint8_t *extradata; | |||
| static const uint8_t nalu_header[4] = { 0x00, 0x00, 0x00, 0x01 }; | |||
| if (avctx->extradata_size < 8) { | |||
| av_log(avctx, AV_LOG_ERROR, | |||
| "Too small extradata size, corrupted stream or invalid MP4/AVCC bitstream\n"); | |||
| return AVERROR(EINVAL); | |||
| } | |||
| *extradata_annexb = NULL; | |||
| *extradata_annexb_size = 0; | |||
| *sps_offset = *sps_size = 0; | |||
| *pps_offset = *pps_size = 0; | |||
| extradata = avctx->extradata + 4; | |||
| /* skip length size */ | |||
| extradata++; | |||
| for (j = 0; j < 2; j ++) { | |||
| if (j == 0) { | |||
| /* number of sps unit(s) */ | |||
| unit_nb = *extradata++ & 0x1f; | |||
| } else { | |||
| /* number of pps unit(s) */ | |||
| unit_nb = *extradata++; | |||
| } | |||
| for (i = 0; i < unit_nb; i++) { | |||
| int err; | |||
| unit_size = AV_RB16(extradata); | |||
| total_size += unit_size + 4; | |||
| if (total_size > INT_MAX) { | |||
| av_log(avctx, AV_LOG_ERROR, | |||
| "Too big extradata size, corrupted stream or invalid MP4/AVCC bitstream\n"); | |||
| av_freep(extradata_annexb); | |||
| return AVERROR(EINVAL); | |||
| } | |||
| if (extradata + 2 + unit_size > avctx->extradata + avctx->extradata_size) { | |||
| av_log(avctx, AV_LOG_ERROR, "Packet header is not contained in global extradata, " | |||
| "corrupted stream or invalid MP4/AVCC bitstream\n"); | |||
| av_freep(extradata_annexb); | |||
| return AVERROR(EINVAL); | |||
| } | |||
| if ((err = av_reallocp(extradata_annexb, total_size)) < 0) { | |||
| return err; | |||
| } | |||
| memcpy(*extradata_annexb + total_size - unit_size - 4, nalu_header, 4); | |||
| memcpy(*extradata_annexb + total_size - unit_size, extradata + 2, unit_size); | |||
| extradata += 2 + unit_size; | |||
| } | |||
| if (unit_nb) { | |||
| if (j == 0) { | |||
| sps_seen = 1; | |||
| *sps_size = total_size; | |||
| } else { | |||
| pps_seen = 1; | |||
| *pps_size = total_size - *sps_size; | |||
| *pps_offset = *sps_size; | |||
| } | |||
| } | |||
| } | |||
| *extradata_annexb_size = total_size; | |||
| if (!sps_seen) | |||
| av_log(avctx, AV_LOG_WARNING, | |||
| "Warning: SPS NALU missing or invalid. " | |||
| "The resulting stream may not play.\n"); | |||
| if (!pps_seen) | |||
| av_log(avctx, AV_LOG_WARNING, | |||
| "Warning: PPS NALU missing or invalid. " | |||
| "The resulting stream may not play.\n"); | |||
| return 0; | |||
| } | |||
| static av_cold int mediacodec_decode_close(AVCodecContext *avctx) | |||
| { | |||
| MediaCodecH264DecContext *s = avctx->priv_data; | |||
| ff_mediacodec_dec_close(avctx, &s->ctx); | |||
| av_fifo_free(s->fifo); | |||
| av_bitstream_filter_close(s->bsf); | |||
| return 0; | |||
| } | |||
| static av_cold int mediacodec_decode_init(AVCodecContext *avctx) | |||
| { | |||
| int ret; | |||
| FFAMediaFormat *format = NULL; | |||
| MediaCodecH264DecContext *s = avctx->priv_data; | |||
| format = ff_AMediaFormat_new(); | |||
| if (!format) { | |||
| av_log(avctx, AV_LOG_ERROR, "Failed to create media format\n"); | |||
| ret = AVERROR_EXTERNAL; | |||
| goto done; | |||
| } | |||
| ff_AMediaFormat_setString(format, "mime", CODEC_MIME); | |||
| ff_AMediaFormat_setInt32(format, "width", avctx->width); | |||
| ff_AMediaFormat_setInt32(format, "height", avctx->height); | |||
| if (avctx->extradata[0] == 1) { | |||
| uint8_t *extradata = NULL; | |||
| int extradata_size = 0; | |||
| int sps_offset, sps_size; | |||
| int pps_offset, pps_size; | |||
| if ((ret = h264_extradata_to_annexb_sps_pps(avctx, &extradata, &extradata_size, | |||
| &sps_offset, &sps_size, &pps_offset, &pps_size)) < 0) { | |||
| goto done; | |||
| } | |||
| ff_AMediaFormat_setBuffer(format, "csd-0", extradata + sps_offset, sps_size); | |||
| ff_AMediaFormat_setBuffer(format, "csd-1", extradata + pps_offset, pps_size); | |||
| av_freep(&extradata); | |||
| } else { | |||
| ff_AMediaFormat_setBuffer(format, "csd-0", avctx->extradata, avctx->extradata_size); | |||
| } | |||
| if ((ret = ff_mediacodec_dec_init(avctx, &s->ctx, CODEC_MIME, format)) < 0) { | |||
| goto done; | |||
| } | |||
| av_log(avctx, AV_LOG_INFO, "MediaCodec started successfully, ret = %d\n", ret); | |||
| s->fifo = av_fifo_alloc(sizeof(AVPacket)); | |||
| if (!s->fifo) { | |||
| ret = AVERROR(ENOMEM); | |||
| goto done; | |||
| } | |||
| s->bsf = av_bitstream_filter_init("h264_mp4toannexb"); | |||
| if (!s->bsf) { | |||
| ret = AVERROR(ENOMEM); | |||
| goto done; | |||
| } | |||
| done: | |||
| if (format) { | |||
| ff_AMediaFormat_delete(format); | |||
| } | |||
| if (ret < 0) { | |||
| mediacodec_decode_close(avctx); | |||
| } | |||
| return ret; | |||
| } | |||
| static int mediacodec_process_data(AVCodecContext *avctx, AVFrame *frame, | |||
| int *got_frame, AVPacket *pkt) | |||
| { | |||
| MediaCodecH264DecContext *s = avctx->priv_data; | |||
| return ff_mediacodec_dec_decode(avctx, &s->ctx, frame, got_frame, pkt); | |||
| } | |||
| static int mediacodec_decode_frame(AVCodecContext *avctx, void *data, | |||
| int *got_frame, AVPacket *avpkt) | |||
| { | |||
| MediaCodecH264DecContext *s = avctx->priv_data; | |||
| AVFrame *frame = data; | |||
| int ret; | |||
| /* buffer the input packet */ | |||
| if (avpkt->size) { | |||
| AVPacket input_ref = { 0 }; | |||
| if (av_fifo_space(s->fifo) < sizeof(input_ref)) { | |||
| ret = av_fifo_realloc2(s->fifo, | |||
| av_fifo_size(s->fifo) + sizeof(input_ref)); | |||
| if (ret < 0) | |||
| return ret; | |||
| } | |||
| ret = av_packet_ref(&input_ref, avpkt); | |||
| if (ret < 0) | |||
| return ret; | |||
| av_fifo_generic_write(s->fifo, &input_ref, sizeof(input_ref), NULL); | |||
| } | |||
| /* process buffered data */ | |||
| while (!*got_frame) { | |||
| /* prepare the input data -- convert to Annex B if needed */ | |||
| if (s->filtered_pkt.size <= 0) { | |||
| int size; | |||
| /* no more data */ | |||
| if (av_fifo_size(s->fifo) < sizeof(AVPacket)) { | |||
| return avpkt->size ? avpkt->size : | |||
| ff_mediacodec_dec_decode(avctx, &s->ctx, frame, got_frame, avpkt); | |||
| } | |||
| if (s->filtered_data != s->input_ref.data) | |||
| av_freep(&s->filtered_data); | |||
| s->filtered_data = NULL; | |||
| av_packet_unref(&s->input_ref); | |||
| av_fifo_generic_read(s->fifo, &s->input_ref, sizeof(s->input_ref), NULL); | |||
| ret = av_bitstream_filter_filter(s->bsf, avctx, NULL, | |||
| &s->filtered_data, &size, | |||
| s->input_ref.data, s->input_ref.size, 0); | |||
| if (ret < 0) { | |||
| s->filtered_data = s->input_ref.data; | |||
| size = s->input_ref.size; | |||
| } | |||
| s->filtered_pkt = s->input_ref; | |||
| s->filtered_pkt.data = s->filtered_data; | |||
| s->filtered_pkt.size = size; | |||
| } | |||
| ret = mediacodec_process_data(avctx, frame, got_frame, &s->filtered_pkt); | |||
| if (ret < 0) | |||
| return ret; | |||
| s->filtered_pkt.size -= ret; | |||
| s->filtered_pkt.data += ret; | |||
| } | |||
| return avpkt->size; | |||
| } | |||
| static void mediacodec_decode_flush(AVCodecContext *avctx) | |||
| { | |||
| MediaCodecH264DecContext *s = avctx->priv_data; | |||
| while (av_fifo_size(s->fifo)) { | |||
| AVPacket pkt; | |||
| av_fifo_generic_read(s->fifo, &pkt, sizeof(pkt), NULL); | |||
| av_packet_unref(&pkt); | |||
| } | |||
| av_fifo_reset(s->fifo); | |||
| av_packet_unref(&s->input_ref); | |||
| av_init_packet(&s->filtered_pkt); | |||
| s->filtered_pkt.data = NULL; | |||
| s->filtered_pkt.size = 0; | |||
| ff_mediacodec_dec_flush(avctx, &s->ctx); | |||
| } | |||
| AVCodec ff_h264_mediacodec_decoder = { | |||
| .name = "h264_mediacodec", | |||
| .long_name = NULL_IF_CONFIG_SMALL("H.264 Android MediaCodec decoder"), | |||
| .type = AVMEDIA_TYPE_VIDEO, | |||
| .id = AV_CODEC_ID_H264, | |||
| .priv_data_size = sizeof(MediaCodecH264DecContext), | |||
| .init = mediacodec_decode_init, | |||
| .decode = mediacodec_decode_frame, | |||
| .flush = mediacodec_decode_flush, | |||
| .close = mediacodec_decode_close, | |||
| .capabilities = CODEC_CAP_DELAY, | |||
| }; | |||
| @@ -28,8 +28,8 @@ | |||
| #include "libavutil/version.h" | |||
| #define LIBAVCODEC_VERSION_MAJOR 57 | |||
| #define LIBAVCODEC_VERSION_MINOR 27 | |||
| #define LIBAVCODEC_VERSION_MICRO 101 | |||
| #define LIBAVCODEC_VERSION_MINOR 28 | |||
| #define LIBAVCODEC_VERSION_MICRO 100 | |||
| #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \ | |||
| LIBAVCODEC_VERSION_MINOR, \ | |||