mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-12-22 03:10:02 +01:00
Enable the header metadata so that we have timestamps again. Rework allocation in the link a little. Reuse previously allocated buffers when possible. Keep track of where the buffers was allocated. Make some more convenience functions to initialize buffer headers. Make it so that we move allocations to the app. Make some helpers to make memfd memory in pinos now that we will be able to do all allocation there.
1167 lines
39 KiB
C
1167 lines
39 KiB
C
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sched.h>
|
|
#include <errno.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/mman.h>
|
|
#include <poll.h>
|
|
|
|
#define CLEAR(x) memset(&(x), 0, sizeof(x))
|
|
|
|
static int
|
|
xioctl (int fd, int request, void *arg)
|
|
{
|
|
int err;
|
|
|
|
do {
|
|
err = ioctl (fd, request, arg);
|
|
} while (err == -1 && errno == EINTR);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
spa_v4l2_open (SpaV4l2Source *this)
|
|
{
|
|
SpaV4l2State *state = &this->state[0];
|
|
struct stat st;
|
|
SpaV4l2SourceProps *props = &this->props[1];
|
|
|
|
if (state->opened)
|
|
return 0;
|
|
|
|
if (props->props.unset_mask & 1) {
|
|
fprintf(stderr, "Device property not set\n");
|
|
return -1;
|
|
}
|
|
|
|
fprintf (stderr, "Playback device is '%s'\n", props->device);
|
|
|
|
if (stat (props->device, &st) < 0) {
|
|
fprintf(stderr, "Cannot identify '%s': %d, %s\n",
|
|
props->device, errno, strerror (errno));
|
|
return -1;
|
|
}
|
|
|
|
if (!S_ISCHR (st.st_mode)) {
|
|
fprintf(stderr, "%s is no device\n", props->device);
|
|
return -1;
|
|
}
|
|
|
|
state->fd = open (props->device, O_RDWR | O_NONBLOCK, 0);
|
|
|
|
if (state->fd == -1) {
|
|
fprintf (stderr, "Cannot open '%s': %d, %s\n",
|
|
props->device, errno, strerror (errno));
|
|
return -1;
|
|
}
|
|
|
|
if (xioctl (state->fd, VIDIOC_QUERYCAP, &state->cap) < 0) {
|
|
perror ("QUERYCAP");
|
|
return -1;
|
|
}
|
|
|
|
if ((state->cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) {
|
|
fprintf (stderr, "%s is no video capture device\n", props->device);
|
|
return -1;
|
|
}
|
|
state->opened = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static SpaResult
|
|
spa_v4l2_buffer_recycle (SpaV4l2Source *this, uint32_t buffer_id)
|
|
{
|
|
SpaV4l2State *state = &this->state[0];
|
|
V4l2Buffer *b = &state->buffers[buffer_id];
|
|
|
|
if (!b->outstanding)
|
|
return SPA_RESULT_OK;
|
|
|
|
b->outstanding = false;
|
|
|
|
if (xioctl (state->fd, VIDIOC_QBUF, &b->v4l2_buffer) < 0) {
|
|
perror ("VIDIOC_QBUF");
|
|
}
|
|
return SPA_RESULT_OK;
|
|
}
|
|
|
|
static SpaResult
|
|
spa_v4l2_clear_buffers (SpaV4l2Source *this)
|
|
{
|
|
SpaV4l2State *state = &this->state[0];
|
|
struct v4l2_requestbuffers reqbuf;
|
|
int i;
|
|
|
|
if (state->n_buffers == 0)
|
|
return SPA_RESULT_OK;
|
|
|
|
for (i = 0; i < state->n_buffers; i++) {
|
|
V4l2Buffer *b;
|
|
|
|
b = &state->buffers[i];
|
|
if (b->outstanding) {
|
|
fprintf (stderr, "queueing outstanding buffer %p\n", b);
|
|
spa_v4l2_buffer_recycle (this, i);
|
|
}
|
|
if (state->export_buf) {
|
|
close (SPA_PTR_TO_INT (b->outbuf->datas[0].data));
|
|
}
|
|
}
|
|
|
|
CLEAR(reqbuf);
|
|
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
reqbuf.memory = state->memtype;
|
|
reqbuf.count = 0;
|
|
|
|
if (xioctl (state->fd, VIDIOC_REQBUFS, &reqbuf) < 0) {
|
|
perror ("VIDIOC_REQBUFS");
|
|
}
|
|
state->n_buffers = 0;
|
|
|
|
return SPA_RESULT_OK;
|
|
}
|
|
|
|
static int
|
|
spa_v4l2_close (SpaV4l2Source *this)
|
|
{
|
|
SpaV4l2State *state = &this->state[0];
|
|
|
|
if (!state->opened)
|
|
return 0;
|
|
|
|
if (state->n_buffers > 0)
|
|
return 0;
|
|
|
|
fprintf (stderr, "close\n");
|
|
if (close(state->fd))
|
|
perror ("close");
|
|
|
|
state->fd = -1;
|
|
state->opened = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
typedef struct {
|
|
uint32_t fourcc;
|
|
SpaVideoFormat format;
|
|
SpaMediaType media_type;
|
|
SpaMediaSubType media_subtype;
|
|
} FormatInfo;
|
|
|
|
static const FormatInfo format_info[] =
|
|
{
|
|
/* RGB formats */
|
|
{ V4L2_PIX_FMT_RGB332, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_ARGB555, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_XRGB555, SPA_VIDEO_FORMAT_RGB15, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_ARGB555X, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_XRGB555X, SPA_VIDEO_FORMAT_BGR15, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_RGB565, SPA_VIDEO_FORMAT_RGB16, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_RGB565X, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_BGR666, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_BGR24, SPA_VIDEO_FORMAT_BGR, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_RGB24, SPA_VIDEO_FORMAT_RGB, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_ABGR32, SPA_VIDEO_FORMAT_BGRA, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_XBGR32, SPA_VIDEO_FORMAT_BGRx, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_ARGB32, SPA_VIDEO_FORMAT_ARGB, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_XRGB32, SPA_VIDEO_FORMAT_xRGB, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
|
|
/* Deprecated Packed RGB Image Formats (alpha ambiguity) */
|
|
{ V4L2_PIX_FMT_RGB444, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_RGB555, SPA_VIDEO_FORMAT_RGB15, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_RGB555X, SPA_VIDEO_FORMAT_BGR15, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_BGR32, SPA_VIDEO_FORMAT_BGRx, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_RGB32, SPA_VIDEO_FORMAT_xRGB, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
|
|
/* Grey formats */
|
|
{ V4L2_PIX_FMT_GREY, SPA_VIDEO_FORMAT_GRAY8, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_Y4, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_Y6, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_Y10, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_Y12, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_Y16, SPA_VIDEO_FORMAT_GRAY16_LE, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_Y16_BE, SPA_VIDEO_FORMAT_GRAY16_BE, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_Y10BPACK, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
|
|
/* Palette formats */
|
|
{ V4L2_PIX_FMT_PAL8, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
|
|
/* Chrominance formats */
|
|
{ V4L2_PIX_FMT_UV8, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
|
|
/* Luminance+Chrominance formats */
|
|
{ V4L2_PIX_FMT_YVU410, SPA_VIDEO_FORMAT_YVU9, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_YVU420, SPA_VIDEO_FORMAT_YV12, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_YVU420M, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_YUYV, SPA_VIDEO_FORMAT_YUY2, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_YYUV, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_YVYU, SPA_VIDEO_FORMAT_YVYU, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_UYVY, SPA_VIDEO_FORMAT_UYVY, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_VYUY, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_YUV422P, SPA_VIDEO_FORMAT_Y42B, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_YUV411P, SPA_VIDEO_FORMAT_Y41B, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_Y41P, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_YUV444, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_YUV555, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_YUV565, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_YUV32, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_YUV410, SPA_VIDEO_FORMAT_YUV9, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_YUV420, SPA_VIDEO_FORMAT_I420, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_YUV420M, SPA_VIDEO_FORMAT_I420, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_HI240, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_HM12, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_M420, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
|
|
/* two planes -- one Y, one Cr + Cb interleaved */
|
|
{ V4L2_PIX_FMT_NV12, SPA_VIDEO_FORMAT_NV12, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_NV12M, SPA_VIDEO_FORMAT_NV12, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_NV12MT, SPA_VIDEO_FORMAT_NV12_64Z32, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_NV12MT_16X16, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_NV21, SPA_VIDEO_FORMAT_NV21, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_NV21M, SPA_VIDEO_FORMAT_NV21, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_NV16, SPA_VIDEO_FORMAT_NV16, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_NV16M, SPA_VIDEO_FORMAT_NV16, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_NV61, SPA_VIDEO_FORMAT_NV61, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_NV61M, SPA_VIDEO_FORMAT_NV61, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_NV24, SPA_VIDEO_FORMAT_NV24, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_NV42, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
|
|
/* Bayer formats - see http://www.siliconimaging.com/RGB%20Bayer.htm */
|
|
{ V4L2_PIX_FMT_SBGGR8, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_BAYER },
|
|
{ V4L2_PIX_FMT_SGBRG8, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_BAYER },
|
|
{ V4L2_PIX_FMT_SGRBG8, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_BAYER },
|
|
{ V4L2_PIX_FMT_SRGGB8, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_BAYER },
|
|
|
|
/* compressed formats */
|
|
{ V4L2_PIX_FMT_MJPEG, SPA_VIDEO_FORMAT_ENCODED, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_MJPG },
|
|
{ V4L2_PIX_FMT_JPEG, SPA_VIDEO_FORMAT_ENCODED, SPA_MEDIA_TYPE_IMAGE, SPA_MEDIA_SUBTYPE_JPEG },
|
|
{ V4L2_PIX_FMT_PJPG, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_DV, SPA_VIDEO_FORMAT_ENCODED, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_DV },
|
|
{ V4L2_PIX_FMT_MPEG, SPA_VIDEO_FORMAT_ENCODED, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_MPEGTS },
|
|
{ V4L2_PIX_FMT_H264, SPA_VIDEO_FORMAT_ENCODED, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_H264 },
|
|
{ V4L2_PIX_FMT_H264_NO_SC, SPA_VIDEO_FORMAT_ENCODED, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_H264 },
|
|
{ V4L2_PIX_FMT_H264_MVC, SPA_VIDEO_FORMAT_ENCODED, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_H264 },
|
|
{ V4L2_PIX_FMT_H263, SPA_VIDEO_FORMAT_ENCODED, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_H263 },
|
|
{ V4L2_PIX_FMT_MPEG1, SPA_VIDEO_FORMAT_ENCODED, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_MPEG1 },
|
|
{ V4L2_PIX_FMT_MPEG2, SPA_VIDEO_FORMAT_ENCODED, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_MPEG2 },
|
|
{ V4L2_PIX_FMT_MPEG4, SPA_VIDEO_FORMAT_ENCODED, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_MPEG4 },
|
|
{ V4L2_PIX_FMT_XVID, SPA_VIDEO_FORMAT_ENCODED, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_XVID },
|
|
{ V4L2_PIX_FMT_VC1_ANNEX_G, SPA_VIDEO_FORMAT_ENCODED, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_VC1 },
|
|
{ V4L2_PIX_FMT_VC1_ANNEX_L, SPA_VIDEO_FORMAT_ENCODED, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_VC1 },
|
|
{ V4L2_PIX_FMT_VP8, SPA_VIDEO_FORMAT_ENCODED, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_VP8 },
|
|
|
|
/* Vendor-specific formats */
|
|
{ V4L2_PIX_FMT_WNVA, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_SN9C10X, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_PWC1, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
{ V4L2_PIX_FMT_PWC2, SPA_VIDEO_FORMAT_UNKNOWN, SPA_MEDIA_TYPE_VIDEO, SPA_MEDIA_SUBTYPE_RAW },
|
|
};
|
|
|
|
static const FormatInfo *
|
|
fourcc_to_format_info (uint32_t fourcc)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < SPA_N_ELEMENTS (format_info); i++) {
|
|
if (format_info[i].fourcc == fourcc)
|
|
return &format_info[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#if 0
|
|
static const FormatInfo *
|
|
video_format_to_format_info (SpaVideoFormat format)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < SPA_N_ELEMENTS (format_info); i++) {
|
|
if (format_info[i].format == format)
|
|
return &format_info[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
static const FormatInfo *
|
|
find_format_info_by_media_type (SpaMediaType type,
|
|
SpaMediaSubType subtype,
|
|
SpaVideoFormat format,
|
|
int startidx)
|
|
{
|
|
int i;
|
|
|
|
for (i = startidx; i < SPA_N_ELEMENTS (format_info); i++) {
|
|
if ((format_info[i].media_type == type) &&
|
|
(format_info[i].media_subtype == subtype) &&
|
|
(format == SPA_VIDEO_FORMAT_UNKNOWN || format_info[i].format == format))
|
|
return &format_info[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static SpaVideoFormat
|
|
enum_filter_format (const SpaFormat *filter, unsigned int index)
|
|
{
|
|
SpaVideoFormat video_format = SPA_VIDEO_FORMAT_UNKNOWN;
|
|
|
|
if ((filter->media_type == SPA_MEDIA_TYPE_VIDEO || filter->media_type == SPA_MEDIA_TYPE_IMAGE)) {
|
|
if (filter->media_subtype == SPA_MEDIA_SUBTYPE_RAW) {
|
|
SpaPropValue val;
|
|
SpaResult res;
|
|
unsigned int idx;
|
|
const SpaPropInfo *pi;
|
|
|
|
idx = spa_props_index_for_id (&filter->props, SPA_PROP_ID_VIDEO_FORMAT);
|
|
if (idx == SPA_IDX_INVALID)
|
|
return SPA_VIDEO_FORMAT_UNKNOWN;
|
|
|
|
pi = &filter->props.prop_info[idx];
|
|
if (pi->type != SPA_PROP_TYPE_UINT32)
|
|
return SPA_VIDEO_FORMAT_UNKNOWN;
|
|
|
|
res = spa_props_get_value (&filter->props, idx, &val);
|
|
if (res >= 0) {
|
|
if (index == 0)
|
|
video_format = *((SpaVideoFormat *)val.value);
|
|
} else if (res == SPA_RESULT_PROPERTY_UNSET) {
|
|
|
|
if (index < pi->n_range_values)
|
|
video_format = *((SpaVideoFormat *)pi->range_values[index].val.value);
|
|
}
|
|
} else {
|
|
if (index == 0)
|
|
video_format = SPA_VIDEO_FORMAT_ENCODED;
|
|
}
|
|
}
|
|
return video_format;
|
|
}
|
|
|
|
static bool
|
|
filter_framesize (struct v4l2_frmsizeenum *frmsize,
|
|
const SpaRectangle *min,
|
|
const SpaRectangle *max,
|
|
const SpaRectangle *step)
|
|
{
|
|
if (frmsize->type == V4L2_FRMSIZE_TYPE_DISCRETE) {
|
|
if (frmsize->discrete.width < min->width ||
|
|
frmsize->discrete.height < min->height ||
|
|
frmsize->discrete.width > max->width ||
|
|
frmsize->discrete.height > max->height) {
|
|
return false;
|
|
}
|
|
} else if (frmsize->type == V4L2_FRMSIZE_TYPE_CONTINUOUS ||
|
|
frmsize->type == V4L2_FRMSIZE_TYPE_STEPWISE) {
|
|
/* FIXME, use LCM */
|
|
frmsize->stepwise.step_width *= step->width;
|
|
frmsize->stepwise.step_height *= step->height;
|
|
|
|
if (frmsize->stepwise.max_width < min->width ||
|
|
frmsize->stepwise.max_height < min->height ||
|
|
frmsize->stepwise.min_width > max->width ||
|
|
frmsize->stepwise.min_height > max->height)
|
|
return false;
|
|
|
|
frmsize->stepwise.min_width = SPA_MAX (frmsize->stepwise.min_width, min->width);
|
|
frmsize->stepwise.min_height = SPA_MAX (frmsize->stepwise.min_height, min->height);
|
|
frmsize->stepwise.max_width = SPA_MIN (frmsize->stepwise.max_width, max->width);
|
|
frmsize->stepwise.max_height = SPA_MIN (frmsize->stepwise.max_height, max->height);
|
|
} else
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static int
|
|
compare_fraction (struct v4l2_fract *f1, const SpaFraction *f2)
|
|
{
|
|
uint64_t n1, n2;
|
|
|
|
/* fractions are reduced when set, so we can quickly see if they're equal */
|
|
if (f1->denominator == f2->num && f1->numerator == f2->denom)
|
|
return 0;
|
|
|
|
/* extend to 64 bits */
|
|
n1 = ((int64_t) f1->denominator) * f2->denom;
|
|
n2 = ((int64_t) f1->numerator) * f2->num;
|
|
if (n1 < n2)
|
|
return -1;
|
|
return 1;
|
|
}
|
|
|
|
static bool
|
|
filter_framerate (struct v4l2_frmivalenum *frmival,
|
|
const SpaFraction *min,
|
|
const SpaFraction *max,
|
|
const SpaFraction *step)
|
|
{
|
|
if (frmival->type == V4L2_FRMIVAL_TYPE_DISCRETE) {
|
|
if (compare_fraction (&frmival->discrete, min) < 0 ||
|
|
compare_fraction (&frmival->discrete, max) > 0)
|
|
return false;
|
|
} else if (frmival->type == V4L2_FRMIVAL_TYPE_CONTINUOUS ||
|
|
frmival->type == V4L2_FRMIVAL_TYPE_STEPWISE) {
|
|
/* FIXME, use LCM */
|
|
frmival->stepwise.step.denominator *= step->num;
|
|
frmival->stepwise.step.numerator *= step->denom;
|
|
|
|
if (compare_fraction (&frmival->stepwise.max, min) < 0 ||
|
|
compare_fraction (&frmival->stepwise.min, max) > 0)
|
|
return false;
|
|
|
|
if (compare_fraction (&frmival->stepwise.min, min) < 0) {
|
|
frmival->stepwise.min.denominator = min->num;
|
|
frmival->stepwise.min.numerator = min->denom;
|
|
}
|
|
if (compare_fraction (&frmival->stepwise.max, max) > 0) {
|
|
frmival->stepwise.max.denominator = max->num;
|
|
frmival->stepwise.max.numerator = max->denom;
|
|
}
|
|
} else
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
#define FOURCC_ARGS(f) (f)&0x7f,((f)>>8)&0x7f,((f)>>16)&0x7f,((f)>>24)&0x7f
|
|
|
|
static SpaResult
|
|
spa_v4l2_enum_format (SpaV4l2Source *this, SpaFormat **format, const SpaFormat *filter, void **cookie)
|
|
{
|
|
SpaV4l2State *state = &this->state[0];
|
|
int res, i, pi;
|
|
V4l2Format *fmt;
|
|
const FormatInfo *info;
|
|
|
|
if (spa_v4l2_open (this) < 0)
|
|
return SPA_RESULT_ERROR;
|
|
|
|
*format = NULL;
|
|
|
|
if (*cookie == NULL) {
|
|
CLEAR (state->fmtdesc);
|
|
state->fmtdesc.index = 0;
|
|
state->fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
state->next_fmtdesc = true;
|
|
CLEAR (state->frmsize);
|
|
state->next_frmsize = true;
|
|
CLEAR (state->frmival);
|
|
*cookie = state;
|
|
}
|
|
|
|
if (false) {
|
|
next_fmtdesc:
|
|
state->fmtdesc.index++;
|
|
state->next_fmtdesc = true;
|
|
}
|
|
|
|
while (state->next_fmtdesc) {
|
|
if (filter) {
|
|
SpaVideoFormat video_format;
|
|
|
|
video_format = enum_filter_format (filter, state->fmtdesc.index);
|
|
if (video_format == SPA_VIDEO_FORMAT_UNKNOWN)
|
|
return SPA_RESULT_ENUM_END;
|
|
|
|
info = find_format_info_by_media_type (filter->media_type,
|
|
filter->media_subtype,
|
|
video_format,
|
|
0);
|
|
if (info == NULL)
|
|
goto next_fmtdesc;
|
|
|
|
state->fmtdesc.pixelformat = info->fourcc;
|
|
} else {
|
|
if ((res = xioctl (state->fd, VIDIOC_ENUM_FMT, &state->fmtdesc)) < 0) {
|
|
if (errno != EINVAL)
|
|
perror ("VIDIOC_ENUM_FMT");
|
|
return SPA_RESULT_ENUM_END;
|
|
}
|
|
}
|
|
state->next_fmtdesc = false;
|
|
state->frmsize.index = 0;
|
|
state->frmsize.pixel_format = state->fmtdesc.pixelformat;
|
|
state->next_frmsize = true;
|
|
}
|
|
|
|
if (!(info = fourcc_to_format_info (state->fmtdesc.pixelformat)))
|
|
goto next_fmtdesc;
|
|
|
|
next_frmsize:
|
|
while (state->next_frmsize) {
|
|
if (filter) {
|
|
const SpaPropInfo *pi;
|
|
unsigned int idx;
|
|
SpaPropValue val;
|
|
SpaResult res;
|
|
|
|
/* check if we have a fixed frame size */
|
|
idx = spa_props_index_for_id (&filter->props, SPA_PROP_ID_VIDEO_SIZE);
|
|
if (idx == SPA_IDX_INVALID)
|
|
goto do_frmsize;
|
|
|
|
pi = &filter->props.prop_info[idx];
|
|
if (pi->type != SPA_PROP_TYPE_RECTANGLE)
|
|
return SPA_RESULT_ENUM_END;
|
|
|
|
res = spa_props_get_value (&filter->props, idx, &val);
|
|
if (res >= 0) {
|
|
const SpaRectangle *size = val.value;
|
|
|
|
if (state->frmsize.index > 0)
|
|
goto next_fmtdesc;
|
|
|
|
state->frmsize.type = V4L2_FRMSIZE_TYPE_DISCRETE;
|
|
state->frmsize.discrete.width = size->width;
|
|
state->frmsize.discrete.height = size->height;
|
|
goto have_size;
|
|
}
|
|
}
|
|
do_frmsize:
|
|
if ((res = xioctl (state->fd, VIDIOC_ENUM_FRAMESIZES, &state->frmsize)) < 0) {
|
|
if (errno == EINVAL)
|
|
goto next_fmtdesc;
|
|
|
|
perror ("VIDIOC_ENUM_FRAMESIZES");
|
|
return SPA_RESULT_ENUM_END;
|
|
}
|
|
if (filter) {
|
|
const SpaPropInfo *pi;
|
|
unsigned int idx;
|
|
const SpaRectangle step = { 1, 1 };
|
|
|
|
/* check if we have a fixed frame size */
|
|
idx = spa_props_index_for_id (&filter->props, SPA_PROP_ID_VIDEO_SIZE);
|
|
if (idx == SPA_IDX_INVALID)
|
|
goto have_size;
|
|
|
|
/* checked above */
|
|
pi = &filter->props.prop_info[idx];
|
|
|
|
if (pi->range_type == SPA_PROP_RANGE_TYPE_MIN_MAX) {
|
|
if (filter_framesize (&state->frmsize, pi->range_values[0].val.value,
|
|
pi->range_values[1].val.value,
|
|
&step))
|
|
goto have_size;
|
|
} else if (pi->range_type == SPA_PROP_RANGE_TYPE_STEP) {
|
|
if (filter_framesize (&state->frmsize, pi->range_values[0].val.value,
|
|
pi->range_values[1].val.value,
|
|
pi->range_values[2].val.value))
|
|
goto have_size;
|
|
} else if (pi->range_type == SPA_PROP_RANGE_TYPE_ENUM) {
|
|
unsigned int i;
|
|
for (i = 0; i < pi->n_range_values; i++) {
|
|
if (filter_framesize (&state->frmsize, pi->range_values[i].val.value,
|
|
pi->range_values[i].val.value,
|
|
&step))
|
|
goto have_size;
|
|
}
|
|
}
|
|
/* nothing matches the filter, get next frame size */
|
|
state->frmsize.index++;
|
|
continue;
|
|
}
|
|
|
|
have_size:
|
|
if (state->frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
|
|
/* we have a fixed size, use this to get the frame intervals */
|
|
state->frmival.index = 0;
|
|
state->frmival.pixel_format = state->frmsize.pixel_format;
|
|
state->frmival.width = state->frmsize.discrete.width;
|
|
state->frmival.height = state->frmsize.discrete.height;
|
|
state->next_frmsize = false;
|
|
}
|
|
else if (state->frmsize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS ||
|
|
state->frmsize.type == V4L2_FRMSIZE_TYPE_STEPWISE) {
|
|
/* we have a non fixed size, fix to something sensible to get the
|
|
* framerate */
|
|
state->frmival.index = 0;
|
|
state->frmival.pixel_format = state->frmsize.pixel_format;
|
|
state->frmival.width = state->frmsize.stepwise.min_width;
|
|
state->frmival.height = state->frmsize.stepwise.min_height;
|
|
state->next_frmsize = false;
|
|
} else {
|
|
state->frmsize.index++;
|
|
}
|
|
}
|
|
|
|
fmt = &state->format[0];
|
|
fmt->fmt.media_type = info->media_type;
|
|
fmt->fmt.media_subtype = info->media_subtype;
|
|
fmt->fmt.props.prop_info = fmt->infos;
|
|
fmt->fmt.props.n_prop_info = pi = 0;
|
|
fmt->fmt.props.unset_mask = 0;
|
|
|
|
if (info->media_subtype == SPA_MEDIA_SUBTYPE_RAW) {
|
|
spa_prop_info_fill_video (&fmt->infos[pi],
|
|
SPA_PROP_ID_VIDEO_FORMAT,
|
|
offsetof (V4l2Format, format));
|
|
fmt->format = info->format;
|
|
pi = ++fmt->fmt.props.n_prop_info;
|
|
} else {
|
|
fmt->format = info->format;
|
|
}
|
|
|
|
spa_prop_info_fill_video (&fmt->infos[pi],
|
|
SPA_PROP_ID_VIDEO_SIZE,
|
|
offsetof (V4l2Format, size));
|
|
fmt->size.width = state->frmsize.discrete.width;
|
|
fmt->size.height = state->frmsize.discrete.height;
|
|
pi = ++fmt->fmt.props.n_prop_info;
|
|
|
|
spa_prop_info_fill_video (&fmt->infos[pi],
|
|
SPA_PROP_ID_VIDEO_FRAMERATE,
|
|
offsetof (V4l2Format, framerate));
|
|
fmt->infos[pi].range_values = fmt->ranges;
|
|
fmt->infos[pi].n_range_values = 0;
|
|
i = state->frmival.index = 0;
|
|
|
|
while (true) {
|
|
if ((res = xioctl (state->fd, VIDIOC_ENUM_FRAMEINTERVALS, &state->frmival)) < 0) {
|
|
if (errno == EINVAL) {
|
|
state->frmsize.index++;
|
|
state->next_frmsize = true;
|
|
if (i == 0)
|
|
goto next_frmsize;
|
|
break;
|
|
}
|
|
perror ("VIDIOC_ENUM_FRAMEINTERVALS");
|
|
return SPA_RESULT_ENUM_END;
|
|
}
|
|
if (filter) {
|
|
SpaPropValue val;
|
|
const SpaPropInfo *pi;
|
|
unsigned int idx;
|
|
SpaResult res;
|
|
const SpaFraction step = { 1, 1 };
|
|
|
|
/* check against filter */
|
|
idx = spa_props_index_for_id (&filter->props, SPA_PROP_ID_VIDEO_FRAMERATE);
|
|
if (idx == SPA_IDX_INVALID)
|
|
goto have_framerate;
|
|
|
|
pi = &filter->props.prop_info[idx];
|
|
if (pi->type != SPA_PROP_TYPE_FRACTION)
|
|
return SPA_RESULT_ENUM_END;
|
|
|
|
res = spa_props_get_value (&filter->props, idx, &val);
|
|
if (res == 0) {
|
|
if (filter_framerate (&state->frmival, val.value,
|
|
val.value,
|
|
&step))
|
|
goto have_framerate;
|
|
} else if (pi->range_type == SPA_PROP_RANGE_TYPE_MIN_MAX) {
|
|
if (filter_framerate (&state->frmival, pi->range_values[0].val.value,
|
|
pi->range_values[1].val.value,
|
|
&step))
|
|
goto have_framerate;
|
|
} else if (pi->range_type == SPA_PROP_RANGE_TYPE_STEP) {
|
|
if (filter_framerate (&state->frmival, pi->range_values[0].val.value,
|
|
pi->range_values[1].val.value,
|
|
pi->range_values[2].val.value))
|
|
goto have_framerate;
|
|
} else if (pi->range_type == SPA_PROP_RANGE_TYPE_ENUM) {
|
|
unsigned int i;
|
|
for (i = 0; i < pi->n_range_values; i++) {
|
|
if (filter_framerate (&state->frmival, pi->range_values[i].val.value,
|
|
pi->range_values[i].val.value,
|
|
&step))
|
|
goto have_framerate;
|
|
}
|
|
}
|
|
state->frmival.index++;
|
|
continue;
|
|
}
|
|
|
|
have_framerate:
|
|
fmt->ranges[i].name = NULL;
|
|
fmt->ranges[i].description = NULL;
|
|
if (state->frmival.type == V4L2_FRMIVAL_TYPE_DISCRETE) {
|
|
fmt->infos[pi].range_type = SPA_PROP_RANGE_TYPE_ENUM;
|
|
fmt->framerates[i].num = state->frmival.discrete.denominator;
|
|
fmt->framerates[i].denom = state->frmival.discrete.numerator;
|
|
fmt->ranges[i].val.size = sizeof (SpaFraction);
|
|
fmt->ranges[i].val.value = &fmt->framerates[i];
|
|
i++;
|
|
state->frmival.index++;
|
|
if (i == 16)
|
|
break;
|
|
} else if (state->frmival.type == V4L2_FRMIVAL_TYPE_CONTINUOUS ||
|
|
state->frmival.type == V4L2_FRMIVAL_TYPE_STEPWISE) {
|
|
fmt->framerates[0].num = state->frmival.stepwise.min.denominator;
|
|
fmt->framerates[0].denom = state->frmival.stepwise.min.numerator;
|
|
fmt->ranges[0].val.size = sizeof (SpaFraction);
|
|
fmt->ranges[0].val.value = &fmt->framerates[0];
|
|
fmt->framerates[1].num = state->frmival.stepwise.max.denominator;
|
|
fmt->framerates[1].denom = state->frmival.stepwise.max.numerator;
|
|
fmt->ranges[1].val.size = sizeof (SpaFraction);
|
|
fmt->ranges[1].val.value = &fmt->framerates[1];
|
|
if (state->frmival.type == V4L2_FRMIVAL_TYPE_CONTINUOUS) {
|
|
fmt->infos[pi].range_type = SPA_PROP_RANGE_TYPE_MIN_MAX;
|
|
i = 2;
|
|
} else {
|
|
fmt->infos[pi].range_type = SPA_PROP_RANGE_TYPE_STEP;
|
|
fmt->framerates[2].num = state->frmival.stepwise.step.denominator;
|
|
fmt->framerates[2].denom = state->frmival.stepwise.step.numerator;
|
|
fmt->ranges[2].val.size = sizeof (SpaFraction);
|
|
fmt->ranges[2].val.value = &fmt->framerates[2];
|
|
i = 3;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
fmt->infos[pi].n_range_values = i;
|
|
fmt->framerate = fmt->framerates[0];
|
|
if (i > 1) {
|
|
SPA_PROPS_INDEX_UNSET (&fmt->fmt.props, pi);
|
|
}
|
|
pi = ++fmt->fmt.props.n_prop_info;
|
|
|
|
*format = &state->format[0].fmt;
|
|
|
|
return SPA_RESULT_OK;
|
|
}
|
|
|
|
static int
|
|
spa_v4l2_set_format (SpaV4l2Source *this, V4l2Format *f, bool try_only)
|
|
{
|
|
SpaV4l2State *state = &this->state[0];
|
|
int cmd;
|
|
struct v4l2_format reqfmt, fmt;
|
|
struct v4l2_streamparm streamparm;
|
|
const FormatInfo *info = NULL;
|
|
|
|
CLEAR (fmt);
|
|
CLEAR (streamparm);
|
|
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
|
|
info = find_format_info_by_media_type (f->fmt.media_type,
|
|
f->fmt.media_subtype,
|
|
f->format,
|
|
0);
|
|
if (info == NULL) {
|
|
fprintf (stderr, "unknown media type %d %d %d\n", f->fmt.media_type,
|
|
f->fmt.media_subtype, f->format);
|
|
return -1;
|
|
}
|
|
|
|
fmt.fmt.pix.pixelformat = info->fourcc;
|
|
fmt.fmt.pix.field = V4L2_FIELD_ANY;
|
|
fmt.fmt.pix.width = f->size.width;
|
|
fmt.fmt.pix.height = f->size.height;
|
|
streamparm.parm.capture.timeperframe.numerator = f->framerate.denom;
|
|
streamparm.parm.capture.timeperframe.denominator = f->framerate.num;
|
|
|
|
fprintf (stderr, "set %08x %dx%d %d/%d\n", fmt.fmt.pix.pixelformat,
|
|
fmt.fmt.pix.width, fmt.fmt.pix.height,
|
|
streamparm.parm.capture.timeperframe.numerator,
|
|
streamparm.parm.capture.timeperframe.denominator);
|
|
|
|
reqfmt = fmt;
|
|
|
|
if (spa_v4l2_open (this) < 0)
|
|
return -1;
|
|
|
|
cmd = try_only ? VIDIOC_TRY_FMT : VIDIOC_S_FMT;
|
|
if (xioctl (state->fd, cmd, &fmt) < 0) {
|
|
perror ("VIDIOC_S_FMT");
|
|
return -1;
|
|
}
|
|
|
|
/* some cheap USB cam's won't accept any change */
|
|
if (xioctl (state->fd, VIDIOC_S_PARM, &streamparm) < 0)
|
|
perror ("VIDIOC_S_PARM");
|
|
|
|
fprintf (stderr, "got %08x %dx%d %d/%d\n", fmt.fmt.pix.pixelformat,
|
|
fmt.fmt.pix.width, fmt.fmt.pix.height,
|
|
streamparm.parm.capture.timeperframe.numerator,
|
|
streamparm.parm.capture.timeperframe.denominator);
|
|
|
|
if (reqfmt.fmt.pix.pixelformat != fmt.fmt.pix.pixelformat ||
|
|
reqfmt.fmt.pix.width != fmt.fmt.pix.width ||
|
|
reqfmt.fmt.pix.height != fmt.fmt.pix.height)
|
|
return -1;
|
|
|
|
if (try_only)
|
|
return 0;
|
|
|
|
state->fmt = fmt;
|
|
state->info.flags = SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS |
|
|
SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS |
|
|
SPA_PORT_INFO_FLAG_LIVE;
|
|
state->info.maxbuffering = -1;
|
|
state->info.latency = (streamparm.parm.capture.timeperframe.numerator * SPA_NSEC_PER_SEC) /
|
|
streamparm.parm.capture.timeperframe.denominator;
|
|
|
|
state->info.n_params = 2;
|
|
state->info.params = state->params;
|
|
state->params[0] = &state->param_buffers.param;
|
|
state->param_buffers.param.type = SPA_ALLOC_PARAM_TYPE_BUFFERS;
|
|
state->param_buffers.param.size = sizeof (state->param_buffers);
|
|
state->param_buffers.minsize = fmt.fmt.pix.sizeimage;
|
|
state->param_buffers.stride = fmt.fmt.pix.bytesperline;
|
|
state->param_buffers.min_buffers = 2;
|
|
state->param_buffers.max_buffers = MAX_BUFFERS;
|
|
state->param_buffers.align = 16;
|
|
state->params[1] = &state->param_meta.param;
|
|
state->param_meta.param.type = SPA_ALLOC_PARAM_TYPE_META_ENABLE;
|
|
state->param_meta.param.size = sizeof (state->param_meta);
|
|
state->param_meta.type = SPA_META_TYPE_HEADER;
|
|
|
|
state->info.features = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static SpaResult
|
|
mmap_read (SpaV4l2Source *this)
|
|
{
|
|
SpaV4l2State *state = &this->state[0];
|
|
struct v4l2_buffer buf;
|
|
V4l2Buffer *b;
|
|
SpaData *d;
|
|
int64_t pts;
|
|
|
|
CLEAR(buf);
|
|
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
buf.memory = state->memtype;
|
|
|
|
if (xioctl (state->fd, VIDIOC_DQBUF, &buf) < 0) {
|
|
switch (errno) {
|
|
case EAGAIN:
|
|
return SPA_RESULT_ERROR;
|
|
case EIO:
|
|
default:
|
|
perror ("VIDIOC_DQBUF");
|
|
return SPA_RESULT_ERROR;
|
|
}
|
|
}
|
|
|
|
state->last_ticks = (int64_t)buf.timestamp.tv_sec * SPA_USEC_PER_SEC + (uint64_t)buf.timestamp.tv_usec;
|
|
pts = state->last_ticks * 1000;
|
|
|
|
if (buf.flags & V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC)
|
|
state->last_monotonic = pts;
|
|
else
|
|
state->last_monotonic = SPA_TIME_INVALID;
|
|
|
|
b = &state->buffers[buf.index];
|
|
if (b->h) {
|
|
b->h->flags = SPA_BUFFER_FLAG_NONE;
|
|
if (buf.flags & V4L2_BUF_FLAG_ERROR)
|
|
b->h->flags |= SPA_BUFFER_FLAG_CORRUPTED;
|
|
b->h->seq = buf.sequence;
|
|
b->h->pts = pts;
|
|
}
|
|
|
|
d = b->outbuf->datas;
|
|
d[0].size = buf.bytesused;
|
|
|
|
b->next = NULL;
|
|
SPA_QUEUE_PUSH_TAIL (&state->ready, V4l2Buffer, next, b);
|
|
|
|
return SPA_RESULT_OK;
|
|
}
|
|
|
|
static int
|
|
v4l2_on_fd_events (SpaPollNotifyData *data)
|
|
{
|
|
SpaV4l2Source *this = data->user_data;
|
|
SpaNodeEvent event;
|
|
SpaNodeEventHaveOutput ho;
|
|
|
|
if (data->fds[0].revents & POLLERR)
|
|
return -1;
|
|
|
|
if (mmap_read (this) < 0)
|
|
return 0;
|
|
|
|
event.type = SPA_NODE_EVENT_TYPE_HAVE_OUTPUT;
|
|
event.size = sizeof (ho);
|
|
event.data = &ho;
|
|
ho.port_id = 0;
|
|
this->event_cb (&this->node, &event, this->user_data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void *
|
|
find_meta_data (SpaBuffer *b, SpaMetaType type)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < b->n_metas; i++)
|
|
if (b->metas[i].type == type)
|
|
return b->metas[i].data;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static SpaResult
|
|
spa_v4l2_use_buffers (SpaV4l2Source *this, SpaBuffer **buffers, uint32_t n_buffers)
|
|
{
|
|
SpaV4l2State *state = &this->state[0];
|
|
struct v4l2_requestbuffers reqbuf;
|
|
int i;
|
|
|
|
state->memtype = V4L2_MEMORY_USERPTR;
|
|
|
|
CLEAR(reqbuf);
|
|
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
reqbuf.memory = state->memtype;
|
|
reqbuf.count = n_buffers;
|
|
|
|
if (xioctl (state->fd, VIDIOC_REQBUFS, &reqbuf) < 0) {
|
|
perror ("VIDIOC_REQBUFS");
|
|
return SPA_RESULT_ERROR;
|
|
}
|
|
fprintf (stderr, "got %d buffers\n", reqbuf.count);
|
|
if (reqbuf.count < 2) {
|
|
fprintf (stderr, "can't allocate enough buffers\n");
|
|
return SPA_RESULT_ERROR;
|
|
}
|
|
|
|
for (i = 0; i < reqbuf.count; i++) {
|
|
V4l2Buffer *b;
|
|
SpaData *d;
|
|
|
|
b = &state->buffers[i];
|
|
b->outbuf = buffers[i];
|
|
b->outstanding = true;
|
|
b->h = find_meta_data (b->outbuf, SPA_META_TYPE_HEADER);
|
|
|
|
fprintf (stderr, "import buffer %p\n", buffers[i]);
|
|
|
|
if (buffers[i]->n_datas < 1) {
|
|
fprintf (stderr, "invalid memory on buffer %p\n", buffers[i]);
|
|
continue;
|
|
}
|
|
d = buffers[i]->datas;
|
|
|
|
CLEAR (b->v4l2_buffer);
|
|
b->v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
b->v4l2_buffer.memory = state->memtype;
|
|
b->v4l2_buffer.index = i;
|
|
b->v4l2_buffer.m.userptr = (unsigned long) SPA_MEMBER (d[0].data, d[0].offset, void *);
|
|
b->v4l2_buffer.length = d[0].size;
|
|
|
|
spa_v4l2_buffer_recycle (this, buffers[i]->id);
|
|
}
|
|
state->n_buffers = reqbuf.count;
|
|
|
|
return SPA_RESULT_OK;
|
|
}
|
|
|
|
static SpaResult
|
|
mmap_init (SpaV4l2Source *this,
|
|
SpaAllocParam **params,
|
|
unsigned int n_params,
|
|
SpaBuffer **buffers,
|
|
unsigned int *n_buffers)
|
|
{
|
|
SpaV4l2State *state = &this->state[0];
|
|
struct v4l2_requestbuffers reqbuf;
|
|
int i;
|
|
|
|
state->memtype = V4L2_MEMORY_MMAP;
|
|
|
|
CLEAR(reqbuf);
|
|
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
reqbuf.memory = state->memtype;
|
|
reqbuf.count = *n_buffers;
|
|
|
|
if (xioctl (state->fd, VIDIOC_REQBUFS, &reqbuf) < 0) {
|
|
perror ("VIDIOC_REQBUFS");
|
|
return SPA_RESULT_ERROR;
|
|
}
|
|
|
|
fprintf (stderr, "got %d buffers\n", reqbuf.count);
|
|
*n_buffers = reqbuf.count;
|
|
|
|
if (reqbuf.count < 2) {
|
|
fprintf (stderr, "can't allocate enough buffers\n");
|
|
return SPA_RESULT_ERROR;
|
|
}
|
|
if (state->export_buf)
|
|
fprintf (stderr, "using EXPBUF\n");
|
|
|
|
for (i = 0; i < reqbuf.count; i++) {
|
|
V4l2Buffer *b;
|
|
SpaData *d;
|
|
|
|
if (buffers[i]->n_datas < 1) {
|
|
fprintf (stderr, "invalid buffer data\n");
|
|
return SPA_RESULT_ERROR;
|
|
}
|
|
|
|
b = &state->buffers[i];
|
|
b->outbuf = buffers[i];
|
|
b->outstanding = true;
|
|
b->h = find_meta_data (b->outbuf, SPA_META_TYPE_HEADER);
|
|
|
|
CLEAR (b->v4l2_buffer);
|
|
b->v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
b->v4l2_buffer.memory = state->memtype;
|
|
b->v4l2_buffer.index = i;
|
|
|
|
if (xioctl (state->fd, VIDIOC_QUERYBUF, &b->v4l2_buffer) < 0) {
|
|
perror ("VIDIOC_QUERYBUF");
|
|
return SPA_RESULT_ERROR;
|
|
}
|
|
|
|
d = buffers[i]->datas;
|
|
d[0].offset = 0;
|
|
d[0].size = b->v4l2_buffer.length;
|
|
d[0].stride = state->fmt.fmt.pix.bytesperline;
|
|
|
|
if (state->export_buf) {
|
|
struct v4l2_exportbuffer expbuf;
|
|
|
|
CLEAR (expbuf);
|
|
expbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
expbuf.index = i;
|
|
expbuf.flags = O_CLOEXEC | O_RDONLY;
|
|
if (xioctl (state->fd, VIDIOC_EXPBUF, &expbuf) < 0) {
|
|
perror("VIDIOC_EXPBUF");
|
|
continue;
|
|
}
|
|
fprintf (stderr, "expbuf %d\n", expbuf.fd);
|
|
d[0].type = SPA_DATA_TYPE_FD;
|
|
d[0].data = SPA_INT_TO_PTR (expbuf.fd);
|
|
} else {
|
|
d[0].type = SPA_DATA_TYPE_MEMPTR;
|
|
d[0].data = mmap (NULL,
|
|
b->v4l2_buffer.length,
|
|
PROT_READ,
|
|
MAP_SHARED,
|
|
state->fd,
|
|
b->v4l2_buffer.m.offset);
|
|
if (d[0].data == MAP_FAILED) {
|
|
perror ("mmap");
|
|
continue;
|
|
}
|
|
}
|
|
spa_v4l2_buffer_recycle (this, i);
|
|
}
|
|
state->n_buffers = reqbuf.count;
|
|
|
|
return SPA_RESULT_OK;
|
|
}
|
|
|
|
static SpaResult
|
|
userptr_init (SpaV4l2Source *this)
|
|
{
|
|
return SPA_RESULT_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
static SpaResult
|
|
read_init (SpaV4l2Source *this)
|
|
{
|
|
return SPA_RESULT_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
static SpaResult
|
|
spa_v4l2_alloc_buffers (SpaV4l2Source *this,
|
|
SpaAllocParam **params,
|
|
unsigned int n_params,
|
|
SpaBuffer **buffers,
|
|
unsigned int *n_buffers)
|
|
{
|
|
SpaResult res;
|
|
SpaV4l2State *state = &this->state[0];
|
|
|
|
if (state->n_buffers > 0)
|
|
return SPA_RESULT_ERROR;
|
|
|
|
if (state->cap.capabilities & V4L2_CAP_STREAMING) {
|
|
if ((res = mmap_init (this, params, n_params, buffers, n_buffers)) < 0)
|
|
if ((res = userptr_init (this)) < 0)
|
|
return res;
|
|
} else if (state->cap.capabilities & V4L2_CAP_READWRITE) {
|
|
if ((res = read_init (this)) < 0)
|
|
return res;
|
|
} else
|
|
return SPA_RESULT_ERROR;
|
|
|
|
return SPA_RESULT_OK;
|
|
}
|
|
|
|
static SpaResult
|
|
spa_v4l2_start (SpaV4l2Source *this)
|
|
{
|
|
SpaV4l2State *state = &this->state[0];
|
|
enum v4l2_buf_type type;
|
|
SpaNodeEvent event;
|
|
|
|
if (state->started)
|
|
return SPA_RESULT_OK;
|
|
|
|
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
if (xioctl (state->fd, VIDIOC_STREAMON, &type) < 0) {
|
|
perror ("VIDIOC_STREAMON");
|
|
return SPA_RESULT_ERROR;
|
|
}
|
|
state->started = true;
|
|
update_state (this, SPA_NODE_STATE_STREAMING);
|
|
|
|
event.type = SPA_NODE_EVENT_TYPE_ADD_POLL;
|
|
event.data = &state->poll;
|
|
event.size = sizeof (state->poll);
|
|
|
|
state->fds[0].fd = state->fd;
|
|
state->fds[0].events = POLLIN | POLLPRI | POLLERR;
|
|
state->fds[0].revents = 0;
|
|
|
|
state->poll.id = 0;
|
|
state->poll.enabled = true;
|
|
state->poll.fds = state->fds;
|
|
state->poll.n_fds = 1;
|
|
state->poll.idle_cb = NULL;
|
|
state->poll.before_cb = NULL;
|
|
state->poll.after_cb = v4l2_on_fd_events;
|
|
state->poll.user_data = this;
|
|
this->event_cb (&this->node, &event, this->user_data);
|
|
|
|
return SPA_RESULT_OK;
|
|
}
|
|
|
|
static SpaResult
|
|
spa_v4l2_pause (SpaV4l2Source *this)
|
|
{
|
|
SpaV4l2State *state = &this->state[0];
|
|
enum v4l2_buf_type type;
|
|
SpaNodeEvent event;
|
|
int i;
|
|
|
|
if (!state->started)
|
|
return SPA_RESULT_OK;
|
|
|
|
state->started = false;
|
|
|
|
event.type = SPA_NODE_EVENT_TYPE_REMOVE_POLL;
|
|
event.data = &state->poll;
|
|
event.size = sizeof (state->poll);
|
|
this->event_cb (&this->node, &event, this->user_data);
|
|
|
|
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
if (xioctl (state->fd, VIDIOC_STREAMOFF, &type) < 0) {
|
|
perror ("VIDIOC_STREAMOFF");
|
|
return SPA_RESULT_ERROR;
|
|
}
|
|
for (i = 0; i < state->n_buffers; i++) {
|
|
V4l2Buffer *b;
|
|
|
|
b = &state->buffers[i];
|
|
if (!b->outstanding)
|
|
if (xioctl (state->fd, VIDIOC_QBUF, &b->v4l2_buffer) < 0)
|
|
perror ("VIDIOC_QBUF");
|
|
}
|
|
update_state (this, SPA_NODE_STATE_PAUSED);
|
|
|
|
return SPA_RESULT_OK;
|
|
}
|