[FFmpeg-devel] [PATCH 2/2] vda: implement h264_vda decoder
Xidorn Quan
quanxunzhen at gmail.com
Tue Aug 21 03:48:47 CEST 2012
---
configure | 1 +
libavcodec/Makefile | 1 +
libavcodec/allcodecs.c | 1 +
libavcodec/vda_h264_dec.c | 450 ++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 453 insertions(+)
create mode 100644 libavcodec/vda_h264_dec.c
diff --git a/configure b/configure
index 679fae0..ca2feab 100755
--- a/configure
+++ b/configure
@@ -1559,6 +1559,7 @@ h264_vaapi_hwaccel_select="vaapi h264_decoder"
h264_vda_hwaccel_deps="VideoDecodeAcceleration_VDADecoder_h pthreads"
h264_vda_hwaccel_select="vda h264_decoder"
h264_vdpau_decoder_select="vdpau h264_decoder"
+h264_vda_decoder_select="vda h264_parser"
iac_decoder_select="fft mdct sinewin"
imc_decoder_select="fft mdct sinewin"
jpegls_decoder_select="golomb"
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 576ec5f..4188fb3 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -215,6 +215,7 @@ OBJS-$(CONFIG_H264_DECODER) += h264.o \
OBJS-$(CONFIG_H264_DXVA2_HWACCEL) += dxva2_h264.o
OBJS-$(CONFIG_H264_VAAPI_HWACCEL) += vaapi_h264.o
OBJS-$(CONFIG_H264_VDA_HWACCEL) += vda_h264.o
+OBJS-$(CONFIG_H264_VDA_DECODER) += vda_h264_dec.o
OBJS-$(CONFIG_HUFFYUV_DECODER) += huffyuv.o
OBJS-$(CONFIG_HUFFYUV_ENCODER) += huffyuv.o
OBJS-$(CONFIG_IAC_DECODER) += imc.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 4a247c4..905aa18 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -134,6 +134,7 @@ void avcodec_register_all(void)
REGISTER_DECODER (H264, h264);
REGISTER_DECODER (H264_CRYSTALHD, h264_crystalhd);
REGISTER_DECODER (H264_VDPAU, h264_vdpau);
+ REGISTER_DECODER (H264_VDA, h264_vda);
REGISTER_ENCDEC (HUFFYUV, huffyuv);
REGISTER_DECODER (IDCIN, idcin);
REGISTER_DECODER (IFF_BYTERUN1, iff_byterun1);
diff --git a/libavcodec/vda_h264_dec.c b/libavcodec/vda_h264_dec.c
new file mode 100644
index 0000000..1a097de
--- /dev/null
+++ b/libavcodec/vda_h264_dec.c
@@ -0,0 +1,450 @@
+/*
+ * Copyright (c) 2012, Xidorn Quan
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * H.264 decoder via VDA
+ * @author Xidorn Quan <quanxunzhen at gmail.com>
+ */
+
+#include <string.h>
+#include <libkern/OSAtomic.h>
+
+#include "vda.h"
+#include "h264.h"
+#include "golomb.h"
+#include "avcodec.h"
+
+typedef struct display_frame {
+ AVFrame f;
+ int mmco_reset, poc;
+ CVPixelBufferRef cv_buffer;
+ struct display_frame *next, *prev;
+} DisplayFrame;
+
+typedef struct {
+ struct vda_context vda_ctx;
+ AVCodecParserContext *parser;
+ H264Context *h;
+ DisplayFrame queue;
+ DisplayFrame *next_frame;
+ int queued_pics;
+ OSSpinLock queue_lock;
+} VDADecoderContext;
+
+static inline DisplayFrame *alloc_frame(CVPixelBufferRef buffer)
+{
+ DisplayFrame *new_frame = av_calloc(1, sizeof(DisplayFrame));
+ if (!new_frame)
+ return NULL;
+
+ new_frame->cv_buffer = buffer;
+ CVPixelBufferLockBaseAddress(buffer, 0);
+ new_frame->f.data[0] = CVPixelBufferGetBaseAddress(buffer);
+ new_frame->f.data[3] = (void *)buffer;
+ new_frame->f.linesize[0] = CVPixelBufferGetBytesPerRow(buffer);
+
+ return new_frame;
+}
+
+static inline void release_frame(DisplayFrame *df)
+{
+ if (!df)
+ return;
+ CVPixelBufferUnlockBaseAddress(df->cv_buffer, 0);
+ CVPixelBufferRelease(df->cv_buffer);
+ av_free(df);
+}
+
+static inline void push_queue(VDADecoderContext *ctx, DisplayFrame *df)
+{
+ DisplayFrame *prev;
+
+ OSSpinLockLock(&ctx->queue_lock);
+
+ prev = ctx->queue.prev;
+ if (!df->f.key_frame && !df->mmco_reset)
+ while (prev->poc > df->poc && !prev->f.key_frame && !prev->mmco_reset)
+ prev = prev->prev;
+ prev->next->prev = df;
+ df->next = prev->next;
+ df->prev = prev;
+ prev->next = df;
+ ctx->queued_pics++;
+
+ OSSpinLockUnlock(&ctx->queue_lock);
+}
+
+static inline DisplayFrame *pop_queue(VDADecoderContext *ctx)
+{
+ DisplayFrame *next;
+
+ OSSpinLockLock(&ctx->queue_lock);
+
+ next = ctx->queue.next;
+ if (next == &ctx->queue) {
+ next = NULL;
+ goto end;
+ }
+
+ ctx->queue.next = next->next;
+ next->next->prev = &ctx->queue;
+ ctx->queued_pics--;
+
+end:
+ OSSpinLockUnlock(&ctx->queue_lock);
+ return next;
+}
+
+static inline void flush_queue(VDADecoderContext *ctx)
+{
+ DisplayFrame *df;
+ while ((df = pop_queue(ctx)))
+ release_frame(df);
+}
+
+/**
+ * Fill poc of frame
+ * Copy & slightly modify from init_poc in h264.c
+ */
+static inline void init_poc(H264Context *h, DisplayFrame *df)
+{
+ MpegEncContext *const s = &h->s;
+ const int max_frame_num = 1 << h->sps.log2_max_frame_num;
+ int field_poc[2];
+ int top_foc = INT_MAX, bottom_foc = INT_MAX;
+
+ h->frame_num_offset = h->prev_frame_num_offset;
+ if (h->frame_num < h->prev_frame_num)
+ h->frame_num_offset += max_frame_num;
+
+ if (h->sps.poc_type == 0) {
+ const int max_poc_lsb = 1 << h->sps.log2_max_poc_lsb;
+
+ if (h->poc_lsb < h->prev_poc_lsb && h->prev_poc_lsb - h->poc_lsb >= max_poc_lsb / 2)
+ h->poc_msb = h->prev_poc_msb + max_poc_lsb;
+ else if (h->poc_lsb > h->prev_poc_lsb && h->prev_poc_lsb - h->poc_lsb < -max_poc_lsb / 2)
+ h->poc_msb = h->prev_poc_msb - max_poc_lsb;
+ else
+ h->poc_msb = h->prev_poc_msb;
+ field_poc[0] =
+ field_poc[1] = h->poc_msb + h->poc_lsb;
+ if (s->picture_structure == PICT_FRAME)
+ field_poc[1] += h->delta_poc_bottom;
+ } else if (h->sps.poc_type == 1) {
+ int abs_frame_num, expected_delta_per_poc_cycle, expectedpoc;
+ int i;
+
+ if (h->sps.poc_cycle_length != 0)
+ abs_frame_num = h->frame_num_offset + h->frame_num;
+ else
+ abs_frame_num = 0;
+
+ if (h->nal_ref_idc == 0 && abs_frame_num > 0)
+ abs_frame_num--;
+
+ expected_delta_per_poc_cycle = 0;
+ for (i = 0; i < h->sps.poc_cycle_length; i++)
+ // FIXME integrate during sps parse
+ expected_delta_per_poc_cycle += h->sps.offset_for_ref_frame[i];
+
+ if (abs_frame_num > 0) {
+ int poc_cycle_cnt = (abs_frame_num - 1) / h->sps.poc_cycle_length;
+ int frame_num_in_poc_cycle = (abs_frame_num - 1) % h->sps.poc_cycle_length;
+
+ expectedpoc = poc_cycle_cnt * expected_delta_per_poc_cycle;
+ for (i = 0; i <= frame_num_in_poc_cycle; i++)
+ expectedpoc = expectedpoc + h->sps.offset_for_ref_frame[i];
+ } else
+ expectedpoc = 0;
+
+ if (h->nal_ref_idc == 0)
+ expectedpoc = expectedpoc + h->sps.offset_for_non_ref_pic;
+
+ field_poc[0] = expectedpoc + h->delta_poc[0];
+ field_poc[1] = field_poc[0] + h->sps.offset_for_top_to_bottom_field;
+
+ if (s->picture_structure == PICT_FRAME)
+ field_poc[1] += h->delta_poc[1];
+ } else {
+ int poc = 2 * (h->frame_num_offset + h->frame_num);
+
+ if (!h->nal_ref_idc)
+ poc--;
+
+ field_poc[0] = poc;
+ field_poc[1] = poc;
+ }
+
+ if (s->picture_structure != PICT_BOTTOM_FIELD)
+ top_foc = field_poc[0];
+ if (s->picture_structure != PICT_TOP_FIELD)
+ bottom_foc = field_poc[1];
+ df->poc = FFMIN(top_foc, bottom_foc);
+
+ h->prev_poc_msb = h->poc_msb;
+ h->prev_poc_lsb = h->poc_lsb;
+ h->prev_frame_num_offset = h->frame_num_offset;
+ h->prev_frame_num = h->frame_num;
+}
+
+static void idr(H264Context *h)
+{
+ int i;
+ h->prev_frame_num = 0;
+ h->prev_frame_num_offset = 0;
+ h->prev_poc_msb = 1 << 16;
+ h->prev_poc_lsb = 0;
+ for (i = 0; i < MAX_DELAYED_PIC_COUNT; i++)
+ h->last_pocs[i] = INT_MIN;
+}
+
+/**
+ * Parse frame information and fill DisplayFrame
+ * Mostly copyed from decode_slice_header in h264.c
+ */
+static inline void parse_frame(AVCodecContext *avctx, void *buf, int len, DisplayFrame *df)
+{
+ VDADecoderContext *ctx = avctx->priv_data;
+ H264Context *h = ctx->h;
+ MpegEncContext *s = &h->s;
+ uint8_t *pout;
+ int psize;
+ int i;
+ int out_of_order;
+
+ /* parse information by H.264 parser */
+ i = av_parser_parse2(ctx->parser, avctx, &pout, &psize, buf, len,
+ avctx->pkt->pts, avctx->pkt->dts, 0);
+
+ /* fill fields for calculating poc */
+ if (h->nal_unit_type == NAL_IDR_SLICE)
+ get_ue_golomb(&s->gb);
+ if (h->sps.poc_type == 0) {
+ h->poc_lsb = get_bits(&s->gb, h->sps.log2_max_poc_lsb);
+ if (h->pps.pic_order_present == 1 && s->picture_structure == PICT_FRAME)
+ h->delta_poc_bottom = get_se_golomb(&s->gb);
+ }
+ if (h->sps.poc_type == 1 && !h->sps.delta_pic_order_always_zero_flag) {
+ h->delta_poc[0] = get_se_golomb(&s->gb);
+ if (h->pps.pic_order_present == 1 && s->picture_structure == PICT_FRAME)
+ h->delta_poc[1] = get_se_golomb(&s->gb);
+ }
+
+ /* calculate poc */
+ if (h->nal_unit_type == NAL_IDR_SLICE)
+ idr(h);
+ init_poc(h, df);
+
+ // FIXME currently not support memory_management_control_operation
+ df->mmco_reset = 0;
+
+ /* fill fields will be used */
+ df->f.pict_type = ctx->parser->pict_type;
+ df->f.key_frame = ctx->parser->key_frame;
+
+ /* update has_b_frames */
+ for (i = 0; i < MAX_DELAYED_PIC_COUNT; i++) {
+ if (df->poc < h->last_pocs[i])
+ break;
+ if (i)
+ h->last_pocs[i - 1] = h->last_pocs[i];
+ }
+ if (i)
+ h->last_pocs[i - 1] = df->poc;
+ out_of_order = MAX_DELAYED_PIC_COUNT - i;
+ if (df->f.pict_type == AV_PICTURE_TYPE_B ||
+ (h->last_pocs[MAX_DELAYED_PIC_COUNT - 2] > INT_MIN &&
+ h->last_pocs[MAX_DELAYED_PIC_COUNT - 1] -
+ h->last_pocs[MAX_DELAYED_PIC_COUNT - 2] > 2))
+ out_of_order = FFMAX(out_of_order, 1);
+ if (avctx->has_b_frames < out_of_order)
+ avctx->has_b_frames = out_of_order;
+
+ /* push the picture into display queue */
+ push_queue(ctx, df);
+}
+
+static inline void prepare_next_frame(AVCodecContext *avctx)
+{
+ VDADecoderContext *ctx = avctx->priv_data;
+ H264Context *h = ctx->h;
+ int out_of_order;
+ int pics = ctx->queued_pics;
+ DisplayFrame *first;
+
+ /* release last outputed frame */
+ if (ctx->next_frame) {
+ release_frame(ctx->next_frame);
+ ctx->next_frame = NULL;
+ }
+
+ first = ctx->queue.next;
+ if (first == &ctx->queue)
+ return;
+ if (first->f.key_frame || first->mmco_reset)
+ h->next_outputed_poc = INT_MIN;
+ out_of_order = first->poc < h->next_outputed_poc;
+ if (out_of_order) {
+ release_frame(pop_queue(ctx));
+ av_log(avctx, AV_LOG_VERBOSE, "Dropped frame due to out_of_order.\n");
+ } else if (pics > avctx->has_b_frames) {
+ first = ctx->next_frame = pop_queue(ctx);
+ if (first->f.key_frame || first->mmco_reset)
+ h->next_outputed_poc = INT_MIN;
+ else
+ h->next_outputed_poc = first->poc;
+ }
+}
+
+static int vdadec_decode(AVCodecContext *avctx,
+ void *data, int *data_size, AVPacket *avpkt)
+{
+ VDADecoderContext *ctx = avctx->priv_data;
+ struct vda_context *vda_ctx = &ctx->vda_ctx;
+ AVFrame *pic = data;
+ OSStatus status;
+
+ if (avpkt->size) {
+ vda_ctx->bitstream = avpkt->data;
+ vda_ctx->bitstream_size = avpkt->size;
+
+ status = ff_vda_sync_decode(vda_ctx);
+ if (status != kVDADecoderNoErr) {
+ av_log(avctx, AV_LOG_ERROR, "Failed to decode frame: %d\n", status);
+ return -1;
+ }
+
+ if (vda_ctx->cv_buffer) {
+ DisplayFrame *df = alloc_frame(vda_ctx->cv_buffer);
+ parse_frame(avctx, avpkt->data, avpkt->size, df);
+ }
+ } else {
+ avctx->has_b_frames = 0;
+ }
+
+ *data_size = 0;
+ prepare_next_frame(avctx);
+ if (ctx->next_frame) {
+ *pic = ctx->next_frame->f;
+ *data_size = sizeof(AVFrame);
+ }
+
+ return avpkt->size;
+}
+
+static av_cold int vdadec_close(AVCodecContext *avctx)
+{
+ VDADecoderContext *ctx = avctx->priv_data;
+ /* flush display queue & free buffer */
+ release_frame(ctx->next_frame);
+ flush_queue(ctx);
+ /* release buffers and decoder */
+ ff_vda_destroy_decoder(&ctx->vda_ctx);
+ /* release parser */
+ av_parser_close(ctx->parser);
+ return 0;
+}
+
+static av_cold int vdadec_init(AVCodecContext *avctx)
+{
+ VDADecoderContext *ctx;
+ struct vda_context *vda_ctx;
+ H264Context *h;
+ OSStatus status;
+
+ ctx = avctx->priv_data;
+ memset(ctx, 0, sizeof(VDADecoderContext));
+
+ /* init display queue */
+ ctx->queue.mmco_reset = 1;
+ ctx->queue.f.key_frame = 1;
+ ctx->queue.poc = INT_MIN;
+ ctx->queue.next = &ctx->queue;
+ ctx->queue.prev = &ctx->queue;
+ ctx->queued_pics = 0;
+
+ /* init H.264 parser */
+ ctx->parser = av_parser_init(avctx->codec->id);
+ if (!ctx->parser) {
+ av_log(avctx, AV_LOG_ERROR, "Failed to open H.264 parser.\n");
+ goto failed;
+ }
+ ctx->parser->flags = PARSER_FLAG_COMPLETE_FRAMES;
+ ctx->h = ctx->parser->priv_data;
+
+ /* init output ordering related fields */
+ h = ctx->h;
+ h->outputed_poc = h->next_outputed_poc = INT_MIN;
+ idr(h);
+ h->prev_frame_num = -1;
+ avctx->has_b_frames = 2;
+
+ /* init vda */
+ vda_ctx = &ctx->vda_ctx;
+ vda_ctx->width = avctx->width;
+ vda_ctx->height = avctx->height;
+ vda_ctx->format = 'avc1';
+ vda_ctx->cv_pix_fmt_type = kCVPixelFormatType_422YpCbCr8_yuvs;
+ vda_ctx->use_sync_decoding = 1;
+ avctx->pix_fmt = PIX_FMT_YUYV422;
+ status = ff_vda_create_decoder(vda_ctx,
+ avctx->extradata, avctx->extradata_size);
+ if (status != kVDADecoderNoErr) {
+ av_log(avctx, AV_LOG_ERROR,
+ "Failed to init VDA decoder: %d.\n", status);
+ goto failed;
+ }
+
+ return 0;
+
+failed:
+ vdadec_close(avctx);
+ return -1;
+}
+
+static void vdadec_flush(AVCodecContext *avctx)
+{
+ VDADecoderContext *ctx = avctx->priv_data;
+ H264Context *h = ctx->h;
+ /* flush display queue & free buffer */
+ release_frame(ctx->next_frame);
+ ctx->next_frame = NULL;
+ flush_queue(ctx);
+ /* flush h.264 context */
+ h->outputed_poc = h->next_outputed_poc = INT_MIN;
+ idr(h);
+ h->prev_frame_num = -1;
+}
+
+AVCodec ff_h264_vda_decoder = {
+ .name = "h264_vda",
+ .type = AVMEDIA_TYPE_VIDEO,
+ .id = AV_CODEC_ID_H264,
+ .priv_data_size = sizeof(VDADecoderContext),
+ .init = vdadec_init,
+ .close = vdadec_close,
+ .decode = vdadec_decode,
+ .capabilities = CODEC_CAP_DELAY,
+ .flush = vdadec_flush,
+ .long_name = NULL_IF_CONFIG_SMALL("H.264 (VDA acceleration)"),
+ .pix_fmts = (const enum PixelFormat[]){ PIX_FMT_YUYV422,
+ PIX_FMT_NONE },
+};
--
1.7.11.4
More information about the ffmpeg-devel
mailing list