diff mbox series

[FFmpeg-devel,2/4,v6] avformat: add a Tile Grid stream group type

Message ID 20240131174718.17829-2-jamrial@gmail.com
State New
Headers show
Series [FFmpeg-devel,1/4,v3] avformat/mov: ignore item boxes for animated heif | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 success Make fate finished
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

James Almer Jan. 31, 2024, 5:47 p.m. UTC
This will be used to support tiled image formats like HEIF.

Signed-off-by: James Almer <jamrial@gmail.com>
---
 libavformat/avformat.c |   5 +++
 libavformat/avformat.h | 100 +++++++++++++++++++++++++++++++++++++++++
 libavformat/dump.c     |  29 ++++++++++++
 libavformat/options.c  |  32 +++++++++++++
 4 files changed, 166 insertions(+)

Comments

Andreas Rheinhardt Jan. 31, 2024, 6:08 p.m. UTC | #1
James Almer:
> This will be used to support tiled image formats like HEIF.
> 
> Signed-off-by: James Almer <jamrial@gmail.com>
> ---
>  libavformat/avformat.c |   5 +++
>  libavformat/avformat.h | 100 +++++++++++++++++++++++++++++++++++++++++
>  libavformat/dump.c     |  29 ++++++++++++
>  libavformat/options.c  |  32 +++++++++++++
>  4 files changed, 166 insertions(+)
> 
> diff --git a/libavformat/avformat.c b/libavformat/avformat.c
> index 8e8c6fbe55..32ef440207 100644
> --- a/libavformat/avformat.c
> +++ b/libavformat/avformat.c
> @@ -100,6 +100,11 @@ void ff_free_stream_group(AVStreamGroup **pstg)
>          av_iamf_mix_presentation_free(&stg->params.iamf_mix_presentation);
>          break;
>      }
> +    case AV_STREAM_GROUP_PARAMS_TILE_GRID:
> +        av_opt_free(stg->params.tile_grid);
> +        av_freep(&stg->params.tile_grid->offsets);
> +        av_freep(&stg->params.tile_grid);
> +        break;
>      default:
>          break;
>      }
> diff --git a/libavformat/avformat.h b/libavformat/avformat.h
> index 5d0fe82250..6577f13ef1 100644
> --- a/libavformat/avformat.h
> +++ b/libavformat/avformat.h
> @@ -1018,10 +1018,109 @@ typedef struct AVStream {
>      int pts_wrap_bits;
>  } AVStream;
>  
> +/**
> + * AVStreamGroupTileGrid holds information on how to combine several
> + * independent images on a single grid for presentation. None of the tiles may
> + * overlap inside the grid.
> + *
> + * The following is an example of a simple grid with 3 rows and 4 columns:
> + *
> + * +---+---+---+---+
> + * | 0 | 1 | 2 | 3 |
> + * +---+---+---+---+
> + * | 4 | 5 | 6 | 7 |
> + * +---+---+---+---+
> + * | 8 | 9 |10 |11 |
> + * +---+---+---+---+
> + *
> + * Assuming all tiles have a dimension of 512x512, the
> + * @ref AVStreamGroupTileGrid.offsets "offset" of the topleft pixel of
> + * the first @ref AVStreamGroup.streams "stream" in the group is "0,0", the
> + * @ref AVStreamGroupTileGrid.offsets "offset" of the topleft pixel of
> + * the second @ref AVStreamGroup.streams "stream" in the group is "512,0", the
> + * @ref AVStreamGroupTileGrid.offsets "offset" of the topleft pixel of
> + * the fifth @ref AVStreamGroup.streams "stream" in the group is "0,512", the
> + * @ref AVStreamGroupTileGrid.offsets "offset", of the topleft pixel of
> + * the sixth @ref AVStreamGroup.streams "stream" in the group is "512,512",
> + * etc.
> + *
> + * sizeof(AVStreamGroupTileGrid) is not a part of the ABI. No new fields may be
> + * added to this struct without a major version bump.

This is inconsistent. I think you mean that sizeof is part of the ABI.

> + */
> +typedef struct AVStreamGroupTileGrid {
> +    const AVClass *av_class;
> +
> +    /**
> +     * Width of the final image in the grid.
> +     *
> +     * Must be > 0.
> +     */
> +    int coded_width;
> +    /**
> +     * Width of the final image in the grid.
> +     *
> +     * Must be > 0.
> +     */
> +    int coded_height;
> +
> +    /**
> +     * An @ref AVStreamGroup.nb_streams "nb_streams" sized array of offsets in
> +     * pixels from the topleft edge of the grid, indicating where each stream
> +     * should be placed.
> +     * It must be allocated with the av_malloc() family of functions.
> +     *
> +     * - demuxing: set by libavformat, must not be modified by the caller.
> +     * - muxing: set by the caller before avformat_write_header().
> +     *
> +     * Freed by libavformat in avformat_free_context().
> +     */
> +    struct {
> +        int x;
> +        int y;
> +    } *offsets;
> +
> +    /**
> +     * Offset in pixels from the left edge of the grid where the actual image
> +     * meant for presentation starts.
> +     *
> +     * This field must be >= 0 and <= @ref coded_width.
> +     */
> +    int horizontal_offset;
> +    /**
> +     * Offset in pixels from the top edge of the grid where the actual image
> +     * meant for presentation starts.
> +     *
> +     * This field must be >= 0 and <= @ref coded_height.
> +     */
> +    int vertical_offset;
> +
> +    /**
> +     * Width of the final image for presentation.
> +     *
> +     * Must be > 0 and <= (@ref coded_width - @ref horizontal_offset).
> +     * When it's not equal to (@ref coded_width - @ref horizontal_offset), the
> +     * result of (@ref coded_width - width - @ref horizontal_offset) is the
> +     * amount amount of pixels to be cropped from the right edge of the
> +     * final image before presentation.
> +     */
> +    int width;
> +    /**
> +     * Height of the final image for presentation.
> +     *
> +     * Must be > 0 and <= (@ref coded_height - @ref vertical_offset).
> +     * When it's not equal to (@ref coded_height - @ref vertical_offset), the
> +     * result of (@ref coded_height - height - @ref vertical_offset) is the
> +     * amount amount of pixels to be cropped from the bototm edge of the
> +     * final image before presentation.
> +     */
> +    int height;
> +} AVStreamGroupTileGrid;
> +
>  enum AVStreamGroupParamsType {
>      AV_STREAM_GROUP_PARAMS_NONE,
>      AV_STREAM_GROUP_PARAMS_IAMF_AUDIO_ELEMENT,
>      AV_STREAM_GROUP_PARAMS_IAMF_MIX_PRESENTATION,
> +    AV_STREAM_GROUP_PARAMS_TILE_GRID,
>  };
>  
>  struct AVIAMFAudioElement;
> @@ -1062,6 +1161,7 @@ typedef struct AVStreamGroup {
>      union {
>          struct AVIAMFAudioElement *iamf_audio_element;
>          struct AVIAMFMixPresentation *iamf_mix_presentation;
> +        struct AVStreamGroupTileGrid *tile_grid;
>      } params;
>  
>      /**
> diff --git a/libavformat/dump.c b/libavformat/dump.c
> index aff51b43f6..c9b7369bcd 100644
> --- a/libavformat/dump.c
> +++ b/libavformat/dump.c
> @@ -22,6 +22,7 @@
>  #include <stdio.h>
>  #include <stdint.h>
>  
> +#include "libavutil/avstring.h"
>  #include "libavutil/channel_layout.h"
>  #include "libavutil/display.h"
>  #include "libavutil/iamf.h"
> @@ -721,6 +722,34 @@ static void dump_stream_group(const AVFormatContext *ic, uint8_t *printed,
>          }
>          break;
>      }
> +    case AV_STREAM_GROUP_PARAMS_TILE_GRID: {
> +        const AVStreamGroupTileGrid *tile_grid = stg->params.tile_grid;
> +        AVCodecContext *avctx = avcodec_alloc_context3(NULL);
> +        const char *ptr = NULL;
> +        av_log(NULL, AV_LOG_INFO, " Tile Grid:");
> +        if (avctx && stg->nb_streams && !avcodec_parameters_to_context(avctx, stg->streams[0]->codecpar)) {
> +            avctx->width  = tile_grid->width;
> +            avctx->height = tile_grid->height;
> +            avctx->coded_width  = tile_grid->coded_width;
> +            avctx->coded_height = tile_grid->coded_height;
> +            if (ic->dump_separator)
> +                av_opt_set(avctx, "dump_separator", ic->dump_separator, 0);
> +            buf[0] = 0;
> +            avcodec_string(buf, sizeof(buf), avctx, is_output);
> +            ptr = av_stristr(buf, " ");
> +        }
> +        avcodec_free_context(&avctx);
> +        if (ptr)
> +            av_log(NULL, AV_LOG_INFO, "%s", ptr);
> +        av_log(NULL, AV_LOG_INFO, "\n");
> +        dump_metadata(NULL, stg->metadata, "    ", AV_LOG_INFO);
> +        for (int i = 0; i < stg->nb_streams; i++) {
> +            const AVStream *st = stg->streams[i];
> +            dump_stream_format(ic, st->index, i, index, is_output, AV_LOG_VERBOSE);
> +            printed[st->index] = 1;
> +        }
> +        break;
> +    }
>      default:
>          break;
>      }
> diff --git a/libavformat/options.c b/libavformat/options.c
> index 75ec86ce05..ed96ba4afb 100644
> --- a/libavformat/options.c
> +++ b/libavformat/options.c
> @@ -337,6 +337,26 @@ fail:
>      return NULL;
>  }
>  
> +#define FLAGS AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_VIDEO_PARAM
> +#define OFFSET(x) offsetof(AVStreamGroupTileGrid, x)
> +static const AVOption tile_grid_options[] = {
> +    { "grid_size", "size of the output image", OFFSET(coded_width),
> +        AV_OPT_TYPE_IMAGE_SIZE, { .str = NULL }, 0, INT_MAX, FLAGS },
> +    { "horizontal_offset", NULL, OFFSET(horizontal_offset), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS },
> +    { "vertical_offset", NULL, OFFSET(vertical_offset),     AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS },
> +    { "output_size", "size of valid pixels in output image meant for presentation", OFFSET(width),
> +        AV_OPT_TYPE_IMAGE_SIZE, { .str = NULL }, 0, INT_MAX, FLAGS },
> +    { NULL },
> +};
> +#undef FLAGS
> +#undef OFFSET
> +
> +static const AVClass tile_grid_class = {
> +    .class_name = "AVStreamGroupTileGrid",
> +    .version    = LIBAVUTIL_VERSION_INT,
> +    .option     = tile_grid_options,
> +};
> +
>  static void *stream_group_child_next(void *obj, void *prev)
>  {
>      AVStreamGroup *stg = obj;
> @@ -346,6 +366,8 @@ static void *stream_group_child_next(void *obj, void *prev)
>              return stg->params.iamf_audio_element;
>          case AV_STREAM_GROUP_PARAMS_IAMF_MIX_PRESENTATION:
>              return stg->params.iamf_mix_presentation;
> +        case AV_STREAM_GROUP_PARAMS_TILE_GRID:
> +            return stg->params.tile_grid;
>          default:
>              break;
>          }
> @@ -368,6 +390,9 @@ static const AVClass *stream_group_child_iterate(void **opaque)
>      case AV_STREAM_GROUP_PARAMS_IAMF_MIX_PRESENTATION:
>          ret = av_iamf_mix_presentation_get_class();
>          break;
> +    case AV_STREAM_GROUP_PARAMS_TILE_GRID:
> +        ret = &tile_grid_class;
> +        break;
>      default:
>          break;
>      }
> @@ -429,6 +454,13 @@ AVStreamGroup *avformat_stream_group_create(AVFormatContext *s,
>          if (!stg->params.iamf_mix_presentation)
>              goto fail;
>          break;
> +    case AV_STREAM_GROUP_PARAMS_TILE_GRID:
> +        stg->params.tile_grid = av_mallocz(sizeof(*stg->params.tile_grid));
> +        if (!stg->params.tile_grid)
> +            goto fail;
> +        stg->params.tile_grid->av_class = &tile_grid_class;
> +        av_opt_set_defaults(stg->params.tile_grid);
> +        break;
>      default:
>          goto fail;
>      }
James Almer Jan. 31, 2024, 6:21 p.m. UTC | #2
On 1/31/2024 3:08 PM, Andreas Rheinhardt wrote:
> James Almer:
>> This will be used to support tiled image formats like HEIF.
>>
>> Signed-off-by: James Almer<jamrial@gmail.com>
>> ---
>>   libavformat/avformat.c |   5 +++
>>   libavformat/avformat.h | 100 +++++++++++++++++++++++++++++++++++++++++
>>   libavformat/dump.c     |  29 ++++++++++++
>>   libavformat/options.c  |  32 +++++++++++++
>>   4 files changed, 166 insertions(+)
>>
>> diff --git a/libavformat/avformat.c b/libavformat/avformat.c
>> index 8e8c6fbe55..32ef440207 100644
>> --- a/libavformat/avformat.c
>> +++ b/libavformat/avformat.c
>> @@ -100,6 +100,11 @@ void ff_free_stream_group(AVStreamGroup **pstg)
>>           av_iamf_mix_presentation_free(&stg->params.iamf_mix_presentation);
>>           break;
>>       }
>> +    case AV_STREAM_GROUP_PARAMS_TILE_GRID:
>> +        av_opt_free(stg->params.tile_grid);
>> +        av_freep(&stg->params.tile_grid->offsets);
>> +        av_freep(&stg->params.tile_grid);
>> +        break;
>>       default:
>>           break;
>>       }
>> diff --git a/libavformat/avformat.h b/libavformat/avformat.h
>> index 5d0fe82250..6577f13ef1 100644
>> --- a/libavformat/avformat.h
>> +++ b/libavformat/avformat.h
>> @@ -1018,10 +1018,109 @@ typedef struct AVStream {
>>       int pts_wrap_bits;
>>   } AVStream;
>>   
>> +/**
>> + * AVStreamGroupTileGrid holds information on how to combine several
>> + * independent images on a single grid for presentation. None of the tiles may
>> + * overlap inside the grid.
>> + *
>> + * The following is an example of a simple grid with 3 rows and 4 columns:
>> + *
>> + * +---+---+---+---+
>> + * | 0 | 1 | 2 | 3 |
>> + * +---+---+---+---+
>> + * | 4 | 5 | 6 | 7 |
>> + * +---+---+---+---+
>> + * | 8 | 9 |10 |11 |
>> + * +---+---+---+---+
>> + *
>> + * Assuming all tiles have a dimension of 512x512, the
>> + * @ref AVStreamGroupTileGrid.offsets "offset" of the topleft pixel of
>> + * the first @ref AVStreamGroup.streams "stream" in the group is "0,0", the
>> + * @ref AVStreamGroupTileGrid.offsets "offset" of the topleft pixel of
>> + * the second @ref AVStreamGroup.streams "stream" in the group is "512,0", the
>> + * @ref AVStreamGroupTileGrid.offsets "offset" of the topleft pixel of
>> + * the fifth @ref AVStreamGroup.streams "stream" in the group is "0,512", the
>> + * @ref AVStreamGroupTileGrid.offsets "offset", of the topleft pixel of
>> + * the sixth @ref AVStreamGroup.streams "stream" in the group is "512,512",
>> + * etc.
>> + *
>> + * sizeof(AVStreamGroupTileGrid) is not a part of the ABI. No new fields may be
>> + * added to this struct without a major version bump.
> This is inconsistent. I think you mean that sizeof is part of the ABI.

No? It's not a part of the ABI because AVStreamGroupTileGrid must not be 
ever used on stack, or allocated by anything other than AVStreamGroup.
That way we can add fields to it without waiting for a major bump.
Andreas Rheinhardt Jan. 31, 2024, 6:33 p.m. UTC | #3
James Almer:
> On 1/31/2024 3:08 PM, Andreas Rheinhardt wrote:
>> James Almer:
>>> This will be used to support tiled image formats like HEIF.
>>>
>>> Signed-off-by: James Almer<jamrial@gmail.com>
>>> ---
>>>   libavformat/avformat.c |   5 +++
>>>   libavformat/avformat.h | 100 +++++++++++++++++++++++++++++++++++++++++
>>>   libavformat/dump.c     |  29 ++++++++++++
>>>   libavformat/options.c  |  32 +++++++++++++
>>>   4 files changed, 166 insertions(+)
>>>
>>> diff --git a/libavformat/avformat.c b/libavformat/avformat.c
>>> index 8e8c6fbe55..32ef440207 100644
>>> --- a/libavformat/avformat.c
>>> +++ b/libavformat/avformat.c
>>> @@ -100,6 +100,11 @@ void ff_free_stream_group(AVStreamGroup **pstg)
>>>          
>>> av_iamf_mix_presentation_free(&stg->params.iamf_mix_presentation);
>>>           break;
>>>       }
>>> +    case AV_STREAM_GROUP_PARAMS_TILE_GRID:
>>> +        av_opt_free(stg->params.tile_grid);
>>> +        av_freep(&stg->params.tile_grid->offsets);
>>> +        av_freep(&stg->params.tile_grid);
>>> +        break;
>>>       default:
>>>           break;
>>>       }
>>> diff --git a/libavformat/avformat.h b/libavformat/avformat.h
>>> index 5d0fe82250..6577f13ef1 100644
>>> --- a/libavformat/avformat.h
>>> +++ b/libavformat/avformat.h
>>> @@ -1018,10 +1018,109 @@ typedef struct AVStream {
>>>       int pts_wrap_bits;
>>>   } AVStream;
>>>   +/**
>>> + * AVStreamGroupTileGrid holds information on how to combine several
>>> + * independent images on a single grid for presentation. None of the
>>> tiles may
>>> + * overlap inside the grid.
>>> + *
>>> + * The following is an example of a simple grid with 3 rows and 4
>>> columns:
>>> + *
>>> + * +---+---+---+---+
>>> + * | 0 | 1 | 2 | 3 |
>>> + * +---+---+---+---+
>>> + * | 4 | 5 | 6 | 7 |
>>> + * +---+---+---+---+
>>> + * | 8 | 9 |10 |11 |
>>> + * +---+---+---+---+
>>> + *
>>> + * Assuming all tiles have a dimension of 512x512, the
>>> + * @ref AVStreamGroupTileGrid.offsets "offset" of the topleft pixel of
>>> + * the first @ref AVStreamGroup.streams "stream" in the group is
>>> "0,0", the
>>> + * @ref AVStreamGroupTileGrid.offsets "offset" of the topleft pixel of
>>> + * the second @ref AVStreamGroup.streams "stream" in the group is
>>> "512,0", the
>>> + * @ref AVStreamGroupTileGrid.offsets "offset" of the topleft pixel of
>>> + * the fifth @ref AVStreamGroup.streams "stream" in the group is
>>> "0,512", the
>>> + * @ref AVStreamGroupTileGrid.offsets "offset", of the topleft pixel of
>>> + * the sixth @ref AVStreamGroup.streams "stream" in the group is
>>> "512,512",
>>> + * etc.
>>> + *
>>> + * sizeof(AVStreamGroupTileGrid) is not a part of the ABI. No new
>>> fields may be
>>> + * added to this struct without a major version bump.
>> This is inconsistent. I think you mean that sizeof is part of the ABI.
> 
> No? It's not a part of the ABI because AVStreamGroupTileGrid must not be
> ever used on stack, or allocated by anything other than AVStreamGroup.
> That way we can add fields to it without waiting for a major bump.

"No new fields may be added to this struct without a major version bump"

- Andreas
James Almer Jan. 31, 2024, 6:33 p.m. UTC | #4
On 1/31/2024 3:33 PM, Andreas Rheinhardt wrote:
> James Almer:
>> On 1/31/2024 3:08 PM, Andreas Rheinhardt wrote:
>>> James Almer:
>>>> This will be used to support tiled image formats like HEIF.
>>>>
>>>> Signed-off-by: James Almer<jamrial@gmail.com>
>>>> ---
>>>>    libavformat/avformat.c |   5 +++
>>>>    libavformat/avformat.h | 100 +++++++++++++++++++++++++++++++++++++++++
>>>>    libavformat/dump.c     |  29 ++++++++++++
>>>>    libavformat/options.c  |  32 +++++++++++++
>>>>    4 files changed, 166 insertions(+)
>>>>
>>>> diff --git a/libavformat/avformat.c b/libavformat/avformat.c
>>>> index 8e8c6fbe55..32ef440207 100644
>>>> --- a/libavformat/avformat.c
>>>> +++ b/libavformat/avformat.c
>>>> @@ -100,6 +100,11 @@ void ff_free_stream_group(AVStreamGroup **pstg)
>>>>           
>>>> av_iamf_mix_presentation_free(&stg->params.iamf_mix_presentation);
>>>>            break;
>>>>        }
>>>> +    case AV_STREAM_GROUP_PARAMS_TILE_GRID:
>>>> +        av_opt_free(stg->params.tile_grid);
>>>> +        av_freep(&stg->params.tile_grid->offsets);
>>>> +        av_freep(&stg->params.tile_grid);
>>>> +        break;
>>>>        default:
>>>>            break;
>>>>        }
>>>> diff --git a/libavformat/avformat.h b/libavformat/avformat.h
>>>> index 5d0fe82250..6577f13ef1 100644
>>>> --- a/libavformat/avformat.h
>>>> +++ b/libavformat/avformat.h
>>>> @@ -1018,10 +1018,109 @@ typedef struct AVStream {
>>>>        int pts_wrap_bits;
>>>>    } AVStream;
>>>>    +/**
>>>> + * AVStreamGroupTileGrid holds information on how to combine several
>>>> + * independent images on a single grid for presentation. None of the
>>>> tiles may
>>>> + * overlap inside the grid.
>>>> + *
>>>> + * The following is an example of a simple grid with 3 rows and 4
>>>> columns:
>>>> + *
>>>> + * +---+---+---+---+
>>>> + * | 0 | 1 | 2 | 3 |
>>>> + * +---+---+---+---+
>>>> + * | 4 | 5 | 6 | 7 |
>>>> + * +---+---+---+---+
>>>> + * | 8 | 9 |10 |11 |
>>>> + * +---+---+---+---+
>>>> + *
>>>> + * Assuming all tiles have a dimension of 512x512, the
>>>> + * @ref AVStreamGroupTileGrid.offsets "offset" of the topleft pixel of
>>>> + * the first @ref AVStreamGroup.streams "stream" in the group is
>>>> "0,0", the
>>>> + * @ref AVStreamGroupTileGrid.offsets "offset" of the topleft pixel of
>>>> + * the second @ref AVStreamGroup.streams "stream" in the group is
>>>> "512,0", the
>>>> + * @ref AVStreamGroupTileGrid.offsets "offset" of the topleft pixel of
>>>> + * the fifth @ref AVStreamGroup.streams "stream" in the group is
>>>> "0,512", the
>>>> + * @ref AVStreamGroupTileGrid.offsets "offset", of the topleft pixel of
>>>> + * the sixth @ref AVStreamGroup.streams "stream" in the group is
>>>> "512,512",
>>>> + * etc.
>>>> + *
>>>> + * sizeof(AVStreamGroupTileGrid) is not a part of the ABI. No new
>>>> fields may be
>>>> + * added to this struct without a major version bump.
>>> This is inconsistent. I think you mean that sizeof is part of the ABI.
>>
>> No? It's not a part of the ABI because AVStreamGroupTileGrid must not be
>> ever used on stack, or allocated by anything other than AVStreamGroup.
>> That way we can add fields to it without waiting for a major bump.
> 
> "No new fields may be added to this struct without a major version bump"

Ah, i see. Copy paste fail. Will remove that part.
diff mbox series

Patch

diff --git a/libavformat/avformat.c b/libavformat/avformat.c
index 8e8c6fbe55..32ef440207 100644
--- a/libavformat/avformat.c
+++ b/libavformat/avformat.c
@@ -100,6 +100,11 @@  void ff_free_stream_group(AVStreamGroup **pstg)
         av_iamf_mix_presentation_free(&stg->params.iamf_mix_presentation);
         break;
     }
+    case AV_STREAM_GROUP_PARAMS_TILE_GRID:
+        av_opt_free(stg->params.tile_grid);
+        av_freep(&stg->params.tile_grid->offsets);
+        av_freep(&stg->params.tile_grid);
+        break;
     default:
         break;
     }
diff --git a/libavformat/avformat.h b/libavformat/avformat.h
index 5d0fe82250..6577f13ef1 100644
--- a/libavformat/avformat.h
+++ b/libavformat/avformat.h
@@ -1018,10 +1018,109 @@  typedef struct AVStream {
     int pts_wrap_bits;
 } AVStream;
 
+/**
+ * AVStreamGroupTileGrid holds information on how to combine several
+ * independent images on a single grid for presentation. None of the tiles may
+ * overlap inside the grid.
+ *
+ * The following is an example of a simple grid with 3 rows and 4 columns:
+ *
+ * +---+---+---+---+
+ * | 0 | 1 | 2 | 3 |
+ * +---+---+---+---+
+ * | 4 | 5 | 6 | 7 |
+ * +---+---+---+---+
+ * | 8 | 9 |10 |11 |
+ * +---+---+---+---+
+ *
+ * Assuming all tiles have a dimension of 512x512, the
+ * @ref AVStreamGroupTileGrid.offsets "offset" of the topleft pixel of
+ * the first @ref AVStreamGroup.streams "stream" in the group is "0,0", the
+ * @ref AVStreamGroupTileGrid.offsets "offset" of the topleft pixel of
+ * the second @ref AVStreamGroup.streams "stream" in the group is "512,0", the
+ * @ref AVStreamGroupTileGrid.offsets "offset" of the topleft pixel of
+ * the fifth @ref AVStreamGroup.streams "stream" in the group is "0,512", the
+ * @ref AVStreamGroupTileGrid.offsets "offset", of the topleft pixel of
+ * the sixth @ref AVStreamGroup.streams "stream" in the group is "512,512",
+ * etc.
+ *
+ * sizeof(AVStreamGroupTileGrid) is not a part of the ABI. No new fields may be
+ * added to this struct without a major version bump.
+ */
+typedef struct AVStreamGroupTileGrid {
+    const AVClass *av_class;
+
+    /**
+     * Width of the final image in the grid.
+     *
+     * Must be > 0.
+     */
+    int coded_width;
+    /**
+     * Width of the final image in the grid.
+     *
+     * Must be > 0.
+     */
+    int coded_height;
+
+    /**
+     * An @ref AVStreamGroup.nb_streams "nb_streams" sized array of offsets in
+     * pixels from the topleft edge of the grid, indicating where each stream
+     * should be placed.
+     * It must be allocated with the av_malloc() family of functions.
+     *
+     * - demuxing: set by libavformat, must not be modified by the caller.
+     * - muxing: set by the caller before avformat_write_header().
+     *
+     * Freed by libavformat in avformat_free_context().
+     */
+    struct {
+        int x;
+        int y;
+    } *offsets;
+
+    /**
+     * Offset in pixels from the left edge of the grid where the actual image
+     * meant for presentation starts.
+     *
+     * This field must be >= 0 and <= @ref coded_width.
+     */
+    int horizontal_offset;
+    /**
+     * Offset in pixels from the top edge of the grid where the actual image
+     * meant for presentation starts.
+     *
+     * This field must be >= 0 and <= @ref coded_height.
+     */
+    int vertical_offset;
+
+    /**
+     * Width of the final image for presentation.
+     *
+     * Must be > 0 and <= (@ref coded_width - @ref horizontal_offset).
+     * When it's not equal to (@ref coded_width - @ref horizontal_offset), the
+     * result of (@ref coded_width - width - @ref horizontal_offset) is the
+     * amount amount of pixels to be cropped from the right edge of the
+     * final image before presentation.
+     */
+    int width;
+    /**
+     * Height of the final image for presentation.
+     *
+     * Must be > 0 and <= (@ref coded_height - @ref vertical_offset).
+     * When it's not equal to (@ref coded_height - @ref vertical_offset), the
+     * result of (@ref coded_height - height - @ref vertical_offset) is the
+     * amount amount of pixels to be cropped from the bototm edge of the
+     * final image before presentation.
+     */
+    int height;
+} AVStreamGroupTileGrid;
+
 enum AVStreamGroupParamsType {
     AV_STREAM_GROUP_PARAMS_NONE,
     AV_STREAM_GROUP_PARAMS_IAMF_AUDIO_ELEMENT,
     AV_STREAM_GROUP_PARAMS_IAMF_MIX_PRESENTATION,
+    AV_STREAM_GROUP_PARAMS_TILE_GRID,
 };
 
 struct AVIAMFAudioElement;
@@ -1062,6 +1161,7 @@  typedef struct AVStreamGroup {
     union {
         struct AVIAMFAudioElement *iamf_audio_element;
         struct AVIAMFMixPresentation *iamf_mix_presentation;
+        struct AVStreamGroupTileGrid *tile_grid;
     } params;
 
     /**
diff --git a/libavformat/dump.c b/libavformat/dump.c
index aff51b43f6..c9b7369bcd 100644
--- a/libavformat/dump.c
+++ b/libavformat/dump.c
@@ -22,6 +22,7 @@ 
 #include <stdio.h>
 #include <stdint.h>
 
+#include "libavutil/avstring.h"
 #include "libavutil/channel_layout.h"
 #include "libavutil/display.h"
 #include "libavutil/iamf.h"
@@ -721,6 +722,34 @@  static void dump_stream_group(const AVFormatContext *ic, uint8_t *printed,
         }
         break;
     }
+    case AV_STREAM_GROUP_PARAMS_TILE_GRID: {
+        const AVStreamGroupTileGrid *tile_grid = stg->params.tile_grid;
+        AVCodecContext *avctx = avcodec_alloc_context3(NULL);
+        const char *ptr = NULL;
+        av_log(NULL, AV_LOG_INFO, " Tile Grid:");
+        if (avctx && stg->nb_streams && !avcodec_parameters_to_context(avctx, stg->streams[0]->codecpar)) {
+            avctx->width  = tile_grid->width;
+            avctx->height = tile_grid->height;
+            avctx->coded_width  = tile_grid->coded_width;
+            avctx->coded_height = tile_grid->coded_height;
+            if (ic->dump_separator)
+                av_opt_set(avctx, "dump_separator", ic->dump_separator, 0);
+            buf[0] = 0;
+            avcodec_string(buf, sizeof(buf), avctx, is_output);
+            ptr = av_stristr(buf, " ");
+        }
+        avcodec_free_context(&avctx);
+        if (ptr)
+            av_log(NULL, AV_LOG_INFO, "%s", ptr);
+        av_log(NULL, AV_LOG_INFO, "\n");
+        dump_metadata(NULL, stg->metadata, "    ", AV_LOG_INFO);
+        for (int i = 0; i < stg->nb_streams; i++) {
+            const AVStream *st = stg->streams[i];
+            dump_stream_format(ic, st->index, i, index, is_output, AV_LOG_VERBOSE);
+            printed[st->index] = 1;
+        }
+        break;
+    }
     default:
         break;
     }
diff --git a/libavformat/options.c b/libavformat/options.c
index 75ec86ce05..ed96ba4afb 100644
--- a/libavformat/options.c
+++ b/libavformat/options.c
@@ -337,6 +337,26 @@  fail:
     return NULL;
 }
 
+#define FLAGS AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_VIDEO_PARAM
+#define OFFSET(x) offsetof(AVStreamGroupTileGrid, x)
+static const AVOption tile_grid_options[] = {
+    { "grid_size", "size of the output image", OFFSET(coded_width),
+        AV_OPT_TYPE_IMAGE_SIZE, { .str = NULL }, 0, INT_MAX, FLAGS },
+    { "horizontal_offset", NULL, OFFSET(horizontal_offset), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS },
+    { "vertical_offset", NULL, OFFSET(vertical_offset),     AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS },
+    { "output_size", "size of valid pixels in output image meant for presentation", OFFSET(width),
+        AV_OPT_TYPE_IMAGE_SIZE, { .str = NULL }, 0, INT_MAX, FLAGS },
+    { NULL },
+};
+#undef FLAGS
+#undef OFFSET
+
+static const AVClass tile_grid_class = {
+    .class_name = "AVStreamGroupTileGrid",
+    .version    = LIBAVUTIL_VERSION_INT,
+    .option     = tile_grid_options,
+};
+
 static void *stream_group_child_next(void *obj, void *prev)
 {
     AVStreamGroup *stg = obj;
@@ -346,6 +366,8 @@  static void *stream_group_child_next(void *obj, void *prev)
             return stg->params.iamf_audio_element;
         case AV_STREAM_GROUP_PARAMS_IAMF_MIX_PRESENTATION:
             return stg->params.iamf_mix_presentation;
+        case AV_STREAM_GROUP_PARAMS_TILE_GRID:
+            return stg->params.tile_grid;
         default:
             break;
         }
@@ -368,6 +390,9 @@  static const AVClass *stream_group_child_iterate(void **opaque)
     case AV_STREAM_GROUP_PARAMS_IAMF_MIX_PRESENTATION:
         ret = av_iamf_mix_presentation_get_class();
         break;
+    case AV_STREAM_GROUP_PARAMS_TILE_GRID:
+        ret = &tile_grid_class;
+        break;
     default:
         break;
     }
@@ -429,6 +454,13 @@  AVStreamGroup *avformat_stream_group_create(AVFormatContext *s,
         if (!stg->params.iamf_mix_presentation)
             goto fail;
         break;
+    case AV_STREAM_GROUP_PARAMS_TILE_GRID:
+        stg->params.tile_grid = av_mallocz(sizeof(*stg->params.tile_grid));
+        if (!stg->params.tile_grid)
+            goto fail;
+        stg->params.tile_grid->av_class = &tile_grid_class;
+        av_opt_set_defaults(stg->params.tile_grid);
+        break;
     default:
         goto fail;
     }