FFmpeg
Loading...
Searching...
No Matches
dnn_backend_tf.c
Go to the documentation of this file.
1/*
2 * Copyright (c) 2018 Sergey Lavrushkin
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 tensorflow backend implementation.
24 */
25
26#include "libavformat/avio.h"
27#include "libavutil/avassert.h"
28#include "libavutil/avstring.h"
29#include "libavutil/cpu.h"
30#include "libavutil/mem.h"
31#include "libavutil/opt.h"
32#include "libavcodec/defs.h"
33#include "dnn_io_proc.h"
34#include "dnn_backend_common.h"
35#include "safe_queue.h"
36#include <tensorflow/c/c_api.h>
37
48
49/**
50 * Stores execution parameters for single
51 * call to the TensorFlow C API
52 */
53typedef struct TFInferRequest {
54 TF_Output *tf_outputs;
55 TF_Tensor **output_tensors;
56 TF_Output *tf_input;
57 TF_Tensor *input_tensor;
59
66
67#define OFFSET(x) offsetof(TFOptions, x)
68#define FLAGS AV_OPT_FLAG_FILTERING_PARAM
70 { "sess_config", "config for SessionOptions", OFFSET(sess_config), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, FLAGS },
71 { NULL }
72};
73
74
75static int execute_model_tf(TFRequestItem *request, Queue *lltask_queue);
76static void infer_completion_callback(void *args);
77static inline void destroy_request_item(TFRequestItem **arg);
78
79static void free_buffer(void *data, size_t length)
80{
81 av_freep(&data);
82}
83
84/**
85 * Free the contents of TensorFlow inference request.
86 * It does not free the TFInferRequest instance.
87 *
88 * @param request pointer to TFInferRequest instance.
89 * NULL pointer is allowed.
90 */
91static void tf_free_request(TFInferRequest *request)
92{
93 if (!request)
94 return;
95 if (request->input_tensor) {
96 TF_DeleteTensor(request->input_tensor);
97 request->input_tensor = NULL;
98 }
99 av_freep(&request->tf_input);
100 av_freep(&request->tf_outputs);
101 if (request->output_tensors) {
102 int nb_output = sizeof(*request->output_tensors)/sizeof(request->output_tensors[0]);
103 for (uint32_t i = 0; i < nb_output; ++i) {
104 if (request->output_tensors[i]) {
105 TF_DeleteTensor(request->output_tensors[i]);
106 request->output_tensors[i] = NULL;
107 }
108 }
109 av_freep(&request->output_tensors);
110 }
111}
112
113/**
114 * Create a TensorFlow inference request. All properties
115 * are initially unallocated and set as NULL.
116 *
117 * @return pointer to the allocated TFInferRequest instance.
118 */
120{
121 TFInferRequest *infer_request = av_malloc(sizeof(TFInferRequest));
122 if (!infer_request) {
123 return NULL;
124 }
125 infer_request->tf_outputs = NULL;
126 infer_request->tf_input = NULL;
127 infer_request->input_tensor = NULL;
128 infer_request->output_tensors = NULL;
129 return infer_request;
130}
131
132/**
133 * Start synchronous inference for the TensorFlow model.
134 *
135 * @param request pointer to the TFRequestItem for inference
136 * @retval 0 if execution is successful
137 * @retval AVERROR(EINVAL) if request is NULL
138 * @retval DNN_GENERIC_ERROR if execution fails
139 */
140static int tf_start_inference(void *args)
141{
142 TFRequestItem *request = args;
143 TFInferRequest *infer_request = request->infer_request;
144 LastLevelTaskItem *lltask = request->lltask;
145 TaskItem *task = lltask->task;
146 TFModel *tf_model = task->model;
147
148 if (!request) {
149 av_log(tf_model->ctx, AV_LOG_ERROR, "TFRequestItem is NULL\n");
150 return AVERROR(EINVAL);
151 }
152
153 TF_SessionRun(tf_model->session, NULL,
154 infer_request->tf_input, &infer_request->input_tensor, 1,
155 infer_request->tf_outputs, infer_request->output_tensors,
156 task->nb_output, NULL, 0, NULL,
157 request->status);
158 if (TF_GetCode(request->status) != TF_OK) {
159 av_log(tf_model->ctx, AV_LOG_ERROR, "%s", TF_Message(request->status));
160 return DNN_GENERIC_ERROR;
161 }
162 return 0;
163}
164
165/**
166 * Free the TFRequestItem completely.
167 *
168 * @param arg Address of the TFInferRequest instance.
169 */
171 TFRequestItem *request;
172 if (!arg) {
173 return;
174 }
175 request = *arg;
177 av_freep(&request->infer_request);
178 av_freep(&request->lltask);
179 TF_DeleteStatus(request->status);
181 av_freep(arg);
182}
183
184static int extract_lltask_from_task(TaskItem *task, Queue *lltask_queue)
185{
186 TFModel *tf_model = task->model;
187 DnnContext *ctx = tf_model->ctx;
188 LastLevelTaskItem *lltask = av_malloc(sizeof(*lltask));
189 if (!lltask) {
190 av_log(ctx, AV_LOG_ERROR, "Unable to allocate space for LastLevelTaskItem\n");
191 return AVERROR(ENOMEM);
192 }
193 task->inference_todo = 1;
194 task->inference_done = 0;
195 lltask->task = task;
196 if (ff_queue_push_back(lltask_queue, lltask) < 0) {
197 av_log(ctx, AV_LOG_ERROR, "Failed to push back lltask_queue.\n");
198 av_freep(&lltask);
199 return AVERROR(ENOMEM);
200 }
201 return 0;
202}
203
204static TF_Buffer *read_graph(const char *model_filename)
205{
206 TF_Buffer *graph_buf;
207 unsigned char *graph_data = NULL;
208 AVIOContext *model_file_context;
209 long size, bytes_read;
210
211 if (avio_open(&model_file_context, model_filename, AVIO_FLAG_READ) < 0){
212 return NULL;
213 }
214
215 size = avio_size(model_file_context);
216
217 graph_data = av_malloc(size);
218 if (!graph_data){
219 avio_closep(&model_file_context);
220 return NULL;
221 }
222 bytes_read = avio_read(model_file_context, graph_data, size);
223 avio_closep(&model_file_context);
224 if (bytes_read != size){
225 av_freep(&graph_data);
226 return NULL;
227 }
228
229 graph_buf = TF_NewBuffer();
230 graph_buf->data = graph_data;
231 graph_buf->length = size;
232 graph_buf->data_deallocator = free_buffer;
233
234 return graph_buf;
235}
236
237static TF_Tensor *allocate_input_tensor(const DNNData *input)
238{
239 TF_DataType dt;
240 size_t size;
241 int64_t input_dims[4] = { 0 };
242
243 input_dims[0] = 1;
244 input_dims[1] = input->dims[dnn_get_height_idx_by_layout(input->layout)];
245 input_dims[2] = input->dims[dnn_get_width_idx_by_layout(input->layout)];
246 input_dims[3] = input->dims[dnn_get_channel_idx_by_layout(input->layout)];
247 switch (input->dt) {
248 case DNN_FLOAT:
249 dt = TF_FLOAT;
250 size = sizeof(float);
251 break;
252 case DNN_UINT8:
253 dt = TF_UINT8;
254 size = 1;
255 break;
256 default:
257 av_assert0(!"should not reach here");
258 }
259
260 return TF_AllocateTensor(dt, input_dims, 4,
261 input_dims[1] * input_dims[2] * input_dims[3] * size);
262}
263
264static int get_input_tf(DNNModel *model, DNNData *input, const char *input_name)
265{
266 TFModel *tf_model = (TFModel *)model;
267 DnnContext *ctx = tf_model->ctx;
268 TF_Status *status;
269 TF_DataType dt;
270 int64_t dims[4];
271
272 TF_Output tf_output;
273 tf_output.oper = TF_GraphOperationByName(tf_model->graph, input_name);
274 if (!tf_output.oper) {
275 av_log(ctx, AV_LOG_ERROR, "Could not find \"%s\" in model\n", input_name);
276 return AVERROR(EINVAL);
277 }
278
279 tf_output.index = 0;
280 dt = TF_OperationOutputType(tf_output);
281 switch (dt) {
282 case TF_FLOAT:
283 input->dt = DNN_FLOAT;
284 break;
285 case TF_UINT8:
286 input->dt = DNN_UINT8;
287 break;
288 default:
289 av_log(ctx, AV_LOG_ERROR, "Unsupported output type %d in model\n", dt);
290 return AVERROR(EINVAL);
291 }
292 input->order = DCO_RGB;
293
294 status = TF_NewStatus();
295 TF_GraphGetTensorShape(tf_model->graph, tf_output, dims, 4, status);
296 if (TF_GetCode(status) != TF_OK){
297 TF_DeleteStatus(status);
298 av_log(ctx, AV_LOG_ERROR, "Failed to get input tensor shape: number of dimension incorrect\n");
299 return DNN_GENERIC_ERROR;
300 }
301 TF_DeleteStatus(status);
302
303 // currently only NHWC is supported
304 av_assert0(dims[0] == 1 || dims[0] == -1);
305 for (int i = 0; i < 4; i++)
306 input->dims[i] = dims[i];
307 input->layout = DL_NHWC;
308
309 return 0;
310}
311
312static int get_output_tf(DNNModel *model, const char *input_name, int input_width, int input_height,
313 const char *output_name, int *output_width, int *output_height)
314{
315 int ret;
316 TFModel *tf_model = (TFModel *)model;
317 DnnContext *ctx = tf_model->ctx;
318 TaskItem task;
319 TFRequestItem *request;
320 DNNExecBaseParams exec_params = {
321 .input_name = input_name,
322 .output_names = &output_name,
323 .nb_output = 1,
324 .in_frame = NULL,
325 .out_frame = NULL,
326 };
327
328 ret = ff_dnn_fill_gettingoutput_task(&task, &exec_params, tf_model, input_height, input_width, ctx);
329 if (ret != 0) {
330 goto err;
331 }
332
333 ret = extract_lltask_from_task(&task, tf_model->lltask_queue);
334 if (ret != 0) {
335 av_log(ctx, AV_LOG_ERROR, "unable to extract inference from task.\n");
336 goto err;
337 }
338
339 request = ff_safe_queue_pop_front(tf_model->request_queue);
340 if (!request) {
341 av_log(ctx, AV_LOG_ERROR, "unable to get infer request.\n");
342 ret = AVERROR(EINVAL);
343 goto err;
344 }
345
346 ret = execute_model_tf(request, tf_model->lltask_queue);
347 *output_width = task.out_frame->width;
348 *output_height = task.out_frame->height;
349
350err:
352 av_frame_free(&task.in_frame);
353 return ret;
354}
355
356#define SPACE_CHARS " \t\r\n"
357static int hex_to_data(uint8_t *data, const char *p)
358{
359 int c, len, v;
360
361 len = 0;
362 v = 1;
363 for (;;) {
364 p += strspn(p, SPACE_CHARS);
365 if (*p == '\0')
366 break;
367 c = av_toupper((unsigned char) *p++);
368 if (c >= '0' && c <= '9')
369 c = c - '0';
370 else if (c >= 'A' && c <= 'F')
371 c = c - 'A' + 10;
372 else
373 break;
374 v = (v << 4) | c;
375 if (v & 0x100) {
376 if (data) {
377 data[len] = v;
378 }
379 len++;
380 v = 1;
381 }
382 }
383 return len;
384}
385
386static int load_tf_model(TFModel *tf_model, const char *model_filename)
387{
388 DnnContext *ctx = tf_model->ctx;
389 TF_Buffer *graph_def;
390 TF_ImportGraphDefOptions *graph_opts;
391 TF_SessionOptions *sess_opts;
392 const TF_Operation *init_op;
393 uint8_t *sess_config = NULL;
394 int sess_config_length = 0;
395
396 // prepare the sess config data
397 if (ctx->tf_option.sess_config != NULL) {
398 const char *config;
399 /*
400 tf_model->ctx.options.sess_config is hex to present the serialized proto
401 required by TF_SetConfig below, so we need to first generate the serialized
402 proto in a python script, tools/python/tf_sess_config.py is a script example
403 to generate the configs of sess_config.
404 */
405 if (strncmp(ctx->tf_option.sess_config, "0x", 2) != 0) {
406 av_log(ctx, AV_LOG_ERROR, "sess_config should start with '0x'\n");
407 return AVERROR(EINVAL);
408 }
409 config = ctx->tf_option.sess_config + 2;
410 sess_config_length = hex_to_data(NULL, config);
411
412 sess_config = av_mallocz(sess_config_length + AV_INPUT_BUFFER_PADDING_SIZE);
413 if (!sess_config) {
414 av_log(ctx, AV_LOG_ERROR, "failed to allocate memory\n");
415 return AVERROR(ENOMEM);
416 }
417 if (hex_to_data(sess_config, config) < 0) {
418 av_log(ctx, AV_LOG_ERROR, "failed to convert hex to data\n");
419 return AVERROR(EINVAL);
420 }
421 }
422
423 graph_def = read_graph(model_filename);
424 if (!graph_def){
425 av_log(ctx, AV_LOG_ERROR, "Failed to read model \"%s\" graph\n", model_filename);
426 av_freep(&sess_config);
427 return AVERROR(EINVAL);
428 }
429 tf_model->graph = TF_NewGraph();
430 tf_model->status = TF_NewStatus();
431 graph_opts = TF_NewImportGraphDefOptions();
432 TF_GraphImportGraphDef(tf_model->graph, graph_def, graph_opts, tf_model->status);
433 TF_DeleteImportGraphDefOptions(graph_opts);
434 TF_DeleteBuffer(graph_def);
435 if (TF_GetCode(tf_model->status) != TF_OK){
436 av_log(ctx, AV_LOG_ERROR, "Failed to import serialized graph to model graph\n");
437 av_freep(&sess_config);
438 return DNN_GENERIC_ERROR;
439 }
440
441 init_op = TF_GraphOperationByName(tf_model->graph, "init");
442 sess_opts = TF_NewSessionOptions();
443
444 if (sess_config) {
445 TF_SetConfig(sess_opts, sess_config, sess_config_length,tf_model->status);
446 av_freep(&sess_config);
447 if (TF_GetCode(tf_model->status) != TF_OK) {
448 TF_DeleteSessionOptions(sess_opts);
449 av_log(ctx, AV_LOG_ERROR, "Failed to set config for sess options with %s\n",
450 ctx->tf_option.sess_config);
451 return DNN_GENERIC_ERROR;
452 }
453 }
454
455 tf_model->session = TF_NewSession(tf_model->graph, sess_opts, tf_model->status);
456 TF_DeleteSessionOptions(sess_opts);
457 if (TF_GetCode(tf_model->status) != TF_OK)
458 {
459 av_freep(&sess_config);
460 av_log(ctx, AV_LOG_ERROR, "Failed to create new session with model graph\n");
461 return DNN_GENERIC_ERROR;
462 }
463
464 // Run initialization operation with name "init" if it is present in graph
465 if (init_op){
466 TF_SessionRun(tf_model->session, NULL,
467 NULL, NULL, 0,
468 NULL, NULL, 0,
469 &init_op, 1, NULL, tf_model->status);
470 if (TF_GetCode(tf_model->status) != TF_OK)
471 {
472 av_freep(&sess_config);
473 av_log(ctx, AV_LOG_ERROR, "Failed to run session when initializing\n");
474 return DNN_GENERIC_ERROR;
475 }
476 }
477
478 return 0;
479}
480
481static void dnn_free_model_tf(DNNModel **model)
482{
483 TFModel *tf_model;
484
485 if (!model || !*model)
486 return;
487
488 tf_model = (TFModel *)(*model);
489 ff_dnn_wait_requests(tf_model->request_queue, tf_model->ctx->nireq);
490 while (ff_safe_queue_size(tf_model->request_queue) != 0) {
493 }
495
496 while (ff_queue_size(tf_model->lltask_queue) != 0) {
498 av_freep(&item);
499 }
501
502 while (ff_queue_size(tf_model->task_queue) != 0) {
503 TaskItem *item = ff_queue_pop_front(tf_model->task_queue);
504 av_frame_free(&item->in_frame);
505 av_frame_free(&item->out_frame);
506 av_freep(&item);
507 }
508 ff_queue_destroy(tf_model->task_queue);
509
510 if (tf_model->graph){
511 TF_DeleteGraph(tf_model->graph);
512 }
513 if (tf_model->session){
514 TF_CloseSession(tf_model->session, tf_model->status);
515 TF_DeleteSession(tf_model->session, tf_model->status);
516 }
517 if (tf_model->status){
518 TF_DeleteStatus(tf_model->status);
519 }
520 av_freep(&tf_model);
521 *model = NULL;
522}
523
525{
526 DNNModel *model = NULL;
527 TFModel *tf_model = NULL;
528
529 tf_model = av_mallocz(sizeof(TFModel));
530 if (!tf_model)
531 return NULL;
532 model = &tf_model->model;
533 tf_model->ctx = ctx;
534
535 if (load_tf_model(tf_model, ctx->model_filename) != 0){
536 av_log(ctx, AV_LOG_ERROR, "Failed to load TensorFlow model: \"%s\"\n", ctx->model_filename);
537 goto err;
538 }
539
540 if (ctx->nireq <= 0) {
541 ctx->nireq = av_cpu_count() / 2 + 1;
542 }
543
544#if !HAVE_PTHREAD_CANCEL
545 if (ctx->async) {
546 ctx->async = 0;
547 av_log(filter_ctx, AV_LOG_WARNING, "pthread is not supported, roll back to sync.\n");
548 }
549#endif
550
552 if (!tf_model->request_queue) {
553 goto err;
554 }
555
556 for (int i = 0; i < ctx->nireq; i++) {
557 TFRequestItem *item = av_mallocz(sizeof(*item));
558 if (!item) {
559 goto err;
560 }
561 item->lltask = NULL;
563 if (!item->infer_request) {
564 av_log(ctx, AV_LOG_ERROR, "Failed to allocate memory for TensorFlow inference request\n");
565 av_freep(&item);
566 goto err;
567 }
568 item->status = TF_NewStatus();
571 item->exec_module.args = item;
572
573 if (ff_safe_queue_push_back(tf_model->request_queue, item) < 0) {
575 goto err;
576 }
577 }
578
579 tf_model->lltask_queue = ff_queue_create();
580 if (!tf_model->lltask_queue) {
581 goto err;
582 }
583
584 tf_model->task_queue = ff_queue_create();
585 if (!tf_model->task_queue) {
586 goto err;
587 }
588
589 model->get_input = &get_input_tf;
590 model->get_output = &get_output_tf;
591 model->filter_ctx = filter_ctx;
592 model->func_type = func_type;
593
594 return model;
595err:
596 dnn_free_model_tf(&model);
597 return NULL;
598}
599
600static int fill_model_input_tf(TFModel *tf_model, TFRequestItem *request) {
601 DNNData input = { 0 };
602 LastLevelTaskItem *lltask;
603 TaskItem *task;
604 TFInferRequest *infer_request = NULL;
605 DnnContext *ctx = tf_model->ctx;
606 int ret = 0;
607
608 lltask = ff_queue_pop_front(tf_model->lltask_queue);
609 av_assert0(lltask);
610 task = lltask->task;
611 request->lltask = lltask;
612
613 ret = get_input_tf(&tf_model->model, &input, task->input_name);
614 if (ret != 0) {
615 goto err;
616 }
617
618 infer_request = request->infer_request;
619 input.dims[1] = task->in_frame->height;
620 input.dims[2] = task->in_frame->width;
621
622 infer_request->tf_input = av_malloc(sizeof(TF_Output));
623 if (!infer_request->tf_input) {
624 av_log(ctx, AV_LOG_ERROR, "Failed to allocate memory for input tensor\n");
625 ret = AVERROR(ENOMEM);
626 goto err;
627 }
628
629 infer_request->tf_input->oper = TF_GraphOperationByName(tf_model->graph, task->input_name);
630 if (!infer_request->tf_input->oper){
631 av_log(ctx, AV_LOG_ERROR, "Could not find \"%s\" in model\n", task->input_name);
632 ret = DNN_GENERIC_ERROR;
633 goto err;
634 }
635 infer_request->tf_input->index = 0;
636
637 infer_request->input_tensor = allocate_input_tensor(&input);
638 if (!infer_request->input_tensor){
639 av_log(ctx, AV_LOG_ERROR, "Failed to allocate memory for input tensor\n");
640 ret = AVERROR(ENOMEM);
641 goto err;
642 }
643 input.data = (float *)TF_TensorData(infer_request->input_tensor);
644
645 switch (tf_model->model.func_type) {
647 if (task->do_ioproc) {
648 if (tf_model->model.frame_pre_proc != NULL) {
649 tf_model->model.frame_pre_proc(task->in_frame, &input, tf_model->model.filter_ctx);
650 } else {
651 ff_proc_from_frame_to_dnn(task->in_frame, &input, ctx);
652 }
653 }
654 break;
656 ff_frame_to_dnn_detect(task->in_frame, &input, ctx);
657 break;
658 default:
659 avpriv_report_missing_feature(ctx, "model function type %d", tf_model->model.func_type);
660 break;
661 }
662
663 infer_request->tf_outputs = av_malloc_array(task->nb_output, sizeof(TF_Output));
664 if (infer_request->tf_outputs == NULL) {
665 av_log(ctx, AV_LOG_ERROR, "Failed to allocate memory for *tf_outputs\n");
666 ret = AVERROR(ENOMEM);
667 goto err;
668 }
669
670 infer_request->output_tensors = av_calloc(task->nb_output, sizeof(*infer_request->output_tensors));
671 if (!infer_request->output_tensors) {
672 av_log(ctx, AV_LOG_ERROR, "Failed to allocate memory for output tensor\n");
673 ret = AVERROR(ENOMEM);
674 goto err;
675 }
676
677 for (int i = 0; i < task->nb_output; ++i) {
678 infer_request->output_tensors[i] = NULL;
679 infer_request->tf_outputs[i].oper = TF_GraphOperationByName(tf_model->graph, task->output_names[i]);
680 if (!infer_request->tf_outputs[i].oper) {
681 av_log(ctx, AV_LOG_ERROR, "Could not find output \"%s\" in model\n", task->output_names[i]);
682 ret = DNN_GENERIC_ERROR;
683 goto err;
684 }
685 infer_request->tf_outputs[i].index = 0;
686 }
687
688 return 0;
689err:
690 tf_free_request(infer_request);
691 return ret;
692}
693
694static void infer_completion_callback(void *args) {
695 TFRequestItem *request = args;
696 LastLevelTaskItem *lltask = request->lltask;
697 TaskItem *task = lltask->task;
699 TFInferRequest *infer_request = request->infer_request;
700 TFModel *tf_model = task->model;
701 DnnContext *ctx = tf_model->ctx;
702
703 outputs = av_calloc(task->nb_output, sizeof(*outputs));
704 if (!outputs) {
705 av_log(ctx, AV_LOG_ERROR, "Failed to allocate memory for *outputs\n");
706 goto err;
707 }
708
709 for (uint32_t i = 0; i < task->nb_output; ++i) {
710 outputs[i].layout = DL_NHWC;
712 TF_Dim(infer_request->output_tensors[i], 1);
714 TF_Dim(infer_request->output_tensors[i], 2);
716 TF_Dim(infer_request->output_tensors[i], 3);
717 outputs[i].data = TF_TensorData(infer_request->output_tensors[i]);
718 outputs[i].dt = (DNNDataType)TF_TensorType(infer_request->output_tensors[i]);
719 }
720 switch (tf_model->model.func_type) {
722 //it only support 1 output if it's frame in & frame out
723 if (task->do_ioproc) {
724 if (tf_model->model.frame_post_proc != NULL) {
725 tf_model->model.frame_post_proc(task->out_frame, outputs, tf_model->model.filter_ctx);
726 } else {
728 }
729 } else {
730 task->out_frame->width =
732 task->out_frame->height =
734 }
735 break;
737 if (!tf_model->model.detect_post_proc) {
738 av_log(ctx, AV_LOG_ERROR, "Detect filter needs provide post proc\n");
739 return;
740 }
741 tf_model->model.detect_post_proc(task->in_frame, outputs, task->nb_output, tf_model->model.filter_ctx);
742 break;
743 default:
744 av_log(ctx, AV_LOG_ERROR, "Tensorflow backend does not support this kind of dnn filter now\n");
745 goto err;
746 }
747 task->inference_done++;
748err:
749 tf_free_request(infer_request);
751
752 if (ff_safe_queue_push_back(tf_model->request_queue, request) < 0) {
753 destroy_request_item(&request);
754 av_log(ctx, AV_LOG_ERROR, "Failed to push back request_queue.\n");
755 }
756}
757
758static int execute_model_tf(TFRequestItem *request, Queue *lltask_queue)
759{
760 TFModel *tf_model;
762 LastLevelTaskItem *lltask;
763 TaskItem *task;
764 int ret = 0;
765
766 if (ff_queue_size(lltask_queue) == 0) {
767 destroy_request_item(&request);
768 return 0;
769 }
770
771 lltask = ff_queue_peek_front(lltask_queue);
772 task = lltask->task;
773 tf_model = task->model;
774 ctx = tf_model->ctx;
775
776 ret = fill_model_input_tf(tf_model, request);
777 if (ret != 0) {
778 goto err;
779 }
780
781 if (task->async) {
782 if (ff_dnn_start_inference_async(ctx, &request->exec_module) != 0) {
783 goto err;
784 }
785 return 0;
786 }
787 else {
788 ret = tf_start_inference(request);
789 if (ret != 0) {
790 goto err;
791 }
793 return (task->inference_done == task->inference_todo) ? 0 : DNN_GENERIC_ERROR;
794 }
795err:
797 if (ff_safe_queue_push_back(tf_model->request_queue, request) < 0) {
798 destroy_request_item(&request);
799 }
800
801 return ret;
802}
803
804static int dnn_execute_model_tf(const DNNModel *model, DNNExecBaseParams *exec_params)
805{
806 TFModel *tf_model = (TFModel *)model;
807 DnnContext *ctx = tf_model->ctx;
808 TaskItem *task;
809 TFRequestItem *request;
810 int ret = 0;
811
812 ret = ff_check_exec_params(ctx, DNN_TF, model->func_type, exec_params);
813 if (ret != 0) {
814 return ret;
815 }
816
817 task = av_malloc(sizeof(*task));
818 if (!task) {
819 av_log(ctx, AV_LOG_ERROR, "unable to alloc memory for task item.\n");
820 return AVERROR(ENOMEM);
821 }
822
823 ret = ff_dnn_fill_task(task, exec_params, tf_model, ctx->async, 1);
824 if (ret != 0) {
825 av_log(ctx, AV_LOG_ERROR, "Fill task with invalid parameter(s).\n");
826 av_freep(&task);
827 return ret;
828 }
829
830 if (ff_queue_push_back(tf_model->task_queue, task) < 0) {
831 av_freep(&task);
832 av_log(ctx, AV_LOG_ERROR, "unable to push back task_queue.\n");
833 return AVERROR(ENOMEM);
834 }
835
836 ret = extract_lltask_from_task(task, tf_model->lltask_queue);
837 if (ret != 0) {
838 av_log(ctx, AV_LOG_ERROR, "unable to extract last level task from task.\n");
839 return ret;
840 }
841
842 request = ff_safe_queue_pop_front(tf_model->request_queue);
843 if (!request) {
844 av_log(ctx, AV_LOG_ERROR, "unable to get infer request.\n");
845 return AVERROR(EINVAL);
846 }
847 return execute_model_tf(request, tf_model->lltask_queue);
848}
849
851{
852 TFModel *tf_model = (TFModel *)model;
853 return ff_dnn_get_result_common(tf_model->task_queue, in, out);
854}
855
856static int dnn_flush_tf(const DNNModel *model)
857{
858 TFModel *tf_model = (TFModel *)model;
859 DnnContext *ctx = tf_model->ctx;
860 TFRequestItem *request;
861 int ret;
862
863 if (ff_queue_size(tf_model->lltask_queue) == 0) {
864 // no pending task need to flush
865 return 0;
866 }
867
868 request = ff_safe_queue_pop_front(tf_model->request_queue);
869 if (!request) {
870 av_log(ctx, AV_LOG_ERROR, "unable to get infer request.\n");
871 return AVERROR(EINVAL);
872 }
873
874 ret = fill_model_input_tf(tf_model, request);
875 if (ret != 0) {
876 av_log(ctx, AV_LOG_ERROR, "Failed to fill model input.\n");
877 if (ff_safe_queue_push_back(tf_model->request_queue, request) < 0) {
878 destroy_request_item(&request);
879 }
880 return ret;
881 }
882
884}
885
887 .clazz = DNN_DEFINE_CLASS(dnn_tensorflow),
888 .type = DNN_TF,
889 .load_model = dnn_load_model_tf,
890 .execute_model = dnn_execute_model_tf,
891 .get_result = dnn_get_result_tf,
892 .flush = dnn_flush_tf,
893 .free_model = dnn_free_model_tf,
894};
static const AVFilterPad outputs[]
Definition af_aap.c:310
simple assert() macros that are a bit more flexible than ISO C assert().
#define av_assert0(cond)
assert() equivalent, that is always enabled.
Definition avassert.h:42
int avio_open(AVIOContext **s, const char *filename, int flags)
Create and initialize a AVIOContext for accessing the resource indicated by url.
Definition avio.c:565
int avio_closep(AVIOContext **s)
Close the resource accessed by the AVIOContext *s, free it and set the pointer pointing to it to NULL...
Definition avio.c:717
Buffered I/O operations.
#define AVIO_FLAG_READ
read-only
Definition avio.h:617
int64_t avio_size(AVIOContext *s)
Get the filesize.
Definition aviobuf.c:326
int avio_read(AVIOContext *s, unsigned char *buf, int size)
Read size bytes from AVIOContext into buf.
Definition aviobuf.c:615
#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
Misc types and constants that do not belong anywhere else.
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_start_inference_async(void *ctx, DNNAsyncExecModule *async_module)
Start asynchronous inference routine for the TensorFlow model on a detached thread.
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 void infer_completion_callback(void *args)
static int execute_model_tf(TFRequestItem *request, Queue *lltask_queue)
static int extract_lltask_from_task(TaskItem *task, Queue *lltask_queue)
static TFInferRequest * tf_create_inference_request(void)
Create a TensorFlow inference request.
static int get_output_tf(DNNModel *model, const char *input_name, int input_width, int input_height, const char *output_name, int *output_width, int *output_height)
static void tf_free_request(TFInferRequest *request)
Free the contents of TensorFlow inference request.
static int get_input_tf(DNNModel *model, DNNData *input, const char *input_name)
static int load_tf_model(TFModel *tf_model, const char *model_filename)
static int hex_to_data(uint8_t *data, const char *p)
static DNNAsyncStatusType dnn_get_result_tf(const DNNModel *model, AVFrame **in, AVFrame **out)
static DNNModel * dnn_load_model_tf(DnnContext *ctx, DNNFunctionType func_type, AVFilterContext *filter_ctx)
static int tf_start_inference(void *args)
Start synchronous inference for the TensorFlow model.
static int dnn_flush_tf(const DNNModel *model)
#define SPACE_CHARS
static TF_Buffer * read_graph(const char *model_filename)
static const AVOption dnn_tensorflow_options[]
static int dnn_execute_model_tf(const DNNModel *model, DNNExecBaseParams *exec_params)
static TF_Tensor * allocate_input_tensor(const DNNData *input)
const DNNModule ff_dnn_backend_tf
static void destroy_request_item(TFRequestItem **arg)
Free the TFRequestItem completely.
static int fill_model_input_tf(TFModel *tf_model, TFRequestItem *request)
#define OFFSET(x)
static void infer_completion_callback(void *args)
static void free_buffer(void *data, size_t length)
static void dnn_free_model_tf(DNNModel **model)
static int dnn_get_height_idx_by_layout(DNNLayout layout)
DNNAsyncStatusType
@ DL_NHWC
@ DNN_TF
DNNFunctionType
@ DFT_PROCESS_FRAME
@ DFT_ANALYTICS_DETECT
static int dnn_get_width_idx_by_layout(DNNLayout layout)
#define DNN_GENERIC_ERROR
DNNDataType
@ DNN_UINT8
@ 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.
@ AV_OPT_TYPE_STRING
Underlying C type is a uint8_t* that is either NULL or points to a C string allocated with the av_mal...
Definition opt.h:275
#define AV_INPUT_BUFFER_PADDING_SIZE
Required number of additionally allocated bytes at the end of the input bitstream for decoding.
Definition defs.h:40
#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_ERROR
Something went wrong and cannot losslessly be recovered.
Definition log.h:210
static av_const int av_toupper(int c)
Locale-independent conversion of ASCII characters to uppercase.
Definition avstring.h:227
const char * arg
Definition jacosubdec.c:65
int av_cpu_count(void)
Definition cpu.c:228
void avpriv_report_missing_feature(void *avc, const char *msg,...) av_printf_format(2
Log a generic warning message about a missing feature.
uint64_t layout
void * av_calloc(size_t nmemb, size_t size)
Definition mem.c:264
Memory handling functions.
const char data[16]
Definition mxf.c:149
#define av_malloc(s)
Definition ops_static.c:52
AVOptions.
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
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
Bytestream IO Context.
Definition avio.h:160
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.
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
DetectPostProc detect_post_proc
AVFilterContext * filter_ctx
DNNFunctionType func_type
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
Stores execution parameters for single call to the TensorFlow C API.
TF_Output * tf_outputs
TF_Tensor ** output_tensors
TF_Tensor * input_tensor
TF_Output * tf_input
SafeQueue * request_queue
DNNModel model
TF_Graph * graph
Queue * task_queue
TF_Status * status
Queue * lltask_queue
TF_Session * session
DnnContext * ctx
LastLevelTaskItem * lltask
TFInferRequest * infer_request
DNNAsyncExecModule exec_module
TF_Status * status
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_malloc_array(a, b)
#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
int size
int len
static double c[64]