forked from FFmpeg/FFmpeg
![Diederick Niehorster](/assets/img/avatar_default.png)
the list_devices option of dshow didn't indicate whether a specific device provides audio or video output. This patch iterates through all media formats of all pins exposed by the device to see what types it provides for capture, and prints this to the console for each device. Importantly, this now allows to find devices that provide both audio and video, and devices that provide neither. Signed-off-by: Diederick Niehorster <dcnieho@gmail.com> Reviewed-by: Roger Pack <rogerdpack2@gmail.com>
1493 lines
55 KiB
C
1493 lines
55 KiB
C
/*
|
|
* Directshow capture interface
|
|
* Copyright (c) 2010 Ramiro Polla
|
|
*
|
|
* This file is part of FFmpeg.
|
|
*
|
|
* FFmpeg is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* FFmpeg is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with FFmpeg; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "dshow_capture.h"
|
|
#include "libavutil/parseutils.h"
|
|
#include "libavutil/pixdesc.h"
|
|
#include "libavutil/opt.h"
|
|
#include "libavformat/internal.h"
|
|
#include "libavformat/riff.h"
|
|
#include "avdevice.h"
|
|
#include "libavcodec/raw.h"
|
|
#include "objidl.h"
|
|
#include "shlwapi.h"
|
|
|
|
|
|
static enum AVPixelFormat dshow_pixfmt(DWORD biCompression, WORD biBitCount)
|
|
{
|
|
switch(biCompression) {
|
|
case BI_BITFIELDS:
|
|
case BI_RGB:
|
|
switch(biBitCount) { /* 1-8 are untested */
|
|
case 1:
|
|
return AV_PIX_FMT_MONOWHITE;
|
|
case 4:
|
|
return AV_PIX_FMT_RGB4;
|
|
case 8:
|
|
return AV_PIX_FMT_RGB8;
|
|
case 16:
|
|
return AV_PIX_FMT_RGB555;
|
|
case 24:
|
|
return AV_PIX_FMT_BGR24;
|
|
case 32:
|
|
return AV_PIX_FMT_0RGB32;
|
|
}
|
|
}
|
|
return avpriv_find_pix_fmt(avpriv_get_raw_pix_fmt_tags(), biCompression); // all others
|
|
}
|
|
|
|
static int
|
|
dshow_read_close(AVFormatContext *s)
|
|
{
|
|
struct dshow_ctx *ctx = s->priv_data;
|
|
PacketList *pktl;
|
|
|
|
if (ctx->control) {
|
|
IMediaControl_Stop(ctx->control);
|
|
IMediaControl_Release(ctx->control);
|
|
}
|
|
|
|
if (ctx->media_event)
|
|
IMediaEvent_Release(ctx->media_event);
|
|
|
|
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. */
|
|
IBaseFilter_Release(f);
|
|
}
|
|
IEnumFilters_Release(fenum);
|
|
}
|
|
IGraphBuilder_Release(ctx->graph);
|
|
}
|
|
|
|
if (ctx->capture_pin[VideoDevice])
|
|
ff_dshow_pin_Release(ctx->capture_pin[VideoDevice]);
|
|
if (ctx->capture_pin[AudioDevice])
|
|
ff_dshow_pin_Release(ctx->capture_pin[AudioDevice]);
|
|
if (ctx->capture_filter[VideoDevice])
|
|
ff_dshow_filter_Release(ctx->capture_filter[VideoDevice]);
|
|
if (ctx->capture_filter[AudioDevice])
|
|
ff_dshow_filter_Release(ctx->capture_filter[AudioDevice]);
|
|
|
|
if (ctx->device_pin[VideoDevice])
|
|
IPin_Release(ctx->device_pin[VideoDevice]);
|
|
if (ctx->device_pin[AudioDevice])
|
|
IPin_Release(ctx->device_pin[AudioDevice]);
|
|
if (ctx->device_filter[VideoDevice])
|
|
IBaseFilter_Release(ctx->device_filter[VideoDevice]);
|
|
if (ctx->device_filter[AudioDevice])
|
|
IBaseFilter_Release(ctx->device_filter[AudioDevice]);
|
|
|
|
av_freep(&ctx->device_name[0]);
|
|
av_freep(&ctx->device_name[1]);
|
|
av_freep(&ctx->device_unique_name[0]);
|
|
av_freep(&ctx->device_unique_name[1]);
|
|
|
|
if(ctx->mutex)
|
|
CloseHandle(ctx->mutex);
|
|
if(ctx->event[0])
|
|
CloseHandle(ctx->event[0]);
|
|
if(ctx->event[1])
|
|
CloseHandle(ctx->event[1]);
|
|
|
|
pktl = ctx->pktl;
|
|
while (pktl) {
|
|
PacketList *next = pktl->next;
|
|
av_packet_unref(&pktl->pkt);
|
|
av_free(pktl);
|
|
pktl = next;
|
|
}
|
|
|
|
CoUninitialize();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static char *dup_wchar_to_utf8(wchar_t *w)
|
|
{
|
|
char *s = NULL;
|
|
int l = WideCharToMultiByte(CP_UTF8, 0, w, -1, 0, 0, 0, 0);
|
|
s = av_malloc(l);
|
|
if (s)
|
|
WideCharToMultiByte(CP_UTF8, 0, w, -1, s, l, 0, 0);
|
|
return s;
|
|
}
|
|
|
|
static int shall_we_drop(AVFormatContext *s, int index, enum dshowDeviceType devtype)
|
|
{
|
|
struct dshow_ctx *ctx = s->priv_data;
|
|
static const uint8_t dropscore[] = {62, 75, 87, 100};
|
|
const int ndropscores = FF_ARRAY_ELEMS(dropscore);
|
|
unsigned int buffer_fullness = (ctx->curbufsize[index]*100)/s->max_picture_buffer;
|
|
const char *devtypename = (devtype == VideoDevice) ? "video" : "audio";
|
|
|
|
if(dropscore[++ctx->video_frame_num%ndropscores] <= buffer_fullness) {
|
|
av_log(s, AV_LOG_ERROR,
|
|
"real-time buffer [%s] [%s input] too full or near too full (%d%% of size: %d [rtbufsize parameter])! frame dropped!\n",
|
|
ctx->device_name[devtype], devtypename, buffer_fullness, s->max_picture_buffer);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
callback(void *priv_data, int index, uint8_t *buf, int buf_size, int64_t time, enum dshowDeviceType devtype)
|
|
{
|
|
AVFormatContext *s = priv_data;
|
|
struct dshow_ctx *ctx = s->priv_data;
|
|
PacketList **ppktl, *pktl_next;
|
|
|
|
// dump_videohdr(s, vdhdr);
|
|
|
|
WaitForSingleObject(ctx->mutex, INFINITE);
|
|
|
|
if(shall_we_drop(s, index, devtype))
|
|
goto fail;
|
|
|
|
pktl_next = av_mallocz(sizeof(PacketList));
|
|
if(!pktl_next)
|
|
goto fail;
|
|
|
|
if(av_new_packet(&pktl_next->pkt, buf_size) < 0) {
|
|
av_free(pktl_next);
|
|
goto fail;
|
|
}
|
|
|
|
pktl_next->pkt.stream_index = index;
|
|
pktl_next->pkt.pts = time;
|
|
memcpy(pktl_next->pkt.data, buf, buf_size);
|
|
|
|
for(ppktl = &ctx->pktl ; *ppktl ; ppktl = &(*ppktl)->next);
|
|
*ppktl = pktl_next;
|
|
ctx->curbufsize[index] += buf_size;
|
|
|
|
SetEvent(ctx->event[1]);
|
|
ReleaseMutex(ctx->mutex);
|
|
|
|
return;
|
|
fail:
|
|
ReleaseMutex(ctx->mutex);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
dshow_get_device_media_types(AVFormatContext *avctx, enum dshowDeviceType devtype,
|
|
enum dshowSourceFilterType sourcetype, IBaseFilter *device_filter,
|
|
enum AVMediaType **media_types, int *nb_media_types)
|
|
{
|
|
struct dshow_ctx *ctx = avctx->priv_data;
|
|
IEnumPins *pins = 0;
|
|
IPin *pin;
|
|
int has_audio = 0, has_video = 0;
|
|
|
|
if (IBaseFilter_EnumPins(device_filter, &pins) != S_OK)
|
|
return;
|
|
|
|
while (IEnumPins_Next(pins, 1, &pin, NULL) == S_OK) {
|
|
IKsPropertySet *p = NULL;
|
|
PIN_INFO info = { 0 };
|
|
GUID category;
|
|
DWORD r2;
|
|
IEnumMediaTypes *types = NULL;
|
|
AM_MEDIA_TYPE *type;
|
|
|
|
if (IPin_QueryPinInfo(pin, &info) != S_OK)
|
|
goto next;
|
|
IBaseFilter_Release(info.pFilter);
|
|
|
|
if (info.dir != PINDIR_OUTPUT)
|
|
goto next;
|
|
if (IPin_QueryInterface(pin, &IID_IKsPropertySet, (void **) &p) != S_OK)
|
|
goto next;
|
|
if (IKsPropertySet_Get(p, &ROPSETID_Pin, AMPROPERTY_PIN_CATEGORY,
|
|
NULL, 0, &category, sizeof(GUID), &r2) != S_OK)
|
|
goto next;
|
|
if (!IsEqualGUID(&category, &PIN_CATEGORY_CAPTURE))
|
|
goto next;
|
|
|
|
if (IPin_EnumMediaTypes(pin, &types) != S_OK)
|
|
goto next;
|
|
|
|
// enumerate media types exposed by pin
|
|
// NB: don't know if a pin can expose both audio and video, check 'm all to be safe
|
|
IEnumMediaTypes_Reset(types);
|
|
while (IEnumMediaTypes_Next(types, 1, &type, NULL) == S_OK) {
|
|
if (IsEqualGUID(&type->majortype, &MEDIATYPE_Video)) {
|
|
has_video = 1;
|
|
} else if (IsEqualGUID(&type->majortype, &MEDIATYPE_Audio)) {
|
|
has_audio = 1;
|
|
}
|
|
CoTaskMemFree(type);
|
|
}
|
|
|
|
next:
|
|
if (types)
|
|
IEnumMediaTypes_Release(types);
|
|
if (p)
|
|
IKsPropertySet_Release(p);
|
|
if (pin)
|
|
IPin_Release(pin);
|
|
}
|
|
|
|
IEnumPins_Release(pins);
|
|
|
|
if (has_audio || has_video) {
|
|
int nb_types = has_audio + has_video;
|
|
*media_types = av_malloc_array(nb_types, sizeof(enum AVMediaType));
|
|
if (*media_types) {
|
|
if (has_audio)
|
|
(*media_types)[0] = AVMEDIA_TYPE_AUDIO;
|
|
if (has_video)
|
|
(*media_types)[0 + has_audio] = AVMEDIA_TYPE_VIDEO;
|
|
*nb_media_types = nb_types;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* If device_list is not NULL, populate it with found devices instead of
|
|
* outputting device names to log
|
|
*/
|
|
static int
|
|
dshow_cycle_devices(AVFormatContext *avctx, ICreateDevEnum *devenum,
|
|
enum dshowDeviceType devtype, enum dshowSourceFilterType sourcetype,
|
|
IBaseFilter **pfilter, char **device_unique_name,
|
|
AVDeviceInfoList **device_list)
|
|
{
|
|
struct dshow_ctx *ctx = avctx->priv_data;
|
|
IBaseFilter *device_filter = NULL;
|
|
IEnumMoniker *classenum = NULL;
|
|
IMoniker *m = NULL;
|
|
const char *device_name = ctx->device_name[devtype];
|
|
int skip = (devtype == VideoDevice) ? ctx->video_device_number
|
|
: ctx->audio_device_number;
|
|
int r;
|
|
|
|
const GUID *device_guid[2] = { &CLSID_VideoInputDeviceCategory,
|
|
&CLSID_AudioInputDeviceCategory };
|
|
const char *devtypename = (devtype == VideoDevice) ? "video" : "audio only";
|
|
const char *sourcetypename = (sourcetype == VideoSourceDevice) ? "video" : "audio";
|
|
|
|
r = ICreateDevEnum_CreateClassEnumerator(devenum, device_guid[sourcetype],
|
|
(IEnumMoniker **) &classenum, 0);
|
|
if (r != S_OK) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not enumerate %s devices (or none found).\n",
|
|
devtypename);
|
|
return AVERROR(EIO);
|
|
}
|
|
|
|
while (!device_filter && IEnumMoniker_Next(classenum, 1, &m, NULL) == S_OK) {
|
|
IPropertyBag *bag = NULL;
|
|
char *friendly_name = NULL;
|
|
char *unique_name = NULL;
|
|
VARIANT var;
|
|
IBindCtx *bind_ctx = NULL;
|
|
LPOLESTR olestr = NULL;
|
|
LPMALLOC co_malloc = NULL;
|
|
AVDeviceInfo *device = NULL;
|
|
enum AVMediaType *media_types = NULL;
|
|
int nb_media_types = 0;
|
|
int i;
|
|
|
|
r = CoGetMalloc(1, &co_malloc);
|
|
if (r != S_OK)
|
|
goto fail;
|
|
r = CreateBindCtx(0, &bind_ctx);
|
|
if (r != S_OK)
|
|
goto fail;
|
|
/* GetDisplayname works for both video and audio, DevicePath doesn't */
|
|
r = IMoniker_GetDisplayName(m, bind_ctx, NULL, &olestr);
|
|
if (r != S_OK)
|
|
goto fail;
|
|
unique_name = dup_wchar_to_utf8(olestr);
|
|
/* replace ':' with '_' since we use : to delineate between sources */
|
|
for (i = 0; i < strlen(unique_name); i++) {
|
|
if (unique_name[i] == ':')
|
|
unique_name[i] = '_';
|
|
}
|
|
|
|
r = IMoniker_BindToStorage(m, 0, 0, &IID_IPropertyBag, (void *) &bag);
|
|
if (r != S_OK)
|
|
goto fail;
|
|
|
|
var.vt = VT_BSTR;
|
|
r = IPropertyBag_Read(bag, L"FriendlyName", &var, NULL);
|
|
if (r != S_OK)
|
|
goto fail;
|
|
friendly_name = dup_wchar_to_utf8(var.bstrVal);
|
|
|
|
if (pfilter) {
|
|
if (strcmp(device_name, friendly_name) && strcmp(device_name, unique_name))
|
|
goto fail;
|
|
|
|
if (!skip--) {
|
|
r = IMoniker_BindToObject(m, 0, 0, &IID_IBaseFilter, (void *) &device_filter);
|
|
if (r != S_OK) {
|
|
av_log(avctx, AV_LOG_ERROR, "Unable to BindToObject for %s\n", device_name);
|
|
goto fail;
|
|
}
|
|
*device_unique_name = unique_name;
|
|
unique_name = NULL;
|
|
// success, loop will end now
|
|
}
|
|
} else {
|
|
// get media types exposed by pins of device
|
|
if (IMoniker_BindToObject(m, 0, 0, &IID_IBaseFilter, (void* ) &device_filter) == S_OK) {
|
|
dshow_get_device_media_types(avctx, devtype, sourcetype, device_filter, &media_types, &nb_media_types);
|
|
IBaseFilter_Release(device_filter);
|
|
device_filter = NULL;
|
|
}
|
|
if (device_list) {
|
|
device = av_mallocz(sizeof(AVDeviceInfo));
|
|
if (!device)
|
|
goto fail;
|
|
|
|
device->device_name = av_strdup(friendly_name);
|
|
device->device_description = av_strdup(unique_name);
|
|
if (!device->device_name || !device->device_description)
|
|
goto fail;
|
|
|
|
// make space in device_list for this new device
|
|
if (av_reallocp_array(&(*device_list)->devices,
|
|
(*device_list)->nb_devices + 1,
|
|
sizeof(*(*device_list)->devices)) < 0)
|
|
goto fail;
|
|
|
|
// store device in list
|
|
(*device_list)->devices[(*device_list)->nb_devices] = device;
|
|
(*device_list)->nb_devices++;
|
|
device = NULL; // copied into array, make sure not freed below
|
|
}
|
|
else {
|
|
av_log(avctx, AV_LOG_INFO, "\"%s\"", friendly_name);
|
|
if (nb_media_types > 0) {
|
|
const char* media_type = av_get_media_type_string(media_types[0]);
|
|
av_log(avctx, AV_LOG_INFO, " (%s", media_type ? media_type : "unknown");
|
|
for (int i = 1; i < nb_media_types; ++i) {
|
|
media_type = av_get_media_type_string(media_types[i]);
|
|
av_log(avctx, AV_LOG_INFO, ", %s", media_type ? media_type : "unknown");
|
|
}
|
|
av_log(avctx, AV_LOG_INFO, ")");
|
|
} else {
|
|
av_log(avctx, AV_LOG_INFO, " (none)");
|
|
}
|
|
av_log(avctx, AV_LOG_INFO, "\n");
|
|
av_log(avctx, AV_LOG_INFO, " Alternative name \"%s\"\n", unique_name);
|
|
}
|
|
}
|
|
|
|
fail:
|
|
av_freep(&media_types);
|
|
if (device) {
|
|
av_freep(&device->device_name);
|
|
av_freep(&device->device_description);
|
|
av_free(device);
|
|
}
|
|
if (olestr && co_malloc)
|
|
IMalloc_Free(co_malloc, olestr);
|
|
if (bind_ctx)
|
|
IBindCtx_Release(bind_ctx);
|
|
av_freep(&friendly_name);
|
|
av_freep(&unique_name);
|
|
if (bag)
|
|
IPropertyBag_Release(bag);
|
|
IMoniker_Release(m);
|
|
}
|
|
|
|
IEnumMoniker_Release(classenum);
|
|
|
|
if (pfilter) {
|
|
if (!device_filter) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not find %s device with name [%s] among source devices of type %s.\n",
|
|
devtypename, device_name, sourcetypename);
|
|
return AVERROR(EIO);
|
|
}
|
|
*pfilter = device_filter;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dshow_get_device_list(AVFormatContext *avctx, AVDeviceInfoList *device_list)
|
|
{
|
|
struct dshow_ctx *ctx = avctx->priv_data;
|
|
ICreateDevEnum *devenum = NULL;
|
|
int r;
|
|
int ret = AVERROR(EIO);
|
|
|
|
if (!device_list)
|
|
return AVERROR(EINVAL);
|
|
|
|
CoInitialize(0);
|
|
|
|
r = CoCreateInstance(&CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
|
|
&IID_ICreateDevEnum, (void**)&devenum);
|
|
if (r != S_OK) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not enumerate system devices.\n");
|
|
goto error;
|
|
}
|
|
|
|
ret = dshow_cycle_devices(avctx, devenum, VideoDevice, VideoSourceDevice, NULL, NULL, &device_list);
|
|
if (ret < S_OK)
|
|
goto error;
|
|
ret = dshow_cycle_devices(avctx, devenum, AudioDevice, AudioSourceDevice, NULL, NULL, &device_list);
|
|
|
|
error:
|
|
if (devenum)
|
|
ICreateDevEnum_Release(devenum);
|
|
|
|
CoUninitialize();
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* 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, r;
|
|
|
|
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++) {
|
|
r = IAMStreamConfig_GetStreamCaps(config, i, &type, (void *) caps);
|
|
if (r != S_OK)
|
|
goto next;
|
|
#if DSHOWDEBUG
|
|
ff_print_AM_MEDIA_TYPE(type);
|
|
#endif
|
|
|
|
if (devtype == VideoDevice) {
|
|
VIDEO_STREAM_CONFIG_CAPS *vcaps = caps;
|
|
BITMAPINFOHEADER *bih;
|
|
int64_t *fr;
|
|
const AVCodecTag *const tags[] = { avformat_get_riff_video_tags(), NULL };
|
|
#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) {
|
|
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);
|
|
const AVCodec *codec = avcodec_find_decoder(codec_id);
|
|
if (codec_id == AV_CODEC_ID_NONE || !codec) {
|
|
av_log(avctx, AV_LOG_INFO, " unknown compression type 0x%X", (int) bih->biCompression);
|
|
} else {
|
|
av_log(avctx, AV_LOG_INFO, " vcodec=%s", codec->name);
|
|
}
|
|
} else {
|
|
av_log(avctx, AV_LOG_INFO, " pixel_format=%s", av_get_pix_fmt_name(pix_fmt));
|
|
}
|
|
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->MaxFrameInterval,
|
|
vcaps->MaxOutputSize.cx, vcaps->MaxOutputSize.cy,
|
|
1e7 / vcaps->MinFrameInterval);
|
|
continue;
|
|
}
|
|
if (ctx->video_codec_id != AV_CODEC_ID_RAWVIDEO) {
|
|
if (ctx->video_codec_id != av_codec_get_id(tags, bih->biCompression))
|
|
goto next;
|
|
}
|
|
if (ctx->pixel_format != AV_PIX_FMT_NONE &&
|
|
ctx->pixel_format != dshow_pixfmt(bih->biCompression, bih->biBitCount)) {
|
|
goto next;
|
|
}
|
|
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->requested_width && ctx->requested_height) {
|
|
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 {
|
|
WAVEFORMATEX *fx;
|
|
#if DSHOWDEBUG
|
|
AUDIO_STREAM_CONFIG_CAPS *acaps = caps;
|
|
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,
|
|
" ch=%2u, bits=%2u, rate=%6lu\n",
|
|
fx->nChannels, fx->wBitsPerSample, fx->nSamplesPerSec
|
|
);
|
|
continue;
|
|
}
|
|
if (
|
|
(ctx->sample_rate && ctx->sample_rate != fx->nSamplesPerSec) ||
|
|
(ctx->sample_size && ctx->sample_size != fx->wBitsPerSample) ||
|
|
(ctx->channels && ctx->channels != fx->nChannels )
|
|
) {
|
|
goto next;
|
|
}
|
|
}
|
|
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);
|
|
av_free(caps);
|
|
if (pformat_set)
|
|
*pformat_set = format_set;
|
|
}
|
|
|
|
/**
|
|
* Set audio device buffer size in milliseconds (which can directly impact
|
|
* latency, depending on the device).
|
|
*/
|
|
static int
|
|
dshow_set_audio_buffer_size(AVFormatContext *avctx, IPin *pin)
|
|
{
|
|
struct dshow_ctx *ctx = avctx->priv_data;
|
|
IAMBufferNegotiation *buffer_negotiation = NULL;
|
|
ALLOCATOR_PROPERTIES props = { -1, -1, -1, -1 };
|
|
IAMStreamConfig *config = NULL;
|
|
AM_MEDIA_TYPE *type = NULL;
|
|
int ret = AVERROR(EIO);
|
|
|
|
if (IPin_QueryInterface(pin, &IID_IAMStreamConfig, (void **) &config) != S_OK)
|
|
goto end;
|
|
if (IAMStreamConfig_GetFormat(config, &type) != S_OK)
|
|
goto end;
|
|
if (!IsEqualGUID(&type->formattype, &FORMAT_WaveFormatEx))
|
|
goto end;
|
|
|
|
props.cbBuffer = (((WAVEFORMATEX *) type->pbFormat)->nAvgBytesPerSec)
|
|
* ctx->audio_buffer_size / 1000;
|
|
|
|
if (IPin_QueryInterface(pin, &IID_IAMBufferNegotiation, (void **) &buffer_negotiation) != S_OK)
|
|
goto end;
|
|
if (IAMBufferNegotiation_SuggestAllocatorProperties(buffer_negotiation, &props) != S_OK)
|
|
goto end;
|
|
|
|
ret = 0;
|
|
|
|
end:
|
|
if (buffer_negotiation)
|
|
IAMBufferNegotiation_Release(buffer_negotiation);
|
|
if (type) {
|
|
if (type->pbFormat)
|
|
CoTaskMemFree(type->pbFormat);
|
|
CoTaskMemFree(type);
|
|
}
|
|
if (config)
|
|
IAMStreamConfig_Release(config);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Pops up a user dialog allowing them to adjust properties for the given filter, if possible.
|
|
*/
|
|
void
|
|
ff_dshow_show_filter_properties(IBaseFilter *device_filter, AVFormatContext *avctx) {
|
|
ISpecifyPropertyPages *property_pages = NULL;
|
|
IUnknown *device_filter_iunknown = NULL;
|
|
HRESULT hr;
|
|
FILTER_INFO filter_info = {0}; /* a warning on this line is false positive GCC bug 53119 AFAICT */
|
|
CAUUID ca_guid = {0};
|
|
|
|
hr = IBaseFilter_QueryInterface(device_filter, &IID_ISpecifyPropertyPages, (void **)&property_pages);
|
|
if (hr != S_OK) {
|
|
av_log(avctx, AV_LOG_WARNING, "requested filter does not have a property page to show");
|
|
goto end;
|
|
}
|
|
hr = IBaseFilter_QueryFilterInfo(device_filter, &filter_info);
|
|
if (hr != S_OK) {
|
|
goto fail;
|
|
}
|
|
hr = IBaseFilter_QueryInterface(device_filter, &IID_IUnknown, (void **)&device_filter_iunknown);
|
|
if (hr != S_OK) {
|
|
goto fail;
|
|
}
|
|
hr = ISpecifyPropertyPages_GetPages(property_pages, &ca_guid);
|
|
if (hr != S_OK) {
|
|
goto fail;
|
|
}
|
|
hr = OleCreatePropertyFrame(NULL, 0, 0, filter_info.achName, 1, &device_filter_iunknown, ca_guid.cElems,
|
|
ca_guid.pElems, 0, 0, NULL);
|
|
if (hr != S_OK) {
|
|
goto fail;
|
|
}
|
|
goto end;
|
|
fail:
|
|
av_log(avctx, AV_LOG_ERROR, "Failure showing property pages for filter");
|
|
end:
|
|
if (property_pages)
|
|
ISpecifyPropertyPages_Release(property_pages);
|
|
if (device_filter_iunknown)
|
|
IUnknown_Release(device_filter_iunknown);
|
|
if (filter_info.pGraph)
|
|
IFilterGraph_Release(filter_info.pGraph);
|
|
if (ca_guid.pElems)
|
|
CoTaskMemFree(ca_guid.pElems);
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
enum dshowSourceFilterType sourcetype, 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 only";
|
|
const char *sourcetypename = (sourcetype == VideoSourceDevice) ? "video" : "audio";
|
|
|
|
int set_format = (devtype == VideoDevice && (ctx->framerate ||
|
|
(ctx->requested_width && ctx->requested_height) ||
|
|
ctx->pixel_format != AV_PIX_FMT_NONE ||
|
|
ctx->video_codec_id != AV_CODEC_ID_RAWVIDEO))
|
|
|| (devtype == AudioDevice && (ctx->channels || ctx->sample_rate || ctx->sample_size));
|
|
int format_set = 0;
|
|
int should_show_properties = (devtype == VideoDevice) ? ctx->show_video_device_dialog : ctx->show_audio_device_dialog;
|
|
|
|
if (should_show_properties)
|
|
ff_dshow_show_filter_properties(device_filter, avctx);
|
|
|
|
r = IBaseFilter_EnumPins(device_filter, &pins);
|
|
if (r != S_OK) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not enumerate pins.\n");
|
|
return AVERROR(EIO);
|
|
}
|
|
|
|
if (!ppin) {
|
|
av_log(avctx, AV_LOG_INFO, "DirectShow %s device options (from %s devices)\n",
|
|
devtypename, sourcetypename);
|
|
}
|
|
|
|
while (!device_pin && IEnumPins_Next(pins, 1, &pin, NULL) == S_OK) {
|
|
IKsPropertySet *p = NULL;
|
|
IEnumMediaTypes *types = NULL;
|
|
PIN_INFO info = {0};
|
|
AM_MEDIA_TYPE *type;
|
|
GUID category;
|
|
DWORD r2;
|
|
char *name_buf = NULL;
|
|
wchar_t *pin_id = NULL;
|
|
char *pin_buf = NULL;
|
|
char *desired_pin_name = devtype == VideoDevice ? ctx->video_pin_name : ctx->audio_pin_name;
|
|
|
|
IPin_QueryPinInfo(pin, &info);
|
|
IBaseFilter_Release(info.pFilter);
|
|
|
|
if (info.dir != PINDIR_OUTPUT)
|
|
goto next;
|
|
if (IPin_QueryInterface(pin, &IID_IKsPropertySet, (void **) &p) != S_OK)
|
|
goto next;
|
|
if (IKsPropertySet_Get(p, &ROPSETID_Pin, AMPROPERTY_PIN_CATEGORY,
|
|
NULL, 0, &category, sizeof(GUID), &r2) != S_OK)
|
|
goto next;
|
|
if (!IsEqualGUID(&category, &PIN_CATEGORY_CAPTURE))
|
|
goto next;
|
|
name_buf = dup_wchar_to_utf8(info.achName);
|
|
|
|
r = IPin_QueryId(pin, &pin_id);
|
|
if (r != S_OK) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not query pin id\n");
|
|
return AVERROR(EIO);
|
|
}
|
|
pin_buf = dup_wchar_to_utf8(pin_id);
|
|
|
|
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);
|
|
goto next;
|
|
}
|
|
|
|
if (desired_pin_name) {
|
|
if(strcmp(name_buf, desired_pin_name) && strcmp(pin_buf, desired_pin_name)) {
|
|
av_log(avctx, AV_LOG_DEBUG, "skipping pin \"%s\" (\"%s\") != requested \"%s\"\n",
|
|
name_buf, pin_buf, desired_pin_name);
|
|
goto next;
|
|
}
|
|
}
|
|
|
|
if (set_format) {
|
|
dshow_cycle_formats(avctx, devtype, pin, &format_set);
|
|
if (!format_set) {
|
|
goto next;
|
|
}
|
|
}
|
|
if (devtype == AudioDevice && ctx->audio_buffer_size) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
if (IPin_EnumMediaTypes(pin, &types) != S_OK)
|
|
goto next;
|
|
|
|
IEnumMediaTypes_Reset(types);
|
|
/* in case format_set was not called, just verify the majortype */
|
|
while (!device_pin && IEnumMediaTypes_Next(types, 1, &type, NULL) == S_OK) {
|
|
if (IsEqualGUID(&type->majortype, mediatype[devtype])) {
|
|
device_pin = pin;
|
|
av_log(avctx, AV_LOG_DEBUG, "Selecting pin %s on %s\n", name_buf, devtypename);
|
|
goto next;
|
|
}
|
|
CoTaskMemFree(type);
|
|
}
|
|
|
|
next:
|
|
if (types)
|
|
IEnumMediaTypes_Release(types);
|
|
if (p)
|
|
IKsPropertySet_Release(p);
|
|
if (device_pin != pin)
|
|
IPin_Release(pin);
|
|
av_free(name_buf);
|
|
av_free(pin_buf);
|
|
if (pin_id)
|
|
CoTaskMemFree(pin_id);
|
|
}
|
|
|
|
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, source filter type sourcetype
|
|
*
|
|
* @param devenum device enumerator used for accessing the device
|
|
*/
|
|
static int
|
|
dshow_list_device_options(AVFormatContext *avctx, ICreateDevEnum *devenum,
|
|
enum dshowDeviceType devtype, enum dshowSourceFilterType sourcetype)
|
|
{
|
|
struct dshow_ctx *ctx = avctx->priv_data;
|
|
IBaseFilter *device_filter = 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;
|
|
ctx->device_unique_name[devtype] = device_unique_name;
|
|
if ((r = dshow_cycle_pins(avctx, devtype, sourcetype, device_filter, NULL)) < 0)
|
|
return r;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
dshow_open_device(AVFormatContext *avctx, ICreateDevEnum *devenum,
|
|
enum dshowDeviceType devtype, enum dshowSourceFilterType sourcetype)
|
|
{
|
|
struct dshow_ctx *ctx = avctx->priv_data;
|
|
IBaseFilter *device_filter = NULL;
|
|
char *device_filter_unique_name = NULL;
|
|
IGraphBuilder *graph = ctx->graph;
|
|
IPin *device_pin = NULL;
|
|
DShowPin *capture_pin = NULL;
|
|
DShowFilter *capture_filter = NULL;
|
|
ICaptureGraphBuilder2 *graph_builder2 = NULL;
|
|
int ret = AVERROR(EIO);
|
|
int r;
|
|
IStream *ifile_stream = NULL;
|
|
IStream *ofile_stream = NULL;
|
|
IPersistStream *pers_stream = NULL;
|
|
enum dshowDeviceType otherDevType = (devtype == VideoDevice) ? AudioDevice : VideoDevice;
|
|
|
|
const wchar_t *filter_name[2] = { L"Audio capture filter", L"Video capture filter" };
|
|
|
|
|
|
if ( ((ctx->audio_filter_load_file) && (strlen(ctx->audio_filter_load_file)>0) && (sourcetype == AudioSourceDevice)) ||
|
|
((ctx->video_filter_load_file) && (strlen(ctx->video_filter_load_file)>0) && (sourcetype == VideoSourceDevice)) ) {
|
|
HRESULT hr;
|
|
char *filename = NULL;
|
|
|
|
if (sourcetype == AudioSourceDevice)
|
|
filename = ctx->audio_filter_load_file;
|
|
else
|
|
filename = ctx->video_filter_load_file;
|
|
|
|
hr = SHCreateStreamOnFile ((LPCSTR) filename, STGM_READ, &ifile_stream);
|
|
if (S_OK != hr) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not open capture filter description file.\n");
|
|
goto error;
|
|
}
|
|
|
|
hr = OleLoadFromStream(ifile_stream, &IID_IBaseFilter, (void **) &device_filter);
|
|
if (hr != S_OK) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not load capture filter from file.\n");
|
|
goto error;
|
|
}
|
|
|
|
if (sourcetype == AudioSourceDevice)
|
|
av_log(avctx, AV_LOG_INFO, "Audio-");
|
|
else
|
|
av_log(avctx, AV_LOG_INFO, "Video-");
|
|
av_log(avctx, AV_LOG_INFO, "Capture filter loaded successfully from file \"%s\".\n", filename);
|
|
} else {
|
|
|
|
if ((r = dshow_cycle_devices(avctx, devenum, devtype, sourcetype, &device_filter, &device_filter_unique_name, NULL)) < 0) {
|
|
ret = r;
|
|
goto error;
|
|
}
|
|
}
|
|
if (ctx->device_filter[otherDevType]) {
|
|
// avoid adding add two instances of the same device to the graph, one for video, one for audio
|
|
// a few devices don't support this (could also do this check earlier to avoid double crossbars, etc. but they seem OK)
|
|
if (strcmp(device_filter_unique_name, ctx->device_unique_name[otherDevType]) == 0) {
|
|
av_log(avctx, AV_LOG_DEBUG, "reusing previous graph capture filter... %s\n", device_filter_unique_name);
|
|
IBaseFilter_Release(device_filter);
|
|
device_filter = ctx->device_filter[otherDevType];
|
|
IBaseFilter_AddRef(ctx->device_filter[otherDevType]);
|
|
} else {
|
|
av_log(avctx, AV_LOG_DEBUG, "not reusing previous graph capture filter %s != %s\n", device_filter_unique_name, ctx->device_unique_name[otherDevType]);
|
|
}
|
|
}
|
|
|
|
ctx->device_filter [devtype] = device_filter;
|
|
ctx->device_unique_name [devtype] = device_filter_unique_name;
|
|
|
|
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, sourcetype, device_filter, &device_pin)) < 0) {
|
|
ret = r;
|
|
goto error;
|
|
}
|
|
|
|
ctx->device_pin[devtype] = device_pin;
|
|
|
|
capture_filter = ff_dshow_filter_Create(avctx, callback, devtype);
|
|
if (!capture_filter) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not create grabber filter.\n");
|
|
goto error;
|
|
}
|
|
ctx->capture_filter[devtype] = capture_filter;
|
|
|
|
if ( ((ctx->audio_filter_save_file) && (strlen(ctx->audio_filter_save_file)>0) && (sourcetype == AudioSourceDevice)) ||
|
|
((ctx->video_filter_save_file) && (strlen(ctx->video_filter_save_file)>0) && (sourcetype == VideoSourceDevice)) ) {
|
|
|
|
HRESULT hr;
|
|
char *filename = NULL;
|
|
|
|
if (sourcetype == AudioSourceDevice)
|
|
filename = ctx->audio_filter_save_file;
|
|
else
|
|
filename = ctx->video_filter_save_file;
|
|
|
|
hr = SHCreateStreamOnFile ((LPCSTR) filename, STGM_CREATE | STGM_READWRITE, &ofile_stream);
|
|
if (S_OK != hr) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not create capture filter description file.\n");
|
|
goto error;
|
|
}
|
|
|
|
hr = IBaseFilter_QueryInterface(device_filter, &IID_IPersistStream, (void **) &pers_stream);
|
|
if (hr != S_OK) {
|
|
av_log(avctx, AV_LOG_ERROR, "Query for IPersistStream failed.\n");
|
|
goto error;
|
|
}
|
|
|
|
hr = OleSaveToStream(pers_stream, ofile_stream);
|
|
if (hr != S_OK) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not save capture filter \n");
|
|
goto error;
|
|
}
|
|
|
|
hr = IStream_Commit(ofile_stream, STGC_DEFAULT);
|
|
if (S_OK != hr) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not commit capture filter data to file.\n");
|
|
goto error;
|
|
}
|
|
|
|
if (sourcetype == AudioSourceDevice)
|
|
av_log(avctx, AV_LOG_INFO, "Audio-");
|
|
else
|
|
av_log(avctx, AV_LOG_INFO, "Video-");
|
|
av_log(avctx, AV_LOG_INFO, "Capture filter saved successfully to file \"%s\".\n", filename);
|
|
}
|
|
|
|
r = IGraphBuilder_AddFilter(graph, (IBaseFilter *) capture_filter,
|
|
filter_name[devtype]);
|
|
if (r != S_OK) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not add capture filter to graph\n");
|
|
goto error;
|
|
}
|
|
|
|
ff_dshow_pin_AddRef(capture_filter->pin);
|
|
capture_pin = capture_filter->pin;
|
|
ctx->capture_pin[devtype] = capture_pin;
|
|
|
|
r = CoCreateInstance(&CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC_SERVER,
|
|
&IID_ICaptureGraphBuilder2, (void **) &graph_builder2);
|
|
if (r != S_OK) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not create CaptureGraphBuilder2\n");
|
|
goto error;
|
|
}
|
|
ICaptureGraphBuilder2_SetFiltergraph(graph_builder2, graph);
|
|
if (r != S_OK) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not set graph for CaptureGraphBuilder2\n");
|
|
goto error;
|
|
}
|
|
|
|
r = ICaptureGraphBuilder2_RenderStream(graph_builder2, NULL, NULL, (IUnknown *) device_pin, NULL /* no intermediate filter */,
|
|
(IBaseFilter *) capture_filter); /* connect pins, optionally insert intermediate filters like crossbar if necessary */
|
|
|
|
if (r != S_OK) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not RenderStream to connect pins\n");
|
|
goto error;
|
|
}
|
|
|
|
r = ff_dshow_try_setup_crossbar_options(graph_builder2, device_filter, devtype, avctx);
|
|
|
|
if (r != S_OK) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not setup CrossBar\n");
|
|
goto error;
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
error:
|
|
if (graph_builder2 != NULL)
|
|
ICaptureGraphBuilder2_Release(graph_builder2);
|
|
|
|
if (pers_stream)
|
|
IPersistStream_Release(pers_stream);
|
|
|
|
if (ifile_stream)
|
|
IStream_Release(ifile_stream);
|
|
|
|
if (ofile_stream)
|
|
IStream_Release(ofile_stream);
|
|
|
|
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)
|
|
{
|
|
struct dshow_ctx *ctx = avctx->priv_data;
|
|
AM_MEDIA_TYPE type;
|
|
AVCodecParameters *par;
|
|
AVStream *st;
|
|
int ret = AVERROR(EIO);
|
|
|
|
type.pbFormat = NULL;
|
|
|
|
st = avformat_new_stream(avctx, NULL);
|
|
if (!st) {
|
|
ret = AVERROR(ENOMEM);
|
|
goto error;
|
|
}
|
|
st->id = devtype;
|
|
|
|
ctx->capture_filter[devtype]->stream_index = st->index;
|
|
|
|
ff_dshow_pin_ConnectionMediaType(ctx->capture_pin[devtype], &type);
|
|
|
|
par = st->codecpar;
|
|
if (devtype == VideoDevice) {
|
|
BITMAPINFOHEADER *bih = NULL;
|
|
AVRational time_base;
|
|
|
|
if (IsEqualGUID(&type.formattype, &FORMAT_VideoInfo)) {
|
|
VIDEOINFOHEADER *v = (void *) type.pbFormat;
|
|
time_base = (AVRational) { v->AvgTimePerFrame, 10000000 };
|
|
bih = &v->bmiHeader;
|
|
} else if (IsEqualGUID(&type.formattype, &FORMAT_VideoInfo2)) {
|
|
VIDEOINFOHEADER2 *v = (void *) type.pbFormat;
|
|
time_base = (AVRational) { v->AvgTimePerFrame, 10000000 };
|
|
bih = &v->bmiHeader;
|
|
}
|
|
if (!bih) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not get media type.\n");
|
|
goto error;
|
|
}
|
|
|
|
st->avg_frame_rate = av_inv_q(time_base);
|
|
st->r_frame_rate = av_inv_q(time_base);
|
|
|
|
par->codec_type = AVMEDIA_TYPE_VIDEO;
|
|
par->width = bih->biWidth;
|
|
par->height = bih->biHeight;
|
|
par->codec_tag = bih->biCompression;
|
|
par->format = dshow_pixfmt(bih->biCompression, bih->biBitCount);
|
|
if (bih->biCompression == MKTAG('H', 'D', 'Y', 'C')) {
|
|
av_log(avctx, AV_LOG_DEBUG, "attempt to use full range for HDYC...\n");
|
|
par->color_range = AVCOL_RANGE_MPEG; // just in case it needs this...
|
|
}
|
|
if (par->format == AV_PIX_FMT_NONE) {
|
|
const AVCodecTag *const tags[] = { avformat_get_riff_video_tags(), NULL };
|
|
par->codec_id = av_codec_get_id(tags, bih->biCompression);
|
|
if (par->codec_id == AV_CODEC_ID_NONE) {
|
|
av_log(avctx, AV_LOG_ERROR, "Unknown compression type. "
|
|
"Please report type 0x%X.\n", (int) bih->biCompression);
|
|
ret = AVERROR_PATCHWELCOME;
|
|
goto error;
|
|
}
|
|
par->bits_per_coded_sample = bih->biBitCount;
|
|
} else {
|
|
par->codec_id = AV_CODEC_ID_RAWVIDEO;
|
|
if (bih->biCompression == BI_RGB || bih->biCompression == BI_BITFIELDS) {
|
|
par->bits_per_coded_sample = bih->biBitCount;
|
|
if (par->height < 0) {
|
|
par->height *= -1;
|
|
} else {
|
|
par->extradata = av_malloc(9 + AV_INPUT_BUFFER_PADDING_SIZE);
|
|
if (par->extradata) {
|
|
par->extradata_size = 9;
|
|
memcpy(par->extradata, "BottomUp", 9);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
WAVEFORMATEX *fx = NULL;
|
|
|
|
if (IsEqualGUID(&type.formattype, &FORMAT_WaveFormatEx)) {
|
|
fx = (void *) type.pbFormat;
|
|
}
|
|
if (!fx) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not get media type.\n");
|
|
goto error;
|
|
}
|
|
|
|
par->codec_type = AVMEDIA_TYPE_AUDIO;
|
|
par->format = sample_fmt_bits_per_sample(fx->wBitsPerSample);
|
|
par->codec_id = waveform_codec_id(par->format);
|
|
par->sample_rate = fx->nSamplesPerSec;
|
|
par->channels = fx->nChannels;
|
|
}
|
|
|
|
avpriv_set_pts_info(st, 64, 1, 10000000);
|
|
|
|
ret = 0;
|
|
|
|
error:
|
|
if (type.pbFormat)
|
|
CoTaskMemFree(type.pbFormat);
|
|
return ret;
|
|
}
|
|
|
|
static int parse_device_name(AVFormatContext *avctx)
|
|
{
|
|
struct dshow_ctx *ctx = avctx->priv_data;
|
|
char **device_name = ctx->device_name;
|
|
char *name = av_strdup(avctx->url);
|
|
char *tmp = name;
|
|
int ret = 1;
|
|
char *type;
|
|
|
|
while ((type = strtok(tmp, "="))) {
|
|
char *token = strtok(NULL, ":");
|
|
tmp = NULL;
|
|
|
|
if (!strcmp(type, "video")) {
|
|
device_name[0] = token;
|
|
} else if (!strcmp(type, "audio")) {
|
|
device_name[1] = token;
|
|
} else {
|
|
device_name[0] = NULL;
|
|
device_name[1] = NULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!device_name[0] && !device_name[1]) {
|
|
ret = 0;
|
|
} else {
|
|
if (device_name[0])
|
|
device_name[0] = av_strdup(device_name[0]);
|
|
if (device_name[1])
|
|
device_name[1] = av_strdup(device_name[1]);
|
|
}
|
|
|
|
av_free(name);
|
|
return ret;
|
|
}
|
|
|
|
static int dshow_read_header(AVFormatContext *avctx)
|
|
{
|
|
struct dshow_ctx *ctx = avctx->priv_data;
|
|
IGraphBuilder *graph = NULL;
|
|
ICreateDevEnum *devenum = NULL;
|
|
IMediaControl *control = NULL;
|
|
IMediaEvent *media_event = NULL;
|
|
HANDLE media_event_handle;
|
|
HANDLE proc;
|
|
int ret = AVERROR(EIO);
|
|
int r;
|
|
|
|
CoInitialize(0);
|
|
|
|
if (!ctx->list_devices && !parse_device_name(avctx)) {
|
|
av_log(avctx, AV_LOG_ERROR, "Malformed dshow input string.\n");
|
|
goto error;
|
|
}
|
|
|
|
ctx->video_codec_id = avctx->video_codec_id ? avctx->video_codec_id
|
|
: AV_CODEC_ID_RAWVIDEO;
|
|
if (ctx->pixel_format != AV_PIX_FMT_NONE) {
|
|
if (ctx->video_codec_id != AV_CODEC_ID_RAWVIDEO) {
|
|
av_log(avctx, AV_LOG_ERROR, "Pixel format may only be set when "
|
|
"video codec is not set or set to rawvideo\n");
|
|
ret = AVERROR(EINVAL);
|
|
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;
|
|
}
|
|
}
|
|
|
|
r = CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
|
|
&IID_IGraphBuilder, (void **) &graph);
|
|
if (r != S_OK) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not create capture graph.\n");
|
|
goto error;
|
|
}
|
|
ctx->graph = graph;
|
|
|
|
r = CoCreateInstance(&CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
|
|
&IID_ICreateDevEnum, (void **) &devenum);
|
|
if (r != S_OK) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not enumerate system devices.\n");
|
|
goto error;
|
|
}
|
|
|
|
if (ctx->list_devices) {
|
|
dshow_cycle_devices(avctx, devenum, VideoDevice, VideoSourceDevice, NULL, NULL, NULL);
|
|
dshow_cycle_devices(avctx, devenum, AudioDevice, AudioSourceDevice, NULL, NULL, NULL);
|
|
ret = AVERROR_EXIT;
|
|
goto error;
|
|
}
|
|
if (ctx->list_options) {
|
|
if (ctx->device_name[VideoDevice])
|
|
if ((r = dshow_list_device_options(avctx, devenum, VideoDevice, VideoSourceDevice))) {
|
|
ret = r;
|
|
goto error;
|
|
}
|
|
if (ctx->device_name[AudioDevice]) {
|
|
if (dshow_list_device_options(avctx, devenum, AudioDevice, AudioSourceDevice)) {
|
|
/* show audio options from combined video+audio sources as fallback */
|
|
if ((r = dshow_list_device_options(avctx, devenum, AudioDevice, VideoSourceDevice))) {
|
|
ret = r;
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
// don't exit yet, allow it to list crossbar options in dshow_open_device
|
|
}
|
|
if (ctx->device_name[VideoDevice]) {
|
|
if ((r = dshow_open_device(avctx, devenum, VideoDevice, VideoSourceDevice)) < 0 ||
|
|
(r = dshow_add_device(avctx, VideoDevice)) < 0) {
|
|
ret = r;
|
|
goto error;
|
|
}
|
|
}
|
|
if (ctx->device_name[AudioDevice]) {
|
|
if ((r = dshow_open_device(avctx, devenum, AudioDevice, AudioSourceDevice)) < 0 ||
|
|
(r = dshow_add_device(avctx, AudioDevice)) < 0) {
|
|
av_log(avctx, AV_LOG_INFO, "Searching for audio device within video devices for %s\n", ctx->device_name[AudioDevice]);
|
|
/* see if there's a video source with an audio pin with the given audio name */
|
|
if ((r = dshow_open_device(avctx, devenum, AudioDevice, VideoSourceDevice)) < 0 ||
|
|
(r = dshow_add_device(avctx, AudioDevice)) < 0) {
|
|
ret = r;
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
if (ctx->list_options) {
|
|
/* allow it to list crossbar options in dshow_open_device */
|
|
ret = AVERROR_EXIT;
|
|
goto error;
|
|
}
|
|
ctx->curbufsize[0] = 0;
|
|
ctx->curbufsize[1] = 0;
|
|
ctx->mutex = CreateMutex(NULL, 0, NULL);
|
|
if (!ctx->mutex) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not create Mutex\n");
|
|
goto error;
|
|
}
|
|
ctx->event[1] = CreateEvent(NULL, 1, 0, NULL);
|
|
if (!ctx->event[1]) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not create Event\n");
|
|
goto error;
|
|
}
|
|
|
|
r = IGraphBuilder_QueryInterface(graph, &IID_IMediaControl, (void **) &control);
|
|
if (r != S_OK) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not get media control.\n");
|
|
goto error;
|
|
}
|
|
ctx->control = control;
|
|
|
|
r = IGraphBuilder_QueryInterface(graph, &IID_IMediaEvent, (void **) &media_event);
|
|
if (r != S_OK) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not get media event.\n");
|
|
goto error;
|
|
}
|
|
ctx->media_event = media_event;
|
|
|
|
r = IMediaEvent_GetEventHandle(media_event, (void *) &media_event_handle);
|
|
if (r != S_OK) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not get media event handle.\n");
|
|
goto error;
|
|
}
|
|
proc = GetCurrentProcess();
|
|
r = DuplicateHandle(proc, media_event_handle, proc, &ctx->event[0],
|
|
0, 0, DUPLICATE_SAME_ACCESS);
|
|
if (!r) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not duplicate media event handle.\n");
|
|
goto error;
|
|
}
|
|
|
|
r = IMediaControl_Run(control);
|
|
if (r == S_FALSE) {
|
|
OAFilterState pfs;
|
|
r = IMediaControl_GetState(control, 0, &pfs);
|
|
}
|
|
if (r != S_OK) {
|
|
av_log(avctx, AV_LOG_ERROR, "Could not run graph (sometimes caused by a device already in use by other application)\n");
|
|
goto error;
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
error:
|
|
|
|
if (devenum)
|
|
ICreateDevEnum_Release(devenum);
|
|
|
|
if (ret < 0)
|
|
dshow_read_close(avctx);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Checks media events from DirectShow and returns -1 on error or EOF. Also
|
|
* purges all events that might be in the event queue to stop the trigger
|
|
* of event notification.
|
|
*/
|
|
static int dshow_check_event_queue(IMediaEvent *media_event)
|
|
{
|
|
LONG_PTR p1, p2;
|
|
long code;
|
|
int ret = 0;
|
|
|
|
while (IMediaEvent_GetEvent(media_event, &code, &p1, &p2, 0) != E_ABORT) {
|
|
if (code == EC_COMPLETE || code == EC_DEVICE_LOST || code == EC_ERRORABORT)
|
|
ret = -1;
|
|
IMediaEvent_FreeEventParams(media_event, code, p1, p2);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int dshow_read_packet(AVFormatContext *s, AVPacket *pkt)
|
|
{
|
|
struct dshow_ctx *ctx = s->priv_data;
|
|
PacketList *pktl = NULL;
|
|
|
|
while (!ctx->eof && !pktl) {
|
|
WaitForSingleObject(ctx->mutex, INFINITE);
|
|
pktl = ctx->pktl;
|
|
if (pktl) {
|
|
*pkt = pktl->pkt;
|
|
ctx->pktl = ctx->pktl->next;
|
|
av_free(pktl);
|
|
ctx->curbufsize[pkt->stream_index] -= pkt->size;
|
|
}
|
|
ResetEvent(ctx->event[1]);
|
|
ReleaseMutex(ctx->mutex);
|
|
if (!pktl) {
|
|
if (dshow_check_event_queue(ctx->media_event) < 0) {
|
|
ctx->eof = 1;
|
|
} else if (s->flags & AVFMT_FLAG_NONBLOCK) {
|
|
return AVERROR(EAGAIN);
|
|
} else {
|
|
WaitForMultipleObjects(2, ctx->event, 0, INFINITE);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ctx->eof ? AVERROR(EIO) : 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(requested_width), AV_OPT_TYPE_IMAGE_SIZE, {.str = NULL}, 0, 0, DEC },
|
|
{ "pixel_format", "set video pixel format", OFFSET(pixel_format), AV_OPT_TYPE_PIXEL_FMT, {.i64 = AV_PIX_FMT_NONE}, -1, INT_MAX, 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, {.i64 = 0}, 0, INT_MAX, DEC },
|
|
{ "sample_size", "set audio sample size", OFFSET(sample_size), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 16, DEC },
|
|
{ "channels", "set number of audio channels, such as 1 or 2", OFFSET(channels), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, DEC },
|
|
{ "audio_buffer_size", "set audio device buffer latency size in milliseconds (default is the device's default)", OFFSET(audio_buffer_size), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, DEC },
|
|
{ "list_devices", "list available devices", OFFSET(list_devices), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, DEC },
|
|
{ "list_options", "list available options for specified device", OFFSET(list_options), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, DEC },
|
|
{ "video_device_number", "set video device number for devices with same name (starts at 0)", OFFSET(video_device_number), AV_OPT_TYPE_INT, {.i64 = 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, {.i64 = 0}, 0, INT_MAX, DEC },
|
|
{ "video_pin_name", "select video capture pin by name", OFFSET(video_pin_name),AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, AV_OPT_FLAG_ENCODING_PARAM },
|
|
{ "audio_pin_name", "select audio capture pin by name", OFFSET(audio_pin_name),AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, AV_OPT_FLAG_ENCODING_PARAM },
|
|
{ "crossbar_video_input_pin_number", "set video input pin number for crossbar device", OFFSET(crossbar_video_input_pin_number), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, DEC },
|
|
{ "crossbar_audio_input_pin_number", "set audio input pin number for crossbar device", OFFSET(crossbar_audio_input_pin_number), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, DEC },
|
|
{ "show_video_device_dialog", "display property dialog for video capture device", OFFSET(show_video_device_dialog), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DEC },
|
|
{ "show_audio_device_dialog", "display property dialog for audio capture device", OFFSET(show_audio_device_dialog), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DEC },
|
|
{ "show_video_crossbar_connection_dialog", "display property dialog for crossbar connecting pins filter on video device", OFFSET(show_video_crossbar_connection_dialog), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DEC },
|
|
{ "show_audio_crossbar_connection_dialog", "display property dialog for crossbar connecting pins filter on audio device", OFFSET(show_audio_crossbar_connection_dialog), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DEC },
|
|
{ "show_analog_tv_tuner_dialog", "display property dialog for analog tuner filter", OFFSET(show_analog_tv_tuner_dialog), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DEC },
|
|
{ "show_analog_tv_tuner_audio_dialog", "display property dialog for analog tuner audio filter", OFFSET(show_analog_tv_tuner_audio_dialog), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DEC },
|
|
{ "audio_device_load", "load audio capture filter device (and properties) from file", OFFSET(audio_filter_load_file), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, DEC },
|
|
{ "audio_device_save", "save audio capture filter device (and properties) to file", OFFSET(audio_filter_save_file), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, DEC },
|
|
{ "video_device_load", "load video capture filter device (and properties) from file", OFFSET(video_filter_load_file), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, DEC },
|
|
{ "video_device_save", "save video capture filter device (and properties) to file", OFFSET(video_filter_save_file), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, DEC },
|
|
{ "use_video_device_timestamps", "use device instead of wallclock timestamps for video frames", OFFSET(use_video_device_timestamps), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DEC },
|
|
{ NULL },
|
|
};
|
|
|
|
static const AVClass dshow_class = {
|
|
.class_name = "dshow indev",
|
|
.item_name = av_default_item_name,
|
|
.option = options,
|
|
.version = LIBAVUTIL_VERSION_INT,
|
|
.category = AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT,
|
|
};
|
|
|
|
const AVInputFormat ff_dshow_demuxer = {
|
|
.name = "dshow",
|
|
.long_name = NULL_IF_CONFIG_SMALL("DirectShow capture"),
|
|
.priv_data_size = sizeof(struct dshow_ctx),
|
|
.read_header = dshow_read_header,
|
|
.read_packet = dshow_read_packet,
|
|
.read_close = dshow_read_close,
|
|
.get_device_list= dshow_get_device_list,
|
|
.flags = AVFMT_NOFILE | AVFMT_NOBINSEARCH | AVFMT_NOGENSEARCH | AVFMT_NO_BYTE_SEEK,
|
|
.priv_class = &dshow_class,
|
|
};
|