From patchwork Tue Jul 6 09:20:14 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: 28815 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a5d:965a:0:0:0:0:0 with SMTP id d26csp4922727ios; Tue, 6 Jul 2021 02:26:40 -0700 (PDT) X-Google-Smtp-Source: ABdhPJwc0Tx391tRRd8wsBSCxQzZz6kJ4tHedonnkbB1UWboDEcEyhJk/X/VAUIxrTzwUp7cPC9Q X-Received: by 2002:a50:eb8c:: with SMTP id y12mr21564124edr.191.1625563600492; Tue, 06 Jul 2021 02:26:40 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1625563600; cv=none; d=google.com; s=arc-20160816; b=xeBDzeoCHmXFMWsjWMbwByRFOU5woh60QyDqLdJXPGWvXpF0LNEsid3n7h6Rq3PRHh pozkxQkds0b0qxCA9sU5NwGwOEZU/sYsxl+CrccafUdJTYL62AKzjDoI89LN5SWTzSdM AI88r7XTWcJ8QapCzwV7x/e6MUF5n/aZ9IYXOAYkFiH5WPvGCzeR5sblltMex65NqJRZ +RZFjvZxnJ50e4VQwuR8vNInWEUfMfZ1+ZpOp5LoEV7JTW2WS1VAKc9qSZ60Xvgwe6a4 YHhJcSZYSM52kw0bCAAH+hUINXaTjuTQVIksDVSAr/ZiFpoj8DRKp4cd849+sg8y8AKJ C+dw== 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=pzU6mpD1X64w2Pj0C+OWMcMwwNvfe5uSa4eMnp/CG1Q=; b=H/788PYdk60aKj1SWxEjl29+TJOi39SMgHwPdEA99Zqi/I+OTjShRhvd6e7iLloEK7 ciBEIZp150m6llPv1ylcQqtYtgJDfaXOjEHp5GS5kcih71T8HN4oCYSqX1f5B1SqAfGU oXgCmm0vmf+nLw67ha0RtBPa0/vYhWUL5v4HUnqSVIB4hVLRy8Quy06Ktej/nL8f3Cb+ iX0OvVG8y+QpLr+r9Lve8xiCd5naTkbUtgHVmOKp4ZcVGq2n3ZI/0o0tokBPAzR0PiWv ePN9YBS/HBMiO/1F5urytT/dWtCWt7ZDfzNkgiPdtIPXytGkFXYNuWkKcp2YqTkK/n3b sajg== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=uZzBhxFp; 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 dp16si16792783ejc.554.2021.07.06.02.26.40; Tue, 06 Jul 2021 02:26:40 -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=uZzBhxFp; 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 7A7DA68A901; Tue, 6 Jul 2021 12:26:21 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-lj1-f173.google.com (mail-lj1-f173.google.com [209.85.208.173]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 774D268A8E6 for ; Tue, 6 Jul 2021 12:26:18 +0300 (EEST) Received: by mail-lj1-f173.google.com with SMTP id u25so28209716ljj.11 for ; Tue, 06 Jul 2021 02:26:18 -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=5Jof89QDZVYagHIEzH0GqQ5PnFtc7mEIV45T1zBt830=; b=uZzBhxFpE5uYPHvtKpsnapKkcrHr56snvbmtvf81NgV//bA63YlG2HxWKlkr1ewWcJ 7wglMfezQvKOs7Xupw2NiMXXJPk4WjHszDM4F45OkvVcfHVyWVEQ94uQu0CFCEdhRkQi QpH7o7RfgNXOlEJW8zeyTSA4YPkteT0bBp5hFDrc2UOu4jHNnqCiPgxu9N2tc8mV7gDR wWmz5hh55Fbjff7wPHUrf+ybKT446oJpy+c1OUD/bTqK5pbLevH22v1dwBk1hiAwKXlF tmGFbR4clScF6X6SEXAM4ayk85vPQoMZKweEjub0CVamZdDXW6XgNy2nHzN8JjY19J9y SWTw== 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=5Jof89QDZVYagHIEzH0GqQ5PnFtc7mEIV45T1zBt830=; b=P077/GkSPwYc03Y3GDlKbS4DLYNghcOOMl6CM3IjUl2DLnR7ituVX+R5S3c5zy5FHR HubtIX4YH5WKteZhdnrTE88m18CP5uMJjGxEzZDxGnobBB6CEImi82UJXeANl20kfukC SgDyHAXr4rjijQC6G3XIjaSRGhlUiwUzGZHX1LNDM+zBNG7exPLQuQjsrCJ37y8/fbD/ f8o7rF6f0CshSoeW+sn5AOx4QbKJrhenHlBG48JrfzplX4pDeplMl4/BDmW4ozLN6gG4 b/LK6bTNYVnObh8qD+K2Xum1qqAie3cHeZ567cVLCJfWHkSuym/JTWvZvRLU8lGR5RBY q9EQ== X-Gm-Message-State: AOAM53248Ol+qk72gaxmL/i9KWv5FIpKEPTb+RXtrf3l0tNi1BHyUuJK pSe7y6JzU7oxvwjW3P1o384Jhk01m9o= X-Received: by 2002:a05:651c:168a:: with SMTP id bd10mr14451192ljb.341.1625563577085; Tue, 06 Jul 2021 02:26:17 -0700 (PDT) Received: from localhost.localdomain (deedock.humlab.lu.se. [130.235.135.183]) by smtp.gmail.com with ESMTPSA id bp34sm1337006lfb.125.2021.07.06.02.26.16 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 06 Jul 2021 02:26:16 -0700 (PDT) From: Diederick Niehorster To: ffmpeg-devel@ffmpeg.org Date: Tue, 6 Jul 2021 11:20:14 +0200 Message-Id: <20210706092020.1057-29-dcnieho@gmail.com> X-Mailer: git-send-email 2.28.0.windows.1 In-Reply-To: <20210706092020.1057-1-dcnieho@gmail.com> References: <20210706092020.1057-1-dcnieho@gmail.com> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v3 28/34] 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: yDiuVVlGf9gE This implements avdevice_capabilities_create and avdevice_capabilities_free for the dshow device. 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 | 430 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 411 insertions(+), 19 deletions(-) diff --git a/libavdevice/dshow.c b/libavdevice/dshow.c index 046c39cc24..590a007073 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" @@ -839,11 +840,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; @@ -887,7 +892,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; @@ -954,7 +959,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) @@ -990,7 +997,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) { @@ -1054,6 +1061,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; @@ -1065,7 +1126,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); @@ -1089,15 +1150,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) { @@ -1108,6 +1223,12 @@ dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype, } next: av_freep(&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); @@ -1230,10 +1351,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; @@ -1272,6 +1396,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); @@ -1296,7 +1421,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; } @@ -1310,12 +1435,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); } @@ -1328,8 +1456,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) @@ -1361,17 +1506,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; @@ -1455,7 +1602,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; } @@ -1881,14 +2028,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; } @@ -2034,6 +2181,249 @@ 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; + } + + // 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; + // dshow checks whether requested framerate is set by means of !ctx->framerate. + // fill with something + if (!isnan(caps->fps)) { + ctx->requested_framerate = av_d2q(caps->fps, INT_MAX); + 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. + 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 = ctx->video_codec_id ? ctx->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); + +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) +{ + struct dshow_ctx *ctx = avctx->priv_data; + int ret = 0; + ICreateDevEnum *devenum = NULL; + + // set class so queries work + caps->av_class = &dshow_dev_caps_class; + + if (ctx->device_name[0] || ctx->device_name[1]) { + av_log(avctx, AV_LOG_ERROR, "You cannot query device capabilities on an opened device\n"); + ret = AVERROR(EIO); + goto fail; + } + + 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 fail; + } + + 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 fail; + } + + // check devices can be found + if (ctx->device_name[VideoDevice]) { + IBaseFilter *device_filter = NULL; + char *device_unique_name = NULL; + if ((ret = dshow_cycle_devices(avctx, devenum, VideoDevice, VideoSourceDevice, &device_filter, &device_unique_name, NULL)) < 0) + return ret; + + if (ret >= 0) { + ctx->device_filter[VideoDevice] = device_filter; + ctx->device_unique_name[VideoDevice] = device_unique_name; + } + } + if (ctx->device_name[AudioDevice]) { + IBaseFilter *device_filter = NULL; + char *device_unique_name = NULL; + if (dshow_cycle_devices(avctx, devenum, AudioDevice, AudioSourceDevice, &device_filter, &device_unique_name, NULL) < 0) { + /* try to access audio from combined video+audio sources as fallback */ + if ((ret = dshow_cycle_devices(avctx, devenum, AudioDevice, VideoSourceDevice, &device_filter, &device_unique_name, NULL)) < 0) + goto fail; + } + if (ret >= 0) { + ctx->device_filter[AudioDevice] = device_filter; + ctx->device_unique_name[AudioDevice] = device_unique_name; + } + } + +fail: + if (devenum) + ICreateDevEnum_Release(devenum); + + if (ret < 0) + return ret; + else + return 0; +} + +static int dshow_free_device_capabilities(struct AVFormatContext *avctx, AVDeviceCapabilitiesQuery *caps) +{ + // clear state variables that may have been set during the querying process + // (e.g. frees device names, removes device_filters, etc) + dshow_read_close(avctx); + + return 0; +} + #define OFFSET(x) offsetof(struct dshow_ctx, x) #define DEC AV_OPT_FLAG_DECODING_PARAM static const AVOption options[] = { @@ -2083,6 +2473,8 @@ 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, + .free_device_capabilities = dshow_free_device_capabilities, .flags = AVFMT_NOFILE | AVFMT_NOBINSEARCH | AVFMT_NOGENSEARCH | AVFMT_NO_BYTE_SEEK, .priv_class = &dshow_class, };