Commit ba8a0334 authored by Laurent Aimar's avatar Laurent Aimar
Browse files

* mjpeg grabbing added, thanks to Paul Forgey <paulf at aphrodite dot com>

 that had done all the work.

Paul Forgey's notes:
--------------------

Unfortunatley, the v4l interface shows how much it sucks here.  I can't
wait for v4l2 to become more common.  Anyway, the mjpeg mechanism
captures completely differently than the uncompressed frame capture
mechanism.  This means the code has to split off a bunch to do similar
things depending on mjpeg being set or not.  For some reason, I can't
use non v4l2 calls to capture uncompressed frames from the lml33,
however I do have another bttv card on my system to test that I didn't
break the non-mjpeg stuff.

The Zoran encoders don't use square pixels.  So the 4:3 (or 16:9)
picture is going to show up as 720x480.  I don't know my way around the
code well enough to know how to specify a non-square pixel aspect
ratio.  If there isn't a way to do it, it would be nice to be able to
specify as an option the aspect ratio of the sourc.  Either 4:3 or 16:9
in the case of anamorphic S-Video, which does occur.

I stole the videodev_mjpeg.h header from the lavrec project.  Despite
the linux kernels having support for the Zoran cards, there doesn't seem
to be a reliable way to pick up this header, which is probably why
lavrec did it this way.  Since it only defines ioctl definitions, I
don't think using it is a legal problem.

I also modified the audio handling a bit.  If the card supports audio,
great, we'll set the card's parameters as we did before.  But if adev=
is specified, use it regardless.  The reason to do this is to capture
audio from the sound card with video coming from, say, a web cam or in
my case, an encoder card which doesn't have any audio capabilities at
all.
parent aaaf677b
SOURCES_v4l = modules/access/v4l/v4l.c
SOURCES_v4l = modules/access/v4l/v4l.c \
modules/access/v4l/videodev_mjpeg.h
......@@ -2,9 +2,11 @@
* v4l.c : Video4Linux input module for vlc
*****************************************************************************
* Copyright (C) 2002 VideoLAN
* $Id: v4l.c,v 1.16 2003/05/15 22:27:36 massiot Exp $
* $Id: v4l.c,v 1.17 2003/05/31 01:23:29 fenrir Exp $
*
* Author: Samuel Hocevar <sam@zoy.org>
* Laurent Aimar <fenrir@via.ecp.fr>
* Paul Forgey <paulf at aphrodite dot com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -43,6 +45,7 @@
#include <fcntl.h>
#include <linux/videodev.h>
#include "videodev_mjpeg.h"
#include <sys/soundcard.h>
......@@ -86,6 +89,22 @@ vlc_module_end();
/****************************************************************************
* I. Access Part
****************************************************************************/
#define MJPEG_BUFFER_SIZE (256*1024)
struct quicktime_mjpeg_app1
{
uint32_t i_reserved; /* set to 0 */
uint32_t i_tag; /* 'mjpg' */
uint32_t i_field_size; /* offset following EOI */
uint32_t i_padded_field_size; /* offset following EOI+pad */
uint32_t i_next_field; /* offset to next field */
uint32_t i_DQT_offset;
uint32_t i_DHT_offset;
uint32_t i_SOF_offset;
uint32_t i_SOS_offset;
uint32_t i_data_offset; /* following SOS marker data */
};
struct access_sys_t
{
char *psz_video_device;
......@@ -104,11 +123,17 @@ struct access_sys_t
int i_width;
int i_height;
vlc_bool_t b_mjpeg;
int i_decimation;
int i_quality;
struct video_capability vid_cap;
struct video_mbuf vid_mbuf;
struct mjpeg_requestbuffers mjpeg_buffers;
uint8_t *p_video_mmap;
int i_frame_pos;
struct video_mmap vid_mmap;
struct video_picture vid_picture;
......@@ -195,6 +220,8 @@ static int AccessOpen( vlc_object_t *p_this )
input_thread_t *p_input = (input_thread_t *)p_this;
access_sys_t *p_sys;
char *psz_dup, *psz_parser;
struct mjpeg_params mjpeg;
int i;
struct video_channel vid_channel;
......@@ -212,6 +239,10 @@ static int AccessOpen( vlc_object_t *p_this )
p_sys->i_width = 0;
p_sys->i_height = 0;
p_sys->b_mjpeg = VLC_FALSE;
p_sys->i_decimation = 1;
p_sys->i_quality = 100;
p_sys->i_frame_pos = 0;
p_sys->i_codec = VLC_FOURCC( 0, 0, 0, 0 );
......@@ -389,9 +420,29 @@ static int AccessOpen( vlc_object_t *p_this )
p_sys->b_stereo = VLC_FALSE;
}
else if( !strncmp( psz_parser, "mjpeg", strlen( "mjpeg" ) ) )
{
psz_parser += strlen( "mjpeg" );
p_sys->b_mjpeg = VLC_TRUE;
}
else if( !strncmp( psz_parser, "decimation=",
strlen( "decimation=" ) ) )
{
p_sys->i_decimation =
strtol( psz_parser + strlen( "decimation=" ),
&psz_parser, 0 );
}
else if( !strncmp( psz_parser, "quality=",
strlen( "quality=" ) ) )
{
p_sys->i_quality =
strtol( psz_parser + strlen( "quality=" ),
&psz_parser, 0 );
}
else
{
msg_Warn( p_input, "unknow option" );
msg_Warn( p_input, "unknown option" );
}
while( *psz_parser && *psz_parser != ':' )
......@@ -567,55 +618,124 @@ static int AccessOpen( vlc_object_t *p_this )
msg_Err( p_input, "cannot set audio" );
goto failed;
}
}
if( p_sys->psz_adev )
{
int i_format;
if( ( p_sys->fd_audio = open( p_sys->psz_adev, O_RDONLY|O_NONBLOCK ) ) < 0 )
{
msg_Err( p_input, "cannot open audio device" );
goto failed;
}
}
i_format = AFMT_S16_LE;
if( ioctl( p_sys->fd_audio, SNDCTL_DSP_SETFMT, &i_format ) < 0
|| i_format != AFMT_S16_LE )
{
msg_Err( p_input,
"cannot set audio format (16b little endian)" );
goto failed;
}
if( p_sys->psz_adev )
{
int i_format;
if( ( p_sys->fd_audio = open( p_sys->psz_adev, O_RDONLY|O_NONBLOCK ) ) < 0 )
{
msg_Err( p_input, "cannot open audio device" );
goto failed;
}
if( ioctl( p_sys->fd_audio, SNDCTL_DSP_STEREO,
&p_sys->b_stereo ) < 0 )
{
msg_Err( p_input, "cannot set audio channels count" );
goto failed;
}
i_format = AFMT_S16_LE;
if( ioctl( p_sys->fd_audio, SNDCTL_DSP_SETFMT, &i_format ) < 0
|| i_format != AFMT_S16_LE )
{
msg_Err( p_input,
"cannot set audio format (16b little endian)" );
goto failed;
}
if( ioctl( p_sys->fd_audio, SNDCTL_DSP_SPEED,
&p_sys->i_sample_rate ) < 0 )
{
msg_Err( p_input, "cannot set audio sample rate" );
goto failed;
}
if( ioctl( p_sys->fd_audio, SNDCTL_DSP_STEREO,
&p_sys->b_stereo ) < 0 )
{
msg_Err( p_input, "cannot set audio channels count" );
goto failed;
}
msg_Dbg( p_input,
"adev=`%s' %s %dHz",
p_sys->psz_adev,
p_sys->b_stereo ? "stereo" : "mono",
p_sys->i_sample_rate );
if( ioctl( p_sys->fd_audio, SNDCTL_DSP_SPEED,
&p_sys->i_sample_rate ) < 0 )
{
msg_Err( p_input, "cannot set audio sample rate" );
goto failed;
}
p_sys->i_audio_frame_size = 0;
p_sys->i_audio_frame_size_allocated = 6*1024;
p_sys->p_audio_frame =
malloc( p_sys->i_audio_frame_size_allocated );
}
msg_Dbg( p_input,
"adev=`%s' %s %dHz",
p_sys->psz_adev,
p_sys->b_stereo ? "stereo" : "mono",
p_sys->i_sample_rate );
p_sys->i_audio_frame_size = 0;
p_sys->i_audio_frame_size_allocated = 6*1024;
p_sys->p_audio_frame =
malloc( p_sys->i_audio_frame_size_allocated );
}
/* establish basic params with input and norm before feeling width
* or height */
if( p_sys->b_mjpeg )
{
struct quicktime_mjpeg_app1 *p_app1;
int32_t i_offset;
if( ioctl( p_sys->fd, MJPIOC_G_PARAMS, &mjpeg ) < 0 )
{
msg_Err( p_input, "cannot get mjpeg params" );
goto failed;
}
mjpeg.input = p_sys->i_channel;
mjpeg.norm = p_sys->i_norm;
mjpeg.decimation = p_sys->i_decimation;
if( p_sys->i_width )
mjpeg.img_width = p_sys->i_width / p_sys->i_decimation;
if( p_sys->i_height )
mjpeg.img_height = p_sys->i_height / p_sys->i_decimation;
/* establish Quicktime APP1 marker while we are here */
mjpeg.APPn = 1;
mjpeg.APP_len = 40;
/* aligned */
p_app1 = (struct quicktime_mjpeg_app1 *)mjpeg.APP_data;
p_app1->i_reserved = 0;
p_app1->i_tag = VLC_FOURCC( 'm','j','p','g' );
p_app1->i_field_size = 0;
p_app1->i_padded_field_size = 0;
p_app1->i_next_field = 0;
/* XXX WARNING XXX */
/* these's nothing magic about these values. We are dangerously
* assuming the encoder card is encoding mjpeg-a and is not throwing
* in marker tags we aren't expecting. It's bad enough we have to
* search through the jpeg output for every frame we grab just to
* find the first field's end marker, so we take this risk to boost
* performance.
* This is really something the driver could do for us because this
* does conform to standards outside of Apple Quicktime.
*/
i_offset = 0x2e;
p_app1->i_DQT_offset = hton32( i_offset );
i_offset = 0xb4;
p_app1->i_DHT_offset = hton32( i_offset );
i_offset = 0x258;
p_app1->i_SOF_offset = hton32( i_offset );
i_offset = 0x26b;
p_app1->i_SOS_offset = hton32( i_offset );
i_offset = 0x279;
p_app1->i_data_offset = hton32( i_offset );
/* SOF and SOS aren't specified by the mjpeg API because they aren't
* optional. They will be present in the output. */
mjpeg.jpeg_markers = JPEG_MARKER_DHT | JPEG_MARKER_DQT;
if( ioctl( p_sys->fd, MJPIOC_S_PARAMS, &mjpeg ) < 0 )
{
msg_Err( p_input, "cannot set mjpeg params" );
goto failed;
}
p_sys->i_width = mjpeg.img_width * mjpeg.HorDcm;
p_sys->i_height = mjpeg.img_height * mjpeg.VerDcm *
mjpeg.field_per_buff;
}
/* fix width/heigh */
if( p_sys->i_width == 0 || p_sys->i_height == 0 )
/* fix width/height */
if( !p_sys->b_mjpeg && ( p_sys->i_width == 0 || p_sys->i_height == 0 ) )
{
struct video_window vid_win;
......@@ -632,79 +752,130 @@ static int AccessOpen( vlc_object_t *p_this )
p_sys->p_video_frame = NULL;
/* Find out video format used by device */
if( ioctl( p_sys->fd, VIDIOCGPICT, &p_sys->vid_picture ) == 0 )
if( p_sys->b_mjpeg )
{
int i_chroma, i;
struct video_picture vid_picture = p_sys->vid_picture;
/* Try to set the format to something easy to encode */
vid_picture.palette = VIDEO_PALETTE_YUV420P;
if( ioctl( p_sys->fd, VIDIOCSPICT, &vid_picture ) == 0 )
{
p_sys->vid_picture = vid_picture;
}
else
p_sys->i_chroma = VLC_FOURCC( 'I','4','2','0' );
}
else
{
/* Find out video format used by device */
if( ioctl( p_sys->fd, VIDIOCGPICT, &p_sys->vid_picture ) == 0 )
{
vid_picture.palette = VIDEO_PALETTE_YUV422P;
int i_chroma;
struct video_picture vid_picture = p_sys->vid_picture;
/* Try to set the format to something easy to encode */
vid_picture.palette = VIDEO_PALETTE_YUV420P;
if( ioctl( p_sys->fd, VIDIOCSPICT, &vid_picture ) == 0 )
{
p_sys->vid_picture = vid_picture;
}
else
{
vid_picture.palette = VIDEO_PALETTE_YUV422P;
if( ioctl( p_sys->fd, VIDIOCSPICT, &vid_picture ) == 0 )
{
p_sys->vid_picture = vid_picture;
}
}
/* Find out final format */
switch( p_sys->vid_picture.palette )
{
case VIDEO_PALETTE_GREY:
i_chroma = VLC_FOURCC( 'G', 'R', 'E', 'Y' );
break;
case VIDEO_PALETTE_HI240:
i_chroma = VLC_FOURCC( 'I', '2', '4', '0' );
break;
case VIDEO_PALETTE_RGB565:
i_chroma = VLC_FOURCC( 'R', 'V', '1', '6' );
break;
case VIDEO_PALETTE_RGB555:
i_chroma = VLC_FOURCC( 'R', 'V', '1', '5' );
break;
case VIDEO_PALETTE_RGB24:
i_chroma = VLC_FOURCC( 'R', 'V', '2', '4' );
break;
case VIDEO_PALETTE_RGB32:
i_chroma = VLC_FOURCC( 'R', 'V', '3', '2' );
break;
case VIDEO_PALETTE_YUV422:
i_chroma = VLC_FOURCC( 'I', '4', '2', '2' );
break;
case VIDEO_PALETTE_YUYV:
i_chroma = VLC_FOURCC( 'Y', 'U', 'Y', 'V' );
break;
case VIDEO_PALETTE_UYVY:
i_chroma = VLC_FOURCC( 'U', 'Y', 'V', 'Y' );
break;
case VIDEO_PALETTE_YUV420:
i_chroma = VLC_FOURCC( 'I', '4', '2', 'N' );
break;
case VIDEO_PALETTE_YUV411:
i_chroma = VLC_FOURCC( 'I', '4', '1', 'N' );
break;
case VIDEO_PALETTE_RAW:
i_chroma = VLC_FOURCC( 'G', 'R', 'A', 'W' );
break;
case VIDEO_PALETTE_YUV422P:
i_chroma = VLC_FOURCC( 'I', '4', '2', '2' );
break;
case VIDEO_PALETTE_YUV420P:
i_chroma = VLC_FOURCC( 'I', '4', '2', '0' );
break;
case VIDEO_PALETTE_YUV411P:
i_chroma = VLC_FOURCC( 'I', '4', '1', '1' );
break;
}
p_sys->i_chroma = i_chroma;
}
else
{
msg_Err( p_input, "ioctl VIDIOCGPICT failed" );
goto failed;
}
}
/* Find out final format */
switch( p_sys->vid_picture.palette )
if( p_sys->b_mjpeg )
{
int i;
p_sys->mjpeg_buffers.count = 8;
p_sys->mjpeg_buffers.size = MJPEG_BUFFER_SIZE;
if( ioctl( p_sys->fd, MJPIOC_REQBUFS, &p_sys->mjpeg_buffers ) < 0 )
{
case VIDEO_PALETTE_GREY:
i_chroma = VLC_FOURCC( 'G', 'R', 'E', 'Y' );
break;
case VIDEO_PALETTE_HI240:
i_chroma = VLC_FOURCC( 'I', '2', '4', '0' );
break;
case VIDEO_PALETTE_RGB565:
i_chroma = VLC_FOURCC( 'R', 'V', '1', '6' );
break;
case VIDEO_PALETTE_RGB555:
i_chroma = VLC_FOURCC( 'R', 'V', '1', '5' );
break;
case VIDEO_PALETTE_RGB24:
i_chroma = VLC_FOURCC( 'R', 'V', '2', '4' );
break;
case VIDEO_PALETTE_RGB32:
i_chroma = VLC_FOURCC( 'R', 'V', '3', '2' );
break;
case VIDEO_PALETTE_YUV422:
i_chroma = VLC_FOURCC( 'I', '4', '2', '2' );
break;
case VIDEO_PALETTE_YUYV:
i_chroma = VLC_FOURCC( 'Y', 'U', 'Y', 'V' );
break;
case VIDEO_PALETTE_UYVY:
i_chroma = VLC_FOURCC( 'U', 'Y', 'V', 'Y' );
break;
case VIDEO_PALETTE_YUV420:
i_chroma = VLC_FOURCC( 'I', '4', '2', 'N' );
break;
case VIDEO_PALETTE_YUV411:
i_chroma = VLC_FOURCC( 'I', '4', '1', 'N' );
break;
case VIDEO_PALETTE_RAW:
i_chroma = VLC_FOURCC( 'G', 'R', 'A', 'W' );
break;
case VIDEO_PALETTE_YUV422P:
i_chroma = VLC_FOURCC( 'I', '4', '2', '2' );
break;
case VIDEO_PALETTE_YUV420P:
i_chroma = VLC_FOURCC( 'I', '4', '2', '0' );
break;
case VIDEO_PALETTE_YUV411P:
i_chroma = VLC_FOURCC( 'I', '4', '1', '1' );
break;
msg_Err( p_input, "mmap unsupported" );
goto failed;
}
p_sys->i_chroma = i_chroma;
p_sys->p_video_mmap = mmap( 0,
p_sys->mjpeg_buffers.size * p_sys->mjpeg_buffers.count,
PROT_READ | PROT_WRITE, MAP_SHARED, p_sys->fd, 0 );
if( p_sys->p_video_mmap == MAP_FAILED )
{
msg_Err( p_input, "mmap failed" );
goto failed;
}
p_sys->i_codec = VLC_FOURCC( 'm','j','p','g' );
p_sys->p_encoder = NULL;
p_sys->i_frame_pos = -1;
/* queue up all the frames */
for( i = 0; i < (int)p_sys->mjpeg_buffers.count; i++ )
{
if( ioctl( p_sys->fd, MJPIOC_QBUF_CAPT, &i ) < 0 )
{
msg_Err( p_input, "unable to queue frame" );
goto failed;
}
}
}
else
{
/* Fill in picture_t fields */
vout_InitPicture( VLC_OBJECT(p_input), &p_sys->pic,
p_sys->i_width, p_sys->i_height, p_sys->i_chroma );
......@@ -722,98 +893,93 @@ static int AccessOpen( vlc_object_t *p_this )
msg_Dbg( p_input, "v4l device uses frame size: %i",
p_sys->i_video_frame_size );
msg_Dbg( p_input, "v4l device uses chroma: %4.4s", (char*)&i_chroma );
}
else
{
msg_Err( p_input, "ioctl VIDIOCGPICT failed" );
goto failed;
}
/* Allocate mmap buffer */
if( ioctl( p_sys->fd, VIDIOCGMBUF, &p_sys->vid_mbuf ) < 0 )
{
msg_Err( p_input, "mmap unsupported" );
goto failed;
}
p_sys->p_video_mmap = mmap( 0, p_sys->vid_mbuf.size,
PROT_READ|PROT_WRITE, MAP_SHARED,
p_sys->fd, 0 );
if( p_sys->p_video_mmap == MAP_FAILED )
{
/* FIXME -> normal read */
msg_Err( p_input, "mmap failed" );
goto failed;
}
/* init grabbing */
p_sys->vid_mmap.frame = 0;
p_sys->vid_mmap.width = p_sys->i_width;
p_sys->vid_mmap.height = p_sys->i_height;
p_sys->vid_mmap.format = p_sys->vid_picture.palette;
if( ioctl( p_sys->fd, VIDIOCMCAPTURE, &p_sys->vid_mmap ) < 0 )
{
msg_Warn( p_input, "%4.4s refused", (char*)&p_sys->i_chroma );
msg_Err( p_input, "chroma selection failed" );
goto failed;
}
/* encoder part */
if( p_sys->i_codec != VLC_FOURCC( 0, 0, 0, 0 ) )
{
msg_Dbg( p_input,
"need a rencoder from %4.4s to %4.4s",
(char*)&p_sys->i_chroma,
(char*)&p_sys->i_codec );
#define p_enc p_sys->p_encoder
p_enc = vlc_object_create( p_input, sizeof( video_encoder_t ) );
p_enc->i_codec = p_sys->i_codec;
p_enc->i_chroma= p_sys->i_chroma;
p_enc->i_width = p_sys->i_width;
p_enc->i_height= p_sys->i_height;
p_enc->i_aspect= 0;
msg_Dbg( p_input, "v4l device uses chroma: %4.4s",
(char*)&p_sys->i_chroma );
/* Allocate mmap buffer */
if( ioctl( p_sys->fd, VIDIOCGMBUF, &p_sys->vid_mbuf ) < 0 )
{
msg_Err( p_input, "mmap unsupported" );
goto failed;
}
p_enc->p_module = module_Need( p_enc, "video encoder",
"$video-encoder" );
if( !p_enc->p_module )
p_sys->p_video_mmap = mmap( 0, p_sys->vid_mbuf.size,
PROT_READ|PROT_WRITE, MAP_SHARED,
p_sys->fd, 0 );
if( p_sys->p_video_mmap == MAP_FAILED )
{
msg_Warn( p_input, "no suitable encoder to %4.4s",
(char*)&p_enc->i_codec );
vlc_object_destroy( p_enc );
/* FIXME -> normal read */
msg_Err( p_input, "mmap failed" );
goto failed;
}
/* *** init the codec *** */
if( p_enc->pf_init( p_enc ) )
/* init grabbing */
p_sys->vid_mmap.frame = 0;
p_sys->vid_mmap.width = p_sys->i_width;
p_sys->vid_mmap.height = p_sys->i_height;
p_sys->vid_mmap.format = p_sys->vid_picture.palette;
if( ioctl( p_sys->fd, VIDIOCMCAPTURE, &p_sys->vid_mmap ) < 0 )
{
msg_Err( p_input, "failed to initialize video encoder plugin" );
vlc_object_destroy( p_enc );
msg_Warn( p_input, "%4.4s refused", (char*)&p_sys->i_chroma );
msg_Err( p_input, "chroma selection failed" );
goto failed;
}
/* *** alloacted buffer *** */
if( p_enc->i_buffer_size <= 0 )
/* encoder part */
if( p_sys->i_codec != VLC_FOURCC( 0, 0, 0, 0 ) )
{
p_enc->i_buffer_size = 1024 * 1024;// * p_enc->i_width * p_enc->i_height;
msg_Dbg( p_input,
"need a rencoder from %4.4s to %4.4s",
(char*)&p_sys->i_chroma,
(char*)&p_sys->i_codec );
#define p_enc p_sys->p_encoder
p_enc = vlc_object_create( p_input, sizeof( video_encoder_t ) );
p_enc->i_codec = p_sys->i_codec;
p_enc->i_chroma= p_sys->i_chroma;
p_enc->i_width = p_sys->i_width;
p_enc->i_height= p_sys->i_height;
p_enc->i_aspect= 0;
p_enc->p_module = module_Need( p_enc, "video encoder",
"$video-encoder" );
if( !p_enc->p_module )
{
msg_Warn( p_input, "no suitable encoder to %4.4s",
(char*)&p_enc->i_codec );
vlc_object_destroy( p_enc );
goto failed;
}
/* *** init the codec *** */
if( p_enc->pf_init( p_enc ) )
{
msg_Err( p_input, "failed to initialize video encoder plugin" );
vlc_object_destroy( p_enc );
goto failed;
}
/* *** alloacted buffer *** */
if( p_enc->i_buffer_size <= 0 )
{
p_enc->i_buffer_size = 1024 * 1024;// * p_enc->i_width * p_enc->i_height;
}
p_sys->i_video_frame_size = p_enc->i_buffer_size;