summaryrefslogtreecommitdiff
path: root/libavcodec/mediacodecdec_h264.c
diff options
context:
space:
mode:
Diffstat (limited to 'libavcodec/mediacodecdec_h264.c')
-rw-r--r--libavcodec/mediacodecdec_h264.c336
1 files changed, 336 insertions, 0 deletions
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 <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,
+};