diff options
Diffstat (limited to 'debian/transcode/transcode-1.1.7/import/v4l/import_v4lcam.c')
| -rw-r--r-- | debian/transcode/transcode-1.1.7/import/v4l/import_v4lcam.c | 663 |
1 files changed, 663 insertions, 0 deletions
diff --git a/debian/transcode/transcode-1.1.7/import/v4l/import_v4lcam.c b/debian/transcode/transcode-1.1.7/import/v4l/import_v4lcam.c new file mode 100644 index 00000000..5dfa14ec --- /dev/null +++ b/debian/transcode/transcode-1.1.7/import/v4l/import_v4lcam.c @@ -0,0 +1,663 @@ +/* + * import_v4lcam.c -- imports video frames from v4l2 using libv4l* + * with special focus on webcams. + * (C) 2009-2010 Francesco Romani <fromani at gmail dot com> + * based on import_v4l2.c code, which is + * (C) Erik Slagter <erik@slagter.name> Sept 2003 + * + * This file is part of transcode, a video stream processing tool. + * + * transcode is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * transcode 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#define MOD_NAME "import_v4lcam.so" +#define MOD_VERSION "v0.1.0 (2009-08-30)" +#define MOD_CODEC "(video) v4l2" + +#include "src/transcode.h" + + +static int verbose_flag = TC_QUIET; +static int capability_flag = TC_CAP_RGB|TC_CAP_YUV; + +/* + * Briefing + * + * Q: why a new module? + * Q: why don't just enhance import_v4l2? + * A: because I want take this chance to do a fresh start with a v4l import + * module, so we can get rid of some old code, try to redesign/rewrite + * it in a better way, experimenting new designes and so on. I want the + * freedom to add special code and special design decisions useful for + * webcams only (or just mostly). import_v4l2 will stay and the experiments + * which time proven to be good will be backported. + * Eventually, v4lcam can be merged into the main v4l module. + * + * Q: there is some duplicate code with import_v4l2.c. Why? + * A: because I'm taking advantage of being a separate module, and because + * I'm experimenting new stuff. After a while, the remaining duplicated + * parts will be merged in a common source. + * + * Q: why libv4lconvert? We can just extend aclib. + * A: no objections of course (but no time either!). However, libv4lconvert + * has IMHO a slightly different focus wrt aclib and I think it's just + * fine to use both of them. As example, the MJPG->I420 conversion + * should NOT enter into aclib (eventually v4lcam can emit MJPG frames + * too, when the module pipeline get enhanced enough). + + */ + +/*%* + *%* DESCRIPTION + *%* This module allow to capture video frames through a V4L2 (V4L api version 2) + *%* device. This module is specialized for webcam devices. + *%* + *%* #BUILD-DEPENDS + *%* + *%* #DEPENDS + *%* + *%* PROCESSING + *%* import/demuxer + *%* + *%* MEDIA + *%* video + *%* + *%* #INPUT + *%* + *%* OUTPUT + *%* YUV420P, RGB24 + *%*/ + +#define MOD_PRE tc_v4lcam +#include "import_def.h" + +#define _ISOC9X_SOURCE 1 + +#include <sys/ioctl.h> +#include <sys/mman.h> + +#include <linux/types.h> + +// The v4l2_buffer struct check is because some distributions protect that +// struct in videodev2 with a #ifdef __KERNEL__ (SuSE 9.0) + +#if defined(HAVE_LINUX_VIDEODEV2_H) && defined(HAVE_STRUCT_V4L2_BUFFER) +#define _LINUX_TIME_H +#include <linux/videodev2.h> +#else +#include "videodev2.h" +#endif + +#include "libv4l2.h" +#include "libv4lconvert.h" + +#include "libtc/libtc.h" +#include "libtc/optstr.h" + +#define TC_V4L2_BUFFERS_NUM (32) + +/* TODO: memset() verify and sanitization */ + +typedef struct tcv4lbuffer TCV4LBuffer; +struct tcv4lbuffer { + void *start; + size_t length; +}; + +typedef struct v4l2source_ V4L2Source; + +/* FIXME: naming */ +typedef int (*TCV4LFetchDataFn)(V4L2Source *vs, + uint8_t *src, int src_len, + uint8_t *dst, int dst_len); + +struct v4l2source_ { + int video_fd; + int video_sequence; + + int v4l_dst_csp; + struct v4l2_format v4l_dst_fmt; + struct v4l2_format v4l_src_fmt; + struct v4lconvert_data *v4l_convert; + int buffers_count; + + int width; + int height; + + TCV4LFetchDataFn fetch_data; + TCV4LBuffer buffers[TC_V4L2_BUFFERS_NUM]; +}; + +static int tc_v4l2_fetch_data_memcpy(V4L2Source *vs, + uint8_t *src, int src_len, + uint8_t *dst, int dst_len) +{ + int ret = TC_ERROR; + if (dst_len >= src_len) { + ac_memcpy(dst, src, src_len); + ret = TC_OK; + } + return ret; +} + +static int tc_v4l2_fetch_data_v4lconv(V4L2Source *vs, + uint8_t *src, int src_len, + uint8_t *dst, int dst_len) +{ + int err = v4lconvert_convert(vs->v4l_convert, + &(vs->v4l_src_fmt), + &(vs->v4l_dst_fmt), + src, src_len, dst, dst_len); + + return (err == -1) ?TC_ERROR :TC_OK; /* FIXME */ +} + +/* FIXME: reorganize the layout */ +static int tc_v4l2_video_grab_frame(V4L2Source *vs, uint8_t *dest, size_t length) +{ + static struct v4l2_buffer buffer; /* FIXME */ + int ix, err = 0, eio = 0, ret = TC_ERROR; + + // get buffer + buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buffer.memory = V4L2_MEMORY_MMAP; + + err = v4l2_ioctl(vs->video_fd, VIDIOC_DQBUF, &buffer); + if (err < 0) { + tc_log_perror(MOD_NAME, + "error in setup grab buffer (ioctl(VIDIOC_DQBUF) failed)"); + + if (errno != EIO) { + return TC_OK; + } else { + eio = 1; + + for (ix = 0; ix < vs->buffers_count; ix++) { + buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buffer.memory = V4L2_MEMORY_MMAP; + buffer.index = ix; + buffer.flags = 0; + + err = v4l2_ioctl(vs->video_fd, VIDIOC_DQBUF, &buffer); + if (err < 0) + tc_log_perror(MOD_NAME, + "error in recovering grab buffer (ioctl(DQBUF) failed)"); + } + + for (ix = 0; ix < vs->buffers_count; ix++) { + buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buffer.memory = V4L2_MEMORY_MMAP; + buffer.index = ix; + buffer.flags = 0; + + err = v4l2_ioctl(vs->video_fd, VIDIOC_QBUF, &buffer); + if (err < 0) + tc_log_perror(MOD_NAME, + "error in recovering grab buffer (ioctl(QBUF) failed)"); + } + } + } + + ix = buffer.index; + + ret = vs->fetch_data(vs, + vs->buffers[ix].start, buffer.bytesused, + dest, length); + + // enqueue buffer again + if (!eio) { + buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buffer.memory = V4L2_MEMORY_MMAP; + buffer.flags = 0; + + err = v4l2_ioctl(vs->video_fd, VIDIOC_QBUF, &buffer); + if (err < 0) { + tc_log_perror(MOD_NAME, "error in enqueuing buffer (ioctl(VIDIOC_QBUF) failed)"); + return TC_OK; + } + } + + return ret; +} + +static int tc_v4l2_video_count_buffers(V4L2Source *vs) +{ + struct v4l2_buffer buffer; + int ix, ret, buffers_filled = 0; + + for (ix = 0; ix < vs->buffers_count; ix++) { + buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buffer.memory = V4L2_MEMORY_MMAP; + buffer.index = ix; + + ret = v4l2_ioctl(vs->video_fd, VIDIOC_QUERYBUF, &buffer); + if (ret < 0) { + tc_log_perror(MOD_NAME, + "error in querying buffers" + " (ioctl(VIDIOC_QUERYBUF) failed)"); + return -1; + } + + if (buffer.flags & V4L2_BUF_FLAG_DONE) + buffers_filled++; + } + return buffers_filled; +} + +static int tc_v4l2_video_check_capabilities(V4L2Source *vs) +{ + struct v4l2_capability caps; + int err = 0; + + err = v4l2_ioctl(vs->video_fd, VIDIOC_QUERYCAP, &caps); + if (err < 0) { + tc_log_error(MOD_NAME, "driver does not support querying capabilities"); + return TC_ERROR; + } + + if (!(caps.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { + tc_log_error(MOD_NAME, "driver does not support video capture"); + return TC_ERROR; + } + + if (!(caps.capabilities & V4L2_CAP_STREAMING)) { + tc_log_error(MOD_NAME, "driver does not support streaming (mmap) video capture"); + return TC_ERROR; + } + + if (verbose_flag > TC_INFO) { + tc_log_info(MOD_NAME, "v4l2 video grabbing, driver = %s, device = %s", + caps.driver, caps.card); + } + + return TC_OK; +} + +#define pixfmt_to_fourcc(pixfmt, fcc) do { \ + fcc[0] = (pixfmt >> 0 ) & 0xFF; \ + fcc[1] = (pixfmt >> 8 ) & 0xFF; \ + fcc[2] = (pixfmt >> 16) & 0xFF; \ + fcc[3] = (pixfmt >> 24) & 0xFF; \ +} while (0) + +static int tc_v4l2_video_setup_image_format(V4L2Source *vs, int width, int height) +{ + int err = 0; + + vs->width = width; + vs->height = height; + + vs->v4l_convert = v4lconvert_create(vs->video_fd); + if (!vs->v4l_convert) { + return TC_ERROR; + } + + memset(&(vs->v4l_dst_fmt), 0, sizeof(vs->v4l_dst_fmt)); + vs->v4l_dst_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vs->v4l_dst_fmt.fmt.pix.width = width; + vs->v4l_dst_fmt.fmt.pix.height = height; + vs->v4l_dst_fmt.fmt.pix.pixelformat = vs->v4l_dst_csp; + + err = v4lconvert_try_format(vs->v4l_convert, + &(vs->v4l_dst_fmt), &(vs->v4l_src_fmt)); + if (err) { + tc_log_error(MOD_NAME, "unable to match formats: %s", + v4lconvert_get_error_message(vs->v4l_convert)); + return TC_ERROR; + } + + err = v4l2_ioctl(vs->video_fd, VIDIOC_S_FMT, &(vs->v4l_src_fmt)); + if (err < 0) { + tc_log_error(MOD_NAME, "error while setting the cam image format"); + return TC_ERROR; + } + + if (!v4lconvert_needs_conversion(vs->v4l_convert, + &(vs->v4l_src_fmt), + &(vs->v4l_dst_fmt))) { + tc_log_info(MOD_NAME, "fetch frames directly"); + vs->fetch_data = tc_v4l2_fetch_data_memcpy; + /* Into the near future we should aim for zero-copy. -- FR */ + } else { + char src_fcc[5] = { '\0' }; + char dst_fcc[5] = { '\0' }; + + pixfmt_to_fourcc(vs->v4l_src_fmt.fmt.pix.pixelformat, src_fcc); + pixfmt_to_fourcc(vs->v4l_dst_fmt.fmt.pix.pixelformat, dst_fcc); + + tc_log_info(MOD_NAME, "fetch frames using libv4lconvert " + "[%s] -> [%s]", + src_fcc, dst_fcc); + vs->fetch_data = tc_v4l2_fetch_data_v4lconv; + } + + return TC_OK; +} + +static void tc_v4l2_teardown_image_format(V4L2Source *vs) +{ + if (vs->v4l_convert) { + v4lconvert_destroy(vs->v4l_convert); + vs->v4l_convert = NULL; + } +} + +static int tc_v4l2_video_setup_stream_parameters(V4L2Source *vs, int fps) +{ + struct v4l2_streamparm streamparm; + int err = 0; + + memset(&streamparm, 0, sizeof(streamparm)); + streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + streamparm.parm.capture.capturemode = 0; + streamparm.parm.capture.timeperframe.numerator = 1e7; + streamparm.parm.capture.timeperframe.denominator = fps; + + err = v4l2_ioctl(vs->video_fd, VIDIOC_S_PARM, &streamparm); + if (err < 0) { + tc_log_warn(MOD_NAME, "driver does not support setting parameters" + " (ioctl(VIDIOC_S_PARM) returns \"%s\")", + errno <= sys_nerr ? sys_errlist[errno] : "unknown"); + } + return TC_OK; +} + +static int tc_v4l2_video_get_capture_buffer_count(V4L2Source *vs) +{ + struct v4l2_requestbuffers reqbuf; + int err = 0; + + reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + reqbuf.memory = V4L2_MEMORY_MMAP; + reqbuf.count = TC_V4L2_BUFFERS_NUM; + + err = v4l2_ioctl(vs->video_fd, VIDIOC_REQBUFS, &reqbuf); + if (err < 0) { + tc_log_perror(MOD_NAME, "VIDIOC_REQBUFS"); + return TC_ERROR; + } + + vs->buffers_count = TC_MIN(reqbuf.count, TC_V4L2_BUFFERS_NUM); + + if (vs->buffers_count < 2) { + tc_log_error(MOD_NAME, "not enough buffers for capture"); + return TC_ERROR; + } + + if (verbose_flag > TC_INFO) { + tc_log_info(MOD_NAME, "%i buffers available (maximum supported: %i)", + vs->buffers_count, TC_V4L2_BUFFERS_NUM); + } + return TC_OK; +} + + +static int tc_v4l2_video_setup_capture_buffers(V4L2Source *vs) +{ + struct v4l2_buffer buffer; + int ix, err = 0; + + /* map the buffers */ + for (ix = 0; ix < vs->buffers_count; ix++) { + buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buffer.memory = V4L2_MEMORY_MMAP; + buffer.index = ix; + + err = v4l2_ioctl(vs->video_fd, VIDIOC_QUERYBUF, &buffer); + if (err < 0) { + tc_log_perror(MOD_NAME, "VIDIOC_QUERYBUF"); + return TC_ERROR; + } + + vs->buffers[ix].length = buffer.length; + vs->buffers[ix].start = v4l2_mmap(0, buffer.length, + PROT_READ|PROT_WRITE, MAP_SHARED, + vs->video_fd, buffer.m.offset); + + if (vs->buffers[ix].start == MAP_FAILED) { + tc_log_perror(MOD_NAME, "mmap"); + return TC_ERROR; + } + } + + /* then enqueue them all */ + for (ix = 0; ix < vs->buffers_count; ix++) { + buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buffer.memory = V4L2_MEMORY_MMAP; + buffer.index = ix; + + err = v4l2_ioctl(vs->video_fd, VIDIOC_QBUF, &buffer); + if (err < 0) { + tc_log_perror(MOD_NAME, "VIDIOC_QBUF"); + return TC_ERROR; + } + } + + return TC_OK; +} + +static int tc_v4l2_capture_start(V4L2Source *vs) +{ + int err = 0, arg = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + err = v4l2_ioctl(vs->video_fd, VIDIOC_STREAMON, &arg); + if (err < 0) { + /* ugh, needs VIDEO_CAPTURE */ + tc_log_perror(MOD_NAME, "VIDIOC_STREAMON"); + return TC_ERROR; + } + + return TC_OK; +} + +static int tc_v4l2_capture_stop(V4L2Source *vs) +{ + int err = 0, arg = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + err = v4l2_ioctl(vs->video_fd, VIDIOC_STREAMOFF, &arg); + if (err < 0) { + /* ugh, needs VIDEO_CAPTURE */ + tc_log_perror(MOD_NAME, "VIDIOC_STREAMOFF"); + return TC_ERROR; + } + + return TC_OK; +} + +static int tc_v4l2_parse_options(V4L2Source *vs, int layout, const char *options) +{ + switch (layout) { + case CODEC_RGB: + case TC_CODEC_RGB: + vs->v4l_dst_csp = V4L2_PIX_FMT_RGB24; + break; + case CODEC_YUV: + case TC_CODEC_YUV420P: + vs->v4l_dst_csp = V4L2_PIX_FMT_YUV420; + break; + case CODEC_YUV422: + case TC_CODEC_YUV422P: + vs->v4l_dst_csp = V4L2_PIX_FMT_YYUV; + break; + default: + tc_log_error(MOD_NAME, + "colorspace (0x%X) must be one of" + " RGB24, YUV 4:2:0 or YUV 4:2:2", + layout); + return TC_ERROR; + } + + return TC_OK; +} + +/* ============================================================ + * V4L2 CORE + * ============================================================*/ + +#define RETURN_IF_FAILED(RET) do { \ + if ((RET) != TC_OK) { \ + return (RET); \ + } \ +} while (0) + +static int tc_v4l2_video_init(V4L2Source *vs, + int layout, const char *device, + int width, int height, int fps, + const char *options) +{ + int ret = tc_v4l2_parse_options(vs, layout, options); + RETURN_IF_FAILED(ret); + + vs->video_fd = v4l2_open(device, O_RDWR, 0); + if (vs->video_fd < 0) { + tc_log_error(MOD_NAME, "cannot open video device %s", device); + return TC_ERROR; + } + + ret = tc_v4l2_video_check_capabilities(vs); + RETURN_IF_FAILED(ret); + + ret = tc_v4l2_video_setup_image_format(vs, width, height); + RETURN_IF_FAILED(ret); + + ret = tc_v4l2_video_setup_stream_parameters(vs, fps); + RETURN_IF_FAILED(ret); + + ret = tc_v4l2_video_get_capture_buffer_count(vs); + RETURN_IF_FAILED(ret); + + ret = tc_v4l2_video_setup_capture_buffers(vs); + RETURN_IF_FAILED(ret); + + return tc_v4l2_capture_start(vs); +} + +static int tc_v4l2_video_get_frame(V4L2Source *vs, uint8_t *data, size_t size) +{ + int ret; + int buffers_filled = tc_v4l2_video_count_buffers(vs); + + if (buffers_filled == -1) { + tc_log_warn(MOD_NAME, "unable to get the capture buffers count," + " assuming OK"); + buffers_filled = 0; + } + + if (buffers_filled > (vs->buffers_count * 3 / 4)) { + tc_log_error(MOD_NAME, "running out of capture buffers (%d left from %d total), " + "stopping capture", + vs->buffers_count - buffers_filled, + vs->buffers_count); + + ret = tc_v4l2_capture_stop(vs); + } else { + ret = tc_v4l2_video_grab_frame(vs, data, size); + vs->video_sequence++; + } + + return ret; +} + +static int tc_v4l2_video_grab_stop(V4L2Source *vs) +{ + int ix, ret; + + tc_v4l2_teardown_image_format(vs); + + ret = tc_v4l2_capture_stop(vs); + RETURN_IF_FAILED(ret); + + for (ix = 0; ix < vs->buffers_count; ix++) + v4l2_munmap(vs->buffers[ix].start, vs->buffers[ix].length); + + v4l2_close(vs->video_fd); + vs->video_fd = -1; + + return TC_OK; +} + +/* ============================================================ + * TRANSCODE INTERFACE + * ============================================================*/ + +static V4L2Source VS; + +/* ------------------------------------------------------------ + * open stream + * ------------------------------------------------------------*/ + +MOD_open +{ + if (param->flag == TC_VIDEO) { + if (tc_v4l2_video_init(&VS, + vob->im_v_codec, vob->video_in_file, + vob->im_v_width, vob->im_v_height, + vob->fps, vob->im_v_string)) { + return TC_ERROR; + } + } else { + tc_log_error(MOD_NAME, "unsupported request (init)"); + return TC_ERROR; + } + + return TC_OK; +} + +/* ------------------------------------------------------------ + * decode stream + * ------------------------------------------------------------*/ + +MOD_decode +{ + if (param->flag == TC_VIDEO) { + if (tc_v4l2_video_get_frame(&VS, param->buffer, param->size)) { + tc_log_error(MOD_NAME, "error in grabbing video"); + return TC_ERROR; + } + } else { + tc_log_error(MOD_NAME, "unsupported request (decode)"); + return TC_ERROR; + } + + return TC_OK; +} + +/* ------------------------------------------------------------ + * close stream + * ------------------------------------------------------------*/ + +MOD_close +{ + if (param->flag == TC_VIDEO) { + tc_v4l2_video_grab_stop(&VS); + } else { + tc_log_error(MOD_NAME, "unsupported request (close)"); + return TC_ERROR; + } + + return TC_OK; +} + +/*************************************************************************/ + +/* + * Local variables: + * c-file-style: "stroustrup" + * c-file-offsets: ((case-label . *) (statement-case-intro . *)) + * indent-tabs-mode: nil + * End: + * + * vim: expandtab shiftwidth=4: + */ + |
