-
-
Save rcombs/e32779b8da641f54a33b040d77b73a46 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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