2 * v4l2 backend to the VFW Capture filter
4 * Copyright 2005 Maarten Lankhorst
5 * Copyright 2019 Zebediah Figura
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 #define BIONIC_IOCTL_NO_SIGNEDNESS_OVERLOAD /* work around ioctl breakage on Android */
25 #include "wine/port.h"
32 #ifdef HAVE_SYS_IOCTL_H
33 #include <sys/ioctl.h>
35 #ifdef HAVE_SYS_MMAN_H
39 #ifdef HAVE_SYS_TIME_H
42 #ifdef HAVE_ASM_TYPES_H
43 #include <asm/types.h>
45 #ifdef HAVE_LINUX_VIDEODEV2_H
46 #include <linux/videodev2.h>
60 #include "wine/debug.h"
61 #include "wine/library.h"
63 #include "qcap_main.h"
66 WINE_DEFAULT_DEBUG_CHANNEL(qcap
);
68 #ifdef HAVE_LINUX_VIDEODEV2_H
70 WINE_DECLARE_DEBUG_CHANNEL(winediag
);
72 static typeof(open
) *video_open
= open
;
73 static typeof(close
) *video_close
= close
;
74 static typeof(ioctl
) *video_ioctl
= ioctl
;
75 static typeof(read
) *video_read
= read
;
77 static BOOL
video_init(void)
80 static void *video_lib
;
84 if (!(video_lib
= wine_dlopen(SONAME_LIBV4L2
, RTLD_NOW
, NULL
, 0)))
86 video_open
= wine_dlsym(video_lib
, "v4l2_open", NULL
, 0);
87 video_close
= wine_dlsym(video_lib
, "v4l2_close", NULL
, 0);
88 video_ioctl
= wine_dlsym(video_lib
, "v4l2_ioctl", NULL
, 0);
89 video_read
= wine_dlsym(video_lib
, "v4l2_read", NULL
, 0);
99 UINT width
, height
, bitDepth
, fps
, outputwidth
, outputheight
;
102 CRITICAL_SECTION CritSect
;
104 struct strmbase_source
*pin
;
106 BOOL iscommitted
, stopped
;
111 static int xioctl(int fd
, int request
, void * arg
)
116 r
= video_ioctl (fd
, request
, arg
);
117 } while (-1 == r
&& EINTR
== errno
);
122 HRESULT
qcap_driver_destroy(Capture
*capBox
)
124 TRACE("%p\n", capBox
);
126 if( capBox
->fd
!= -1 )
127 video_close(capBox
->fd
);
128 capBox
->CritSect
.DebugInfo
->Spare
[0] = 0;
129 DeleteCriticalSection(&capBox
->CritSect
);
130 CoTaskMemFree(capBox
);
134 HRESULT
qcap_driver_check_format(Capture
*device
, const AM_MEDIA_TYPE
*mt
)
137 TRACE("device %p, mt %p.\n", device
, mt
);
138 dump_AM_MEDIA_TYPE(mt
);
143 if (!IsEqualGUID(&mt
->majortype
, &MEDIATYPE_Video
))
146 if (IsEqualGUID(&mt
->formattype
, &FORMAT_VideoInfo
) && mt
->pbFormat
147 && mt
->cbFormat
>= sizeof(VIDEOINFOHEADER
))
149 VIDEOINFOHEADER
*vih
= (VIDEOINFOHEADER
*)mt
->pbFormat
;
150 if (vih
->bmiHeader
.biBitCount
== 24 && vih
->bmiHeader
.biCompression
== BI_RGB
)
154 FIXME("Unsupported compression %#x, bpp %u.\n", vih
->bmiHeader
.biCompression
,
155 vih
->bmiHeader
.biBitCount
);
160 hr
= VFW_E_INVALIDMEDIATYPE
;
165 HRESULT
qcap_driver_set_format(Capture
*device
, AM_MEDIA_TYPE
*mt
)
167 struct v4l2_format format
= {0};
168 int newheight
, newwidth
;
169 VIDEOINFOHEADER
*vih
;
173 if (FAILED(hr
= qcap_driver_check_format(device
, mt
)))
175 vih
= (VIDEOINFOHEADER
*)mt
->pbFormat
;
177 newwidth
= vih
->bmiHeader
.biWidth
;
178 newheight
= vih
->bmiHeader
.biHeight
;
180 if (device
->height
== newheight
&& device
->width
== newwidth
)
183 format
.type
= V4L2_BUF_TYPE_VIDEO_CAPTURE
;
184 if (xioctl(fd
, VIDIOC_G_FMT
, &format
) == -1)
186 ERR("Failed to get current format: %s\n", strerror(errno
));
187 return VFW_E_TYPE_NOT_ACCEPTED
;
190 format
.fmt
.pix
.width
= newwidth
;
191 format
.fmt
.pix
.height
= newheight
;
193 if (!xioctl(fd
, VIDIOC_S_FMT
, &format
)
194 && format
.fmt
.pix
.width
== newwidth
195 && format
.fmt
.pix
.height
== newheight
)
197 device
->width
= newwidth
;
198 device
->height
= newheight
;
199 device
->swresize
= FALSE
;
203 TRACE("Using software resize: %dx%d -> %dx%d.\n",
204 format
.fmt
.pix
.width
, format
.fmt
.pix
.height
, device
->width
, device
->height
);
205 device
->swresize
= TRUE
;
207 device
->outputwidth
= format
.fmt
.pix
.width
;
208 device
->outputheight
= format
.fmt
.pix
.height
;
212 HRESULT
qcap_driver_get_format(const Capture
*capBox
, AM_MEDIA_TYPE
** mT
)
216 mT
[0] = CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE
));
218 return E_OUTOFMEMORY
;
219 vi
= CoTaskMemAlloc(sizeof(VIDEOINFOHEADER
));
220 mT
[0]->cbFormat
= sizeof(VIDEOINFOHEADER
);
223 CoTaskMemFree(mT
[0]);
225 return E_OUTOFMEMORY
;
227 mT
[0]->majortype
= MEDIATYPE_Video
;
228 mT
[0]->subtype
= MEDIASUBTYPE_RGB24
;
229 mT
[0]->formattype
= FORMAT_VideoInfo
;
230 mT
[0]->bFixedSizeSamples
= TRUE
;
231 mT
[0]->bTemporalCompression
= FALSE
;
233 mT
[0]->lSampleSize
= capBox
->outputwidth
* capBox
->outputheight
* capBox
->bitDepth
/ 8;
234 TRACE("Output format: %dx%d - %d bits = %u KB\n", capBox
->outputwidth
,
235 capBox
->outputheight
, capBox
->bitDepth
, mT
[0]->lSampleSize
/1024);
236 vi
->rcSource
.left
= 0; vi
->rcSource
.top
= 0;
237 vi
->rcTarget
.left
= 0; vi
->rcTarget
.top
= 0;
238 vi
->rcSource
.right
= capBox
->width
; vi
->rcSource
.bottom
= capBox
->height
;
239 vi
->rcTarget
.right
= capBox
->outputwidth
; vi
->rcTarget
.bottom
= capBox
->outputheight
;
240 vi
->dwBitRate
= capBox
->fps
* mT
[0]->lSampleSize
;
241 vi
->dwBitErrorRate
= 0;
242 vi
->AvgTimePerFrame
= (LONGLONG
)10000000.0 / (LONGLONG
)capBox
->fps
;
243 vi
->bmiHeader
.biSize
= 40;
244 vi
->bmiHeader
.biWidth
= capBox
->outputwidth
;
245 vi
->bmiHeader
.biHeight
= capBox
->outputheight
;
246 vi
->bmiHeader
.biPlanes
= 1;
247 vi
->bmiHeader
.biBitCount
= 24;
248 vi
->bmiHeader
.biCompression
= BI_RGB
;
249 vi
->bmiHeader
.biSizeImage
= mT
[0]->lSampleSize
;
250 vi
->bmiHeader
.biClrUsed
= vi
->bmiHeader
.biClrImportant
= 0;
251 vi
->bmiHeader
.biXPelsPerMeter
= 100;
252 vi
->bmiHeader
.biYPelsPerMeter
= 100;
253 mT
[0]->pbFormat
= (void *)vi
;
254 dump_AM_MEDIA_TYPE(mT
[0]);
258 static __u32
v4l2_cid_from_qcap_property(VideoProcAmpProperty property
)
262 case VideoProcAmp_Brightness
:
263 return V4L2_CID_BRIGHTNESS
;
264 case VideoProcAmp_Contrast
:
265 return V4L2_CID_CONTRAST
;
266 case VideoProcAmp_Hue
:
268 case VideoProcAmp_Saturation
:
269 return V4L2_CID_SATURATION
;
271 FIXME("Unhandled property %d.\n", property
);
276 HRESULT
qcap_driver_get_prop_range(Capture
*device
, VideoProcAmpProperty property
,
277 LONG
*min
, LONG
*max
, LONG
*step
, LONG
*default_value
, LONG
*flags
)
279 struct v4l2_queryctrl ctrl
;
281 ctrl
.id
= v4l2_cid_from_qcap_property(property
);
283 if (xioctl(device
->fd
, VIDIOC_QUERYCTRL
, &ctrl
) == -1)
285 WARN("Failed to query control: %s\n", strerror(errno
));
286 return E_PROP_ID_UNSUPPORTED
;
292 *default_value
= ctrl
.default_value
;
293 *flags
= VideoProcAmp_Flags_Manual
;
297 HRESULT
qcap_driver_get_prop(Capture
*device
, VideoProcAmpProperty property
,
298 LONG
*value
, LONG
*flags
)
300 struct v4l2_control ctrl
;
302 ctrl
.id
= v4l2_cid_from_qcap_property(property
);
304 if (xioctl(device
->fd
, VIDIOC_G_CTRL
, &ctrl
) == -1)
306 WARN("Failed to get property: %s\n", strerror(errno
));
311 *flags
= VideoProcAmp_Flags_Manual
;
316 HRESULT
qcap_driver_set_prop(Capture
*device
, VideoProcAmpProperty property
,
317 LONG value
, LONG flags
)
319 struct v4l2_control ctrl
;
321 ctrl
.id
= v4l2_cid_from_qcap_property(property
);
324 if (xioctl(device
->fd
, VIDIOC_S_CTRL
, &ctrl
) == -1)
326 WARN("Failed to set property: %s\n", strerror(errno
));
333 static void Resize(const Capture
* capBox
, LPBYTE output
, const BYTE
*input
)
335 /* the whole image needs to be reversed,
336 because the dibs are messed up in windows */
337 if (!capBox
->swresize
)
339 int depth
= capBox
->bitDepth
/ 8;
340 int inoffset
= 0, outoffset
= capBox
->height
* capBox
->width
* depth
;
341 int ow
= capBox
->width
* depth
;
342 while (outoffset
> 0)
346 for (x
= 0; x
< ow
; x
++)
347 output
[outoffset
+ x
] = input
[inoffset
+ x
];
354 HBITMAP bmp_s
, bmp_d
;
355 int depth
= capBox
->bitDepth
/ 8;
356 int inoffset
= 0, outoffset
= (capBox
->outputheight
) * capBox
->outputwidth
* depth
;
357 int ow
= capBox
->outputwidth
* depth
;
360 /* FIXME: Improve software resizing: add error checks and optimize */
362 myarray
= CoTaskMemAlloc(capBox
->outputwidth
* capBox
->outputheight
* depth
);
363 dc_s
= CreateCompatibleDC(NULL
);
364 dc_d
= CreateCompatibleDC(NULL
);
365 bmp_s
= CreateBitmap(capBox
->width
, capBox
->height
, 1, capBox
->bitDepth
, input
);
366 bmp_d
= CreateBitmap(capBox
->outputwidth
, capBox
->outputheight
, 1, capBox
->bitDepth
, NULL
);
367 SelectObject(dc_s
, bmp_s
);
368 SelectObject(dc_d
, bmp_d
);
369 StretchBlt(dc_d
, 0, 0, capBox
->outputwidth
, capBox
->outputheight
,
370 dc_s
, 0, 0, capBox
->width
, capBox
->height
, SRCCOPY
);
371 GetBitmapBits(bmp_d
, capBox
->outputwidth
* capBox
->outputheight
* depth
, myarray
);
372 while (outoffset
> 0)
377 for (i
= 0; i
< ow
; i
++)
378 output
[outoffset
+ i
] = myarray
[inoffset
+ i
];
381 CoTaskMemFree(myarray
);
389 static DWORD WINAPI
ReadThread(LPVOID lParam
)
391 Capture
* capBox
= lParam
;
393 IMediaSample
*pSample
= NULL
;
394 ULONG framecount
= 0;
395 unsigned char *pTarget
, *image_data
;
396 unsigned int image_size
;
398 image_size
= capBox
->height
* capBox
->width
* 3;
399 if (!(image_data
= heap_alloc(image_size
)))
401 ERR("Failed to allocate memory.\n");
403 capBox
->stopped
= TRUE
;
409 EnterCriticalSection(&capBox
->CritSect
);
412 hr
= BaseOutputPinImpl_GetDeliveryBuffer(capBox
->pin
, &pSample
, NULL
, NULL
, 0);
417 if (!capBox
->swresize
)
418 len
= capBox
->height
* capBox
->width
* capBox
->bitDepth
/ 8;
420 len
= capBox
->outputheight
* capBox
->outputwidth
* capBox
->bitDepth
/ 8;
421 IMediaSample_SetActualDataLength(pSample
, len
);
423 len
= IMediaSample_GetActualDataLength(pSample
);
424 TRACE("Data length: %d KB\n", len
/ 1024);
426 IMediaSample_GetPointer(pSample
, &pTarget
);
428 while (video_read(capBox
->fd
, image_data
, image_size
) == -1)
432 ERR("Failed to read frame: %s\n", strerror(errno
));
437 Resize(capBox
, pTarget
, image_data
);
438 hr
= BaseOutputPinImpl_Deliver(capBox
->pin
, pSample
);
439 TRACE("%p -> Frame %u: %x\n", capBox
, ++framecount
, hr
);
440 IMediaSample_Release(pSample
);
442 if (FAILED(hr
) && hr
!= VFW_E_NOT_CONNECTED
)
444 TRACE("Return %x, stop IFilterGraph\n", hr
);
446 capBox
->stopped
= TRUE
;
449 LeaveCriticalSection(&capBox
->CritSect
);
452 LeaveCriticalSection(&capBox
->CritSect
);
453 heap_free(image_data
);
457 HRESULT
qcap_driver_run(Capture
*capBox
, FILTER_STATE
*state
)
462 TRACE("%p -> (%p)\n", capBox
, state
);
464 if (*state
== State_Running
) return S_OK
;
466 EnterCriticalSection(&capBox
->CritSect
);
468 capBox
->stopped
= FALSE
;
470 if (*state
== State_Stopped
)
472 *state
= State_Running
;
473 if (!capBox
->iscommitted
)
475 ALLOCATOR_PROPERTIES ap
, actual
;
477 capBox
->iscommitted
= TRUE
;
480 if (!capBox
->swresize
)
481 ap
.cbBuffer
= capBox
->width
* capBox
->height
;
483 ap
.cbBuffer
= capBox
->outputwidth
* capBox
->outputheight
;
484 ap
.cbBuffer
= (ap
.cbBuffer
* capBox
->bitDepth
) / 8;
488 hr
= IMemAllocator_SetProperties(capBox
->pin
->pAllocator
, &ap
, &actual
);
491 hr
= IMemAllocator_Commit(capBox
->pin
->pAllocator
);
493 TRACE("Committing allocator: %x\n", hr
);
496 thread
= CreateThread(NULL
, 0, ReadThread
, capBox
, 0, NULL
);
499 capBox
->thread
= thread
;
500 SetThreadPriority(thread
, THREAD_PRIORITY_LOWEST
);
501 LeaveCriticalSection(&capBox
->CritSect
);
504 ERR("Creating thread failed.. %u\n", GetLastError());
505 LeaveCriticalSection(&capBox
->CritSect
);
509 ResumeThread(capBox
->thread
);
510 *state
= State_Running
;
511 LeaveCriticalSection(&capBox
->CritSect
);
515 HRESULT
qcap_driver_pause(Capture
*capBox
, FILTER_STATE
*state
)
517 TRACE("%p -> (%p)\n", capBox
, state
);
519 if (*state
== State_Paused
)
521 if (*state
== State_Stopped
)
522 qcap_driver_run(capBox
, state
);
524 EnterCriticalSection(&capBox
->CritSect
);
525 *state
= State_Paused
;
526 SuspendThread(capBox
->thread
);
527 LeaveCriticalSection(&capBox
->CritSect
);
532 HRESULT
qcap_driver_stop(Capture
*capBox
, FILTER_STATE
*state
)
534 TRACE("%p -> (%p)\n", capBox
, state
);
536 if (*state
== State_Stopped
)
539 EnterCriticalSection(&capBox
->CritSect
);
543 if (*state
== State_Paused
)
544 ResumeThread(capBox
->thread
);
545 capBox
->stopped
= TRUE
;
547 if (capBox
->iscommitted
)
551 capBox
->iscommitted
= FALSE
;
553 hr
= IMemAllocator_Decommit(capBox
->pin
->pAllocator
);
555 if (hr
!= S_OK
&& hr
!= VFW_E_NOT_COMMITTED
)
556 WARN("Decommitting allocator: %x\n", hr
);
560 *state
= State_Stopped
;
561 LeaveCriticalSection(&capBox
->CritSect
);
565 Capture
*qcap_driver_init(struct strmbase_source
*pin
, USHORT card
)
567 struct v4l2_capability caps
= {{0}};
568 struct v4l2_format format
= {0};
569 Capture
*device
= NULL
;
574 have_libv4l2
= video_init();
576 if (!(device
= CoTaskMemAlloc(sizeof(*device
))))
579 InitializeCriticalSection(&device
->CritSect
);
580 device
->CritSect
.DebugInfo
->Spare
[0] = (DWORD_PTR
)(__FILE__
": Capture.CritSect");
582 sprintf(path
, "/dev/video%i", card
);
583 TRACE("Opening device %s.\n", path
);
585 if ((fd
= video_open(path
, O_RDWR
| O_NONBLOCK
| O_CLOEXEC
)) == -1 && errno
== EINVAL
)
587 fd
= video_open(path
, O_RDWR
| O_NONBLOCK
);
590 WARN("Failed to open video device: %s\n", strerror(errno
));
593 fcntl(fd
, F_SETFD
, FD_CLOEXEC
); /* in case O_CLOEXEC isn't supported */
596 if (xioctl(fd
, VIDIOC_QUERYCAP
, &caps
) == -1)
598 WARN("Failed to query device capabilities: %s\n", strerror(errno
));
602 #ifdef V4L2_CAP_DEVICE_CAPS
603 if (caps
.capabilities
& V4L2_CAP_DEVICE_CAPS
)
604 caps
.capabilities
= caps
.device_caps
;
607 if (!(caps
.capabilities
& V4L2_CAP_VIDEO_CAPTURE
))
609 WARN("Device does not support single-planar video capture.\n");
613 if (!(caps
.capabilities
& V4L2_CAP_READWRITE
))
615 WARN("Device does not support read().\n");
617 #ifdef SONAME_LIBV4L2
618 ERR_(winediag
)("Reading from %s requires libv4l2, but it could not be loaded.\n", path
);
620 ERR_(winediag
)("Reading from %s requires libv4l2, but Wine was compiled without libv4l2 support.\n", path
);
625 format
.type
= V4L2_BUF_TYPE_VIDEO_CAPTURE
;
626 if (xioctl(fd
, VIDIOC_G_FMT
, &format
) == -1)
628 ERR("Failed to get device format: %s\n", strerror(errno
));
632 format
.fmt
.pix
.pixelformat
= V4L2_PIX_FMT_BGR24
;
633 if (xioctl(fd
, VIDIOC_S_FMT
, &format
) == -1
634 || format
.fmt
.pix
.pixelformat
!= V4L2_PIX_FMT_BGR24
)
636 ERR("Failed to set pixel format: %s\n", strerror(errno
));
638 ERR_(winediag
)("You may need libv4l2 to use this device.\n");
642 device
->outputwidth
= device
->width
= format
.fmt
.pix
.width
;
643 device
->outputheight
= device
->height
= format
.fmt
.pix
.height
;
644 device
->swresize
= FALSE
;
645 device
->bitDepth
= 24;
648 device
->stopped
= FALSE
;
649 device
->iscommitted
= FALSE
;
651 TRACE("Format: %d bpp - %dx%d.\n", device
->bitDepth
, device
->width
, device
->height
);
656 qcap_driver_destroy(device
);
662 Capture
*qcap_driver_init(struct strmbase_source
*pin
, USHORT card
)
664 static const char msg
[] =
665 "The v4l headers were not available at compile time,\n"
666 "so video capture support is not available.\n";
671 #define FAIL_WITH_ERR \
672 ERR("v4l absent: shouldn't be called\n"); \
675 HRESULT
qcap_driver_destroy(Capture
*capBox
)
680 HRESULT
qcap_driver_check_format(Capture
*device
, const AM_MEDIA_TYPE
*mt
)
685 HRESULT
qcap_driver_set_format(Capture
*capBox
, AM_MEDIA_TYPE
* mT
)
690 HRESULT
qcap_driver_get_format(const Capture
*capBox
, AM_MEDIA_TYPE
** mT
)
695 HRESULT
qcap_driver_get_prop_range( Capture
*capBox
,
696 VideoProcAmpProperty Property
, LONG
*pMin
, LONG
*pMax
,
697 LONG
*pSteppingDelta
, LONG
*pDefault
, LONG
*pCapsFlags
)
702 HRESULT
qcap_driver_get_prop(Capture
*capBox
,
703 VideoProcAmpProperty Property
, LONG
*lValue
, LONG
*Flags
)
708 HRESULT
qcap_driver_set_prop(Capture
*capBox
, VideoProcAmpProperty Property
,
709 LONG lValue
, LONG Flags
)
714 HRESULT
qcap_driver_run(Capture
*capBox
, FILTER_STATE
*state
)
719 HRESULT
qcap_driver_pause(Capture
*capBox
, FILTER_STATE
*state
)
724 HRESULT
qcap_driver_stop(Capture
*capBox
, FILTER_STATE
*state
)
729 #endif /* defined(VIDIOCMCAPTURE) */