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 | 28 | #include <libplacebo/utils/libav.h> |
| 29 | 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 | 39 | enum { |
| 32 | 40 | TONE_MAP_AUTO, |
| 33 | 41 | TONE_MAP_CLIP, |
| @@ -555,35 +563,27 @@ fail: · 521 unmodified lines | ||
| 555 | 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; | |
| 561 | − LibplaceboContext *s = avctx->priv; | |
| 562 | − const AVPixFmtDescriptor *outdesc = av_pix_fmt_desc_get(out->format); | |
| 563 | − struct pl_frame image, target; | |
| 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 | − } | |
| 571 | + LibplaceboContext *s = ctx->priv; | |
| 572 | + const AVFrame *in = pl_get_mapped_avframe(image); | |
| 573 | + const AVFilterLink *inlink = ctx->inputs[0]; | |
| 574 | + const double in_pts = in->pts * av_q2d(inlink->time_base); | |
| 578 | 575 | |
| 579 | − if (!ok) { | |
| 580 | − err = AVERROR_EXTERNAL; | |
| 581 | − goto fail; | |
| 582 | − } | |
| 576 | + s->var_values[VAR_IN_T] = s->var_values[VAR_T] = in_pts; | |
| 577 | + s->var_values[VAR_OUT_T] = s->var_values[VAR_OT] = target_pts; | |
| 578 | + s->var_values[VAR_N] = inlink->frame_count_out; | |
| 583 | 579 | |
| 584 | − if (!s->apply_filmgrain) | |
| 585 | − image.film_grain.type = PL_FILM_GRAIN_NONE; | |
| 580 | + /* Clear these explicitly to avoid leaking previous frames' state */ | |
| 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 | 587 | s->var_values[VAR_CROP_W] = s->var_values[VAR_CW] = |
| 588 | 588 | av_expr_eval(s->crop_w_pexpr, s->var_values, NULL); |
| 589 | 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 | 593 | s->var_values[VAR_POS_H] = s->var_values[VAR_PH] = |
| 594 | 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); | |
| 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]; | |
| 599 | − image.crop.y1 = image.crop.y0 + s->var_values[VAR_CROP_H]; | |
| 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); | |
| 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]; | |
| 600 | 600 | |
| 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); | |
| 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]; | |
| 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); | |
| 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]; | |
| 605 | 605 | |
| 606 | 606 | if (s->target_sar.num) { |
| 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); | |
| 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; | |
| 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); | |
| 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; | |
| 634 | − AVFilterContext *ctx = link->dst; | |
| 615 | + int err = 0, ok, changed_csp; | |
| 635 | 616 | LibplaceboContext *s = ctx->priv; |
| 636 | 617 | AVFilterLink *outlink = ctx->outputs[0]; |
| 637 | − | |
| 618 | + const AVPixFmtDescriptor *outdesc = av_pix_fmt_desc_get(outlink->format); | |
| 638 | 619 | AVFrame *out = ff_get_video_buffer(outlink, outlink->w, outlink->h); |
| 639 | − if (!out) { | |
| 640 | − err = AVERROR(ENOMEM); | |
| 641 | − goto fail; | |
| 642 | − } | |
| 643 | − | |
| 644 | − pl_log_level_update(s->log, get_log_level()); | |
| 620 | + const AVFrame *in = pl_get_mapped_avframe(image); | |
| 621 | + struct pl_frame target; | |
| 622 | + if (!out) | |
| 623 | + return AVERROR(ENOMEM); | |
| 645 | 624 | |
| 646 | 625 | RET(av_frame_copy_props(out, in)); |
| 647 | 626 | out->width = outlink->w; |
| 648 | 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 | 629 | if (s->apply_dovi && av_frame_get_side_data(in, AV_FRAME_DATA_DOVI_METADATA)) { |
| 663 | 630 | /* Output of dovi reshaping is always BT.2020+PQ, so infer the correct |
| 664 | 631 | * output colorspace defaults */ |
| @@ -694,18 +661,65 @@ static int filter_frame(AVFilterLink *link, AVFrame *in) · 29 unmodified lines | ||
| 694 | 661 | if (s->apply_filmgrain) |
| 695 | 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 | 687 | return ff_filter_frame(outlink, out); |
| 702 | 688 | |
| 703 | 689 | fail: |
| 704 | − av_frame_free(&in); | |
| 705 | 690 | av_frame_free(&out); |
| 706 | 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 | 723 | static int libplacebo_query_format(AVFilterContext *ctx) |
| 710 | 724 | { |
| 711 | 725 | int err; |