2005-06-04 11:49:02 +02:00
|
|
|
/*
|
2019-04-10 16:46:25 +02:00
|
|
|
* v4l2 backend to the VFW Capture filter
|
2005-06-04 11:49:02 +02:00
|
|
|
*
|
|
|
|
* Copyright 2005 Maarten Lankhorst
|
2019-04-10 16:46:25 +02:00
|
|
|
* Copyright 2019 Zebediah Figura
|
2005-06-04 11:49:02 +02:00
|
|
|
*
|
|
|
|
* This library 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.
|
|
|
|
*
|
|
|
|
* This library 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 this library; if not, write to the Free Software
|
2006-05-18 14:49:52 +02:00
|
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
2005-06-04 11:49:02 +02:00
|
|
|
*/
|
|
|
|
|
2019-04-10 16:46:25 +02:00
|
|
|
#define BIONIC_IOCTL_NO_SIGNEDNESS_OVERLOAD /* work around ioctl breakage on Android */
|
|
|
|
|
2005-06-04 11:49:02 +02:00
|
|
|
#include "config.h"
|
2005-08-03 23:25:10 +02:00
|
|
|
#include "wine/port.h"
|
2005-06-04 11:49:02 +02:00
|
|
|
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#ifdef HAVE_SYS_IOCTL_H
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_MMAN_H
|
|
|
|
#include <sys/mman.h>
|
|
|
|
#endif
|
2011-12-15 11:16:27 +01:00
|
|
|
#include <errno.h>
|
2005-06-04 11:49:02 +02:00
|
|
|
#ifdef HAVE_SYS_TIME_H
|
|
|
|
#include <sys/time.h>
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_ASM_TYPES_H
|
|
|
|
#include <asm/types.h>
|
|
|
|
#endif
|
2019-04-10 16:46:25 +02:00
|
|
|
#ifdef HAVE_LINUX_VIDEODEV2_H
|
|
|
|
#include <linux/videodev2.h>
|
2005-06-04 11:49:02 +02:00
|
|
|
#endif
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
|
|
#include <unistd.h>
|
|
|
|
#endif
|
|
|
|
|
2020-06-29 17:26:15 +02:00
|
|
|
#include "qcap_private.h"
|
2011-09-06 12:12:05 +02:00
|
|
|
|
2019-04-10 16:46:25 +02:00
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(qcap);
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2019-04-10 16:46:25 +02:00
|
|
|
#ifdef HAVE_LINUX_VIDEODEV2_H
|
|
|
|
|
|
|
|
WINE_DECLARE_DEBUG_CHANNEL(winediag);
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2010-01-28 23:27:12 +01:00
|
|
|
static typeof(open) *video_open = open;
|
|
|
|
static typeof(close) *video_close = close;
|
|
|
|
static typeof(ioctl) *video_ioctl = ioctl;
|
|
|
|
static typeof(read) *video_read = read;
|
|
|
|
|
2019-04-10 16:46:25 +02:00
|
|
|
static BOOL video_init(void)
|
2010-01-28 23:27:12 +01:00
|
|
|
{
|
2019-04-10 16:46:25 +02:00
|
|
|
#ifdef SONAME_LIBV4L2
|
2010-01-28 23:27:12 +01:00
|
|
|
static void *video_lib;
|
|
|
|
|
|
|
|
if (video_lib)
|
2019-04-10 16:46:25 +02:00
|
|
|
return TRUE;
|
2020-04-06 22:38:50 +02:00
|
|
|
if (!(video_lib = dlopen(SONAME_LIBV4L2, RTLD_NOW)))
|
2019-04-10 16:46:25 +02:00
|
|
|
return FALSE;
|
2020-04-06 22:38:50 +02:00
|
|
|
video_open = dlsym(video_lib, "v4l2_open");
|
|
|
|
video_close = dlsym(video_lib, "v4l2_close");
|
|
|
|
video_ioctl = dlsym(video_lib, "v4l2_ioctl");
|
|
|
|
video_read = dlsym(video_lib, "v4l2_read");
|
2019-04-10 16:46:25 +02:00
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
#else
|
|
|
|
return FALSE;
|
2010-01-28 23:27:12 +01:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2020-04-28 18:55:16 +02:00
|
|
|
struct caps
|
|
|
|
{
|
|
|
|
__u32 pixelformat;
|
|
|
|
AM_MEDIA_TYPE media_type;
|
|
|
|
VIDEOINFOHEADER video_info;
|
|
|
|
VIDEO_STREAM_CONFIG_CAPS config;
|
|
|
|
};
|
|
|
|
|
2020-06-29 17:26:16 +02:00
|
|
|
struct v4l_device
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2020-06-29 17:26:16 +02:00
|
|
|
struct video_capture_device d;
|
|
|
|
|
2020-04-28 18:55:17 +02:00
|
|
|
const struct caps *current_caps;
|
2020-04-28 18:55:16 +02:00
|
|
|
struct caps *caps;
|
|
|
|
LONG caps_count;
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2019-09-12 21:28:25 +02:00
|
|
|
struct strmbase_source *pin;
|
2005-06-04 11:49:02 +02:00
|
|
|
int fd, mmap;
|
2019-12-05 06:03:30 +01:00
|
|
|
FILTER_STATE state;
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2019-12-05 06:03:30 +01:00
|
|
|
HANDLE thread, run_event;
|
2005-06-04 11:49:02 +02:00
|
|
|
};
|
|
|
|
|
2020-06-29 17:26:16 +02:00
|
|
|
static inline struct v4l_device *v4l_device(struct video_capture_device *iface)
|
|
|
|
{
|
|
|
|
return CONTAINING_RECORD(iface, struct v4l_device, d);
|
|
|
|
}
|
|
|
|
|
2005-06-04 11:49:02 +02:00
|
|
|
static int xioctl(int fd, int request, void * arg)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
|
|
|
|
do {
|
2010-01-28 23:27:12 +01:00
|
|
|
r = video_ioctl (fd, request, arg);
|
2005-06-04 11:49:02 +02:00
|
|
|
} while (-1 == r && EINTR == errno);
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2020-06-29 17:26:16 +02:00
|
|
|
static void v4l_device_destroy(struct video_capture_device *iface)
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2020-06-29 17:26:16 +02:00
|
|
|
struct v4l_device *device = v4l_device(iface);
|
|
|
|
|
2020-04-28 18:55:16 +02:00
|
|
|
if (device->fd != -1)
|
|
|
|
video_close(device->fd);
|
|
|
|
if (device->caps_count)
|
|
|
|
heap_free(device->caps);
|
|
|
|
heap_free(device);
|
2005-06-04 11:49:02 +02:00
|
|
|
}
|
|
|
|
|
2020-06-29 17:26:16 +02:00
|
|
|
static const struct caps *find_caps(struct v4l_device *device, const AM_MEDIA_TYPE *mt)
|
2020-04-28 18:55:17 +02:00
|
|
|
{
|
|
|
|
const VIDEOINFOHEADER *video_info = (VIDEOINFOHEADER *)mt->pbFormat;
|
|
|
|
LONG index;
|
|
|
|
|
|
|
|
if (mt->cbFormat < sizeof(VIDEOINFOHEADER) || !video_info)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
for (index = 0; index < device->caps_count; index++)
|
|
|
|
{
|
|
|
|
struct caps *caps = &device->caps[index];
|
|
|
|
|
|
|
|
if (IsEqualGUID(&mt->formattype, &caps->media_type.formattype)
|
|
|
|
&& video_info->bmiHeader.biWidth == caps->video_info.bmiHeader.biWidth
|
|
|
|
&& video_info->bmiHeader.biHeight == caps->video_info.bmiHeader.biHeight)
|
|
|
|
return caps;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2020-06-29 17:26:16 +02:00
|
|
|
static HRESULT v4l_device_check_format(struct video_capture_device *iface, const AM_MEDIA_TYPE *mt)
|
2019-05-01 06:09:13 +02:00
|
|
|
{
|
2020-06-29 17:26:16 +02:00
|
|
|
struct v4l_device *device = v4l_device(iface);
|
|
|
|
|
2019-05-01 06:09:13 +02:00
|
|
|
TRACE("device %p, mt %p.\n", device, mt);
|
|
|
|
|
|
|
|
if (!mt)
|
|
|
|
return E_POINTER;
|
|
|
|
|
|
|
|
if (!IsEqualGUID(&mt->majortype, &MEDIATYPE_Video))
|
2020-04-28 18:55:17 +02:00
|
|
|
return E_FAIL;
|
|
|
|
|
|
|
|
if (find_caps(device, mt))
|
|
|
|
return S_OK;
|
2019-05-01 06:09:13 +02:00
|
|
|
|
2020-04-28 18:55:17 +02:00
|
|
|
return E_FAIL;
|
|
|
|
}
|
|
|
|
|
2020-06-29 17:26:16 +02:00
|
|
|
static BOOL set_caps(struct v4l_device *device, const struct caps *caps)
|
2020-04-28 18:55:17 +02:00
|
|
|
{
|
|
|
|
struct v4l2_format format = {0};
|
|
|
|
LONG width, height;
|
|
|
|
|
|
|
|
width = caps->video_info.bmiHeader.biWidth;
|
|
|
|
height = caps->video_info.bmiHeader.biHeight;
|
|
|
|
|
|
|
|
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
|
|
format.fmt.pix.pixelformat = caps->pixelformat;
|
|
|
|
format.fmt.pix.width = width;
|
|
|
|
format.fmt.pix.height = height;
|
|
|
|
if (xioctl(device->fd, VIDIOC_S_FMT, &format) == -1
|
|
|
|
|| format.fmt.pix.pixelformat != caps->pixelformat
|
|
|
|
|| format.fmt.pix.width != width
|
|
|
|
|| format.fmt.pix.height != height)
|
2019-05-01 06:09:13 +02:00
|
|
|
{
|
2020-04-28 18:55:17 +02:00
|
|
|
ERR("Failed to set pixel format: %s.\n", strerror(errno));
|
|
|
|
return FALSE;
|
2019-05-01 06:09:13 +02:00
|
|
|
}
|
|
|
|
|
2020-04-28 18:55:17 +02:00
|
|
|
device->current_caps = caps;
|
|
|
|
|
|
|
|
return TRUE;
|
2019-05-01 06:09:13 +02:00
|
|
|
}
|
|
|
|
|
2020-06-29 17:26:16 +02:00
|
|
|
static HRESULT v4l_device_set_format(struct video_capture_device *iface, const AM_MEDIA_TYPE *mt)
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2020-06-29 17:26:16 +02:00
|
|
|
struct v4l_device *device = v4l_device(iface);
|
2020-04-28 18:55:17 +02:00
|
|
|
const struct caps *caps;
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2020-04-28 18:55:17 +02:00
|
|
|
caps = find_caps(device, mt);
|
|
|
|
if (!caps)
|
|
|
|
return E_FAIL;
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2020-04-28 18:55:17 +02:00
|
|
|
if (device->current_caps == caps)
|
2005-06-04 11:49:02 +02:00
|
|
|
return S_OK;
|
|
|
|
|
2020-04-28 18:55:17 +02:00
|
|
|
if (!set_caps(device, caps))
|
2019-04-10 16:46:25 +02:00
|
|
|
return VFW_E_TYPE_NOT_ACCEPTED;
|
|
|
|
|
2005-06-04 11:49:02 +02:00
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
2020-06-29 17:26:17 +02:00
|
|
|
static HRESULT v4l_device_get_format(struct video_capture_device *iface, AM_MEDIA_TYPE *mt)
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2020-06-29 17:26:16 +02:00
|
|
|
struct v4l_device *device = v4l_device(iface);
|
|
|
|
|
2020-06-29 17:26:17 +02:00
|
|
|
return CopyMediaType(mt, &device->current_caps->media_type);
|
2005-06-04 11:49:02 +02:00
|
|
|
}
|
|
|
|
|
2019-04-10 16:46:25 +02:00
|
|
|
static __u32 v4l2_cid_from_qcap_property(VideoProcAmpProperty property)
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2019-04-10 16:46:25 +02:00
|
|
|
switch (property)
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
|
|
|
case VideoProcAmp_Brightness:
|
2019-04-10 16:46:25 +02:00
|
|
|
return V4L2_CID_BRIGHTNESS;
|
2005-06-04 11:49:02 +02:00
|
|
|
case VideoProcAmp_Contrast:
|
2019-04-10 16:46:25 +02:00
|
|
|
return V4L2_CID_CONTRAST;
|
2005-06-04 11:49:02 +02:00
|
|
|
case VideoProcAmp_Hue:
|
2019-04-10 16:46:25 +02:00
|
|
|
return V4L2_CID_HUE;
|
2005-06-04 11:49:02 +02:00
|
|
|
case VideoProcAmp_Saturation:
|
2019-04-10 16:46:25 +02:00
|
|
|
return V4L2_CID_SATURATION;
|
2005-06-04 11:49:02 +02:00
|
|
|
default:
|
2019-04-10 16:46:25 +02:00
|
|
|
FIXME("Unhandled property %d.\n", property);
|
|
|
|
return 0;
|
2005-06-04 11:49:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-29 17:26:16 +02:00
|
|
|
static HRESULT v4l_device_get_prop_range(struct video_capture_device *iface, VideoProcAmpProperty property,
|
2019-04-10 16:46:25 +02:00
|
|
|
LONG *min, LONG *max, LONG *step, LONG *default_value, LONG *flags)
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2020-06-29 17:26:16 +02:00
|
|
|
struct v4l_device *device = v4l_device(iface);
|
2019-04-10 16:46:25 +02:00
|
|
|
struct v4l2_queryctrl ctrl;
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2019-04-10 16:46:25 +02:00
|
|
|
ctrl.id = v4l2_cid_from_qcap_property(property);
|
|
|
|
|
|
|
|
if (xioctl(device->fd, VIDIOC_QUERYCTRL, &ctrl) == -1)
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2019-04-10 16:46:25 +02:00
|
|
|
WARN("Failed to query control: %s\n", strerror(errno));
|
|
|
|
return E_PROP_ID_UNSUPPORTED;
|
2005-06-04 11:49:02 +02:00
|
|
|
}
|
2019-04-10 16:46:25 +02:00
|
|
|
|
|
|
|
*min = ctrl.minimum;
|
|
|
|
*max = ctrl.maximum;
|
|
|
|
*step = ctrl.step;
|
|
|
|
*default_value = ctrl.default_value;
|
|
|
|
*flags = VideoProcAmp_Flags_Manual;
|
2005-06-04 11:49:02 +02:00
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
2020-06-29 17:26:16 +02:00
|
|
|
static HRESULT v4l_device_get_prop(struct video_capture_device *iface,
|
|
|
|
VideoProcAmpProperty property, LONG *value, LONG *flags)
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2020-06-29 17:26:16 +02:00
|
|
|
struct v4l_device *device = v4l_device(iface);
|
2019-04-10 16:46:25 +02:00
|
|
|
struct v4l2_control ctrl;
|
|
|
|
|
|
|
|
ctrl.id = v4l2_cid_from_qcap_property(property);
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2019-04-10 16:46:25 +02:00
|
|
|
if (xioctl(device->fd, VIDIOC_G_CTRL, &ctrl) == -1)
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2019-04-10 16:46:25 +02:00
|
|
|
WARN("Failed to get property: %s\n", strerror(errno));
|
|
|
|
return E_FAIL;
|
2005-06-04 11:49:02 +02:00
|
|
|
}
|
|
|
|
|
2019-04-10 16:46:25 +02:00
|
|
|
*value = ctrl.value;
|
|
|
|
*flags = VideoProcAmp_Flags_Manual;
|
|
|
|
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
2020-06-29 17:26:16 +02:00
|
|
|
static HRESULT v4l_device_set_prop(struct video_capture_device *iface,
|
|
|
|
VideoProcAmpProperty property, LONG value, LONG flags)
|
2019-04-10 16:46:25 +02:00
|
|
|
{
|
2020-06-29 17:26:16 +02:00
|
|
|
struct v4l_device *device = v4l_device(iface);
|
2019-04-10 16:46:25 +02:00
|
|
|
struct v4l2_control ctrl;
|
|
|
|
|
|
|
|
ctrl.id = v4l2_cid_from_qcap_property(property);
|
|
|
|
ctrl.value = value;
|
|
|
|
|
|
|
|
if (xioctl(device->fd, VIDIOC_S_CTRL, &ctrl) == -1)
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2019-04-10 16:46:25 +02:00
|
|
|
WARN("Failed to set property: %s\n", strerror(errno));
|
2005-06-04 11:49:02 +02:00
|
|
|
return E_FAIL;
|
|
|
|
}
|
2019-04-10 16:46:25 +02:00
|
|
|
|
2005-06-04 11:49:02 +02:00
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
2020-06-29 17:26:16 +02:00
|
|
|
static void reverse_image(struct v4l_device *device, LPBYTE output, const BYTE *input)
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2020-04-28 18:55:18 +02:00
|
|
|
int inoffset, outoffset, pitch;
|
|
|
|
UINT width, height, depth;
|
2020-04-28 18:55:17 +02:00
|
|
|
|
2020-04-28 18:55:18 +02:00
|
|
|
width = device->current_caps->video_info.bmiHeader.biWidth;
|
|
|
|
height = device->current_caps->video_info.bmiHeader.biHeight;
|
|
|
|
depth = device->current_caps->video_info.bmiHeader.biBitCount / 8;
|
2005-06-04 11:49:02 +02:00
|
|
|
/* the whole image needs to be reversed,
|
|
|
|
because the dibs are messed up in windows */
|
2020-04-28 18:55:18 +02:00
|
|
|
outoffset = width * height * depth;
|
|
|
|
pitch = width * depth;
|
|
|
|
inoffset = 0;
|
|
|
|
while (outoffset > 0)
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2020-04-28 18:55:18 +02:00
|
|
|
int x;
|
|
|
|
outoffset -= pitch;
|
|
|
|
for (x = 0; x < pitch; x++)
|
|
|
|
output[outoffset + x] = input[inoffset + x];
|
|
|
|
inoffset += pitch;
|
2005-06-04 11:49:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static DWORD WINAPI ReadThread(LPVOID lParam)
|
|
|
|
{
|
2020-06-29 17:26:16 +02:00
|
|
|
struct v4l_device *capBox = lParam;
|
2005-06-04 11:49:02 +02:00
|
|
|
HRESULT hr;
|
|
|
|
IMediaSample *pSample = NULL;
|
2010-05-19 12:55:49 +02:00
|
|
|
ULONG framecount = 0;
|
2019-04-11 05:32:03 +02:00
|
|
|
unsigned char *pTarget, *image_data;
|
|
|
|
unsigned int image_size;
|
2020-04-28 18:55:17 +02:00
|
|
|
UINT width, height, depth;
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2020-04-28 18:55:17 +02:00
|
|
|
width = capBox->current_caps->video_info.bmiHeader.biWidth;
|
|
|
|
height = capBox->current_caps->video_info.bmiHeader.biHeight;
|
|
|
|
depth = capBox->current_caps->video_info.bmiHeader.biBitCount / 8;
|
|
|
|
image_size = width * height * depth;
|
2019-04-11 05:32:03 +02:00
|
|
|
if (!(image_data = heap_alloc(image_size)))
|
2017-02-07 15:20:29 +01:00
|
|
|
{
|
2019-04-11 05:32:00 +02:00
|
|
|
ERR("Failed to allocate memory.\n");
|
2017-02-07 15:20:29 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2019-12-05 06:03:30 +01:00
|
|
|
while (capBox->state != State_Stopped)
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2019-12-05 06:03:30 +01:00
|
|
|
if (capBox->state == State_Paused)
|
|
|
|
WaitForSingleObject(capBox->run_event, INFINITE);
|
|
|
|
|
2019-06-26 03:38:11 +02:00
|
|
|
hr = BaseOutputPinImpl_GetDeliveryBuffer(capBox->pin, &pSample, NULL, NULL, 0);
|
2005-06-04 11:49:02 +02:00
|
|
|
if (SUCCEEDED(hr))
|
|
|
|
{
|
|
|
|
int len;
|
|
|
|
|
2020-04-28 18:55:18 +02:00
|
|
|
len = width * height * depth;
|
2005-06-04 11:49:02 +02:00
|
|
|
IMediaSample_SetActualDataLength(pSample, len);
|
|
|
|
|
|
|
|
len = IMediaSample_GetActualDataLength(pSample);
|
|
|
|
TRACE("Data length: %d KB\n", len / 1024);
|
|
|
|
|
|
|
|
IMediaSample_GetPointer(pSample, &pTarget);
|
2019-04-11 05:32:01 +02:00
|
|
|
|
2019-04-11 05:32:03 +02:00
|
|
|
while (video_read(capBox->fd, image_data, image_size) == -1)
|
2019-04-11 05:32:01 +02:00
|
|
|
{
|
|
|
|
if (errno != EAGAIN)
|
|
|
|
{
|
|
|
|
ERR("Failed to read frame: %s\n", strerror(errno));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-28 18:55:18 +02:00
|
|
|
reverse_image(capBox, pTarget, image_data);
|
2019-11-27 00:39:18 +01:00
|
|
|
hr = IMemInputPin_Receive(capBox->pin->pMemInputPin, pSample);
|
2010-05-19 12:55:49 +02:00
|
|
|
TRACE("%p -> Frame %u: %x\n", capBox, ++framecount, hr);
|
2005-06-04 11:49:02 +02:00
|
|
|
IMediaSample_Release(pSample);
|
|
|
|
}
|
|
|
|
if (FAILED(hr) && hr != VFW_E_NOT_CONNECTED)
|
|
|
|
{
|
2017-02-07 15:20:29 +01:00
|
|
|
TRACE("Return %x, stop IFilterGraph\n", hr);
|
|
|
|
break;
|
2005-06-04 11:49:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-11 05:32:03 +02:00
|
|
|
heap_free(image_data);
|
2005-06-04 11:49:02 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-06-29 17:26:16 +02:00
|
|
|
static void v4l_device_init_stream(struct video_capture_device *iface)
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2020-06-29 17:26:16 +02:00
|
|
|
struct v4l_device *device = v4l_device(iface);
|
2019-12-05 06:03:30 +01:00
|
|
|
ALLOCATOR_PROPERTIES req_props, ret_props;
|
2005-06-04 11:49:02 +02:00
|
|
|
HRESULT hr;
|
|
|
|
|
2019-12-05 06:03:30 +01:00
|
|
|
req_props.cBuffers = 3;
|
2020-04-28 18:55:18 +02:00
|
|
|
req_props.cbBuffer = device->current_caps->video_info.bmiHeader.biWidth * device->current_caps->video_info.bmiHeader.biHeight;
|
2020-04-28 18:55:17 +02:00
|
|
|
req_props.cbBuffer = (req_props.cbBuffer * device->current_caps->video_info.bmiHeader.biBitCount) / 8;
|
2019-12-05 06:03:30 +01:00
|
|
|
req_props.cbAlign = 1;
|
|
|
|
req_props.cbPrefix = 0;
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2019-12-05 06:03:30 +01:00
|
|
|
hr = IMemAllocator_SetProperties(device->pin->pAllocator, &req_props, &ret_props);
|
|
|
|
if (FAILED(hr))
|
|
|
|
ERR("Failed to set allocator properties (buffer size %u), hr %#x.\n", req_props.cbBuffer, hr);
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2019-12-05 06:03:30 +01:00
|
|
|
if (SUCCEEDED(hr))
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2019-12-05 06:03:30 +01:00
|
|
|
if (FAILED(hr = IMemAllocator_Commit(device->pin->pAllocator)))
|
|
|
|
ERR("Failed to commit allocator, hr %#x.\n", hr);
|
2005-06-04 11:49:02 +02:00
|
|
|
}
|
|
|
|
|
2019-12-05 06:03:30 +01:00
|
|
|
device->state = State_Paused;
|
|
|
|
device->thread = CreateThread(NULL, 0, ReadThread, device, 0, NULL);
|
2005-06-04 11:49:02 +02:00
|
|
|
}
|
|
|
|
|
2020-06-29 17:26:16 +02:00
|
|
|
static void v4l_device_start_stream(struct video_capture_device *iface)
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2020-06-29 17:26:16 +02:00
|
|
|
struct v4l_device *device = v4l_device(iface);
|
2019-12-05 06:03:30 +01:00
|
|
|
device->state = State_Running;
|
|
|
|
SetEvent(device->run_event);
|
2005-06-04 11:49:02 +02:00
|
|
|
}
|
|
|
|
|
2020-06-29 17:26:16 +02:00
|
|
|
static void v4l_device_stop_stream(struct video_capture_device *iface)
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2020-06-29 17:26:16 +02:00
|
|
|
struct v4l_device *device = v4l_device(iface);
|
2019-12-05 06:03:30 +01:00
|
|
|
device->state = State_Paused;
|
|
|
|
ResetEvent(device->run_event);
|
|
|
|
}
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2020-06-29 17:26:16 +02:00
|
|
|
static void v4l_device_cleanup_stream(struct video_capture_device *iface)
|
2019-12-05 06:03:30 +01:00
|
|
|
{
|
2020-06-29 17:26:16 +02:00
|
|
|
struct v4l_device *device = v4l_device(iface);
|
2019-12-05 06:03:30 +01:00
|
|
|
HRESULT hr;
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2019-12-05 06:03:30 +01:00
|
|
|
device->state = State_Stopped;
|
|
|
|
WaitForSingleObject(device->thread, INFINITE);
|
|
|
|
CloseHandle(device->thread);
|
|
|
|
device->thread = NULL;
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2019-12-05 06:03:30 +01:00
|
|
|
hr = IMemAllocator_Decommit(device->pin->pAllocator);
|
|
|
|
if (hr != S_OK && hr != VFW_E_NOT_COMMITTED)
|
|
|
|
ERR("Failed to decommit allocator, hr %#x.\n", hr);
|
2005-06-04 11:49:02 +02:00
|
|
|
}
|
|
|
|
|
2020-04-28 18:55:16 +02:00
|
|
|
|
|
|
|
static void fill_caps(__u32 pixelformat, __u32 width, __u32 height,
|
|
|
|
__u32 max_fps, __u32 min_fps, struct caps *caps)
|
|
|
|
{
|
|
|
|
LONG depth = 24;
|
|
|
|
|
2020-05-12 15:36:10 +02:00
|
|
|
memset(caps, 0, sizeof(*caps));
|
2020-04-28 18:55:16 +02:00
|
|
|
caps->video_info.dwBitRate = width * height * depth * max_fps;
|
|
|
|
caps->video_info.bmiHeader.biSize = sizeof(caps->video_info.bmiHeader);
|
|
|
|
caps->video_info.bmiHeader.biWidth = width;
|
|
|
|
caps->video_info.bmiHeader.biHeight = height;
|
|
|
|
caps->video_info.bmiHeader.biPlanes = 1;
|
|
|
|
caps->video_info.bmiHeader.biBitCount = depth;
|
|
|
|
caps->video_info.bmiHeader.biCompression = BI_RGB;
|
|
|
|
caps->video_info.bmiHeader.biSizeImage = width * height * depth / 8;
|
|
|
|
caps->media_type.majortype = MEDIATYPE_Video;
|
|
|
|
caps->media_type.subtype = MEDIASUBTYPE_RGB24;
|
|
|
|
caps->media_type.bFixedSizeSamples = TRUE;
|
|
|
|
caps->media_type.bTemporalCompression = FALSE;
|
|
|
|
caps->media_type.lSampleSize = width * height * depth / 8;
|
|
|
|
caps->media_type.formattype = FORMAT_VideoInfo;
|
|
|
|
caps->media_type.pUnk = NULL;
|
|
|
|
caps->media_type.cbFormat = sizeof(VIDEOINFOHEADER);
|
|
|
|
/* We reallocate the caps array, so pbFormat has to be set after all caps
|
|
|
|
* have been enumerated. */
|
|
|
|
caps->config.MaxFrameInterval = 10000000 * max_fps;
|
|
|
|
caps->config.MinFrameInterval = 10000000 * min_fps;
|
|
|
|
caps->config.MaxOutputSize.cx = width;
|
|
|
|
caps->config.MaxOutputSize.cy = height;
|
|
|
|
caps->config.MinOutputSize.cx = width;
|
|
|
|
caps->config.MinOutputSize.cy = height;
|
|
|
|
caps->config.guid = FORMAT_VideoInfo;
|
|
|
|
caps->config.MinBitsPerSecond = width * height * depth * min_fps;
|
|
|
|
caps->config.MaxBitsPerSecond = width * height * depth * max_fps;
|
|
|
|
caps->pixelformat = pixelformat;
|
|
|
|
}
|
|
|
|
|
2020-06-29 17:26:16 +02:00
|
|
|
static HRESULT v4l_device_get_caps(struct video_capture_device *iface, LONG index,
|
|
|
|
AM_MEDIA_TYPE **type, VIDEO_STREAM_CONFIG_CAPS *vscc)
|
|
|
|
{
|
|
|
|
struct v4l_device *device = v4l_device(iface);
|
|
|
|
|
|
|
|
if (index >= device->caps_count)
|
|
|
|
return S_FALSE;
|
|
|
|
|
|
|
|
*type = CreateMediaType(&device->caps[index].media_type);
|
|
|
|
if (!*type)
|
|
|
|
return E_OUTOFMEMORY;
|
|
|
|
|
|
|
|
if (vscc)
|
|
|
|
memcpy(vscc, &device->caps[index].config, sizeof(VIDEO_STREAM_CONFIG_CAPS));
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static LONG v4l_device_get_caps_count(struct video_capture_device *iface)
|
|
|
|
{
|
|
|
|
struct v4l_device *device = v4l_device(iface);
|
|
|
|
|
|
|
|
return device->caps_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct video_capture_device_ops v4l_device_ops =
|
|
|
|
{
|
|
|
|
.destroy = v4l_device_destroy,
|
|
|
|
.check_format = v4l_device_check_format,
|
|
|
|
.set_format = v4l_device_set_format,
|
|
|
|
.get_format = v4l_device_get_format,
|
|
|
|
.get_caps = v4l_device_get_caps,
|
|
|
|
.get_caps_count = v4l_device_get_caps_count,
|
|
|
|
.get_prop_range = v4l_device_get_prop_range,
|
|
|
|
.get_prop = v4l_device_get_prop,
|
|
|
|
.set_prop = v4l_device_set_prop,
|
|
|
|
.init_stream = v4l_device_init_stream,
|
|
|
|
.start_stream = v4l_device_start_stream,
|
|
|
|
.stop_stream = v4l_device_stop_stream,
|
|
|
|
.cleanup_stream = v4l_device_cleanup_stream,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct video_capture_device *v4l_device_create(struct strmbase_source *pin, USHORT card)
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2020-04-28 18:55:16 +02:00
|
|
|
struct v4l2_frmsizeenum frmsize = {0};
|
2019-04-10 16:46:25 +02:00
|
|
|
struct v4l2_capability caps = {{0}};
|
2019-04-10 16:46:27 +02:00
|
|
|
struct v4l2_format format = {0};
|
2020-06-29 17:26:16 +02:00
|
|
|
struct v4l_device *device;
|
2019-04-10 16:46:25 +02:00
|
|
|
BOOL have_libv4l2;
|
|
|
|
char path[20];
|
2020-04-28 18:55:16 +02:00
|
|
|
int fd, i;
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2019-04-10 16:46:25 +02:00
|
|
|
have_libv4l2 = video_init();
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2020-04-28 18:55:16 +02:00
|
|
|
if (!(device = heap_alloc_zero(sizeof(*device))))
|
2019-04-10 16:46:25 +02:00
|
|
|
return NULL;
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2019-04-10 16:46:25 +02:00
|
|
|
sprintf(path, "/dev/video%i", card);
|
|
|
|
TRACE("Opening device %s.\n", path);
|
2017-05-10 20:08:57 +02:00
|
|
|
#ifdef O_CLOEXEC
|
2019-04-10 16:46:25 +02:00
|
|
|
if ((fd = video_open(path, O_RDWR | O_NONBLOCK | O_CLOEXEC)) == -1 && errno == EINVAL)
|
2017-05-10 20:08:57 +02:00
|
|
|
#endif
|
2019-04-10 16:46:25 +02:00
|
|
|
fd = video_open(path, O_RDWR | O_NONBLOCK);
|
|
|
|
if (fd == -1)
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2019-04-10 16:46:25 +02:00
|
|
|
WARN("Failed to open video device: %s\n", strerror(errno));
|
2005-06-04 11:49:02 +02:00
|
|
|
goto error;
|
|
|
|
}
|
2019-04-10 16:46:25 +02:00
|
|
|
fcntl(fd, F_SETFD, FD_CLOEXEC); /* in case O_CLOEXEC isn't supported */
|
|
|
|
device->fd = fd;
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2019-04-10 16:46:25 +02:00
|
|
|
if (xioctl(fd, VIDIOC_QUERYCAP, &caps) == -1)
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2019-04-10 16:46:25 +02:00
|
|
|
WARN("Failed to query device capabilities: %s\n", strerror(errno));
|
2005-06-04 11:49:02 +02:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2019-04-27 21:07:00 +02:00
|
|
|
#ifdef V4L2_CAP_DEVICE_CAPS
|
2019-04-20 19:06:56 +02:00
|
|
|
if (caps.capabilities & V4L2_CAP_DEVICE_CAPS)
|
|
|
|
caps.capabilities = caps.device_caps;
|
2019-04-27 21:07:00 +02:00
|
|
|
#endif
|
2019-04-20 19:06:56 +02:00
|
|
|
|
2019-04-10 16:46:25 +02:00
|
|
|
if (!(caps.capabilities & V4L2_CAP_VIDEO_CAPTURE))
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2019-04-10 16:46:25 +02:00
|
|
|
WARN("Device does not support single-planar video capture.\n");
|
2005-06-04 11:49:02 +02:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2019-04-10 16:46:25 +02:00
|
|
|
if (!(caps.capabilities & V4L2_CAP_READWRITE))
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2019-04-10 16:46:25 +02:00
|
|
|
WARN("Device does not support read().\n");
|
|
|
|
if (!have_libv4l2)
|
|
|
|
#ifdef SONAME_LIBV4L2
|
|
|
|
ERR_(winediag)("Reading from %s requires libv4l2, but it could not be loaded.\n", path);
|
|
|
|
#else
|
|
|
|
ERR_(winediag)("Reading from %s requires libv4l2, but Wine was compiled without libv4l2 support.\n", path);
|
|
|
|
#endif
|
2005-06-04 11:49:02 +02:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2019-04-10 16:46:27 +02:00
|
|
|
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
|
|
if (xioctl(fd, VIDIOC_G_FMT, &format) == -1)
|
|
|
|
{
|
|
|
|
ERR("Failed to get device format: %s\n", strerror(errno));
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2020-04-28 18:55:16 +02:00
|
|
|
format.fmt.pix.pixelformat = V4L2_PIX_FMT_BGR24;
|
|
|
|
if (xioctl(fd, VIDIOC_TRY_FMT, &format) == -1
|
|
|
|
|| format.fmt.pix.pixelformat != V4L2_PIX_FMT_BGR24)
|
|
|
|
{
|
|
|
|
ERR("This device doesn't support V4L2_PIX_FMT_BGR24 format.\n");
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
frmsize.pixel_format = V4L2_PIX_FMT_BGR24;
|
|
|
|
while (xioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) != -1)
|
|
|
|
{
|
|
|
|
struct v4l2_frmivalenum frmival = {0};
|
|
|
|
__u32 max_fps = 30, min_fps = 30;
|
|
|
|
struct caps *new_caps;
|
|
|
|
|
|
|
|
frmival.pixel_format = format.fmt.pix.pixelformat;
|
|
|
|
if (frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE)
|
|
|
|
{
|
|
|
|
frmival.width = frmsize.discrete.width;
|
|
|
|
frmival.height = frmsize.discrete.height;
|
|
|
|
}
|
|
|
|
else if (frmsize.type == V4L2_FRMSIZE_TYPE_STEPWISE)
|
|
|
|
{
|
|
|
|
frmival.width = frmsize.stepwise.max_width;
|
|
|
|
frmival.height = frmsize.stepwise.min_height;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
FIXME("Unhandled frame size type: %d.\n", frmsize.type);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (xioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival) != -1)
|
|
|
|
{
|
|
|
|
if (frmival.type == V4L2_FRMIVAL_TYPE_DISCRETE)
|
|
|
|
{
|
|
|
|
max_fps = frmival.discrete.denominator / frmival.discrete.numerator;
|
|
|
|
min_fps = max_fps;
|
|
|
|
}
|
|
|
|
else if (frmival.type == V4L2_FRMIVAL_TYPE_STEPWISE
|
|
|
|
|| frmival.type == V4L2_FRMIVAL_TYPE_CONTINUOUS)
|
|
|
|
{
|
|
|
|
max_fps = frmival.stepwise.max.denominator / frmival.stepwise.max.numerator;
|
|
|
|
min_fps = frmival.stepwise.min.denominator / frmival.stepwise.min.numerator;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
ERR("Failed to get fps: %s.\n", strerror(errno));
|
|
|
|
|
|
|
|
new_caps = heap_realloc(device->caps, (device->caps_count + 1) * sizeof(*device->caps));
|
|
|
|
if (!new_caps)
|
|
|
|
goto error;
|
|
|
|
device->caps = new_caps;
|
|
|
|
fill_caps(format.fmt.pix.pixelformat, frmsize.discrete.width, frmsize.discrete.height,
|
|
|
|
max_fps, min_fps, &device->caps[device->caps_count]);
|
|
|
|
device->caps_count++;
|
|
|
|
|
|
|
|
frmsize.index++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We reallocate the caps array, so we have to delay setting pbFormat. */
|
|
|
|
for (i = 0; i < device->caps_count; ++i)
|
|
|
|
device->caps[i].media_type.pbFormat = (BYTE *)&device->caps[i].video_info;
|
|
|
|
|
2020-04-28 18:55:17 +02:00
|
|
|
if (!set_caps(device, &device->caps[0]))
|
2019-04-10 16:46:27 +02:00
|
|
|
{
|
|
|
|
ERR("Failed to set pixel format: %s\n", strerror(errno));
|
|
|
|
if (!have_libv4l2)
|
|
|
|
ERR_(winediag)("You may need libv4l2 to use this device.\n");
|
2005-06-04 11:49:02 +02:00
|
|
|
goto error;
|
2019-04-10 16:46:27 +02:00
|
|
|
}
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2020-06-29 17:26:16 +02:00
|
|
|
device->d.ops = &v4l_device_ops;
|
2019-06-26 03:38:11 +02:00
|
|
|
device->pin = pin;
|
2019-12-05 06:03:30 +01:00
|
|
|
device->state = State_Stopped;
|
|
|
|
device->run_event = CreateEventW(NULL, TRUE, FALSE, NULL);
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2020-04-28 18:55:17 +02:00
|
|
|
TRACE("Format: %d bpp - %dx%d.\n", device->current_caps->video_info.bmiHeader.biBitCount,
|
|
|
|
device->current_caps->video_info.bmiHeader.biWidth,
|
|
|
|
device->current_caps->video_info.bmiHeader.biHeight);
|
2005-06-04 11:49:02 +02:00
|
|
|
|
2020-06-29 17:26:16 +02:00
|
|
|
return &device->d;
|
2005-06-04 11:49:02 +02:00
|
|
|
|
|
|
|
error:
|
2020-06-29 17:26:16 +02:00
|
|
|
v4l_device_destroy(&device->d);
|
2005-06-04 11:49:02 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
2020-06-29 17:26:16 +02:00
|
|
|
struct video_capture_device *v4l_device_create(struct strmbase_source *pin, USHORT card)
|
2005-06-04 11:49:02 +02:00
|
|
|
{
|
2020-06-29 17:26:16 +02:00
|
|
|
ERR("v4l2 was not present at compilation time.\n");
|
2005-06-04 11:49:02 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2013-03-06 15:11:40 +01:00
|
|
|
#endif /* defined(VIDIOCMCAPTURE) */
|