| commit b0e16c55a8291720d2e5f389a28c4d98589da305 | |
| Author: Rodger Combs <rodger.combs@gmail.com> | |
| Date: Fri Apr 1 20:36:13 2016 -0500 | |
| lavc: add videotoolbox decoders | |
| diff --git a/configure b/configure | |
| index 94a66d8..1ce09b1 100755 | |
| --- a/configure | |
| +++ b/configure | |
| @@ -2526,6 +2526,7 @@ xvmc_deps="X11_extensions_XvMClib_h" | |
| h263_vaapi_hwaccel_deps="vaapi" | |
| h263_vaapi_hwaccel_select="h263_decoder" | |
| +h263_videotoolbox_deps="videotoolbox pthreads" | |
| h263_videotoolbox_hwaccel_deps="videotoolbox" | |
| h263_videotoolbox_hwaccel_select="h263_decoder" | |
| h264_crystalhd_decoder_select="crystalhd h264_mp4toannexb_bsf h264_parser" | |
| @@ -2551,6 +2552,7 @@ h264_vdpau_decoder_deps="vdpau" | |
| h264_vdpau_decoder_select="h264_decoder" | |
| h264_vdpau_hwaccel_deps="vdpau" | |
| h264_vdpau_hwaccel_select="h264_decoder" | |
| +h264_videotoolbox_deps="videotoolbox pthreads" | |
| h264_videotoolbox_hwaccel_deps="videotoolbox" | |
| h264_videotoolbox_hwaccel_select="h264_decoder" | |
| hevc_d3d11va_hwaccel_deps="d3d11va DXVA_PicParams_HEVC" | |
| @@ -2570,6 +2572,7 @@ mpeg1_vdpau_decoder_deps="vdpau" | |
| mpeg1_vdpau_decoder_select="mpeg1video_decoder" | |
| mpeg1_vdpau_hwaccel_deps="vdpau" | |
| mpeg1_vdpau_hwaccel_select="mpeg1video_decoder" | |
| +mpeg1_videotoolbox_deps="videotoolbox pthreads" | |
| mpeg1_videotoolbox_hwaccel_deps="videotoolbox" | |
| mpeg1_videotoolbox_hwaccel_select="mpeg1video_decoder" | |
| mpeg1_xvmc_hwaccel_deps="xvmc" | |
| @@ -2588,6 +2591,7 @@ mpeg2_vaapi_hwaccel_deps="vaapi" | |
| mpeg2_vaapi_hwaccel_select="mpeg2video_decoder" | |
| mpeg2_vdpau_hwaccel_deps="vdpau" | |
| mpeg2_vdpau_hwaccel_select="mpeg2video_decoder" | |
| +mpeg2_videotoolbox_deps="videotoolbox pthreads" | |
| mpeg2_videotoolbox_hwaccel_deps="videotoolbox" | |
| mpeg2_videotoolbox_hwaccel_select="mpeg2video_decoder" | |
| mpeg2_xvmc_hwaccel_deps="xvmc" | |
| @@ -2602,6 +2606,7 @@ mpeg4_vdpau_decoder_deps="vdpau" | |
| mpeg4_vdpau_decoder_select="mpeg4_decoder" | |
| mpeg4_vdpau_hwaccel_deps="vdpau" | |
| mpeg4_vdpau_hwaccel_select="mpeg4_decoder" | |
| +mpeg4_videotoolbox_deps="videotoolbox pthreads" | |
| mpeg4_videotoolbox_hwaccel_deps="videotoolbox" | |
| mpeg4_videotoolbox_hwaccel_select="mpeg4_decoder" | |
| msmpeg4_crystalhd_decoder_select="crystalhd" | |
| diff --git a/libavcodec/Makefile b/libavcodec/Makefile | |
| index 801cccd..7502ec9 100644 | |
| --- a/libavcodec/Makefile | |
| +++ b/libavcodec/Makefile | |
| @@ -71,6 +71,11 @@ OBJS-$(CONFIG_H264CHROMA) += h264chroma.o | |
| OBJS-$(CONFIG_H264DSP) += h264dsp.o h264idct.o | |
| OBJS-$(CONFIG_H264PRED) += h264pred.o | |
| OBJS-$(CONFIG_H264QPEL) += h264qpel.o | |
| +OBJS-$(CONFIG_H263_VIDEOTOOLBOX_DECODER) += videotoolboxdec.o | |
| +OBJS-$(CONFIG_H264_VIDEOTOOLBOX_DECODER) += videotoolboxdec.o | |
| +OBJS-$(CONFIG_MPEG1_VIDEOTOOLBOX_DECODER) += videotoolboxdec.o | |
| +OBJS-$(CONFIG_MPEG2_VIDEOTOOLBOX_DECODER) += videotoolboxdec.o | |
| +OBJS-$(CONFIG_MPEG4_VIDEOTOOLBOX_DECODER) += videotoolboxdec.o | |
| OBJS-$(CONFIG_H264_VIDEOTOOLBOX_ENCODER) += videotoolboxenc.o | |
| OBJS-$(CONFIG_HPELDSP) += hpeldsp.o | |
| OBJS-$(CONFIG_HUFFMAN) += huffman.o | |
| diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c | |
| index 0bbf7d3..660d03d 100644 | |
| --- a/libavcodec/allcodecs.c | |
| +++ b/libavcodec/allcodecs.c | |
| @@ -200,6 +200,7 @@ void avcodec_register_all(void) | |
| REGISTER_DECODER(H264_MMAL, h264_mmal); | |
| REGISTER_DECODER(H264_QSV, h264_qsv); | |
| REGISTER_DECODER(H264_VDA, h264_vda); | |
| + REGISTER_DECODER(H264_VIDEOTOOLBOX, h264_videotoolbox); | |
| #if FF_API_VDPAU | |
| REGISTER_DECODER(H264_VDPAU, h264_vdpau); | |
| #endif | |
| @@ -242,14 +243,17 @@ void avcodec_register_all(void) | |
| #if FF_API_VDPAU | |
| REGISTER_DECODER(MPEG4_VDPAU, mpeg4_vdpau); | |
| #endif | |
| + REGISTER_DECODER(MPEG4_VIDEOTOOLBOX,mpeg4_videotoolbox); | |
| REGISTER_DECODER(MPEGVIDEO, mpegvideo); | |
| #if FF_API_VDPAU | |
| REGISTER_DECODER(MPEG_VDPAU, mpeg_vdpau); | |
| REGISTER_DECODER(MPEG1_VDPAU, mpeg1_vdpau); | |
| #endif | |
| + REGISTER_DECODER(MPEG1_VIDEOTOOLBOX,mpeg1_videotoolbox); | |
| REGISTER_DECODER(MPEG2_MMAL, mpeg2_mmal); | |
| REGISTER_DECODER(MPEG2_CRYSTALHD, mpeg2_crystalhd); | |
| REGISTER_DECODER(MPEG2_QSV, mpeg2_qsv); | |
| + REGISTER_DECODER(MPEG2_VIDEOTOOLBOX,mpeg2_videotoolbox); | |
| REGISTER_DECODER(MSA1, msa1); | |
| REGISTER_DECODER(MSMPEG4_CRYSTALHD, msmpeg4_crystalhd); | |
| REGISTER_DECODER(MSMPEG4V1, msmpeg4v1); | |
| @@ -619,7 +623,6 @@ void avcodec_register_all(void) | |
| * above is available */ | |
| REGISTER_ENCODER(LIBOPENH264, libopenh264); | |
| REGISTER_ENCODER(H264_QSV, h264_qsv); | |
| - REGISTER_ENCODER(H264_VIDEOTOOLBOX, h264_videotoolbox); | |
| REGISTER_ENCODER(NVENC, nvenc); | |
| REGISTER_ENCODER(NVENC_H264, nvenc_h264); | |
| REGISTER_ENCODER(NVENC_HEVC, nvenc_hevc); | |
| diff --git a/libavcodec/videotoolboxdec.c b/libavcodec/videotoolboxdec.c | |
| new file mode 100644 | |
| index 0000000..29647f8 | |
| --- /dev/null | |
| +++ b/libavcodec/videotoolboxdec.c | |
| @@ -0,0 +1,761 @@ | |
| +/* | |
| + * VideoToolbox decoders | |
| + * | |
| + * copyright (c) 2016 Rodger Combs | |
| + * | |
| + * 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" | |
| +#include "avcodec.h" | |
| +#include "bytestream.h" | |
| +#include "internal.h" | |
| +#include "libavutil/avassert.h" | |
| +#include "libavutil/imgutils.h" | |
| +#include "libavutil/thread.h" | |
| +#include "libavutil/opt.h" | |
| + | |
| +#include <VideoToolbox/VideoToolbox.h> | |
| + | |
| +#ifndef kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder | |
| +# define kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder CFSTR("RequireHardwareAcceleratedVideoDecoder") | |
| +#endif | |
| + | |
| +#define NB_TIMESTAMPS 16 | |
| + | |
| +typedef struct FFBufferEntry { | |
| + CVImageBufferRef frame; | |
| + int64_t pts; | |
| + int64_t duration; | |
| + struct FFBufferEntry *next; | |
| + struct FFBufferEntry *prev; | |
| +} FFBufferEntry; | |
| + | |
| +typedef struct VTDecodeContext { | |
| + AVClass *av_class; | |
| + VTDecompressionSessionRef session; | |
| + CMFormatDescriptionRef fmt_desc; | |
| + | |
| + // Waiting output frames. | |
| + FFBufferEntry *waiting_buffers, *waiting_buffers_tail; | |
| + pthread_mutex_t lock; | |
| +} VTDecodeContext; | |
| + | |
| +static const uint8_t *ff_avc_find_startcode_internal(const uint8_t *p, const uint8_t *end) | |
| +{ | |
| + const uint8_t *a = p + 4 - ((intptr_t)p & 3); | |
| + | |
| + for (end -= 3; p < a && p < end; p++) { | |
| + if (p[0] == 0 && p[1] == 0 && p[2] == 1) | |
| + return p; | |
| + } | |
| + | |
| + for (end -= 3; p < end; p += 4) { | |
| + uint32_t x = *(const uint32_t*)p; | |
| + if ((x - 0x01010101) & (~x) & 0x80808080) { | |
| + if (p[1] == 0) { | |
| + if (p[0] == 0 && p[2] == 1) | |
| + return p; | |
| + if (p[2] == 0 && p[3] == 1) | |
| + return p+1; | |
| + } | |
| + if (p[3] == 0) { | |
| + if (p[2] == 0 && p[4] == 1) | |
| + return p+2; | |
| + if (p[4] == 0 && p[5] == 1) | |
| + return p+3; | |
| + } | |
| + } | |
| + } | |
| + | |
| + for (end += 3; p < end; p++) { | |
| + if (p[0] == 0 && p[1] == 0 && p[2] == 1) | |
| + return p; | |
| + } | |
| + | |
| + return end + 3; | |
| +} | |
| + | |
| +static const uint8_t *ff_avc_find_startcode(const uint8_t *p, const uint8_t *end) | |
| +{ | |
| + const uint8_t *out= ff_avc_find_startcode_internal(p, end); | |
| + if(p < out && out < end && !out[-1]) out--; | |
| + return out; | |
| +} | |
| + | |
| +static void ffvt_parse_nal_units_internal(PutByteContext *pb, const uint8_t *p, int size) | |
| +{ | |
| + const uint8_t *end = p + size; | |
| + const uint8_t *nal_start, *nal_end; | |
| + | |
| + nal_start = ff_avc_find_startcode(p, end); | |
| + for (;;) { | |
| + while (nal_start < end && !*(nal_start++)); | |
| + if (nal_start == end) | |
| + break; | |
| + | |
| + nal_end = ff_avc_find_startcode(nal_start, end); | |
| + bytestream2_put_be32(pb, nal_end - nal_start); | |
| + bytestream2_put_buffer(pb, nal_start, nal_end - nal_start); | |
| + nal_start = nal_end; | |
| + } | |
| +} | |
| + | |
| +static int ffvt_parse_nal_units(uint8_t **buf, int *size) | |
| +{ | |
| + const uint8_t *p = *buf; | |
| + PutByteContext pb; | |
| + *buf = av_malloc(*size + 20); | |
| + if (!*buf) | |
| + return AVERROR(ENOMEM); | |
| + bytestream2_init_writer(&pb, *buf, *size + 20); | |
| + ffvt_parse_nal_units_internal(&pb, p, *size); | |
| + | |
| + *size = bytestream2_tell_p(&pb); | |
| + | |
| + return 0; | |
| +} | |
| + | |
| +static int ffvt_make_avcc(uint8_t **in_buf, int *size) | |
| +{ | |
| + uint8_t *buf, *end, *start; | |
| + uint32_t sps_size = 0, pps_size = 0; | |
| + uint8_t *sps = 0, *pps = 0; | |
| + PutByteContext pb; | |
| + | |
| + int ret = ffvt_parse_nal_units(in_buf, size); | |
| + if (ret < 0) | |
| + return ret; | |
| + buf = *in_buf; | |
| + start = buf; | |
| + end = buf + *size; | |
| + | |
| + /* look for sps and pps */ | |
| + while (end - buf > 4) { | |
| + uint32_t size; | |
| + uint8_t nal_type; | |
| + size = FFMIN(AV_RB32(buf), end - buf - 4); | |
| + buf += 4; | |
| + nal_type = buf[0] & 0x1f; | |
| + | |
| + if (nal_type == 7) { /* SPS */ | |
| + sps = buf; | |
| + sps_size = size; | |
| + } else if (nal_type == 8) { /* PPS */ | |
| + pps = buf; | |
| + pps_size = size; | |
| + } | |
| + | |
| + buf += size; | |
| + } | |
| + | |
| + if (!sps || !pps || sps_size < 4 || sps_size > UINT16_MAX || pps_size > UINT16_MAX) { | |
| + av_freep(in_buf); | |
| + return AVERROR_INVALIDDATA; | |
| + } | |
| + | |
| + *in_buf = av_malloc(sps_size + pps_size + 12); | |
| + bytestream2_init_writer(&pb, *in_buf, sps_size + pps_size + 12); | |
| + | |
| + bytestream2_put_byte(&pb, 1); /* version */ | |
| + bytestream2_put_byte(&pb, sps[1]); /* profile */ | |
| + bytestream2_put_byte(&pb, sps[2]); /* profile compat */ | |
| + bytestream2_put_byte(&pb, sps[3]); /* level */ | |
| + bytestream2_put_byte(&pb, 0xff); /* 6 bits reserved (111111) + 2 bits nal size length - 1 (11) */ | |
| + bytestream2_put_byte(&pb, 0xe1); /* 3 bits reserved (111) + 5 bits number of sps (00001) */ | |
| + | |
| + bytestream2_put_be16(&pb, sps_size); | |
| + bytestream2_put_buffer(&pb, sps, sps_size); | |
| + bytestream2_put_byte(&pb, 1); /* number of pps */ | |
| + bytestream2_put_be16(&pb, pps_size); | |
| + bytestream2_put_buffer(&pb, pps, pps_size); | |
| + av_free(start); | |
| + *size = bytestream2_tell_p(&pb); | |
| + return 0; | |
| +} | |
| + | |
| +static void ffvt_release_frame(void *locked, uint8_t *data) | |
| +{ | |
| + CVPixelBufferRef cv_buffer = (CVImageBufferRef)data; | |
| + if (locked) | |
| + CVPixelBufferUnlockBaseAddress(cv_buffer, kCVPixelBufferLock_ReadOnly); | |
| + CVPixelBufferRelease(cv_buffer); | |
| +} | |
| + | |
| +static int ffvt_set_ref(AVFrame *frame, CVImageBufferRef ref, intptr_t locked) | |
| +{ | |
| + frame->buf[0] = av_buffer_create((void*)ref, | |
| + sizeof(ref), | |
| + ffvt_release_frame, | |
| + (void*)locked, | |
| + AV_BUFFER_FLAG_READONLY); | |
| + if (!frame->buf[0]) | |
| + return AVERROR(ENOMEM); | |
| + | |
| + return 0; | |
| +} | |
| + | |
| +static int ffvt_copy_frame(AVCodecContext *avctx, AVFrame *frame, FFBufferEntry* entry) | |
| +{ | |
| + int ret = 0; | |
| + | |
| + if ((ret = ff_decode_frame_props(avctx, frame)) < 0) | |
| + goto done; | |
| + | |
| + if ((ret = ffvt_set_ref(frame, entry->frame, avctx->pix_fmt != AV_PIX_FMT_VIDEOTOOLBOX)) < 0) | |
| + goto done; | |
| + | |
| + frame->width = CVPixelBufferGetWidth(entry->frame); | |
| + frame->height = CVPixelBufferGetHeight(entry->frame); | |
| + | |
| + if (avctx->pix_fmt == AV_PIX_FMT_VIDEOTOOLBOX) { | |
| + frame->data[3] = (void*)entry->frame; | |
| + frame->format = AV_PIX_FMT_VIDEOTOOLBOX; | |
| + } else { | |
| + int plane; | |
| + int nb_planes = CVPixelBufferGetPlaneCount(entry->frame); | |
| + | |
| + CVPixelBufferLockBaseAddress(entry->frame, kCVPixelBufferLock_ReadOnly); | |
| + | |
| + for (plane = 0; plane < nb_planes; plane++) { | |
| + frame->data[plane] = CVPixelBufferGetBaseAddressOfPlane(entry->frame, plane); | |
| + frame->linesize[plane] = CVPixelBufferGetBytesPerRowOfPlane(entry->frame, plane); | |
| + } | |
| + } | |
| + | |
| + frame->pkt_pts = entry->pts; | |
| + frame->pkt_duration = entry->duration; | |
| + frame->pkt_dts = AV_NOPTS_VALUE; | |
| + | |
| +done: | |
| + return ret; | |
| +} | |
| + | |
| +static void ffvt_write_mp4_descr_length(PutByteContext *pb, int length) | |
| +{ | |
| + int i; | |
| + uint8_t b; | |
| + | |
| + for (i = 3; i >= 0; i--) { | |
| + b = (length >> (i * 7)) & 0x7F; | |
| + if (i != 0) | |
| + b |= 0x80; | |
| + | |
| + bytestream2_put_byteu(pb, b); | |
| + } | |
| +} | |
| + | |
| +#define VIDEOTOOLBOX_ESDS_EXTRADATA_PADDING 12 | |
| + | |
| +static CFDataRef ffvt_esds_extradata_create(AVCodecContext *avctx) | |
| +{ | |
| + CFDataRef data; | |
| + uint8_t *rw_extradata; | |
| + PutByteContext pb; | |
| + int full_size = 3 + 5 + 13 + 5 + avctx->extradata_size + 3; | |
| + // ES_DescrTag data + DecoderConfigDescrTag + data + DecSpecificInfoTag + size + SLConfigDescriptor | |
| + int config_size = 13 + 5 + avctx->extradata_size; | |
| + int s; | |
| + | |
| + if (!(rw_extradata = av_mallocz(full_size + VIDEOTOOLBOX_ESDS_EXTRADATA_PADDING))) | |
| + return NULL; | |
| + | |
| + bytestream2_init_writer(&pb, rw_extradata, full_size + VIDEOTOOLBOX_ESDS_EXTRADATA_PADDING); | |
| + bytestream2_put_byteu(&pb, 0); // version | |
| + bytestream2_put_ne24(&pb, 0); // flags | |
| + | |
| + // elementary stream descriptor | |
| + bytestream2_put_byteu(&pb, 0x03); // ES_DescrTag | |
| + ffvt_write_mp4_descr_length(&pb, full_size); | |
| + bytestream2_put_ne16(&pb, 0); // esid | |
| + bytestream2_put_byteu(&pb, 0); // stream priority (0-32) | |
| + | |
| + // decoder configuration descriptor | |
| + bytestream2_put_byteu(&pb, 0x04); // DecoderConfigDescrTag | |
| + ffvt_write_mp4_descr_length(&pb, config_size); | |
| + bytestream2_put_byteu(&pb, 32); // object type indication. 32 = AV_CODEC_ID_MPEG4 | |
| + bytestream2_put_byteu(&pb, 0x11); // stream type | |
| + bytestream2_put_ne24(&pb, 0); // buffer size | |
| + bytestream2_put_ne32(&pb, 0); // max bitrate | |
| + bytestream2_put_ne32(&pb, 0); // avg bitrate | |
| + | |
| + // decoder specific descriptor | |
| + bytestream2_put_byteu(&pb, 0x05); ///< DecSpecificInfoTag | |
| + ffvt_write_mp4_descr_length(&pb, avctx->extradata_size); | |
| + | |
| + bytestream2_put_buffer(&pb, avctx->extradata, avctx->extradata_size); | |
| + | |
| + // SLConfigDescriptor | |
| + bytestream2_put_byteu(&pb, 0x06); // SLConfigDescrTag | |
| + bytestream2_put_byteu(&pb, 0x01); // length | |
| + bytestream2_put_byteu(&pb, 0x02); // | |
| + | |
| + s = bytestream2_size_p(&pb); | |
| + | |
| + data = CFDataCreate(kCFAllocatorDefault, rw_extradata, s); | |
| + | |
| + av_freep(&rw_extradata); | |
| + return data; | |
| +} | |
| + | |
| +static void ffvt_free_block(void *refCon, void *block, size_t size) | |
| +{ | |
| + AVPacket *avpkt = refCon; | |
| + av_packet_free(&avpkt); | |
| +} | |
| + | |
| +static void ffvt_free_block_simple(void *refCon, void *block, size_t size) | |
| +{ | |
| + av_free(block); | |
| +} | |
| + | |
| +static CMSampleBufferRef ffvt_create_sample_buffer(AVCodecContext *avctx, AVPacket *avpkt) | |
| +{ | |
| + VTDecodeContext *vt = avctx->priv_data; | |
| + OSStatus status; | |
| + CMBlockBufferRef block_buf = NULL; | |
| + CMSampleBufferRef sample_buf = NULL; | |
| + AVPacket *clone = av_packet_clone(avpkt); | |
| + CMBlockBufferCustomBlockSource source = { | |
| + .version = kCMBlockBufferCustomBlockSourceVersion, | |
| + .AllocateBlock = NULL, | |
| + .FreeBlock = ffvt_free_block, | |
| + .refCon = clone | |
| + }; | |
| + CMSampleTimingInfo timing = { | |
| + .duration = CMTimeMake(avpkt->duration, 1), | |
| + }; | |
| + uint8_t *data = avpkt->data; | |
| + int size = avpkt->size; | |
| + | |
| + if (!clone) | |
| + return NULL; | |
| + | |
| + if (avctx->codec_id == AV_CODEC_ID_H264 && avctx->extradata_size > 0 && | |
| + (AV_RB24(avctx->extradata) == 1 || AV_RB32(avctx->extradata) == 1)) { | |
| + av_packet_free(&clone); | |
| + if ((status = ffvt_parse_nal_units(&data, &size)) < 0) | |
| + return NULL; | |
| + source.FreeBlock = ffvt_free_block_simple; | |
| + } | |
| + | |
| + if (avpkt->pts != AV_NOPTS_VALUE) | |
| + timing.presentationTimeStamp = CMTimeMake(avpkt->pts, 1); | |
| + if (avpkt->dts != AV_NOPTS_VALUE) | |
| + timing.decodeTimeStamp = CMTimeMake(avpkt->dts, 1); | |
| + | |
| + status = CMBlockBufferCreateWithMemoryBlock(NULL, // structureAllocator | |
| + data, // memoryBlock | |
| + size, // blockLength | |
| + NULL, // blockAllocator | |
| + &source, // customBlockSource | |
| + 0, // offsetToData | |
| + size, // dataLength | |
| + 0, // flags | |
| + &block_buf); | |
| + | |
| + if (!status) { | |
| + status = CMSampleBufferCreate(NULL, // allocator | |
| + block_buf, // dataBuffer | |
| + TRUE, // dataReady | |
| + NULL, // makeDataReadyCallback | |
| + NULL, // makeDataReadyRefcon | |
| + vt->fmt_desc, // formatDescription | |
| + 1, // numSamples | |
| + 1, // numSampleTimingEntries | |
| + &timing, // sampleTimingArray | |
| + 0, // numSampleSizeEntries | |
| + NULL, // sampleSizeArray | |
| + &sample_buf); | |
| + } else { | |
| + av_packet_free(&clone); | |
| + } | |
| + | |
| + if (block_buf) | |
| + CFRelease(block_buf); | |
| + | |
| + return sample_buf; | |
| +} | |
| + | |
| +static void ffvt_decoder_callback(void *opaque, | |
| + void *sourceFrameRefCon, | |
| + OSStatus status, | |
| + VTDecodeInfoFlags flags, | |
| + CVImageBufferRef image_buffer, | |
| + CMTime pts, | |
| + CMTime duration) | |
| +{ | |
| + AVCodecContext *avctx = opaque; | |
| + VTDecodeContext *vt = avctx->priv_data; | |
| + FFBufferEntry *buf = av_mallocz(sizeof(*buf)); | |
| + | |
| + if (!buf) { | |
| + av_log(NULL, AV_LOG_ERROR, "vt decoder cb: failed to allocated buffer entry\n"); | |
| + return; | |
| + } | |
| + | |
| + if (!image_buffer) | |
| + av_log(NULL, AV_LOG_DEBUG, "vt decoder cb: output image buffer is null\n"); | |
| + | |
| + buf->frame = CVPixelBufferRetain(image_buffer); | |
| + if (!CMTIME_IS_INVALID(pts)) | |
| + buf->pts = pts.value; | |
| + else | |
| + buf->pts = AV_NOPTS_VALUE; | |
| + if (!CMTIME_IS_INVALID(duration)) | |
| + buf->duration = duration.value; | |
| + | |
| + pthread_mutex_lock(&vt->lock); | |
| + if (vt->waiting_buffers_tail) { | |
| + vt->waiting_buffers_tail->next = buf; | |
| + buf->prev = vt->waiting_buffers_tail; | |
| + vt->waiting_buffers_tail = buf; | |
| + } else { | |
| + vt->waiting_buffers = vt->waiting_buffers_tail = buf; | |
| + } | |
| + pthread_mutex_unlock(&vt->lock); | |
| +} | |
| + | |
| +static int ffvt_decode(AVCodecContext *avctx, void *data, | |
| + int *got_frame_ptr, AVPacket *avpkt) | |
| +{ | |
| + VTDecodeContext *vt = avctx->priv_data; | |
| + OSStatus status = noErr; | |
| + CMSampleBufferRef sample_buf = NULL; | |
| + FFBufferEntry *entry = NULL, *search_entry; | |
| + | |
| + if (avpkt->data) { | |
| + sample_buf = ffvt_create_sample_buffer(avctx, avpkt); | |
| + if (!sample_buf) | |
| + return AVERROR(ENOMEM); | |
| + | |
| + status = VTDecompressionSessionDecodeFrame(vt->session, sample_buf, | |
| + 0, | |
| + NULL, // sourceFrameRefCon | |
| + 0); // infoFlagsOut | |
| + | |
| + CFRelease(sample_buf); | |
| + } else { | |
| + VTDecompressionSessionFinishDelayedFrames(vt->session); | |
| + } | |
| + | |
| + if (status) { | |
| + av_log(avctx, AV_LOG_ERROR, "Decode error: %i\n", status); | |
| + return AVERROR_UNKNOWN; | |
| + } | |
| + | |
| + pthread_mutex_lock(&vt->lock); | |
| + for (search_entry = vt->waiting_buffers; search_entry; search_entry = search_entry->next) { | |
| + if ((avpkt->dts == AV_NOPTS_VALUE || search_entry->pts <= avpkt->dts) && | |
| + (!entry || search_entry->pts < entry->pts)) | |
| + entry = search_entry; | |
| + } | |
| + if (entry) { | |
| + FFBufferEntry *prev = entry->prev; | |
| + if (prev) | |
| + prev->next = entry->next; | |
| + else | |
| + vt->waiting_buffers = entry->next; | |
| + if (entry->next) | |
| + entry->next->prev = prev; | |
| + else | |
| + vt->waiting_buffers_tail = prev; | |
| + pthread_mutex_unlock(&vt->lock); | |
| + if ((status = ffvt_copy_frame(avctx, data, entry)) < 0) | |
| + return status; | |
| + av_free(entry); | |
| + *got_frame_ptr = 1; | |
| + } else { | |
| + pthread_mutex_unlock(&vt->lock); | |
| + } | |
| + return avpkt->size; | |
| +} | |
| + | |
| +static CFDictionaryRef ffvt_decoder_config_create(CMVideoCodecType codec_type, | |
| + AVCodecContext *avctx) | |
| +{ | |
| + CFMutableDictionaryRef config_info = CFDictionaryCreateMutable(kCFAllocatorDefault, | |
| + 1, | |
| + &kCFTypeDictionaryKeyCallBacks, | |
| + &kCFTypeDictionaryValueCallBacks); | |
| +/* | |
| + CFDictionarySetValue(config_info, | |
| + kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder, | |
| + kCFBooleanTrue);*/ | |
| + | |
| + if (avctx->extradata_size) { | |
| + CFMutableDictionaryRef avc_info; | |
| + CFDataRef data = NULL; | |
| + | |
| + avc_info = CFDictionaryCreateMutable(kCFAllocatorDefault, | |
| + 1, | |
| + &kCFTypeDictionaryKeyCallBacks, | |
| + &kCFTypeDictionaryValueCallBacks); | |
| + | |
| + switch (codec_type) { | |
| + case kCMVideoCodecType_MPEG4Video : | |
| + data = ffvt_esds_extradata_create(avctx); | |
| + if (data) | |
| + CFDictionarySetValue(avc_info, CFSTR("esds"), data); | |
| + break; | |
| + case kCMVideoCodecType_H264 : | |
| + if (avctx->extradata_size > 6 && | |
| + (AV_RB24(avctx->extradata) == 1 || AV_RB32(avctx->extradata) == 1)) { | |
| + uint8_t *buf = avctx->extradata; | |
| + int size = avctx->extradata_size; | |
| + if (ffvt_make_avcc(&buf, &size) < 0) | |
| + break; | |
| + data = CFDataCreate(NULL, buf, size); | |
| + av_free(buf); | |
| + } else { | |
| + data = CFDataCreate(NULL, avctx->extradata, avctx->extradata_size); | |
| + } | |
| + if (data) | |
| + CFDictionarySetValue(avc_info, CFSTR("avcC"), data); | |
| + break; | |
| + default: | |
| + break; | |
| + } | |
| + | |
| + CFDictionarySetValue(config_info, | |
| + kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms, | |
| + avc_info); | |
| + | |
| + if (data) | |
| + CFRelease(data); | |
| + | |
| + CFRelease(avc_info); | |
| + } | |
| + return config_info; | |
| +} | |
| + | |
| +static CFDictionaryRef ffvt_buffer_attributes_create(int width, int height, OSType pix_fmt) | |
| +{ | |
| + CFMutableDictionaryRef buffer_attributes; | |
| + CFMutableDictionaryRef io_surface_properties; | |
| + CFNumberRef w; | |
| + CFNumberRef h; | |
| + CFNumberRef cv_pix_fmt = NULL; | |
| + | |
| + w = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &width); | |
| + h = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &height); | |
| + | |
| + buffer_attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, | |
| + 4, | |
| + &kCFTypeDictionaryKeyCallBacks, | |
| + &kCFTypeDictionaryValueCallBacks); | |
| + io_surface_properties = CFDictionaryCreateMutable(kCFAllocatorDefault, | |
| + 0, | |
| + &kCFTypeDictionaryKeyCallBacks, | |
| + &kCFTypeDictionaryValueCallBacks); | |
| + | |
| + if (pix_fmt) { | |
| + cv_pix_fmt = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pix_fmt); | |
| + CFDictionarySetValue(buffer_attributes, kCVPixelBufferPixelFormatTypeKey, cv_pix_fmt); | |
| + } | |
| + CFDictionarySetValue(buffer_attributes, kCVPixelBufferIOSurfacePropertiesKey, io_surface_properties); | |
| + CFDictionarySetValue(buffer_attributes, kCVPixelBufferWidthKey, w); | |
| + CFDictionarySetValue(buffer_attributes, kCVPixelBufferHeightKey, h); | |
| + | |
| + CFRelease(io_surface_properties); | |
| + if (cv_pix_fmt) | |
| + CFRelease(cv_pix_fmt); | |
| + CFRelease(w); | |
| + CFRelease(h); | |
| + | |
| + return buffer_attributes; | |
| +} | |
| + | |
| +static CMVideoFormatDescriptionRef ffvt_format_desc_create(CMVideoCodecType codec_type, | |
| + CFDictionaryRef decoder_spec, | |
| + int width, int height) | |
| +{ | |
| + CMFormatDescriptionRef fmt_desc; | |
| + OSStatus status; | |
| + | |
| + status = CMVideoFormatDescriptionCreate(kCFAllocatorDefault, | |
| + codec_type, | |
| + width, | |
| + height, | |
| + decoder_spec, // Dictionary of extension | |
| + &fmt_desc); | |
| + | |
| + if (status) | |
| + return NULL; | |
| + | |
| + return fmt_desc; | |
| +} | |
| + | |
| +static av_cold void ffvt_decode_flush(AVCodecContext *avctx) | |
| +{ | |
| + VTDecodeContext *vt = avctx->priv_data; | |
| + VTDecompressionSessionWaitForAsynchronousFrames(vt->session); | |
| + while (vt->waiting_buffers) { | |
| + FFBufferEntry *entry = vt->waiting_buffers; | |
| + CVPixelBufferRelease(entry->frame); | |
| + vt->waiting_buffers = entry->next; | |
| + av_free(entry); | |
| + } | |
| +} | |
| + | |
| +static int ffvt_decode_init(AVCodecContext *avctx) | |
| +{ | |
| + VTDecodeContext *vt = avctx->priv_data; | |
| + int ret; | |
| + VTDecompressionOutputCallbackRecord decoder_cb; | |
| + CFDictionaryRef decoder_spec; | |
| + CFDictionaryRef buf_attr; | |
| + CMVideoCodecType cm_codec_type; | |
| + OSType cv_pix_fmt_type = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange; | |
| + | |
| + switch (avctx->codec_id) { | |
| + case AV_CODEC_ID_H263: | |
| + cm_codec_type = kCMVideoCodecType_H263; | |
| + break; | |
| + case AV_CODEC_ID_H264: | |
| + cm_codec_type = kCMVideoCodecType_H264; | |
| + break; | |
| + case AV_CODEC_ID_MPEG1VIDEO: | |
| + cm_codec_type = kCMVideoCodecType_MPEG1Video; | |
| + break; | |
| + case AV_CODEC_ID_MPEG2VIDEO: | |
| + cm_codec_type = kCMVideoCodecType_MPEG2Video; | |
| + break; | |
| + case AV_CODEC_ID_MPEG4: | |
| + cm_codec_type = kCMVideoCodecType_MPEG4Video; | |
| + break; | |
| + default: | |
| + av_assert0(!"Unknown codec ID!"); | |
| + break; | |
| + } | |
| + | |
| + if ((ret = ff_get_format(avctx, avctx->codec->pix_fmts)) < 0) | |
| + return ret; | |
| + | |
| + avctx->pix_fmt = ret; | |
| + | |
| + decoder_spec = ffvt_decoder_config_create(cm_codec_type, avctx); | |
| + | |
| + vt->fmt_desc = ffvt_format_desc_create(cm_codec_type, decoder_spec, | |
| + avctx->width, avctx->height); | |
| + if (!vt->fmt_desc) { | |
| + if (decoder_spec) | |
| + CFRelease(decoder_spec); | |
| + | |
| + av_log(avctx, AV_LOG_ERROR, "format description creation failed\n"); | |
| + return -1; | |
| + } | |
| + | |
| + buf_attr = ffvt_buffer_attributes_create(avctx->width, avctx->height, | |
| + cv_pix_fmt_type); | |
| + | |
| + decoder_cb.decompressionOutputCallback = ffvt_decoder_callback; | |
| + decoder_cb.decompressionOutputRefCon = avctx; | |
| + | |
| + ret = VTDecompressionSessionCreate(NULL, // allocator | |
| + vt->fmt_desc, // videoFormatDescription | |
| + decoder_spec, // videoDecoderSpecification | |
| + buf_attr, // destinationImageBufferAttributes | |
| + &decoder_cb, // outputCallback | |
| + &vt->session); // decompressionSessionOut | |
| + | |
| + if (decoder_spec) | |
| + CFRelease(decoder_spec); | |
| + if (buf_attr) | |
| + CFRelease(buf_attr); | |
| + | |
| + pthread_mutex_init(&vt->lock, NULL); | |
| + | |
| + switch (ret) { | |
| + case kVTVideoDecoderNotAvailableNowErr: | |
| + case kVTVideoDecoderUnsupportedDataFormatErr: | |
| + return AVERROR(ENOSYS); | |
| + case kVTVideoDecoderMalfunctionErr: | |
| + return AVERROR(EINVAL); | |
| + case kVTVideoDecoderBadDataErr : | |
| + return AVERROR_INVALIDDATA; | |
| + case 0: | |
| + return 0; | |
| + default: | |
| + return AVERROR_UNKNOWN; | |
| + } | |
| +} | |
| + | |
| +static int ffvt_decode_close(AVCodecContext *avctx) | |
| +{ | |
| + VTDecodeContext *vt = avctx->priv_data; | |
| + | |
| + if (vt->fmt_desc) | |
| + CFRelease(vt->fmt_desc); | |
| + | |
| + ffvt_decode_flush(avctx); | |
| + | |
| + if (vt->session) { | |
| + VTDecompressionSessionInvalidate(vt->session); | |
| + CFRelease(vt->session); | |
| + } | |
| + | |
| + pthread_mutex_destroy(&vt->lock); | |
| + | |
| + return 0; | |
| +} | |
| + | |
| +#define AE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_DECODING_PARAM | |
| +static const AVOption options[] = { | |
| +// {"aac_at_quality", "quality vs speed control", offsetof(ATDecodeContext, quality), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 2, AE}, | |
| + { NULL }, | |
| +}; | |
| + | |
| +#define FFVT_DEC_CLASS(NAME) \ | |
| + static const AVClass vt_##NAME##_dec_class = { \ | |
| + .class_name = "videotoolbox_" #NAME "_dec", \ | |
| + .option = options, \ | |
| + .version = LIBAVUTIL_VERSION_INT, \ | |
| + }; | |
| + | |
| +#if !TARGET_OS_IPHONE | |
| +#define IF_OSX(x) x, | |
| +#else | |
| +#define IF_OSX(x) | |
| +#endif | |
| + | |
| +#define FFVT_DEC(NAME, ID) \ | |
| + FFVT_DEC_CLASS(NAME) \ | |
| + AVCodec ff_##NAME##_videotoolbox_decoder = { \ | |
| + .name = #NAME "_vt", \ | |
| + .long_name = NULL_IF_CONFIG_SMALL(#NAME " (videotoolbox)"), \ | |
| + .type = AVMEDIA_TYPE_VIDEO, \ | |
| + .id = ID, \ | |
| + .priv_data_size = sizeof(VTDecodeContext), \ | |
| + .init = ffvt_decode_init, \ | |
| + .close = ffvt_decode_close, \ | |
| + .decode = ffvt_decode, \ | |
| + .flush = ffvt_decode_flush, \ | |
| + .priv_class = &vt_##NAME##_dec_class, \ | |
| + .capabilities = AV_CODEC_CAP_DELAY, \ | |
| + .caps_internal = FF_CODEC_CAP_SETS_PKT_DTS, \ | |
| + .pix_fmts = (const enum AVPixelFormat[]) { AV_PIX_FMT_VIDEOTOOLBOX, \ | |
| + AV_PIX_FMT_NV12, \ | |
| + IF_OSX(AV_PIX_FMT_YUV420P) \ | |
| + AV_PIX_FMT_NONE }, \ | |
| + }; | |
| + | |
| +FFVT_DEC(h263, AV_CODEC_ID_H263) | |
| +FFVT_DEC(h264, AV_CODEC_ID_H264) | |
| +FFVT_DEC(mpeg1, AV_CODEC_ID_MPEG1VIDEO) | |
| +FFVT_DEC(mpeg2, AV_CODEC_ID_MPEG2VIDEO) | |
| +FFVT_DEC(mpeg4, AV_CODEC_ID_MPEG4) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment