| @@ -0,0 +1,406 @@ |
| | 1 | /* |
| | 2 | * Copyright (C) 2018 Philip Langdale <philipl@overt.org> |
| | 3 | * 2020 Aman Karmani <aman@tmm1.net> |
| | 4 | * |
| | 5 | * This file is part of FFmpeg. |
| | 6 | * |
| | 7 | * FFmpeg is free software; you can redistribute it and/or |
| | 8 | * modify it under the terms of the GNU Lesser General Public |
| | 9 | * License as published by the Free Software Foundation; either |
| | 10 | * version 2.1 of the License, or (at your option) any later version. |
| | 11 | * |
| | 12 | * FFmpeg is distributed in the hope that it will be useful, |
| | 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| | 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| | 15 | * Lesser General Public License for more details. |
| | 16 | * |
| | 17 | * You should have received a copy of the GNU Lesser General Public |
| | 18 | * License along with FFmpeg; if not, write to the Free Software |
| | 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| | 20 | */ |
| | 21 | |
| | 22 | #include "internal.h" |
| | 23 | #include "yadif.h" |
| | 24 | #include <libavutil/avassert.h> |
| | 25 | #include <libavutil/hwcontext.h> |
| | 26 | #include <libavutil/objc.h> |
| | 27 | #include <libavfilter/metal/utils.h> |
| | 28 | |
| | 29 | extern char ff_vf_yadif_videotoolbox_metallib_data[]; |
| | 30 | extern unsigned int ff_vf_yadif_videotoolbox_metallib_len; |
| | 31 | |
| | 32 | typedef struct YADIFVTContext { |
| | 33 | YADIFContext yadif; |
| | 34 | |
| | 35 | AVBufferRef *device_ref; |
| | 36 | AVBufferRef *input_frames_ref; |
| | 37 | AVHWFramesContext *input_frames; |
| | 38 | |
| | 39 | id<MTLDevice> mtlDevice; |
| | 40 | id<MTLLibrary> mtlLibrary; |
| | 41 | id<MTLCommandQueue> mtlQueue; |
| | 42 | id<MTLComputePipelineState> mtlPipeline; |
| | 43 | id<MTLFunction> mtlFunction; |
| | 44 | id<MTLBuffer> mtlParamsBuffer; |
| | 45 | |
| | 46 | CVMetalTextureCacheRef textureCache; |
| | 47 | } YADIFVTContext; |
| | 48 | |
| | 49 | struct mtlYadifParams { |
| | 50 | uint channels; |
| | 51 | uint parity; |
| | 52 | uint tff; |
| | 53 | bool is_second_field; |
| | 54 | bool skip_spatial_check; |
| | 55 | int field_mode; |
| | 56 | }; |
| | 57 | |
| | 58 | static void call_kernel(AVFilterContext *ctx, |
| | 59 | id<MTLTexture> dst, |
| | 60 | id<MTLTexture> prev, |
| | 61 | id<MTLTexture> cur, |
| | 62 | id<MTLTexture> next, |
| | 63 | int channels, |
| | 64 | int parity, |
| | 65 | int tff) |
| | 66 | { |
| | 67 | YADIFVTContext *s = ctx->priv; |
| | 68 | id<MTLCommandBuffer> buffer = s->mtlQueue.commandBuffer; |
| | 69 | id<MTLComputeCommandEncoder> encoder = buffer.computeCommandEncoder; |
| | 70 | struct mtlYadifParams *params = (struct mtlYadifParams *)s->mtlParamsBuffer.contents; |
| | 71 | *params = (struct mtlYadifParams){ |
| | 72 | .channels = channels, |
| | 73 | .parity = parity, |
| | 74 | .tff = tff, |
| | 75 | .is_second_field = !(parity ^ tff), |
| | 76 | .skip_spatial_check = s->yadif.mode&2, |
| | 77 | .field_mode = s->yadif.current_field |
| | 78 | }; |
| | 79 | |
| | 80 | [encoder setTexture:dst atIndex:0]; |
| | 81 | [encoder setTexture:prev atIndex:1]; |
| | 82 | [encoder setTexture:cur atIndex:2]; |
| | 83 | [encoder setTexture:next atIndex:3]; |
| | 84 | [encoder setBuffer:s->mtlParamsBuffer offset:0 atIndex:4]; |
| | 85 | ff_metal_compute_encoder_dispatch(s->mtlDevice, s->mtlPipeline, encoder, dst.width, dst.height); |
| | 86 | [encoder endEncoding]; |
| | 87 | |
| | 88 | [buffer commit]; |
| | 89 | [buffer waitUntilCompleted]; |
| | 90 | |
| | 91 | ff_objc_release(&encoder); |
| | 92 | ff_objc_release(&buffer); |
| | 93 | } |
| | 94 | |
| | 95 | static void filter(AVFilterContext *ctx, AVFrame *dst, |
| | 96 | int parity, int tff) |
| | 97 | { |
| | 98 | YADIFVTContext *s = ctx->priv; |
| | 99 | YADIFContext *y = &s->yadif; |
| | 100 | int i; |
| | 101 | |
| | 102 | for (i = 0; i < y->csp->nb_components; i++) { |
| | 103 | int pixel_size, channels; |
| | 104 | const AVComponentDescriptor *comp = &y->csp->comp[i]; |
| | 105 | CVMetalTextureRef prev, cur, next, dest; |
| | 106 | id<MTLTexture> tex_prev, tex_cur, tex_next, tex_dest; |
| | 107 | MTLPixelFormat format; |
| | 108 | |
| | 109 | if (comp->plane < i) { |
| | 110 | // We process planes as a whole, so don't reprocess |
| | 111 | // them for additional components |
| | 112 | continue; |
| | 113 | } |
| | 114 | |
| | 115 | pixel_size = (comp->depth + comp->shift) / 8; |
| | 116 | channels = comp->step / pixel_size; |
| | 117 | if (pixel_size > 2 || channels > 2) { |
| | 118 | av_log(ctx, AV_LOG_ERROR, "Unsupported pixel format: %s\n", y->csp->name); |
| | 119 | goto exit; |
| | 120 | } |
| | 121 | switch (pixel_size) { |
| | 122 | case 1: |
| | 123 | format = channels == 1 ? MTLPixelFormatR8Unorm : MTLPixelFormatRG8Unorm; |
| | 124 | break; |
| | 125 | case 2: |
| | 126 | format = channels == 1 ? MTLPixelFormatR16Unorm : MTLPixelFormatRG16Unorm; |
| | 127 | break; |
| | 128 | default: |
| | 129 | av_log(ctx, AV_LOG_ERROR, "Unsupported pixel format: %s\n", y->csp->name); |
| | 130 | goto exit; |
| | 131 | } |
| | 132 | av_log(ctx, AV_LOG_TRACE, |
| | 133 | "Deinterlacing plane %d: pixel_size: %d channels: %d\n", |
| | 134 | comp->plane, pixel_size, channels); |
| | 135 | |
| | 136 | prev = ff_metal_texture_from_pixbuf(ctx, s->textureCache, (CVPixelBufferRef)y->prev->data[3], i, format); |
| | 137 | cur = ff_metal_texture_from_pixbuf(ctx, s->textureCache, (CVPixelBufferRef)y->cur->data[3], i, format); |
| | 138 | next = ff_metal_texture_from_pixbuf(ctx, s->textureCache, (CVPixelBufferRef)y->next->data[3], i, format); |
| | 139 | dest = ff_metal_texture_from_pixbuf(ctx, s->textureCache, (CVPixelBufferRef)dst->data[3], i, format); |
| | 140 | |
| | 141 | tex_prev = CVMetalTextureGetTexture(prev); |
| | 142 | tex_cur = CVMetalTextureGetTexture(cur); |
| | 143 | tex_next = CVMetalTextureGetTexture(next); |
| | 144 | tex_dest = CVMetalTextureGetTexture(dest); |
| | 145 | |
| | 146 | call_kernel(ctx, tex_dest, tex_prev, tex_cur, tex_next, |
| | 147 | channels, parity, tff); |
| | 148 | |
| | 149 | CFRelease(prev); |
| | 150 | CFRelease(cur); |
| | 151 | CFRelease(next); |
| | 152 | CFRelease(dest); |
| | 153 | } |
| | 154 | |
| | 155 | CVBufferPropagateAttachments((CVPixelBufferRef)y->cur->data[3], (CVPixelBufferRef)dst->data[3]); |
| | 156 | |
| | 157 | if (y->current_field == YADIF_FIELD_END) { |
| | 158 | y->current_field = YADIF_FIELD_NORMAL; |
| | 159 | } |
| | 160 | |
| | 161 | exit: |
| | 162 | return; |
| | 163 | } |
| | 164 | |
| | 165 | static av_cold void yadif_videotoolbox_uninit(AVFilterContext *ctx) |
| | 166 | { |
| | 167 | YADIFVTContext *s = ctx->priv; |
| | 168 | YADIFContext *y = &s->yadif; |
| | 169 | |
| | 170 | av_frame_free(&y->prev); |
| | 171 | av_frame_free(&y->cur); |
| | 172 | av_frame_free(&y->next); |
| | 173 | |
| | 174 | av_buffer_unref(&s->device_ref); |
| | 175 | av_buffer_unref(&s->input_frames_ref); |
| | 176 | s->input_frames = NULL; |
| | 177 | |
| | 178 | ff_objc_release(&s->mtlParamsBuffer); |
| | 179 | ff_objc_release(&s->mtlFunction); |
| | 180 | ff_objc_release(&s->mtlPipeline); |
| | 181 | ff_objc_release(&s->mtlQueue); |
| | 182 | ff_objc_release(&s->mtlLibrary); |
| | 183 | ff_objc_release(&s->mtlDevice); |
| | 184 | |
| | 185 | if (s->textureCache) { |
| | 186 | CFRelease(s->textureCache); |
| | 187 | s->textureCache = NULL; |
| | 188 | } |
| | 189 | } |
| | 190 | |
| | 191 | static av_cold int yadif_videotoolbox_init(AVFilterContext *ctx) |
| | 192 | { |
| | 193 | YADIFVTContext *s = ctx->priv; |
| | 194 | NSError *err = nil; |
| | 195 | CVReturn ret; |
| | 196 | |
| | 197 | s->mtlDevice = MTLCreateSystemDefaultDevice(); |
| | 198 | if (!s->mtlDevice) { |
| | 199 | av_log(ctx, AV_LOG_ERROR, "Unable to find Metal device\n"); |
| | 200 | goto fail; |
| | 201 | } |
| | 202 | |
| | 203 | av_log(ctx, AV_LOG_INFO, "Using Metal device: %s\n", s->mtlDevice.name.UTF8String); |
| | 204 | |
| | 205 | dispatch_data_t libData = dispatch_data_create( |
| | 206 | ff_vf_yadif_videotoolbox_metallib_data, |
| | 207 | ff_vf_yadif_videotoolbox_metallib_len, |
| | 208 | nil, |
| | 209 | nil); |
| | 210 | s->mtlLibrary = [s->mtlDevice newLibraryWithData:libData error:&err]; |
| | 211 | dispatch_release(libData); |
| | 212 | libData = nil; |
| | 213 | if (err) { |
| | 214 | av_log(ctx, AV_LOG_ERROR, "Failed to load Metal library: %s\n", err.description.UTF8String); |
| | 215 | goto fail; |
| | 216 | } |
| | 217 | |
| | 218 | s->mtlFunction = [s->mtlLibrary newFunctionWithName:@"deint"]; |
| | 219 | if (!s->mtlFunction) { |
| | 220 | av_log(ctx, AV_LOG_ERROR, "Failed to create Metal function!\n"); |
| | 221 | goto fail; |
| | 222 | } |
| | 223 | |
| | 224 | s->mtlQueue = s->mtlDevice.newCommandQueue; |
| | 225 | if (!s->mtlQueue) { |
| | 226 | av_log(ctx, AV_LOG_ERROR, "Failed to create Metal command queue!\n"); |
| | 227 | goto fail; |
| | 228 | } |
| | 229 | |
| | 230 | s->mtlPipeline = [s->mtlDevice |
| | 231 | newComputePipelineStateWithFunction:s->mtlFunction |
| | 232 | error:&err]; |
| | 233 | if (err) { |
| | 234 | av_log(ctx, AV_LOG_ERROR, "Failed to create Metal compute pipeline: %s\n", err.description.UTF8String); |
| | 235 | goto fail; |
| | 236 | } |
| | 237 | |
| | 238 | s->mtlParamsBuffer = [s->mtlDevice |
| | 239 | newBufferWithLength:sizeof(struct mtlYadifParams) |
| | 240 | options:MTLResourceStorageModeShared]; |
| | 241 | if (!s->mtlParamsBuffer) { |
| | 242 | av_log(ctx, AV_LOG_ERROR, "Failed to create Metal buffer for parameters\n"); |
| | 243 | goto fail; |
| | 244 | } |
| | 245 | |
| | 246 | ret = CVMetalTextureCacheCreate( |
| | 247 | NULL, |
| | 248 | NULL, |
| | 249 | s->mtlDevice, |
| | 250 | NULL, |
| | 251 | &s->textureCache |
| | 252 | ); |
| | 253 | if (ret != kCVReturnSuccess) { |
| | 254 | av_log(ctx, AV_LOG_ERROR, "Failed to create CVMetalTextureCache: %d\n", ret); |
| | 255 | goto fail; |
| | 256 | } |
| | 257 | |
| | 258 | return 0; |
| | 259 | fail: |
| | 260 | yadif_videotoolbox_uninit(ctx); |
| | 261 | return AVERROR_EXTERNAL; |
| | 262 | } |
| | 263 | |
| | 264 | static int config_input(AVFilterLink *inlink) |
| | 265 | { |
| | 266 | AVFilterContext *ctx = inlink->dst; |
| | 267 | YADIFVTContext *s = ctx->priv; |
| | 268 | |
| | 269 | if (!inlink->hw_frames_ctx) { |
| | 270 | av_log(ctx, AV_LOG_ERROR, "A hardware frames reference is " |
| | 271 | "required to associate the processing device.\n"); |
| | 272 | return AVERROR(EINVAL); |
| | 273 | } |
| | 274 | |
| | 275 | s->input_frames_ref = av_buffer_ref(inlink->hw_frames_ctx); |
| | 276 | if (!s->input_frames_ref) { |
| | 277 | av_log(ctx, AV_LOG_ERROR, "A input frames reference create " |
| | 278 | "failed.\n"); |
| | 279 | return AVERROR(ENOMEM); |
| | 280 | } |
| | 281 | s->input_frames = (AVHWFramesContext*)s->input_frames_ref->data; |
| | 282 | |
| | 283 | return 0; |
| | 284 | } |
| | 285 | |
| | 286 | static int config_output(AVFilterLink *link) |
| | 287 | { |
| | 288 | AVHWFramesContext *output_frames; |
| | 289 | AVFilterContext *ctx = link->src; |
| | 290 | YADIFVTContext *s = ctx->priv; |
| | 291 | YADIFContext *y = &s->yadif; |
| | 292 | int ret = 0; |
| | 293 | |
| | 294 | av_assert0(s->input_frames); |
| | 295 | s->device_ref = av_buffer_ref(s->input_frames->device_ref); |
| | 296 | if (!s->device_ref) { |
| | 297 | av_log(ctx, AV_LOG_ERROR, "A device reference create " |
| | 298 | "failed.\n"); |
| | 299 | return AVERROR(ENOMEM); |
| | 300 | } |
| | 301 | |
| | 302 | link->hw_frames_ctx = av_hwframe_ctx_alloc(s->device_ref); |
| | 303 | if (!link->hw_frames_ctx) { |
| | 304 | av_log(ctx, AV_LOG_ERROR, "Failed to create HW frame context " |
| | 305 | "for output.\n"); |
| | 306 | ret = AVERROR(ENOMEM); |
| | 307 | goto exit; |
| | 308 | } |
| | 309 | |
| | 310 | output_frames = (AVHWFramesContext*)link->hw_frames_ctx->data; |
| | 311 | |
| | 312 | output_frames->format = AV_PIX_FMT_VIDEOTOOLBOX; |
| | 313 | output_frames->sw_format = s->input_frames->sw_format; |
| | 314 | output_frames->width = ctx->inputs[0]->w; |
| | 315 | output_frames->height = ctx->inputs[0]->h; |
| | 316 | |
| | 317 | ret = ff_filter_init_hw_frames(ctx, link, 10); |
| | 318 | if (ret < 0) |
| | 319 | goto exit; |
| | 320 | |
| | 321 | ret = av_hwframe_ctx_init(link->hw_frames_ctx); |
| | 322 | if (ret < 0) { |
| | 323 | av_log(ctx, AV_LOG_ERROR, "Failed to initialise VideoToolbox frame " |
| | 324 | "context for output: %d\n", ret); |
| | 325 | goto exit; |
| | 326 | } |
| | 327 | |
| | 328 | link->time_base.num = ctx->inputs[0]->time_base.num; |
| | 329 | link->time_base.den = ctx->inputs[0]->time_base.den * 2; |
| | 330 | link->w = ctx->inputs[0]->w; |
| | 331 | link->h = ctx->inputs[0]->h; |
| | 332 | |
| | 333 | if(y->mode & 1) |
| | 334 | link->frame_rate = av_mul_q(ctx->inputs[0]->frame_rate, |
| | 335 | (AVRational){2, 1}); |
| | 336 | |
| | 337 | if (link->w < 3 || link->h < 3) { |
| | 338 | av_log(ctx, AV_LOG_ERROR, "Video of less than 3 columns or lines is not supported\n"); |
| | 339 | ret = AVERROR(EINVAL); |
| | 340 | goto exit; |
| | 341 | } |
| | 342 | |
| | 343 | y->csp = av_pix_fmt_desc_get(output_frames->sw_format); |
| | 344 | y->filter = filter; |
| | 345 | |
| | 346 | exit: |
| | 347 | return ret; |
| | 348 | } |
| | 349 | |
| | 350 | #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM |
| | 351 | #define CONST(name, help, val, unit) { name, help, 0, AV_OPT_TYPE_CONST, {.i64=val}, INT_MIN, INT_MAX, FLAGS, unit } |
| | 352 | |
| | 353 | static const AVOption yadif_videotoolbox_options[] = { |
| | 354 | #define OFFSET(x) offsetof(YADIFContext, x) |
| | 355 | { "mode", "specify the interlacing mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=YADIF_MODE_SEND_FRAME}, 0, 3, FLAGS, "mode"}, |
| | 356 | CONST("send_frame", "send one frame for each frame", YADIF_MODE_SEND_FRAME, "mode"), |
| | 357 | CONST("send_field", "send one frame for each field", YADIF_MODE_SEND_FIELD, "mode"), |
| | 358 | CONST("send_frame_nospatial", "send one frame for each frame, but skip spatial interlacing check", YADIF_MODE_SEND_FRAME_NOSPATIAL, "mode"), |
| | 359 | CONST("send_field_nospatial", "send one frame for each field, but skip spatial interlacing check", YADIF_MODE_SEND_FIELD_NOSPATIAL, "mode"), |
| | 360 | |
| | 361 | { "parity", "specify the assumed picture field parity", OFFSET(parity), AV_OPT_TYPE_INT, {.i64=YADIF_PARITY_AUTO}, -1, 1, FLAGS, "parity" }, |
| | 362 | CONST("tff", "assume top field first", YADIF_PARITY_TFF, "parity"), |
| | 363 | CONST("bff", "assume bottom field first", YADIF_PARITY_BFF, "parity"), |
| | 364 | CONST("auto", "auto detect parity", YADIF_PARITY_AUTO, "parity"), |
| | 365 | |
| | 366 | { "deint", "specify which frames to deinterlace", OFFSET(deint), AV_OPT_TYPE_INT, {.i64=YADIF_DEINT_ALL}, 0, 1, FLAGS, "deint" }, |
| | 367 | CONST("all", "deinterlace all frames", YADIF_DEINT_ALL, "deint"), |
| | 368 | CONST("interlaced", "only deinterlace frames marked as interlaced", YADIF_DEINT_INTERLACED, "deint"), |
| | 369 | #undef OFFSET |
| | 370 | |
| | 371 | { NULL } |
| | 372 | }; |
| | 373 | |
| | 374 | AVFILTER_DEFINE_CLASS(yadif_videotoolbox); |
| | 375 | |
| | 376 | static const AVFilterPad yadif_videotoolbox_inputs[] = { |
| | 377 | { |
| | 378 | .name = "default", |
| | 379 | .type = AVMEDIA_TYPE_VIDEO, |
| | 380 | .filter_frame = ff_yadif_filter_frame, |
| | 381 | .config_props = config_input, |
| | 382 | }, |
| | 383 | }; |
| | 384 | |
| | 385 | static const AVFilterPad yadif_videotoolbox_outputs[] = { |
| | 386 | { |
| | 387 | .name = "default", |
| | 388 | .type = AVMEDIA_TYPE_VIDEO, |
| | 389 | .request_frame = ff_yadif_request_frame, |
| | 390 | .config_props = config_output, |
| | 391 | }, |
| | 392 | }; |
| | 393 | |
| | 394 | AVFilter ff_vf_yadif_videotoolbox = { |
| | 395 | .name = "yadif_videotoolbox", |
| | 396 | .description = NULL_IF_CONFIG_SMALL("YADIF for VideoToolbox frames using Metal compute"), |
| | 397 | .priv_size = sizeof(YADIFVTContext), |
| | 398 | .priv_class = &yadif_videotoolbox_class, |
| | 399 | .init = yadif_videotoolbox_init, |
| | 400 | .uninit = yadif_videotoolbox_uninit, |
| | 401 | FILTER_SINGLE_PIXFMT(AV_PIX_FMT_VIDEOTOOLBOX), |
| | 402 | FILTER_INPUTS(yadif_videotoolbox_inputs), |
| | 403 | FILTER_OUTPUTS(yadif_videotoolbox_outputs), |
| | 404 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL, |
| | 405 | .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE, |
| | 406 | }; |