From patchwork Thu Jun 3 22:45:51 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: 28071 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a6b:b214:0:0:0:0:0 with SMTP id b20csp538055iof; Thu, 3 Jun 2021 15:46:59 -0700 (PDT) X-Google-Smtp-Source: ABdhPJxPdGtozAG/TIUnNcUcIrzX//fjESSPNpAcBxavbjqshsJl612Q8LZXmAdrvGgD7X0Co9F9 X-Received: by 2002:a05:6402:b6f:: with SMTP id cb15mr1653586edb.25.1622760419447; Thu, 03 Jun 2021 15:46:59 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1622760419; cv=none; d=google.com; s=arc-20160816; b=AhkmOAaaTAgSezZJIk0E0u4kk1Bz5suHubjWokbKJCq7op9bQ18RrqPw00bq+oKImB USw3di5IyzxipwzjyeCVwS1tmbVmklz/uyg8usj/frhvQvWib5ZIyqv5SPnt/COzgcS9 iXT+tTYKUxQ59lTuz7uedHePSLR+J19GzfueoKF9I8B7m+3G1ybzsDPxPcW6arRVbKJH Ab8I9TanneI1CBrPwB8QPatGXNIou8bBGSYdmmzp6jK+BuZjKfMpqutXrupcH1qD7M2d kIKzKsr6K+xt/iHwlhw95Pc6qgZkvo8rFlCkx2t0VASdNizUCCSRcYplBLMkP91zK5BG nL2A== 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=W5WvVP4XBuD/RVma6oeSLltTwu2E52w69962jdYCeas=; b=ErCIBXxUYV91YG3Rc3TzbPQw5k/vCCgcdwpN0yHcfE9gIPWzktkHT6g3JXEFzw7/aq DDsn+XXuVVparSPMxTBGuxtDZyJnrUudnRK3Kz3gBmL27aNqAl+4DXpZE5CNjcTsd157 AWHSCY7LFZsh++TjV67WhPDB5ZKy4dP1YltAV4yK5dkVeVICS4QHo90kHNPAZMIux6yr fn6xe6ReX5QSnzckGEVjazOx+4+McGXbK/1wcks2mT4+fFN6JQ9cP5c88mynnSn9Hk8H rqlKygdO9J/968/ekpPmLq0JAb3+mnCiwUAuKTCSchoJfntbdN0E8969TSPLHdZ5b47x a+qg== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=qCQ2Fu0C; 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 ch8si3093550edb.535.2021.06.03.15.46.59; Thu, 03 Jun 2021 15:46:59 -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=qCQ2Fu0C; 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 A65FF68A2CE; Fri, 4 Jun 2021 01:46:32 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-lf1-f46.google.com (mail-lf1-f46.google.com [209.85.167.46]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id BBF4568A2BC for ; Fri, 4 Jun 2021 01:46:25 +0300 (EEST) Received: by mail-lf1-f46.google.com with SMTP id v22so9840796lfa.3 for ; Thu, 03 Jun 2021 15:46:25 -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=qePLRAslwWh43bV5keS+HlPYlH+olkr8nivIcuT1nQc=; b=qCQ2Fu0C/aCrx/+wIfiXgUH93NXo6ZO8Re4DsNpt+lW+htqHWWCtj0IfXbrS7zYWyb qUCLo4vH6qiGsWDlNlPPRW45pY1CHXQaHaG2tshHyNaaZSI+jZM0kAndFdxPBLMF6u5+ OWyM5XyP4bbHhpL2YuaOPrIlgmiUI9OOwcQzcye1RP2pN3bxhOeGqMDkI+/RF7MWmrTa b6pN1ndRXiA68EqacWDmotUz4nA+BKSI3Ydz+hYLAQPnKoeugFoJk0V3xi9YACvvZms1 VZT5PhW2axHRCMW73uk+aIc7lAm0Ujj4+LEu3IrWABpOjD6N1JNzo4vWEQS/ipCMSOgB Zmgg== 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=qePLRAslwWh43bV5keS+HlPYlH+olkr8nivIcuT1nQc=; b=f/XAHXWIQGliT9CrBhe83EzieKftJIQ3LWbB31N2sX7XTf/4lRzcb4eA7YnYW0/a6k WtvvlYQgJivqAgJrJfxZmQjG+X4x7wk3O0A5eQ140lb840q+0szrRPIWFi43XV8ncix8 2gN6eWHBcY1cUkp/2rJxtb1wmkC5+ecPzqXOFytxDqzP0gzQCe08aekobglbpdFrRRvP 0bwBeGDILMN9gtQxyEkziwWOvt1+dGTlO1QDVI+xNklFBF5IKPb7PjYtSHxkM7LL8ZTe UsV6J8M1KEb8Tnmm3G9ZxeBBoiKHf+6ED3sSur0MA/TzWsYHneKgFn1zLMs58JoTuKPC v5tg== X-Gm-Message-State: AOAM532wQaQWp+933sODc68q0GyZ/MVz1sf9YxLxHn1o691NRm05tRUJ gqEVQyYXiMsPe3TkIg4gU4ujCQiTxZU= X-Received: by 2002:a05:6512:1c3:: with SMTP id f3mr754027lfp.79.1622760384465; Thu, 03 Jun 2021 15:46:24 -0700 (PDT) Received: from localhost.localdomain (84-217-56-54.customers.ownit.se. [84.217.56.54]) by smtp.gmail.com with ESMTPSA id t15sm82373lfp.176.2021.06.03.15.46.23 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 03 Jun 2021 15:46:24 -0700 (PDT) From: Diederick Niehorster To: ffmpeg-devel@ffmpeg.org Date: Fri, 4 Jun 2021 00:45:51 +0200 Message-Id: <20210603224552.1218-4-dcnieho@gmail.com> X-Mailer: git-send-email 2.28.0.windows.1 In-Reply-To: <20210603224552.1218-1-dcnieho@gmail.com> References: <20210603224552.1218-1-dcnieho@gmail.com> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH 3/4] 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: QDFwO0D2P/tG This implements avdevice_capabilities_create and avdevice_capabilities_free for the dshow device. Signed-off-by: Diederick Niehorster --- 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)); + 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, };