[FFmpeg-devel] [PATCH 1/8 v2] fftools/ffprobe: add support for Stream Groups

Stefano Sabatini stefasab at gmail.com
Mon Mar 4 12:34:12 EET 2024


On date Sunday 2024-03-03 15:09:19 -0300, James Almer wrote:
> Signed-off-by: James Almer <jamrial at gmail.com>
> ---
>  doc/ffprobe.xsd                               |   1 +
>  fftools/ffprobe.c                             | 145 ++++++++++++++++--
>  tests/ref/fate/cavs-demux                     |   2 +-
>  tests/ref/fate/ffprobe_compact                |   2 +-
>  tests/ref/fate/ffprobe_csv                    |   2 +-
>  tests/ref/fate/ffprobe_default                |   1 +
>  tests/ref/fate/ffprobe_flat                   |   1 +
>  tests/ref/fate/ffprobe_ini                    |   1 +
>  tests/ref/fate/ffprobe_json                   |   1 +
>  tests/ref/fate/ffprobe_xml                    |   2 +-
>  tests/ref/fate/ffprobe_xsd                    |   2 +-
>  tests/ref/fate/flv-demux                      |   2 +-
>  tests/ref/fate/gapless-mp3-side-data          |   2 +-
>  .../ref/fate/mov-mp4-disposition-mpegts-remux |   3 +
>  tests/ref/fate/mov-mp4-ttml-dfxp              |   3 +
>  tests/ref/fate/mov-mp4-ttml-stpp              |   3 +
>  tests/ref/fate/oggopus-demux                  |   2 +-
>  tests/ref/fate/ts-demux                       |   2 +-
>  tests/ref/fate/ts-opus-demux                  |   2 +-
>  tests/ref/fate/ts-small-demux                 |   2 +-
>  tests/ref/fate/ts-timed-id3-demux             |   2 +-
>  21 files changed, 160 insertions(+), 23 deletions(-)
> 
> diff --git a/doc/ffprobe.xsd b/doc/ffprobe.xsd
> index 23d97ed11a..08f2909c02 100644
> --- a/doc/ffprobe.xsd
> +++ b/doc/ffprobe.xsd
> @@ -305,6 +305,7 @@
>      <xsd:attribute name="filename"         type="xsd:string" use="required"/>
>      <xsd:attribute name="nb_streams"       type="xsd:int"    use="required"/>
>      <xsd:attribute name="nb_programs"      type="xsd:int"    use="required"/>
> +    <xsd:attribute name="nb_stream_groups" type="xsd:int"    use="required"/>
>      <xsd:attribute name="format_name"      type="xsd:string" use="required"/>
>      <xsd:attribute name="format_long_name" type="xsd:string"/>
>      <xsd:attribute name="start_time"       type="xsd:float"/>

do we also need to add a new type for the stream group?


> diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c
> index ac6b92f5d6..548c5e0948 100644
> --- a/fftools/ffprobe.c
> +++ b/fftools/ffprobe.c
> @@ -112,8 +112,10 @@ static int do_show_format  = 0;
>  static int do_show_frames  = 0;
>  static int do_show_packets = 0;
>  static int do_show_programs = 0;
> +static int do_show_stream_groups = 0;
>  static int do_show_streams = 0;
>  static int do_show_stream_disposition = 0;
> +static int do_show_stream_group_disposition = 0;
>  static int do_show_data    = 0;
>  static int do_show_program_version  = 0;
>  static int do_show_library_versions = 0;
> @@ -126,6 +128,7 @@ static int do_show_chapter_tags = 0;
>  static int do_show_format_tags = 0;
>  static int do_show_frame_tags = 0;
>  static int do_show_program_tags = 0;
> +static int do_show_stream_group_tags = 0;
>  static int do_show_stream_tags = 0;
>  static int do_show_packet_tags = 0;
>  
> @@ -159,7 +162,7 @@ static int find_stream_info  = 1;
>  
>  /* section structure definition */
>  
> -#define SECTION_MAX_NB_CHILDREN 10
> +#define SECTION_MAX_NB_CHILDREN 11
>  
>  typedef enum {
>      SECTION_ID_NONE = -1,
> @@ -203,6 +206,14 @@ typedef enum {
>      SECTION_ID_PROGRAM_TAGS,
>      SECTION_ID_PROGRAM_VERSION,
>      SECTION_ID_PROGRAMS,
> +    SECTION_ID_STREAM_GROUP_STREAM_DISPOSITION,
> +    SECTION_ID_STREAM_GROUP_STREAM_TAGS,
> +    SECTION_ID_STREAM_GROUP,
> +    SECTION_ID_STREAM_GROUP_STREAMS,
> +    SECTION_ID_STREAM_GROUP_STREAM,
> +    SECTION_ID_STREAM_GROUP_DISPOSITION,
> +    SECTION_ID_STREAM_GROUP_TAGS,
> +    SECTION_ID_STREAM_GROUPS,
>      SECTION_ID_ROOT,
>      SECTION_ID_STREAM,
>      SECTION_ID_STREAM_DISPOSITION,
> @@ -285,8 +296,16 @@ static struct section sections[] = {
>      [SECTION_ID_PROGRAM_TAGS] =               { SECTION_ID_PROGRAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "program_tags" },
>      [SECTION_ID_PROGRAM_VERSION] =    { SECTION_ID_PROGRAM_VERSION, "program_version", 0, { -1 } },
>      [SECTION_ID_PROGRAMS] =                   { SECTION_ID_PROGRAMS, "programs", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PROGRAM, -1 } },
> +    [SECTION_ID_STREAM_GROUP_STREAM_DISPOSITION] = { SECTION_ID_STREAM_GROUP_STREAM_DISPOSITION, "disposition", 0, { -1 }, .unique_name = "stream_group_stream_disposition" },
> +    [SECTION_ID_STREAM_GROUP_STREAM_TAGS] =        { SECTION_ID_STREAM_GROUP_STREAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "stream_group_stream_tags" },
> +    [SECTION_ID_STREAM_GROUP] =                    { SECTION_ID_STREAM_GROUP, "stream_group", 0, { SECTION_ID_STREAM_GROUP_TAGS, SECTION_ID_STREAM_GROUP_DISPOSITION, SECTION_ID_STREAM_GROUP_STREAMS, -1 } },
> +    [SECTION_ID_STREAM_GROUP_STREAMS] =            { SECTION_ID_STREAM_GROUP_STREAMS, "streams", SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM_GROUP_STREAM, -1 }, .unique_name = "stream_group_streams" },
> +    [SECTION_ID_STREAM_GROUP_STREAM] =             { SECTION_ID_STREAM_GROUP_STREAM, "stream", 0, { SECTION_ID_STREAM_GROUP_STREAM_DISPOSITION, SECTION_ID_STREAM_GROUP_STREAM_TAGS, -1 }, .unique_name = "stream_group_stream" },
> +    [SECTION_ID_STREAM_GROUP_DISPOSITION] =        { SECTION_ID_STREAM_GROUP_DISPOSITION, "disposition", 0, { -1 }, .unique_name = "stream_group_disposition" },
> +    [SECTION_ID_STREAM_GROUP_TAGS] =               { SECTION_ID_STREAM_GROUP_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "stream_group_tags" },
> +    [SECTION_ID_STREAM_GROUPS] =                   { SECTION_ID_STREAM_GROUPS, "stream_groups", SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM_GROUP, -1 } },
>      [SECTION_ID_ROOT] =               { SECTION_ID_ROOT, "root", SECTION_FLAG_IS_WRAPPER,
> -                                        { SECTION_ID_CHAPTERS, SECTION_ID_FORMAT, SECTION_ID_FRAMES, SECTION_ID_PROGRAMS, SECTION_ID_STREAMS,
> +                                        { SECTION_ID_CHAPTERS, SECTION_ID_FORMAT, SECTION_ID_FRAMES, SECTION_ID_PROGRAMS, SECTION_ID_STREAM_GROUPS, SECTION_ID_STREAMS,
>                                            SECTION_ID_PACKETS, SECTION_ID_ERROR, SECTION_ID_PROGRAM_VERSION, SECTION_ID_LIBRARY_VERSIONS,
>                                            SECTION_ID_PIXEL_FORMATS, -1} },
>      [SECTION_ID_STREAMS] =            { SECTION_ID_STREAMS, "streams", SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM, -1 } },
> @@ -3051,7 +3070,10 @@ static void print_dispositions(WriterContext *w, uint32_t disposition, SectionID
>      writer_print_section_footer(w);
>  }
>  
> -static int show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_idx, InputStream *ist, int in_program)

> +#define IN_PROGRAM 1
> +#define IN_STREAM_GROUP 2
> +
> +static int show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_idx, InputStream *ist, int container)
>  {
>      AVStream *stream = ist->st;
>      AVCodecParameters *par;
> @@ -3061,12 +3083,29 @@ static int show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_id
>      AVRational sar, dar;
>      AVBPrint pbuf;
>      const AVCodecDescriptor *cd;
> +    const SectionID section_header[] = {
> +        SECTION_ID_STREAM,
> +        SECTION_ID_PROGRAM_STREAM,
> +        SECTION_ID_STREAM_GROUP_STREAM,
> +    };
> +    const SectionID section_disposition[] = {
> +        SECTION_ID_STREAM_DISPOSITION,
> +        SECTION_ID_PROGRAM_STREAM_DISPOSITION,
> +        SECTION_ID_STREAM_GROUP_STREAM_DISPOSITION,
> +    };
> +    const SectionID section_tags[] = {
> +        SECTION_ID_STREAM_TAGS,
> +        SECTION_ID_PROGRAM_STREAM_TAGS,
> +        SECTION_ID_STREAM_GROUP_STREAM_TAGS,
> +    };

what about something as:

typedef struct {
    SectionID stream;
    SectionID disposition;
    SectionID tags;
} StreamContainerSectionsIds;

static const StreamContainerSectionsIds stream_container_sections_ids = {
    SECTION_ID_STREAM,
    SECTION_ID_STREAM_DISPOSITION,
    SECTION_ID_STREAM_TAGS
};

static const StreamContainerSectionsIds program_stream_container_sections_ids = {
    SECTION_ID_PROGRAM_STREAM,
    SECTION_ID_PROGRAM_STREAM_DISPOSITION,
    SECTION_ID_PROGRAM_STREAM_TAGS
};

static const StreamContainerSectionsIds stream_group_container_sections_ids = {
    SECTION_ID_STREAM_GROUP_STREAM,
    SECTION_ID_STREAM_GROUP_STREAM_DISPOSITION,
    SECTION_ID_STREAM_GROUP_STREAM_TAGS
};

static int show_stream(WriterContext *w, AVFormatContext *fmt_ctx, stream_idx, InputStream *ist, StreamContainerSectionsIds *sections_ids)
...

this is probably a bit longer but should be more robust

>      int ret = 0;
>      const char *profile = NULL;
>  
> +    av_assert0(container < FF_ARRAY_ELEMS(section_header));
> +
>      av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED);
>  
> -    writer_print_section_header(w, NULL, in_program ? SECTION_ID_PROGRAM_STREAM : SECTION_ID_STREAM);
> +    writer_print_section_header(w, NULL, section_header[container]);
>  
>      print_int("index", stream->index);
>  
> @@ -3228,13 +3267,14 @@ static int show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_id
>  
>      /* Print disposition information */
>      if (do_show_stream_disposition) {
> -        print_dispositions(w, stream->disposition,
> -                           in_program ? SECTION_ID_PROGRAM_STREAM_DISPOSITION
> -                                      : SECTION_ID_STREAM_DISPOSITION);
> +        av_assert0(container < FF_ARRAY_ELEMS(section_disposition));
> +        print_dispositions(w, stream->disposition, section_disposition[container]);
>      }
>  
> -    if (do_show_stream_tags)
> -        ret = show_tags(w, stream->metadata, in_program ? SECTION_ID_PROGRAM_STREAM_TAGS : SECTION_ID_STREAM_TAGS);
> +    if (do_show_stream_tags) {
> +        av_assert0(container < FF_ARRAY_ELEMS(section_tags));
> +        ret = show_tags(w, stream->metadata, section_tags[container]);
> +    }
>  
>      if (stream->codecpar->nb_coded_side_data) {
>          writer_print_section_header(w, NULL, SECTION_ID_STREAM_SIDE_DATA_LIST);
> @@ -3289,7 +3329,7 @@ static int show_program(WriterContext *w, InputFile *ifile, AVProgram *program)
>      writer_print_section_header(w, NULL, SECTION_ID_PROGRAM_STREAMS);
>      for (i = 0; i < program->nb_stream_indexes; i++) {
>          if (selected_streams[program->stream_index[i]]) {
> -            ret = show_stream(w, fmt_ctx, program->stream_index[i], &ifile->streams[program->stream_index[i]], 1);
> +            ret = show_stream(w, fmt_ctx, program->stream_index[i], &ifile->streams[program->stream_index[i]], IN_PROGRAM);
>              if (ret < 0)
>                  break;
>          }
> @@ -3319,6 +3359,76 @@ static int show_programs(WriterContext *w, InputFile *ifile)
>      return ret;
>  }
>  
> +

nit++: drop double empty line

> +static void print_stream_group_params(WriterContext *w, AVStreamGroup *stg)
> +{
> +    if (stg->type == AV_STREAM_GROUP_PARAMS_IAMF_AUDIO_ELEMENT)
> +        print_str("type", "IAMF Audio Element");
> +    else if (stg->type == AV_STREAM_GROUP_PARAMS_IAMF_MIX_PRESENTATION)
> +        print_str("type", "IAMF Mix Presentation");
> +    else if (stg->type == AV_STREAM_GROUP_PARAMS_TILE_GRID) {
> +        print_str("type", "Tile Grid");
> +    } else
> +        print_str_opt("type", "unknown");

nit: you can factorize type to avoid multiple print_str()

> +}
> +
> +static int show_stream_group(WriterContext *w, InputFile *ifile, AVStreamGroup *stg)
> +{
> +    AVFormatContext *fmt_ctx = ifile->fmt_ctx;
> +    AVBPrint pbuf;
> +    int i, ret = 0;
> +
> +    av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED);
> +    writer_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP);
> +    print_int("index", stg->index);
> +    if (fmt_ctx->iformat->flags & AVFMT_SHOW_IDS) print_fmt    ("id", "0x%"PRIx64, stg->id);
> +    else                                          print_str_opt("id", "N/A");
> +    print_int("nb_streams", stg->nb_streams);
> +    print_stream_group_params(w, stg);
> +
> +    /* Print disposition information */
> +    if (do_show_stream_group_disposition)
> +        print_dispositions(w, stg->disposition, SECTION_ID_STREAM_GROUP_DISPOSITION);
> +
> +    if (do_show_stream_group_tags)
> +        ret = show_tags(w, stg->metadata, SECTION_ID_STREAM_GROUP_TAGS);
> +    if (ret < 0)
> +        goto end;
> +
> +    writer_print_section_header(w, NULL, SECTION_ID_STREAM_GROUP_STREAMS);
> +    for (i = 0; i < stg->nb_streams; i++) {
> +        if (selected_streams[stg->streams[i]->index]) {
> +            ret = show_stream(w, fmt_ctx, stg->streams[i]->index, &ifile->streams[stg->streams[i]->index], IN_STREAM_GROUP);
> +            if (ret < 0)
> +                break;
> +        }
> +    }
> +    writer_print_section_footer(w);
> +
> +end:
> +    av_bprint_finalize(&pbuf, NULL);
> +    writer_print_section_footer(w);
> +    return ret;
> +}
> +
> +static int show_stream_groups(WriterContext *w, InputFile *ifile)
> +{
> +    AVFormatContext *fmt_ctx = ifile->fmt_ctx;
> +    int i, ret = 0;
> +
> +    writer_print_section_header(w, NULL, SECTION_ID_STREAM_GROUPS);
> +    for (i = 0; i < fmt_ctx->nb_stream_groups; i++) {
> +        AVStreamGroup *stg = fmt_ctx->stream_groups[i];
> +        if (!stg)
> +            continue;
> +        ret = show_stream_group(w, ifile, stg);
> +        if (ret < 0)
> +            break;
> +    }
> +    writer_print_section_footer(w);
> +    return ret;
> +}
> +
>  static int show_chapters(WriterContext *w, InputFile *ifile)
>  {
>      AVFormatContext *fmt_ctx = ifile->fmt_ctx;
> @@ -3355,6 +3465,7 @@ static int show_format(WriterContext *w, InputFile *ifile)
>      print_str_validate("filename", fmt_ctx->url);
>      print_int("nb_streams",       fmt_ctx->nb_streams);
>      print_int("nb_programs",      fmt_ctx->nb_programs);
> +    print_int("nb_stream_groups", fmt_ctx->nb_stream_groups);
>      print_str("format_name",      fmt_ctx->iformat->name);
>      if (!do_bitexact) {
>          if (fmt_ctx->iformat->long_name) print_str    ("format_long_name", fmt_ctx->iformat->long_name);
> @@ -3580,6 +3691,11 @@ static int probe_file(WriterContext *wctx, const char *filename,
>          CHECK_END;
>      }
>  
> +    if (do_show_stream_groups) {
> +        ret = show_stream_groups(wctx, &ifile);
> +        CHECK_END;
> +    }
> +
>      if (do_show_streams) {
>          ret = show_streams(wctx, &ifile);
>          CHECK_END;
> @@ -4078,6 +4194,7 @@ DEFINE_OPT_SHOW_SECTION(pixel_formats,    PIXEL_FORMATS)
>  DEFINE_OPT_SHOW_SECTION(program_version,  PROGRAM_VERSION)
>  DEFINE_OPT_SHOW_SECTION(streams,          STREAMS)
>  DEFINE_OPT_SHOW_SECTION(programs,         PROGRAMS)
> +DEFINE_OPT_SHOW_SECTION(stream_groups,    STREAM_GROUPS)
>  
>  static const OptionDef real_options[] = {
>      CMDUTILS_COMMON_OPTIONS
> @@ -4108,6 +4225,7 @@ static const OptionDef real_options[] = {
>  #endif
>      { "show_packets",          OPT_TYPE_FUNC,        0, { .func_arg = &opt_show_packets }, "show packets info" },
>      { "show_programs",         OPT_TYPE_FUNC,        0, { .func_arg = &opt_show_programs }, "show programs info" },
> +    { "show_stream_groups",    OPT_TYPE_FUNC,        0, { .func_arg = &opt_show_stream_groups }, "show stream groups info" },

Missing documentation update, also probably we want to advertise the
new option in the Changelog.

Thanks.


More information about the ffmpeg-devel mailing list