You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

739 lines
19 KiB

/*
* import_bktr.c
*
* Copyright (C) Jacob Meuser - September 2004
* based on code, hints and suggestions from: Roger Hardiman,
* Steve O'Hara-Smith, Erik Slagter and Stefan Scheffler
*
* 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_bktr.so"
#define MOD_VERSION "v0.0.2 (2004-10-02)"
#define MOD_CODEC "(video) bktr"
#include "transcode.h"
#include "libtc/libtc.h"
#include "libtc/optstr.h"
#include "libtcvideo/tcvideo.h"
/*%*
*%* DESCRIPTION
*%* This module reads video frames from an capture device using bktr module.
*%* This module is designed to work on *BSD. For linux, use the v4l module.
*%*
*%* #BUILD-DEPENDS
*%*
*%* #DEPENDS
*%*
*%* PROCESSING
*%* import/demuxer
*%*
*%* MEDIA
*%* video
*%*
*%* #INPUT
*%*
*%* OUTPUT
*%* YUV420, YUV422P, RGB24
*%*
*%* OPTION
*%* format (string)
*%* selects video normalization.
*%*
*%* OPTION
*%* vsource (string)
*%* selects video source (device dependant input).
*%*
*%* OPTION
*%* asource (string)
*%* selects audio source (device dependant input).
*%*
*%* OPTION
*%* tunerdev (string)
*%* help: selects tuner devince.
*%*/
static int verbose_flag = TC_QUIET;
static int capability_flag = TC_CAP_RGB | TC_CAP_YUV | TC_CAP_YUV422;
#define MOD_PRE bktr
#include "import_def.h"
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/mman.h>
#ifdef HAVE_DEV_IC_BT8XX_H
#include <dev/ic/bt8xx.h>
#else
# ifdef HAVE_DEV_BKTR_IOCTL_BT848_H
# include <dev/bktr/ioctl_meteor.h>
# include <dev/bktr/ioctl_bt848.h>
# else
# ifdef HAVE_MACHINE_IOCTL_BT848_H
# include <machine/ioctl_meteor.h>
# include <machine/ioctl_bt848.h>
# endif
# endif
#endif
static const struct {
char *name;
u_int format;
} formats[] = {
{ "ntsc", METEOR_FMT_NTSC },
{ "pal", METEOR_FMT_PAL },
{ 0 }
};
static const struct {
char *name;
u_int vsource;
} vsources[] = {
{ "composite", METEOR_INPUT_DEV0 },
{ "tuner", METEOR_INPUT_DEV1 },
{ "svideo_comp", METEOR_INPUT_DEV2 },
{ "svideo", METEOR_INPUT_DEV_SVIDEO },
{ "input3", METEOR_INPUT_DEV3 },
{ 0 }
};
static const struct {
char *name;
u_int asource;
} asources[] = {
{ "tuner", AUDIO_TUNER },
{ "external", AUDIO_EXTERN },
{ "internal", AUDIO_INTERN },
{ 0 }
};
void bktr_usage(void);
int bktr_parse_options(char *);
static void catchsignal(int);
int bktr_init(int, const char *, int, int, int, char *);
int bktr_grab(size_t, char *);
int bktr_stop();
static void copy_buf_yuv422(char *, size_t);
static void copy_buf_yuv(char *, size_t);
static void copy_buf_rgb(char *, size_t);
volatile sig_atomic_t bktr_frame_waiting;
sigset_t sa_mask;
uint8_t *bktr_buffer;
size_t bktr_buffer_size;
static int bktr_vfd = -1;
static int bktr_tfd = -1;
char bktr_tuner[128] = "/dev/tuner0";
int bktr_convert;
#define BKTR2RGB 0
#define BKTR2YUV422 1
#define BKTR2YUV 2
u_int bktr_format = 0;
u_int bktr_vsource = METEOR_INPUT_DEV1; /* tuner */
u_int bktr_asource = AUDIO_TUNER;
int bktr_hwfps = 0;
int bktr_mute = 0;
TCVHandle bktr_tcvhandle = 0;
void bktr_usage(void)
{
int i;
tc_log_info(MOD_NAME,
"* Overview");
tc_log_info(MOD_NAME,
" This module grabs video frames from bktr(4) devices");
tc_log_info(MOD_NAME,
" found on BSD systems.");
tc_log_info(MOD_NAME,
"* Options");
tc_log_info(MOD_NAME,
" 'format=<format>' Video norm, valid arguments:");
for (i = 0; formats[i].name; i++)
tc_log_info(MOD_NAME, " %s", formats[i].name);
tc_log_info(MOD_NAME,
" default: driver default");
tc_log_info(MOD_NAME,
" 'vsource=<vsource>' Video source, valid arguments:");
for (i = 0; vsources[i].name; i++)
tc_log_info(MOD_NAME, " %s", vsources[i].name);
tc_log_info(MOD_NAME,
" default: driver default (usually 'composite')");
tc_log_info(MOD_NAME,
" 'asource=<asource>' Audio source, valid arguments:");
for (i = 0; asources[i].name; i++)
tc_log_info(MOD_NAME, " %s", asources[i].name);
tc_log_info(MOD_NAME,
" default: driver default (usually 'tuner')");
tc_log_info(MOD_NAME,
" 'tunerdev=<tunerdev>' Tuner device, default: %s",
bktr_tuner);
tc_log_info(MOD_NAME,
" 'mute' Mute the bktr device, off by default.");
tc_log_info(MOD_NAME,
" 'hwfps' Set frame rate in hardware, off by default.");
tc_log_info(MOD_NAME,
" It's possible to get smoother captures by using");
tc_log_info(MOD_NAME,
" -f to capture in the highest possible frame rate");
tc_log_info(MOD_NAME,
" along with a frame rate filter to get a lower fps.");
tc_log_info(MOD_NAME,
" 'help' Show this help message");
tc_log_info(MOD_NAME, "");
}
int bktr_parse_options(char *options)
{
char format[128];
char vsource[128];
char asource[128];
char tuner[128];
int i;
if (optstr_lookup(options, "help") != NULL) {
bktr_usage();
return(1);
}
if (optstr_lookup(options, "hwfps") != NULL)
bktr_hwfps = 1;
if (optstr_lookup(options, "mute") != NULL)
bktr_mute = 1;
if (optstr_get(options, "format", "%[^:]", &format) >= 0) {
for (i = 0; formats[i].name; i++)
if (strncmp(formats[i].name, format, 128) == 0)
break;
if (formats[i].name)
bktr_format = formats[i].format;
else {
tc_log_warn(MOD_NAME,
"invalid format: %s",
format);
return(1);
}
}
if (optstr_get(options, "vsource", "%[^:]", &vsource) >= 0) {
for (i = 0; vsources[i].name; i++)
if (strncmp(vsources[i].name, vsource, 128) == 0)
break;
if (vsources[i].name)
bktr_vsource = vsources[i].vsource;
else {
tc_log_warn(MOD_NAME,
"invalid vsource: %s",
vsource);
return(1);
}
}
if (optstr_get(options, "asource", "%[^:]", &asource) >= 0) {
for (i = 0; asources[i].name; i++)
if (strncmp(asources[i].name, asource, 128) == 0)
break;
if (asources[i].name)
bktr_asource = asources[i].asource;
else {
tc_log_warn(MOD_NAME,
"invalid asource: %s",
asource);
return(1);
}
}
if (optstr_get(options, "tunerdev", "%[^:]", &tuner) >= 0)
strlcpy(bktr_tuner, tuner, sizeof(bktr_tuner));
return(0);
}
static void catchsignal(int signal)
{
if (signal == SIGUSR1)
bktr_frame_waiting = 1;
}
int bktr_init(int video_codec, const char *video_device,
int width, int height,
int fps, char *options)
{
struct meteor_geomet geo;
struct meteor_pixfmt pxf;
struct sigaction act;
int h_max, w_max;
int rgb_idx = -1;
int yuv422_idx = -1;
int yuv_idx = -1;
int i;
if (options != NULL)
if (bktr_parse_options(options))
return(1);
switch (bktr_format) {
case METEOR_FMT_NTSC: h_max = 480; w_max = 640; break;
case METEOR_FMT_PAL: h_max = 576; w_max = 768; break;
default: h_max = 576; w_max = 768; break;
}
if (width > w_max) {
tc_log_warn(MOD_NAME,
"import width '%d' too large! "
"PAL max width = 768, NTSC max width = 640",
width);
return(1);
}
if (height > h_max) {
tc_log_warn(MOD_NAME,
"import height %d too large! "
"PAL max height = 576, NTSC max height = 480",
height);
return(1);
}
bktr_tcvhandle = tcv_init();
if (!bktr_tcvhandle) {
tcv_log_warn(MOD_NAME, "tcv_init() failed");
return(1);
}
/* set the audio via the tuner. opening the device unmutes it. */
/* closing the device mutes it again. so we hold it open */
bktr_tfd = open(bktr_tuner, O_RDONLY);
if (bktr_tfd < 0) {
tc_log_perror(MOD_NAME, "open tuner");
return(1);
}
if (ioctl(bktr_tfd, BT848_SAUDIO, &bktr_asource) < 0) {
tc_log_perror(MOD_NAME, "BT848_SAUDIO asource");
return(1);
}
if (bktr_mute) {
i = AUDIO_MUTE;
if (ioctl(bktr_tfd, BT848_SAUDIO, &i) < 0) {
tc_log_perror(MOD_NAME, "BT848_SAUDIO AUDIO_MUTE");
return(1);
}
} else {
i = AUDIO_UNMUTE;
if (ioctl(bktr_tfd, BT848_SAUDIO, &i) < 0) {
tc_log_perror(MOD_NAME, "BT848_SAUDIO AUDIO_UNMUTE");
return(1);
}
}
/* open the video device */
bktr_vfd = open(video_device, O_RDONLY);
if (bktr_vfd < 0) {
tc_log_perror(MOD_NAME, video_device);
return(1);
}
/* get the indices of supported formats that transcode can use */
for (i = 0; ; i++) {
pxf.index = i;
if (ioctl(bktr_vfd, METEORGSUPPIXFMT, &pxf) < 0) {
if (errno == EINVAL)
break;
else
return(1);
}
switch(pxf.type) {
case METEOR_PIXTYPE_RGB:
if ((pxf.Bpp == 4) && (pxf.swap_bytes == 0) &&
(pxf.swap_shorts == 0)) {
rgb_idx = pxf.index;
}
break;
case METEOR_PIXTYPE_YUV_PACKED:
if ((pxf.swap_bytes == 0) && (pxf.swap_shorts == 1)) {
yuv422_idx = pxf.index;
}
break;
case METEOR_PIXTYPE_YUV_12:
if ((pxf.swap_bytes == 1) && (pxf.swap_shorts == 1)) {
yuv_idx = pxf.index;
}
break;
case METEOR_PIXTYPE_YUV:
default:
break;
}
}
/* set format, conversion function, and buffer size */
switch(video_codec) {
case CODEC_RGB:
i = rgb_idx;
bktr_convert = BKTR2RGB;
bktr_buffer_size = width * height * 4;
break;
case CODEC_YUV422:
i = yuv422_idx;
bktr_convert = BKTR2YUV422;
bktr_buffer_size = width * height * 2;
break;
case CODEC_YUV:
i = yuv_idx;
bktr_convert = BKTR2YUV;
bktr_buffer_size = width * height * 3 / 2;
break;
default:
tc_log_warn(MOD_NAME,
"video_codec (%d) must be %d or %d or %d\n",
video_codec, CODEC_RGB, CODEC_YUV422, CODEC_YUV);
return(1);
}
if (ioctl(bktr_vfd, METEORSACTPIXFMT, &i) < 0) {
tc_log_perror(MOD_NAME, "METEORSACTPIXFMT");
return(1);
}
/* set the geometry */
geo.rows = height;
geo.columns = width;
geo.frames = 1;
geo.oformat = 0;
if (verbose_flag & TC_DEBUG) {
tc_log_info(MOD_NAME,
"geo.rows = %d, geo.columns = %d, "
"geo.frames = %d, geo.oformat = %ld",
geo.rows, geo.columns,
geo.frames, (long)geo.oformat);
}
if (ioctl(bktr_vfd, METEORSETGEO, &geo) < 0) {
tc_log_perror(MOD_NAME, "METEORSETGEO");
return(1);
}
/* extra options */
if (bktr_vsource) {
if (ioctl(bktr_vfd, METEORSINPUT, &bktr_vsource) < 0) {
tc_log_perror(MOD_NAME, "METEORSINPUT");
return(1);
}
}
if (bktr_format) {
if (ioctl(bktr_vfd, METEORSFMT, &bktr_format) < 0) {
tc_log_perror(MOD_NAME, "METEORSFMT");
return(1);
}
}
if (bktr_hwfps) {
if (ioctl(bktr_vfd, METEORSFPS, &fps) < 0) {
tc_log_perror(MOD_NAME, "METEORSFPS");
return(1);
}
}
/* mmap the buffer */
bktr_buffer = mmap(0, bktr_buffer_size, PROT_READ, MAP_SHARED, bktr_vfd, 0);
if (bktr_buffer == MAP_FAILED) {
tc_log_perror(MOD_NAME, "mmap bktr_buffer");
return(1);
}
/* for sigsuspend() */
sigfillset(&sa_mask);
sigdelset(&sa_mask, SIGUSR1);
sigdelset(&sa_mask, SIGALRM);
/* signal handler to know when data is ready to be read() */
memset(&act, 0, sizeof(act));
sigemptyset(&act.sa_mask);
act.sa_handler = catchsignal;
sigaction(SIGUSR1, &act, NULL);
sigaction(SIGALRM, &act, NULL);
i = SIGUSR1;
if (ioctl(bktr_vfd, METEORSSIGNAL, &i) < 0) {
tc_log_perror(MOD_NAME, "METEORSSIGNAL");
return(1);
}
/* let `er rip! */
i = METEOR_CAP_CONTINOUS;
if (ioctl(bktr_vfd, METEORCAPTUR, &i) < 0) {
tc_log_perror(MOD_NAME, "METEORCAPTUR");
return(1);
}
return(0);
}
int bktr_grab(size_t size, char *dest)
{
/* wait for a "buffer full" signal, but longer than 1 second */
alarm(1);
sigsuspend(&sa_mask);
alarm(0);
if (bktr_frame_waiting) {
bktr_frame_waiting = 0;
if (dest) {
if (verbose_flag & TC_DEBUG) {
tc_log_info(MOD_NAME, "copying %lu bytes, buffer size is %lu",
(unsigned long)size,
(unsigned long)bktr_buffer_size);
}
switch (bktr_convert) {
case BKTR2RGB: copy_buf_rgb(dest, size); break;
case BKTR2YUV422: copy_buf_yuv422(dest, size); break;
case BKTR2YUV: copy_buf_yuv(dest, size); break;
default:
tc_log_warn(MOD_NAME,
"unrecognized video conversion request");
return(1);
break;
}
} else {
tc_log_warn(MOD_NAME,
"no destination buffer to copy frames to");
return(1);
}
} else { /* bktr_frame_waiting */
tc_log_warn(MOD_NAME, "sigalrm");
}
return(0);
}
static void copy_buf_yuv422(char *dest, size_t size)
{
uint8_t *planes;
if (bktr_buffer_size != size) {
tc_log_warn(MOD_NAME,
"buffer sizes do not match (input %lu != output %lu)",
(unsigned long)bktr_buffer_size, (unsigned long)size);
}
tcv_convert(bktr_tcvhandle, bktr_buffer, dest, size/2, 1,
IMG_UYVY, IMG_YUV422P);
}
static void copy_buf_yuv(char *dest, size_t size)
{
int y_size = bktr_buffer_size * 4 / 6;
int u_size = bktr_buffer_size * 1 / 6;
int y_offset = 0;
int u1_offset = y_size + 0;
int u2_offset = y_size + u_size;
if (bktr_buffer_size != size)
tc_log_warn(MOD_NAME,
"buffer sizes do not match (input %lu != output %lu)",
(unsigned long)bktr_buffer_size, (unsigned long)size);
ac_memcpy(dest + y_offset, bktr_buffer + y_offset, y_size);
ac_memcpy(dest + u1_offset, bktr_buffer + u1_offset, u_size);
ac_memcpy(dest + u2_offset, bktr_buffer + u2_offset, u_size);
}
static void copy_buf_rgb(char *dest, size_t size)
{
int i;
/* 24 bit RGB packed into 32 bits (NULL, R, G, B) */
if ((bktr_buffer_size * 3 / 4) != size)
tc_log_warn(MOD_NAME,
"buffer sizes do not match (input %lu != output %lu)",
(unsigned long)bktr_buffer_size * 3 / 4, (unsigned long)size);
/* bktr_buffer_size was set to width * height * 4 (32 bits) */
/* so width * height = bktr_buffer_size / 4 */
tcv_convert(bktr_tcvhandle, bktr_buffer, dest, bktr_buffer_size/4, 1,
IMG_ARGB32, IMG_RGB24);
}
int bktr_stop()
{
int c;
/* shutdown signals first */
c = METEOR_SIG_MODE_MASK;
ioctl(bktr_vfd, METEORSSIGNAL, &c);
alarm(0);
c = METEOR_CAP_STOP_CONT;
ioctl(bktr_vfd, METEORCAPTUR, &c);
c = AUDIO_MUTE;
if (ioctl(bktr_tfd, BT848_SAUDIO, &c) < 0) {
tc_log_perror(MOD_NAME, "BT848_SAUDIO AUDIO_MUTE");
return(1);
}
if (bktr_vfd > 0) {
close(bktr_vfd);
bktr_vfd = -1;
}
if (bktr_tfd > 0) {
close(bktr_tfd);
bktr_tfd = -1;
}
munmap(bktr_buffer, bktr_buffer_size);
return(0);
}
/* ------------------------------------------------------------
*
* open stream
*
* ------------------------------------------------------------*/
MOD_open
{
int ret = TC_IMPORT_OK;
switch (param->flag) {
case TC_VIDEO:
if (verbose_flag & TC_DEBUG) {
tc_log_info(MOD_NAME,
"bktr video grabbing");
}
if (bktr_init(vob->im_v_codec, vob->video_in_file,
vob->im_v_width, vob->im_v_height,
vob->fps, vob->im_v_string)) {
ret = TC_IMPORT_ERROR;
}
break;
case TC_AUDIO:
tc_log_warn(MOD_NAME,
"unsupported request (init audio)\n");
break;
default:
tc_log_warn(MOD_NAME,
"unsupported request (init)\n");
ret = TC_IMPORT_ERROR;
break;
}
return(ret);
}
/* ------------------------------------------------------------
*
* decode stream
*
* ------------------------------------------------------------*/
MOD_decode
{
int ret = TC_IMPORT_OK;
switch (param->flag) {
case TC_VIDEO:
if (bktr_grab(param->size, param->buffer)) {
tc_log_warn(MOD_NAME,
"error in grabbing video");
ret = TC_IMPORT_ERROR;
}
break;
case TC_AUDIO:
tc_log_warn(MOD_NAME,
"unsupported request (decode audio)");
ret = TC_IMPORT_ERROR;
break;
default:
tc_log_warn(MOD_NAME,
"unsupported request (decode)");
ret = TC_IMPORT_ERROR;
break;
}
return(ret);
}
/* ------------------------------------------------------------
*
* close stream
*
* ------------------------------------------------------------*/
MOD_close
{
int ret = TC_IMPORT_OK;
switch (param->flag) {
case TC_VIDEO:
bktr_stop();
break;
case TC_AUDIO:
tc_log_warn(MOD_NAME,
"unsupported request (close audio)");
ret = TC_IMPORT_ERROR;
break;
default:
tc_log_warn(MOD_NAME,
"unsupported request (close)");
ret = TC_IMPORT_ERROR;
break;
}
return(ret);
}