This commit contains no functional change. The goal is merely to separate the highly intertwined `filter_frame` and `process_frames` functions into their separate concerns, specifically to separate frame uploading (which is now done directly in `filter_frame`) from emitting a frame (which is now done by a dedicated function `output_frame`). The overall idea here is to be able to ultimately call `output_frame` multiple times, to e.g. emit several output frames for a single input frame.
Unified · Split
| @@ -28,6 +28,14 @@ · 27 unmodified lines | |||
| 28 | #include <libplacebo/utils/libav.h> | 28 | #include <libplacebo/utils/libav.h> |
| 29 | #include <libplacebo/vulkan.h> | 29 | #include <libplacebo/vulkan.h> |
| 30 | 30 | ||
| 31 | /* Backwards compatibility with older libplacebo */ | ||
| 32 | #if PL_API_VER < 276 | ||
| 33 | static inline AVFrame *pl_get_mapped_avframe(const struct pl_frame *frame) | ||
| 34 | { | ||
| 35 | return frame->user_data; | ||
| 36 | } | ||
| 37 | #endif | ||
| 38 | |||
| 31 | enum { | 39 | enum { |
| 32 | TONE_MAP_AUTO, | 40 | TONE_MAP_AUTO, |
| 33 | TONE_MAP_CLIP, | 41 | TONE_MAP_CLIP, |
| @@ -555,35 +563,27 @@ fail: · 521 unmodified lines | |||
| 555 | return err; | 563 | return err; |
| 556 | } | 564 | } |
| 557 | 565 | ||
| 558 | static int process_frames(AVFilterContext *avctx, AVFrame *out, AVFrame *in) | 566 | static void update_crops(AVFilterContext *ctx, |
| 567 | struct pl_frame *image, | ||
| 568 | struct pl_frame *target, | ||
| 569 | const double target_pts) | ||
| 559 | { | 570 | { |
| 560 | int err = 0, ok; | 571 | LibplaceboContext *s = ctx->priv; |
| 561 | LibplaceboContext *s = avctx->priv; | 572 | const AVFrame *in = pl_get_mapped_avframe(image); |
| 562 | const AVPixFmtDescriptor *outdesc = av_pix_fmt_desc_get(out->format); | 573 | const AVFilterLink *inlink = ctx->inputs[0]; |
| 563 | struct pl_frame image, target; | 574 | const double in_pts = in->pts * av_q2d(inlink->time_base); |
| 564 | ok = pl_map_avframe_ex(s->gpu, &image, pl_avframe_params( | ||
| 565 | .frame = in, | ||
| 566 | .tex = s->tex, | ||
| 567 | .map_dovi = s->apply_dovi, | ||
| 568 | )); | ||
| 569 | |||
| 570 | if (outdesc->flags & AV_PIX_FMT_FLAG_HWACCEL) { | ||
| 571 | ok &= pl_map_avframe_ex(s->gpu, &target, pl_avframe_params( | ||
| 572 | .frame = out, | ||
| 573 | .map_dovi = false, | ||
| 574 | )); | ||
| 575 | } else { | ||
| 576 | ok &= pl_frame_recreate_from_avframe(s->gpu, &target, s->tex + 4, out); | ||
| 577 | } | ||
| 578 | 575 | ||
| 579 | if (!ok) { | 576 | s->var_values[VAR_IN_T] = s->var_values[VAR_T] = in_pts; |
| 580 | err = AVERROR_EXTERNAL; | 577 | s->var_values[VAR_OUT_T] = s->var_values[VAR_OT] = target_pts; |
| 581 | goto fail; | 578 | s->var_values[VAR_N] = inlink->frame_count_out; |
| 582 | } | ||
| 583 | 579 | ||
| 584 | if (!s->apply_filmgrain) | 580 | /* Clear these explicitly to avoid leaking previous frames' state */ |
| 585 | image.film_grain.type = PL_FILM_GRAIN_NONE; | 581 | s->var_values[VAR_CROP_W] = s->var_values[VAR_CW] = NAN; |
| 582 | s->var_values[VAR_CROP_H] = s->var_values[VAR_CH] = NAN; | ||
| 583 | s->var_values[VAR_POS_W] = s->var_values[VAR_PW] = NAN; | ||
| 584 | s->var_values[VAR_POS_H] = s->var_values[VAR_PH] = NAN; | ||
| 586 | 585 | ||
| 586 | /* Evaluate crop/pos dimensions first, and placement second */ | ||
| 587 | s->var_values[VAR_CROP_W] = s->var_values[VAR_CW] = | 587 | s->var_values[VAR_CROP_W] = s->var_values[VAR_CW] = |
| 588 | av_expr_eval(s->crop_w_pexpr, s->var_values, NULL); | 588 | av_expr_eval(s->crop_w_pexpr, s->var_values, NULL); |
| 589 | s->var_values[VAR_CROP_H] = s->var_values[VAR_CH] = | 589 | s->var_values[VAR_CROP_H] = s->var_values[VAR_CH] = |
| @@ -593,72 +593,39 @@ static int process_frames(AVFilterContext *avctx, AVFrame *out, AVFrame *in) · 3 unmodified lines | |||
| 593 | s->var_values[VAR_POS_H] = s->var_values[VAR_PH] = | 593 | s->var_values[VAR_POS_H] = s->var_values[VAR_PH] = |
| 594 | av_expr_eval(s->pos_h_pexpr, s->var_values, NULL); | 594 | av_expr_eval(s->pos_h_pexpr, s->var_values, NULL); |
| 595 | 595 | ||
| 596 | image.crop.x0 = av_expr_eval(s->crop_x_pexpr, s->var_values, NULL); | 596 | image->crop.x0 = av_expr_eval(s->crop_x_pexpr, s->var_values, NULL); |
| 597 | image.crop.y0 = av_expr_eval(s->crop_y_pexpr, s->var_values, NULL); | 597 | image->crop.y0 = av_expr_eval(s->crop_y_pexpr, s->var_values, NULL); |
| 598 | image.crop.x1 = image.crop.x0 + s->var_values[VAR_CROP_W]; | 598 | image->crop.x1 = image->crop.x0 + s->var_values[VAR_CROP_W]; |
| 599 | image.crop.y1 = image.crop.y0 + s->var_values[VAR_CROP_H]; | 599 | image->crop.y1 = image->crop.y0 + s->var_values[VAR_CROP_H]; |
| 600 | 600 | ||
| 601 | target.crop.x0 = av_expr_eval(s->pos_x_pexpr, s->var_values, NULL); | 601 | target->crop.x0 = av_expr_eval(s->pos_x_pexpr, s->var_values, NULL); |
| 602 | target.crop.y0 = av_expr_eval(s->pos_y_pexpr, s->var_values, NULL); | 602 | target->crop.y0 = av_expr_eval(s->pos_y_pexpr, s->var_values, NULL); |
| 603 | target.crop.x1 = target.crop.x0 + s->var_values[VAR_POS_W]; | 603 | target->crop.x1 = target->crop.x0 + s->var_values[VAR_POS_W]; |
| 604 | target.crop.y1 = target.crop.y0 + s->var_values[VAR_POS_H]; | 604 | target->crop.y1 = target->crop.y0 + s->var_values[VAR_POS_H]; |
| 605 | 605 | ||
| 606 | if (s->target_sar.num) { | 606 | if (s->target_sar.num) { |
| 607 | float aspect = pl_rect2df_aspect(&target.crop) * av_q2d(s->target_sar); | 607 | float aspect = pl_rect2df_aspect(&target->crop) * av_q2d(s->target_sar); |
| 608 | pl_rect2df_aspect_set(&target.crop, aspect, s->pad_crop_ratio); | 608 | pl_rect2df_aspect_set(&target->crop, aspect, s->pad_crop_ratio); |
| 609 | } | ||
| 610 | |||
| 611 | pl_render_image(s->renderer, &image, &target, &s->params); | ||
| 612 | pl_unmap_avframe(s->gpu, &image); | ||
| 613 | |||
| 614 | if (outdesc->flags & AV_PIX_FMT_FLAG_HWACCEL) { | ||
| 615 | pl_unmap_avframe(s->gpu, &target); | ||
| 616 | } else if (!pl_download_avframe(s->gpu, &target, out)) { | ||
| 617 | err = AVERROR_EXTERNAL; | ||
| 618 | goto fail; | ||
| 619 | } | 609 | } |
| 620 | |||
| 621 | /* Flush the command queues for performance */ | ||
| 622 | pl_gpu_flush(s->gpu); | ||
| 623 | return 0; | ||
| 624 | |||
| 625 | fail: | ||
| 626 | pl_unmap_avframe(s->gpu, &image); | ||
| 627 | pl_unmap_avframe(s->gpu, &target); | ||
| 628 | return err; | ||
| 629 | } | 610 | } |
| 630 | 611 | ||
| 631 | static int filter_frame(AVFilterLink *link, AVFrame *in) | 612 | /* Construct and emit an output frame for `image` */ |
| 613 | static int output_frame(AVFilterContext *ctx, struct pl_frame *image) | ||
| 632 | { | 614 | { |
| 633 | int err, changed_csp; | 615 | int err = 0, ok, changed_csp; |
| 634 | AVFilterContext *ctx = link->dst; | ||
| 635 | LibplaceboContext *s = ctx->priv; | 616 | LibplaceboContext *s = ctx->priv; |
| 636 | AVFilterLink *outlink = ctx->outputs[0]; | 617 | AVFilterLink *outlink = ctx->outputs[0]; |
| 637 | 618 | const AVPixFmtDescriptor *outdesc = av_pix_fmt_desc_get(outlink->format); | |
| 638 | AVFrame *out = ff_get_video_buffer(outlink, outlink->w, outlink->h); | 619 | AVFrame *out = ff_get_video_buffer(outlink, outlink->w, outlink->h); |
| 639 | if (!out) { | 620 | const AVFrame *in = pl_get_mapped_avframe(image); |
| 640 | err = AVERROR(ENOMEM); | 621 | struct pl_frame target; |
| 641 | goto fail; | 622 | if (!out) |
| 642 | } | 623 | return AVERROR(ENOMEM); |
| 643 | |||
| 644 | pl_log_level_update(s->log, get_log_level()); | ||
| 645 | 624 | ||
| 646 | RET(av_frame_copy_props(out, in)); | 625 | RET(av_frame_copy_props(out, in)); |
| 647 | out->width = outlink->w; | 626 | out->width = outlink->w; |
| 648 | out->height = outlink->h; | 627 | out->height = outlink->h; |
| 649 | 628 | ||
| 650 | /* Dynamic variables */ | ||
| 651 | s->var_values[VAR_IN_T] = s->var_values[VAR_T] = | ||
| 652 | in->pts == AV_NOPTS_VALUE ? NAN : in->pts * av_q2d(link->time_base); | ||
| 653 | s->var_values[VAR_OUT_T] = s->var_values[VAR_OT] = | ||
| 654 | out->pts == AV_NOPTS_VALUE ? NAN : out->pts * av_q2d(outlink->time_base); | ||
| 655 | s->var_values[VAR_N] = link->frame_count_out; | ||
| 656 | /* Will be evaluated/set by `process_frames` */ | ||
| 657 | s->var_values[VAR_CROP_W] = s->var_values[VAR_CW] = NAN; | ||
| 658 | s->var_values[VAR_CROP_H] = s->var_values[VAR_CH] = NAN; | ||
| 659 | s->var_values[VAR_POS_W] = s->var_values[VAR_PW] = NAN; | ||
| 660 | s->var_values[VAR_POS_H] = s->var_values[VAR_PH] = NAN; | ||
| 661 | |||
| 662 | if (s->apply_dovi && av_frame_get_side_data(in, AV_FRAME_DATA_DOVI_METADATA)) { | 629 | if (s->apply_dovi && av_frame_get_side_data(in, AV_FRAME_DATA_DOVI_METADATA)) { |
| 663 | /* Output of dovi reshaping is always BT.2020+PQ, so infer the correct | 630 | /* Output of dovi reshaping is always BT.2020+PQ, so infer the correct |
| 664 | * output colorspace defaults */ | 631 | * output colorspace defaults */ |
| @@ -694,18 +661,65 @@ static int filter_frame(AVFilterLink *link, AVFrame *in) · 29 unmodified lines | |||
| 694 | if (s->apply_filmgrain) | 661 | if (s->apply_filmgrain) |
| 695 | av_frame_remove_side_data(out, AV_FRAME_DATA_FILM_GRAIN_PARAMS); | 662 | av_frame_remove_side_data(out, AV_FRAME_DATA_FILM_GRAIN_PARAMS); |
| 696 | 663 | ||
| 697 | RET(process_frames(ctx, out, in)); | 664 | /* Map, render and unmap output frame */ |
| 665 | if (outdesc->flags & AV_PIX_FMT_FLAG_HWACCEL) { | ||
| 666 | ok = pl_map_avframe_ex(s->gpu, &target, pl_avframe_params( | ||
| 667 | .frame = out, | ||
| 668 | .map_dovi = false, | ||
| 669 | )); | ||
| 670 | } else { | ||
| 671 | ok = pl_frame_recreate_from_avframe(s->gpu, &target, s->tex + 4, out); | ||
| 672 | } | ||
| 673 | if (!ok) { | ||
| 674 | err = AVERROR_EXTERNAL; | ||
| 675 | goto fail; | ||
| 676 | } | ||
| 698 | 677 | ||
| 699 | av_frame_free(&in); | 678 | update_crops(ctx, image, &target, out->pts * av_q2d(outlink->time_base)); |
| 679 | pl_render_image(s->renderer, image, &target, &s->params); | ||
| 700 | 680 | ||
| 681 | if (outdesc->flags & AV_PIX_FMT_FLAG_HWACCEL) { | ||
| 682 | pl_unmap_avframe(s->gpu, &target); | ||
| 683 | } else if (!pl_download_avframe(s->gpu, &target, out)) { | ||
| 684 | err = AVERROR_EXTERNAL; | ||
| 685 | goto fail; | ||
| 686 | } | ||
| 701 | return ff_filter_frame(outlink, out); | 687 | return ff_filter_frame(outlink, out); |
| 702 | 688 | ||
| 703 | fail: | 689 | fail: |
| 704 | av_frame_free(&in); | ||
| 705 | av_frame_free(&out); | 690 | av_frame_free(&out); |
| 706 | return err; | 691 | return err; |
| 707 | } | 692 | } |
| 708 | 693 | ||
| 694 | static int filter_frame(AVFilterLink *link, AVFrame *in) | ||
| 695 | { | ||
| 696 | int ret, ok; | ||
| 697 | AVFilterContext *ctx = link->dst; | ||
| 698 | LibplaceboContext *s = ctx->priv; | ||
| 699 | struct pl_frame image; | ||
| 700 | |||
| 701 | pl_log_level_update(s->log, get_log_level()); | ||
| 702 | |||
| 703 | /* Map input frame */ | ||
| 704 | ok = pl_map_avframe_ex(s->gpu, &image, pl_avframe_params( | ||
| 705 | .frame = in, | ||
| 706 | .tex = s->tex, | ||
| 707 | .map_dovi = s->apply_dovi, | ||
| 708 | )); | ||
| 709 | av_frame_free(&in); | ||
| 710 | |||
| 711 | if (!s->apply_filmgrain) | ||
| 712 | image.film_grain.type = PL_FILM_GRAIN_NONE; | ||
| 713 | |||
| 714 | if (!ok) | ||
| 715 | return AVERROR_EXTERNAL; | ||
| 716 | |||
| 717 | ret = output_frame(ctx, &image); | ||
| 718 | |||
| 719 | pl_unmap_avframe(s->gpu, &image); | ||
| 720 | return ret; | ||
| 721 | } | ||
| 722 | |||
| 709 | static int libplacebo_query_format(AVFilterContext *ctx) | 723 | static int libplacebo_query_format(AVFilterContext *ctx) |
| 710 | { | 724 | { |
| 711 | int err; | 725 | int err; |