[FFmpeg-devel] [PATCH 29/35] avdevice/dshow: implement capabilities API
Diederick Niehorster
dcnieho at gmail.com
Tue Jun 8 02:04:08 EEST 2021
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 <dcnieho at gmail.com>
---
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,
};
--
2.28.0.windows.1
More information about the ffmpeg-devel
mailing list