diff options
Diffstat (limited to 'debian/transcode/transcode-1.1.7/import/v4l/import_v4l2.c')
| -rw-r--r-- | debian/transcode/transcode-1.1.7/import/v4l/import_v4l2.c | 1448 |
1 files changed, 1448 insertions, 0 deletions
diff --git a/debian/transcode/transcode-1.1.7/import/v4l/import_v4l2.c b/debian/transcode/transcode-1.1.7/import/v4l/import_v4l2.c new file mode 100644 index 00000000..6a50e7e8 --- /dev/null +++ b/debian/transcode/transcode-1.1.7/import/v4l/import_v4l2.c @@ -0,0 +1,1448 @@ +/* + * import_v4l2.c + * + * By Erik Slagter <erik@slagter.name> Sept 2003 + * some cleanup and tuning support: + * Francesco Romani <fromani@gmail.com> Sept 2008 + * + * 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, 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 GNU Make; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#define MOD_NAME "import_v4l2.so" +#define MOD_VERSION "v1.6.2 (2008-10-25)" +#define MOD_CODEC "(video) v4l2 | (audio) pcm" + +#include "src/transcode.h" + +static int verbose_flag = TC_QUIET; +static int capability_flag = TC_CAP_RGB|TC_CAP_YUV|TC_CAP_YUV422|TC_CAP_PCM; + +/*%* + *%* DESCRIPTION + *%* This module allow to capture video frames through a V4L2 (V4L api version 2) + *%* device. While audio capturing is possible, this kind of usage is discouraged + *%* in favour of OSS or ALSA import modules. + *%* + *%* #BUILD-DEPENDS + *%* + *%* #DEPENDS + *%* + *%* PROCESSING + *%* import/demuxer + *%* + *%* MEDIA + *%* video, audio + *%* + *%* #INPUT + *%* + *%* OUTPUT + *%* YUV420P, YUV422P, RGB24, PCM + *%* + *%* OPTION + *%* ignore_mute (boolean) + *%* disable the device audio muting during the operation. + *%* + *%* OPTION + *%* resync_margin (integer) + *%* threshold audio/video desync (in frames) that triggers resync once reached. + *%* + *%* OPTION + *%* resync_interval (integer) + *%* checks the resync_margin every given amount of frames. + *%* + *%* OPTION + *%* overrun_guard (integer) + *%* flag (default off). Toggles the buffer overrun guard, that prevents crash when capture buffers are full. + *%* + *%* OPTION + *%* crop (string) + *%* forces cropping into selected window (format: WIDTHxHEIGHT+LEFTxTOP) + *%* + *%* OPTION + *%* convert (integer) + *%* forces video frames convertion by using index; use the special value "list" + *%* to get a list of supported conversions. + *%* + *%* OPTION + *%* format (string) + *%* forces output format to given one; use "list" to get a list of supported formats. + *%* + *%* OPTION + *%* input (string) + *%* select the V4L input source. V4L cards have often have more than an input source like, + *%* say, a tv tuner and a composite source. Use "list" parameter to get a list of supported + *%* input sources. + *%* + *%* OPTION + *%* channel (string) + *%* synthonize the V4L tuner to selected TV channel. The channel frequencies are taken by + *%* the module configuration file, and they must be expressed in KHz. + *%*/ + +#define MOD_PRE tc_v4l2 +#include "import_def.h" + +#define _ISOC9X_SOURCE 1 + +#include <sys/soundcard.h> +#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 "libtc/libtc.h" +#include "libtc/optstr.h" +#include "libtc/cfgfile.h" +#include "libtcvideo/tcvideo.h" + +#define TC_V4L2_CHANNELS_FILE "tvchannels.cfg" +#define TC_V4L2_BUFFERS_NUM (32) +#define TC_V4L2_DEFAULT_TUNER_ID (0) + +/* + Changelog + + 1.0.0 EMS first published version + 1.0.1 EMS added YUV422 and RGB support + disable timestamp stuff for now, doesn't work anyways + as long as tc core doesn't support it. + missing mute control is not an error. + 1.0.2 EMS changed parameter passing from -T to -x v4l2=a=x,b=y + try various (native) capture formats before giving up + 1.0.3 EMS changed "videodev2.h" back to <linux/videodev2.h>, + it doesn't work with linux 2.6.0, #defines are wrong. + tibit figure out if the system does have videodev2.h + gcc-2.95 bugfix + tibit check for struct v4l2_buffer + 1.1.0 EMS added dma overrun protection, use overrun_guard=0 to disable + this prevents from crashing the computer when all + capture buffers are full while capturing, by stopping capturing + when > 75% of the buffers are filled. + EMS added YUV422 capture -> YUV420 transcode core conversion + for those whose cards' hardware downsampling to YUV420 conversion is broken + 1.2.0 EMS added a trick to get a better a/v sync in the beginning: + don't start audio (which seems always to be started first) + until video is up and running using a mutex. + This means that must not use -D anymore. + 1.2.1 EMS added bttv driver to blacklist 'does not support cropping + info ioctl' + tibit added mmx version of yuy2_to_uyvy + hacked in alternate fields (#if 0'ed) + fixed a typo (UYUV -> UYVY) + 1.2.2 EMS fixed av sync mutex not yet grabbed problem with "busy" wait + 1.3.0 EMS added cropping cap, removed saa7134 and bttv specific code, not + necessary + 1.3.1 EMS make conversion user-selectable + 1.3.2 EMS removed a/v sync mutex, doesn't work as expected + EMS added explicit colour format / frame rate selection + EMS deleted disfunctional experimental alternating fields code + EMS added experimental code to make sa7134 survive sync glitches + 1.3.3 EMS adapted fast memcpy to new default transcode method + 1.3.4 EMS fixed RGB24 capturing bug when using saa7134. + 1.3.5 EMS test with unrestricted cloning/dropping of frames using resync_interval=0 + adjusted saa7134 audio message to make clear the user must take action + 1.4.0 AC switch to aclib for image conversion + 1.5.0 FR made STYLEish and switched to optstr + 1.6.0 FR tuning support + internal rename v4l2_* -> tc_v4l_* to make te code libv4l-safe. + 1.6.1 FR verbosiness fixes (made module more silent by default). +*/ + +/* TODO: memset() verify and sanitization */ + +typedef enum { + mute_on, + mute_off +} v4l2_mute_op; + +typedef enum { + resync_none, + resync_clone, + resync_drop +} v4l2_resync_op; + +typedef struct tcv4lconversion TCV4LConversion; +struct tcv4lconversion { + int v4l_format; + ImageFormat from; + ImageFormat to; + const char *description; +}; + +typedef struct tcv4lbuffer TCV4LBuffer; +struct tcv4lbuffer { + void *start; + size_t length; +}; + +typedef struct tccroparea_ TCCropArea; +struct tccroparea_ { + int width; + int height; + int left; + int top; +}; + +typedef struct v4l2source_ V4L2Source; +struct v4l2source_ { + int video_fd; + int audio_fd; + + ImageFormat fmt; + int overrun_guard; + int buffers_count; + + int frame_rate; + int width; + int height; + + TCCropArea crop; + int crop_enabled; + int convert_id; + + struct v4l2_input input; + struct v4l2_tuner tuner; + int has_tuner; /* flag */ + + char crop_parm[TC_BUF_MIN]; + char format_name[TC_BUF_MIN]; + char input_name[TC_BUF_MIN]; + char channel_name[TC_BUF_MIN]; + + TCVHandle tcvhandle; + TCV4LBuffer buffers[TC_V4L2_BUFFERS_NUM]; + int saa7134_audio; + int mute_audio; + v4l2_resync_op video_resync_op; + int resync_margin_frames; + int resync_interval_frames; + int video_sequence; + int audio_sequence; + int video_cloned; + int video_dropped; + + uint8_t *resync_previous_frame; +}; + +static TCV4LConversion v4l2_format_conversions[] = { + { V4L2_PIX_FMT_RGB24, IMG_RGB24, IMG_RGB_DEFAULT, "RGB24 [packed] -> RGB [packed] (no conversion)" }, + { V4L2_PIX_FMT_BGR24, IMG_BGR24, IMG_RGB_DEFAULT, "BGR24 [packed] -> RGB [packed]" }, + { V4L2_PIX_FMT_RGB32, IMG_RGBA32, IMG_RGB_DEFAULT, "RGB32 [packed] -> RGB [packed]" }, + { V4L2_PIX_FMT_BGR32, IMG_BGRA32, IMG_RGB_DEFAULT, "BGR32 [packed] -> RGB [packed]" }, + { V4L2_PIX_FMT_GREY, IMG_GRAY8, IMG_RGB_DEFAULT, "8-bit grayscale -> RGB [packed]" }, + { V4L2_PIX_FMT_YUYV, IMG_YUY2, IMG_RGB_DEFAULT, "YUY2 [packed] -> RGB [packed]" }, + /* an exception for the `vivi' v4l testing fake device */ + + { V4L2_PIX_FMT_YYUV, IMG_YUV422P, IMG_YUV422P, "YUV422 [planar] -> YUV422 [planar] (no conversion)" }, + { V4L2_PIX_FMT_UYVY, IMG_UYVY, IMG_YUV422P, "UYVY [packed] -> YUV422 [planar] (no conversion)" }, + { V4L2_PIX_FMT_YUYV, IMG_YUY2, IMG_YUV422P, "YUY2 [packed] -> YUV422 [planar]" }, + { V4L2_PIX_FMT_YUV420, IMG_YUV420P, IMG_YUV422P, "YUV420 [planar] -> YUV422 [planar]" }, + { V4L2_PIX_FMT_YVU420, IMG_YV12, IMG_YUV422P, "YVU420 [planar] -> YUV422 [planar]" }, + { V4L2_PIX_FMT_Y41P, IMG_YUV411P, IMG_YUV422P, "YUV411 [planar] -> YUV422 [planar]" }, + { V4L2_PIX_FMT_GREY, IMG_GRAY8, IMG_YUV422P, "8-bit grayscale -> YUV422 [planar]" }, + + { V4L2_PIX_FMT_YUV420, IMG_YUV420P, IMG_YUV_DEFAULT, "YUV420 [planar] -> YUV420 [planar] (no conversion)" }, + { V4L2_PIX_FMT_YVU420, IMG_YV12, IMG_YUV_DEFAULT, "YVU420 [planar] -> YUV420 [planar]" }, + { V4L2_PIX_FMT_YYUV, IMG_YUV422P, IMG_YUV_DEFAULT, "YUV422 [planar] -> YUV420 [planar]" }, + { V4L2_PIX_FMT_Y41P, IMG_YUV411P, IMG_YUV_DEFAULT, "YUV411 [planar] -> YUV420 [planar]" }, + { V4L2_PIX_FMT_UYVY, IMG_UYVY, IMG_YUV_DEFAULT, "UYVY [packed] -> YUV420 [planar]" }, + { V4L2_PIX_FMT_YUYV, IMG_YUY2, IMG_YUV_DEFAULT, "YUY2 [packed] -> YUV420 [planar]" }, + { V4L2_PIX_FMT_GREY, IMG_GRAY8, IMG_YUV_DEFAULT, "8-bit grayscale -> YUV420 [planar]" }, +}; +#define CONVERSIONS_NUM (sizeof(v4l2_format_conversions) / sizeof(*v4l2_format_conversions)) + +/* ============================================================ + * IMAGE FORMAT CONVERSION ROUTINE + * ============================================================*/ + +static void tc_v4l2_convert(V4L2Source *vs, + uint8_t *source, uint8_t *dest) +{ + if (vs->convert_id >= 0) { + const TCV4LConversion *conv = &v4l2_format_conversions[vs->convert_id]; + tcv_convert(vs->tcvhandle, + source, dest, vs->width, vs->height, + conv->from, conv->to); + } + return; +} + +/* ============================================================ + * UTILS + * ============================================================*/ + +static int tc_v4l2_mute(V4L2Source *vs, v4l2_mute_op value) +{ + if (vs->mute_audio) { + struct v4l2_control control = { + .id = V4L2_CID_AUDIO_MUTE, + .value = value + }; + int ret = ioctl(vs->video_fd, VIDIOC_S_CTRL, &control); + if (ret < 0) { + if (verbose_flag > TC_INFO) + tc_log_perror(MOD_NAME, + "error in muting (ioctl(VIDIOC_S_CTRL) failed)"); + return 0; + } + } + return 1; +} + + +static int tc_v4l2_video_clone_frame(V4L2Source *vs, uint8_t *dest, size_t size) +{ + if (!vs->resync_previous_frame) + memset(dest, 0, size); + else + ac_memcpy(dest, vs->resync_previous_frame, size); + + return 1; +} + +static void tc_v4l2_video_save_frame(V4L2Source *vs, const uint8_t *source, size_t length) +{ + if (!vs->resync_previous_frame) + vs->resync_previous_frame = tc_malloc(length); + + ac_memcpy(vs->resync_previous_frame, source, length); +} + +static int tc_v4l2_video_grab_frame(V4L2Source *vs, uint8_t *dest, size_t length) +{ + static struct v4l2_buffer buffer; + int ix, err = 0, eio = 0; + + // get buffer + buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buffer.memory = V4L2_MEMORY_MMAP; + + err = 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 = 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 = 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; + + // copy frame + if (dest) { + tc_v4l2_convert(vs, vs->buffers[ix].start, dest); + } + + // enqueue buffer again + if (!eio) { + buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buffer.memory = V4L2_MEMORY_MMAP; + buffer.flags = 0; + + err = 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 1; +} + +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 = 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_setup_cropping(V4L2Source *vs, + const char *v4l2_crop_parm, + int width, int height) +{ + size_t slen = strlen(v4l2_crop_parm); + struct v4l2_cropcap cropcap; + struct v4l2_crop crop; + int ret; + + if (!v4l2_crop_parm || !slen) { + return TC_OK; + } + if (sscanf(v4l2_crop_parm, "%ux%u+%ux%u", + &vs->crop.width, &vs->crop.height, + &vs->crop.left, &vs->crop.top) == 4) { + vs->crop_enabled = 1; + } + + if ((verbose_flag > TC_INFO) && vs->crop_enabled) { + tc_log_info(MOD_NAME, "source frame set to: %dx%d+%dx%d", + vs->crop.width, vs->crop.height, + vs->crop.left, vs->crop.top); + } + + cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + ret = ioctl(vs->video_fd, VIDIOC_CROPCAP, &cropcap); + if (ret < 0) { + tc_log_warn(MOD_NAME, + "driver does not support cropping" + "(ioctl(VIDIOC_CROPCAP) returns \"%s\"), disabled", + errno <= sys_nerr ? sys_errlist[errno] : "unknown"); + return TC_ERROR; + } + if (verbose_flag > TC_INFO) { + tc_log_info(MOD_NAME, "frame size: %dx%d", width, height); + tc_log_info(MOD_NAME, "cropcap bounds: %dx%d +%d+%d", + cropcap.bounds.width, cropcap.bounds.height, + cropcap.bounds.left, cropcap.bounds.top); + tc_log_info(MOD_NAME, "cropcap defrect: %dx%d +%d+%d", + cropcap.defrect.width, cropcap.defrect.height, + cropcap.defrect.left, cropcap.defrect.top); + tc_log_info(MOD_NAME, "cropcap pixelaspect: %d/%d", + cropcap.pixelaspect.numerator, + cropcap.pixelaspect.denominator); + } + if ((width > cropcap.bounds.width) + || (height > cropcap.bounds.height) + || (width < 0) || (height < 0)) { + tc_log_error(MOD_NAME, "capturing dimensions exceed" + " maximum crop area: %dx%d", + cropcap.bounds.width, cropcap.bounds.height); + return TC_ERROR; + } + + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + ret = ioctl(vs->video_fd, VIDIOC_G_CROP, &crop); + if (ret < 0) { + tc_log_warn(MOD_NAME, + "driver does not support inquiring cropping" + " parameters (ioctl(VIDIOC_G_CROP) returns \"%s\")", + errno <= sys_nerr ? sys_errlist[errno] : "unknown"); + return -1; + } + + if (verbose_flag > TC_INFO) { + tc_log_info(MOD_NAME, "default cropping: %dx%d +%d+%d", + crop.c.width, crop.c.height, crop.c.left, crop.c.top); + } + + if (vs->crop_enabled) { + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + crop.c.width = vs->crop.width; + crop.c.height = vs->crop.height; + crop.c.left = vs->crop.left; + crop.c.top = vs->crop.top; + + ret = ioctl(vs->video_fd, VIDIOC_S_CROP, &crop); + if (ret < 0) { + tc_log_perror(MOD_NAME, "VIDIOC_S_CROP"); + return -1; + } + + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + ret = ioctl(vs->video_fd, VIDIOC_G_CROP, &crop); + if (ret < 0) { + tc_log_warn(MOD_NAME, + "driver does not support inquering cropping" + " parameters (ioctl(VIDIOC_G_CROP) returns \"%s\")", + errno <= sys_nerr ? sys_errlist[errno] : "unknown"); + return -1; + } + if (verbose_flag > TC_INFO) { + tc_log_info(MOD_NAME, "cropping after set frame source: %dx%d +%d+%d", + crop.c.width, crop.c.height, crop.c.left, crop.c.top); + } + } + return 0; +} + +static int tc_v4l2_video_check_capabilities(V4L2Source *vs) +{ + struct v4l2_capability caps; + int err = 0; + + err = 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, card = %s", + caps.driver, caps.card); + + + + return TC_OK; +} + +static int tc_v4l2_video_setup_image_format(V4L2Source *vs, int width, int height) +{ + TCV4LConversion *fcp = v4l2_format_conversions; + int ix = 0, err = 0, found = 0; + struct v4l2_format format; + + vs->width = width; + vs->height = height; + + for (ix = 0; ix < CONVERSIONS_NUM; ix++) { + if (fcp[ix].to != vs->fmt) + continue; + + if ((vs->convert_id >= 0) && (vs->convert_id != ix)) + continue; + + memset(&format, 0, sizeof(format)); + format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + format.fmt.pix.width = width; + format.fmt.pix.height = height; + format.fmt.pix.pixelformat = fcp[ix].v4l_format; + + err = ioctl(vs->video_fd, VIDIOC_S_FMT, &format); + if (err < 0) { + if (verbose_flag >= TC_INFO) { + tc_log_warn(MOD_NAME, "bad pixel format conversion: %s", fcp[ix].description); + } + } else { + if (verbose_flag > TC_INFO) { + tc_log_info(MOD_NAME, "found pixel format conversion: %s", fcp[ix].description); + } + vs->convert_id = ix; + found = 1; + break; + } + } + + if (!found) { + tc_log_error(MOD_NAME, "no usable pixel format supported by card"); + return TC_ERROR; + } + return TC_OK; +} + +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 = ioctl(vs->video_fd, VIDIOC_S_PARM, &streamparm); + if (err < 0) { + if (verbose_flag) { + 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_TV_standard(V4L2Source *vs) +{ + struct v4l2_standard standard; + v4l2_std_id stdid; + int err = 0; + + err = ioctl(vs->video_fd, VIDIOC_G_STD, &stdid); + if (err < 0) { + tc_log_warn(MOD_NAME, "driver does not support get std (ioctl(VIDIOC_G_STD) returns \"%s\")", + errno <= sys_nerr ? sys_errlist[errno] : "unknown"); + memset(&stdid, 0, sizeof(v4l2_std_id)); + } + + if (stdid & V4L2_STD_525_60) { + vs->frame_rate = 30; + } else if (stdid & V4L2_STD_625_50) { + vs->frame_rate = 25; + } else { + tc_log_info(MOD_NAME, "unknown TV std, defaulting to 50 Hz field rate"); + vs->frame_rate = 25; + } + + if (verbose_flag > TC_INFO) { + int ix; + + for (ix = 0; ix < 128; ix++) { + standard.index = ix; + + err = ioctl(vs->video_fd, VIDIOC_ENUMSTD, &standard); + if (err < 0) { + if (errno == EINVAL) + break; + + tc_log_perror(MOD_NAME, + "error in enumerating TV standards (ioctl(VIDIOC_ENUMSTD) failed)"); + return TC_ERROR; + } + + if (standard.id == stdid) { + tc_log_info(MOD_NAME, "V4L2 device supports format [%s] ", standard.name); + break; + } + } + + tc_log_info(MOD_NAME, "receiving %d frames / sec", vs->frame_rate); + } + return TC_OK; +} + +static int tc_v4l2_video_list_TV_standards(V4L2Source *vs) +{ + struct v4l2_standard standard; + int ix, err = 0; + + for (ix = 0; ix < 128; ix++) { + standard.index = ix; + + err = ioctl(vs->video_fd, VIDIOC_ENUMSTD, &standard); + if (err < 0) { + if (errno == EINVAL) + break; + + tc_log_perror(MOD_NAME, + "error in enumerating TV standards (ioctl(VIDIOC_ENUMSTD) failed)"); + return TC_ERROR; + } + + if (standard.id & vs->input.std) { + tc_log_info(MOD_NAME, "%s", standard.name); + } + } + + return TC_ERROR; +} + +static int tc_v4l2_video_setup_TV_standard(V4L2Source *vs) +{ + struct v4l2_standard standard; + int err, ix = 0, found = 0, supported = 0; + + if (!strcmp(vs->format_name, "list")) { + return tc_v4l2_video_list_TV_standards(vs); + } + + if (strlen(vs->format_name) > 0) { + for (ix = 0; ix < 128; ix++) { + standard.index = ix; + + err = ioctl(vs->video_fd, VIDIOC_ENUMSTD, &standard); + if (err < 0) { + if (errno == EINVAL) + break; + + tc_log_perror(MOD_NAME, + "error in enumerating TV standards (ioctl(VIDIOC_ENUMSTD) failed)"); + return TC_ERROR; + } + + if (!strcasecmp(standard.name, vs->format_name)) { + found = 1; + if (standard.id & vs->input.std) { + supported = 1; + } + } + } + + if (!found) { + tc_log_error(MOD_NAME, "unknown format '%s'", vs->format_name); + return TC_ERROR; + } + if (!supported) { + tc_log_error(MOD_NAME, "current input doesn't support format '%s'", vs->format_name); + return TC_ERROR; + } + + err = ioctl(vs->video_fd, VIDIOC_S_STD, &standard.id); + if (err < 0) { + tc_log_perror(MOD_NAME, "error in setting TV standard (ioctl(VIDIOC_S_STD) failed)"); + return TC_ERROR; + } + + if (verbose_flag > TC_INFO) { + tc_log_info(MOD_NAME, "colour & framerate standard set to: [%s]", standard.name); + } + } + return tc_v4l2_video_get_TV_standard(vs); +} + +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 = 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 = 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 = 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 = 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 = 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 = 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_video_get_tuner_properties(V4L2Source *vs) +{ + int err = 0; + + memset(&(vs->tuner), 0, sizeof(vs->tuner)); + + if (vs->input.type != V4L2_INPUT_TYPE_TUNER) { + if (verbose_flag > TC_INFO) { + tc_log_info(MOD_NAME, "input has not tuner"); + } + } else { + vs->tuner.index = vs->input.tuner; + err = ioctl(vs->video_fd, VIDIOC_G_TUNER, &(vs->tuner)); + if (err) { + tc_log_perror(MOD_NAME, "getting input tuner properties"); + return TC_ERROR; + } + + if (verbose_flag > TC_INFO) { + tc_log_info(MOD_NAME, "input has attached tuner '%s'", vs->tuner.name); + } + vs->has_tuner = 1; + } + return TC_OK; +} + +static int tc_v4l2_video_set_tuner_frequency(V4L2Source *vs) +{ + /* sanity check */ + if (vs->has_tuner && (vs->channel_name && strlen(vs->channel_name))) { + struct v4l2_frequency freq; + int ret = 0, chan_freq = 0; + + TCConfigEntry chan_conf[] = { + { "frequency", &chan_freq, TCCONF_TYPE_INT, 0, 0, 0 }, + /* FIXME: add limits */ + /* End of the config file */ + { NULL, 0, 0, 0, 0, 0 } + }; + + ret = module_read_config(TC_V4L2_CHANNELS_FILE, + vs->channel_name, + chan_conf, MOD_NAME); + if (!ret) { + tc_log_error(MOD_NAME, "Error reading the frequencies" + " configuration file."); + return TC_ERROR; + } + + memset(&freq, 0, sizeof(freq)); + freq.tuner = vs->tuner.index; + freq.type = vs->tuner.type; + /* + * The base unit (see V4L spec) is 62.5 KHz. + * From configuration file we got frequency in KHz. + * In order to safely do an integer division, we multiply + * both operands by 4 (so 62.5*4 = 250) + */ + freq.frequency = (chan_freq * 4) / 250; + if (vs->tuner.capability & V4L2_TUNER_CAP_LOW) { + freq.frequency *= 1000; /* KHz -> Hz */ + } + + ret = ioctl(vs->video_fd, VIDIOC_S_FREQUENCY, &freq); + if (ret != 0) { + tc_log_perror(MOD_NAME, "tuning the channel"); + return TC_ERROR; + } + } + return TC_OK; /* silently skip on error */ +} + +static int tc_v4l2_parse_options(V4L2Source *vs, int layout, const char *options) +{ + char fmt_name[TC_BUF_MIN] = { '\0' }; + int ix = 0; + + vs->mute_audio = TC_TRUE; /* for back compatibility and comfort */ + + switch (layout) { + case CODEC_RGB: + case TC_CODEC_RGB: + vs->fmt = IMG_RGB_DEFAULT; + break; + case CODEC_YUV: + case TC_CODEC_YUV420P: + vs->fmt = IMG_YUV_DEFAULT; + break; + case CODEC_YUV422: + case TC_CODEC_YUV422P: + vs->fmt = IMG_YUV422P; + break; + default: + tc_log_error(MOD_NAME, + "colorspace (0x%X) must be one of RGB, YUV 4:2:0 or YUV 4:2:2", + layout); + return TC_ERROR; + } + + /* reset to defaults */ + vs->convert_id = -1; + + if (options) { + /* flags first */ + if (optstr_lookup(options, "ignore_mute")) { + vs->mute_audio = TC_FALSE; + } + + optstr_get(options, "resync_margin", "%i", &vs->resync_margin_frames); + optstr_get(options, "resync_interval", "%i", &vs->resync_interval_frames); + optstr_get(options, "overrun_guard", "%i", &vs->overrun_guard); + optstr_get(options, "crop", "%[^:]", vs->crop_parm); + optstr_get(options, "format", "%[^:]", vs->format_name); + optstr_get(options, "convert", "%[^:]", fmt_name); + optstr_get(options, "input", "%[^:]", vs->input_name); + optstr_get(options, "channel", "%[^:]", vs->channel_name); + } + + if (!strcmp(fmt_name, "list")) { + TCV4LConversion *fcp = v4l2_format_conversions; + for (ix = 0; ix < CONVERSIONS_NUM; ix++) + tc_log_info(MOD_NAME, + "conversion index: %d = %s", ix, fcp[ix].description); + + return TC_ERROR; + } + if (fmt_name[0]) { /* we can do better */ + vs->convert_id = atoi(fmt_name); + } + + if (verbose_flag > TC_INFO) { + if (!vs->mute_audio) { + tc_log_info(MOD_NAME, "audio muting disabled"); + } + + if (vs->resync_margin_frames == 0) { + tc_log_info(MOD_NAME, "resync disabled"); + } else { + tc_log_info(MOD_NAME, "resync enabled, margin = %d frames, interval = %d frames,", + vs->resync_margin_frames, vs->resync_interval_frames); + } + } + + return TC_OK; +} + +static int tc_v4l2_video_get_input_source(V4L2Source *vs) +{ + int err = 0; + + err = ioctl(vs->video_fd, VIDIOC_G_INPUT, &(vs->input.index)); + if (err) { + tc_log_perror(MOD_NAME, "getting the default input source"); + return TC_ERROR; + } + err = ioctl(vs->video_fd, VIDIOC_ENUMINPUT, &(vs->input)); + if (err) { + tc_log_perror(MOD_NAME, "getting the default input source properties"); + return TC_ERROR; + } + if (verbose_flag > TC_INFO) { + tc_log_info(MOD_NAME, "using input '%s'", vs->input.name); + } + + return TC_OK; +} + +static int tc_v4l2_video_list_input_sources(V4L2Source *vs) +{ + struct v4l2_input input; + int err = 0; + uint32_t i; + + for (i = 0; !err; i++) { + input.index = i; + err = ioctl(vs->video_fd, VIDIOC_ENUMINPUT, &input); + if (!err) { + tc_log_info(MOD_NAME, "input source: '%s'", input.name); + } + } + return TC_ERROR; +} + +static int tc_v4l2_video_setup_input_source(V4L2Source *vs) +{ + if (!strcmp(vs->input_name, "list")) { + return tc_v4l2_video_list_input_sources(vs); + } + + if (strlen(vs->input_name) > 0) { + int err = 0, idx = 0, found = 0; + uint32_t i = 0; + + for (i = 0; !err; i++) { + vs->input.index = i; + err = ioctl(vs->video_fd, VIDIOC_ENUMINPUT, &(vs->input)); + if (!err) { + if (strcasecmp(vs->input.name, vs->input_name) == 0) { + found = 1; + } + } + } + /* sanity checks */ + if (err && errno != EINVAL) { + tc_log_perror(MOD_NAME, "selecting the input source"); + return TC_ERROR; + } + if (!found) { + tc_log_error(MOD_NAME, "unknown input source '%s'", vs->input_name); + return TC_ERROR; + } + + idx = vs->input.index; + err = ioctl(vs->video_fd, VIDIOC_S_INPUT, &idx); + if (err) { + tc_log_perror(MOD_NAME, "setting the input source"); + return TC_ERROR; + } + } + return tc_v4l2_video_get_input_source(vs); +} + +/* ============================================================ + * 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->tcvhandle = tcv_init(); + if (!vs->tcvhandle) { + tc_log_error(MOD_NAME, "tcv_init() failed"); + return TC_ERROR; + } + + vs->video_fd = 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_setup_input_source(vs); + RETURN_IF_FAILED(ret); + + ret = tc_v4l2_video_setup_TV_standard(vs); + RETURN_IF_FAILED(ret); + + ret = tc_v4l2_video_get_tuner_properties(vs); + RETURN_IF_FAILED(ret); + + tc_v4l2_video_set_tuner_frequency(vs); + RETURN_IF_FAILED(ret); + + ret = tc_v4l2_video_setup_cropping(vs, vs->crop_parm, width, height); + 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); + + if (!tc_v4l2_mute(vs, mute_on)) + return TC_ERROR; + + return tc_v4l2_capture_start(vs); +} + +static int tc_v4l2_video_get_frame(V4L2Source *vs, uint8_t *data, size_t size) +{ + if (vs->overrun_guard) { + int buffers_filled = tc_v4l2_video_count_buffers(vs); + + 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); + + return tc_v4l2_capture_stop(vs); + } + } + + switch (vs->video_resync_op) { + case resync_clone: + if (!tc_v4l2_video_clone_frame(vs, data, size)) + return 1; + break; + + case resync_drop: + if (!tc_v4l2_video_grab_frame(vs, 0, 0)) + return 1; + if (!tc_v4l2_video_grab_frame(vs, data, size)) + return 1; + break; + + case resync_none: + if (!tc_v4l2_video_grab_frame(vs, data, size)) + return 1; + break; + + default: + tc_log_error(MOD_NAME, "impossible case"); + return 1; + } + + vs->video_resync_op = resync_none; + + if ((vs->resync_margin_frames != 0) + && (vs->video_sequence != 0) + && (vs->audio_sequence != 0) + && ((vs->resync_interval_frames == 0) || (vs->video_sequence % vs->resync_interval_frames) == 0)) { + if (abs(vs->audio_sequence - vs->video_sequence) > vs->resync_margin_frames) { + if (vs->audio_sequence > vs->video_sequence) { + tc_v4l2_video_save_frame(vs, data, size); + vs->video_cloned++; + vs->video_resync_op = resync_clone; + } else { + vs->video_resync_op = resync_drop; + vs->video_dropped++; + } + } + + if (vs->video_resync_op != resync_none && (verbose_flag > TC_INFO)) { + tc_log_msg(MOD_NAME, "OP: %s VS/AS: %d/%d C/D: %d/%d", + vs->video_resync_op == resync_drop ? "drop" : "clone", + vs->video_sequence, vs->audio_sequence, + vs->video_cloned, vs->video_dropped); + } + } + + vs->video_sequence++; + + return TC_OK; +} + +static int tc_v4l2_video_grab_stop(V4L2Source *vs) +{ + int ix, ret; + + if (!tc_v4l2_mute(vs, mute_off)) + return 1; + + ret = tc_v4l2_capture_stop(vs); + RETURN_IF_FAILED(ret); + + for (ix = 0; ix < vs->buffers_count; ix++) + munmap(vs->buffers[ix].start, vs->buffers[ix].length); + + close(vs->video_fd); + vs->video_fd = -1; + + tc_free(vs->resync_previous_frame); + vs->resync_previous_frame = NULL; + + tcv_free(vs->tcvhandle); + vs->tcvhandle = 0; + + return TC_OK; +} + +static int tc_v4l2_audio_init(V4L2Source *vs, const char *device, + int rate, int bits, int channels) +{ + int version, tmp, err = 0; + + vs->audio_fd = open(device, O_RDONLY, 0); + if (vs->audio_fd < 0) { + tc_log_perror(MOD_NAME, "open audio device"); + return TC_ERROR; + } + + if (!strcmp(device, "/dev/null") + || !strcmp(device, "/dev/zero")) { + return TC_OK; + } + + if (bits != 8 && bits != 16) { + tc_log_error(MOD_NAME, "bits/sample must be 8 or 16"); + return TC_ERROR; + } + + err = ioctl(vs->audio_fd, OSS_GETVERSION, &version); + if (err < 0) { + tc_log_perror(MOD_NAME, "OSS_GETVERSION"); + return TC_ERROR; + } + + tmp = (bits == 8) ?AFMT_U8 :AFMT_S16_LE; + + err = ioctl(vs->audio_fd, SNDCTL_DSP_SETFMT, &tmp); + if (err < 0) { + tc_log_perror(MOD_NAME, "SNDCTL_DSP_SETFMT"); + return TC_ERROR; + } + + err = ioctl(vs->audio_fd, SNDCTL_DSP_CHANNELS, &channels); + if (err < 0) { + tc_log_perror(MOD_NAME, "SNDCTL_DSP_CHANNELS"); + return TC_ERROR; + } + + // check for saa7134 + // this test will: set sampling to "0 khz", check if this returns "OK" and "32 khz" + tmp = 0; + /* + * http://manuals.opensound.com/developer/SNDCTL_DSP_SPEED.html : + * Description + * This ioctl call selects the sampling rate (in Hz) to be used with the stream. + * After the call the active sampling rate will be returned in the variable + * pointed by the argument. The application must check this value and adjust + * it's operation depending on it. + * + */ + err = ioctl(vs->audio_fd, SNDCTL_DSP_SPEED, &tmp); + if (err >= 0) { + if (tmp == 0 || tmp == 32000) + vs->saa7134_audio = 1; + } + + if (vs->saa7134_audio) { + if(verbose_flag) + tc_log_info(MOD_NAME, + "Audio input from saa7134 detected, you should " + "set audio sample rate to 32 Khz using -e"); + } else { + /* this is the real sample rate setting */ + tmp = rate; + err = ioctl(vs->audio_fd, SNDCTL_DSP_SPEED, &tmp); + if (err < 0) { + tc_log_perror(MOD_NAME, "SNDCTL_DSP_SPEED"); + return TC_ERROR; + } + if (tmp != rate) { + tc_log_warn(MOD_NAME, "sample rate requested=%i obtained=%i", + rate, tmp); + } + } + + return TC_OK; +} + +static int tc_v4l2_audio_grab_frame(V4L2Source *vs, uint8_t *buffer, size_t size) +{ + int left = size; + int offset = 0; + int received; + + while (left > 0) { + received = read(vs->audio_fd, buffer + offset, left); + + if (received == 0) + tc_log_warn(MOD_NAME, "audio grab: received == 0"); + + if (received < 0) { + if (errno == EINTR) { + received = 0; + } else { + tc_log_perror(MOD_NAME, "read audio"); + return TC_ERROR; + } + } + + if (received > left) { + tc_log_error(MOD_NAME, + "read returns more bytes than requested! (requested: %i, returned: %i", + left, received); + return TC_ERROR; + } + + offset += received; + left -= received; + } + + vs->audio_sequence++; + + return TC_OK; +} + +static int tc_v4l2_audio_grab_stop(V4L2Source *vs) +{ + close(vs->audio_fd); + + if (verbose_flag) { + tc_log_msg(MOD_NAME, "Totals: sequence V/A: %d/%d, frames C/D: %d/%d", + vs->video_sequence, vs->audio_sequence, + vs->video_cloned, vs->video_dropped); + } + + 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 if(param->flag == TC_AUDIO) { + if (tc_v4l2_audio_init(&VS, + vob->audio_in_file, + vob->a_rate, vob->a_bits, vob->a_chan)) { + 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 if (param->flag == TC_AUDIO) { + if (tc_v4l2_audio_grab_frame(&VS, param->buffer, param->size)) { + tc_log_error(MOD_NAME, "error in grabbing audio"); + 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 if (param->flag == TC_AUDIO) { + tc_v4l2_audio_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: + */ + |
