summaryrefslogtreecommitdiffstats
path: root/debian/transcode/transcode-1.1.7/import/v4l/import_v4lcam.c
diff options
context:
space:
mode:
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.c663
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:
+ */
+