From 4737fe6907d2693d044d29ca06510cc6fcbec544 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 21 Jan 2016 09:29:39 +0100 Subject: lavc: add h264 mediacodec decoder --- libavcodec/mediacodecdec_h264.c | 336 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 336 insertions(+) create mode 100644 libavcodec/mediacodecdec_h264.c (limited to 'libavcodec/mediacodecdec_h264.c') diff --git a/libavcodec/mediacodecdec_h264.c b/libavcodec/mediacodecdec_h264.c new file mode 100644 index 0000000000..2d1d52560f --- /dev/null +++ b/libavcodec/mediacodecdec_h264.c @@ -0,0 +1,336 @@ +/* + * Android MediaCodec H.264 decoder + * + * Copyright (c) 2015-2016 Matthieu Bouron + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include "libavutil/common.h" +#include "libavutil/fifo.h" +#include "libavutil/opt.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/pixfmt.h" +#include "libavutil/atomic.h" + +#include "avcodec.h" +#include "internal.h" +#include "mediacodecdec.h" +#include "mediacodec_wrapper.h" + +#define CODEC_MIME "video/avc" + +typedef struct MediaCodecH264DecContext { + + MediaCodecDecContext ctx; + + AVBitStreamFilterContext *bsf; + + AVFifoBuffer *fifo; + + AVPacket input_ref; + AVPacket filtered_pkt; + uint8_t *filtered_data; + +} MediaCodecH264DecContext; + +static int h264_extradata_to_annexb_sps_pps(AVCodecContext *avctx, + uint8_t **extradata_annexb, int *extradata_annexb_size, + int *sps_offset, int *sps_size, + int *pps_offset, int *pps_size) +{ + uint16_t unit_size; + uint64_t total_size = 0; + + uint8_t i, j, unit_nb; + uint8_t sps_seen = 0; + uint8_t pps_seen = 0; + + const uint8_t *extradata; + static const uint8_t nalu_header[4] = { 0x00, 0x00, 0x00, 0x01 }; + + if (avctx->extradata_size < 8) { + av_log(avctx, AV_LOG_ERROR, + "Too small extradata size, corrupted stream or invalid MP4/AVCC bitstream\n"); + return AVERROR(EINVAL); + } + + *extradata_annexb = NULL; + *extradata_annexb_size = 0; + + *sps_offset = *sps_size = 0; + *pps_offset = *pps_size = 0; + + extradata = avctx->extradata + 4; + + /* skip length size */ + extradata++; + + for (j = 0; j < 2; j ++) { + + if (j == 0) { + /* number of sps unit(s) */ + unit_nb = *extradata++ & 0x1f; + } else { + /* number of pps unit(s) */ + unit_nb = *extradata++; + } + + for (i = 0; i < unit_nb; i++) { + int err; + + unit_size = AV_RB16(extradata); + total_size += unit_size + 4; + + if (total_size > INT_MAX) { + av_log(avctx, AV_LOG_ERROR, + "Too big extradata size, corrupted stream or invalid MP4/AVCC bitstream\n"); + av_freep(extradata_annexb); + return AVERROR(EINVAL); + } + + if (extradata + 2 + unit_size > avctx->extradata + avctx->extradata_size) { + av_log(avctx, AV_LOG_ERROR, "Packet header is not contained in global extradata, " + "corrupted stream or invalid MP4/AVCC bitstream\n"); + av_freep(extradata_annexb); + return AVERROR(EINVAL); + } + + if ((err = av_reallocp(extradata_annexb, total_size)) < 0) { + return err; + } + + memcpy(*extradata_annexb + total_size - unit_size - 4, nalu_header, 4); + memcpy(*extradata_annexb + total_size - unit_size, extradata + 2, unit_size); + extradata += 2 + unit_size; + } + + if (unit_nb) { + if (j == 0) { + sps_seen = 1; + *sps_size = total_size; + } else { + pps_seen = 1; + *pps_size = total_size - *sps_size; + *pps_offset = *sps_size; + } + } + } + + *extradata_annexb_size = total_size; + + if (!sps_seen) + av_log(avctx, AV_LOG_WARNING, + "Warning: SPS NALU missing or invalid. " + "The resulting stream may not play.\n"); + + if (!pps_seen) + av_log(avctx, AV_LOG_WARNING, + "Warning: PPS NALU missing or invalid. " + "The resulting stream may not play.\n"); + + return 0; +} + +static av_cold int mediacodec_decode_close(AVCodecContext *avctx) +{ + MediaCodecH264DecContext *s = avctx->priv_data; + + ff_mediacodec_dec_close(avctx, &s->ctx); + + av_fifo_free(s->fifo); + + av_bitstream_filter_close(s->bsf); + + return 0; +} + +static av_cold int mediacodec_decode_init(AVCodecContext *avctx) +{ + int ret; + FFAMediaFormat *format = NULL; + MediaCodecH264DecContext *s = avctx->priv_data; + + format = ff_AMediaFormat_new(); + if (!format) { + av_log(avctx, AV_LOG_ERROR, "Failed to create media format\n"); + ret = AVERROR_EXTERNAL; + goto done; + } + + ff_AMediaFormat_setString(format, "mime", CODEC_MIME); + ff_AMediaFormat_setInt32(format, "width", avctx->width); + ff_AMediaFormat_setInt32(format, "height", avctx->height); + + if (avctx->extradata[0] == 1) { + uint8_t *extradata = NULL; + int extradata_size = 0; + + int sps_offset, sps_size; + int pps_offset, pps_size; + + if ((ret = h264_extradata_to_annexb_sps_pps(avctx, &extradata, &extradata_size, + &sps_offset, &sps_size, &pps_offset, &pps_size)) < 0) { + goto done; + } + + ff_AMediaFormat_setBuffer(format, "csd-0", extradata + sps_offset, sps_size); + ff_AMediaFormat_setBuffer(format, "csd-1", extradata + pps_offset, pps_size); + + av_freep(&extradata); + } else { + ff_AMediaFormat_setBuffer(format, "csd-0", avctx->extradata, avctx->extradata_size); + } + + if ((ret = ff_mediacodec_dec_init(avctx, &s->ctx, CODEC_MIME, format)) < 0) { + goto done; + } + + av_log(avctx, AV_LOG_INFO, "MediaCodec started successfully, ret = %d\n", ret); + + s->fifo = av_fifo_alloc(sizeof(AVPacket)); + if (!s->fifo) { + ret = AVERROR(ENOMEM); + goto done; + } + + s->bsf = av_bitstream_filter_init("h264_mp4toannexb"); + if (!s->bsf) { + ret = AVERROR(ENOMEM); + goto done; + } + +done: + if (format) { + ff_AMediaFormat_delete(format); + } + + if (ret < 0) { + mediacodec_decode_close(avctx); + } + return ret; +} + + +static int mediacodec_process_data(AVCodecContext *avctx, AVFrame *frame, + int *got_frame, AVPacket *pkt) +{ + MediaCodecH264DecContext *s = avctx->priv_data; + + return ff_mediacodec_dec_decode(avctx, &s->ctx, frame, got_frame, pkt); +} + +static int mediacodec_decode_frame(AVCodecContext *avctx, void *data, + int *got_frame, AVPacket *avpkt) +{ + MediaCodecH264DecContext *s = avctx->priv_data; + AVFrame *frame = data; + int ret; + + /* buffer the input packet */ + if (avpkt->size) { + AVPacket input_ref = { 0 }; + + if (av_fifo_space(s->fifo) < sizeof(input_ref)) { + ret = av_fifo_realloc2(s->fifo, + av_fifo_size(s->fifo) + sizeof(input_ref)); + if (ret < 0) + return ret; + } + + ret = av_packet_ref(&input_ref, avpkt); + if (ret < 0) + return ret; + av_fifo_generic_write(s->fifo, &input_ref, sizeof(input_ref), NULL); + } + + /* process buffered data */ + while (!*got_frame) { + /* prepare the input data -- convert to Annex B if needed */ + if (s->filtered_pkt.size <= 0) { + int size; + + /* no more data */ + if (av_fifo_size(s->fifo) < sizeof(AVPacket)) { + return avpkt->size ? avpkt->size : + ff_mediacodec_dec_decode(avctx, &s->ctx, frame, got_frame, avpkt); + } + + if (s->filtered_data != s->input_ref.data) + av_freep(&s->filtered_data); + s->filtered_data = NULL; + av_packet_unref(&s->input_ref); + + av_fifo_generic_read(s->fifo, &s->input_ref, sizeof(s->input_ref), NULL); + ret = av_bitstream_filter_filter(s->bsf, avctx, NULL, + &s->filtered_data, &size, + s->input_ref.data, s->input_ref.size, 0); + if (ret < 0) { + s->filtered_data = s->input_ref.data; + size = s->input_ref.size; + } + s->filtered_pkt = s->input_ref; + s->filtered_pkt.data = s->filtered_data; + s->filtered_pkt.size = size; + } + + ret = mediacodec_process_data(avctx, frame, got_frame, &s->filtered_pkt); + if (ret < 0) + return ret; + + s->filtered_pkt.size -= ret; + s->filtered_pkt.data += ret; + } + + return avpkt->size; +} + +static void mediacodec_decode_flush(AVCodecContext *avctx) +{ + MediaCodecH264DecContext *s = avctx->priv_data; + + while (av_fifo_size(s->fifo)) { + AVPacket pkt; + av_fifo_generic_read(s->fifo, &pkt, sizeof(pkt), NULL); + av_packet_unref(&pkt); + } + av_fifo_reset(s->fifo); + + av_packet_unref(&s->input_ref); + + av_init_packet(&s->filtered_pkt); + s->filtered_pkt.data = NULL; + s->filtered_pkt.size = 0; + + ff_mediacodec_dec_flush(avctx, &s->ctx); +} + +AVCodec ff_h264_mediacodec_decoder = { + .name = "h264_mediacodec", + .long_name = NULL_IF_CONFIG_SMALL("H.264 Android MediaCodec decoder"), + .type = AVMEDIA_TYPE_VIDEO, + .id = AV_CODEC_ID_H264, + .priv_data_size = sizeof(MediaCodecH264DecContext), + .init = mediacodec_decode_init, + .decode = mediacodec_decode_frame, + .flush = mediacodec_decode_flush, + .close = mediacodec_decode_close, + .capabilities = CODEC_CAP_DELAY, +}; -- cgit v1.2.3