diff mbox series

[FFmpeg-devel,2/7] lavfi/buffersink: add a flag for retrieving stream parameters

Message ID 20240923150146.31693-2-anton@khirnov.net
State New
Headers show
Series [FFmpeg-devel,1/7] lavfi/buffersink: set AVFrame.time_base | expand

Checks

Context Check Description
yinshiyou/configure_loongarch64 warning Failed to apply patch
andriy/configure_x86 warning Failed to apply patch

Commit Message

Anton Khirnov Sept. 23, 2024, 3:01 p.m. UTC
This way, av_buffersink_get_frame_flags() can replace almost all
av_buffersink_get_*(), including some future parameters we will want to
export.
---
 doc/APIchanges           |  1 +
 libavfilter/buffersink.c | 47 ++++++++++++++++++++++++++++++++++++++++
 libavfilter/buffersink.h | 16 ++++++++++++--
 3 files changed, 62 insertions(+), 2 deletions(-)

Comments

Nicolas George Sept. 23, 2024, 5:01 p.m. UTC | #1
Anton Khirnov (12024-09-23):
> This way, av_buffersink_get_frame_flags() can replace almost all
> av_buffersink_get_*(), including some future parameters we will want to
> export.
> ---
>  doc/APIchanges           |  1 +
>  libavfilter/buffersink.c | 47 ++++++++++++++++++++++++++++++++++++++++
>  libavfilter/buffersink.h | 16 ++++++++++++--
>  3 files changed, 62 insertions(+), 2 deletions(-)
> 
> diff --git a/doc/APIchanges b/doc/APIchanges
> index b392c756d7..5ddd7189f8 100644
> --- a/doc/APIchanges
> +++ b/doc/APIchanges
> @@ -4,6 +4,7 @@ API changes, most recent first:
>  
>  2024-09-xx - xxxxxxxxxx - lavfi 10.4.100
>    Buffersink now sets AVFrame.time_base on the frames it outputs.
> +  Add AV_BUFFERSINK_FLAG_PARAMS.
>  
>  2024-09-23 - xxxxxxxxxx - lavc 61.18.100 - avcodec.h
>    Add a new flag AV_CODEC_EXPORT_DATA_ENHANCEMENTS for export_side_data.
> diff --git a/libavfilter/buffersink.c b/libavfilter/buffersink.c
> index 575075ff47..d9cd074f17 100644
> --- a/libavfilter/buffersink.c
> +++ b/libavfilter/buffersink.c
> @@ -128,8 +128,55 @@ static int get_frame_internal(AVFilterContext *ctx, AVFrame *frame, int flags, i
>      }
>  }
>  
> +static int get_frame_params(AVFilterContext *ctx, AVFrame *frame)
> +{
> +    FilterLink *l = ff_filter_link(ctx->inputs[0]);
> +    int ret = 0;
> +
> +    frame->time_base = l->pub.time_base;
> +    frame->format    = l->pub.format;
> +
> +    switch (l->pub.type) {
> +    case AVMEDIA_TYPE_AUDIO:
> +        ret = av_channel_layout_copy(&frame->ch_layout, &l->pub.ch_layout);
> +        if (ret < 0)
> +            goto fail;
> +
> +        frame->sample_rate          = l->pub.sample_rate;
> +        break;
> +    case AVMEDIA_TYPE_VIDEO:
> +        frame->width                = l->pub.w;
> +        frame->height               = l->pub.h;
> +        frame->sample_aspect_ratio  = l->pub.sample_aspect_ratio;
> +        frame->colorspace           = l->pub.colorspace;
> +        frame->color_range          = l->pub.color_range;
> +        break;
> +    default: av_assert0(0);
> +    }
> +
> +    if (l->hw_frames_ctx) {
> +        frame->hw_frames_ctx = av_buffer_ref(l->hw_frames_ctx);
> +        if (!frame->hw_frames_ctx) {
> +            ret = AVERROR(ENOMEM);
> +            goto fail;
> +        }
> +    }
> +
> +    return 0;
> +fail:
> +    av_frame_unref(frame);
> +    return ret;
> +}
> +
>  int attribute_align_arg av_buffersink_get_frame_flags(AVFilterContext *ctx, AVFrame *frame, int flags)
>  {
> +    if (flags & AV_BUFFERSINK_FLAG_PARAMS) {
> +        if (flags & ~AV_BUFFERSINK_FLAG_PARAMS)
> +            return AVERROR(EINVAL);
> +
> +        return get_frame_params(ctx, frame);
> +    }
> +
>      return get_frame_internal(ctx, frame, flags,
>                                ff_filter_link(ctx->inputs[0])->min_samples);
>  }
> diff --git a/libavfilter/buffersink.h b/libavfilter/buffersink.h
> index 361d603679..9c4468af5b 100644
> --- a/libavfilter/buffersink.h
> +++ b/libavfilter/buffersink.h
> @@ -87,14 +87,26 @@ int av_buffersink_get_frame_flags(AVFilterContext *ctx, AVFrame *frame, int flag
>   * reference, but not remove it from the buffer. This is useful if you
>   * need only to read a video/samples buffer, without to fetch it.
>   */
> -#define AV_BUFFERSINK_FLAG_PEEK 1
> +#define AV_BUFFERSINK_FLAG_PEEK         (1 << 0)
>  
>  /**
>   * Tell av_buffersink_get_buffer_ref() not to request a frame from its input.
>   * If a frame is already buffered, it is read (and removed from the buffer),
>   * but if no frame is present, return AVERROR(EAGAIN).
>   */
> -#define AV_BUFFERSINK_FLAG_NO_REQUEST 2
> +#define AV_BUFFERSINK_FLAG_NO_REQUEST   (1 << 1)
> +

> +/**
> + * Retrieve stream parameters rather than frame data.
> + *
> + * When this flag is set, av_buffersink_get_frame_flags() fills the non-data
> + * fields of the supplied frame without causing any filtergraph activity.
> + *
> + * @note While frame data will be NULL, certain other allocated fields may be
> + * filled (e.g. ch_layout, hw_frames_ctx), so the frame should still be
> + * unreffed before reuse.
> + */
> +#define AV_BUFFERSINK_FLAG_PARAMS       (1 << 2)

NAK, this is one of the worst API design I can think of for this task.

>  
>  /**
>   * Set the frame size for an audio buffer sink.
Marton Balint Sept. 25, 2024, 8:25 a.m. UTC | #2
On Mon, 23 Sep 2024, Anton Khirnov wrote:

> This way, av_buffersink_get_frame_flags() can replace almost all
> av_buffersink_get_*(), including some future parameters we will want to
> export.

Why don't you simply expose the AVFilterLink which contains all the 
parameters of the sink? That seems a lot cleaner.

Thanks,
Marton


> ---
> doc/APIchanges           |  1 +
> libavfilter/buffersink.c | 47 ++++++++++++++++++++++++++++++++++++++++
> libavfilter/buffersink.h | 16 ++++++++++++--
> 3 files changed, 62 insertions(+), 2 deletions(-)
>
> diff --git a/doc/APIchanges b/doc/APIchanges
> index b392c756d7..5ddd7189f8 100644
> --- a/doc/APIchanges
> +++ b/doc/APIchanges
> @@ -4,6 +4,7 @@ API changes, most recent first:
>
> 2024-09-xx - xxxxxxxxxx - lavfi 10.4.100
>   Buffersink now sets AVFrame.time_base on the frames it outputs.
> +  Add AV_BUFFERSINK_FLAG_PARAMS.
>
> 2024-09-23 - xxxxxxxxxx - lavc 61.18.100 - avcodec.h
>   Add a new flag AV_CODEC_EXPORT_DATA_ENHANCEMENTS for export_side_data.
> diff --git a/libavfilter/buffersink.c b/libavfilter/buffersink.c
> index 575075ff47..d9cd074f17 100644
> --- a/libavfilter/buffersink.c
> +++ b/libavfilter/buffersink.c
> @@ -128,8 +128,55 @@ static int get_frame_internal(AVFilterContext *ctx, AVFrame *frame, int flags, i
>     }
> }
>
> +static int get_frame_params(AVFilterContext *ctx, AVFrame *frame)
> +{
> +    FilterLink *l = ff_filter_link(ctx->inputs[0]);
> +    int ret = 0;
> +
> +    frame->time_base = l->pub.time_base;
> +    frame->format    = l->pub.format;
> +
> +    switch (l->pub.type) {
> +    case AVMEDIA_TYPE_AUDIO:
> +        ret = av_channel_layout_copy(&frame->ch_layout, &l->pub.ch_layout);
> +        if (ret < 0)
> +            goto fail;
> +
> +        frame->sample_rate          = l->pub.sample_rate;
> +        break;
> +    case AVMEDIA_TYPE_VIDEO:
> +        frame->width                = l->pub.w;
> +        frame->height               = l->pub.h;
> +        frame->sample_aspect_ratio  = l->pub.sample_aspect_ratio;
> +        frame->colorspace           = l->pub.colorspace;
> +        frame->color_range          = l->pub.color_range;
> +        break;
> +    default: av_assert0(0);
> +    }
> +
> +    if (l->hw_frames_ctx) {
> +        frame->hw_frames_ctx = av_buffer_ref(l->hw_frames_ctx);
> +        if (!frame->hw_frames_ctx) {
> +            ret = AVERROR(ENOMEM);
> +            goto fail;
> +        }
> +    }
> +
> +    return 0;
> +fail:
> +    av_frame_unref(frame);
> +    return ret;
> +}
> +
> int attribute_align_arg av_buffersink_get_frame_flags(AVFilterContext *ctx, AVFrame *frame, int flags)
> {
> +    if (flags & AV_BUFFERSINK_FLAG_PARAMS) {
> +        if (flags & ~AV_BUFFERSINK_FLAG_PARAMS)
> +            return AVERROR(EINVAL);
> +
> +        return get_frame_params(ctx, frame);
> +    }
> +
>     return get_frame_internal(ctx, frame, flags,
>                               ff_filter_link(ctx->inputs[0])->min_samples);
> }
> diff --git a/libavfilter/buffersink.h b/libavfilter/buffersink.h
> index 361d603679..9c4468af5b 100644
> --- a/libavfilter/buffersink.h
> +++ b/libavfilter/buffersink.h
> @@ -87,14 +87,26 @@ int av_buffersink_get_frame_flags(AVFilterContext *ctx, AVFrame *frame, int flag
>  * reference, but not remove it from the buffer. This is useful if you
>  * need only to read a video/samples buffer, without to fetch it.
>  */
> -#define AV_BUFFERSINK_FLAG_PEEK 1
> +#define AV_BUFFERSINK_FLAG_PEEK         (1 << 0)
>
> /**
>  * Tell av_buffersink_get_buffer_ref() not to request a frame from its input.
>  * If a frame is already buffered, it is read (and removed from the buffer),
>  * but if no frame is present, return AVERROR(EAGAIN).
>  */
> -#define AV_BUFFERSINK_FLAG_NO_REQUEST 2
> +#define AV_BUFFERSINK_FLAG_NO_REQUEST   (1 << 1)
> +
> +/**
> + * Retrieve stream parameters rather than frame data.
> + *
> + * When this flag is set, av_buffersink_get_frame_flags() fills the non-data
> + * fields of the supplied frame without causing any filtergraph activity.
> + *
> + * @note While frame data will be NULL, certain other allocated fields may be
> + * filled (e.g. ch_layout, hw_frames_ctx), so the frame should still be
> + * unreffed before reuse.
> + */
> +#define AV_BUFFERSINK_FLAG_PARAMS       (1 << 2)
>
> /**
>  * Set the frame size for an audio buffer sink.
> -- 
> 2.43.0
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>
Anton Khirnov Sept. 25, 2024, 1:33 p.m. UTC | #3
Quoting Marton Balint (2024-09-25 10:25:44)
> 
> 
> On Mon, 23 Sep 2024, Anton Khirnov wrote:
> 
> > This way, av_buffersink_get_frame_flags() can replace almost all
> > av_buffersink_get_*(), including some future parameters we will want to
> > export.
> 
> Why don't you simply expose the AVFilterLink which contains all the 
> parameters of the sink? That seems a lot cleaner.

Because AVFilterLink is internal state of the filtergraph and should not
be exposed at all.
Nicolas George Sept. 25, 2024, 1:38 p.m. UTC | #4
Anton Khirnov (12024-09-25):
> Because AVFilterLink is internal state of the filtergraph and should not
> be exposed at all.

Anyway, if you cannot come up with an API that does not require dynamic
allocations and all the boilerplate code it requires just to retrieve a
few integers, then drop the series altogether.
Marton Balint Sept. 25, 2024, 7:27 p.m. UTC | #5
On Wed, 25 Sep 2024, Nicolas George wrote:

> Anton Khirnov (12024-09-25):
>> Because AVFilterLink is internal state of the filtergraph and should not
>> be exposed at all.
>
> Anyway, if you cannot come up with an API that does not require dynamic
> allocations and all the boilerplate code it requires just to retrieve a
> few integers, then drop the series altogether.

I kind of agree. It is suboptimal that the buffersink stores all its 
parameters already in a public struct, but you can only access them if you 
get yourself an owned copy with all the overhead of copying / 
referencing, plus now you have to do extra allocation for the results and 
error checking as well.

Also I don't like that you are misusing an AVFrame struct to pass 
parameters. How the user should know which parameters are set in AVFrame 
and which are not? A dedicated struct would be better, or just use 
AVFilterLink.

Regards,
Marton
James Almer Sept. 25, 2024, 7:39 p.m. UTC | #6
On 9/25/2024 4:27 PM, Marton Balint wrote:
> 
> 
> On Wed, 25 Sep 2024, Nicolas George wrote:
> 
>> Anton Khirnov (12024-09-25):
>>> Because AVFilterLink is internal state of the filtergraph and should not
>>> be exposed at all.
>>
>> Anyway, if you cannot come up with an API that does not require dynamic
>> allocations and all the boilerplate code it requires just to retrieve a
>> few integers, then drop the series altogether.
> 
> I kind of agree. It is suboptimal that the buffersink stores all its 
> parameters already in a public struct, but you can only access them if 
> you get yourself an owned copy with all the overhead of copying / 
> referencing, plus now you have to do extra allocation for the results 
> and error checking as well.
> 
> Also I don't like that you are misusing an AVFrame struct to pass 
> parameters. How the user should know which parameters are set in AVFrame 
> and which are not? A dedicated struct would be better, or just use 
> AVFilterLink.

There is AVBufferSrcParameters, which could be renamed and reused here. 
Exact same fields, but for the opposite purpose.
It still requires allocating it before using it, but since a normal 
filterchain will have buffersrc -> [...] -> buffersink, the same 
allocated struct can be used for both.

The main benefit i see from doing this is that, much like you don't 
create references/copies for the fields you put in AVBufferSrcParameters 
because av_buffersrc_parameters_set() is what internally will do that, 
buffersink wouldn't need to create references/copies to store anything 
there either, leaving that to the caller (who will be informed the 
fields in the output struct are still owned by the library).
Nicolas George Sept. 26, 2024, 2:45 p.m. UTC | #7
James Almer (12024-09-25):
> It still requires allocating it before using it, but since a normal
> filterchain will have buffersrc -> [...] -> buffersink, the same allocated
> struct can be used for both.

A filter chain can have buffersinks but no buffersrc, if it uses other
sources. Also, a dynamic allocation does the same damage to the code
readability and reliability whether it is in a speed-critical part of
the code or not.

We should be trying to REMOVE instances where a dynamic allocation is
necessary, not add to them. This consideration takes precedence over
cosmetic considerations like internal consistency or adherence to
established practices (aka coding like Java developers who see C for the
first time).

In this case, that means we should find a solution without dynamic
allocation for buffersink and then implement it for buffersrc also
rather than using the solution with dynamic allocation on both.

And we should do that especially because it is very easy: two solutions
came to me in the first few minutes, more could come.

- Let callers allocate their structure as they wish, on the stack if
  necessary, pass sizeof(params) as an extra argument and use memcpy()
  to fetch/return it.

- Have an instance of the struct in the buffersrc/sink and make it
  available for that use.

Regards,
diff mbox series

Patch

diff --git a/doc/APIchanges b/doc/APIchanges
index b392c756d7..5ddd7189f8 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -4,6 +4,7 @@  API changes, most recent first:
 
 2024-09-xx - xxxxxxxxxx - lavfi 10.4.100
   Buffersink now sets AVFrame.time_base on the frames it outputs.
+  Add AV_BUFFERSINK_FLAG_PARAMS.
 
 2024-09-23 - xxxxxxxxxx - lavc 61.18.100 - avcodec.h
   Add a new flag AV_CODEC_EXPORT_DATA_ENHANCEMENTS for export_side_data.
diff --git a/libavfilter/buffersink.c b/libavfilter/buffersink.c
index 575075ff47..d9cd074f17 100644
--- a/libavfilter/buffersink.c
+++ b/libavfilter/buffersink.c
@@ -128,8 +128,55 @@  static int get_frame_internal(AVFilterContext *ctx, AVFrame *frame, int flags, i
     }
 }
 
+static int get_frame_params(AVFilterContext *ctx, AVFrame *frame)
+{
+    FilterLink *l = ff_filter_link(ctx->inputs[0]);
+    int ret = 0;
+
+    frame->time_base = l->pub.time_base;
+    frame->format    = l->pub.format;
+
+    switch (l->pub.type) {
+    case AVMEDIA_TYPE_AUDIO:
+        ret = av_channel_layout_copy(&frame->ch_layout, &l->pub.ch_layout);
+        if (ret < 0)
+            goto fail;
+
+        frame->sample_rate          = l->pub.sample_rate;
+        break;
+    case AVMEDIA_TYPE_VIDEO:
+        frame->width                = l->pub.w;
+        frame->height               = l->pub.h;
+        frame->sample_aspect_ratio  = l->pub.sample_aspect_ratio;
+        frame->colorspace           = l->pub.colorspace;
+        frame->color_range          = l->pub.color_range;
+        break;
+    default: av_assert0(0);
+    }
+
+    if (l->hw_frames_ctx) {
+        frame->hw_frames_ctx = av_buffer_ref(l->hw_frames_ctx);
+        if (!frame->hw_frames_ctx) {
+            ret = AVERROR(ENOMEM);
+            goto fail;
+        }
+    }
+
+    return 0;
+fail:
+    av_frame_unref(frame);
+    return ret;
+}
+
 int attribute_align_arg av_buffersink_get_frame_flags(AVFilterContext *ctx, AVFrame *frame, int flags)
 {
+    if (flags & AV_BUFFERSINK_FLAG_PARAMS) {
+        if (flags & ~AV_BUFFERSINK_FLAG_PARAMS)
+            return AVERROR(EINVAL);
+
+        return get_frame_params(ctx, frame);
+    }
+
     return get_frame_internal(ctx, frame, flags,
                               ff_filter_link(ctx->inputs[0])->min_samples);
 }
diff --git a/libavfilter/buffersink.h b/libavfilter/buffersink.h
index 361d603679..9c4468af5b 100644
--- a/libavfilter/buffersink.h
+++ b/libavfilter/buffersink.h
@@ -87,14 +87,26 @@  int av_buffersink_get_frame_flags(AVFilterContext *ctx, AVFrame *frame, int flag
  * reference, but not remove it from the buffer. This is useful if you
  * need only to read a video/samples buffer, without to fetch it.
  */
-#define AV_BUFFERSINK_FLAG_PEEK 1
+#define AV_BUFFERSINK_FLAG_PEEK         (1 << 0)
 
 /**
  * Tell av_buffersink_get_buffer_ref() not to request a frame from its input.
  * If a frame is already buffered, it is read (and removed from the buffer),
  * but if no frame is present, return AVERROR(EAGAIN).
  */
-#define AV_BUFFERSINK_FLAG_NO_REQUEST 2
+#define AV_BUFFERSINK_FLAG_NO_REQUEST   (1 << 1)
+
+/**
+ * Retrieve stream parameters rather than frame data.
+ *
+ * When this flag is set, av_buffersink_get_frame_flags() fills the non-data
+ * fields of the supplied frame without causing any filtergraph activity.
+ *
+ * @note While frame data will be NULL, certain other allocated fields may be
+ * filled (e.g. ch_layout, hw_frames_ctx), so the frame should still be
+ * unreffed before reuse.
+ */
+#define AV_BUFFERSINK_FLAG_PARAMS       (1 << 2)
 
 /**
  * Set the frame size for an audio buffer sink.