Commit ba8a0334 authored by Laurent Aimar's avatar Laurent Aimar

* 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,6 +618,9 @@ static int AccessOpen( vlc_object_t *p_this )
msg_Err( p_input, "cannot set audio" );
goto failed;
}
}
}
if( p_sys->psz_adev )
{
......@@ -611,11 +665,77 @@ static int AccessOpen( vlc_object_t *p_this )
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,10 +752,16 @@ static int AccessOpen( vlc_object_t *p_this )
p_sys->p_video_frame = NULL;
if( p_sys->b_mjpeg )
{
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 )
{
int i_chroma, i;
int i_chroma;
struct video_picture vid_picture = p_sys->vid_picture;
/* Try to set the format to something easy to encode */
......@@ -702,9 +828,54 @@ static int AccessOpen( vlc_object_t *p_this )
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;
}
}
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 )
{
msg_Err( p_input, "mmap unsupported" );
goto failed;
}
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,13 +893,8 @@ 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;
}
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 )
......@@ -812,7 +978,7 @@ static int AccessOpen( vlc_object_t *p_this )
p_sys->i_codec = p_sys->i_chroma;
p_sys->p_encoder = NULL;
}
}
p_input->pf_read = Read;
p_input->pf_seek = NULL;
......@@ -883,10 +1049,20 @@ static void AccessClose( vlc_object_t *p_this )
msg_Info( p_input, "v4l grabbing stoped" );
if( p_sys->b_mjpeg )
{
int i_noframe = -1;
ioctl( p_sys->fd, MJPIOC_QBUF_CAPT, &i_noframe );
}
free( p_sys->psz_video_device );
close( p_sys->fd );
if( p_sys->p_video_mmap )
if( p_sys->p_video_mmap && p_sys->p_video_mmap != MAP_FAILED )
{
if( p_sys->b_mjpeg )
munmap( p_sys->p_video_mmap, p_sys->mjpeg_buffers.size *
p_sys->mjpeg_buffers.count );
else
munmap( p_sys->p_video_mmap, p_sys->vid_mbuf.size );
}
if( p_sys->fd_audio >= 0 )
......@@ -942,13 +1118,9 @@ static int GrabAudio( input_thread_t * p_input,
return VLC_SUCCESS;
}
static int GrabVideo( input_thread_t * p_input,
uint8_t **pp_data,
int *pi_data,
mtime_t *pi_pts )
static uint8_t *GrabCapture( input_thread_t *p_input )
{
access_sys_t *p_sys = p_input->p_access_data;
p_sys->vid_mmap.frame = ( p_sys->i_frame_pos + 1 ) %
p_sys->vid_mbuf.frames;
for( ;; )
......@@ -961,7 +1133,7 @@ static int GrabVideo( input_thread_t * p_input,
if( errno != EAGAIN )
{
msg_Err( p_input, "failed while grabbing new frame" );
return( -1 );
return( NULL );
}
msg_Dbg( p_input, "another try ?" );
}
......@@ -971,15 +1143,120 @@ static int GrabVideo( input_thread_t * p_input,
while( ioctl(p_sys->fd, VIDIOCSYNC, &p_sys->i_frame_pos) < 0 &&
( errno == EAGAIN || errno == EINTR ) );
p_sys->i_frame_pos = p_sys->vid_mmap.frame;
/* leave i_video_frame_size alone */
return p_sys->p_video_mmap + p_sys->vid_mbuf.offsets[p_sys->i_frame_pos];
}
static uint8_t *GrabMJPEG( input_thread_t *p_input )
{
access_sys_t *p_sys = p_input->p_access_data;
struct mjpeg_sync sync;
uint8_t *p_frame, *p_field, *p;
uint16_t tag;
uint32_t i_size;
struct quicktime_mjpeg_app1 *p_app1 = NULL;
/* re-queue the last frame we sync'd */
if( p_sys->i_frame_pos != -1 )
while( ioctl( p_sys->fd, MJPIOC_QBUF_CAPT, &p_sys->i_frame_pos ) < 0 &&
( errno == EAGAIN || errno == EINTR ) );
/* sync on the next frame */
while( ioctl( p_sys->fd, MJPIOC_SYNC, &sync ) < 0 &&
( errno == EAGAIN || errno == EINTR ) );
p_sys->i_frame_pos = sync.frame;
p_frame = p_sys->p_video_mmap + p_sys->mjpeg_buffers.size * sync.frame;
/* p_frame now points to the data. fix up the Quicktime APP1 marker */
tag = 0xffd9;
tag = hton16( tag );
p_field = p_frame;
/* look for EOI */
p = memmem( p_field, sync.length, &tag, 2 );
if( p )
{
p += 2; /* data immediately following EOI */
/* UNALIGNED! */
p_app1 = (struct quicktime_mjpeg_app1 *)(p_field + 6);
i_size = ((uint32_t)(p - p_field));
i_size = hton32( i_size );
memcpy( &p_app1->i_field_size, &i_size, 4 );
while( *p == 0xff && *(p+1) == 0xff )
p++;
i_size = ((uint32_t)(p - p_field));
i_size = hton32( i_size );
memcpy( &p_app1->i_padded_field_size, &i_size, 4 );
}
tag = 0xffd8;
tag = hton16( tag );
p_field = memmem( p, sync.length - (size_t)(p - p_frame), &tag, 2 );
if( p_field )
{
i_size = (uint32_t)(p_field - p_frame);
i_size = hton32( i_size );
memcpy( &p_app1->i_next_field, &i_size, 4 );
/* UNALIGNED! */
p_app1 = (struct quicktime_mjpeg_app1 *)(p_field + 6);
tag = 0xffd9;
tag = hton16( tag );
p = memmem( p_field, sync.length - (size_t)(p_field - p_frame),
&tag, 2 );
if( !p )
{
/* sometimes the second field doesn't have the EOI. just put it
* there
*/
p = p_frame + sync.length;
memcpy( p, &tag, 2 );
sync.length += 2;
}
p += 2;
i_size = (uint32_t)(p - p_field);
i_size = hton32( i_size );
memcpy( &p_app1->i_field_size, &i_size, 4 );
i_size = (uint32_t)(sync.length - (uint32_t)(p_field - p_frame));
i_size = hton32( i_size );
memcpy( &p_app1->i_padded_field_size, &i_size, 4 );
}
p_sys->i_video_frame_size = sync.length;
return p_frame;
}
static int GrabVideo( input_thread_t * p_input,
uint8_t **pp_data,
int *pi_data,
mtime_t *pi_pts )
{
access_sys_t *p_sys = p_input->p_access_data;
uint8_t *p_frame;
if( p_sys->b_mjpeg )
p_frame = GrabMJPEG( p_input );
else
p_frame = GrabCapture( p_input );
if( !p_frame )
return -1;
if( p_sys->p_encoder )
{
int i;
/* notice we can't get here if we are using mjpeg */
p_sys->pic.p[0].p_pixels = p_sys->p_video_mmap +
p_sys->vid_mbuf.offsets[p_sys->i_frame_pos];
p_sys->pic.p[0].p_pixels = p_frame;
for( i = 1; i < p_sys->pic.i_planes; i++ )
{
......@@ -994,8 +1271,7 @@ static int GrabVideo( input_thread_t * p_input,
}
else
{
p_sys->p_video_frame = p_sys->p_video_mmap +
p_sys->vid_mbuf.offsets[p_sys->i_frame_pos];
p_sys->p_video_frame = p_frame;
}
*pp_data = p_sys->p_video_frame;
......
/*****************************************************************************
* Copyright (C) lavrec (see http://mjpeg.sourceforge.net)
* ( XXX This file was get from the driver-zoran package and it is under GPL)
*
* $Id: videodev_mjpeg.h,v 1.1 2003/05/31 01:23:29 fenrir Exp $
*
* 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
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
/* These are the MJPEG API extensions for the Video4Linux API,
first introduced by the Iomega Buz driver by Rainer Johanni
<rainer@johanni.de>
*/
/* This is identical with the mgavideo internal params struct,
please tell me if you change this struct here ! <gz@lysator.liu.se) */
struct mjpeg_params
{
/* The following parameters can only be queried */
int major_version; /* Major version number of driver */
int minor_version; /* Minor version number of driver */
/* Main control parameters */
int input; /* Input channel: 0 = Composite, 1 = S-VHS */
int norm; /* Norm: VIDEO_MODE_PAL or VIDEO_MODE_NTSC */
int decimation; /* decimation of captured video,
enlargement of video played back.
Valid values are 1, 2, 4 or 0.
0 is a special value where the user
has full control over video scaling */
/* The following parameters only have to be set if decimation==0,
for other values of decimation they provide the data how the image is captured */
int HorDcm; /* Horizontal decimation: 1, 2 or 4 */
int VerDcm; /* Vertical decimation: 1 or 2 */
int TmpDcm; /* Temporal decimation: 1 or 2,
if TmpDcm==2 in capture every second frame is dropped,
in playback every frame is played twice */
int field_per_buff; /* Number of fields per buffer: 1 or 2 */
int img_x; /* start of image in x direction */
int img_y; /* start of image in y direction */
int img_width; /* image width BEFORE decimation,
must be a multiple of HorDcm*16 */
int img_height; /* image height BEFORE decimation,
must be a multiple of VerDcm*8 */
/* --- End of parameters for decimation==0 only --- */
/* JPEG control parameters */
int quality; /* Measure for quality of compressed images.
Scales linearly with the size of the compressed images.
Must be beetween 0 and 100, 100 is a compression
ratio of 1:4 */
int odd_even; /* Which field should come first ???
This is more aptly named "top_first",
i.e. (odd_even==1) --> top-field-first */
int APPn; /* Number of APP segment to be written, must be 0..15 */
int APP_len; /* Length of data in JPEG APPn segment */
char APP_data[60]; /* Data in the JPEG APPn segment. */
int COM_len; /* Length of data in JPEG COM segment */
char COM_data[60]; /* Data in JPEG COM segment */
unsigned long jpeg_markers; /* Which markers should go into the JPEG output.
Unless you exactly know what you do, leave them untouched.
Inluding less markers will make the resulting code
smaller, but there will be fewer aplications
which can read it.
The presence of the APP and COM marker is
influenced by APP0_len and COM_len ONLY! */
#define JPEG_MARKER_DHT (1<<3) /* Define Huffman Tables */
#define JPEG_MARKER_DQT (1<<4) /* Define Quantization Tables */
#define JPEG_MARKER_DRI (1<<5) /* Define Restart Interval */
#define JPEG_MARKER_COM (1<<6) /* Comment segment */
#define JPEG_MARKER_APP (1<<7) /* App segment, driver will allways use APP0 */
int VFIFO_FB; /* Flag for enabling Video Fifo Feedback.
If this flag is turned on and JPEG decompressing
is going to the screen, the decompress process
is stopped every time the Video Fifo is full.
This enables a smooth decompress to the screen
but the video output signal will get scrambled */
/* Misc */
char reserved[312]; /* Makes 512 bytes for this structure */
};
struct mjpeg_requestbuffers
{
unsigned long count; /* Number of buffers for MJPEG grabbing */
unsigned long size; /* Size PER BUFFER in bytes */
};
struct mjpeg_sync
{
unsigned long frame; /* Frame (0 - n) for double buffer */
unsigned long length; /* number of code bytes in buffer (capture only) */
unsigned long seq; /* frame sequence number */
struct timeval timestamp; /* timestamp */
};
struct mjpeg_status
{
int input; /* Input channel, has to be set prior to BUZIOC_G_STATUS */
int signal; /* Returned: 1 if valid video signal detected */
int norm; /* Returned: VIDEO_MODE_PAL or VIDEO_MODE_NTSC */
int color; /* Returned: 1 if color signal detected */
};
/*
Private IOCTL to set up for displaying MJPEG
*/
#define MJPIOC_G_PARAMS _IOR ('v', BASE_VIDIOCPRIVATE+0, struct mjpeg_params)
#define MJPIOC_S_PARAMS _IOWR('v', BASE_VIDIOCPRIVATE+1, struct mjpeg_params)
#define MJPIOC_REQBUFS _IOWR('v', BASE_VIDIOCPRIVATE+2, struct mjpeg_requestbuffers)
#define MJPIOC_QBUF_CAPT _IOW ('v', BASE_VIDIOCPRIVATE+3, int)
#define MJPIOC_QBUF_PLAY _IOW ('v', BASE_VIDIOCPRIVATE+4, int)
#define MJPIOC_SYNC _IOR ('v', BASE_VIDIOCPRIVATE+5, struct mjpeg_sync)
#define MJPIOC_G_STATUS _IOWR('v', BASE_VIDIOCPRIVATE+6, struct mjpeg_status)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment