Skip to content

Instantly share code, notes, and snippets.

@rcombs

rcombs/stdin Secret

Created June 17, 2016 17:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rcombs/e32779b8da641f54a33b040d77b73a46 to your computer and use it in GitHub Desktop.
Save rcombs/e32779b8da641f54a33b040d77b73a46 to your computer and use it in GitHub Desktop.
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