diff mbox series

[FFmpeg-devel,3/4] avdevice/dshow: implement capabilities API

Message ID 20210603224552.1218-4-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
This implements avdevice_capabilities_create and avdevice_capabilities_free for the dshow device.

Signed-off-by: Diederick Niehorster <dcnieho@gmail.com>
---
 libavdevice/dshow.c | 498 ++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 462 insertions(+), 36 deletions(-)

Comments

Andreas Rheinhardt June 5, 2021, 12:25 p.m. UTC | #1
Diederick Niehorster:
> This implements avdevice_capabilities_create and avdevice_capabilities_free for the dshow device.
> 
> Signed-off-by: Diederick Niehorster <dcnieho@gmail.com>
> ---
>  libavdevice/dshow.c | 498 ++++++++++++++++++++++++++++++++++++++++----
>  1 file changed, 462 insertions(+), 36 deletions(-)
> 
> diff --git a/libavdevice/dshow.c b/libavdevice/dshow.c
> index 8d0a6fcc09..c65492119e 100644
> --- a/libavdevice/dshow.c
> +++ b/libavdevice/dshow.c
> @@ -30,6 +30,48 @@
>  #include "objidl.h"
>  #include "shlwapi.h"
>  
> +enum DshowCapQueryType {
> +    CAP_QUERY_NONE = 0,
> +    CAP_QUERY_SAMPLE_FORMAT,
> +    CAP_QUERY_SAMPLE_RATE,
> +    CAP_QUERY_CHANNELS,
> +    CAP_QUERY_CODEC,
> +    CAP_QUERY_PIXEL_FORMAT,
> +    CAP_QUERY_FRAME_SIZE,
> +    CAP_QUERY_FPS
> +};
> +typedef struct DshowCapQueryTypeEntry {
> +    const char*             name;
> +    enum DshowCapQueryType  query_type;
> +} DshowCapQueryTypeEntry;
> +
> +static const DshowCapQueryTypeEntry query_table[] = {
> +    { "sample_format",  CAP_QUERY_SAMPLE_FORMAT },
> +    { "sample_rate",    CAP_QUERY_SAMPLE_RATE },
> +    { "channels",       CAP_QUERY_CHANNELS },
> +    { "codec",          CAP_QUERY_CODEC },
> +    { "pixel_format",   CAP_QUERY_PIXEL_FORMAT },
> +    { "frame_size",     CAP_QUERY_FRAME_SIZE },
> +    { "fps",            CAP_QUERY_FPS },
> +};
> +
> +static enum DshowCapQueryType dshow_get_query_type(const char* option_name)
> +{
> +    for (int i = 0; i < FF_ARRAY_ELEMS(query_table); ++i) {
> +        if (!strcmp(query_table[i].name, option_name))
> +            return query_table[i].query_type;
> +    }
> +    return CAP_QUERY_NONE;
> +}
> +
> +static const char* dshow_get_query_type_string(enum DshowCapQueryType query_type)
> +{
> +    for (int i = 0; i < FF_ARRAY_ELEMS(query_table); ++i) {
> +        if (query_table[i].query_type == query_type)
> +            return query_table[i].name;
> +    }
> +    return NULL;
> +}
>  
>  static enum AVPixelFormat dshow_pixfmt(DWORD biCompression, WORD biBitCount)
>  {
> @@ -54,6 +96,26 @@ static enum AVPixelFormat dshow_pixfmt(DWORD biCompression, WORD biBitCount)
>      return avpriv_find_pix_fmt(avpriv_get_raw_pix_fmt_tags(), biCompression); // all others
>  }
>  
> +static enum AVCodecID waveform_codec_id(enum AVSampleFormat sample_fmt)
> +{
> +    switch (sample_fmt) {
> +    case AV_SAMPLE_FMT_U8:  return AV_CODEC_ID_PCM_U8;
> +    case AV_SAMPLE_FMT_S16: return AV_CODEC_ID_PCM_S16LE;
> +    case AV_SAMPLE_FMT_S32: return AV_CODEC_ID_PCM_S32LE;
> +    default:                return AV_CODEC_ID_NONE; /* Should never happen. */
> +    }
> +}
> +
> +static enum AVSampleFormat sample_fmt_bits_per_sample(int bits)
> +{
> +    switch (bits) {
> +    case 8:  return AV_SAMPLE_FMT_U8;
> +    case 16: return AV_SAMPLE_FMT_S16;
> +    case 32: return AV_SAMPLE_FMT_S32;
> +    default: return AV_SAMPLE_FMT_NONE; /* Should never happen. */
> +    }
> +}
> +
>  static int
>  dshow_read_close(AVFormatContext *s)
>  {
> @@ -317,10 +379,13 @@ fail1:
>   * try to set parameters specified through AVOptions and if successful
>   * return 1 in *pformat_set.
>   * If pformat_set is NULL, list all pin capabilities.
> + * When listing pin capabilities, if ranges is NULL, output to log,
> + * else store capabilities in ranges.
>   */
>  static void
>  dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype,
> -                    IPin *pin, int *pformat_set)
> +                    IPin *pin, int *pformat_set,
> +                    AVOptionRanges *ranges, enum DshowCapQueryType query_type)
>  {
>      struct dshow_ctx *ctx = avctx->priv_data;
>      IAMStreamConfig *config = NULL;
> @@ -338,7 +403,11 @@ dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype,
>      if (!caps)
>          goto end;
>  
> -    for (i = 0; i < n && !format_set; i++) {
> +    for (i = 0; i < n && (!format_set || ranges); i++) {
> +        AVOptionRange *range = NULL;
> +        AVOptionRange **range_list = NULL;
> +        int nb_range = 0;
> +
>          r = IAMStreamConfig_GetStreamCaps(config, i, &type, (void *) caps);
>          if (r != S_OK)
>              goto next;
> @@ -365,7 +434,7 @@ dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype,
>              } else {
>                  goto next;
>              }
> -            if (!pformat_set) {
> +            if (!pformat_set && !ranges) {
>                  enum AVPixelFormat pix_fmt = dshow_pixfmt(bih->biCompression, bih->biBitCount);
>                  if (pix_fmt == AV_PIX_FMT_NONE) {
>                      enum AVCodecID codec_id = av_codec_get_id(tags, bih->biCompression);
> @@ -410,6 +479,81 @@ dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype,
>                  bih->biWidth  = ctx->requested_width;
>                  bih->biHeight = ctx->requested_height;
>              }
> +
> +            if (ranges) {
> +                if (query_type == CAP_QUERY_FRAME_SIZE) {
> +                    for (int j = 0; j < 3; j++) {
> +                        range = av_mallocz(sizeof(AVOptionRange));
> +                        if (!range)
> +                            goto next;
> +                        range->str = av_strdup(j == 0 ? "pixel_count" : (j == 1 ? "width" : (j == 2 ? "height" : "")));
> +                        if (!range->str)
> +                            goto next;
> +                        switch (j)
> +                        {
> +                        case 0:
> +                            range->value_min = vcaps->MinOutputSize.cx * vcaps->MinOutputSize.cy;
> +                            range->value_max = vcaps->MaxOutputSize.cx * vcaps->MaxOutputSize.cy;
> +                            break;
> +                        case 1:
> +                            range->value_min = vcaps->MinOutputSize.cx;
> +                            range->value_max = vcaps->MaxOutputSize.cx;
> +                            break;
> +                        case 2:
> +                            range->value_min = vcaps->MinOutputSize.cy;
> +                            range->value_max = vcaps->MaxOutputSize.cy;
> +                            break;
> +                        }
> +                        range->is_range = range->value_min != range->value_max;
> +
> +                        if (av_dynarray_add_nofree(&range_list,
> +                                                   &nb_range, range) < 0)
> +                            goto next;
> +                        range = NULL;  // copied into array, make sure not freed below
> +                    }
> +                }
> +                else {
> +                    range = av_mallocz(sizeof(AVOptionRange));
> +                    if (!range)
> +                        goto next;
> +                    range->str = av_strdup(dshow_get_query_type_string(query_type));
> +                    if (!range->str)
> +                        goto next;
> +
> +                    switch (query_type)
> +                    {
> +                    case CAP_QUERY_CODEC:
> +                        if (dshow_pixfmt(bih->biCompression, bih->biBitCount) == AV_PIX_FMT_NONE) {
> +                            const AVCodecTag* const tags[] = { avformat_get_riff_video_tags(), NULL };
> +                            range->value_min = av_codec_get_id(tags, bih->biCompression);
> +                        }
> +                        else
> +                            range->value_min = AV_CODEC_ID_RAWVIDEO;
> +                        range->value_max = range->value_min;
> +                        break;
> +                    case CAP_QUERY_PIXEL_FORMAT:
> +                        range->value_min = range->value_max = dshow_pixfmt(bih->biCompression, bih->biBitCount);
> +                        range->value_min;
> +                        break;
> +                    case CAP_QUERY_FPS:
> +                        range->value_min = 1e7 / vcaps->MaxFrameInterval;
> +                        range->value_max = 1e7 / vcaps->MinFrameInterval;
> +                        break;
> +
> +                    default:
> +                        // an audio property is being queried, output 0
> +                        range->value_min = range->value_max = 0;
> +                        break;
> +                    }
> +
> +                    range->is_range = range->value_min != range->value_max;
> +
> +                    if (av_dynarray_add_nofree(&range_list,
> +                                               &nb_range, range) < 0)
> +                        goto next;
> +                    range = NULL;  // copied into array, make sure not freed below
> +                }
> +            }
>          } else {
>              AUDIO_STREAM_CONFIG_CAPS *acaps = caps;
>              WAVEFORMATEX *fx;
> @@ -421,7 +565,7 @@ dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype,
>              } else {
>                  goto next;
>              }
> -            if (!pformat_set) {
> +            if (!pformat_set && !ranges) {
>                  av_log(avctx, AV_LOG_INFO, "  min ch=%lu bits=%lu rate=%6lu max ch=%lu bits=%lu rate=%6lu\n",
>                         acaps->MinimumChannels, acaps->MinimumBitsPerSample, acaps->MinimumSampleFrequency,
>                         acaps->MaximumChannels, acaps->MaximumBitsPerSample, acaps->MaximumSampleFrequency);
> @@ -445,11 +589,90 @@ dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype,
>                      goto next;
>                  fx->nChannels = ctx->channels;
>              }
> +
> +            if (ranges) {
> +                if (query_type == CAP_QUERY_FRAME_SIZE) {
> +                    for (int j = 0; j < 3; j++) {
> +                        range = av_mallocz(sizeof(AVOptionRange));

It seems that you are allocating individual AVOptionRange structures.
This is bad, because that is not how av_opt_freep_ranges() treats them:
They are supposed to be pointers into one big array of AVOptionRange
structures.

Anyway, there is quite a lot of code duplication (mallocz, strdup,
av_dynarray_add_nofree) here. Could this be avoided?

> +                        if (!range)
> +                            goto next;
> +                        range->str = av_strdup(j == 0 ? "pixel_count" : (j == 1 ? "width" : (j == 2 ? "height" : "")));
> +                        if (!range->str)
> +                            goto next;
> +                        // an video property is being queried, output 0
> +                        range->value_min = range->value_max = 0;
> +                        range->is_range = 0;
> +
> +                        if (av_dynarray_add_nofree(&range_list,
> +                                                   &nb_range, range) < 0)
> +                            goto next;
> +                        range = NULL;  // copied into array, make sure not freed below
> +                    }
> +                }
> +                else {
> +                    range = av_mallocz(sizeof(AVOptionRange));
> +                    if (!range)
> +                        goto next;
> +                    range->str = av_strdup(dshow_get_query_type_string(query_type));
> +                    if (!range->str)
> +                        goto next;
> +
> +                    switch (query_type)
> +                    {
> +                    case CAP_QUERY_SAMPLE_FORMAT:
> +                        range->value_min = sample_fmt_bits_per_sample(acaps->MinimumBitsPerSample);
> +                        range->value_max = sample_fmt_bits_per_sample(acaps->MaximumBitsPerSample);
> +                        break;
> +                    case CAP_QUERY_SAMPLE_RATE:
> +                        range->value_min = acaps->MinimumSampleFrequency;
> +                        range->value_max = acaps->MaximumSampleFrequency;
> +                        break;
> +                    case CAP_QUERY_CHANNELS:
> +                        range->value_min = acaps->MinimumChannels;
> +                        range->value_max = acaps->MaximumChannels;
> +                        break;
> +
> +                    default:
> +                        // an video property is being queried, output 0
> +                        range->value_min = range->value_max = 0;
> +                        break;
> +                    }
> +
> +                    range->is_range = range->value_min != range->value_max;
> +
> +                    if (av_dynarray_add_nofree(&range_list,
> +                                               &nb_range, range) < 0)
> +                        goto next;
> +                    range = NULL;  // copied into array, make sure not freed below
> +                }
> +            }
>          }
>          if (IAMStreamConfig_SetFormat(config, type) != S_OK)
>              goto next;
> +        else if (ranges) {
> +            // format matched and could be set successfully. Add capabilities to ranges output
> +            for (int j=0; j< nb_range; ++j) {
> +                if (av_dynarray_add_nofree(&ranges->range,
> +                                           &ranges->nb_ranges, range_list[j]) < 0)
> +                    goto next;

The way you are doing it here has two drawbacks:
1. av_dynarray_add_nofree() overallocates, but it only stores the amount
of currently valid entries, not the amount of allocated entries. To
achieve this, it simply presumes that the array has already been
allocated by av_dynarray_add_nofree (it overallocates to a power-of-two
and it presumes the array to be so allocated, so if range->nb_ranges is
(say) 9, it just presumes that there is enough space for the next
element). I don't know whether this is always fulfilled in this code,
but I don't like this implicit presumption.
2. You actually know how many elements your array will have at the end:
ranges->nb_ranges + nb_range. Why don't you reallocate the array once?

> +                range_list[j] = NULL;  // copied into array, make sure not freed below
> +            }
> +        }
>          format_set = 1;
>  next:
> +        if (range) {
> +            av_freep(&range->str);
> +            av_freep(&range);
> +        }
> +        if (range_list) {
> +            for (int j = 0; j < nb_range; ++j) {
> +                if (range_list[j]) {
> +                    av_freep(&range_list[j]->str);
> +                    av_freep(&range_list[j]);
> +                }
> +            }
> +            av_freep(&range_list);
> +        }
>          if (type->pbFormat)
>              CoTaskMemFree(type->pbFormat);
>          CoTaskMemFree(type);
> @@ -558,10 +781,13 @@ end:
>   * devtype, retrieve the first output pin and return the pointer to the
>   * object found in *ppin.
>   * If ppin is NULL, cycle through all pins listing audio/video capabilities.
> + * If ppin is not NULL and ranges is also not null, enumerate all formats
> + * supported by the selected pin.
>   */
>  static int
>  dshow_cycle_pins(AVFormatContext *avctx, enum dshowDeviceType devtype,
> -                 enum dshowSourceFilterType sourcetype, IBaseFilter *device_filter, IPin **ppin)
> +                 enum dshowSourceFilterType sourcetype, IBaseFilter *device_filter,
> +                 IPin **ppin, AVOptionRanges *ranges, enum DshowCapQueryType query_type)
>  {
>      struct dshow_ctx *ctx = avctx->priv_data;
>      IEnumPins *pins = 0;
> @@ -630,7 +856,7 @@ dshow_cycle_pins(AVFormatContext *avctx, enum dshowDeviceType devtype,
>  
>          if (!ppin) {
>              av_log(avctx, AV_LOG_INFO, " Pin \"%s\" (alternative pin name \"%s\")\n", name_buf, pin_buf);
> -            dshow_cycle_formats(avctx, devtype, pin, NULL);
> +            dshow_cycle_formats(avctx, devtype, pin, NULL, NULL, CAP_QUERY_NONE);
>              goto next;
>          }
>  
> @@ -642,13 +868,13 @@ dshow_cycle_pins(AVFormatContext *avctx, enum dshowDeviceType devtype,
>              }
>          }
>  
> -        if (set_format) {
> -            dshow_cycle_formats(avctx, devtype, pin, &format_set);
> +        if (set_format || ranges) {
> +            dshow_cycle_formats(avctx, devtype, pin, &format_set, ranges, query_type);
>              if (!format_set) {
>                  goto next;
>              }
>          }
> -        if (devtype == AudioDevice && ctx->audio_buffer_size) {
> +        if (devtype == AudioDevice && ctx->audio_buffer_size && !ranges) {
>              if (dshow_set_audio_buffer_size(avctx, pin) < 0) {
>                  av_log(avctx, AV_LOG_ERROR, "unable to set audio buffer size %d to pin, using pin anyway...", ctx->audio_buffer_size);
>              }
> @@ -673,8 +899,22 @@ next:
>              IEnumMediaTypes_Release(types);
>          if (p)
>              IKsPropertySet_Release(p);
> -        if (device_pin != pin)
> +        if (device_pin != pin) {
>              IPin_Release(pin);
> +            // clear all ranges info again, wrong pin
> +            if (ranges) {
> +                for (int i = 0; i < ranges->nb_ranges; i++) {
> +                    AVOptionRange* range = ranges->range[i];
> +                    if (range) {
> +                        av_freep(&range->str);
> +                        av_freep(&ranges->range[i]);
> +                    }
> +                }
> +                av_freep(&ranges->range);
> +                ranges->nb_ranges = 0;
> +            }
> +            device_pin = NULL;
> +        }
>          av_free(name_buf);
>          av_free(pin_buf);
>          if (pin_id)
> @@ -706,17 +946,19 @@ next:
>   */
>  static int
>  dshow_list_device_options(AVFormatContext *avctx, ICreateDevEnum *devenum,
> -                          enum dshowDeviceType devtype, enum dshowSourceFilterType sourcetype)
> +                          enum dshowDeviceType devtype, enum dshowSourceFilterType sourcetype,
> +                          AVOptionRanges *ranges, enum DshowCapQueryType query_type)
>  {
>      struct dshow_ctx *ctx = avctx->priv_data;
>      IBaseFilter *device_filter = NULL;
> +    IPin *device_pin = NULL;
>      char *device_unique_name = NULL;
>      int r;
>  
>      if ((r = dshow_cycle_devices(avctx, devenum, devtype, sourcetype, &device_filter, &device_unique_name)) < 0)
>          return r;
>      ctx->device_filter[devtype] = device_filter;
> -    if ((r = dshow_cycle_pins(avctx, devtype, sourcetype, device_filter, NULL)) < 0)
> +    if ((r = dshow_cycle_pins(avctx, devtype, sourcetype, device_filter, ranges ? &device_pin : NULL, ranges, query_type)) < 0)
>          return r;
>      av_freep(&device_unique_name);
>      return 0;
> @@ -800,7 +1042,7 @@ dshow_open_device(AVFormatContext *avctx, ICreateDevEnum *devenum,
>          goto error;
>      }
>  
> -    if ((r = dshow_cycle_pins(avctx, devtype, sourcetype, device_filter, &device_pin)) < 0) {
> +    if ((r = dshow_cycle_pins(avctx, devtype, sourcetype, device_filter, &device_pin, NULL, CAP_QUERY_NONE)) < 0) {
>          ret = r;
>          goto error;
>      }
> @@ -912,26 +1154,6 @@ error:
>      return ret;
>  }
>  
> -static enum AVCodecID waveform_codec_id(enum AVSampleFormat sample_fmt)
> -{
> -    switch (sample_fmt) {
> -    case AV_SAMPLE_FMT_U8:  return AV_CODEC_ID_PCM_U8;
> -    case AV_SAMPLE_FMT_S16: return AV_CODEC_ID_PCM_S16LE;
> -    case AV_SAMPLE_FMT_S32: return AV_CODEC_ID_PCM_S32LE;
> -    default:                return AV_CODEC_ID_NONE; /* Should never happen. */
> -    }
> -}
> -
> -static enum AVSampleFormat sample_fmt_bits_per_sample(int bits)
> -{
> -    switch (bits) {
> -    case 8:  return AV_SAMPLE_FMT_U8;
> -    case 16: return AV_SAMPLE_FMT_S16;
> -    case 32: return AV_SAMPLE_FMT_S32;
> -    default: return AV_SAMPLE_FMT_NONE; /* Should never happen. */
> -    }
> -}
> -
>  static int
>  dshow_add_device(AVFormatContext *avctx,
>                   enum dshowDeviceType devtype)
> @@ -1138,14 +1360,14 @@ static int dshow_read_header(AVFormatContext *avctx)
>      }
>      if (ctx->list_options) {
>          if (ctx->device_name[VideoDevice])
> -            if ((r = dshow_list_device_options(avctx, devenum, VideoDevice, VideoSourceDevice))) {
> +            if ((r = dshow_list_device_options(avctx, devenum, VideoDevice, VideoSourceDevice, NULL, CAP_QUERY_NONE))) {
>                  ret = r;
>                  goto error;
>              }
>          if (ctx->device_name[AudioDevice]) {
> -            if (dshow_list_device_options(avctx, devenum, AudioDevice, AudioSourceDevice)) {
> +            if (dshow_list_device_options(avctx, devenum, AudioDevice, AudioSourceDevice, NULL, CAP_QUERY_NONE)) {
>                  /* show audio options from combined video+audio sources as fallback */
> -                if ((r = dshow_list_device_options(avctx, devenum, AudioDevice, VideoSourceDevice))) {
> +                if ((r = dshow_list_device_options(avctx, devenum, AudioDevice, VideoSourceDevice, NULL, CAP_QUERY_NONE))) {
>                      ret = r;
>                      goto error;
>                  }
> @@ -1289,6 +1511,208 @@ static int dshow_read_packet(AVFormatContext *s, AVPacket *pkt)
>      return ctx->eof ? AVERROR(EIO) : pkt->size;
>  }
>  
> +// TODO: how to expose extra info? If no field found, check if it in a set of extra keys (like color_range)?
> +static int dshow_query_ranges_func(AVOptionRanges** ranges_arg, void* obj, const char* key, int flags)
> +{
> +    AVDeviceCapabilitiesQuery *caps = obj;
> +    const AVFormatContext *avctx = caps->device_context;

avctx is only used for AVCodecContexts by convention.

> +    struct dshow_ctx *ctx = avctx->priv_data;
> +
> +    int backup_sample_size;
> +    int backup_sample_rate;
> +    int backup_channels;
> +    enum AVCodecID backup_video_codec_id;
> +    enum AVPixelFormat backup_pixel_format;
> +    int backup_requested_width;
> +    int backup_requested_height;
> +    char* backup_framerate = NULL;
> +
> +    enum DshowCapQueryType query_type = CAP_QUERY_NONE;
> +
> +    AVOptionRanges *ranges = av_mallocz(sizeof(AVOptionRanges));

I have to admit that my first instinct here was to mention that this
can't be legal because it would make sizeof(AVOptionRanges) part of the
public ABI, but given that av_opt_query_ranges() does indeed not
allocate the AVOptionRange for us this is how it seems to be.

> +    const AVOption *field = av_opt_find(obj, key, NULL, 0, flags);
> +    int ret;
> +
> +    ICreateDevEnum *devenum = NULL;
> +
> +    *ranges_arg = NULL;
> +
> +    if (!ranges) {
> +        ret = AVERROR(ENOMEM);
> +        goto fail1;
> +    }
> +
> +    if (!field) {
> +        ret = AVERROR_OPTION_NOT_FOUND;
> +        goto fail1;
> +    }
> +
> +    // turn option name into cap query
> +    query_type = dshow_get_query_type(field->name);
> +
> +    if (query_type == CAP_QUERY_NONE) {
> +        av_log(avctx, AV_LOG_ERROR, "Querying the option %s is not supported for a dshow device\n",field->name);
> +        ret = AVERROR(EINVAL);
> +        goto fail1;
> +    }
> +
> +    if (ctx->device_name[0]) {
> +        av_log(avctx, AV_LOG_ERROR, "You cannot query device capabilities on an opened device\n");

Is this supposed to be a limitation of the capabilities API in general
or only of the implementation here in for dshow?

> +        ret = AVERROR(EIO);
> +        goto fail1;
> +    }
> +
> +    if (!parse_device_name(avctx)) {
> +        av_log(avctx, AV_LOG_ERROR, "You must set a device name (AVFormatContext url) to specify which device to query capabilities from\n");
> +        ret = AVERROR(EINVAL);
> +        goto fail1;
> +    }
> +
> +    // take backup of dshow parameters/options
> +    // audio
> +    backup_sample_size = ctx->sample_size;
> +    backup_sample_rate = ctx->sample_rate;
> +    backup_channels = ctx->channels;
> +    // video
> +    backup_video_codec_id = ctx->video_codec_id;
> +    backup_pixel_format = ctx->pixel_format;
> +    backup_requested_width = ctx->requested_width;
> +    backup_requested_height= ctx->requested_height;
> +    backup_framerate = ctx->framerate;
> +
> +
> +    // set format constraints set in AVDeviceCapabilitiesQuery
> +    // audio (NB: channel_layout not used)
> +    ctx->sample_size = av_get_bytes_per_sample(caps->sample_format) << 3;
> +    ctx->sample_rate = (caps->sample_rate == -1) ? 0 : caps->sample_rate;
> +    ctx->channels = (caps->channels == -1) ? 0 : caps->channels;
> +    // video (NB: window_width and window_height not used)
> +    ctx->video_codec_id = caps->codec;
> +    ctx->pixel_format = caps->pixel_format;
> +    ctx->requested_width = caps->frame_width;
> +    ctx->requested_height = caps->frame_height;
> +    // checking whether requested framerate is set is done by !ctx->framerate
> +    if (caps->fps.num > 0 && caps->fps.den > 0) {
> +        ctx->requested_framerate = caps->fps;
> +        ctx->framerate = av_strdup("dummy");    // just make sure its non-zero
> +        if (!ctx->framerate) {
> +            ret = AVERROR(ENOMEM);
> +            goto fail2;
> +        }
> +    }
> +    else
> +        ctx->framerate = NULL;  // make sure its NULL (if it wasn't, we already have a backup of the pointer to restore later)
> +
> +    // now iterate matching format of pin that would be selected when device
> +    // is opened with options currently in effect.
> +    // for each matching format, output its parameter range, also if that same
> +    // range already returned for another format. That way, user can reconstruct
> +    // possible valid combinations by av_opt_query_ranges() for each of the
> +    // format options and matching returned values by sequence number.
> +    CoInitialize(0);
> +
> +    if (CoCreateInstance(&CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
> +                         &IID_ICreateDevEnum, (void **) &devenum)) {
> +        av_log(avctx, AV_LOG_ERROR, "Could not enumerate system devices.\n");
> +        goto fail2;

You forgot to set ret.

> +    }
> +    ctx->video_codec_id = avctx->video_codec_id ? avctx->video_codec_id
> +                                                : AV_CODEC_ID_RAWVIDEO;
> +
> +    ranges->nb_components = field->type == AV_OPT_TYPE_IMAGE_SIZE ? 3 : 1;
> +    if (ctx->device_name[VideoDevice])
> +        if ((ret = dshow_list_device_options(avctx, devenum, VideoDevice, VideoSourceDevice, ranges, query_type)) < 0)
> +            goto fail2;
> +    if (ctx->device_name[AudioDevice]) {
> +        if (dshow_list_device_options(avctx, devenum, AudioDevice, AudioSourceDevice, ranges, query_type) < 0) {
> +            /* show audio options from combined video+audio sources as fallback */
> +            if ((ret = dshow_list_device_options(avctx, devenum, AudioDevice, VideoSourceDevice, ranges, query_type)) < 0)
> +                goto fail2;
> +        }
> +    }
> +    ret = ranges->nb_ranges ? ranges->nb_components : 0;
> +
> +    // when dealing with a multi-component item (regardless of whether
> +    // AV_OPT_MULTI_COMPONENT_RANGE is set or not), we need to reorganize the
> +    // output range array from [r1_c1 r1_c2 r1_c3 r2_c1 r2_c2 r2_c3 ...] to
> +    // [r1_c1 r2_c1 ... r1_c2 r2_c2 ... r1_c3 r2_c3 ...] to be consistent with
> +    // documentation of AVOptionRanges in libavutil/opt.h
> +    // this is only the case for a AV_OPT_TYPE_IMAGE_SIZE option.
> +    if (ranges->nb_ranges && ranges->nb_components>1) {
> +        AVOptionRanges* old_ranges;

Actually, all you need is a second range array (which could even be
avoided if one performed the transpose in-place, but I don't know how
much effort it would be to implement this).

> +        ranges->nb_ranges /= ranges->nb_components;
> +        old_ranges = ranges;
> +        ranges = av_mallocz(sizeof(AVOptionRanges));
> +        if (!ranges) {
> +            ranges = old_ranges;    // for cleanup
> +            ret = AVERROR(ENOMEM);
> +            goto fail2;
> +        }
> +        ranges->nb_components = old_ranges->nb_components;
> +        ranges->nb_ranges = old_ranges->nb_ranges;
> +        ranges->range = av_malloc(ranges->nb_components * ranges->nb_ranges * sizeof(AVOptionRange*));

Missing check; also, using sizeof(*ranges->range) increases clarity (I
already wanted to complain about you using the wrong type here until I
noticed that it is an array of arrays).

> +
> +        for (int n = 0; n < ranges->nb_components * ranges->nb_ranges; n++) {
> +            int i = n / ranges->nb_components;
> +            int j = n % ranges->nb_components;
> +            ranges->range[ranges->nb_ranges * j + i] = old_ranges->range[n];
> +            old_ranges->range[n] = NULL; // don't keep double references
> +        }
> +
> +        av_opt_freep_ranges(&old_ranges);
> +    }
> +
> +    // success, set output
> +    *ranges_arg = ranges;
> +
> +fail2:
> +    // set dshow parameters/options back to original values
> +    // audio
> +    ctx->sample_size = backup_sample_size;
> +    ctx->sample_rate = backup_sample_rate;
> +    ctx->channels = backup_channels;
> +    // video
> +    ctx->video_codec_id = backup_video_codec_id;
> +    ctx->pixel_format = backup_pixel_format;
> +    ctx->requested_width = backup_requested_width;
> +    ctx->requested_height = backup_requested_height;
> +    if (ctx->framerate)
> +        av_free(ctx->framerate);
> +    ctx->framerate = backup_framerate;
> +
> +    if (devenum)
> +        ICreateDevEnum_Release(devenum);
> +
> +    dshow_read_close(avctx); // clears filenames and removes device_filters or other state variables that may have been set
> +
> +fail1:
> +    if (ret < 0)
> +        av_opt_freep_ranges(&ranges);
> +
> +    return ret;
> +}
> +
> +// fake class to point av_opt_query_ranges to our query_ranges function
> +static const AVClass dshow_dev_caps_class = {
> +    .class_name = "",
> +    .item_name = av_default_item_name,
> +    .option = av_device_capabilities,
> +    .version = LIBAVDEVICE_VERSION_INT,

LIBAVUTIL_VERSION_INT (this is meant to make AVClass extensible even
without a major bump).

> +    .query_ranges = dshow_query_ranges_func,

We typically don't add "_func" to function pointers (as you can see from
the other function pointers set above).

Furthermore, aligning this on '=' improves legibility.

> +};
> +
> +static int dshow_create_device_capabilities(struct AVFormatContext* s, struct AVDeviceCapabilitiesQuery* caps)
> +{
> +    caps->av_class = &dshow_dev_caps_class;
> +    return 0;
> +}
> +
> +static int dshow_free_device_capabilities(struct AVFormatContext* s, struct AVDeviceCapabilitiesQuery* caps)
> +{
> +    // nothing to clean up

Then this function should not exist at all (after all,
avdevice_capabilities_free() checks for whether this function exists, it
is not called unconditionally).

> +    return 0;
> +}
> +
>  #define OFFSET(x) offsetof(struct dshow_ctx, x)
>  #define DEC AV_OPT_FLAG_DECODING_PARAM
>  static const AVOption options[] = {
> @@ -1335,6 +1759,8 @@ const AVInputFormat ff_dshow_demuxer = {
>      .read_header    = dshow_read_header,
>      .read_packet    = dshow_read_packet,
>      .read_close     = dshow_read_close,
> +    .create_device_capabilities = dshow_create_device_capabilities,
> +    .free_device_capabilities = dshow_free_device_capabilities,
>      .flags          = AVFMT_NOFILE,
>      .priv_class     = &dshow_class,
>  };
>
Diederick C. Niehorster June 5, 2021, 8:24 p.m. UTC | #2
On Sat, Jun 5, 2021 at 2:25 PM Andreas Rheinhardt
<andreas.rheinhardt@outlook.com> wrote:
> Diederick Niehorster:
> > This implements avdevice_capabilities_create and avdevice_capabilities_free for the dshow device.> > +
> > +            if (ranges) {
> > +                if (query_type == CAP_QUERY_FRAME_SIZE) {
> > +                    for (int j = 0; j < 3; j++) {
> > +                        range = av_mallocz(sizeof(AVOptionRange));
>
> It seems that you are allocating individual AVOptionRange structures.
> This is bad, because that is not how av_opt_freep_ranges() treats them:
> They are supposed to be pointers into one big array of AVOptionRange
> structures.

av_opt_freep_ranges() does this (NB: ranges->range is an AVOptionRange **):
for (i = 0; i < ranges->nb_ranges * ranges->nb_components; i++) {
        AVOptionRange *range = ranges->range[i];
        if (range) {
            av_freep(&range->str);
            av_freep(&ranges->range[i]); // <--
        }
    }
    av_freep(&ranges->range);

Note line highlighted by <--:
it frees each individual element, one at a time, and only after that
deletes the whole array. So i think it is correct that i am allocating
them one at a time? Am i misreading/-understanding this?

> > +    const AVFormatContext *avctx = caps->device_context;
>
> avctx is only used for AVCodecContexts by convention.

the dshow device (almost) consistently uses avctx as name for the
AVFormatContext. Should i change that everywhere (what is the
convention name for a AVFormatContext?), or is it ok if i remain
consistent with that here?

> > +    if (ctx->device_name[0]) {
> > +        av_log(avctx, AV_LOG_ERROR, "You cannot query device capabilities on an opened device\n");
>
> Is this supposed to be a limitation of the capabilities API in general
> or only of the implementation here in for dshow?

See other mails: the bit of documentation in avdevice.h (lines
334--402) suggest the capabilities API is supposed to be used on an
unopened device. It would make sense: probe the device for what it can
do, decide what settings you want to use, then open device). In case
of the dshow device, I think i cannot avoid potential false positives
(entries in the query output that shouldn't be there) if the device is
already opened. I'll experiment, perhaps this is only a theoretical
worry.

Thanks!
Dee
Andreas Rheinhardt June 6, 2021, 4:15 a.m. UTC | #3
Diederick C. Niehorster:
> On Sat, Jun 5, 2021 at 2:25 PM Andreas Rheinhardt
> <andreas.rheinhardt@outlook.com> wrote:
>> Diederick Niehorster:
>>> This implements avdevice_capabilities_create and avdevice_capabilities_free for the dshow device.> > +
>>> +            if (ranges) {
>>> +                if (query_type == CAP_QUERY_FRAME_SIZE) {
>>> +                    for (int j = 0; j < 3; j++) {
>>> +                        range = av_mallocz(sizeof(AVOptionRange));
>>
>> It seems that you are allocating individual AVOptionRange structures.
>> This is bad, because that is not how av_opt_freep_ranges() treats them:
>> They are supposed to be pointers into one big array of AVOptionRange
>> structures.
> 
> av_opt_freep_ranges() does this (NB: ranges->range is an AVOptionRange **):
> for (i = 0; i < ranges->nb_ranges * ranges->nb_components; i++) {
>         AVOptionRange *range = ranges->range[i];
>         if (range) {
>             av_freep(&range->str);
>             av_freep(&ranges->range[i]); // <--
>         }
>     }
>     av_freep(&ranges->range);
> 
> Note line highlighted by <--:
> it frees each individual element, one at a time, and only after that
> deletes the whole array. So i think it is correct that i am allocating
> them one at a time? Am i misreading/-understanding this?
> 

No, I was wrong.

>>> +    const AVFormatContext *avctx = caps->device_context;
>>
>> avctx is only used for AVCodecContexts by convention.
> 
> the dshow device (almost) consistently uses avctx as name for the
> AVFormatContext. Should i change that everywhere (what is the
> convention name for a AVFormatContext?), or is it ok if i remain
> consistent with that here?
> 

Normally we use 's' for AVFormatContext; but it seems that several
devices use avctx. Probably best to stick with what the rest of this
file uses then.

>>> +    if (ctx->device_name[0]) {
>>> +        av_log(avctx, AV_LOG_ERROR, "You cannot query device capabilities on an opened device\n");
>>
>> Is this supposed to be a limitation of the capabilities API in general
>> or only of the implementation here in for dshow?
> 
> See other mails: the bit of documentation in avdevice.h (lines
> 334--402) suggest the capabilities API is supposed to be used on an
> unopened device. It would make sense: probe the device for what it can
> do, decide what settings you want to use, then open device). In case
> of the dshow device, I think i cannot avoid potential false positives
> (entries in the query output that shouldn't be there) if the device is
> already opened. I'll experiment, perhaps this is only a theoretical
> worry.
> 
And is the AVFormatContext that has been used for probing then supposed
to be reused for actually using the device? (You are calling
dshow_read_close() manually, but our cleanup functions typically only
clean up things that need to be freed; they e.g. don't reset (say)
ordinary ints to zero, although the code may rely upon this.)

- Andreas
Diederick C. Niehorster June 6, 2021, 7:02 a.m. UTC | #4
On Sun, Jun 6, 2021 at 6:15 AM Andreas Rheinhardt
<andreas.rheinhardt@outlook.com> wrote:
> Diederick C. Niehorster:
> > See other mails: the bit of documentation in avdevice.h (lines
> > 334--402) suggest the capabilities API is supposed to be used on an
> > unopened device. It would make sense: probe the device for what it can
> > do, decide what settings you want to use, then open device). In case
> > of the dshow device, I think i cannot avoid potential false positives
> > (entries in the query output that shouldn't be there) if the device is
> > already opened. I'll experiment, perhaps this is only a theoretical
> > worry.
> >
> And is the AVFormatContext that has been used for probing then supposed
> to be reused for actually using the device? (You are calling
> dshow_read_close() manually, but our cleanup functions typically only
> clean up things that need to be freed; they e.g. don't reset (say)
> ordinary ints to zero, although the code may rely upon this.)

It is not supposed to, but does work. However, as user you still need
to set the device options in the opts dict passed to
avformat_open_input() even after narrowing down the format you want to
use through the capabilities API. I'll document this. NB: reusing the
AVFormatContext currently leaks a little bit of memory as, e.g.,
avformat_open_input() set url without checking and freeing if its
already set. And maybe more. I'll just add a patch with my proposed
API extension avformat_alloc_input_context() and needed extra checks
to avformat_open_input(), so we can discuss it.

All the best,
Dee
diff mbox series

Patch

diff --git a/libavdevice/dshow.c b/libavdevice/dshow.c
index 8d0a6fcc09..c65492119e 100644
--- a/libavdevice/dshow.c
+++ b/libavdevice/dshow.c
@@ -30,6 +30,48 @@ 
 #include "objidl.h"
 #include "shlwapi.h"
 
+enum DshowCapQueryType {
+    CAP_QUERY_NONE = 0,
+    CAP_QUERY_SAMPLE_FORMAT,
+    CAP_QUERY_SAMPLE_RATE,
+    CAP_QUERY_CHANNELS,
+    CAP_QUERY_CODEC,
+    CAP_QUERY_PIXEL_FORMAT,
+    CAP_QUERY_FRAME_SIZE,
+    CAP_QUERY_FPS
+};
+typedef struct DshowCapQueryTypeEntry {
+    const char*             name;
+    enum DshowCapQueryType  query_type;
+} DshowCapQueryTypeEntry;
+
+static const DshowCapQueryTypeEntry query_table[] = {
+    { "sample_format",  CAP_QUERY_SAMPLE_FORMAT },
+    { "sample_rate",    CAP_QUERY_SAMPLE_RATE },
+    { "channels",       CAP_QUERY_CHANNELS },
+    { "codec",          CAP_QUERY_CODEC },
+    { "pixel_format",   CAP_QUERY_PIXEL_FORMAT },
+    { "frame_size",     CAP_QUERY_FRAME_SIZE },
+    { "fps",            CAP_QUERY_FPS },
+};
+
+static enum DshowCapQueryType dshow_get_query_type(const char* option_name)
+{
+    for (int i = 0; i < FF_ARRAY_ELEMS(query_table); ++i) {
+        if (!strcmp(query_table[i].name, option_name))
+            return query_table[i].query_type;
+    }
+    return CAP_QUERY_NONE;
+}
+
+static const char* dshow_get_query_type_string(enum DshowCapQueryType query_type)
+{
+    for (int i = 0; i < FF_ARRAY_ELEMS(query_table); ++i) {
+        if (query_table[i].query_type == query_type)
+            return query_table[i].name;
+    }
+    return NULL;
+}
 
 static enum AVPixelFormat dshow_pixfmt(DWORD biCompression, WORD biBitCount)
 {
@@ -54,6 +96,26 @@  static enum AVPixelFormat dshow_pixfmt(DWORD biCompression, WORD biBitCount)
     return avpriv_find_pix_fmt(avpriv_get_raw_pix_fmt_tags(), biCompression); // all others
 }
 
+static enum AVCodecID waveform_codec_id(enum AVSampleFormat sample_fmt)
+{
+    switch (sample_fmt) {
+    case AV_SAMPLE_FMT_U8:  return AV_CODEC_ID_PCM_U8;
+    case AV_SAMPLE_FMT_S16: return AV_CODEC_ID_PCM_S16LE;
+    case AV_SAMPLE_FMT_S32: return AV_CODEC_ID_PCM_S32LE;
+    default:                return AV_CODEC_ID_NONE; /* Should never happen. */
+    }
+}
+
+static enum AVSampleFormat sample_fmt_bits_per_sample(int bits)
+{
+    switch (bits) {
+    case 8:  return AV_SAMPLE_FMT_U8;
+    case 16: return AV_SAMPLE_FMT_S16;
+    case 32: return AV_SAMPLE_FMT_S32;
+    default: return AV_SAMPLE_FMT_NONE; /* Should never happen. */
+    }
+}
+
 static int
 dshow_read_close(AVFormatContext *s)
 {
@@ -317,10 +379,13 @@  fail1:
  * try to set parameters specified through AVOptions and if successful
  * return 1 in *pformat_set.
  * If pformat_set is NULL, list all pin capabilities.
+ * When listing pin capabilities, if ranges is NULL, output to log,
+ * else store capabilities in ranges.
  */
 static void
 dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype,
-                    IPin *pin, int *pformat_set)
+                    IPin *pin, int *pformat_set,
+                    AVOptionRanges *ranges, enum DshowCapQueryType query_type)
 {
     struct dshow_ctx *ctx = avctx->priv_data;
     IAMStreamConfig *config = NULL;
@@ -338,7 +403,11 @@  dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype,
     if (!caps)
         goto end;
 
-    for (i = 0; i < n && !format_set; i++) {
+    for (i = 0; i < n && (!format_set || ranges); i++) {
+        AVOptionRange *range = NULL;
+        AVOptionRange **range_list = NULL;
+        int nb_range = 0;
+
         r = IAMStreamConfig_GetStreamCaps(config, i, &type, (void *) caps);
         if (r != S_OK)
             goto next;
@@ -365,7 +434,7 @@  dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype,
             } else {
                 goto next;
             }
-            if (!pformat_set) {
+            if (!pformat_set && !ranges) {
                 enum AVPixelFormat pix_fmt = dshow_pixfmt(bih->biCompression, bih->biBitCount);
                 if (pix_fmt == AV_PIX_FMT_NONE) {
                     enum AVCodecID codec_id = av_codec_get_id(tags, bih->biCompression);
@@ -410,6 +479,81 @@  dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype,
                 bih->biWidth  = ctx->requested_width;
                 bih->biHeight = ctx->requested_height;
             }
+
+            if (ranges) {
+                if (query_type == CAP_QUERY_FRAME_SIZE) {
+                    for (int j = 0; j < 3; j++) {
+                        range = av_mallocz(sizeof(AVOptionRange));
+                        if (!range)
+                            goto next;
+                        range->str = av_strdup(j == 0 ? "pixel_count" : (j == 1 ? "width" : (j == 2 ? "height" : "")));
+                        if (!range->str)
+                            goto next;
+                        switch (j)
+                        {
+                        case 0:
+                            range->value_min = vcaps->MinOutputSize.cx * vcaps->MinOutputSize.cy;
+                            range->value_max = vcaps->MaxOutputSize.cx * vcaps->MaxOutputSize.cy;
+                            break;
+                        case 1:
+                            range->value_min = vcaps->MinOutputSize.cx;
+                            range->value_max = vcaps->MaxOutputSize.cx;
+                            break;
+                        case 2:
+                            range->value_min = vcaps->MinOutputSize.cy;
+                            range->value_max = vcaps->MaxOutputSize.cy;
+                            break;
+                        }
+                        range->is_range = range->value_min != range->value_max;
+
+                        if (av_dynarray_add_nofree(&range_list,
+                                                   &nb_range, range) < 0)
+                            goto next;
+                        range = NULL;  // copied into array, make sure not freed below
+                    }
+                }
+                else {
+                    range = av_mallocz(sizeof(AVOptionRange));
+                    if (!range)
+                        goto next;
+                    range->str = av_strdup(dshow_get_query_type_string(query_type));
+                    if (!range->str)
+                        goto next;
+
+                    switch (query_type)
+                    {
+                    case CAP_QUERY_CODEC:
+                        if (dshow_pixfmt(bih->biCompression, bih->biBitCount) == AV_PIX_FMT_NONE) {
+                            const AVCodecTag* const tags[] = { avformat_get_riff_video_tags(), NULL };
+                            range->value_min = av_codec_get_id(tags, bih->biCompression);
+                        }
+                        else
+                            range->value_min = AV_CODEC_ID_RAWVIDEO;
+                        range->value_max = range->value_min;
+                        break;
+                    case CAP_QUERY_PIXEL_FORMAT:
+                        range->value_min = range->value_max = dshow_pixfmt(bih->biCompression, bih->biBitCount);
+                        range->value_min;
+                        break;
+                    case CAP_QUERY_FPS:
+                        range->value_min = 1e7 / vcaps->MaxFrameInterval;
+                        range->value_max = 1e7 / vcaps->MinFrameInterval;
+                        break;
+
+                    default:
+                        // an audio property is being queried, output 0
+                        range->value_min = range->value_max = 0;
+                        break;
+                    }
+
+                    range->is_range = range->value_min != range->value_max;
+
+                    if (av_dynarray_add_nofree(&range_list,
+                                               &nb_range, range) < 0)
+                        goto next;
+                    range = NULL;  // copied into array, make sure not freed below
+                }
+            }
         } else {
             AUDIO_STREAM_CONFIG_CAPS *acaps = caps;
             WAVEFORMATEX *fx;
@@ -421,7 +565,7 @@  dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype,
             } else {
                 goto next;
             }
-            if (!pformat_set) {
+            if (!pformat_set && !ranges) {
                 av_log(avctx, AV_LOG_INFO, "  min ch=%lu bits=%lu rate=%6lu max ch=%lu bits=%lu rate=%6lu\n",
                        acaps->MinimumChannels, acaps->MinimumBitsPerSample, acaps->MinimumSampleFrequency,
                        acaps->MaximumChannels, acaps->MaximumBitsPerSample, acaps->MaximumSampleFrequency);
@@ -445,11 +589,90 @@  dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype,
                     goto next;
                 fx->nChannels = ctx->channels;
             }
+
+            if (ranges) {
+                if (query_type == CAP_QUERY_FRAME_SIZE) {
+                    for (int j = 0; j < 3; j++) {
+                        range = av_mallocz(sizeof(AVOptionRange));
+                        if (!range)
+                            goto next;
+                        range->str = av_strdup(j == 0 ? "pixel_count" : (j == 1 ? "width" : (j == 2 ? "height" : "")));
+                        if (!range->str)
+                            goto next;
+                        // an video property is being queried, output 0
+                        range->value_min = range->value_max = 0;
+                        range->is_range = 0;
+
+                        if (av_dynarray_add_nofree(&range_list,
+                                                   &nb_range, range) < 0)
+                            goto next;
+                        range = NULL;  // copied into array, make sure not freed below
+                    }
+                }
+                else {
+                    range = av_mallocz(sizeof(AVOptionRange));
+                    if (!range)
+                        goto next;
+                    range->str = av_strdup(dshow_get_query_type_string(query_type));
+                    if (!range->str)
+                        goto next;
+
+                    switch (query_type)
+                    {
+                    case CAP_QUERY_SAMPLE_FORMAT:
+                        range->value_min = sample_fmt_bits_per_sample(acaps->MinimumBitsPerSample);
+                        range->value_max = sample_fmt_bits_per_sample(acaps->MaximumBitsPerSample);
+                        break;
+                    case CAP_QUERY_SAMPLE_RATE:
+                        range->value_min = acaps->MinimumSampleFrequency;
+                        range->value_max = acaps->MaximumSampleFrequency;
+                        break;
+                    case CAP_QUERY_CHANNELS:
+                        range->value_min = acaps->MinimumChannels;
+                        range->value_max = acaps->MaximumChannels;
+                        break;
+
+                    default:
+                        // an video property is being queried, output 0
+                        range->value_min = range->value_max = 0;
+                        break;
+                    }
+
+                    range->is_range = range->value_min != range->value_max;
+
+                    if (av_dynarray_add_nofree(&range_list,
+                                               &nb_range, range) < 0)
+                        goto next;
+                    range = NULL;  // copied into array, make sure not freed below
+                }
+            }
         }
         if (IAMStreamConfig_SetFormat(config, type) != S_OK)
             goto next;
+        else if (ranges) {
+            // format matched and could be set successfully. Add capabilities to ranges output
+            for (int j=0; j< nb_range; ++j) {
+                if (av_dynarray_add_nofree(&ranges->range,
+                                           &ranges->nb_ranges, range_list[j]) < 0)
+                    goto next;
+                range_list[j] = NULL;  // copied into array, make sure not freed below
+            }
+        }
         format_set = 1;
 next:
+        if (range) {
+            av_freep(&range->str);
+            av_freep(&range);
+        }
+        if (range_list) {
+            for (int j = 0; j < nb_range; ++j) {
+                if (range_list[j]) {
+                    av_freep(&range_list[j]->str);
+                    av_freep(&range_list[j]);
+                }
+            }
+            av_freep(&range_list);
+        }
         if (type->pbFormat)
             CoTaskMemFree(type->pbFormat);
         CoTaskMemFree(type);
@@ -558,10 +781,13 @@  end:
  * devtype, retrieve the first output pin and return the pointer to the
  * object found in *ppin.
  * If ppin is NULL, cycle through all pins listing audio/video capabilities.
+ * If ppin is not NULL and ranges is also not null, enumerate all formats
+ * supported by the selected pin.
  */
 static int
 dshow_cycle_pins(AVFormatContext *avctx, enum dshowDeviceType devtype,
-                 enum dshowSourceFilterType sourcetype, IBaseFilter *device_filter, IPin **ppin)
+                 enum dshowSourceFilterType sourcetype, IBaseFilter *device_filter,
+                 IPin **ppin, AVOptionRanges *ranges, enum DshowCapQueryType query_type)
 {
     struct dshow_ctx *ctx = avctx->priv_data;
     IEnumPins *pins = 0;
@@ -630,7 +856,7 @@  dshow_cycle_pins(AVFormatContext *avctx, enum dshowDeviceType devtype,
 
         if (!ppin) {
             av_log(avctx, AV_LOG_INFO, " Pin \"%s\" (alternative pin name \"%s\")\n", name_buf, pin_buf);
-            dshow_cycle_formats(avctx, devtype, pin, NULL);
+            dshow_cycle_formats(avctx, devtype, pin, NULL, NULL, CAP_QUERY_NONE);
             goto next;
         }
 
@@ -642,13 +868,13 @@  dshow_cycle_pins(AVFormatContext *avctx, enum dshowDeviceType devtype,
             }
         }
 
-        if (set_format) {
-            dshow_cycle_formats(avctx, devtype, pin, &format_set);
+        if (set_format || ranges) {
+            dshow_cycle_formats(avctx, devtype, pin, &format_set, ranges, query_type);
             if (!format_set) {
                 goto next;
             }
         }
-        if (devtype == AudioDevice && ctx->audio_buffer_size) {
+        if (devtype == AudioDevice && ctx->audio_buffer_size && !ranges) {
             if (dshow_set_audio_buffer_size(avctx, pin) < 0) {
                 av_log(avctx, AV_LOG_ERROR, "unable to set audio buffer size %d to pin, using pin anyway...", ctx->audio_buffer_size);
             }
@@ -673,8 +899,22 @@  next:
             IEnumMediaTypes_Release(types);
         if (p)
             IKsPropertySet_Release(p);
-        if (device_pin != pin)
+        if (device_pin != pin) {
             IPin_Release(pin);
+            // clear all ranges info again, wrong pin
+            if (ranges) {
+                for (int i = 0; i < ranges->nb_ranges; i++) {
+                    AVOptionRange* range = ranges->range[i];
+                    if (range) {
+                        av_freep(&range->str);
+                        av_freep(&ranges->range[i]);
+                    }
+                }
+                av_freep(&ranges->range);
+                ranges->nb_ranges = 0;
+            }
+            device_pin = NULL;
+        }
         av_free(name_buf);
         av_free(pin_buf);
         if (pin_id)
@@ -706,17 +946,19 @@  next:
  */
 static int
 dshow_list_device_options(AVFormatContext *avctx, ICreateDevEnum *devenum,
-                          enum dshowDeviceType devtype, enum dshowSourceFilterType sourcetype)
+                          enum dshowDeviceType devtype, enum dshowSourceFilterType sourcetype,
+                          AVOptionRanges *ranges, enum DshowCapQueryType query_type)
 {
     struct dshow_ctx *ctx = avctx->priv_data;
     IBaseFilter *device_filter = NULL;
+    IPin *device_pin = NULL;
     char *device_unique_name = NULL;
     int r;
 
     if ((r = dshow_cycle_devices(avctx, devenum, devtype, sourcetype, &device_filter, &device_unique_name)) < 0)
         return r;
     ctx->device_filter[devtype] = device_filter;
-    if ((r = dshow_cycle_pins(avctx, devtype, sourcetype, device_filter, NULL)) < 0)
+    if ((r = dshow_cycle_pins(avctx, devtype, sourcetype, device_filter, ranges ? &device_pin : NULL, ranges, query_type)) < 0)
         return r;
     av_freep(&device_unique_name);
     return 0;
@@ -800,7 +1042,7 @@  dshow_open_device(AVFormatContext *avctx, ICreateDevEnum *devenum,
         goto error;
     }
 
-    if ((r = dshow_cycle_pins(avctx, devtype, sourcetype, device_filter, &device_pin)) < 0) {
+    if ((r = dshow_cycle_pins(avctx, devtype, sourcetype, device_filter, &device_pin, NULL, CAP_QUERY_NONE)) < 0) {
         ret = r;
         goto error;
     }
@@ -912,26 +1154,6 @@  error:
     return ret;
 }
 
-static enum AVCodecID waveform_codec_id(enum AVSampleFormat sample_fmt)
-{
-    switch (sample_fmt) {
-    case AV_SAMPLE_FMT_U8:  return AV_CODEC_ID_PCM_U8;
-    case AV_SAMPLE_FMT_S16: return AV_CODEC_ID_PCM_S16LE;
-    case AV_SAMPLE_FMT_S32: return AV_CODEC_ID_PCM_S32LE;
-    default:                return AV_CODEC_ID_NONE; /* Should never happen. */
-    }
-}
-
-static enum AVSampleFormat sample_fmt_bits_per_sample(int bits)
-{
-    switch (bits) {
-    case 8:  return AV_SAMPLE_FMT_U8;
-    case 16: return AV_SAMPLE_FMT_S16;
-    case 32: return AV_SAMPLE_FMT_S32;
-    default: return AV_SAMPLE_FMT_NONE; /* Should never happen. */
-    }
-}
-
 static int
 dshow_add_device(AVFormatContext *avctx,
                  enum dshowDeviceType devtype)
@@ -1138,14 +1360,14 @@  static int dshow_read_header(AVFormatContext *avctx)
     }
     if (ctx->list_options) {
         if (ctx->device_name[VideoDevice])
-            if ((r = dshow_list_device_options(avctx, devenum, VideoDevice, VideoSourceDevice))) {
+            if ((r = dshow_list_device_options(avctx, devenum, VideoDevice, VideoSourceDevice, NULL, CAP_QUERY_NONE))) {
                 ret = r;
                 goto error;
             }
         if (ctx->device_name[AudioDevice]) {
-            if (dshow_list_device_options(avctx, devenum, AudioDevice, AudioSourceDevice)) {
+            if (dshow_list_device_options(avctx, devenum, AudioDevice, AudioSourceDevice, NULL, CAP_QUERY_NONE)) {
                 /* show audio options from combined video+audio sources as fallback */
-                if ((r = dshow_list_device_options(avctx, devenum, AudioDevice, VideoSourceDevice))) {
+                if ((r = dshow_list_device_options(avctx, devenum, AudioDevice, VideoSourceDevice, NULL, CAP_QUERY_NONE))) {
                     ret = r;
                     goto error;
                 }
@@ -1289,6 +1511,208 @@  static int dshow_read_packet(AVFormatContext *s, AVPacket *pkt)
     return ctx->eof ? AVERROR(EIO) : pkt->size;
 }
 
+// TODO: how to expose extra info? If no field found, check if it in a set of extra keys (like color_range)?
+static int dshow_query_ranges_func(AVOptionRanges** ranges_arg, void* obj, const char* key, int flags)
+{
+    AVDeviceCapabilitiesQuery *caps = obj;
+    const AVFormatContext *avctx = caps->device_context;
+    struct dshow_ctx *ctx = avctx->priv_data;
+
+    int backup_sample_size;
+    int backup_sample_rate;
+    int backup_channels;
+    enum AVCodecID backup_video_codec_id;
+    enum AVPixelFormat backup_pixel_format;
+    int backup_requested_width;
+    int backup_requested_height;
+    char* backup_framerate = NULL;
+
+    enum DshowCapQueryType query_type = CAP_QUERY_NONE;
+
+    AVOptionRanges *ranges = av_mallocz(sizeof(AVOptionRanges));
+    const AVOption *field = av_opt_find(obj, key, NULL, 0, flags);
+    int ret;
+
+    ICreateDevEnum *devenum = NULL;
+
+    *ranges_arg = NULL;
+
+    if (!ranges) {
+        ret = AVERROR(ENOMEM);
+        goto fail1;
+    }
+
+    if (!field) {
+        ret = AVERROR_OPTION_NOT_FOUND;
+        goto fail1;
+    }
+
+    // turn option name into cap query
+    query_type = dshow_get_query_type(field->name);
+
+    if (query_type == CAP_QUERY_NONE) {
+        av_log(avctx, AV_LOG_ERROR, "Querying the option %s is not supported for a dshow device\n",field->name);
+        ret = AVERROR(EINVAL);
+        goto fail1;
+    }
+
+    if (ctx->device_name[0]) {
+        av_log(avctx, AV_LOG_ERROR, "You cannot query device capabilities on an opened device\n");
+        ret = AVERROR(EIO);
+        goto fail1;
+    }
+
+    if (!parse_device_name(avctx)) {
+        av_log(avctx, AV_LOG_ERROR, "You must set a device name (AVFormatContext url) to specify which device to query capabilities from\n");
+        ret = AVERROR(EINVAL);
+        goto fail1;
+    }
+
+    // take backup of dshow parameters/options
+    // audio
+    backup_sample_size = ctx->sample_size;
+    backup_sample_rate = ctx->sample_rate;
+    backup_channels = ctx->channels;
+    // video
+    backup_video_codec_id = ctx->video_codec_id;
+    backup_pixel_format = ctx->pixel_format;
+    backup_requested_width = ctx->requested_width;
+    backup_requested_height= ctx->requested_height;
+    backup_framerate = ctx->framerate;
+
+
+    // set format constraints set in AVDeviceCapabilitiesQuery
+    // audio (NB: channel_layout not used)
+    ctx->sample_size = av_get_bytes_per_sample(caps->sample_format) << 3;
+    ctx->sample_rate = (caps->sample_rate == -1) ? 0 : caps->sample_rate;
+    ctx->channels = (caps->channels == -1) ? 0 : caps->channels;
+    // video (NB: window_width and window_height not used)
+    ctx->video_codec_id = caps->codec;
+    ctx->pixel_format = caps->pixel_format;
+    ctx->requested_width = caps->frame_width;
+    ctx->requested_height = caps->frame_height;
+    // checking whether requested framerate is set is done by !ctx->framerate
+    if (caps->fps.num > 0 && caps->fps.den > 0) {
+        ctx->requested_framerate = caps->fps;
+        ctx->framerate = av_strdup("dummy");    // just make sure its non-zero
+        if (!ctx->framerate) {
+            ret = AVERROR(ENOMEM);
+            goto fail2;
+        }
+    }
+    else
+        ctx->framerate = NULL;  // make sure its NULL (if it wasn't, we already have a backup of the pointer to restore later)
+
+    // now iterate matching format of pin that would be selected when device
+    // is opened with options currently in effect.
+    // for each matching format, output its parameter range, also if that same
+    // range already returned for another format. That way, user can reconstruct
+    // possible valid combinations by av_opt_query_ranges() for each of the
+    // format options and matching returned values by sequence number.
+    CoInitialize(0);
+
+    if (CoCreateInstance(&CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
+                         &IID_ICreateDevEnum, (void **) &devenum)) {
+        av_log(avctx, AV_LOG_ERROR, "Could not enumerate system devices.\n");
+        goto fail2;
+    }
+    ctx->video_codec_id = avctx->video_codec_id ? avctx->video_codec_id
+                                                : AV_CODEC_ID_RAWVIDEO;
+
+    ranges->nb_components = field->type == AV_OPT_TYPE_IMAGE_SIZE ? 3 : 1;
+    if (ctx->device_name[VideoDevice])
+        if ((ret = dshow_list_device_options(avctx, devenum, VideoDevice, VideoSourceDevice, ranges, query_type)) < 0)
+            goto fail2;
+    if (ctx->device_name[AudioDevice]) {
+        if (dshow_list_device_options(avctx, devenum, AudioDevice, AudioSourceDevice, ranges, query_type) < 0) {
+            /* show audio options from combined video+audio sources as fallback */
+            if ((ret = dshow_list_device_options(avctx, devenum, AudioDevice, VideoSourceDevice, ranges, query_type)) < 0)
+                goto fail2;
+        }
+    }
+    ret = ranges->nb_ranges ? ranges->nb_components : 0;
+
+    // when dealing with a multi-component item (regardless of whether
+    // AV_OPT_MULTI_COMPONENT_RANGE is set or not), we need to reorganize the
+    // output range array from [r1_c1 r1_c2 r1_c3 r2_c1 r2_c2 r2_c3 ...] to
+    // [r1_c1 r2_c1 ... r1_c2 r2_c2 ... r1_c3 r2_c3 ...] to be consistent with
+    // documentation of AVOptionRanges in libavutil/opt.h
+    // this is only the case for a AV_OPT_TYPE_IMAGE_SIZE option.
+    if (ranges->nb_ranges && ranges->nb_components>1) {
+        AVOptionRanges* old_ranges;
+        ranges->nb_ranges /= ranges->nb_components;
+        old_ranges = ranges;
+        ranges = av_mallocz(sizeof(AVOptionRanges));
+        if (!ranges) {
+            ranges = old_ranges;    // for cleanup
+            ret = AVERROR(ENOMEM);
+            goto fail2;
+        }
+        ranges->nb_components = old_ranges->nb_components;
+        ranges->nb_ranges = old_ranges->nb_ranges;
+        ranges->range = av_malloc(ranges->nb_components * ranges->nb_ranges * sizeof(AVOptionRange*));
+
+        for (int n = 0; n < ranges->nb_components * ranges->nb_ranges; n++) {
+            int i = n / ranges->nb_components;
+            int j = n % ranges->nb_components;
+            ranges->range[ranges->nb_ranges * j + i] = old_ranges->range[n];
+            old_ranges->range[n] = NULL; // don't keep double references
+        }
+
+        av_opt_freep_ranges(&old_ranges);
+    }
+
+    // success, set output
+    *ranges_arg = ranges;
+
+fail2:
+    // set dshow parameters/options back to original values
+    // audio
+    ctx->sample_size = backup_sample_size;
+    ctx->sample_rate = backup_sample_rate;
+    ctx->channels = backup_channels;
+    // video
+    ctx->video_codec_id = backup_video_codec_id;
+    ctx->pixel_format = backup_pixel_format;
+    ctx->requested_width = backup_requested_width;
+    ctx->requested_height = backup_requested_height;
+    if (ctx->framerate)
+        av_free(ctx->framerate);
+    ctx->framerate = backup_framerate;
+
+    if (devenum)
+        ICreateDevEnum_Release(devenum);
+
+    dshow_read_close(avctx); // clears filenames and removes device_filters or other state variables that may have been set
+
+fail1:
+    if (ret < 0)
+        av_opt_freep_ranges(&ranges);
+
+    return ret;
+}
+
+// fake class to point av_opt_query_ranges to our query_ranges function
+static const AVClass dshow_dev_caps_class = {
+    .class_name = "",
+    .item_name = av_default_item_name,
+    .option = av_device_capabilities,
+    .version = LIBAVDEVICE_VERSION_INT,
+    .query_ranges = dshow_query_ranges_func,
+};
+
+static int dshow_create_device_capabilities(struct AVFormatContext* s, struct AVDeviceCapabilitiesQuery* caps)
+{
+    caps->av_class = &dshow_dev_caps_class;
+    return 0;
+}
+
+static int dshow_free_device_capabilities(struct AVFormatContext* s, struct AVDeviceCapabilitiesQuery* caps)
+{
+    // nothing to clean up
+    return 0;
+}
+
 #define OFFSET(x) offsetof(struct dshow_ctx, x)
 #define DEC AV_OPT_FLAG_DECODING_PARAM
 static const AVOption options[] = {
@@ -1335,6 +1759,8 @@  const AVInputFormat ff_dshow_demuxer = {
     .read_header    = dshow_read_header,
     .read_packet    = dshow_read_packet,
     .read_close     = dshow_read_close,
+    .create_device_capabilities = dshow_create_device_capabilities,
+    .free_device_capabilities = dshow_free_device_capabilities,
     .flags          = AVFMT_NOFILE,
     .priv_class     = &dshow_class,
 };