[FFmpeg-devel] lavf/vf_stack: add keep_dar option for vstack and hstack

Submitted by lance.lmwang@gmail.com on July 8, 2019, 10:48 a.m.

Details

Message ID 1562582926-27231-1-git-send-email-lance.lmwang@gmail.com
State New
Headers show

Commit Message

lance.lmwang@gmail.com July 8, 2019, 10:48 a.m.
From: Limin Wang <lance.lmwang@gmail.com>

It's useful to compare two 4K videos quality side by side on 4K TV.

Signed-off-by: Limin Wang <lance.lmwang@gmail.com>
---
 doc/filters.texi       |  6 ++++++
 libavfilter/vf_stack.c | 31 ++++++++++++++++++++++++++-----
 2 files changed, 32 insertions(+), 5 deletions(-)

Comments

Moritz Barsnick July 8, 2019, 11:52 a.m.
On Mon, Jul 08, 2019 at 18:48:46 +0800, lance.lmwang@gmail.com wrote:
> +@item keep_dar
> +stack with 1/nb_inputs of each inputs screen to keep the same DAR, Default is 0.
                                  ^
Probably "input's". Is screen the correct term here at all? (In three
places in the patch.)

> +    { "keep_dar", "stack with 1/nb_inputs of each inputs screen to keep the same DAR", OFFSET(keep_dar), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, .flags = FLAGS },

AV_OPT_TYPE_BOOL

Not sure about the actual implementation, can't judge.

Cheers,
Moritz
Gyan July 8, 2019, 12:14 p.m.
On 08-07-2019 04:18 PM, lance.lmwang@gmail.com wrote:
> From: Limin Wang <lance.lmwang@gmail.com>
>
> It's useful to compare two 4K videos quality side by side on 4K TV.
>
> Signed-off-by: Limin Wang <lance.lmwang@gmail.com>
> ---
>   doc/filters.texi       |  6 ++++++
>   libavfilter/vf_stack.c | 31 ++++++++++++++++++++++++++-----
>   2 files changed, 32 insertions(+), 5 deletions(-)
>
> diff --git a/doc/filters.texi b/doc/filters.texi
> index ee6a93ffbf..675b02fc34 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -11280,6 +11280,9 @@ The filter accept the following option:
>   @item inputs
>   Set number of input streams. Default is 2.
>   
> +@item keep_dar
> +stack with 1/nb_inputs of each inputs screen to keep the same DAR, Default is 0.
> +
>   @item shortest
>   If set to 1, force the output to terminate when the shortest input
>   terminates. Default value is 0.
> @@ -18333,6 +18336,9 @@ The filter accept the following option:
>   @item inputs
>   Set number of input streams. Default is 2.
>   
> +@item keep_dar
> +stack with 1/nb_inputs of each inputs screen to keep the same DAR, Default is 0.
> +
>   @item shortest
>   If set to 1, force the output to terminate when the shortest input
>   terminates. Default value is 0.
> diff --git a/libavfilter/vf_stack.c b/libavfilter/vf_stack.c
> index 4d254e0013..9196b5b0b7 100644
> --- a/libavfilter/vf_stack.c
> +++ b/libavfilter/vf_stack.c
> @@ -44,6 +44,7 @@ typedef struct StackContext {
>       int is_vertical;
>       int is_horizontal;
>       int nb_planes;
> +    int keep_dar;
>   
>       StackItem *items;
>       AVFrame **frames;
> @@ -123,7 +124,7 @@ static int process_frame(FFFrameSync *fs)
>       StackContext *s = fs->opaque;
>       AVFrame **in = s->frames;
>       AVFrame *out;
> -    int i, p, ret, offset[4] = { 0 };
> +    int i, p, ret, offset[4] = { 0 }, in_offset[4] = { 0 };
>   
>       for (i = 0; i < s->nb_inputs; i++) {
>           if ((ret = ff_framesync_get_frame(&s->fs, i, &in[i], 0)) < 0)
> @@ -153,16 +154,25 @@ static int process_frame(FFFrameSync *fs)
>   
>           for (p = 0; p < s->nb_planes; p++) {
>               if (s->is_vertical) {
> +                if (s->keep_dar) {
> +                    height[p] /= s->nb_inputs;
> +                    in_offset[p] = i * height[p] * in[i]->linesize[p];
> +                }
> +
>                   av_image_copy_plane(out->data[p] + offset[p] * out->linesize[p],
>                                       out->linesize[p],
> -                                    in[i]->data[p],
> +                                    in[i]->data[p] + in_offset[p],
>                                       in[i]->linesize[p],
>                                       linesize[p], height[p]);
>                   offset[p] += height[p];
>               } else if (s->is_horizontal) {
> +                if (s->keep_dar) {
> +                    linesize[p] /= s->nb_inputs;
> +                    in_offset[p] = i * in[i]->linesize[p] / s->nb_inputs;
> +                }
>                   av_image_copy_plane(out->data[p] + offset[p],
>                                       out->linesize[p],
> -                                    in[i]->data[p],
> +                                    in[i]->data[p] + in_offset[p],
>                                       in[i]->linesize[p],
>                                       linesize[p], height[p]);
>                   offset[p] += linesize[p];
> @@ -197,20 +207,30 @@ static int config_output(AVFilterLink *outlink)
>           return AVERROR_BUG;
>   
>       if (s->is_vertical) {
> +        if (s->keep_dar)
> +            height /= s->nb_inputs;
>           for (i = 1; i < s->nb_inputs; i++) {
>               if (ctx->inputs[i]->w != width) {
>                   av_log(ctx, AV_LOG_ERROR, "Input %d width %d does not match input %d width %d.\n", i, ctx->inputs[i]->w, 0, width);
>                   return AVERROR(EINVAL);
>               }
> -            height += ctx->inputs[i]->h;
> +            if (!s->keep_dar)
> +                height += ctx->inputs[i]->h;
> +            else
> +                height += ctx->inputs[i]->h / s->nb_inputs;
>           }
>       } else if (s->is_horizontal) {
> +        if (s->keep_dar)
> +            width /= s->nb_inputs;
>           for (i = 1; i < s->nb_inputs; i++) {
>               if (ctx->inputs[i]->h != height) {
>                   av_log(ctx, AV_LOG_ERROR, "Input %d height %d does not match input %d height %d.\n", i, ctx->inputs[i]->h, 0, height);
>                   return AVERROR(EINVAL);
>               }
> -            width += ctx->inputs[i]->w;
> +            if (!s->keep_dar)
> +                width += ctx->inputs[i]->w;
> +            else
> +                width += ctx->inputs[i]->w / s->nb_inputs;
>           }
>       } else {
>           char *arg, *p = s->layout, *saveptr = NULL;
> @@ -339,6 +359,7 @@ static int activate(AVFilterContext *ctx)
>   #define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM
>   static const AVOption stack_options[] = {
>       { "inputs", "set number of inputs", OFFSET(nb_inputs), AV_OPT_TYPE_INT, {.i64=2}, 2, INT_MAX, .flags = FLAGS },
> +    { "keep_dar", "stack with 1/nb_inputs of each inputs screen to keep the same DAR", OFFSET(keep_dar), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, .flags = FLAGS },
>       { "shortest", "force termination when the shortest input terminates", OFFSET(shortest), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, .flags = FLAGS },
>       { NULL },
>   };

Keep DAR of which input i.e. what if the inputs have different DARs?

The user can already emulate this by cropping inputs beforehand.

Gyan
Paul B Mahol July 8, 2019, 12:23 p.m.
Not acceptable patch.

Please do not merge.

On 7/8/19, lance.lmwang@gmail.com <lance.lmwang@gmail.com> wrote:
> From: Limin Wang <lance.lmwang@gmail.com>
>
> It's useful to compare two 4K videos quality side by side on 4K TV.
>
> Signed-off-by: Limin Wang <lance.lmwang@gmail.com>
> ---
>  doc/filters.texi       |  6 ++++++
>  libavfilter/vf_stack.c | 31 ++++++++++++++++++++++++++-----
>  2 files changed, 32 insertions(+), 5 deletions(-)
>
> diff --git a/doc/filters.texi b/doc/filters.texi
> index ee6a93ffbf..675b02fc34 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -11280,6 +11280,9 @@ The filter accept the following option:
>  @item inputs
>  Set number of input streams. Default is 2.
>
> +@item keep_dar
> +stack with 1/nb_inputs of each inputs screen to keep the same DAR, Default
> is 0.
> +
>  @item shortest
>  If set to 1, force the output to terminate when the shortest input
>  terminates. Default value is 0.
> @@ -18333,6 +18336,9 @@ The filter accept the following option:
>  @item inputs
>  Set number of input streams. Default is 2.
>
> +@item keep_dar
> +stack with 1/nb_inputs of each inputs screen to keep the same DAR, Default
> is 0.
> +
>  @item shortest
>  If set to 1, force the output to terminate when the shortest input
>  terminates. Default value is 0.
> diff --git a/libavfilter/vf_stack.c b/libavfilter/vf_stack.c
> index 4d254e0013..9196b5b0b7 100644
> --- a/libavfilter/vf_stack.c
> +++ b/libavfilter/vf_stack.c
> @@ -44,6 +44,7 @@ typedef struct StackContext {
>      int is_vertical;
>      int is_horizontal;
>      int nb_planes;
> +    int keep_dar;
>
>      StackItem *items;
>      AVFrame **frames;
> @@ -123,7 +124,7 @@ static int process_frame(FFFrameSync *fs)
>      StackContext *s = fs->opaque;
>      AVFrame **in = s->frames;
>      AVFrame *out;
> -    int i, p, ret, offset[4] = { 0 };
> +    int i, p, ret, offset[4] = { 0 }, in_offset[4] = { 0 };
>
>      for (i = 0; i < s->nb_inputs; i++) {
>          if ((ret = ff_framesync_get_frame(&s->fs, i, &in[i], 0)) < 0)
> @@ -153,16 +154,25 @@ static int process_frame(FFFrameSync *fs)
>
>          for (p = 0; p < s->nb_planes; p++) {
>              if (s->is_vertical) {
> +                if (s->keep_dar) {
> +                    height[p] /= s->nb_inputs;
> +                    in_offset[p] = i * height[p] * in[i]->linesize[p];
> +                }
> +
>                  av_image_copy_plane(out->data[p] + offset[p] *
> out->linesize[p],
>                                      out->linesize[p],
> -                                    in[i]->data[p],
> +                                    in[i]->data[p] + in_offset[p],
>                                      in[i]->linesize[p],
>                                      linesize[p], height[p]);
>                  offset[p] += height[p];
>              } else if (s->is_horizontal) {
> +                if (s->keep_dar) {
> +                    linesize[p] /= s->nb_inputs;
> +                    in_offset[p] = i * in[i]->linesize[p] / s->nb_inputs;
> +                }
>                  av_image_copy_plane(out->data[p] + offset[p],
>                                      out->linesize[p],
> -                                    in[i]->data[p],
> +                                    in[i]->data[p] + in_offset[p],
>                                      in[i]->linesize[p],
>                                      linesize[p], height[p]);
>                  offset[p] += linesize[p];
> @@ -197,20 +207,30 @@ static int config_output(AVFilterLink *outlink)
>          return AVERROR_BUG;
>
>      if (s->is_vertical) {
> +        if (s->keep_dar)
> +            height /= s->nb_inputs;
>          for (i = 1; i < s->nb_inputs; i++) {
>              if (ctx->inputs[i]->w != width) {
>                  av_log(ctx, AV_LOG_ERROR, "Input %d width %d does not match
> input %d width %d.\n", i, ctx->inputs[i]->w, 0, width);
>                  return AVERROR(EINVAL);
>              }
> -            height += ctx->inputs[i]->h;
> +            if (!s->keep_dar)
> +                height += ctx->inputs[i]->h;
> +            else
> +                height += ctx->inputs[i]->h / s->nb_inputs;
>          }
>      } else if (s->is_horizontal) {
> +        if (s->keep_dar)
> +            width /= s->nb_inputs;
>          for (i = 1; i < s->nb_inputs; i++) {
>              if (ctx->inputs[i]->h != height) {
>                  av_log(ctx, AV_LOG_ERROR, "Input %d height %d does not
> match input %d height %d.\n", i, ctx->inputs[i]->h, 0, height);
>                  return AVERROR(EINVAL);
>              }
> -            width += ctx->inputs[i]->w;
> +            if (!s->keep_dar)
> +                width += ctx->inputs[i]->w;
> +            else
> +                width += ctx->inputs[i]->w / s->nb_inputs;
>          }
>      } else {
>          char *arg, *p = s->layout, *saveptr = NULL;
> @@ -339,6 +359,7 @@ static int activate(AVFilterContext *ctx)
>  #define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM
>  static const AVOption stack_options[] = {
>      { "inputs", "set number of inputs", OFFSET(nb_inputs), AV_OPT_TYPE_INT,
> {.i64=2}, 2, INT_MAX, .flags = FLAGS },
> +    { "keep_dar", "stack with 1/nb_inputs of each inputs screen to keep the
> same DAR", OFFSET(keep_dar), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, .flags = FLAGS
> },
>      { "shortest", "force termination when the shortest input terminates",
> OFFSET(shortest), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, .flags = FLAGS },
>      { NULL },
>  };
> --
> 2.21.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".
lance.lmwang@gmail.com July 8, 2019, 3:09 p.m.
On Mon, Jul 08, 2019 at 05:44:06PM +0530, Gyan wrote:
> 
> 
> On 08-07-2019 04:18 PM, lance.lmwang@gmail.com wrote:
> >From: Limin Wang <lance.lmwang@gmail.com>
> >
> >It's useful to compare two 4K videos quality side by side on 4K TV.
> >
> >Signed-off-by: Limin Wang <lance.lmwang@gmail.com>
> >---
> >  doc/filters.texi       |  6 ++++++
> >  libavfilter/vf_stack.c | 31 ++++++++++++++++++++++++++-----
> >  2 files changed, 32 insertions(+), 5 deletions(-)
> >
> >diff --git a/doc/filters.texi b/doc/filters.texi
> >index ee6a93ffbf..675b02fc34 100644
> >--- a/doc/filters.texi
> >+++ b/doc/filters.texi
> >@@ -11280,6 +11280,9 @@ The filter accept the following option:
> >  @item inputs
> >  Set number of input streams. Default is 2.
> >+@item keep_dar
> >+stack with 1/nb_inputs of each inputs screen to keep the same DAR, Default is 0.
> >+
> >  @item shortest
> >  If set to 1, force the output to terminate when the shortest input
> >  terminates. Default value is 0.
> >@@ -18333,6 +18336,9 @@ The filter accept the following option:
> >  @item inputs
> >  Set number of input streams. Default is 2.
> >+@item keep_dar
> >+stack with 1/nb_inputs of each inputs screen to keep the same DAR, Default is 0.
> >+
> >  @item shortest
> >  If set to 1, force the output to terminate when the shortest input
> >  terminates. Default value is 0.
> >diff --git a/libavfilter/vf_stack.c b/libavfilter/vf_stack.c
> >index 4d254e0013..9196b5b0b7 100644
> >--- a/libavfilter/vf_stack.c
> >+++ b/libavfilter/vf_stack.c
> >@@ -44,6 +44,7 @@ typedef struct StackContext {
> >      int is_vertical;
> >      int is_horizontal;
> >      int nb_planes;
> >+    int keep_dar;
> >      StackItem *items;
> >      AVFrame **frames;
> >@@ -123,7 +124,7 @@ static int process_frame(FFFrameSync *fs)
> >      StackContext *s = fs->opaque;
> >      AVFrame **in = s->frames;
> >      AVFrame *out;
> >-    int i, p, ret, offset[4] = { 0 };
> >+    int i, p, ret, offset[4] = { 0 }, in_offset[4] = { 0 };
> >      for (i = 0; i < s->nb_inputs; i++) {
> >          if ((ret = ff_framesync_get_frame(&s->fs, i, &in[i], 0)) < 0)
> >@@ -153,16 +154,25 @@ static int process_frame(FFFrameSync *fs)
> >          for (p = 0; p < s->nb_planes; p++) {
> >              if (s->is_vertical) {
> >+                if (s->keep_dar) {
> >+                    height[p] /= s->nb_inputs;
> >+                    in_offset[p] = i * height[p] * in[i]->linesize[p];
> >+                }
> >+
> >                  av_image_copy_plane(out->data[p] + offset[p] * out->linesize[p],
> >                                      out->linesize[p],
> >-                                    in[i]->data[p],
> >+                                    in[i]->data[p] + in_offset[p],
> >                                      in[i]->linesize[p],
> >                                      linesize[p], height[p]);
> >                  offset[p] += height[p];
> >              } else if (s->is_horizontal) {
> >+                if (s->keep_dar) {
> >+                    linesize[p] /= s->nb_inputs;
> >+                    in_offset[p] = i * in[i]->linesize[p] / s->nb_inputs;
> >+                }
> >                  av_image_copy_plane(out->data[p] + offset[p],
> >                                      out->linesize[p],
> >-                                    in[i]->data[p],
> >+                                    in[i]->data[p] + in_offset[p],
> >                                      in[i]->linesize[p],
> >                                      linesize[p], height[p]);
> >                  offset[p] += linesize[p];
> >@@ -197,20 +207,30 @@ static int config_output(AVFilterLink *outlink)
> >          return AVERROR_BUG;
> >      if (s->is_vertical) {
> >+        if (s->keep_dar)
> >+            height /= s->nb_inputs;
> >          for (i = 1; i < s->nb_inputs; i++) {
> >              if (ctx->inputs[i]->w != width) {
> >                  av_log(ctx, AV_LOG_ERROR, "Input %d width %d does not match input %d width %d.\n", i, ctx->inputs[i]->w, 0, width);
> >                  return AVERROR(EINVAL);
> >              }
> >-            height += ctx->inputs[i]->h;
> >+            if (!s->keep_dar)
> >+                height += ctx->inputs[i]->h;
> >+            else
> >+                height += ctx->inputs[i]->h / s->nb_inputs;
> >          }
> >      } else if (s->is_horizontal) {
> >+        if (s->keep_dar)
> >+            width /= s->nb_inputs;
> >          for (i = 1; i < s->nb_inputs; i++) {
> >              if (ctx->inputs[i]->h != height) {
> >                  av_log(ctx, AV_LOG_ERROR, "Input %d height %d does not match input %d height %d.\n", i, ctx->inputs[i]->h, 0, height);
> >                  return AVERROR(EINVAL);
> >              }
> >-            width += ctx->inputs[i]->w;
> >+            if (!s->keep_dar)
> >+                width += ctx->inputs[i]->w;
> >+            else
> >+                width += ctx->inputs[i]->w / s->nb_inputs;
> >          }
> >      } else {
> >          char *arg, *p = s->layout, *saveptr = NULL;
> >@@ -339,6 +359,7 @@ static int activate(AVFilterContext *ctx)
> >  #define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM
> >  static const AVOption stack_options[] = {
> >      { "inputs", "set number of inputs", OFFSET(nb_inputs), AV_OPT_TYPE_INT, {.i64=2}, 2, INT_MAX, .flags = FLAGS },
> >+    { "keep_dar", "stack with 1/nb_inputs of each inputs screen to keep the same DAR", OFFSET(keep_dar), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, .flags = FLAGS },
> >      { "shortest", "force termination when the shortest input terminates", OFFSET(shortest), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, .flags = FLAGS },
> >      { NULL },
> >  };
> 
> Keep DAR of which input i.e. what if the inputs have different DARs?

I'm using it for comparing two same video quality, it's not general
for all condition anyway. 

> 
> The user can already emulate this by cropping inputs befoehand.
Yes, we can use the crop filter to get the same function, however the
command line isn't very direct to use and difficult to recall.


> 
> Gyan
> _______________________________________________
> 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".
lance.lmwang@gmail.com July 8, 2019, 3:15 p.m.
On Mon, Jul 08, 2019 at 01:52:16PM +0200, Moritz Barsnick wrote:
> On Mon, Jul 08, 2019 at 18:48:46 +0800, lance.lmwang@gmail.com wrote:
> > +@item keep_dar
> > +stack with 1/nb_inputs of each inputs screen to keep the same DAR, Default is 0.
>                                   ^
> Probably "input's". Is screen the correct term here at all? (In three
> places in the patch.)
> 
> > +    { "keep_dar", "stack with 1/nb_inputs of each inputs screen to keep the same DAR", OFFSET(keep_dar), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, .flags = FLAGS },
> 
> AV_OPT_TYPE_BOOL
> 
Yes, I'll update for my local, thank for the review.

> Not sure about the actual implementation, can't judge.
> 
> Cheers,
> Moritz
> _______________________________________________
> 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".
Paul B Mahol July 8, 2019, 3:17 p.m.
>>
>> Keep DAR of which input i.e. what if the inputs have different DARs?
>
> I'm using it for comparing two same video quality, it's not general
> for all condition anyway.
>
>>
>> The user can already emulate this by cropping inputs befoehand.
> Yes, we can use the crop filter to get the same function, however the
> command line isn't very direct to use and difficult to recall.

Write scripts then.

Patch hide | download patch | download mbox

diff --git a/doc/filters.texi b/doc/filters.texi
index ee6a93ffbf..675b02fc34 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -11280,6 +11280,9 @@  The filter accept the following option:
 @item inputs
 Set number of input streams. Default is 2.
 
+@item keep_dar
+stack with 1/nb_inputs of each inputs screen to keep the same DAR, Default is 0.
+
 @item shortest
 If set to 1, force the output to terminate when the shortest input
 terminates. Default value is 0.
@@ -18333,6 +18336,9 @@  The filter accept the following option:
 @item inputs
 Set number of input streams. Default is 2.
 
+@item keep_dar
+stack with 1/nb_inputs of each inputs screen to keep the same DAR, Default is 0.
+
 @item shortest
 If set to 1, force the output to terminate when the shortest input
 terminates. Default value is 0.
diff --git a/libavfilter/vf_stack.c b/libavfilter/vf_stack.c
index 4d254e0013..9196b5b0b7 100644
--- a/libavfilter/vf_stack.c
+++ b/libavfilter/vf_stack.c
@@ -44,6 +44,7 @@  typedef struct StackContext {
     int is_vertical;
     int is_horizontal;
     int nb_planes;
+    int keep_dar;
 
     StackItem *items;
     AVFrame **frames;
@@ -123,7 +124,7 @@  static int process_frame(FFFrameSync *fs)
     StackContext *s = fs->opaque;
     AVFrame **in = s->frames;
     AVFrame *out;
-    int i, p, ret, offset[4] = { 0 };
+    int i, p, ret, offset[4] = { 0 }, in_offset[4] = { 0 };
 
     for (i = 0; i < s->nb_inputs; i++) {
         if ((ret = ff_framesync_get_frame(&s->fs, i, &in[i], 0)) < 0)
@@ -153,16 +154,25 @@  static int process_frame(FFFrameSync *fs)
 
         for (p = 0; p < s->nb_planes; p++) {
             if (s->is_vertical) {
+                if (s->keep_dar) {
+                    height[p] /= s->nb_inputs;
+                    in_offset[p] = i * height[p] * in[i]->linesize[p];
+                }
+
                 av_image_copy_plane(out->data[p] + offset[p] * out->linesize[p],
                                     out->linesize[p],
-                                    in[i]->data[p],
+                                    in[i]->data[p] + in_offset[p],
                                     in[i]->linesize[p],
                                     linesize[p], height[p]);
                 offset[p] += height[p];
             } else if (s->is_horizontal) {
+                if (s->keep_dar) {
+                    linesize[p] /= s->nb_inputs;
+                    in_offset[p] = i * in[i]->linesize[p] / s->nb_inputs;
+                }
                 av_image_copy_plane(out->data[p] + offset[p],
                                     out->linesize[p],
-                                    in[i]->data[p],
+                                    in[i]->data[p] + in_offset[p],
                                     in[i]->linesize[p],
                                     linesize[p], height[p]);
                 offset[p] += linesize[p];
@@ -197,20 +207,30 @@  static int config_output(AVFilterLink *outlink)
         return AVERROR_BUG;
 
     if (s->is_vertical) {
+        if (s->keep_dar)
+            height /= s->nb_inputs;
         for (i = 1; i < s->nb_inputs; i++) {
             if (ctx->inputs[i]->w != width) {
                 av_log(ctx, AV_LOG_ERROR, "Input %d width %d does not match input %d width %d.\n", i, ctx->inputs[i]->w, 0, width);
                 return AVERROR(EINVAL);
             }
-            height += ctx->inputs[i]->h;
+            if (!s->keep_dar)
+                height += ctx->inputs[i]->h;
+            else
+                height += ctx->inputs[i]->h / s->nb_inputs;
         }
     } else if (s->is_horizontal) {
+        if (s->keep_dar)
+            width /= s->nb_inputs;
         for (i = 1; i < s->nb_inputs; i++) {
             if (ctx->inputs[i]->h != height) {
                 av_log(ctx, AV_LOG_ERROR, "Input %d height %d does not match input %d height %d.\n", i, ctx->inputs[i]->h, 0, height);
                 return AVERROR(EINVAL);
             }
-            width += ctx->inputs[i]->w;
+            if (!s->keep_dar)
+                width += ctx->inputs[i]->w;
+            else
+                width += ctx->inputs[i]->w / s->nb_inputs;
         }
     } else {
         char *arg, *p = s->layout, *saveptr = NULL;
@@ -339,6 +359,7 @@  static int activate(AVFilterContext *ctx)
 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM
 static const AVOption stack_options[] = {
     { "inputs", "set number of inputs", OFFSET(nb_inputs), AV_OPT_TYPE_INT, {.i64=2}, 2, INT_MAX, .flags = FLAGS },
+    { "keep_dar", "stack with 1/nb_inputs of each inputs screen to keep the same DAR", OFFSET(keep_dar), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, .flags = FLAGS },
     { "shortest", "force termination when the shortest input terminates", OFFSET(shortest), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, .flags = FLAGS },
     { NULL },
 };