From patchwork Mon Jun 7 23:04:08 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: 28144 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a6b:b214:0:0:0:0:0 with SMTP id b20csp3887163iof; Mon, 7 Jun 2021 16:06:49 -0700 (PDT) X-Google-Smtp-Source: ABdhPJwaAwe4dutNkq91fnX8yLySrjEvt5MeqSsyX+k95J4QWdV6OyddROnshtfO+sLKZ8pxKZ5V X-Received: by 2002:a05:6402:1d38:: with SMTP id dh24mr22735772edb.18.1623107208985; Mon, 07 Jun 2021 16:06:48 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1623107208; cv=none; d=google.com; s=arc-20160816; b=wkJbNWd+ZZqiQ3iR+vfwJulALpzOEgZ3bBbdS3FhysI8NxSpe0+skI2uyAdxzuVLhM KqC4+qlpwuevtq7IsU+5rk8o3l3HguwuDAplAu/m8G2QkEOw177ZSSwcve8tCQ4SAdBV 2vMyCq9yRvbyjYBh7cZACHFQXITpAbM2vXhAiSLzoU8O+0NrYiTCOmm5rfI7UAOPGC6I 5BrcShCvEFUSgdk7kVMTNc8+L8mA7R1u9G6EaGEpODh7XIIhPHs6pYWw52X/IsKAg3Yk tDHvOE+jmaOQjhrYxMNcIl0go3ayBO5+yXvcXOawM5IlKWZkBj6qszrGNWYngcXfwkiL zcSQ== 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=PCN6y0Nj8Ux0dkg1HQx83lWWjyPogCfFm81vpVJjoF4=; b=xstMkFKt5yb/bUaAOWA186ZtodQDHYPQjS/nVR/ChLzAPLx7EY3MW5JD5yNroouZFD FBXbOuhun5JONNFdhdmBUR+oe3F9gGZKUtuKwCLgnJthIMg9MwWGloJKJmJAceWkR/C3 PXid7xg7JPW3i8Kftu33gCOKKSK19lPWTDV1k94BXMM+sf5VMESWYZqZgiqMabDLBdYZ +OXpp/xNBBtROVkM9RoGF2HB4CyJaDeqCNOMWAANVPG18slGKEbXVHHYhCXQEZ3U3EGP EA+wHPV1YHMgeAEmU++DpgCgMefVDCIna87Dzs6iVvnri1F/gcEQFwcYM2xO4BNRV8to 0zBQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=h6M43PSx; 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 ga2si13545490ejb.249.2021.06.07.16.06.48; Mon, 07 Jun 2021 16:06:48 -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=h6M43PSx; 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 EEF78687FD9; Tue, 8 Jun 2021 02:04:58 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-lf1-f43.google.com (mail-lf1-f43.google.com [209.85.167.43]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 47BF9680BBD for ; Tue, 8 Jun 2021 02:04:55 +0300 (EEST) Received: by mail-lf1-f43.google.com with SMTP id t7so21898062lff.0 for ; Mon, 07 Jun 2021 16:04:55 -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=l4gTKpId/8dXk0MDHDWJxoYPN1z/4E74WRGN8NMECA4=; b=h6M43PSxAuMa4Bida22qYbcKiyQew++mg12SLn563Ye9AiyEJYE1aANM2TyZwBsNtA 0Z+iHSLbvLNELkmZ74MJB8mo0op+uNtxKJIA2GvyxnvCi2I9ce9urKaW4fGUmDlS8sID lfazT3oHGPqxnwa4xgncADVm7JL+G7mc3mf41xV+gx353Aepm1O5IcA+8fsoG3Ww2nRC HzgXOJCmFTBsVxR9eYZu62SeSDfjGxVboeYS9Jgd4tUibeiUpUi8malYcqS0pO7oKtKa BotPHbkslTrHkXZgk714QuftdQUs6zCakBK0xHzMZPjSBDVThUqubE3dvzStAELtwrmo ne8A== 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=l4gTKpId/8dXk0MDHDWJxoYPN1z/4E74WRGN8NMECA4=; b=CTvclVVLNaoMBtQY89sRetHiDBAzKtwr6jDsv9HQo2Ya6/V3aoBwypQ5VqQBt8nNEd q/hRTPwpzRNFjDdPmRMP8s1n0hs/IOvHswX6TvMCqWbIn6wcUk0MZvLNj1nINgYbdMnK cBWOzenCnLpauJVw41aFI22oYqnw2D5GsJ/NZCMMim4EmUaKj3HqXVRu6MRO8ikTJ0Tx WUr1e/QtMCpdIwIz8UuOlGbcaHMPvo0b7TeVFYStpZX4qdwS9Qvoo0AjX9TfSNQovpvx eIfV+7bJhhDkzN7K6dvwRes19YaHIJV6AQaB6yC0RVr1enQhikznwwnygvT+uTh6M0g0 RmzQ== X-Gm-Message-State: AOAM532LHawSi85LWFsJZvB0fjpBt+BSMALUhQvy/VbV4ZEtTjxo95Pp LgURjVM74QelAVS8jGj5/hiEDss9pY9CcA== X-Received: by 2002:a19:6a08:: with SMTP id u8mr7480115lfu.93.1623107093939; Mon, 07 Jun 2021 16:04:53 -0700 (PDT) Received: from localhost.localdomain (84-217-56-54.customers.ownit.se. [84.217.56.54]) by smtp.gmail.com with ESMTPSA id v9sm1999563ljv.131.2021.06.07.16.04.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 07 Jun 2021 16:04:53 -0700 (PDT) From: Diederick Niehorster To: ffmpeg-devel@ffmpeg.org Date: Tue, 8 Jun 2021 01:04:08 +0200 Message-Id: <20210607230414.612-30-dcnieho@gmail.com> X-Mailer: git-send-email 2.28.0.windows.1 In-Reply-To: <20210607230414.612-1-dcnieho@gmail.com> References: <20210607230414.612-1-dcnieho@gmail.com> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH 29/35] 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: HwjHkY7jCaYo This implements avdevice_capabilities_create for the dshow device (avdevice_capabilities_free not needed as it would be no-op). Signed-off-by: Diederick Niehorster --- libavdevice/dshow.c | 384 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 365 insertions(+), 19 deletions(-) diff --git a/libavdevice/dshow.c b/libavdevice/dshow.c index 40a492b787..082ae5f26c 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" @@ -815,11 +816,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; @@ -863,7 +868,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; @@ -931,7 +936,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) @@ -967,7 +974,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) { @@ -1031,6 +1038,63 @@ 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; + ++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; + new_range[j]->is_set = 1; + 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; + new_range[j]->is_set = 1; + 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; + } + new_range[j]->is_set = 1; + break; + } + case AV_DEV_CAP_QUERY_FPS: + new_range[j]->value_min = 1e7 / vcaps->MaxFrameInterval; + new_range[j]->value_max = 1e7 / vcaps->MinFrameInterval; + new_range[j]->is_set = 1; + 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; @@ -1042,7 +1106,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); @@ -1066,15 +1130,72 @@ 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; + ++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); + new_range[j]->is_set = 1; + break; + case AV_DEV_CAP_QUERY_SAMPLE_RATE: + new_range[j]->value_min = acaps->MinimumSampleFrequency; + new_range[j]->value_max = acaps->MaximumSampleFrequency; + new_range[j]->is_set = 1; + break; + case AV_DEV_CAP_QUERY_CHANNELS: + new_range[j]->value_min = acaps->MinimumChannels; + new_range[j]->value_max = acaps->MaximumChannels; + new_range[j]->is_set = 1; + 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; + if (new_range[j]->is_set) + 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) { @@ -1086,6 +1207,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); @@ -1208,10 +1335,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; @@ -1250,6 +1380,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); @@ -1274,7 +1405,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; } @@ -1288,12 +1419,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); } @@ -1306,8 +1440,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) @@ -1339,17 +1490,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; @@ -1433,7 +1586,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; } @@ -1860,14 +2013,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; } @@ -2013,6 +2166,198 @@ 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 = av_device_capabilities, + .version = LIBAVUTIL_VERSION_INT, + .query_ranges = dshow_query_ranges, +}; + +static int dshow_create_device_capabilities(struct AVFormatContext* avctx, void *opaque) +{ + struct AVDeviceCapabilitiesQuery *caps = opaque; + 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[] = { @@ -2062,6 +2407,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, };