/* * 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 #include #include #include "buffer.h" #include "common.h" #include "hwcontext.h" #include "hwcontext_internal.h" #include "hwcontext_videotoolbox.h" #include "mem.h" #include "pixfmt.h" #include "pixdesc.h" static const struct { uint32_t cv_fmt; enum AVPixelFormat pix_fmt; } cv_pix_fmts[] = { { kCVPixelFormatType_420YpCbCr8Planar, AV_PIX_FMT_YUV420P }, { kCVPixelFormatType_422YpCbCr8, AV_PIX_FMT_UYVY422 }, { kCVPixelFormatType_32BGRA, AV_PIX_FMT_BGRA }, #ifdef kCFCoreFoundationVersionNumber10_7 { kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, AV_PIX_FMT_NV12 }, #endif }; enum AVPixelFormat av_map_videotoolbox_format_to_pixfmt(uint32_t cv_fmt) { int i; for (i = 0; i < FF_ARRAY_ELEMS(cv_pix_fmts); i++) { if (cv_pix_fmts[i].cv_fmt == cv_fmt) return cv_pix_fmts[i].pix_fmt; } return AV_PIX_FMT_NONE; } uint32_t av_map_videotoolbox_format_from_pixfmt(enum AVPixelFormat pix_fmt) { int i; for (i = 0; i < FF_ARRAY_ELEMS(cv_pix_fmts); i++) { if (cv_pix_fmts[i].pix_fmt == pix_fmt) return cv_pix_fmts[i].cv_fmt; } return 0; } static int vt_get_buffer(AVHWFramesContext *ctx, AVFrame *frame) { frame->buf[0] = av_buffer_pool_get(ctx->pool); if (!frame->buf[0]) return AVERROR(ENOMEM); frame->data[3] = frame->buf[0]->data; frame->format = AV_PIX_FMT_VIDEOTOOLBOX; frame->width = ctx->width; frame->height = ctx->height; return 0; } static int vt_transfer_get_formats(AVHWFramesContext *ctx, enum AVHWFrameTransferDirection dir, enum AVPixelFormat **formats) { enum AVPixelFormat *fmts = av_malloc_array(2, sizeof(*fmts)); if (!fmts) return AVERROR(ENOMEM); fmts[0] = ctx->sw_format; fmts[1] = AV_PIX_FMT_NONE; *formats = fmts; return 0; } static void vt_unmap(AVHWFramesContext *ctx, HWMapDescriptor *hwmap) { CVPixelBufferRef pixbuf = (CVPixelBufferRef)hwmap->source->data[3]; CVPixelBufferUnlockBaseAddress(pixbuf, (uintptr_t)hwmap->priv); } static int vt_map_frame(AVHWFramesContext *ctx, AVFrame *dst, const AVFrame *src, int flags) { CVPixelBufferRef pixbuf = (CVPixelBufferRef)src->data[3]; OSType pixel_format = CVPixelBufferGetPixelFormatType(pixbuf); CVReturn err; uint32_t map_flags = 0; int ret; int i; enum AVPixelFormat format; format = av_map_videotoolbox_format_to_pixfmt(pixel_format); if (dst->format != format) { av_log(ctx, AV_LOG_ERROR, "Unsupported or mismatching pixel format: %s\n", av_fourcc2str(pixel_format)); return AVERROR_UNKNOWN; } if (CVPixelBufferGetWidth(pixbuf) != ctx->width || CVPixelBufferGetHeight(pixbuf) != ctx->height) { av_log(ctx, AV_LOG_ERROR, "Inconsistent frame dimensions.\n"); return AVERROR_UNKNOWN; } if (flags == AV_HWFRAME_MAP_READ) map_flags = kCVPixelBufferLock_ReadOnly; err = CVPixelBufferLockBaseAddress(pixbuf, map_flags); if (err != kCVReturnSuccess) { av_log(ctx, AV_LOG_ERROR, "Error locking the pixel buffer.\n"); return AVERROR_UNKNOWN; } if (CVPixelBufferIsPlanar(pixbuf)) { int planes = CVPixelBufferGetPlaneCount(pixbuf); for (i = 0; i < planes; i++) { dst->data[i] = CVPixelBufferGetBaseAddressOfPlane(pixbuf, i); dst->linesize[i] = CVPixelBufferGetBytesPerRowOfPlane(pixbuf, i); } } else { dst->data[0] = CVPixelBufferGetBaseAddress(pixbuf); dst->linesize[0] = CVPixelBufferGetBytesPerRow(pixbuf); } ret = ff_hwframe_map_create(src->hw_frames_ctx, dst, src, vt_unmap, (void *)(uintptr_t)map_flags); if (ret < 0) goto unlock; return 0; unlock: CVPixelBufferUnlockBaseAddress(pixbuf, map_flags); return ret; } static int vt_transfer_data_from(AVHWFramesContext *hwfc, AVFrame *dst, const AVFrame *src) { AVFrame *map; int err; if (dst->width > hwfc->width || dst->height > hwfc->height) return AVERROR(EINVAL); map = av_frame_alloc(); if (!map) return AVERROR(ENOMEM); map->format = dst->format; err = vt_map_frame(hwfc, map, src, AV_HWFRAME_MAP_READ); if (err) goto fail; map->width = dst->width; map->height = dst->height; err = av_frame_copy(dst, map); if (err) goto fail; err = 0; fail: av_frame_free(&map); return err; } static int vt_transfer_data_to(AVHWFramesContext *hwfc, AVFrame *dst, const AVFrame *src) { AVFrame *map; int err; if (src->width > hwfc->width || src->height > hwfc->height) return AVERROR(EINVAL); map = av_frame_alloc(); if (!map) return AVERROR(ENOMEM); map->format = src->format; err = vt_map_frame(hwfc, map, dst, AV_HWFRAME_MAP_WRITE | AV_HWFRAME_MAP_OVERWRITE); if (err) goto fail; map->width = src->width; map->height = src->height; err = av_frame_copy(map, src); if (err) goto fail; err = 0; fail: av_frame_free(&map); return err; } static int vt_device_create(AVHWDeviceContext *ctx, const char *device, AVDictionary *opts, int flags) { if (device && device[0]) { av_log(ctx, AV_LOG_ERROR, "Device selection unsupported.\n"); return AVERROR_UNKNOWN; } return 0; } const HWContextType ff_hwcontext_type_videotoolbox = { .type = AV_HWDEVICE_TYPE_VIDEOTOOLBOX, .name = "videotoolbox", .device_create = vt_device_create, .frames_get_buffer = vt_get_buffer, .transfer_get_formats = vt_transfer_get_formats, .transfer_data_to = vt_transfer_data_to, .transfer_data_from = vt_transfer_data_from, .pix_fmts = (const enum AVPixelFormat[]){ AV_PIX_FMT_VIDEOTOOLBOX, AV_PIX_FMT_NONE }, };