From patchwork Fri Jun 11 20:30:58 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Diederick C. Niehorster" X-Patchwork-Id: 28244 Delivered-To: andriy.gelman@gmail.com Received: by 2002:a25:bbc9:0:0:0:0:0 with SMTP id c9csp837491ybk; Fri, 11 Jun 2021 13:39:20 -0700 (PDT) X-Google-Smtp-Source: ABdhPJzEIGZGMT5qi9qEPO5Yh2bMZW0sPn9vPsdHmEYFOaAX61y4US5Gg/DEaBYL24D8+IFeeXBQ X-Received: by 2002:a17:906:55cb:: with SMTP id z11mr5227988ejp.475.1623443960604; Fri, 11 Jun 2021 13:39:20 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1623443960; cv=none; d=google.com; s=arc-20160816; b=qgqbswHKnQcMIGoMn/jn72T/fBemSL9nl7aVVPJcREcQr8vs6VuAdxMsU0F7Fxcs2V VNHRuCM+ukfqypbrtwHs7kDFgqmuMk79xtIqSu3Wm2gRnnPF3WXXEoipk0ulVl8r++4s nZXy04NDL8i17iUPid4gaWjUS/JzE5IlLZN1D9oM5YgjFhhgQq3Me+0GCRh13N8gXaOB R8h+0rq95vZ5ef5quSKeW6E7et87zy9jOhv6rs/mEphK5dhvgY/j+y6rQrqw8e2yVYfp /3gnLCL9HXxXVntxKyAIAxAW/o43yGPnzys9x6HGaye72t99/5Fb87WKN0uoFkdUbXwd 25BQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:references:in-reply-to :message-id:date:to:from:dkim-signature:delivered-to; bh=4mEP6bFWTq4viyWxTYVGEP9JWA2xH6oKlvZ7K5PgbzM=; b=amHJUMBcZ0GMjWhVgOZAYQUfrgBq5arVd26z+Q5M9z14/zTEez+Lx9/Hh24Ld5583y Cb4+n2piE9rV0Ac9+iqskpRqcVVHuZXFXxk66krGyqsRVs60edVPxS1gGWZZ07m+oM0u Cr1osqRE/3NaSUlIeEhZLo1gR5cJ0MrhcPPwhSr3LrJR/owNkb1eAAHGdRArE8OS5cKm 7S9YDNy/bTs8Gcz1P3IoJ74a5H7siSOaoYrKBJu7Hgmp/PpCI+hzollDUyRHPDZovPAa +7ugthouti1Nw0LUF5HmHrVcjJhyM0cQupiwj875WxKj3gerbd4/H7FS/3eJkNeFqpUa Jslg== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=S6RIQllX; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id f22si5504119ejq.458.2021.06.11.13.39.20; Fri, 11 Jun 2021 13:39:20 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=S6RIQllX; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id E62B2689ADE; Fri, 11 Jun 2021 23:39:18 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-lf1-f49.google.com (mail-lf1-f49.google.com [209.85.167.49]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 2CAE76808CC for ; Fri, 11 Jun 2021 23:39:16 +0300 (EEST) Received: by mail-lf1-f49.google.com with SMTP id r5so10395946lfr.5 for ; Fri, 11 Jun 2021 13:39:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=46RlDjkLKngfgdVk0aKspVyPXpCL9SnAmNkdoVCUWKo=; b=S6RIQllXpfYDLzQVQOFmJzlv6YU4MKrn9cwMloi6991hOhGQ3iiepVULoRfixTW0Im mWUYiDJL+nLKxUWWa6HjC3PnjdTukl0nYXw1ZLojsEqnoMAH1AUetjlvy750a4CjW8W6 X5cjsu+rGkZmK7RlhWvYP3ZfVmltE77Nrqcq2d1cdIZ7VTatALsChzgFTZiOgTilujAm 8j52FQ45kk3udmMgZioWle+Y3MojV8FQ01UDHW+C8IuCQPsemd+w1u+nVM1gsMUAPSxr lQQenpu+OCXLZpCWyxmtlqQoWnW6E9EjZNbjT6/6KOhOvPgjcem8GxLnssrSXGjQu6SS GC0g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=46RlDjkLKngfgdVk0aKspVyPXpCL9SnAmNkdoVCUWKo=; b=H0obApEAQsMe3bcAVN/Vm8XBAQlo1r2+DdBotLd1guneEypPlj23yVTkrfd27l3nec wnUTQqEAFEXHBVRWc2DksXNBUsMlNea2BjAReQRBMiT3BrHKlhMsoyGS3RH4hJ857Lte 2JG3u8+LG6OGemey4OxhvrV1yeGtvk6jGPxRnd5hoaopGml6YI5vv1gMuO/vVjQLrKBg RAUUO6Ma3695tYOFI/U+WbYZ2DybiWe2L69qJyaWRdHTGNDpODRwXE2NEucPSRd5RkdY bboYpQrCo07IxzXUddtMSx0CiAWr1N8bBEatHzocmQWrqSKrPtLPhehffeVVK7FwRwwV FbUg== X-Gm-Message-State: AOAM532tNvMtY5qAUSCPgPpa63eh6f50Nl9/dJFNUP9/Sd8GZ3xN0jhI kJ6MiwSSLeni0b0tIp3Qvy8+Ic/B6ADGlg== X-Received: by 2002:a19:ca4a:: with SMTP id h10mr3613117lfj.131.1623443955774; Fri, 11 Jun 2021 13:39:15 -0700 (PDT) Received: from localhost.localdomain ([196.196.203.214]) by smtp.gmail.com with ESMTPSA id t3sm687479lfl.78.2021.06.11.13.39.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 11 Jun 2021 13:39:15 -0700 (PDT) From: Diederick Niehorster To: ffmpeg-devel@ffmpeg.org Date: Fri, 11 Jun 2021 22:30:58 +0200 Message-Id: <20210611203104.1692-28-dcnieho@gmail.com> X-Mailer: git-send-email 2.28.0.windows.1 In-Reply-To: <20210611203104.1692-1-dcnieho@gmail.com> References: <20210611203104.1692-1-dcnieho@gmail.com> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v2 27/33] avdevice/dshow: implement capabilities API X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Diederick Niehorster Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: r5qlbnFiqdW0 Content-Length: 25710 This implements avdevice_capabilities_create for the dshow device (avdevice_capabilities_free not needed as it would be no-op). This enables configuration discovery of DirectShow devices through the API, which is important for my use case. It enables making proper GUIs presenting users with options, instead of asking them to discover a dshow device's capabilities through the list_options option with an FFmpeg tool, and listing what they want to configure in dumb text boxes. Signed-off-by: Diederick Niehorster --- libavdevice/dshow.c | 377 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 358 insertions(+), 19 deletions(-) diff --git a/libavdevice/dshow.c b/libavdevice/dshow.c index 20f58aee63..73113acddd 100644 --- a/libavdevice/dshow.c +++ b/libavdevice/dshow.c @@ -27,6 +27,7 @@ #include "libavformat/internal.h" #include "libavformat/riff.h" #include "avdevice.h" +#include "internal.h" #include "libavcodec/raw.h" #include "objidl.h" #include "shlwapi.h" @@ -834,11 +835,15 @@ static struct dshow_format_info* dshow_get_format_info(AM_MEDIA_TYPE* type) * try to set parameters specified through AVOptions, or the pin's * default format if no such parameters were set. If successful, * return 1 in *pformat_set. - * If pformat_set is NULL, list all pin capabilities. + * If pformat_set is NULL or the ranges input is not 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 AVDeviceCapabilitiesQueryType query_type) { struct dshow_ctx *ctx = avctx->priv_data; IAMStreamConfig *config = NULL; @@ -882,7 +887,7 @@ dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype, * one, with most info exposed (see comment below). */ use_default = !dshow_should_set_format(avctx, devtype); - if (use_default && pformat_set) + if (use_default && pformat_set && !ranges) { HRESULT hr; @@ -950,7 +955,9 @@ dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype, // exposes contains a VIDEOINFOHEADER2. Fall back to the VIDEOINFOHEADER // format if no corresponding VIDEOINFOHEADER2 is found when we finish // iterating. - for (i = 0; i < n && !format_set; i++) { + for (i = 0; i < n && (!format_set || ranges); i++) { + AVOptionRange *new_range[3] = { NULL }; + int nb_range = 0; struct dshow_format_info *fmt_info = NULL; r = IAMStreamConfig_GetStreamCaps(config, i, &type, (void *) caps); if (r != S_OK) @@ -986,7 +993,7 @@ dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype, wait_for_better = 0; } - if (!pformat_set) { + if (!pformat_set && !ranges) { if (fmt_info->pix_fmt == AV_PIX_FMT_NONE) { const AVCodec *codec = avcodec_find_decoder(fmt_info->codec_id); if (fmt_info->codec_id == AV_CODEC_ID_NONE || !codec) { @@ -1050,6 +1057,60 @@ dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype, bih->biWidth = requested_width; bih->biHeight = requested_height; } + + if (ranges) { + for (int j = 0; j < ranges->nb_components; j++) { + new_range[j] = av_mallocz(sizeof(**new_range)); + if (!new_range[j]) + goto next; + new_range[j]->value_max = -1.; // init (min:0, max:-1 means value not set) + ++nb_range; + + switch (query_type) + { + case AV_DEV_CAP_QUERY_CODEC: + if (dshow_pixfmt(bih->biCompression, bih->biBitCount) == AV_PIX_FMT_NONE) { + const AVCodecTag* const tags[] = { avformat_get_riff_video_tags(), NULL }; + new_range[j]->value_min = av_codec_get_id(tags, bih->biCompression); + } + else + new_range[j]->value_min = AV_CODEC_ID_RAWVIDEO; + new_range[j]->value_max = new_range[j]->value_min; + break; + case AV_DEV_CAP_QUERY_PIXEL_FORMAT: + new_range[j]->value_min = new_range[j]->value_max = dshow_pixfmt(bih->biCompression, bih->biBitCount); + new_range[j]->value_min; + break; + case AV_DEV_CAP_QUERY_FRAME_SIZE: + { + switch (j) + { + case 0: + new_range[j]->value_min = vcaps->MinOutputSize.cx * vcaps->MinOutputSize.cy; + new_range[j]->value_max = vcaps->MaxOutputSize.cx * vcaps->MaxOutputSize.cy; + break; + case 1: + new_range[j]->value_min = vcaps->MinOutputSize.cx; + new_range[j]->value_max = vcaps->MaxOutputSize.cx; + break; + case 2: + new_range[j]->value_min = vcaps->MinOutputSize.cy; + new_range[j]->value_max = vcaps->MaxOutputSize.cy; + break; + } + break; + } + case AV_DEV_CAP_QUERY_FPS: + new_range[j]->value_min = 1e7 / vcaps->MaxFrameInterval; + new_range[j]->value_max = 1e7 / vcaps->MinFrameInterval; + break; + + // default: + // an audio property is being queried, output all fields 0 (mallocz above) is fine + } + } + } + } else { AUDIO_STREAM_CONFIG_CAPS *acaps = caps; WAVEFORMATEX *fx; @@ -1061,7 +1122,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); @@ -1085,15 +1146,69 @@ dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype, goto next; fx->nChannels = requested_channels; } + + if (ranges) { + for (int j = 0; j < ranges->nb_components; j++) { + new_range[j] = av_mallocz(sizeof(**new_range)); + if (!new_range[j]) + goto next; + new_range[j]->value_max = -1.; // init (min:0, max:-1 means value not set) + ++nb_range; + + switch (query_type) + { + case AV_DEV_CAP_QUERY_SAMPLE_FORMAT: + new_range[j]->value_min = sample_fmt_bits_per_sample(acaps->MinimumBitsPerSample); + new_range[j]->value_max = sample_fmt_bits_per_sample(acaps->MaximumBitsPerSample); + break; + case AV_DEV_CAP_QUERY_SAMPLE_RATE: + new_range[j]->value_min = acaps->MinimumSampleFrequency; + new_range[j]->value_max = acaps->MaximumSampleFrequency; + break; + case AV_DEV_CAP_QUERY_CHANNELS: + new_range[j]->value_min = acaps->MinimumChannels; + new_range[j]->value_max = acaps->MaximumChannels; + break; + + // default: + // a video property is being queried, output all fields 0 (mallocz above) is fine + // NB: this is a for-loop since some of the video queries are multi-component + // and all components should be set + } + } + } } // found a matching format. Either apply or store // for safekeeping if we might maybe find a better // format with more info attached to it (see comment - // above loop) - if (!wait_for_better) { + // above loop). If storing all capabilities of device + // in ranges, try to apply in all cases, and store + // caps if successfully applied + if (!wait_for_better || ranges) { if (IAMStreamConfig_SetFormat(config, type) != S_OK) goto next; + else if (ranges) { + // format matched and could be set successfully. + // fill in some fields for each capability + for (int j = 0; j < nb_range; j++) { + new_range[j]->str = av_strdup(ff_device_get_query_component_name(query_type, j)); + if (!new_range[j]->str) + goto next; + new_range[j]->is_range = new_range[j]->value_min < new_range[j]->value_max; + } + + // store to ranges output + if (av_reallocp_array(&ranges->range, + ranges->nb_ranges*ranges->nb_components + nb_range*ranges->nb_components, + sizeof(*ranges->range)) < 0) + goto next; + for (int j = 0; j < nb_range; ++j) { + ranges->range[ranges->nb_ranges] = new_range[j]; + ranges->nb_ranges++; + new_range[j] = NULL; // copied into array, make sure not freed below + } + } format_set = 1; } else if (!previous_match_type) { @@ -1105,6 +1220,12 @@ dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype, next: if (fmt_info) av_free(fmt_info); + for (int j = 0; j < nb_range; ++j) { + if (new_range[j]) { + av_freep(&new_range[j]->str); + av_freep(&new_range[j]); + } + } if (type && type->pbFormat) CoTaskMemFree(type->pbFormat); CoTaskMemFree(type); @@ -1227,10 +1348,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 AVDeviceCapabilitiesQueryType query_type) { struct dshow_ctx *ctx = avctx->priv_data; IEnumPins *pins = 0; @@ -1269,6 +1393,7 @@ dshow_cycle_pins(AVFormatContext *avctx, enum dshowDeviceType devtype, wchar_t *pin_id = NULL; char *pin_buf = NULL; char *desired_pin_name = devtype == VideoDevice ? ctx->video_pin_name : ctx->audio_pin_name; + int nb_ranges = ranges ? ranges->nb_ranges : 0; IPin_QueryPinInfo(pin, &info); IBaseFilter_Release(info.pFilter); @@ -1293,7 +1418,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, AV_DEV_CAP_QUERY_NONE); goto next; } @@ -1307,12 +1432,15 @@ dshow_cycle_pins(AVFormatContext *avctx, enum dshowDeviceType devtype, // will either try to find format matching options supplied by user // or try to open default format. Successful if returns with format_set==1 - dshow_cycle_formats(avctx, devtype, pin, &format_set); + // if ranges is non-NULL, will iterate over all formats and return info + // about all the valid ones. If any valid found, format_set==1, else + // format_set will be 0 + 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); } @@ -1325,8 +1453,25 @@ dshow_cycle_pins(AVFormatContext *avctx, enum dshowDeviceType devtype, next: if (p) IKsPropertySet_Release(p); - if (device_pin != pin) + if (device_pin != pin) { IPin_Release(pin); + // remove any option ranges info we just added, wrong pin + if (ranges && nb_ranges>0) { + int nb_original_entries = nb_ranges * ranges->nb_components; + for (int i = nb_original_entries; i < ranges->nb_ranges * ranges->nb_components; i++) { + AVOptionRange* range = ranges->range[i]; + if (range) { + av_freep(&range->str); + av_freep(&ranges->range[i]); + } + } + if (av_reallocp_array(&ranges->range, nb_original_entries, sizeof(*ranges->range)) < 0) + ranges->nb_ranges = 0; + else + ranges->nb_ranges = nb_ranges; + } + device_pin = NULL; + } av_free(name_buf); av_free(pin_buf); if (pin_id) @@ -1358,17 +1503,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 AVDeviceCapabilitiesQueryType 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, NULL)) < 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; @@ -1452,7 +1599,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, AV_DEV_CAP_QUERY_NONE)) < 0) { ret = r; goto error; } @@ -1879,14 +2026,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, AV_DEV_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, AV_DEV_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, AV_DEV_CAP_QUERY_NONE))) { ret = r; goto error; } @@ -2032,6 +2179,197 @@ static int dshow_read_packet(AVFormatContext *s, AVPacket *pkt) return ctx->eof ? AVERROR(EIO) : pkt->size; } +// TODO: consider if and how to expose extra info we have about formats, such as color_range +static int dshow_query_ranges(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 AVDeviceCapabilitiesQueryType query_type = AV_DEV_CAP_QUERY_NONE; + + AVOptionRanges *ranges = av_mallocz(sizeof(**ranges_arg)); + 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 = ff_device_get_query_type(field->name); + + if (query_type == AV_DEV_CAP_QUERY_CHANNEL_LAYOUT || query_type == AV_DEV_CAP_QUERY_WINDOW_SIZE) { + 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"); + ret = AVERROR(EIO); + 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 && (flags & AV_OPT_MULTI_COMPONENT_RANGE)) ? 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 + if (ranges->nb_ranges && ranges->nb_components>1) { + AVOptionRange **new_range = av_malloc_array(ranges->nb_components * ranges->nb_ranges, sizeof(*ranges->range)); + AVOptionRange **old_range = ranges->range; + if (!new_range) { + ret = AVERROR(ENOMEM); + goto fail2; + } + ranges->nb_ranges /= ranges->nb_components; + for (int n = 0; n < ranges->nb_components * ranges->nb_ranges; n++) { + int i = n / ranges->nb_components; + int j = n % ranges->nb_components; + new_range[ranges->nb_ranges * j + i] = old_range[n]; + } + ranges->range = new_range; + av_freep(&old_range); + } + + // 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); + + // clear state variables that may have been set during the above process + // (e.g. frees device names, removes device_filters, etc) + dshow_read_close(avctx); + +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 = ff_device_capabilities, + .version = LIBAVUTIL_VERSION_INT, + .query_ranges = dshow_query_ranges, +}; + +static int dshow_create_device_capabilities(struct AVFormatContext* avctx, AVDeviceCapabilitiesQuery *caps) +{ + caps->av_class = &dshow_dev_caps_class; + return 0; +} + #define OFFSET(x) offsetof(struct dshow_ctx, x) #define DEC AV_OPT_FLAG_DECODING_PARAM static const AVOption options[] = { @@ -2081,6 +2419,7 @@ const AVInputFormat ff_dshow_demuxer = { .read_close = dshow_read_close, .control_message = dshow_control_message, .get_device_list= dshow_get_device_list, + .create_device_capabilities = dshow_create_device_capabilities, .flags = AVFMT_NOFILE | AVFMT_NOBINSEARCH | AVFMT_NOGENSEARCH | AVFMT_NO_BYTE_SEEK, .priv_class = &dshow_class, };