diff mbox series

[FFmpeg-devel,4/4] examples: adding device_get_capabilities example

Message ID 20210603224552.1218-5-dcnieho@gmail.com
State Superseded, archived
Headers show
Series avdevice/dshow: implement capabilities API | expand

Checks

Context Check Description
andriy/x86_make success Make finished
andriy/x86_make_fate success Make fate finished
andriy/PPC64_make success Make finished
andriy/PPC64_make_fate success Make fate finished

Commit Message

Diederick C. Niehorster June 3, 2021, 10:45 p.m. UTC
Signed-off-by: Diederick Niehorster <dcnieho@gmail.com>
---
 configure                              |   2 +
 doc/examples/.gitignore                |   1 +
 doc/examples/Makefile                  |  47 ++++----
 doc/examples/Makefile.example          |   1 +
 doc/examples/device_get_capabilities.c | 151 +++++++++++++++++++++++++
 5 files changed, 179 insertions(+), 23 deletions(-)
 create mode 100644 doc/examples/device_get_capabilities.c

Comments

Andreas Rheinhardt June 5, 2021, 6:51 a.m. UTC | #1
Diederick Niehorster:
> Signed-off-by: Diederick Niehorster <dcnieho@gmail.com>
> ---
>  configure                              |   2 +
>  doc/examples/.gitignore                |   1 +
>  doc/examples/Makefile                  |  47 ++++----
>  doc/examples/Makefile.example          |   1 +
>  doc/examples/device_get_capabilities.c | 151 +++++++++++++++++++++++++
>  5 files changed, 179 insertions(+), 23 deletions(-)
>  create mode 100644 doc/examples/device_get_capabilities.c
> 
> diff --git a/configure b/configure
> index 82367fd30d..5e9666d017 100755
> --- a/configure
> +++ b/configure
> @@ -1705,6 +1705,7 @@ EXAMPLE_LIST="
>      decode_audio_example
>      decode_video_example
>      demuxing_decoding_example
> +    device_get_capabilities_example
>      encode_audio_example
>      encode_video_example
>      extract_mvs_example
> @@ -3712,6 +3713,7 @@ avio_reading_deps="avformat avcodec avutil"
>  decode_audio_example_deps="avcodec avutil"
>  decode_video_example_deps="avcodec avutil"
>  demuxing_decoding_example_deps="avcodec avformat avutil"
> +device_get_capabilities_example_deps="avdevice avformat avutil"
>  encode_audio_example_deps="avcodec avutil"
>  encode_video_example_deps="avcodec avutil"
>  extract_mvs_example_deps="avcodec avformat avutil"
> diff --git a/doc/examples/.gitignore b/doc/examples/.gitignore
> index 44960e1de7..256f33a600 100644
> --- a/doc/examples/.gitignore
> +++ b/doc/examples/.gitignore
> @@ -3,6 +3,7 @@
>  /decode_audio
>  /decode_video
>  /demuxing_decoding
> +/device_get_capabilities
>  /encode_audio
>  /encode_video
>  /extract_mvs
> diff --git a/doc/examples/Makefile b/doc/examples/Makefile
> index 81bfd34d5d..7988ed4226 100644
> --- a/doc/examples/Makefile
> +++ b/doc/examples/Makefile
> @@ -1,26 +1,27 @@
> -EXAMPLES-$(CONFIG_AVIO_LIST_DIR_EXAMPLE)     += avio_list_dir
> -EXAMPLES-$(CONFIG_AVIO_READING_EXAMPLE)      += avio_reading
> -EXAMPLES-$(CONFIG_DECODE_AUDIO_EXAMPLE)      += decode_audio
> -EXAMPLES-$(CONFIG_DECODE_VIDEO_EXAMPLE)      += decode_video
> -EXAMPLES-$(CONFIG_DEMUXING_DECODING_EXAMPLE) += demuxing_decoding
> -EXAMPLES-$(CONFIG_ENCODE_AUDIO_EXAMPLE)      += encode_audio
> -EXAMPLES-$(CONFIG_ENCODE_VIDEO_EXAMPLE)      += encode_video
> -EXAMPLES-$(CONFIG_EXTRACT_MVS_EXAMPLE)       += extract_mvs
> -EXAMPLES-$(CONFIG_FILTER_AUDIO_EXAMPLE)      += filter_audio
> -EXAMPLES-$(CONFIG_FILTERING_AUDIO_EXAMPLE)   += filtering_audio
> -EXAMPLES-$(CONFIG_FILTERING_VIDEO_EXAMPLE)   += filtering_video
> -EXAMPLES-$(CONFIG_HTTP_MULTICLIENT_EXAMPLE)  += http_multiclient
> -EXAMPLES-$(CONFIG_HW_DECODE_EXAMPLE)         += hw_decode
> -EXAMPLES-$(CONFIG_METADATA_EXAMPLE)          += metadata
> -EXAMPLES-$(CONFIG_MUXING_EXAMPLE)            += muxing
> -EXAMPLES-$(CONFIG_QSVDEC_EXAMPLE)            += qsvdec
> -EXAMPLES-$(CONFIG_REMUXING_EXAMPLE)          += remuxing
> -EXAMPLES-$(CONFIG_RESAMPLING_AUDIO_EXAMPLE)  += resampling_audio
> -EXAMPLES-$(CONFIG_SCALING_VIDEO_EXAMPLE)     += scaling_video
> -EXAMPLES-$(CONFIG_TRANSCODE_AAC_EXAMPLE)     += transcode_aac
> -EXAMPLES-$(CONFIG_TRANSCODING_EXAMPLE)       += transcoding
> -EXAMPLES-$(CONFIG_VAAPI_ENCODE_EXAMPLE)      += vaapi_encode
> -EXAMPLES-$(CONFIG_VAAPI_TRANSCODE_EXAMPLE)   += vaapi_transcode
> +EXAMPLES-$(CONFIG_AVIO_LIST_DIR_EXAMPLE)             += avio_list_dir
> +EXAMPLES-$(CONFIG_AVIO_READING_EXAMPLE)              += avio_reading
> +EXAMPLES-$(CONFIG_DECODE_AUDIO_EXAMPLE)              += decode_audio
> +EXAMPLES-$(CONFIG_DECODE_VIDEO_EXAMPLE)              += decode_video
> +EXAMPLES-$(CONFIG_DEMUXING_DECODING_EXAMPLE)         += demuxing_decoding
> +EXAMPLES-$(CONFIG_DEVICE_GET_CAPABILITIES_EXAMPLE)   += device_get_capabilities
> +EXAMPLES-$(CONFIG_ENCODE_AUDIO_EXAMPLE)              += encode_audio
> +EXAMPLES-$(CONFIG_ENCODE_VIDEO_EXAMPLE)              += encode_video
> +EXAMPLES-$(CONFIG_EXTRACT_MVS_EXAMPLE)               += extract_mvs
> +EXAMPLES-$(CONFIG_FILTER_AUDIO_EXAMPLE)              += filter_audio
> +EXAMPLES-$(CONFIG_FILTERING_AUDIO_EXAMPLE)           += filtering_audio
> +EXAMPLES-$(CONFIG_FILTERING_VIDEO_EXAMPLE)           += filtering_video
> +EXAMPLES-$(CONFIG_HTTP_MULTICLIENT_EXAMPLE)          += http_multiclient
> +EXAMPLES-$(CONFIG_HW_DECODE_EXAMPLE)                 += hw_decode
> +EXAMPLES-$(CONFIG_METADATA_EXAMPLE)                  += metadata
> +EXAMPLES-$(CONFIG_MUXING_EXAMPLE)                    += muxing
> +EXAMPLES-$(CONFIG_QSVDEC_EXAMPLE)                    += qsvdec
> +EXAMPLES-$(CONFIG_REMUXING_EXAMPLE)                  += remuxing
> +EXAMPLES-$(CONFIG_RESAMPLING_AUDIO_EXAMPLE)          += resampling_audio
> +EXAMPLES-$(CONFIG_SCALING_VIDEO_EXAMPLE)             += scaling_video
> +EXAMPLES-$(CONFIG_TRANSCODE_AAC_EXAMPLE)             += transcode_aac
> +EXAMPLES-$(CONFIG_TRANSCODING_EXAMPLE)               += transcoding
> +EXAMPLES-$(CONFIG_VAAPI_ENCODE_EXAMPLE)              += vaapi_encode
> +EXAMPLES-$(CONFIG_VAAPI_TRANSCODE_EXAMPLE)           += vaapi_transcode
>  

Moving everything to the right should be done in a separate commit (if
at all).

>  EXAMPLES       := $(EXAMPLES-yes:%=doc/examples/%$(PROGSSUF)$(EXESUF))
>  EXAMPLES_G     := $(EXAMPLES-yes:%=doc/examples/%$(PROGSSUF)_g$(EXESUF))
> diff --git a/doc/examples/Makefile.example b/doc/examples/Makefile.example
> index a232d97f98..b861b9cc74 100644
> --- a/doc/examples/Makefile.example
> +++ b/doc/examples/Makefile.example
> @@ -16,6 +16,7 @@ EXAMPLES=       avio_list_dir                      \
>                  decode_audio                       \
>                  decode_video                       \
>                  demuxing_decoding                  \
> +                device_get_capabilities            \
>                  encode_audio                       \
>                  encode_video                       \
>                  extract_mvs                        \
> diff --git a/doc/examples/device_get_capabilities.c b/doc/examples/device_get_capabilities.c
> new file mode 100644
> index 0000000000..f3b9f31e0d
> --- /dev/null
> +++ b/doc/examples/device_get_capabilities.c
> @@ -0,0 +1,151 @@
> +/*
> + * Copyright (c) 2021 Diederick Niehorster
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a copy
> + * of this software and associated documentation files (the "Software"), to deal
> + * in the Software without restriction, including without limitation the rights
> + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> + * copies of the Software, and to permit persons to whom the Software is
> + * furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice shall be included in
> + * all copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> + * THE SOFTWARE.
> + */
> +
> +/**
> + * @file
> + * avdevice getting capabilities example.
> + *
> + * Shows how to use the avdevice capabilities API to probe
> + * device capabilities (supported codecs, pixel formats, sample
> + * formats, resolutions, channel counts, etc)
> + * @example device_get_capabilities.c
> + */
> +
> +#include <libavdevice/avdevice.h>
> +#include <libavutil/avstring.h>
> +#include <libavformat/avformat.h>
> +#include <libavutil/log.h>
> +
> +
> +
> +int main (int argc, char **argv)
> +{
> +    int ret = 0;
> +    const char* dshow_input_name = NULL;
> +    const char* query_cap = NULL;
> +    const char* set_cap_name = NULL;
> +    const char* set_cap_value = NULL;
> +
> +    const AVInputFormat* fmt = NULL;
> +    AVFormatContext* fmt_ctx = NULL;
> +    AVDeviceCapabilitiesQuery* caps = NULL;
> +    AVOptionRanges* ranges = NULL;
> +
> +    if (argc != 5) {
> +        fprintf(stderr, "usage: %s  dshow_input_name query_cap set_cap_name set_cap_value\n"
> +                "API example program to show how to use the avdevice\n"
> +                "capabilities API to probe device capabilities \n"
> +                "(supported codecs, pixel formats, sample formats,\n"
> +                "resolutions, channel counts, etc).\n\n"
> +                "example invocation: "
> +                "%s video=\"Integrated Webcam\" frame_size pixel_format yuyv422",
> +                argv[0], argv[0]);
> +        exit(1);
> +    }
> +    dshow_input_name = argv[1];
> +    query_cap = argv[2];
> +    set_cap_name = argv[3];
> +    set_cap_value = argv[4];
> +
> +    // make sure avdevices can be found
> +    avdevice_register_all();
> +    // find our device (capabilities API is currently
> +    // only implemented for dshow device, so hardcode that)

This information is internal and does not belong into an example; the
best way is probably to set the device type via a new argument.

> +    fmt = av_find_input_format("dshow");

Missing check for fmt (e.g. dshow is unavailable if not on Windows).

> +
> +    // since there is no equivalent of avformat_alloc_output_context2 for an input context,
> +    // so we get this dirty code that users shouldn't have to write....
> +    fmt_ctx = avformat_alloc_context();
> +    fmt_ctx->url = av_strdup(dshow_input_name);

Missing checks.

> +    fmt_ctx->iformat = fmt;
> +    if (fmt_ctx->iformat->priv_data_size > 0) {

priv_data_size is not part of the public API; so the code is indeed
dirty and so this example is unacceptable as-is. This might be a
scenario where AVFMT_FLAG_PRIV_OPT might have been useful, see
2ff40b98ecbd9befadddc8fe665a391f99bfca32.

> +        if (!(fmt_ctx->priv_data = av_mallocz(fmt_ctx->iformat->priv_data_size))) {
> +            ret = AVERROR(ENOMEM);
> +            goto end;
> +        }
> +        if (fmt_ctx->iformat->priv_class) {
> +            *(const AVClass**)fmt_ctx->priv_data = fmt_ctx->iformat->priv_class;
> +            av_opt_set_defaults(fmt_ctx->priv_data);
> +        }
> +    }
> +    // end dirty code
> +
> +    // query the capability without any filter set
> +    ret = avdevice_capabilities_create(&caps, fmt_ctx, NULL);
> +    if (ret < 0)
> +        goto end;
> +
> +    ret = av_opt_query_ranges(&ranges, caps, query_cap, AV_OPT_MULTI_COMPONENT_RANGE);
> +    if (ret < 0)
> +        goto end;
> +
> +    for (int range_index = 0; range_index < ranges->nb_ranges; range_index++) {
> +        for (int component_index = 0; component_index < ranges->nb_components; component_index++)
> +        {
> +            AVOptionRange *range = ranges->range[ranges->nb_ranges * component_index + range_index];
> +            if (component_index > 0)
> +                printf(", ");
> +            printf("%s: %.2f -- %.2f", range->str, range->value_min, range->value_max);
> +        }
> +        printf("\n");
> +    }
> +    av_opt_freep_ranges(&ranges);
> +
> +    printf("=============\n");
> +
> +    // set one capability, which may filter out some returned capabilities
> +    // (or all, if set to an invalid value)
> +    ret = av_opt_set(caps, set_cap_name, set_cap_value, 0);
> +    if (ret < 0)
> +        goto end;
> +
> +    ret = av_opt_query_ranges(&ranges, caps, query_cap, AV_OPT_MULTI_COMPONENT_RANGE);
> +    if (ret < 0)
> +        goto end;
> +
> +    for (int range_index = 0; range_index < ranges->nb_ranges; range_index++) {
> +        for (int component_index = 0; component_index < ranges->nb_components; component_index++)
> +        {
> +            AVOptionRange* range = ranges->range[ranges->nb_ranges * component_index + range_index];
> +            if (component_index > 0)
> +                printf(", ");
> +            printf("%s: %.2f -- %.2f", range->str, range->value_min, range->value_max);
> +        }
> +        printf("\n");
> +    }
> +
> +
> +end:
> +    av_opt_freep_ranges(&ranges);
> +    avdevice_capabilities_free(&caps, fmt_ctx);
> +
> +    avformat_close_input(&fmt_ctx);

avformat_close_input() may only be used if you have used
avformat_open_input(); otherwise one has to use avformat_free_context().

> +
> +    if (ret < 0) {
> +        char a[AV_ERROR_MAX_STRING_SIZE] = { 0 };
> +        av_make_error_string(a, AV_ERROR_MAX_STRING_SIZE, ret);
> +
> +        printf("!!Error: %s\n", a);
> +    }
> +
> +    return ret < 0;
> +}
>
Diederick C. Niehorster June 5, 2021, 11:29 a.m. UTC | #2
On Sat, Jun 5, 2021 at 8:51 AM Andreas Rheinhardt
<andreas.rheinhardt@outlook.com> wrote:
>
> Diederick Niehorster:
> > +
> > +    // since there is no equivalent of avformat_alloc_output_context2 for an input context,
> > +    // so we get this dirty code that users shouldn't have to write....
> > +    fmt_ctx = avformat_alloc_context();
> > +    fmt_ctx->url = av_strdup(dshow_input_name);
>
> Missing checks.
>
> > +    fmt_ctx->iformat = fmt;
> > +    if (fmt_ctx->iformat->priv_data_size > 0) {
>
> priv_data_size is not part of the public API; so the code is indeed
> dirty and so this example is unacceptable as-is. This might be a
> scenario where AVFMT_FLAG_PRIV_OPT might have been useful, see
> 2ff40b98ecbd9befadddc8fe665a391f99bfca32.
>
> > +        if (!(fmt_ctx->priv_data = av_mallocz(fmt_ctx->iformat->priv_data_size))) {
> > +            ret = AVERROR(ENOMEM);
> > +            goto end;
> > +        }
> > +        if (fmt_ctx->iformat->priv_class) {
> > +            *(const AVClass**)fmt_ctx->priv_data = fmt_ctx->iformat->priv_class;
> > +            av_opt_set_defaults(fmt_ctx->priv_data);
> > +        }
> > +    }
> > +    // end dirty code

I have fixed the rest, but I believe it is not possible to fix the
above problem with the current API? The code above shows the state i
need the format context and attached input context to be in when
calling av_opt_query_ranges. The device cannot be opened (that is
read_header should not be called) before calling av_opt_query_ranges,
as it is not possible to reliably query possible settings of the
device once it is opened (without temporarily closing it anyway, which
would be undesirable). One solution i see is to:
1. implement a avformat_alloc_input_context()
2. analogous to avformat_alloc_output_context2, add a few
if-statements in avformat_open_input() to skip steps if they are
already done on the passed-in AVFormatContext or its iformat.

It would e.g. allow something along the lines of:
AVFormatContext* fmt_ctx = NULL;
avformat_alloc_input_context(&fmt_ctx, NULL, "dshow",
"video=\"Integrated Webcam\"");
// ...
// query device capabilities, built up option dict for setting up
device as wanted based on query results
// ....
avformat_open_input(&fmt_ctx,NULL,NULL,opts);
// NB: filename and fmt inputs above are NULL as already set by
avformat_alloc_input_context

Would such an API extension be considered?

NB: The constraint of my implementation of the AVDevice Capabilties
API should also be documented. I propose, more generally: the
avdevice_capabilities_create/av_opt_query_ranges API has the
constraint that while it is always valid to query capabilities on an
unopened avdevice (as provided by avformat_alloc_input_context), some
devices may not allow querying capabilities once avformat_open_input()
has been called on them. In that case, upon av_opt_query_ranges these
devices will return AVERROR(EIO).

All the best,
Dee
Andreas Rheinhardt June 5, 2021, 12:41 p.m. UTC | #3
Diederick C. Niehorster:
> On Sat, Jun 5, 2021 at 8:51 AM Andreas Rheinhardt
> <andreas.rheinhardt@outlook.com> wrote:
>>
>> Diederick Niehorster:
>>> +
>>> +    // since there is no equivalent of avformat_alloc_output_context2 for an input context,
>>> +    // so we get this dirty code that users shouldn't have to write....
>>> +    fmt_ctx = avformat_alloc_context();
>>> +    fmt_ctx->url = av_strdup(dshow_input_name);
>>
>> Missing checks.
>>
>>> +    fmt_ctx->iformat = fmt;
>>> +    if (fmt_ctx->iformat->priv_data_size > 0) {
>>
>> priv_data_size is not part of the public API; so the code is indeed
>> dirty and so this example is unacceptable as-is. This might be a
>> scenario where AVFMT_FLAG_PRIV_OPT might have been useful, see
>> 2ff40b98ecbd9befadddc8fe665a391f99bfca32.
>>
>>> +        if (!(fmt_ctx->priv_data = av_mallocz(fmt_ctx->iformat->priv_data_size))) {
>>> +            ret = AVERROR(ENOMEM);
>>> +            goto end;
>>> +        }
>>> +        if (fmt_ctx->iformat->priv_class) {
>>> +            *(const AVClass**)fmt_ctx->priv_data = fmt_ctx->iformat->priv_class;
>>> +            av_opt_set_defaults(fmt_ctx->priv_data);
>>> +        }
>>> +    }
>>> +    // end dirty code
> 
> I have fixed the rest, but I believe it is not possible to fix the
> above problem with the current API? The code above shows the state i
> need the format context and attached input context to be in when
> calling av_opt_query_ranges. The device cannot be opened (that is
> read_header should not be called) before calling av_opt_query_ranges,
> as it is not possible to reliably query possible settings of the
> device once it is opened (without temporarily closing it anyway, which
> would be undesirable). One solution i see is to:
> 1. implement a avformat_alloc_input_context()
> 2. analogous to avformat_alloc_output_context2, add a few
> if-statements in avformat_open_input() to skip steps if they are
> already done on the passed-in AVFormatContext or its iformat.
> 

Have you looked at
2ff40b98ecbd9befadddc8fe665a391f99bfca32/0a071f7124beaf0929f772a8618ac1b6c17b0222?

> It would e.g. allow something along the lines of:
> AVFormatContext* fmt_ctx = NULL;
> avformat_alloc_input_context(&fmt_ctx, NULL, "dshow",
> "video=\"Integrated Webcam\"");
> // ...
> // query device capabilities, built up option dict for setting up
> device as wanted based on query results
> // ....
> avformat_open_input(&fmt_ctx,NULL,NULL,opts);
> // NB: filename and fmt inputs above are NULL as already set by
> avformat_alloc_input_context
> 
> Would such an API extension be considered?
> 
> NB: The constraint of my implementation of the AVDevice Capabilties
> API should also be documented. I propose, more generally: the
> avdevice_capabilities_create/av_opt_query_ranges API has the
> constraint that while it is always valid to query capabilities on an
> unopened avdevice (as provided by avformat_alloc_input_context), some
> devices may not allow querying capabilities once avformat_open_input()
> has been called on them. In that case, upon av_opt_query_ranges these
> devices will return AVERROR(EIO).
> 
Of course you can propose all sorts of API extensions; but don't be too
beholden to the current API. It has never been tested in practice and it
shows (e.g. it is built on top of the AVOptionRanges API which is quite
underdocumented (see the remark/gotcha about the AVOptionRange structs
not being individually allocated for your patch 3/4); and making each
query_range callback allocate an AVOptionRange unnecessarily ties
sizeof(AVOptionRange) to the ABI -- I don't get why this has been done).
Also what you are experiencing here shows that there probably never was
a working example.

Besides that, there are more weird things: Why is
AVDeviceCapabilitiesQuery a public struct and not an opaque one if it is
not to be used? Why is av_device_capabilities public if it should not be
used by a user?

- Andreas
Diederick C. Niehorster June 5, 2021, 1:33 p.m. UTC | #4
On Sat, Jun 5, 2021 at 2:41 PM Andreas Rheinhardt
<andreas.rheinhardt@outlook.com> wrote:
>
> Have you looked at
> 2ff40b98ecbd9befadddc8fe665a391f99bfca32/0a071f7124beaf0929f772a8618ac1b6c17b0222?

I have, but didn't know what to do with it. Are you suggesting
reverting part of those two commits? The bit of documentation of the
API (lines 334--402) of avdevice.h suggest it was the intention for
the API to work with an allocated, but not opened, input or output
context and at the time that was written, it was possible to create
either. Now, as you know, input formats can only be directly opened
and created. It seems the removed flag and such you are referring to
in the commits had another use, and i don't see that a new flag is
needed. If i would implement something like
avformat_alloc_input_context, we:
1. would again be able to make allocated but unopened iformats
(admittedly for the very small use case of using this API, but at no
cost if used in other cases
2. would only need to make small changes to avformat_open_input (don't
set url if input NULL, else overwrite??; don't allocate priv_data if
already set; think thats it)
So no need for adding a flag, or reintroducing demuxer_open or so, i think.

> > [...]
> >
> Of course you can propose all sorts of API extensions; but don't be too
> beholden to the current API. It has never been tested in practice and it
> shows (e.g. it is built on top of the AVOptionRanges API which is quite
> underdocumented (see the remark/gotcha about the AVOptionRange structs
> not being individually allocated for your patch 3/4); and making each
> query_range callback allocate an AVOptionRange unnecessarily ties
> sizeof(AVOptionRange) to the ABI -- I don't get why this has been done).
> Also what you are experiencing here shows that there probably never was
> a working example.

Yes, I had trouble wrapping my head around the AVOptionRanges API, the
only usage example i found was av_opt_query_ranges_default() in
libavutil/opt.c and regarding the gotcha, it seems i
misread/misunderstood something of what i read there. Do you think
making sizeof(AVOptionRange) part of the public ABI is something to
fix / can it still be fixed?

> Besides that, there are more weird things: Why is
> AVDeviceCapabilitiesQuery a public struct and not an opaque one if it is
> not to be used? Why is av_device_capabilities public if it should not be
> used by a user?

Lets clean it up. And since it hasn't been used yet, i guess i can do
so without deprecations for either?
Just to check, you are suggesting the following, correct?
1. from avdevice.h, remove typedef struct AVDeviceCapabilitiesQuery
and extern const AVOption av_device_capabilities[];
2. change to avdevice_capabilities_create and
avdevice_capabilities_free signatures to taking **void as first input
3. remove forward declare of struct AVDeviceCapabilitiesQuery; from avformat.h
4. change signature of create_device_capabilities and
free_device_capabilities to taking *void as second argument

Thanks and all the best,
Dee
diff mbox series

Patch

diff --git a/configure b/configure
index 82367fd30d..5e9666d017 100755
--- a/configure
+++ b/configure
@@ -1705,6 +1705,7 @@  EXAMPLE_LIST="
     decode_audio_example
     decode_video_example
     demuxing_decoding_example
+    device_get_capabilities_example
     encode_audio_example
     encode_video_example
     extract_mvs_example
@@ -3712,6 +3713,7 @@  avio_reading_deps="avformat avcodec avutil"
 decode_audio_example_deps="avcodec avutil"
 decode_video_example_deps="avcodec avutil"
 demuxing_decoding_example_deps="avcodec avformat avutil"
+device_get_capabilities_example_deps="avdevice avformat avutil"
 encode_audio_example_deps="avcodec avutil"
 encode_video_example_deps="avcodec avutil"
 extract_mvs_example_deps="avcodec avformat avutil"
diff --git a/doc/examples/.gitignore b/doc/examples/.gitignore
index 44960e1de7..256f33a600 100644
--- a/doc/examples/.gitignore
+++ b/doc/examples/.gitignore
@@ -3,6 +3,7 @@ 
 /decode_audio
 /decode_video
 /demuxing_decoding
+/device_get_capabilities
 /encode_audio
 /encode_video
 /extract_mvs
diff --git a/doc/examples/Makefile b/doc/examples/Makefile
index 81bfd34d5d..7988ed4226 100644
--- a/doc/examples/Makefile
+++ b/doc/examples/Makefile
@@ -1,26 +1,27 @@ 
-EXAMPLES-$(CONFIG_AVIO_LIST_DIR_EXAMPLE)     += avio_list_dir
-EXAMPLES-$(CONFIG_AVIO_READING_EXAMPLE)      += avio_reading
-EXAMPLES-$(CONFIG_DECODE_AUDIO_EXAMPLE)      += decode_audio
-EXAMPLES-$(CONFIG_DECODE_VIDEO_EXAMPLE)      += decode_video
-EXAMPLES-$(CONFIG_DEMUXING_DECODING_EXAMPLE) += demuxing_decoding
-EXAMPLES-$(CONFIG_ENCODE_AUDIO_EXAMPLE)      += encode_audio
-EXAMPLES-$(CONFIG_ENCODE_VIDEO_EXAMPLE)      += encode_video
-EXAMPLES-$(CONFIG_EXTRACT_MVS_EXAMPLE)       += extract_mvs
-EXAMPLES-$(CONFIG_FILTER_AUDIO_EXAMPLE)      += filter_audio
-EXAMPLES-$(CONFIG_FILTERING_AUDIO_EXAMPLE)   += filtering_audio
-EXAMPLES-$(CONFIG_FILTERING_VIDEO_EXAMPLE)   += filtering_video
-EXAMPLES-$(CONFIG_HTTP_MULTICLIENT_EXAMPLE)  += http_multiclient
-EXAMPLES-$(CONFIG_HW_DECODE_EXAMPLE)         += hw_decode
-EXAMPLES-$(CONFIG_METADATA_EXAMPLE)          += metadata
-EXAMPLES-$(CONFIG_MUXING_EXAMPLE)            += muxing
-EXAMPLES-$(CONFIG_QSVDEC_EXAMPLE)            += qsvdec
-EXAMPLES-$(CONFIG_REMUXING_EXAMPLE)          += remuxing
-EXAMPLES-$(CONFIG_RESAMPLING_AUDIO_EXAMPLE)  += resampling_audio
-EXAMPLES-$(CONFIG_SCALING_VIDEO_EXAMPLE)     += scaling_video
-EXAMPLES-$(CONFIG_TRANSCODE_AAC_EXAMPLE)     += transcode_aac
-EXAMPLES-$(CONFIG_TRANSCODING_EXAMPLE)       += transcoding
-EXAMPLES-$(CONFIG_VAAPI_ENCODE_EXAMPLE)      += vaapi_encode
-EXAMPLES-$(CONFIG_VAAPI_TRANSCODE_EXAMPLE)   += vaapi_transcode
+EXAMPLES-$(CONFIG_AVIO_LIST_DIR_EXAMPLE)             += avio_list_dir
+EXAMPLES-$(CONFIG_AVIO_READING_EXAMPLE)              += avio_reading
+EXAMPLES-$(CONFIG_DECODE_AUDIO_EXAMPLE)              += decode_audio
+EXAMPLES-$(CONFIG_DECODE_VIDEO_EXAMPLE)              += decode_video
+EXAMPLES-$(CONFIG_DEMUXING_DECODING_EXAMPLE)         += demuxing_decoding
+EXAMPLES-$(CONFIG_DEVICE_GET_CAPABILITIES_EXAMPLE)   += device_get_capabilities
+EXAMPLES-$(CONFIG_ENCODE_AUDIO_EXAMPLE)              += encode_audio
+EXAMPLES-$(CONFIG_ENCODE_VIDEO_EXAMPLE)              += encode_video
+EXAMPLES-$(CONFIG_EXTRACT_MVS_EXAMPLE)               += extract_mvs
+EXAMPLES-$(CONFIG_FILTER_AUDIO_EXAMPLE)              += filter_audio
+EXAMPLES-$(CONFIG_FILTERING_AUDIO_EXAMPLE)           += filtering_audio
+EXAMPLES-$(CONFIG_FILTERING_VIDEO_EXAMPLE)           += filtering_video
+EXAMPLES-$(CONFIG_HTTP_MULTICLIENT_EXAMPLE)          += http_multiclient
+EXAMPLES-$(CONFIG_HW_DECODE_EXAMPLE)                 += hw_decode
+EXAMPLES-$(CONFIG_METADATA_EXAMPLE)                  += metadata
+EXAMPLES-$(CONFIG_MUXING_EXAMPLE)                    += muxing
+EXAMPLES-$(CONFIG_QSVDEC_EXAMPLE)                    += qsvdec
+EXAMPLES-$(CONFIG_REMUXING_EXAMPLE)                  += remuxing
+EXAMPLES-$(CONFIG_RESAMPLING_AUDIO_EXAMPLE)          += resampling_audio
+EXAMPLES-$(CONFIG_SCALING_VIDEO_EXAMPLE)             += scaling_video
+EXAMPLES-$(CONFIG_TRANSCODE_AAC_EXAMPLE)             += transcode_aac
+EXAMPLES-$(CONFIG_TRANSCODING_EXAMPLE)               += transcoding
+EXAMPLES-$(CONFIG_VAAPI_ENCODE_EXAMPLE)              += vaapi_encode
+EXAMPLES-$(CONFIG_VAAPI_TRANSCODE_EXAMPLE)           += vaapi_transcode
 
 EXAMPLES       := $(EXAMPLES-yes:%=doc/examples/%$(PROGSSUF)$(EXESUF))
 EXAMPLES_G     := $(EXAMPLES-yes:%=doc/examples/%$(PROGSSUF)_g$(EXESUF))
diff --git a/doc/examples/Makefile.example b/doc/examples/Makefile.example
index a232d97f98..b861b9cc74 100644
--- a/doc/examples/Makefile.example
+++ b/doc/examples/Makefile.example
@@ -16,6 +16,7 @@  EXAMPLES=       avio_list_dir                      \
                 decode_audio                       \
                 decode_video                       \
                 demuxing_decoding                  \
+                device_get_capabilities            \
                 encode_audio                       \
                 encode_video                       \
                 extract_mvs                        \
diff --git a/doc/examples/device_get_capabilities.c b/doc/examples/device_get_capabilities.c
new file mode 100644
index 0000000000..f3b9f31e0d
--- /dev/null
+++ b/doc/examples/device_get_capabilities.c
@@ -0,0 +1,151 @@ 
+/*
+ * Copyright (c) 2021 Diederick Niehorster
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * @file
+ * avdevice getting capabilities example.
+ *
+ * Shows how to use the avdevice capabilities API to probe
+ * device capabilities (supported codecs, pixel formats, sample
+ * formats, resolutions, channel counts, etc)
+ * @example device_get_capabilities.c
+ */
+
+#include <libavdevice/avdevice.h>
+#include <libavutil/avstring.h>
+#include <libavformat/avformat.h>
+#include <libavutil/log.h>
+
+
+
+int main (int argc, char **argv)
+{
+    int ret = 0;
+    const char* dshow_input_name = NULL;
+    const char* query_cap = NULL;
+    const char* set_cap_name = NULL;
+    const char* set_cap_value = NULL;
+
+    const AVInputFormat* fmt = NULL;
+    AVFormatContext* fmt_ctx = NULL;
+    AVDeviceCapabilitiesQuery* caps = NULL;
+    AVOptionRanges* ranges = NULL;
+
+    if (argc != 5) {
+        fprintf(stderr, "usage: %s  dshow_input_name query_cap set_cap_name set_cap_value\n"
+                "API example program to show how to use the avdevice\n"
+                "capabilities API to probe device capabilities \n"
+                "(supported codecs, pixel formats, sample formats,\n"
+                "resolutions, channel counts, etc).\n\n"
+                "example invocation: "
+                "%s video=\"Integrated Webcam\" frame_size pixel_format yuyv422",
+                argv[0], argv[0]);
+        exit(1);
+    }
+    dshow_input_name = argv[1];
+    query_cap = argv[2];
+    set_cap_name = argv[3];
+    set_cap_value = argv[4];
+
+    // make sure avdevices can be found
+    avdevice_register_all();
+    // find our device (capabilities API is currently
+    // only implemented for dshow device, so hardcode that)
+    fmt = av_find_input_format("dshow");
+
+    // since there is no equivalent of avformat_alloc_output_context2 for an input context,
+    // so we get this dirty code that users shouldn't have to write....
+    fmt_ctx = avformat_alloc_context();
+    fmt_ctx->url = av_strdup(dshow_input_name);
+    fmt_ctx->iformat = fmt;
+    if (fmt_ctx->iformat->priv_data_size > 0) {
+        if (!(fmt_ctx->priv_data = av_mallocz(fmt_ctx->iformat->priv_data_size))) {
+            ret = AVERROR(ENOMEM);
+            goto end;
+        }
+        if (fmt_ctx->iformat->priv_class) {
+            *(const AVClass**)fmt_ctx->priv_data = fmt_ctx->iformat->priv_class;
+            av_opt_set_defaults(fmt_ctx->priv_data);
+        }
+    }
+    // end dirty code
+
+    // query the capability without any filter set
+    ret = avdevice_capabilities_create(&caps, fmt_ctx, NULL);
+    if (ret < 0)
+        goto end;
+
+    ret = av_opt_query_ranges(&ranges, caps, query_cap, AV_OPT_MULTI_COMPONENT_RANGE);
+    if (ret < 0)
+        goto end;
+
+    for (int range_index = 0; range_index < ranges->nb_ranges; range_index++) {
+        for (int component_index = 0; component_index < ranges->nb_components; component_index++)
+        {
+            AVOptionRange *range = ranges->range[ranges->nb_ranges * component_index + range_index];
+            if (component_index > 0)
+                printf(", ");
+            printf("%s: %.2f -- %.2f", range->str, range->value_min, range->value_max);
+        }
+        printf("\n");
+    }
+    av_opt_freep_ranges(&ranges);
+
+    printf("=============\n");
+
+    // set one capability, which may filter out some returned capabilities
+    // (or all, if set to an invalid value)
+    ret = av_opt_set(caps, set_cap_name, set_cap_value, 0);
+    if (ret < 0)
+        goto end;
+
+    ret = av_opt_query_ranges(&ranges, caps, query_cap, AV_OPT_MULTI_COMPONENT_RANGE);
+    if (ret < 0)
+        goto end;
+
+    for (int range_index = 0; range_index < ranges->nb_ranges; range_index++) {
+        for (int component_index = 0; component_index < ranges->nb_components; component_index++)
+        {
+            AVOptionRange* range = ranges->range[ranges->nb_ranges * component_index + range_index];
+            if (component_index > 0)
+                printf(", ");
+            printf("%s: %.2f -- %.2f", range->str, range->value_min, range->value_max);
+        }
+        printf("\n");
+    }
+
+
+end:
+    av_opt_freep_ranges(&ranges);
+    avdevice_capabilities_free(&caps, fmt_ctx);
+
+    avformat_close_input(&fmt_ctx);
+
+    if (ret < 0) {
+        char a[AV_ERROR_MAX_STRING_SIZE] = { 0 };
+        av_make_error_string(a, AV_ERROR_MAX_STRING_SIZE, ret);
+
+        printf("!!Error: %s\n", a);
+    }
+
+    return ret < 0;
+}