FFmpeg
Loading...
Searching...
No Matches
dnn_backend_onnx.c
Go to the documentation of this file.
1/*
2 * Copyright (c) 2026 Advanced Micro Devices, Inc.
3 *
4 * This file is part of FFmpeg.
5 *
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21/**
22 * @file
23 * DNN ONNX Runtime backend implementation.
24 */
25
26#include "libavutil/opt.h"
27#include "libavutil/avassert.h"
28#include "libavutil/mem.h"
29#include "libavutil/avstring.h"
30#include "libavutil/thread.h"
32#include "../filters.h"
33#include "dnn_io_proc.h"
34#include "dnn_backend_common.h"
35#include "queue.h"
36#include "safe_queue.h"
37#include <onnxruntime_c_api.h>
38#include <inttypes.h>
39#include <stdio.h>
40#include <string.h>
41
56
57typedef struct ONNXInferRequest {
58 OrtValue *input_tensor;
59 OrtValue *output_tensor;
62
68
69#define OFFSET(x) offsetof(ONNXOptions, x)
70#define FLAGS AV_OPT_FLAG_FILTERING_PARAM
71static const AVOption dnn_onnx_options[] = {
72 { "threads_per_operation", "number of CPU threads per ORT operator (device=cpu only)",
73 OFFSET(num_threads), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS },
74 { NULL }
75};
76
78
79static const OrtApi *g_ort = NULL;
81
82static void init_ort_api(void)
83{
84 g_ort = OrtGetApiBase()->GetApi(ORT_API_VERSION);
85}
86
87#define ORT_ABORT_ON_ERROR(expr) \
88 do { \
89 OrtStatus *status = (expr); \
90 if (status != NULL) { \
91 const char *msg = g_ort->GetErrorMessage(status); \
92 av_log(ctx, AV_LOG_ERROR, "ONNX Runtime error: %s\n", msg); \
93 g_ort->ReleaseStatus(status); \
94 goto err; \
95 } \
96 } while (0)
97
98static int extract_lltask_from_task(TaskItem *task, Queue *lltask_queue)
99{
100 ONNXModel *onnx_model = (ONNXModel *)task->model;
101 DnnContext *ctx = onnx_model->ctx;
102 LastLevelTaskItem *lltask = av_malloc(sizeof(*lltask));
103
104 if (!lltask) {
105 av_log(ctx, AV_LOG_ERROR, "Failed to allocate memory for LastLevelTaskItem\n");
106 return AVERROR(ENOMEM);
107 }
108 task->inference_todo = 1;
109 task->inference_done = 0;
110 lltask->task = task;
111 if (ff_queue_push_back(lltask_queue, lltask) < 0) {
112 av_log(ctx, AV_LOG_ERROR, "Failed to push back lltask_queue.\n");
113 av_freep(&lltask);
114 return AVERROR(ENOMEM);
115 }
116 return 0;
117}
118
120{
121 if (!request)
122 return;
123 if (request->input_tensor) {
124 g_ort->ReleaseValue(request->input_tensor);
125 request->input_tensor = NULL;
126 }
127 av_freep(&request->input_data);
128 if (request->output_tensor) {
129 g_ort->ReleaseValue(request->output_tensor);
130 request->output_tensor = NULL;
131 }
132}
133
135{
136 ONNXRequestItem *item;
137 if (!arg || !*arg)
138 return;
139 item = *arg;
141 av_freep(&item->infer_request);
142 av_freep(&item->lltask);
144 av_freep(arg);
145}
146
147static void dnn_free_model_onnx(DNNModel **model)
148{
149 ONNXModel *onnx_model;
150 if (!model || !*model)
151 return;
152
153 onnx_model = (ONNXModel *)(*model);
154
155 ff_dnn_wait_requests(onnx_model->request_queue, onnx_model->ctx->nireq);
156 while (ff_safe_queue_size(onnx_model->request_queue) != 0) {
159 }
161
162 while (ff_queue_size(onnx_model->lltask_queue) != 0) {
164 av_freep(&item);
165 }
166 ff_queue_destroy(onnx_model->lltask_queue);
167
168 while (ff_queue_size(onnx_model->task_queue) != 0) {
169 TaskItem *item = (TaskItem *)ff_queue_pop_front(onnx_model->task_queue);
170 av_frame_free(&item->in_frame);
171 av_frame_free(&item->out_frame);
172 av_freep(&item);
173 }
174 ff_queue_destroy(onnx_model->task_queue);
175
176 if (onnx_model->session)
177 g_ort->ReleaseSession(onnx_model->session);
178 if (onnx_model->session_options)
179 g_ort->ReleaseSessionOptions(onnx_model->session_options);
180 if (onnx_model->env)
181 g_ort->ReleaseEnv(onnx_model->env);
182
183 av_freep(&onnx_model);
184 *model = NULL;
185}
186
187static int get_input_onnx(DNNModel *model, DNNData *input, const char *input_name)
188{
189 ONNXModel *onnx_model = (ONNXModel *)model;
190 DnnContext *ctx = onnx_model->ctx;
191 OrtTypeInfo *type_info = NULL;
192 const OrtTensorTypeAndShapeInfo *tensor_info = NULL;
193 size_t num_dims;
194 size_t input_count = 0;
195 size_t input_index = 0;
196 int found_input = 0;
197 int64_t *dims;
198 ONNXTensorElementDataType tensor_type;
199 OrtStatus *status;
200
201 if (!input_name || !*input_name) {
202 av_log(ctx, AV_LOG_ERROR, "ONNX input name is not specified\n");
203 return AVERROR(EINVAL);
204 }
205
206 if (onnx_model->input_resolved) {
207 *input = onnx_model->input_info;
208 return 0;
209 }
210
211 status = g_ort->SessionGetInputCount(onnx_model->session, &input_count);
212 if (status != NULL) {
213 const char *msg = g_ort->GetErrorMessage(status);
214 av_log(ctx, AV_LOG_ERROR, "Failed to get input count: %s\n", msg);
215 g_ort->ReleaseStatus(status);
216 return AVERROR(EINVAL);
217 }
218
219 for (size_t i = 0; i < input_count; i++) {
220 char *name = NULL;
221 status = g_ort->SessionGetInputName(onnx_model->session, i,
222 onnx_model->allocator, &name);
223 if (status != NULL) {
224 g_ort->ReleaseStatus(status);
225 continue;
226 }
227 if (!strcmp(name, input_name)) {
228 input_index = i;
229 found_input = 1;
230 }
231 onnx_model->allocator->Free(onnx_model->allocator, name);
232 if (found_input)
233 break;
234 }
235
236 if (!found_input) {
237 av_log(ctx, AV_LOG_ERROR, "Input name '%s' not found in ONNX model\n",
238 input_name);
239 return AVERROR(EINVAL);
240 }
241
242 status = g_ort->SessionGetInputTypeInfo(onnx_model->session, input_index,
243 &type_info);
244 if (status != NULL) {
245 const char *msg = g_ort->GetErrorMessage(status);
246 av_log(ctx, AV_LOG_ERROR, "Failed to get input type info: %s\n", msg);
247 g_ort->ReleaseStatus(status);
248 return AVERROR(EINVAL);
249 }
250
251 status = g_ort->CastTypeInfoToTensorInfo(type_info, &tensor_info);
252 if (status != NULL) {
253 g_ort->ReleaseTypeInfo(type_info);
254 g_ort->ReleaseStatus(status);
255 return AVERROR(EINVAL);
256 }
257
258 status = g_ort->GetDimensionsCount(tensor_info, &num_dims);
259 if (status != NULL) {
260 g_ort->ReleaseTypeInfo(type_info);
261 g_ort->ReleaseStatus(status);
262 return AVERROR(EINVAL);
263 }
264
265 if (num_dims != 4) {
266 avpriv_report_missing_feature(ctx, "Support for %zu dimensional input", num_dims);
267 g_ort->ReleaseTypeInfo(type_info);
268 return AVERROR(ENOSYS);
269 }
270
271 dims = av_malloc(num_dims * sizeof(int64_t));
272 if (!dims) {
273 g_ort->ReleaseTypeInfo(type_info);
274 return AVERROR(ENOMEM);
275 }
276
277 g_ort->GetDimensions(tensor_info, dims, num_dims);
278 g_ort->GetTensorElementType(tensor_info, &tensor_type);
279
280 if (dims[0] > 1) {
282 "ONNX model has fixed batch size %"PRId64", but the backend "
283 "only supports a batch size of 1\n", dims[0]);
284 av_free(dims);
285 g_ort->ReleaseTypeInfo(type_info);
286 return AVERROR(ENOSYS);
287 }
288
289 /*
290 * The ONNX backend assumes a 4-D NCHW input tensor (the rank check
291 * above already rejects anything else).
292 */
293 input->layout = DL_NCHW;
294 input->dims[0] = dims[0] > 0 ? dims[0] : 1;
295 input->dims[1] = dims[1] > 0 ? dims[1] : 3;
296 input->dims[2] = dims[2] > 0 ? dims[2] : -1;
297 input->dims[3] = dims[3] > 0 ? dims[3] : -1;
298
299 if (tensor_type == ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT) {
300 input->dt = DNN_FLOAT;
301 } else {
302 av_log(ctx, AV_LOG_ERROR, "Unsupported input tensor data type, only float is supported\n");
303 av_free(dims);
304 g_ort->ReleaseTypeInfo(type_info);
305 return AVERROR(ENOSYS);
306 }
307
308 /*
309 * The DCO_RGB setting below is only consulted by the dnn_detect and dnn_classify;
310 * the dnn_processing path lets the source AVFrame pixel format determine the
311 * tensor channel order, so both RGB24 and BGR24 inputs work transparently
312 * for that flow.
313 */
314 input->order = DCO_RGB;
315 av_free(dims);
316 g_ort->ReleaseTypeInfo(type_info);
317
318 onnx_model->input_info = *input;
319 onnx_model->input_resolved = 1;
320 return 0;
321}
322
323static int fill_model_input_onnx(ONNXModel *onnx_model, ONNXRequestItem *request)
324{
325 LastLevelTaskItem *lltask = NULL;
326 TaskItem *task = NULL;
327 ONNXInferRequest *infer_request = NULL;
328 DNNData input = { 0 };
329 DnnContext *ctx = onnx_model->ctx;
330 int ret, width_idx, height_idx, channel_idx;
331 int64_t input_shape[4];
332 size_t input_tensor_size;
333 OrtMemoryInfo *memory_info;
334 OrtStatus *status;
335
336 lltask = (LastLevelTaskItem *)ff_queue_pop_front(onnx_model->lltask_queue);
337 if (!lltask) {
338 ret = AVERROR(EINVAL);
339 goto err;
340 }
341 request->lltask = lltask;
342 task = lltask->task;
343 infer_request = request->infer_request;
344
345 ret = get_input_onnx(&onnx_model->model, &input, task->input_name);
346 if (ret != 0) {
347 goto err;
348 }
349
350 width_idx = dnn_get_width_idx_by_layout(input.layout);
351 height_idx = dnn_get_height_idx_by_layout(input.layout);
352 channel_idx = dnn_get_channel_idx_by_layout(input.layout);
353
354 input.dims[height_idx] = task->in_frame->height;
355 input.dims[width_idx] = task->in_frame->width;
356
357 input_shape[0] = input.dims[0];
358 input_shape[1] = input.dims[channel_idx];
359 input_shape[2] = input.dims[height_idx];
360 input_shape[3] = input.dims[width_idx];
361
362 input_tensor_size = input_shape[0] * input_shape[1] * input_shape[2] * input_shape[3];
363 input_tensor_size *= sizeof(float);
364
365 input.data = av_malloc(input_tensor_size);
366 if (!input.data) {
367 ret = AVERROR(ENOMEM);
368 goto err;
369 }
370 infer_request->input_data = input.data;
371
372 switch (onnx_model->model.func_type) {
374 input.scale = 255;
375 if (task->do_ioproc) {
376 if (onnx_model->model.frame_pre_proc != NULL) {
377 onnx_model->model.frame_pre_proc(task->in_frame, &input, onnx_model->model.filter_ctx);
378 } else {
379 ff_proc_from_frame_to_dnn(task->in_frame, &input, ctx);
380 }
381 }
382 break;
384 ff_frame_to_dnn_detect(task->in_frame, &input, ctx);
385 break;
386 default:
387 avpriv_report_missing_feature(ctx, "model function type %d", onnx_model->model.func_type);
388 ret = AVERROR(ENOSYS);
389 goto err;
390 }
391
392 status = g_ort->CreateCpuMemoryInfo(OrtArenaAllocator, OrtMemTypeDefault, &memory_info);
393 if (status != NULL) {
394 ret = AVERROR(ENOMEM);
395 goto err;
396 }
397
398 status = g_ort->CreateTensorWithDataAsOrtValue(
399 memory_info, input.data, input_tensor_size,
400 input_shape, 4, ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT,
401 &infer_request->input_tensor);
402
403 g_ort->ReleaseMemoryInfo(memory_info);
404
405 if (status != NULL) {
406 const char *msg = g_ort->GetErrorMessage(status);
407 av_log(ctx, AV_LOG_ERROR, "Failed to create input tensor: %s\n", msg);
408 g_ort->ReleaseStatus(status);
409 ret = AVERROR(ENOMEM);
410 goto err;
411 }
412
413 return 0;
414
415err:
416 onnx_free_request(infer_request);
417 return ret;
418}
419
420static int onnx_start_inference(void *args)
421{
422 ONNXRequestItem *request = (ONNXRequestItem *)args;
423 ONNXInferRequest *infer_request = NULL;
424 LastLevelTaskItem *lltask = NULL;
425 TaskItem *task = NULL;
426 ONNXModel *onnx_model = NULL;
428 OrtStatus *status;
429 const char *input_names[1];
430 const char *output_names[1];
431
432 if (!request) {
433 av_log(NULL, AV_LOG_ERROR, "ONNXRequestItem is NULL\n");
434 return AVERROR(EINVAL);
435 }
436
437 infer_request = request->infer_request;
438 lltask = request->lltask;
439 task = lltask->task;
440 onnx_model = (ONNXModel *)task->model;
441 ctx = onnx_model->ctx;
442
443 if (task->nb_output > 1) {
445 "Multiple output tensors (%u) for ONNX backend", task->nb_output);
446 return AVERROR(ENOSYS);
447 }
448
449 if (!task->input_name || !task->output_names || !task->output_names[0]) {
451 "ONNX backend: input/output tensor name was not resolved at load time\n");
452 return AVERROR(EINVAL);
453 }
454
455 if (!infer_request->input_tensor) {
456 av_log(ctx, AV_LOG_ERROR, "Input tensor is NULL\n");
457 return DNN_GENERIC_ERROR;
458 }
459
460 if (!onnx_model->output_resolved) {
461 size_t output_count = 0;
462 int found_output = 0;
463
464 status = g_ort->SessionGetOutputCount(onnx_model->session, &output_count);
465 if (status != NULL) {
466 const char *msg = g_ort->GetErrorMessage(status);
467 av_log(ctx, AV_LOG_ERROR, "Failed to get output count: %s\n", msg);
468 g_ort->ReleaseStatus(status);
469 return AVERROR(EINVAL);
470 }
471
472 for (size_t i = 0; i < output_count; i++) {
473 char *name = NULL;
474 status = g_ort->SessionGetOutputName(onnx_model->session, i,
475 onnx_model->allocator, &name);
476 if (status != NULL) {
477 g_ort->ReleaseStatus(status);
478 continue;
479 }
480 if (!strcmp(name, task->output_names[0]))
481 found_output = 1;
482 onnx_model->allocator->Free(onnx_model->allocator, name);
483 if (found_output)
484 break;
485 }
486
487 if (!found_output) {
489 "Output name '%s' not found in ONNX model\n",
490 task->output_names[0]);
491 return AVERROR(EINVAL);
492 }
493
494 onnx_model->output_resolved = 1;
495 }
496
497 input_names[0] = task->input_name;
498 output_names[0] = task->output_names[0];
499
500 status = g_ort->Run(onnx_model->session, NULL,
501 input_names, (const OrtValue *const *)&infer_request->input_tensor, 1,
502 output_names, 1, &infer_request->output_tensor);
503
504 if (status != NULL) {
505 const char *msg = g_ort->GetErrorMessage(status);
506 av_log(ctx, AV_LOG_ERROR, "ONNX inference failed: %s\n", msg);
507 g_ort->ReleaseStatus(status);
508 return DNN_GENERIC_ERROR;
509 }
510
511 return 0;
512}
513
514static void infer_completion_callback(void *args)
515{
516 ONNXRequestItem *request = (ONNXRequestItem *)args;
517 LastLevelTaskItem *lltask = request->lltask;
518 TaskItem *task = lltask->task;
519 DNNData outputs = { 0 };
520 ONNXInferRequest *infer_request = request->infer_request;
521 ONNXModel *onnx_model = (ONNXModel *)task->model;
522 DnnContext *ctx = onnx_model->ctx;
523 OrtTensorTypeAndShapeInfo *tensor_info;
524 ONNXTensorElementDataType tensor_type;
525 size_t num_dims;
526 int64_t *dims;
527 void *output_data;
528 OrtStatus *status;
529
530 if (!infer_request->output_tensor) {
531 av_log(ctx, AV_LOG_ERROR, "Output tensor is NULL\n");
532 goto err;
533 }
534
535 status = g_ort->GetTensorTypeAndShape(infer_request->output_tensor, &tensor_info);
536 if (status != NULL) {
537 av_log(ctx, AV_LOG_ERROR, "Failed to get output tensor info\n");
538 g_ort->ReleaseStatus(status);
539 goto err;
540 }
541
542 g_ort->GetDimensionsCount(tensor_info, &num_dims);
543 dims = av_malloc(num_dims * sizeof(int64_t));
544 if (!dims) {
545 av_log(ctx, AV_LOG_ERROR, "Failed to allocate memory for dimensions\n");
546 g_ort->ReleaseTensorTypeAndShapeInfo(tensor_info);
547 goto err;
548 }
549 g_ort->GetDimensions(tensor_info, dims, num_dims);
550
551 /* Output is interpreted as NCHW, matching the input assumption. */
552 outputs.layout = DL_NCHW;
553 outputs.order = DCO_RGB;
554
555 g_ort->GetTensorElementType(tensor_info, &tensor_type);
556 if (tensor_type == ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT) {
557 outputs.dt = DNN_FLOAT;
558 } else {
559 av_log(ctx, AV_LOG_ERROR, "Unsupported output tensor data type, only float is supported\n");
560 av_free(dims);
561 g_ort->ReleaseTensorTypeAndShapeInfo(tensor_info);
562 goto err;
563 }
564
565 if (num_dims == 4) {
566 outputs.dims[0] = dims[0];
567 outputs.dims[1] = dims[1];
568 outputs.dims[2] = dims[2];
569 outputs.dims[3] = dims[3];
570 } else {
571 avpriv_report_missing_feature(ctx, "Support for %zu dimensional output", num_dims);
572 av_free(dims);
573 g_ort->ReleaseTensorTypeAndShapeInfo(tensor_info);
574 goto err;
575 }
576
577 status = g_ort->GetTensorMutableData(infer_request->output_tensor, &output_data);
578 if (status != NULL) {
579 av_log(ctx, AV_LOG_ERROR, "Failed to get tensor data\n");
580 g_ort->ReleaseStatus(status);
581 av_free(dims);
582 g_ort->ReleaseTensorTypeAndShapeInfo(tensor_info);
583 goto err;
584 }
585
586 outputs.data = output_data;
587
588 switch (onnx_model->model.func_type) {
590 if (task->do_ioproc) {
591 outputs.scale = 255;
592 if (onnx_model->model.frame_post_proc != NULL) {
593 onnx_model->model.frame_post_proc(task->out_frame, &outputs, onnx_model->model.filter_ctx);
594 } else {
596 }
597 } else {
600 }
601 break;
602 default:
603 avpriv_report_missing_feature(ctx, "model function type %d", onnx_model->model.func_type);
604 av_free(dims);
605 g_ort->ReleaseTensorTypeAndShapeInfo(tensor_info);
606 goto err;
607 }
608
609 av_free(dims);
610 g_ort->ReleaseTensorTypeAndShapeInfo(tensor_info);
611 task->inference_done++;
612
613err:
614 av_freep(&request->lltask);
615 onnx_free_request(infer_request);
616 if (ff_safe_queue_push_back(onnx_model->request_queue, request) < 0) {
617 destroy_request_item(&request);
618 av_log(ctx, AV_LOG_ERROR, "Unable to push back request_queue.\n");
619 }
620}
621
622static int execute_model_onnx(ONNXRequestItem *request, Queue *lltask_queue)
623{
624 ONNXModel *onnx_model = NULL;
625 LastLevelTaskItem *lltask;
626 TaskItem *task = NULL;
627 int ret = 0;
628
629 if (ff_queue_size(lltask_queue) == 0) {
630 destroy_request_item(&request);
631 return 0;
632 }
633
634 lltask = (LastLevelTaskItem *)ff_queue_peek_front(lltask_queue);
635 if (lltask == NULL) {
636 av_log(NULL, AV_LOG_ERROR, "Failed to get LastLevelTaskItem\n");
637 destroy_request_item(&request);
638 return AVERROR(EINVAL);
639 }
640 task = lltask->task;
641 onnx_model = (ONNXModel *)task->model;
642
643 ret = fill_model_input_onnx(onnx_model, request);
644 if (ret != 0) {
645 goto err;
646 }
647
648 if (task->async) {
649 avpriv_report_missing_feature(onnx_model->ctx, "ONNX async inference");
650 ret = AVERROR(ENOSYS);
651 goto err;
652 } else {
653 ret = onnx_start_inference((void *)request);
654 if (ret != 0) {
655 goto err;
656 }
658 return (task->inference_done == task->inference_todo) ? 0 : DNN_GENERIC_ERROR;
659 }
660
661err:
662 av_freep(&request->lltask);
664 if (ff_safe_queue_push_back(onnx_model->request_queue, request) < 0) {
665 destroy_request_item(&request);
666 }
667 return ret;
668}
669
670static int get_output_onnx(DNNModel *model, const char *input_name, int input_width, int input_height,
671 const char *output_name, int *output_width, int *output_height)
672{
673 int ret = 0;
674 ONNXModel *onnx_model = (ONNXModel *)model;
675 DnnContext *ctx = onnx_model->ctx;
676 TaskItem task = { 0 };
677 ONNXRequestItem *request = NULL;
678 DNNExecBaseParams exec_params = {
679 .input_name = input_name,
680 .output_names = &output_name,
681 .nb_output = 1,
682 .in_frame = NULL,
683 .out_frame = NULL,
684 };
685
686 ret = ff_dnn_fill_gettingoutput_task(&task, &exec_params, onnx_model, input_height, input_width, ctx);
687 if (ret != 0) {
688 goto err;
689 }
690
691 ret = extract_lltask_from_task(&task, onnx_model->lltask_queue);
692 if (ret != 0) {
693 av_log(ctx, AV_LOG_ERROR, "Unable to extract last level task from task.\n");
694 goto err;
695 }
696
697 request = (ONNXRequestItem *)ff_safe_queue_pop_front(onnx_model->request_queue);
698 if (!request) {
699 av_log(ctx, AV_LOG_ERROR, "Unable to get infer request.\n");
700 ret = AVERROR(EINVAL);
701 goto err;
702 }
703
704 ret = execute_model_onnx(request, onnx_model->lltask_queue);
705 *output_width = task.out_frame->width;
706 *output_height = task.out_frame->height;
707
708err:
710 av_frame_free(&task.in_frame);
711 return ret;
712}
713
715{
716 ONNXInferRequest *request = av_malloc(sizeof(ONNXInferRequest));
717 if (!request)
718 return NULL;
719 request->input_tensor = NULL;
720 request->output_tensor = NULL;
721 request->input_data = NULL;
722 return request;
723}
724
726{
727 DNNModel *model = NULL;
728 ONNXModel *onnx_model = NULL;
729 ONNXRequestItem *item = NULL;
730 ONNXOptions *options = &ctx->onnx_option;
731 OrtStatus *status;
732
734 if (!g_ort) {
735 av_log(ctx, AV_LOG_ERROR, "Failed to get ONNX Runtime API\n");
736 return NULL;
737 }
738
739 onnx_model = av_mallocz(sizeof(ONNXModel));
740 if (!onnx_model)
741 return NULL;
742
743 model = &onnx_model->model;
744 onnx_model->ctx = ctx;
745
746 status = g_ort->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "FFmpeg", &onnx_model->env);
747 if (status != NULL) {
748 av_log(ctx, AV_LOG_ERROR, "Failed to create ONNX Runtime environment\n");
749 goto fail;
750 }
751
752 status = g_ort->CreateSessionOptions(&onnx_model->session_options);
753 if (status != NULL) {
754 av_log(ctx, AV_LOG_ERROR, "Failed to create session options\n");
755 goto fail;
756 }
757
758 if (options->num_threads > 0 &&
759 (!ctx->device || av_strcasecmp(ctx->device, "cpu") == 0)) {
760 g_ort->SetIntraOpNumThreads(onnx_model->session_options, options->num_threads);
761 }
762 g_ort->SetSessionGraphOptimizationLevel(onnx_model->session_options, ORT_ENABLE_ALL);
763
764 if (ctx->device && av_strcasecmp(ctx->device, "cpu") != 0) {
765 if (av_strcasecmp(ctx->device, "cuda") == 0) {
766 if (g_ort->SessionOptionsAppendExecutionProvider_CUDA) {
767 OrtCUDAProviderOptions cuda_options;
768 memset(&cuda_options, 0, sizeof(cuda_options));
769 cuda_options.device_id = ctx->device_id;
770
771 status = g_ort->SessionOptionsAppendExecutionProvider_CUDA(
772 onnx_model->session_options, &cuda_options);
773 if (status != NULL) {
774 const char *msg = g_ort->GetErrorMessage(status);
775 av_log(ctx, AV_LOG_WARNING, "Failed to enable CUDA (device %d): %s. Falling back to CPU\n",
776 ctx->device_id, msg);
777 g_ort->ReleaseStatus(status);
778 } else {
779 av_log(ctx, AV_LOG_INFO, "Using CUDA execution provider on device %d\n", ctx->device_id);
780 }
781 } else {
782 av_log(ctx, AV_LOG_WARNING, "CUDA provider function not available in this ONNX Runtime API version. Falling back to CPU\n");
783 }
784 } else if (av_strcasecmp(ctx->device, "dml") == 0) {
785#ifdef _WIN32
786 const char* dml_options_keys[] = {"device_id"};
787 const char* dml_options_values[] = {NULL};
788 char device_id_str[32];
789 snprintf(device_id_str, sizeof(device_id_str), "%d", ctx->device_id);
790 dml_options_values[0] = device_id_str;
791
792 /* DirectML cannot use ORT's memory-pattern optimizer and only
793 * supports sequential execution. */
794 status = g_ort->SetSessionExecutionMode(onnx_model->session_options, ORT_SEQUENTIAL);
795 if (status)
796 g_ort->ReleaseStatus(status);
797 status = g_ort->DisableMemPattern(onnx_model->session_options);
798 if (status)
799 g_ort->ReleaseStatus(status);
800
801 if (g_ort->SessionOptionsAppendExecutionProvider) {
802 status = g_ort->SessionOptionsAppendExecutionProvider(
803 onnx_model->session_options, "DML",
804 dml_options_keys, dml_options_values, 1);
805 if (status != NULL) {
806 const char *msg = g_ort->GetErrorMessage(status);
807 av_log(ctx, AV_LOG_WARNING, "Failed to enable DirectML (device %d): %s. Falling back to CPU\n",
808 ctx->device_id, msg);
809 g_ort->ReleaseStatus(status);
810 } else {
811 av_log(ctx, AV_LOG_INFO, "Using DirectML execution provider on device %d\n", ctx->device_id);
812 }
813 } else {
814 av_log(ctx, AV_LOG_WARNING, "DirectML provider function not available in this ONNX Runtime API version. Falling back to CPU\n");
815 }
816#else
817 av_log(ctx, AV_LOG_WARNING, "DirectML is only available on Windows. Falling back to CPU\n");
818#endif
819 } else if (av_strcasecmp(ctx->device, "vitisai") == 0) {
820 if (g_ort->SessionOptionsAppendExecutionProvider) {
821 status = g_ort->SessionOptionsAppendExecutionProvider(
822 onnx_model->session_options, "VitisAI",
823 NULL, NULL, 0);
824 if (status != NULL) {
825 const char *msg = g_ort->GetErrorMessage(status);
827 "Failed to enable VitisAI EP: %s. Falling back to CPU\n", msg);
828 g_ort->ReleaseStatus(status);
829 } else {
830 av_log(ctx, AV_LOG_INFO, "Using VitisAI execution provider (AMD Ryzen AI NPU)\n");
831 }
832 } else {
834 "VitisAI provider function not available in this ONNX Runtime API version. Falling back to CPU.\n");
835 }
836 } else {
837#ifdef _WIN32
839 "Unknown device '%s'. Supported: cpu, cuda, dml, vitisai. Using CPU\n",
840 ctx->device);
841#else
843 "Unknown device '%s'. Supported: cpu, cuda, vitisai. Using CPU\n",
844 ctx->device);
845#endif
846 }
847 } else {
848 av_log(ctx, AV_LOG_INFO, "Using CPU execution provider\n");
849 }
850
851#ifdef _WIN32
852 {
853 wchar_t *wfilename = NULL;
854 if (utf8towchar(ctx->model_filename, &wfilename)) {
855 av_log(ctx, AV_LOG_ERROR, "Failed to convert model filename to UTF-16\n");
856 goto fail;
857 }
858 if (!wfilename) {
859 av_log(ctx, AV_LOG_ERROR, "Failed to convert model filename to UTF-16\n");
860 goto fail;
861 }
862
863 status = g_ort->CreateSession(onnx_model->env, wfilename,
864 onnx_model->session_options, &onnx_model->session);
865 av_free(wfilename);
866 }
867#else
868 status = g_ort->CreateSession(onnx_model->env, ctx->model_filename,
869 onnx_model->session_options, &onnx_model->session);
870#endif
871 if (status != NULL) {
872 const char *msg = g_ort->GetErrorMessage(status);
873 av_log(ctx, AV_LOG_ERROR, "Failed to create ONNX session: %s\n", msg);
874 g_ort->ReleaseStatus(status);
875 goto fail;
876 }
877
878 status = g_ort->GetAllocatorWithDefaultOptions(&onnx_model->allocator);
879 if (status != NULL) {
880 av_log(ctx, AV_LOG_ERROR, "Failed to get allocator\n");
881 goto fail;
882 }
883
884 /*
885 * The ONNX backend binds exactly one input tensor to Run(), so only
886 * single-input models are supported.
887 */
888 {
889 size_t input_count = 0;
890 status = g_ort->SessionGetInputCount(onnx_model->session, &input_count);
891 if (status != NULL) {
892 const char *msg = g_ort->GetErrorMessage(status);
893 av_log(ctx, AV_LOG_ERROR, "Failed to get model input count: %s\n", msg);
894 g_ort->ReleaseStatus(status);
895 goto fail;
896 }
897 if (input_count == 0) {
898 av_log(ctx, AV_LOG_ERROR, "ONNX model exposes no input tensors\n");
899 goto fail;
900 }
901 if (input_count > 1) {
903 "ONNX model exposes %zu input tensors; the ONNX backend "
904 "supports single-input models only.\n",
905 input_count);
906 goto fail;
907 }
908 }
909
910 /* Auto-detect the input tensor name when the user did not pass input=NAME. */
911 if (!ctx->model_inputname || !*ctx->model_inputname) {
912 char *name = NULL;
913 status = g_ort->SessionGetInputName(onnx_model->session, 0,
914 onnx_model->allocator, &name);
915 if (status != NULL) {
916 const char *msg = g_ort->GetErrorMessage(status);
917 av_log(ctx, AV_LOG_ERROR, "Failed to get model input name: %s\n", msg);
918 g_ort->ReleaseStatus(status);
919 goto fail;
920 }
921 av_freep(&ctx->model_inputname);
922 ctx->model_inputname = av_strdup(name);
923 onnx_model->allocator->Free(onnx_model->allocator, name);
924 if (!ctx->model_inputname)
925 goto fail;
926 av_log(ctx, AV_LOG_INFO, "Auto-detected ONNX input tensor '%s'\n",
927 ctx->model_inputname);
928 }
929
930 /* Auto-detect the output tensor name when the user did not pass output=NAME. */
931 if (!ctx->model_outputnames) {
932 size_t output_count = 0;
933 char *name = NULL;
934 status = g_ort->SessionGetOutputCount(onnx_model->session, &output_count);
935 if (status != NULL) {
936 const char *msg = g_ort->GetErrorMessage(status);
937 av_log(ctx, AV_LOG_ERROR, "Failed to get model output count: %s\n", msg);
938 g_ort->ReleaseStatus(status);
939 goto fail;
940 }
941 if (output_count == 0) {
942 av_log(ctx, AV_LOG_ERROR, "ONNX model exposes no output tensors\n");
943 goto fail;
944 }
945 status = g_ort->SessionGetOutputName(onnx_model->session, 0,
946 onnx_model->allocator, &name);
947 if (status != NULL) {
948 const char *msg = g_ort->GetErrorMessage(status);
949 av_log(ctx, AV_LOG_ERROR, "Failed to get model output name: %s\n", msg);
950 g_ort->ReleaseStatus(status);
951 goto fail;
952 }
953 ctx->model_outputnames = av_calloc(1, sizeof(*ctx->model_outputnames));
954 if (!ctx->model_outputnames) {
955 onnx_model->allocator->Free(onnx_model->allocator, name);
956 goto fail;
957 }
958 ctx->model_outputnames[0] = av_strdup(name);
959 onnx_model->allocator->Free(onnx_model->allocator, name);
960 if (!ctx->model_outputnames[0]) {
961 av_freep(&ctx->model_outputnames);
962 goto fail;
963 }
964 ctx->nb_outputs = 1;
965 if (output_count == 1) {
966 av_log(ctx, AV_LOG_INFO, "Auto-detected ONNX output tensor '%s'\n",
967 ctx->model_outputnames[0]);
968 } else {
970 "ONNX model exposes %zu output tensors; auto-using index 0 ('%s'). "
971 "Specify output=NAME to choose a different one.\n",
972 output_count, ctx->model_outputnames[0]);
973 }
974 }
975
976 onnx_model->request_queue = ff_safe_queue_create();
977 if (!onnx_model->request_queue) {
978 goto fail;
979 }
980
981 item = av_mallocz(sizeof(ONNXRequestItem));
982 if (!item) {
983 goto fail;
984 }
985 item->lltask = NULL;
987 if (!item->infer_request) {
988 av_log(ctx, AV_LOG_ERROR, "Failed to allocate memory for ONNX inference request\n");
989 goto fail;
990 }
993 item->exec_module.args = item;
994
995 if (ff_safe_queue_push_back(onnx_model->request_queue, item) < 0) {
996 goto fail;
997 }
998 item = NULL;
999
1000 onnx_model->task_queue = ff_queue_create();
1001 if (!onnx_model->task_queue) {
1002 goto fail;
1003 }
1004
1005 onnx_model->lltask_queue = ff_queue_create();
1006 if (!onnx_model->lltask_queue) {
1007 goto fail;
1008 }
1009
1010 model->get_input = &get_input_onnx;
1011 model->get_output = &get_output_onnx;
1012 model->filter_ctx = filter_ctx;
1013 model->func_type = func_type;
1014
1015 return model;
1016
1017fail:
1018 if (item) {
1019 destroy_request_item(&item);
1020 }
1021 dnn_free_model_onnx(&model);
1022 return NULL;
1023}
1024
1025static int dnn_execute_model_onnx(const DNNModel *model, DNNExecBaseParams *exec_params)
1026{
1027 ONNXModel *onnx_model = (ONNXModel *)model;
1028 DnnContext *ctx = onnx_model->ctx;
1029 TaskItem *task;
1030 ONNXRequestItem *request;
1031 int ret = 0;
1032
1033 ret = ff_check_exec_params(ctx, DNN_ONNX, model->func_type, exec_params);
1034 if (ret != 0) {
1035 av_log(ctx, AV_LOG_ERROR, "Exec parameter checking failed.\n");
1036 return ret;
1037 }
1038
1039 task = av_malloc(sizeof(TaskItem));
1040 if (!task) {
1041 av_log(ctx, AV_LOG_ERROR, "Unable to alloc memory for task item.\n");
1042 return AVERROR(ENOMEM);
1043 }
1044
1045 ret = ff_dnn_fill_task(task, exec_params, onnx_model, 0, 1);
1046 if (ret != 0) {
1047 av_freep(&task);
1048 av_log(ctx, AV_LOG_ERROR, "Unable to fill task.\n");
1049 return ret;
1050 }
1051
1052 ret = ff_queue_push_back(onnx_model->task_queue, task);
1053 if (ret < 0) {
1054 av_freep(&task);
1055 av_log(ctx, AV_LOG_ERROR, "Unable to push back task_queue.\n");
1056 return ret;
1057 }
1058
1059 ret = extract_lltask_from_task(task, onnx_model->lltask_queue);
1060 if (ret != 0) {
1061 av_log(ctx, AV_LOG_ERROR, "Unable to extract last level task from task.\n");
1062 return ret;
1063 }
1064
1065 request = (ONNXRequestItem *)ff_safe_queue_pop_front(onnx_model->request_queue);
1066 if (!request) {
1067 av_log(ctx, AV_LOG_ERROR, "Unable to get infer request.\n");
1068 return AVERROR(EINVAL);
1069 }
1070
1071 return execute_model_onnx(request, onnx_model->lltask_queue);
1072}
1073
1075{
1076 ONNXModel *onnx_model = (ONNXModel *)model;
1077 return ff_dnn_get_result_common(onnx_model->task_queue, in, out);
1078}
1079
1080static int dnn_flush_onnx(const DNNModel *model)
1081{
1082 ONNXModel *onnx_model = (ONNXModel *)model;
1083 ONNXRequestItem *request;
1084
1085 if (ff_queue_size(onnx_model->lltask_queue) == 0)
1086 return 0;
1087
1088 request = (ONNXRequestItem *)ff_safe_queue_pop_front(onnx_model->request_queue);
1089 if (!request) {
1090 av_log(onnx_model->ctx, AV_LOG_ERROR, "Unable to get infer request.\n");
1091 return AVERROR(EINVAL);
1092 }
1093
1094 return execute_model_onnx(request, onnx_model->lltask_queue);
1095}
1096
1098 .clazz = DNN_DEFINE_CLASS(dnn_onnx),
1099 .type = DNN_ONNX,
1100 .load_model = dnn_load_model_onnx,
1101 .execute_model = dnn_execute_model_onnx,
1102 .get_result = dnn_get_result_onnx,
1103 .flush = dnn_flush_onnx,
1104 .free_model = dnn_free_model_onnx,
1105};
static const AVFilterPad outputs[]
Definition af_aap.c:310
simple assert() macros that are a bit more flexible than ISO C assert().
#define i(width, name, range_min, range_max)
Definition cbs_h264.c:63
#define FLAGS
Definition cmdutils.c:598
#define NULL
Definition coverity.c:32
long long int64_t
Definition coverity.c:34
int ff_check_exec_params(void *ctx, DNNBackendType backend, DNNFunctionType func_type, DNNExecBaseParams *exec_params)
void ff_dnn_wait_requests(SafeQueue *request_queue, int nireq)
Wait for all inference requests to complete before teardown.
DNNAsyncStatusType ff_dnn_get_result_common(Queue *task_queue, AVFrame **in, AVFrame **out)
Extract input and output frame from the Task Queue after asynchronous inference.
int ff_dnn_async_module_cleanup(DNNAsyncExecModule *async_module)
Join the Async Execution thread and set module pointers to NULL.
int ff_dnn_fill_task(TaskItem *task, DNNExecBaseParams *exec_params, void *backend_model, int async, int do_ioproc)
Fill the Task for Backend Execution.
int ff_dnn_fill_gettingoutput_task(TaskItem *task, DNNExecBaseParams *exec_params, void *backend_model, int input_height, int input_width, void *ctx)
Allocate input and output frames and fill the Task with execution parameters.
DNN common functions different backends.
#define DNN_DEFINE_CLASS(fname)
static int dnn_execute_model_onnx(const DNNModel *model, DNNExecBaseParams *exec_params)
static int extract_lltask_from_task(TaskItem *task, Queue *lltask_queue)
const DNNModule ff_dnn_backend_onnx
static const AVOption dnn_onnx_options[]
static int dnn_flush_onnx(const DNNModel *model)
static DNNModel * dnn_load_model_onnx(DnnContext *ctx, DNNFunctionType func_type, AVFilterContext *filter_ctx)
static int get_output_onnx(DNNModel *model, const char *input_name, int input_width, int input_height, const char *output_name, int *output_width, int *output_height)
static int fill_model_input_onnx(ONNXModel *onnx_model, ONNXRequestItem *request)
static int execute_model_onnx(ONNXRequestItem *request, Queue *lltask_queue)
static ONNXInferRequest * onnx_create_inference_request(void)
static void init_ort_api(void)
static void onnx_free_request(ONNXInferRequest *request)
static const OrtApi * g_ort
static void dnn_free_model_onnx(DNNModel **model)
static DNNAsyncStatusType dnn_get_result_onnx(const DNNModel *model, AVFrame **in, AVFrame **out)
static AVOnce g_ort_init_once
static int onnx_start_inference(void *args)
#define OFFSET(x)
static void destroy_request_item(ONNXRequestItem **arg)
static void infer_completion_callback(void *args)
static int get_input_onnx(DNNModel *model, DNNData *input, const char *input_name)
static int dnn_get_height_idx_by_layout(DNNLayout layout)
DNNAsyncStatusType
@ DL_NCHW
@ DNN_ONNX
DNNFunctionType
@ DFT_PROCESS_FRAME
@ DFT_ANALYTICS_DETECT
static int dnn_get_width_idx_by_layout(DNNLayout layout)
#define DNN_GENERIC_ERROR
@ DNN_FLOAT
@ DCO_RGB
static int dnn_get_channel_idx_by_layout(DNNLayout layout)
int ff_proc_from_frame_to_dnn(AVFrame *frame, DNNData *input, void *log_ctx)
int ff_frame_to_dnn_detect(AVFrame *frame, DNNData *input, void *log_ctx)
int ff_proc_from_dnn_to_frame(AVFrame *frame, DNNData *output, void *log_ctx)
Definition dnn_io_proc.c:42
DNN input&output process between AVFrame and DNNData.
#define fail
Definition test.h:479
@ AV_OPT_TYPE_INT
Underlying C type is int.
Definition opt.h:258
#define AVERROR(e)
Definition error.h:45
void av_frame_free(AVFrame **frame)
Free the frame and any dynamically allocated objects in it, e.g.
Definition frame.c:64
#define AV_LOG_WARNING
Something somehow does not look correct.
Definition log.h:216
#define AV_LOG_INFO
Standard information.
Definition log.h:221
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition log.h:210
int av_strcasecmp(const char *a, const char *b)
Locale-independent case-insensitive compare.
Definition avstring.c:208
const char * arg
Definition jacosubdec.c:65
static int output_data(MLPDecodeContext *m, unsigned int substr, AVFrame *frame, int *got_frame_ptr)
Write the audio data into the output buffer.
Definition mlpdec.c:1107
#define AVFILTER_DEFINE_CLASS(fname)
Definition filters.h:478
void avpriv_report_missing_feature(void *avc, const char *msg,...) av_printf_format(2
Log a generic warning message about a missing feature.
#define AVOnce
Definition thread.h:202
static int ff_thread_once(char *control, void(*routine)(void))
Definition thread.h:205
#define AV_ONCE_INIT
Definition thread.h:203
void * av_calloc(size_t nmemb, size_t size)
Definition mem.c:264
Memory handling functions.
#define av_strdup(s)
Definition ops_static.c:55
#define av_malloc(s)
Definition ops_static.c:52
AVOptions.
const char * name
Definition qsvenc.c:142
void ff_queue_destroy(Queue *q)
Destroy the Queue instance.
Definition queue.c:72
void * ff_queue_pop_front(Queue *q)
Remove and free first element from the Queue.
Definition queue.c:151
int ff_queue_push_back(Queue *q, void *v)
Add data to the tail of the queue.
Definition queue.c:130
void * ff_queue_peek_front(Queue *q)
Return a pointer to the data at the head of the queue.
Definition queue.c:93
size_t ff_queue_size(Queue *q)
Return the length of the Queue.
Definition queue.c:88
Queue * ff_queue_create(void)
Create a Queue instance.
Definition queue.c:47
int ff_safe_queue_push_back(SafeQueue *sq, void *v)
Add data to the tail of queue in the SafeQueue after locking mutex.
Definition safe_queue.c:106
void * ff_safe_queue_pop_front(SafeQueue *sq)
Remove and free first element from the queue in SafeQueue.
Definition safe_queue.c:116
size_t ff_safe_queue_size(SafeQueue *sq)
Return the length of the SafeQueue.
Definition safe_queue.c:80
SafeQueue * ff_safe_queue_create(void)
Create and initialize a SafeQueue instance.
Definition safe_queue.c:52
void ff_safe_queue_destroy(SafeQueue *sq)
Destroy the SafeQueue instance.
Definition safe_queue.c:69
#define snprintf
Definition snprintf.h:34
An instance of a filter.
Definition avfilter.h:273
This structure describes decoded (raw) audio or video data.
Definition frame.h:472
int width
Definition frame.h:544
int height
Definition frame.h:544
AVOption.
Definition opt.h:428
Common Async Execution Mechanism for the DNN Backends.
void * args
Argument for the execution functions.
int(* start_inference)(void *request)
Synchronous inference function for the backend with corresponding request item as the argument.
void(* callback)(void *args)
Completion Callback for the backend.
float scale
DNNDataType dt
int dims[4]
DNNColorOrder order
void * data
DNNLayout layout
int(* get_input)(struct DNNModel *model, DNNData *input, const char *input_name)
int(* get_output)(struct DNNModel *model, const char *input_name, int input_width, int input_height, const char *output_name, int *output_width, int *output_height)
FramePrePostProc frame_pre_proc
FramePrePostProc frame_post_proc
AVFilterContext * filter_ctx
DNNFunctionType func_type
OrtValue * output_tensor
OrtValue * input_tensor
Queue * task_queue
DnnContext * ctx
DNNData input_info
OrtSessionOptions * session_options
SafeQueue * request_queue
OrtSession * session
DNNModel model
OrtAllocator * allocator
Queue * lltask_queue
DNNAsyncExecModule exec_module
ONNXInferRequest * infer_request
LastLevelTaskItem * lltask
Linear double-ended data structure.
Definition executor.c:51
Double-ended queue with mutex locks ensuring data consistency while multithreading.
Definition safe_queue.c:46
uint32_t inference_done
AVFrame * in_frame
const char ** output_names
uint8_t do_ioproc
uint32_t inference_todo
const char * input_name
AVFrame * out_frame
uint32_t nb_output
#define av_free(p)
#define av_mallocz(s)
#define av_freep(p)
#define av_log(a,...)
static FILE * out
Definition movenc.c:55
static AVFormatContext * ctx
Definition movenc.c:49
static FilteringContext * filter_ctx
Definition transcode.c:52