X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavdevice%2Fdshow.c;h=a5263df90b9886910e079513227725b108a419ec;hb=2f9b955918f8efd9915dbd51bfe8be657cb337e3;hp=10e3d4f17996e53062f0b96986479e6563c0810f;hpb=22bf6f7054d46e942ab02d434d3af6c08875576b;p=ffmpeg diff --git a/libavdevice/dshow.c b/libavdevice/dshow.c index 10e3d4f1799..a5263df90b9 100644 --- a/libavdevice/dshow.c +++ b/libavdevice/dshow.c @@ -19,13 +19,23 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "libavutil/parseutils.h" +#include "libavutil/opt.h" + #include "avdevice.h" #include "dshow.h" struct dshow_ctx { + const AVClass *class; + IGraphBuilder *graph; char *device_name[2]; + int video_device_number; + int audio_device_number; + + int list_options; + int list_devices; IBaseFilter *device_filter[2]; IPin *device_pin[2]; @@ -40,6 +50,17 @@ struct dshow_ctx { unsigned int video_frame_num; IMediaControl *control; + + char *video_size; + char *framerate; + + int requested_width; + int requested_height; + AVRational requested_framerate; + + int sample_rate; + int sample_size; + int channels; }; static enum PixelFormat dshow_pixfmt(DWORD biCompression, WORD biBitCount) @@ -93,6 +114,22 @@ dshow_read_close(AVFormatContext *s) IMediaControl_Release(ctx->control); } + if (ctx->graph) { + IEnumFilters *fenum; + int r; + r = IGraphBuilder_EnumFilters(ctx->graph, &fenum); + if (r == S_OK) { + IBaseFilter *f; + IEnumFilters_Reset(fenum); + while (IEnumFilters_Next(fenum, 1, &f, NULL) == S_OK) + if (IGraphBuilder_RemoveFilter(ctx->graph, f) == S_OK) + IEnumFilters_Reset(fenum); /* When a filter is removed, + * the list must be reset. */ + IEnumFilters_Release(fenum); + } + IGraphBuilder_Release(ctx->graph); + } + if (ctx->capture_pin[VideoDevice]) libAVPin_Release(ctx->capture_pin[VideoDevice]); if (ctx->capture_pin[AudioDevice]) @@ -111,20 +148,6 @@ dshow_read_close(AVFormatContext *s) if (ctx->device_filter[AudioDevice]) IBaseFilter_Release(ctx->device_filter[AudioDevice]); - if (ctx->graph) { - IEnumFilters *fenum; - int r; - r = IGraphBuilder_EnumFilters(ctx->graph, &fenum); - if (r == S_OK) { - IBaseFilter *f; - IEnumFilters_Reset(fenum); - while (IEnumFilters_Next(fenum, 1, &f, NULL) == S_OK) - IGraphBuilder_RemoveFilter(ctx->graph, f); - IEnumFilters_Release(fenum); - } - IGraphBuilder_Release(ctx->graph); - } - if (ctx->device_name[0]) av_free(ctx->device_name[0]); if (ctx->device_name[1]) @@ -213,39 +236,38 @@ fail: return; } +/** + * Cycle through available devices using the device enumerator devenum, + * retrieve the device with type specified by devtype and return the + * pointer to the object found in *pfilter. + * If pfilter is NULL, list all device names. + */ static int -dshow_open_device(AVFormatContext *avctx, ICreateDevEnum *devenum, - enum dshowDeviceType devtype) +dshow_cycle_devices(AVFormatContext *avctx, ICreateDevEnum *devenum, + enum dshowDeviceType devtype, IBaseFilter **pfilter) { struct dshow_ctx *ctx = avctx->priv_data; IBaseFilter *device_filter = NULL; IEnumMoniker *classenum = NULL; - IGraphBuilder *graph = ctx->graph; - IEnumPins *pins = 0; IMoniker *m = NULL; - IPin *device_pin = NULL; - libAVPin *capture_pin = NULL; - libAVFilter *capture_filter = NULL; const char *device_name = ctx->device_name[devtype]; - int ret = AVERROR(EIO); - IPin *pin; + int skip = (devtype == VideoDevice) ? ctx->video_device_number + : ctx->audio_device_number; int r; const GUID *device_guid[2] = { &CLSID_VideoInputDeviceCategory, &CLSID_AudioInputDeviceCategory }; - const GUID *mediatype[2] = { &MEDIATYPE_Video, &MEDIATYPE_Audio }; const char *devtypename = (devtype == VideoDevice) ? "video" : "audio"; - const wchar_t *filter_name[2] = { L"Audio capture filter", L"Video capture filter" }; r = ICreateDevEnum_CreateClassEnumerator(devenum, device_guid[devtype], (IEnumMoniker **) &classenum, 0); if (r != S_OK) { av_log(avctx, AV_LOG_ERROR, "Could not enumerate %s devices.\n", devtypename); - goto error; + return AVERROR(EIO); } - while (IEnumMoniker_Next(classenum, 1, &m, NULL) == S_OK && !device_filter) { + while (!device_filter && IEnumMoniker_Next(classenum, 1, &m, NULL) == S_OK) { IPropertyBag *bag = NULL; char *buf = NULL; VARIANT var; @@ -261,10 +283,15 @@ dshow_open_device(AVFormatContext *avctx, ICreateDevEnum *devenum, buf = dup_wchar_to_utf8(var.bstrVal); - if (strcmp(device_name, buf)) - goto fail1; + if (pfilter) { + if (strcmp(device_name, buf)) + goto fail1; - IMoniker_BindToObject(m, 0, 0, &IID_IBaseFilter, (void *) &device_filter); + if (!skip--) + IMoniker_BindToObject(m, 0, 0, &IID_IBaseFilter, (void *) &device_filter); + } else { + av_log(avctx, AV_LOG_INFO, " \"%s\"\n", buf); + } fail1: if (buf) @@ -274,28 +301,184 @@ fail1: IMoniker_Release(m); } - if (!device_filter) { - av_log(avctx, AV_LOG_ERROR, "Could not find %s device.\n", - devtypename); - goto error; - } - ctx->device_filter [devtype] = device_filter; + IEnumMoniker_Release(classenum); - r = IGraphBuilder_AddFilter(graph, device_filter, NULL); - if (r != S_OK) { - av_log(avctx, AV_LOG_ERROR, "Could not add device filter to graph.\n"); - goto error; + if (pfilter) { + if (!device_filter) { + av_log(avctx, AV_LOG_ERROR, "Could not find %s device.\n", + devtypename); + return AVERROR(EIO); + } + *pfilter = device_filter; } + return 0; +} + +/** + * Cycle through available formats using the specified pin, + * try to set parameters specified through AVOptions and if successful + * return 1 in *pformat_set. + * If pformat_set is NULL, list all pin capabilities. + */ +static void +dshow_cycle_formats(AVFormatContext *avctx, enum dshowDeviceType devtype, + IPin *pin, int *pformat_set) +{ + struct dshow_ctx *ctx = avctx->priv_data; + IAMStreamConfig *config = NULL; + AM_MEDIA_TYPE *type = NULL; + int format_set = 0; + void *caps = NULL; + int i, n, size; + + if (IPin_QueryInterface(pin, &IID_IAMStreamConfig, (void **) &config) != S_OK) + return; + if (IAMStreamConfig_GetNumberOfCapabilities(config, &n, &size) != S_OK) + goto end; + + caps = av_malloc(size); + if (!caps) + goto end; + + for (i = 0; i < n && !format_set; i++) { + IAMStreamConfig_GetStreamCaps(config, i, &type, (void *) caps); + +#if DSHOWDEBUG + ff_print_AM_MEDIA_TYPE(type); +#endif + + if (devtype == VideoDevice) { + VIDEO_STREAM_CONFIG_CAPS *vcaps = caps; + BITMAPINFOHEADER *bih; + int64_t *fr; +#if DSHOWDEBUG + ff_print_VIDEO_STREAM_CONFIG_CAPS(vcaps); +#endif + if (IsEqualGUID(&type->formattype, &FORMAT_VideoInfo)) { + VIDEOINFOHEADER *v = (void *) type->pbFormat; + fr = &v->AvgTimePerFrame; + bih = &v->bmiHeader; + } else if (IsEqualGUID(&type->formattype, &FORMAT_VideoInfo2)) { + VIDEOINFOHEADER2 *v = (void *) type->pbFormat; + fr = &v->AvgTimePerFrame; + bih = &v->bmiHeader; + } else { + goto next; + } + if (!pformat_set) { + av_log(avctx, AV_LOG_INFO, " min s=%ldx%ld fps=%g max s=%ldx%ld fps=%g\n", + vcaps->MinOutputSize.cx, vcaps->MinOutputSize.cy, + 1e7 / vcaps->MinFrameInterval, + vcaps->MaxOutputSize.cx, vcaps->MaxOutputSize.cy, + 1e7 / vcaps->MaxFrameInterval); + continue; + } + if (ctx->framerate) { + int64_t framerate = ((int64_t) ctx->requested_framerate.den*10000000) + / ctx->requested_framerate.num; + if (framerate > vcaps->MaxFrameInterval || + framerate < vcaps->MinFrameInterval) + goto next; + *fr = framerate; + } + if (ctx->video_size) { + if (ctx->requested_width > vcaps->MaxOutputSize.cx || + ctx->requested_width < vcaps->MinOutputSize.cx || + ctx->requested_height > vcaps->MaxOutputSize.cy || + ctx->requested_height < vcaps->MinOutputSize.cy) + goto next; + bih->biWidth = ctx->requested_width; + bih->biHeight = ctx->requested_height; + } + } else { + AUDIO_STREAM_CONFIG_CAPS *acaps = caps; + WAVEFORMATEX *fx; +#if DSHOWDEBUG + ff_print_AUDIO_STREAM_CONFIG_CAPS(acaps); +#endif + if (IsEqualGUID(&type->formattype, &FORMAT_WaveFormatEx)) { + fx = (void *) type->pbFormat; + } else { + goto next; + } + if (!pformat_set) { + 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); + continue; + } + if (ctx->sample_rate) { + if (ctx->sample_rate > acaps->MaximumSampleFrequency || + ctx->sample_rate < acaps->MinimumSampleFrequency) + goto next; + fx->nSamplesPerSec = ctx->sample_rate; + } + if (ctx->sample_size) { + if (ctx->sample_size > acaps->MaximumBitsPerSample || + ctx->sample_size < acaps->MinimumBitsPerSample) + goto next; + fx->wBitsPerSample = ctx->sample_size; + } + if (ctx->channels) { + if (ctx->channels > acaps->MaximumChannels || + ctx->channels < acaps->MinimumChannels) + goto next; + fx->nChannels = ctx->channels; + } + } + if (IAMStreamConfig_SetFormat(config, type) != S_OK) + goto next; + format_set = 1; +next: + if (type->pbFormat) + CoTaskMemFree(type->pbFormat); + CoTaskMemFree(type); + } +end: + IAMStreamConfig_Release(config); + if (caps) + av_free(caps); + if (pformat_set) + *pformat_set = format_set; +} + +/** + * Cycle through available pins using the device_filter device, of type + * 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. + */ +static int +dshow_cycle_pins(AVFormatContext *avctx, enum dshowDeviceType devtype, + IBaseFilter *device_filter, IPin **ppin) +{ + struct dshow_ctx *ctx = avctx->priv_data; + IEnumPins *pins = 0; + IPin *device_pin = NULL; + IPin *pin; + int r; + + const GUID *mediatype[2] = { &MEDIATYPE_Video, &MEDIATYPE_Audio }; + const char *devtypename = (devtype == VideoDevice) ? "video" : "audio"; + + int set_format = (devtype == VideoDevice && (ctx->video_size || ctx->framerate)) + || (devtype == AudioDevice && (ctx->channels || ctx->sample_rate)); + int format_set = 0; + r = IBaseFilter_EnumPins(device_filter, &pins); if (r != S_OK) { av_log(avctx, AV_LOG_ERROR, "Could not enumerate pins.\n"); - goto error; + return AVERROR(EIO); } - while (IEnumPins_Next(pins, 1, &pin, NULL) == S_OK && !device_pin) { + if (!ppin) { + av_log(avctx, AV_LOG_INFO, "DirectShow %s device options\n", + devtypename); + } + while (!device_pin && IEnumPins_Next(pins, 1, &pin, NULL) == S_OK) { IKsPropertySet *p = NULL; - IEnumMediaTypes *types; + IEnumMediaTypes *types = NULL; PIN_INFO info = {0}; AM_MEDIA_TYPE *type; GUID category; @@ -314,11 +497,25 @@ fail1: if (!IsEqualGUID(&category, &PIN_CATEGORY_CAPTURE)) goto next; + if (!ppin) { + char *buf = dup_wchar_to_utf8(info.achName); + av_log(avctx, AV_LOG_INFO, " Pin \"%s\"\n", buf); + av_free(buf); + dshow_cycle_formats(avctx, devtype, pin, NULL); + goto next; + } + if (set_format) { + dshow_cycle_formats(avctx, devtype, pin, &format_set); + if (!format_set) { + goto next; + } + } + if (IPin_EnumMediaTypes(pin, &types) != S_OK) goto next; IEnumMediaTypes_Reset(types); - while (IEnumMediaTypes_Next(types, 1, &type, NULL) == S_OK && !device_pin) { + while (!device_pin && IEnumMediaTypes_Next(types, 1, &type, NULL) == S_OK) { if (IsEqualGUID(&type->majortype, mediatype[devtype])) { device_pin = pin; goto next; @@ -335,9 +532,74 @@ next: IPin_Release(pin); } - if (!device_pin) { - av_log(avctx, AV_LOG_ERROR, - "Could not find output pin from %s capture device.\n", devtypename); + IEnumPins_Release(pins); + + if (ppin) { + if (set_format && !format_set) { + av_log(avctx, AV_LOG_ERROR, "Could not set %s options\n", devtypename); + return AVERROR(EIO); + } + if (!device_pin) { + av_log(avctx, AV_LOG_ERROR, + "Could not find output pin from %s capture device.\n", devtypename); + return AVERROR(EIO); + } + *ppin = device_pin; + } + + return 0; +} + +/** + * List options for device with type devtype. + * + * @param devenum device enumerator used for accessing the device + */ +static int +dshow_list_device_options(AVFormatContext *avctx, ICreateDevEnum *devenum, + enum dshowDeviceType devtype) +{ + IBaseFilter *device_filter = NULL; + int r; + + if ((r = dshow_cycle_devices(avctx, devenum, devtype, &device_filter)) < 0) + return r; + if ((r = dshow_cycle_pins(avctx, devtype, device_filter, NULL)) < 0) + return r; + + return 0; +} + +static int +dshow_open_device(AVFormatContext *avctx, ICreateDevEnum *devenum, + enum dshowDeviceType devtype) +{ + struct dshow_ctx *ctx = avctx->priv_data; + IBaseFilter *device_filter = NULL; + IGraphBuilder *graph = ctx->graph; + IPin *device_pin = NULL; + libAVPin *capture_pin = NULL; + libAVFilter *capture_filter = NULL; + int ret = AVERROR(EIO); + int r; + + const wchar_t *filter_name[2] = { L"Audio capture filter", L"Video capture filter" }; + + if ((r = dshow_cycle_devices(avctx, devenum, devtype, &device_filter)) < 0) { + ret = r; + goto error; + } + + ctx->device_filter [devtype] = device_filter; + + r = IGraphBuilder_AddFilter(graph, device_filter, NULL); + if (r != S_OK) { + av_log(avctx, AV_LOG_ERROR, "Could not add device filter to graph.\n"); + goto error; + } + + if ((r = dshow_cycle_pins(avctx, devtype, device_filter, &device_pin)) < 0) { + ret = r; goto error; } ctx->device_pin[devtype] = device_pin; @@ -369,11 +631,6 @@ next: ret = 0; error: - if (pins) - IEnumPins_Release(pins); - if (classenum) - IEnumMoniker_Release(classenum); - return ret; } @@ -407,11 +664,12 @@ dshow_add_device(AVFormatContext *avctx, AVFormatParameters *ap, AVStream *st; int ret = AVERROR(EIO); - st = av_new_stream(avctx, devtype); + st = avformat_new_stream(avctx, NULL); if (!st) { ret = AVERROR(ENOMEM); goto error; } + st->id = devtype; ctx->capture_filter[devtype]->stream_index = st->index; @@ -530,11 +788,26 @@ static int dshow_read_header(AVFormatContext *avctx, AVFormatParameters *ap) int ret = AVERROR(EIO); int r; - if (!parse_device_name(avctx)) { + if (!ctx->list_devices && !parse_device_name(avctx)) { av_log(avctx, AV_LOG_ERROR, "Malformed dshow input string.\n"); goto error; } + if (ctx->video_size) { + r = av_parse_video_size(&ctx->requested_width, &ctx->requested_height, ctx->video_size); + if (r < 0) { + av_log(avctx, AV_LOG_ERROR, "Could not parse video size '%s'.\n", ctx->video_size); + goto error; + } + } + if (ctx->framerate) { + r = av_parse_video_rate(&ctx->requested_framerate, ctx->framerate); + if (r < 0) { + av_log(avctx, AV_LOG_ERROR, "Could not parse framerate '%s'.\n", ctx->framerate); + goto error; + } + } + CoInitialize(0); r = CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, @@ -552,6 +825,23 @@ static int dshow_read_header(AVFormatContext *avctx, AVFormatParameters *ap) goto error; } + if (ctx->list_devices) { + av_log(avctx, AV_LOG_INFO, "DirectShow video devices\n"); + dshow_cycle_devices(avctx, devenum, VideoDevice, NULL); + av_log(avctx, AV_LOG_INFO, "DirectShow audio devices\n"); + dshow_cycle_devices(avctx, devenum, AudioDevice, NULL); + ret = AVERROR_EXIT; + goto error; + } + if (ctx->list_options) { + if (ctx->device_name[VideoDevice]) + dshow_list_device_options(avctx, devenum, VideoDevice); + if (ctx->device_name[AudioDevice]) + dshow_list_device_options(avctx, devenum, AudioDevice); + ret = AVERROR_EXIT; + goto error; + } + if (ctx->device_name[VideoDevice]) { ret = dshow_open_device(avctx, devenum, VideoDevice); if (ret < 0) @@ -639,6 +929,32 @@ static int dshow_read_packet(AVFormatContext *s, AVPacket *pkt) return pkt->size; } +#define OFFSET(x) offsetof(struct dshow_ctx, x) +#define DEC AV_OPT_FLAG_DECODING_PARAM +static const AVOption options[] = { + { "video_size", "set video size given a string such as 640x480 or hd720.", OFFSET(video_size), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, DEC }, + { "framerate", "set video frame rate", OFFSET(framerate), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, DEC }, + { "sample_rate", "set audio sample rate", OFFSET(sample_rate), AV_OPT_TYPE_INT, {.dbl = 0}, 0, INT_MAX, DEC }, + { "sample_size", "set audio sample size", OFFSET(sample_size), AV_OPT_TYPE_INT, {.dbl = 0}, 0, 16, DEC }, + { "channels", "set number of audio channels, such as 1 or 2", OFFSET(channels), AV_OPT_TYPE_INT, {.dbl = 0}, 0, INT_MAX, DEC }, + { "list_devices", "list available devices", OFFSET(list_devices), AV_OPT_TYPE_INT, {.dbl=0}, 0, 1, DEC, "list_devices" }, + { "true", "", 0, AV_OPT_TYPE_CONST, {.dbl=1}, 0, 0, DEC, "list_devices" }, + { "false", "", 0, AV_OPT_TYPE_CONST, {.dbl=0}, 0, 0, DEC, "list_devices" }, + { "list_options", "list available options for specified device", OFFSET(list_options), AV_OPT_TYPE_INT, {.dbl=0}, 0, 1, DEC, "list_options" }, + { "true", "", 0, AV_OPT_TYPE_CONST, {.dbl=1}, 0, 0, DEC, "list_options" }, + { "false", "", 0, AV_OPT_TYPE_CONST, {.dbl=0}, 0, 0, DEC, "list_options" }, + { "video_device_number", "set video device number for devices with same name (starts at 0)", OFFSET(video_device_number), AV_OPT_TYPE_INT, {.dbl = 0}, 0, INT_MAX, DEC }, + { "audio_device_number", "set audio device number for devices with same name (starts at 0)", OFFSET(audio_device_number), AV_OPT_TYPE_INT, {.dbl = 0}, 0, INT_MAX, DEC }, + { NULL }, +}; + +static const AVClass dshow_class = { + .class_name = "DirectShow indev", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + AVInputFormat ff_dshow_demuxer = { "dshow", NULL_IF_CONFIG_SMALL("DirectShow capture"), @@ -648,4 +964,5 @@ AVInputFormat ff_dshow_demuxer = { dshow_read_packet, dshow_read_close, .flags = AVFMT_NOFILE, + .priv_class = &dshow_class, };