[vlc-devel] [PATCH] add V4L2 video_output module
Laurent Aimar
fenrir at elivagar.org
Tue Jan 17 20:47:37 CET 2012
Hi,
Sorry for the late review.
On Sun, Jan 15, 2012 at 11:08:02PM +0100, Francois Cartegnie wrote:
> +#define V4L2_DEVICE_TEXT N_("Device")
> +#define V4L2_DEVICE_LONGTEXT N_("V4L2 output device")
> +#define V4L2_PROFILE_TEXT N_("Loopback application profile")
> +#define V4L2_PROFILE_LONGTEXT N_("Profile (chroma + size) for special apps using v4l2loopback")
> +#define V4L2_CHROMA_TEXT N_("Chroma used")
> +#define V4L2_CHROMA_LONGTEXT N_("Force use of a specific chroma for output. Default is I420.")
Can't this be auto detected ?
> +/* applications profiles for v4l2loopback output */
> +static struct {
> + const char const * psz_name;
> + const int i_width;
> + const int i_height;
> + const int i_chroma;
> +} const app_profiles[] = {
> + /* apps requested profiles */
> + { "skype", 640, 480, V4L2_PIX_FMT_YUV420 },
> + { "flashplugin", 0, 0, V4L2_PIX_FMT_RGB32 },
> + { NULL, 0, 0, 0 }
> +};
> +
> +/* same/from v4l2 access IsPixelFormatSupported() */
> +static bool is_fallback_v4l2_chroma( uint32_t i_pixelformat )
> +{
> + static const uint32_t const p_chroma_fallbacks[] =
> + {
> + V4L2_PIX_FMT_YUV420,
> + V4L2_PIX_FMT_YVU420,
> + V4L2_PIX_FMT_YUV422P,
> + V4L2_PIX_FMT_YUYV,
> + V4L2_PIX_FMT_UYVY,
> + V4L2_PIX_FMT_BGR24,
> + V4L2_PIX_FMT_BGR32
> + // minus mjpeg/jpeg
> + };
> +
> + size_t n = ARRAY_SIZE( p_chroma_fallbacks );
> + for( size_t j = 0; j < n; j++ )
> + if( i_pixelformat == p_chroma_fallbacks[ j ] )
> + return true;
> + return false;
> +}
Please use vlc_fourcc_GetYUVFallback/vlc_fourcc_GetRGBFallback.
> +static int Open (vlc_object_t *obj)
> +{
> + vout_display_t *vd = (vout_display_t *)obj;
> + struct v4l2_capability vid_caps;
> + struct v4l2_format v;
> + char *psz_video_device = var_InheritString(vd, CFG_PREFIX "dev");
> + char *psz_profile = var_InheritString(vd, CFG_PREFIX "profile");
> + char *psz_reqchroma = var_InheritString( vd, CFG_PREFIX "chroma" );
> + uint32_t i_requested_v4l2_chroma = 0;
> + bool b_streamable;
> + struct v4l2_fmtdesc *codecs = NULL;
> + uint32_t ncodecs = 0;
> +
> + vd->sys = calloc( 1, sizeof(vout_display_sys_t) );
> + if ( !vd->sys ) goto earlyerror;
> +
> + vd->sys->fd = open( psz_video_device, O_RDWR|O_NONBLOCK|O_CLOEXEC );
> + if (vd->sys->fd < 0)
> + {
> + msg_Err( obj, "cannot open v4l2 output device (%s): %m", psz_video_device );
> + free(vd->sys);
> + goto earlyerror;
> + }
> +
> + if ( ioctl(vd->sys->fd, VIDIOC_QUERYCAP, &vid_caps) < 0 )
> + {
> + msg_Err( obj, "cannot query v4l2 output device (%s): %m", psz_video_device );
> + goto error;
> + }
> +
> + if ( !strncmp( (char*)vid_caps.driver, "v4l2 loopback", 16 )
> + && ( vid_caps.version < 0x00000500 ) )
> + {
> + /* some apps won't work on v4l2oopback < 0.5 (skype) */
> + msg_Warn( obj, "Please update your V4L2 loopback driver" );
> + }
> +
> + if ( ( vid_caps.capabilities & V4L2_CAP_VIDEO_OUTPUT ) == 0 )
> + {
> + msg_Err( obj, "device (%s) is not an output device", psz_video_device );
> + goto error;
> + }
> +
> + if ( ( vid_caps.capabilities & (V4L2_CAP_READWRITE|V4L2_CAP_STREAMING) ) == 0 )
> + {
> + msg_Err( obj, "no supported I/O method" );
> + goto error;
> + }
> +
> + b_streamable = vid_caps.capabilities & V4L2_CAP_STREAMING;
> +// b_streamble = false; // uncomment to force RW method
> +
> + v.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
> + if ( ioctl(vd->sys->fd, VIDIOC_G_FMT, &v) < 0 )
> + {
> + msg_Err( obj, "cannot control v4l2 format: %m" );
> + goto error;
> + }
> +
> + vd->pool = Pool;
> + vd->prepare = NULL;
> + vd->control = Control;
> + vd->manage = NULL;
> + vout_display_SendEventFullscreen(vd, false);
You MUST not change any properties of vd (except vd->sys) nor send any event
before you are sure to return VLC_SUCCESS (as it is done by all other vout
plugins).
> + if ( apply_profile( psz_profile, & vd->fmt ) ) /* sets vd->fmt.i_chroma */
> + msg_Info( obj, "using profile %s", psz_profile );
> +
> + if( psz_reqchroma != NULL ) /* can override profile */
> + vd->fmt.i_chroma = vlc_fourcc_GetCodecFromString( VIDEO_ES, psz_reqchroma );
> +
> + /* try to match selection from above or by default module's one first */
> + if ( ! get_v4l2pixelformat_by_fourcc( & i_requested_v4l2_chroma, vd->fmt.i_chroma ) )
> + msg_Warn( obj, "can't match requested chroma");
> +
> + v.fmt.pix.width = vd->fmt.i_visible_width;
> + v.fmt.pix.height = vd->fmt.i_visible_height;
> + v.fmt.pix.pixelformat = 0;
> +
> + if ( probe_device_v4l2_chromas( obj, vd->sys->fd, V4L2_CAP_VIDEO_OUTPUT,
> + &codecs, &ncodecs ) )
> + {
> + for( uint32_t i = 0; i < ncodecs; i++ )
> + {
> + if ( codecs[i].reserved[0] == true ) /* supported */
> + {
> + if ( i_requested_v4l2_chroma > 0 )
> + {
> + if ( i_requested_v4l2_chroma == codecs[i].pixelformat )
> + {
> + v.fmt.pix.pixelformat = i_requested_v4l2_chroma;
> + msg_Dbg( obj, "using chroma %s", codecs[i].description );
> + break;
> + }
> + }
> + else
> + {
> + /* pick first available fallback */
> + if ( codecs[i].reserved[1] == true ) /* is_fallback */
> + {
> + v.fmt.pix.pixelformat = codecs[i].pixelformat;
> + msg_Dbg( obj, "using fallback chroma %s", codecs[i].description );
> + break;
> + }
> + }
> + }
> + }
> + free( codecs );
> + }
> + else
> + {
> + msg_Err( obj, "probing device for chromas failed" );
> + }
> +
> + if ( v.fmt.pix.pixelformat == 0 )
> + {
> + msg_Warn( obj, "No valid V4L2 chroma found" );
> + goto error;
> + }
> + else
> + {
> + int foo;
> + /* ensure to have it configured on display's side */
> + get_fourcc_by_v4l2pixelformat( & vd->fmt.i_chroma, &foo, &foo, &foo,
> + v.fmt.pix.pixelformat );
> + }
> +
> + video_format_FixRgb(&vd->fmt);
> + v.fmt.pix.field = V4L2_FIELD_NONE;
> +
> + if ( ioctl(vd->sys->fd, VIDIOC_S_FMT, &v) < 0 )
> + {
> + msg_Err( obj, "cannot control dst format: %m" );
> + goto error;
> + }
> +
> + /* image size has been computed by driver */
> + vd->sys->i_imagesize = v.fmt.pix.sizeimage;
> +
> + struct v4l2_requestbuffers reqbuf;
> + memset (&reqbuf, 0, sizeof (reqbuf));
> + reqbuf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
> + reqbuf.memory = V4L2_MEMORY_MMAP;
> + reqbuf.count = 8;
> +
> + if ( !b_streamable || ( ioctl(vd->sys->fd, VIDIOC_REQBUFS, &reqbuf) < 0 ) )
> + {
> + if ( !b_streamable || errno == EINVAL )
> + {
> + msg_Dbg( obj, "Video capturing or mmap-streaming is not supported" );
> + vd->display = PictureDisplayRW;
> + vd->sys->buffers = calloc( 1, sizeof( struct buffer_t ) );
> + if ( !vd->sys->buffers )
> + {
> + msg_Err( obj, "can't allocate buffer");
> + goto error;
> + }
> + vd->sys->i_buffers = 1;
> + vd->sys->buffers[0].length = v.fmt.pix.sizeimage;
> + vd->sys->buffers[0].start = calloc( v.fmt.pix.sizeimage, sizeof(uint32_t) );
> + if ( !vd->sys->buffers[0].start ) goto error;
> + }
> + else
> + {
> + msg_Err( obj, "ioctl error with reqbufs: %m");
> + goto error;
> + }
> + }
> + else
> + {
> +
> + if ( reqbuf.count < 2 ) /* should at least have an in and out queue */
> + {
> + msg_Err( obj, "Not enough buffer memory");
> + free( vd->sys );
> + return VLC_EGENERIC;
> + }
> +
> + vd->display = PictureDisplayMMAP;
> + vd->sys->poollock = MMAPPoolLock;
> +
> + vd->sys->buffers = calloc( reqbuf.count, sizeof( struct buffer_t ) );
> + if ( !vd->sys->buffers )
> + {
> + msg_Err( obj, "can't allocate mmap buffers");
> + goto error;
> + }
> +
> + for( unsigned int i=0; i<reqbuf.count; i++ )
> + {
> + struct v4l2_buffer buf;
> + buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
> + buf.memory = V4L2_MEMORY_MMAP;
> + buf.index = i;
> +
> + if ( ioctl (vd->sys->fd, VIDIOC_QUERYBUF, & buf ) < 0 )
> + {
> + msg_Err( obj, "Error in VIDIOC_QUERYBUF: %m");
> + goto error;
> + }
> +
> + vd->sys->buffers[ i ].start = mmap( NULL, buf.length,
> + PROT_READ | PROT_WRITE,
> + MAP_SHARED,
> + vd->sys->fd, buf.m.offset );
> + if ( vd->sys->buffers[ i ].start == MAP_FAILED )
> + {
> + msg_Err( obj, "Can't mmap buffer");
> + goto error;
> + }
> + vd->sys->buffers[ i ].length = buf.length;
> + vd->sys->i_buffers++;
> + }
> +
> + }
> +
> + return VLC_SUCCESS;
> +
> +error:
> + close( vd->sys->fd );
> + free_buffers( vd->sys );
> + free( vd->sys );
> +earlyerror:
> + free( psz_video_device );
> + free( psz_profile );
> + free( psz_reqchroma );
> + return VLC_EGENERIC;
> +}
> +
> +static void Close (vlc_object_t *obj)
> +{
> + vout_display_t *vd = (vout_display_t *)obj;
> +
> + if ( vd->sys->b_streaming )
> + toggle_stream( vd, VIDIOC_STREAMOFF );
> +
> + if (vd->sys->pool)
> + picture_pool_Delete(vd->sys->pool);
> +
> + close( vd->sys->fd );
> + if ( vd->display == PictureDisplayRW )
> + free( vd->sys->buffers[ 0 ].start ); /* free local buffer */
> + free_buffers( vd->sys );
> + free( vd->sys );
> +}
> +
> +static picture_pool_t *Pool(vout_display_t *vd, unsigned count)
> +{
> + vout_display_sys_t *sys = vd->sys;
> +
> + if ( sys->pool ) return sys->pool;
> +
> + /* Allocate pictures */
> + picture_t **pp_picture = calloc( count, sizeof( picture_t * ) );
picture_t *pp_picture[count] is simpler and will avoid a memleak.
> + if ( !pp_picture )
> + return NULL;
> +
> + unsigned i_allocated_count = 0;
> + for (i_allocated_count = 0; i_allocated_count < count; i_allocated_count++)
> + {
> + pp_picture[ i_allocated_count ] = picture_NewFromFormat( &vd->fmt );
> + if ( !pp_picture[ i_allocated_count ] )
> + break;
> + pp_picture[ i_allocated_count ]->p_sys = calloc( 1, sizeof( picture_sys_t ) );
> + if ( !pp_picture[ i_allocated_count ]->p_sys )
> + {
> + free( pp_picture[ i_allocated_count ] );
> + break;
> + }
> + pp_picture[ i_allocated_count ]->p_sys->p_vdsys = sys;
> + }
Please use
> +
> + /* Configure pool */
> + picture_pool_configuration_t cfg;
> + memset( &cfg, 0, sizeof( cfg ) );
> + cfg.picture_count = i_allocated_count;
> + cfg.picture = pp_picture;
> + cfg.lock = sys->poollock;
I don't really see the need of the poollock if you don't provide direct
memory rendering.
Shouldn't you do what you do in poollock in vd->prepare() instead ?
> +
> + sys->pool = picture_pool_NewExtended( &cfg );
> + if ( !sys->pool ) goto error;
> +
> + return sys->pool;
> +
> +error:
> + for ( unsigned i = 0; i < i_allocated_count; i++ )
> + picture_Delete( pp_picture[ i ] );
> + return NULL;
> +
> +}
> +
> +static void PictureDisplayRW(vout_display_t *vd, picture_t *picture, subpicture_t *subpicture)
> +{
> + assert( vd->sys->i_buffers );
> + vout_display_sys_t *sys = vd->sys;
> + struct buffer_t *buffer = &vd->sys->buffers[0];
> + unsigned int i_offset;
> +
> + /* we need to merge into memory to make only 1 write */
> + copy_frames( picture, buffer, &i_offset );
> + if ( i_offset != write( sys->fd, buffer->start, i_offset ) )
> + msg_Warn( vd, "can't fully write to device");
> +
> + picture_Release(picture);
> + VLC_UNUSED(subpicture);
> +}
> +
> +static void PictureDisplayMMAP(vout_display_t *vd, picture_t *picture, subpicture_t *subpicture)
> +{
> + vout_display_sys_t *sys = vd->sys;
> + struct v4l2_buffer freebuf;
> + if ( ! picture->p_sys->b_buffer_ready )
> + {
> + msg_Warn( vd, "device buffer not ready after VIDIOC_DQBUF: %m");
> + }
> + else
> + {
> + freebuf.index = picture->p_sys->i_target_buffer;
> + struct buffer_t *buffer = &vd->sys->buffers[ freebuf.index ];
> + unsigned int i_offset = 0;
> +
> + copy_frames( picture, buffer, &i_offset );
> +
> + struct timeval tv;
> + gettimeofday (&tv, NULL);
> + freebuf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
> + freebuf.memory = V4L2_MEMORY_MMAP;
> + freebuf.timestamp = tv;
> + freebuf.flags = 0;
> + freebuf.reserved = 0;
> + freebuf.field = V4L2_FIELD_NONE;
> + freebuf.bytesused = i_offset;
> + if ( ioctl( sys->fd, VIDIOC_QBUF, &freebuf ) < 0 )
> + msg_Err( vd, "VIDIOC_QBUF: %m");
> + /* we should test for V4L2_BUF_FLAG_QUEUED flag here */
> + /* but this won't work with the v4l2loopback driver which changes */
> + /* flag immediately to DONE and returns it in the ioctl */
> +
> + if ( !sys->b_streaming )
> + {
> + if ( toggle_stream( vd, VIDIOC_STREAMON ) < 0 )
> + msg_Warn( vd, "can't VIDIOC_STREAMON");
> + else
> + sys->b_streaming = true;
> + }
> +
> + }
> +
> + picture_Release(picture);
> + VLC_UNUSED(subpicture);
> +}
> +
> +static int MMAPPoolLock( picture_t *p_picture )
> +{
> + vout_display_sys_t *p_sys = p_picture->p_sys->p_vdsys;
> +
> + struct v4l2_buffer freebuf;
> +
> + freebuf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
> + freebuf.memory = V4L2_MEMORY_MMAP;
> + freebuf.flags = 0;
> +
> + /* Query the driver for a free buffer */
> + if ( ioctl( p_sys->fd, VIDIOC_DQBUF, &freebuf ) < 0 )
> + {
> + p_picture->p_sys->b_buffer_ready = false;
> + return VLC_EGENERIC; /* error will be triggered later */
> + }
> +
> + p_picture->p_sys->b_buffer_ready = true;
> + p_picture->p_sys->i_target_buffer = freebuf.index;
> +
> + return VLC_SUCCESS;
> +}
> +
> +static int Control(vout_display_t *vd, int query, va_list args)
> +{
> + VLC_UNUSED(vd);
> + VLC_UNUSED(args);
> + switch (query)
> + {
> + default:
> + return VLC_EGENERIC;
> + }
> +}
Regards,
--
fenrir
More information about the vlc-devel
mailing list