/* (c) Copyright 2001-2009 The world wide DirectFB Open Source Community (directfb.org) (c) Copyright 2000-2004 Convergence (integrated media) GmbH All rights reserved. Written by Denis Oliver Kropp , Andreas Hundt , Sven Neumann , Ville Syrjälä and Claudio Ciccani . This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if defined(__dietlibc__) && !defined(_BSD_SOURCE) #define _BSD_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LINUX_COMPILER_H #include #endif #include "videodev.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DFB_HAVE_V4L2 #include "videodev2.h" #endif static DFBResult Probe( IDirectFBVideoProvider_ProbeContext *ctx ); static DFBResult Construct( IDirectFBVideoProvider *thiz, ... ); #include DIRECT_INTERFACE_IMPLEMENTATION( IDirectFBVideoProvider, V4L ) /* * private data struct of IDirectFBVideoProvider */ typedef struct { int ref; /* reference counter */ char *filename; int fd; #ifdef DFB_HAVE_V4L2 #define NUMBER_OF_BUFFERS 2 bool is_v4l2; struct v4l2_format fmt; struct v4l2_capability caps; struct v4l2_queryctrl brightness; struct v4l2_queryctrl contrast; struct v4l2_queryctrl saturation; struct v4l2_queryctrl hue; struct v4l2_requestbuffers req; struct v4l2_buffer vidbuf[NUMBER_OF_BUFFERS]; char *ptr[NUMBER_OF_BUFFERS]; /* only used for capture to system memory */ bool framebuffer_or_system; #endif struct video_capability vcap; struct video_mmap vmmap; struct video_mbuf vmbuf; void *buffer; bool grab_mode; DirectThread *thread; CoreSurface *destination; CoreSurfaceBufferLock destinationlock; DVFrameCallback callback; void *ctx; CoreCleanup *cleanup; bool running; pthread_mutex_t lock; Reaction reaction; /* for the destination listener */ CoreDFB *core; } IDirectFBVideoProvider_V4L_data; static const unsigned int zero = 0; static const unsigned int one = 1; static void* OverlayThread( DirectThread *thread, void *context ); static void* GrabThread( DirectThread *thread, void *context ); static ReactionResult v4l_videosurface_listener( const void *msg_data, void *ctx ); static ReactionResult v4l_systemsurface_listener( const void *msg_data, void *ctx ); static DFBResult v4l_to_surface_overlay( CoreSurface *surface, DFBRectangle *rect, IDirectFBVideoProvider_V4L_data *data ); static DFBResult v4l_to_surface_grab( CoreSurface *surface, DFBRectangle *rect, IDirectFBVideoProvider_V4L_data *data ); static DFBResult v4l_stop( IDirectFBVideoProvider_V4L_data *data, bool detach ); static void v4l_deinit( IDirectFBVideoProvider_V4L_data *data ); static void v4l_cleanup( void *data, int emergency ); #ifdef DFB_HAVE_V4L2 static DFBResult v4l2_playto( CoreSurface *surface, DFBRectangle *rect, IDirectFBVideoProvider_V4L_data *data ); #endif static void IDirectFBVideoProvider_V4L_Destruct( IDirectFBVideoProvider *thiz ) { IDirectFBVideoProvider_V4L_data *data = (IDirectFBVideoProvider_V4L_data*)thiz->priv; if (data->cleanup) dfb_core_cleanup_remove( NULL, data->cleanup ); v4l_deinit( data ); D_FREE( data->filename ); pthread_mutex_destroy( &data->lock ); DIRECT_DEALLOCATE_INTERFACE( thiz ); } static DirectResult IDirectFBVideoProvider_V4L_AddRef( IDirectFBVideoProvider *thiz ) { DIRECT_INTERFACE_GET_DATA (IDirectFBVideoProvider_V4L) data->ref++; return DFB_OK; } static DirectResult IDirectFBVideoProvider_V4L_Release( IDirectFBVideoProvider *thiz ) { DIRECT_INTERFACE_GET_DATA (IDirectFBVideoProvider_V4L) if (--data->ref == 0) { IDirectFBVideoProvider_V4L_Destruct( thiz ); } return DFB_OK; } static DFBResult IDirectFBVideoProvider_V4L_GetCapabilities( IDirectFBVideoProvider *thiz, DFBVideoProviderCapabilities *caps ) { DIRECT_INTERFACE_GET_DATA (IDirectFBVideoProvider_V4L) if (!caps) return DFB_INVARG; #ifdef DFB_HAVE_V4L2 if (data->is_v4l2) { *caps = 0; data->saturation.id = V4L2_CID_SATURATION; if (ioctl( data->fd, VIDIOC_G_CTRL, &data->saturation )) { *caps |= DVCAPS_SATURATION; } else { data->saturation.id = 0; } data->brightness.id = V4L2_CID_BRIGHTNESS; if (ioctl( data->fd, VIDIOC_G_CTRL, &data->brightness )) { *caps |= DVCAPS_BRIGHTNESS; } else { data->brightness.id = 0; } data->contrast.id = V4L2_CID_CONTRAST; if (ioctl( data->fd, VIDIOC_G_CTRL, &data->contrast )) { *caps |= DVCAPS_CONTRAST; } else { data->contrast.id = 0; } data->hue.id = V4L2_CID_HUE; if (ioctl( data->fd, VIDIOC_G_CTRL, &data->hue )) { *caps |= DVCAPS_HUE; } else { data->hue.id = 0; } /* fixme: interlaced might not be true for field capture */ *caps |= DVCAPS_BASIC | DVCAPS_SCALE | DVCAPS_INTERLACED; } else #endif { *caps = ( DVCAPS_BASIC | DVCAPS_BRIGHTNESS | DVCAPS_CONTRAST | DVCAPS_HUE | DVCAPS_SATURATION | DVCAPS_INTERLACED ); if (data->vcap.type & VID_TYPE_SCALES) *caps |= DVCAPS_SCALE; } return DFB_OK; } static DFBResult IDirectFBVideoProvider_V4L_GetSurfaceDescription( IDirectFBVideoProvider *thiz, DFBSurfaceDescription *desc ) { IDirectFBVideoProvider_V4L_data *data; if (!thiz || !desc) return DFB_INVARG; data = (IDirectFBVideoProvider_V4L_data*)thiz->priv; if (!data) return DFB_DEAD; desc->flags = DSDESC_WIDTH | DSDESC_HEIGHT | DSDESC_PIXELFORMAT | DSDESC_CAPS; #ifdef DFB_HAVE_V4L2 if (data->is_v4l2) { desc->width = 720; /* fimxe: depends on the selected standard: query standard and set accordingly */ desc->height = 576; } else #endif { desc->width = data->vcap.maxwidth; desc->height = data->vcap.maxheight; } desc->pixelformat = dfb_primary_layer_pixelformat(); desc->caps = DSCAPS_INTERLACED; return DFB_OK; } static DFBResult IDirectFBVideoProvider_V4L_GetStreamDescription( IDirectFBVideoProvider *thiz, DFBStreamDescription *desc ) { DIRECT_INTERFACE_GET_DATA (IDirectFBVideoProvider_V4L) if (!desc) return DFB_INVARG; desc->caps = DVSCAPS_VIDEO; desc->video.encoding[0] = 0; desc->video.framerate = 10; // assume 10fps #ifdef DFB_HAVE_V4L2 desc->video.aspect = 720.0/576.0; #else desc->video.aspect = (double)data->vcap.maxwidth / (double)data->vcap.maxheight; #endif desc->video.bitrate = 0; desc->title[0] = desc->author[0] = desc->album[0] = desc->year = desc->genre[0] = desc->comment[0] = 0; return DFB_OK; } static DFBResult IDirectFBVideoProvider_V4L_PlayTo( IDirectFBVideoProvider *thiz, IDirectFBSurface *destination, const DFBRectangle *dstrect, DVFrameCallback callback, void *ctx ) { DFBRectangle rect; IDirectFBSurface_data *dst_data; CoreSurface *surface = 0; DFBResult ret; DIRECT_INTERFACE_GET_DATA (IDirectFBVideoProvider_V4L) if (!destination) return DFB_INVARG; dst_data = (IDirectFBSurface_data*)destination->priv; if (!dst_data) return DFB_DEAD; if (!dst_data->area.current.w || !dst_data->area.current.h) return DFB_INVAREA; if (dstrect) { if (dstrect->w < 1 || dstrect->h < 1) return DFB_INVARG; rect = *dstrect; rect.x += dst_data->area.wanted.x; rect.y += dst_data->area.wanted.y; } else rect = dst_data->area.wanted; if (!dfb_rectangle_intersect( &rect, &dst_data->area.current )) return DFB_INVAREA; v4l_stop( data, true ); pthread_mutex_lock( &data->lock ); data->callback = callback; data->ctx = ctx; surface = dst_data->surface; #ifdef DFB_HAVE_V4L2 if (data->is_v4l2) { ret = v4l2_playto( surface, &rect, data ); } else #endif { data->grab_mode = false; if (getenv( "DFB_V4L_GRAB" ) || (surface->config.caps & DSCAPS_SYSTEMONLY) || (surface->config.caps & DSCAPS_FLIPPING) || !(VID_TYPE_OVERLAY & data->vcap.type)) data->grab_mode = true; else { /* * Because we're constantly writing to the surface we * permanently lock it. */ ret = dfb_surface_lock_buffer( surface, CSBR_BACK, CSAID_GPU, CSAF_WRITE, &data->destinationlock ); if (ret) { pthread_mutex_unlock( &data->lock ); return ret; } } if (data->grab_mode) ret = v4l_to_surface_grab( surface, &rect, data ); else ret = v4l_to_surface_overlay( surface, &rect, data ); } if (ret && !data->grab_mode) dfb_surface_unlock_buffer( surface, &data->destinationlock ); pthread_mutex_unlock( &data->lock ); return ret; } static DFBResult IDirectFBVideoProvider_V4L_Stop( IDirectFBVideoProvider *thiz ) { DIRECT_INTERFACE_GET_DATA (IDirectFBVideoProvider_V4L) return v4l_stop( data, true ); } static DFBResult IDirectFBVideoProvider_V4L_GetStatus( IDirectFBVideoProvider *thiz, DFBVideoProviderStatus *status ) { DIRECT_INTERFACE_GET_DATA (IDirectFBVideoProvider_V4L) if (!status) return DFB_INVARG; *status = data->running ? DVSTATE_PLAY : DVSTATE_STOP; return DFB_OK; } static DFBResult IDirectFBVideoProvider_V4L_SeekTo( IDirectFBVideoProvider *thiz, double seconds ) { DIRECT_INTERFACE_GET_DATA (IDirectFBVideoProvider_V4L) return DFB_UNIMPLEMENTED; } static DFBResult IDirectFBVideoProvider_V4L_GetPos( IDirectFBVideoProvider *thiz, double *seconds ) { DIRECT_INTERFACE_GET_DATA (IDirectFBVideoProvider_V4L) return DFB_UNSUPPORTED; } static DFBResult IDirectFBVideoProvider_V4L_GetLength( IDirectFBVideoProvider *thiz, double *seconds ) { DIRECT_INTERFACE_GET_DATA (IDirectFBVideoProvider_V4L) return DFB_UNSUPPORTED; } static DFBResult IDirectFBVideoProvider_V4L_GetColorAdjustment( IDirectFBVideoProvider *thiz, DFBColorAdjustment *adj ) { struct video_picture pic; DIRECT_INTERFACE_GET_DATA (IDirectFBVideoProvider_V4L) if (!adj) return DFB_INVARG; #ifdef DFB_HAVE_V4L2 if (data->is_v4l2) { struct v4l2_control ctrl; if (data->brightness.id) { ctrl.id = data->brightness.id; if (!ioctl( data->fd, VIDIOC_G_CTRL, &ctrl )) { adj->flags |= DCAF_BRIGHTNESS; adj->brightness = 0xffff * ctrl.value / (data->brightness.maximum - data->brightness.minimum); } } if (data->contrast.id) { ctrl.id = data->contrast.id; if (!ioctl( data->fd, VIDIOC_G_CTRL, &ctrl )) { adj->flags |= DCAF_CONTRAST; adj->contrast = 0xffff * ctrl.value / (data->contrast.maximum - data->contrast.minimum); } } if (data->hue.id) { ctrl.id = data->hue.id; if (!ioctl( data->fd, VIDIOC_G_CTRL, &ctrl )) { adj->flags |= DCAF_HUE; adj->hue = 0xffff * ctrl.value / (data->hue.maximum - data->hue.minimum); } } if (data->saturation.id) { ctrl.id = data->saturation.id; if (!ioctl( data->fd, VIDIOC_G_CTRL, &ctrl )) { adj->flags |= DCAF_SATURATION; adj->saturation = 0xffff * ctrl.value / (data->saturation.maximum - data->saturation.minimum); } } } else #endif { ioctl( data->fd, VIDIOCGPICT, &pic ); adj->flags = DCAF_BRIGHTNESS | DCAF_CONTRAST | DCAF_HUE | DCAF_SATURATION; adj->brightness = pic.brightness; adj->contrast = pic.contrast; adj->hue = pic.hue; adj->saturation = pic.colour; } return DFB_OK; } static DFBResult IDirectFBVideoProvider_V4L_SetColorAdjustment( IDirectFBVideoProvider *thiz, const DFBColorAdjustment *adj ) { struct video_picture pic; DIRECT_INTERFACE_GET_DATA (IDirectFBVideoProvider_V4L) if (!adj) return DFB_INVARG; if (adj->flags == DCAF_NONE) return DFB_OK; #ifdef DFB_HAVE_V4L2 if (data->is_v4l2) { struct v4l2_control ctrl; if ((adj->flags & DCAF_BRIGHTNESS) && data->brightness.id) { ctrl.id = data->brightness.id; ctrl.value = (adj->brightness * (data->brightness.maximum - data->brightness.minimum)) / 0xfff; ioctl( data->fd, VIDIOC_S_CTRL, &ctrl ); } if ((adj->flags & DCAF_CONTRAST) && data->contrast.id) { ctrl.id = data->contrast.id; ctrl.value = (adj->contrast * (data->contrast.maximum - data->contrast.minimum)) / 0xfff; ioctl( data->fd, VIDIOC_S_CTRL, &ctrl ); } if ((adj->flags & DCAF_HUE) && data->hue.id) { ctrl.id = data->hue.id; ctrl.value = (adj->hue * (data->hue.maximum - data->hue.minimum)) / 0xfff; ioctl( data->fd, VIDIOC_S_CTRL, &ctrl ); } if ((adj->flags & DCAF_SATURATION) && data->saturation.id) { ctrl.id = data->saturation.id; ctrl.value = (adj->saturation * (data->saturation.maximum - data->saturation.minimum)) / 0xfff; ioctl( data->fd, VIDIOC_S_CTRL, &ctrl ); } } else #endif { if (ioctl( data->fd, VIDIOCGPICT, &pic ) < 0) { DFBResult ret = errno2result( errno ); D_PERROR( "DirectFB/Video4Linux: VIDIOCGPICT failed!\n" ); return ret; } if (adj->flags & DCAF_BRIGHTNESS) pic.brightness = adj->brightness; if (adj->flags & DCAF_CONTRAST) pic.contrast = adj->contrast; if (adj->flags & DCAF_HUE) pic.hue = adj->hue; if (adj->flags & DCAF_SATURATION) pic.colour = adj->saturation; if (ioctl( data->fd, VIDIOCSPICT, &pic ) < 0) { DFBResult ret = errno2result( errno ); D_PERROR( "DirectFB/Video4Linux: VIDIOCSPICT failed!\n" ); return ret; } } return DFB_OK; } static DFBResult IDirectFBVideoProvider_V4L_SendEvent( IDirectFBVideoProvider *thiz, const DFBEvent *evt ) { DIRECT_INTERFACE_GET_DATA (IDirectFBVideoProvider_V4L) return DFB_UNSUPPORTED; } /* exported symbols */ static DFBResult Probe( IDirectFBVideoProvider_ProbeContext *ctx ) { if (ctx->filename) { if (strncmp( ctx->filename, "/dev/video", 10 ) == 0) return DFB_OK; if (strncmp( ctx->filename, "/dev/v4l/video", 14 ) == 0) return DFB_OK; } return DFB_UNSUPPORTED; } static DFBResult Construct( IDirectFBVideoProvider *thiz, ... ) { int fd; IDirectFBDataBuffer *buffer; IDirectFBDataBuffer_data *buffer_data; CoreDFB *core; va_list tag; DIRECT_ALLOCATE_INTERFACE_DATA(thiz, IDirectFBVideoProvider_V4L) va_start( tag, thiz ); buffer = va_arg( tag, IDirectFBDataBuffer * ); core = va_arg( tag, CoreDFB * ); va_end( tag ); data->ref = 1; data->core = core; buffer_data = (IDirectFBDataBuffer_data*) buffer->priv; fd = open( buffer_data->filename, O_RDWR ); if (fd < 0) { DFBResult ret = errno2result( errno ); D_PERROR( "DirectFB/Video4Linux: Cannot open `%s'!\n", buffer_data->filename ); DIRECT_DEALLOCATE_INTERFACE( thiz ); return ret; } direct_util_recursive_pthread_mutex_init( &data->lock ); #ifdef DFB_HAVE_V4L2 data->is_v4l2 = 0; /* look if the device is a v4l2 device */ if (!ioctl( fd, VIDIOC_QUERYCAP, &data->caps )) { D_INFO( "DirectFB/Video4Linux: This is a Video4Linux-2 device.\n" ); data->is_v4l2 = 1; } if (data->is_v4l2) { /* hmm, anything to do here? */ } else #endif { D_INFO( "DirectFB/Video4Linux: This is a Video4Linux-1 device.\n" ); ioctl( fd, VIDIOCGCAP, &data->vcap ); ioctl( fd, VIDIOCCAPTURE, &zero ); ioctl( fd, VIDIOCGMBUF, &data->vmbuf ); data->buffer = mmap( NULL, data->vmbuf.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 ); } data->filename = D_STRDUP( buffer_data->filename ); data->fd = fd; thiz->AddRef = IDirectFBVideoProvider_V4L_AddRef; thiz->Release = IDirectFBVideoProvider_V4L_Release; thiz->GetCapabilities = IDirectFBVideoProvider_V4L_GetCapabilities; thiz->GetSurfaceDescription = IDirectFBVideoProvider_V4L_GetSurfaceDescription; thiz->GetStreamDescription = IDirectFBVideoProvider_V4L_GetStreamDescription; thiz->PlayTo = IDirectFBVideoProvider_V4L_PlayTo; thiz->Stop = IDirectFBVideoProvider_V4L_Stop; thiz->GetStatus = IDirectFBVideoProvider_V4L_GetStatus; thiz->SeekTo = IDirectFBVideoProvider_V4L_SeekTo; thiz->GetPos = IDirectFBVideoProvider_V4L_GetPos; thiz->GetLength = IDirectFBVideoProvider_V4L_GetLength; thiz->GetColorAdjustment = IDirectFBVideoProvider_V4L_GetColorAdjustment; thiz->SetColorAdjustment = IDirectFBVideoProvider_V4L_SetColorAdjustment; thiz->SendEvent = IDirectFBVideoProvider_V4L_SendEvent; return DFB_OK; } /*****************/ /* * bogus thread to generate callback, * because video4linux does not support syncing in overlay mode */ static void * OverlayThread( DirectThread *thread, void *ctx ) { IDirectFBVideoProvider_V4L_data *data = (IDirectFBVideoProvider_V4L_data*)ctx; int field = 0; struct timeval tv; while (data->running) { tv.tv_sec = 0; tv.tv_usec = 20000; select( 0, 0, 0, 0, &tv ); if (!data->running) break; if (data->destination && (data->destination->config.caps & DSCAPS_INTERLACED)) { dfb_surface_set_field( data->destination, field ); field = !field; } if (!data->running) break; if (data->callback) data->callback( data->ctx ); } return NULL; } /* * thread to capture data from v4l buffers and generate callback */ static void * GrabThread( DirectThread *thread, void *ctx ) { IDirectFBVideoProvider_V4L_data *data = (IDirectFBVideoProvider_V4L_data*)ctx; CoreSurface *surface = data->destination; void *src, *dst; int dst_pitch, src_pitch, h; int frame = 0; D_DEBUG( "DirectFB/Video4Linux: %s started.\n", __FUNCTION__ ); src_pitch = DFB_BYTES_PER_LINE( surface->config.format, surface->config.size.w ); while (frame < data->vmbuf.frames) { data->vmmap.frame = frame; ioctl( data->fd, VIDIOCMCAPTURE, &data->vmmap ); frame++; } if (dfb_surface_ref( surface )) { D_ERROR( "DirectFB/Video4Linux: dfb_surface_ref() failed!\n" ); return NULL; } frame = 0; while (data->running) { ioctl( data->fd, VIDIOCSYNC, &frame ); if (!data->running) break; h = surface->config.size.h; src = data->buffer + data->vmbuf.offsets[frame]; dfb_surface_lock_buffer( surface, CSBR_BACK, CSAID_CPU, CSAF_WRITE, &data->destinationlock ); dst = data->destinationlock.addr; dst_pitch = data->destinationlock.pitch; while (h--) { direct_memcpy( dst, src, src_pitch ); dst += dst_pitch; src += src_pitch; } if (surface->config.format == DSPF_I420) { h = surface->config.size.h; while (h--) { direct_memcpy( dst, src, src_pitch >> 1 ); dst += dst_pitch >> 1; src += src_pitch >> 1; } } else if (surface->config.format == DSPF_YV12) { h = surface->config.size.h >> 1; src += h * (src_pitch >> 1); while (h--) { direct_memcpy( dst, src, src_pitch >> 1 ); dst += dst_pitch >> 1; src += src_pitch >> 1; } h = surface->config.size.h >> 1; src -= 2 * h * (src_pitch >> 1); while (h--) { direct_memcpy( dst, src, src_pitch >> 1 ); dst += dst_pitch >> 1; src += src_pitch >> 1; } } dfb_surface_unlock_buffer( surface, &data->destinationlock ); data->vmmap.frame = frame; ioctl( data->fd, VIDIOCMCAPTURE, &data->vmmap ); if (!data->running) break; if (surface->config.caps & DSCAPS_INTERLACED) dfb_surface_set_field( surface, 0 ); if (data->callback) data->callback( data->ctx ); if (!data->running) break; sched_yield(); if (surface->config.caps & DSCAPS_INTERLACED) { if (!data->running) break; dfb_surface_set_field( surface, 1 ); if (data->callback) data->callback( data->ctx ); if (!data->running) break; sched_yield(); } if (++frame == data->vmbuf.frames) frame = 0; } dfb_surface_unref( surface ); return NULL; } static ReactionResult v4l_videosurface_listener( const void *msg_data, void *ctx ) { // const CoreSurfaceNotification *notification = msg_data; // IDirectFBVideoProvider_V4L_data *data = ctx; // CoreSurface *surface = notification->surface; /* if ((notification->flags & CSNF_SIZEFORMAT) || (surface->back_buffer->video.health != CSH_STORED)) { v4l_stop( data, false ); return RS_REMOVE; } */ return RS_OK; } static ReactionResult v4l_systemsurface_listener( const void *msg_data, void *ctx ) { // const CoreSurfaceNotification *notification = msg_data; // IDirectFBVideoProvider_V4L_data *data = ctx; // CoreSurface *surface = notification->surface; /* if ((notification->flags & CSNF_SIZEFORMAT) || (surface->back_buffer->system.health != CSH_STORED && surface->back_buffer->video.health != CSH_STORED)) { v4l_stop( data, false ); return RS_REMOVE; } */ return RS_OK; } /************/ static DFBResult v4l_to_surface_overlay( CoreSurface *surface, DFBRectangle *rect, IDirectFBVideoProvider_V4L_data *data ) { int bpp, palette; D_DEBUG( "DirectFB/Video4Linux: %s (%p, %d,%d - %dx%d)\n", __FUNCTION__, surface, rect->x, rect->y, rect->w, rect->h ); /* * Sanity check. Overlay to system surface isn't possible. */ if (surface->config.caps & DSCAPS_SYSTEMONLY) return DFB_UNSUPPORTED; switch (surface->config.format) { case DSPF_YUY2: bpp = 16; palette = VIDEO_PALETTE_YUYV; break; case DSPF_UYVY: bpp = 16; palette = VIDEO_PALETTE_UYVY; break; case DSPF_I420: bpp = 8; palette = VIDEO_PALETTE_YUV420P; break; case DSPF_ARGB1555: bpp = 15; palette = VIDEO_PALETTE_RGB555; break; case DSPF_RGB16: bpp = 16; palette = VIDEO_PALETTE_RGB565; break; case DSPF_RGB24: bpp = 24; palette = VIDEO_PALETTE_RGB24; break; case DSPF_ARGB: case DSPF_AiRGB: case DSPF_RGB32: bpp = 32; palette = VIDEO_PALETTE_RGB32; break; default: return DFB_UNSUPPORTED; } { struct video_buffer b; /* in overlay mode, the destinationlock is already taken * and pointing to the back buffer */ CoreSurfaceBufferLock *lock = &data->destinationlock; b.base = (void*)dfb_gfxcard_memory_physical( NULL, lock->offset ); b.width = lock->pitch / ((bpp + 7) / 8); b.height = surface->config.size.h; b.depth = bpp; b.bytesperline = lock->pitch; if (ioctl( data->fd, VIDIOCSFBUF, &b ) < 0) { DFBResult ret = errno2result( errno ); D_PERROR( "DirectFB/Video4Linux: VIDIOCSFBUF failed, must run being root!\n" ); return ret; } } { struct video_picture p; if (ioctl( data->fd, VIDIOCGPICT, &p ) < 0) { DFBResult ret = errno2result( errno ); D_PERROR( "DirectFB/Video4Linux: VIDIOCGPICT failed!\n" ); return ret; } p.depth = bpp; p.palette = palette; if (ioctl( data->fd, VIDIOCSPICT, &p ) < 0) { DFBResult ret = errno2result( errno ); D_PERROR( "DirectFB/Video4Linux: VIDIOCSPICT failed!\n" ); return ret; } } { struct video_window win; win.width = rect->w; win.height = rect->h; win.x = rect->x; win.y = rect->y; win.flags = 0; win.clips = NULL; win.clipcount = 0; win.chromakey = 0; if (ioctl( data->fd, VIDIOCSWIN, &win ) < 0) { DFBResult ret = errno2result( errno ); D_PERROR( "DirectFB/Video4Linux: VIDIOCSWIN failed!\n" ); return ret; } } if (!data->cleanup) data->cleanup = dfb_core_cleanup_add( NULL, v4l_cleanup, data, true ); if (ioctl( data->fd, VIDIOCCAPTURE, &one ) < 0) { DFBResult ret = errno2result( errno ); D_PERROR( "DirectFB/Video4Linux: Could not start capturing (VIDIOCCAPTURE failed)!\n" ); return ret; } data->destination = surface; dfb_surface_attach( surface, v4l_videosurface_listener, data, &data->reaction ); data->running = true; if (data->callback || (surface->config.caps & DSCAPS_INTERLACED)) data->thread = direct_thread_create( DTT_CRITICAL, OverlayThread, data, "V4L Overlay" ); return DFB_OK; } static DFBResult v4l_to_surface_grab( CoreSurface *surface, DFBRectangle *rect, IDirectFBVideoProvider_V4L_data *data ) { int palette; D_DEBUG( "DirectFB/Video4Linux: %s...\n", __FUNCTION__ ); if (!data->vmbuf.frames) return DFB_UNSUPPORTED; switch (surface->config.format) { case DSPF_YUY2: palette = VIDEO_PALETTE_YUYV; break; case DSPF_UYVY: palette = VIDEO_PALETTE_UYVY; break; case DSPF_I420: case DSPF_YV12: palette = VIDEO_PALETTE_YUV420P; break; case DSPF_ARGB1555: palette = VIDEO_PALETTE_RGB555; break; case DSPF_RGB16: palette = VIDEO_PALETTE_RGB565; break; case DSPF_RGB24: palette = VIDEO_PALETTE_RGB24; break; case DSPF_ARGB: case DSPF_AiRGB: case DSPF_RGB32: palette = VIDEO_PALETTE_RGB32; break; default: return DFB_UNSUPPORTED; } data->vmmap.width = surface->config.size.w; data->vmmap.height = surface->config.size.h; data->vmmap.format = palette; data->vmmap.frame = 0; if (ioctl( data->fd, VIDIOCMCAPTURE, &data->vmmap ) < 0) { DFBResult ret = errno2result( errno ); D_PERROR( "DirectFB/Video4Linux: Could not start capturing (VIDIOCMCAPTURE failed)!\n" ); return ret; } if (!data->cleanup) data->cleanup = dfb_core_cleanup_add( NULL, v4l_cleanup, data, true ); data->destination = surface; dfb_surface_attach( surface, v4l_systemsurface_listener, data, &data->reaction ); data->running = true; data->thread = direct_thread_create( DTT_INPUT, GrabThread, data, "V4L Grabber" ); return DFB_OK; } static DFBResult v4l_stop( IDirectFBVideoProvider_V4L_data *data, bool detach ) { CoreSurface *destination; D_DEBUG( "DirectFB/Video4Linux: %s...\n", __FUNCTION__ ); pthread_mutex_lock( &data->lock ); if (!data->running) { pthread_mutex_unlock( &data->lock ); return DFB_OK; } if (data->thread) { data->running = false; direct_thread_join( data->thread ); direct_thread_destroy( data->thread ); data->thread = NULL; } #ifdef DFB_HAVE_V4L2 if (data->is_v4l2) { /* turn off streaming */ int type = V4L2_BUF_TYPE_VIDEO_CAPTURE; int err = ioctl( data->fd, VIDIOC_STREAMOFF, &type ); if (err) { D_PERROR( "DirectFB/Video4Linux2: VIDIOC_STREAMOFF.\n" ); /* don't quit here */ } } else #endif { if (!data->grab_mode) { if (ioctl( data->fd, VIDIOCCAPTURE, &zero ) < 0) D_PERROR( "DirectFB/Video4Linux: " "Could not stop capturing (VIDIOCCAPTURE failed)!\n" ); } } destination = data->destination; if (!destination) { pthread_mutex_unlock( &data->lock ); return DFB_OK; } #ifdef DFB_HAVE_V4L2 if (data->is_v4l2) { /* unmap all buffers, if necessary */ if (data->framebuffer_or_system) { int i; for (i = 0; i < data->req.count; i++) { struct v4l2_buffer *vidbuf = &data->vidbuf[i]; D_DEBUG( "DirectFB/Video4Linux2: %d => 0x%08x, len:%d\n", i, (u32) data->ptr[i], vidbuf->length ); if (munmap( data->ptr[i], vidbuf->length )) { D_PERROR( "DirectFB/Video4Linux2: munmap().\n" ); } } } else { dfb_surface_unlock_buffer( destination, &data->destinationlock ); } } else #endif { if (!data->grab_mode) dfb_surface_unlock_buffer( destination, &data->destinationlock ); } data->destination = NULL; pthread_mutex_unlock( &data->lock ); if (detach) dfb_surface_detach( destination, &data->reaction ); return DFB_OK; } static void v4l_deinit( IDirectFBVideoProvider_V4L_data *data ) { if (data->fd == -1) { D_BUG( "v4l_deinit with 'fd == -1'" ); return; } v4l_stop( data, true ); munmap( data->buffer, data->vmbuf.size ); close( data->fd ); data->fd = -1; } static void v4l_cleanup( void *ctx, int emergency ) { IDirectFBVideoProvider_V4L_data *data = (IDirectFBVideoProvider_V4L_data*)ctx; if (emergency) v4l_stop( data, false ); else v4l_deinit( data ); } /* v4l2 specific stuff */ #ifdef DFB_HAVE_V4L2 static int wait_for_buffer( int vid, struct v4l2_buffer *cur ) { fd_set rdset; struct timeval timeout; int n, err; // D_DEBUG("DirectFB/Video4Linux2: %s...\n", __FUNCTION__); cur->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; FD_ZERO( &rdset ); FD_SET( vid, &rdset ); timeout.tv_sec = 5; timeout.tv_usec = 0; n = select( vid + 1, &rdset, NULL, NULL, &timeout ); if (n == -1) { D_PERROR( "DirectFB/Video4Linux2: select().\n" ); return -1; /* fixme */ } else if (n == 0) { D_PERROR( "DirectFB/Video4Linux2: select(), timeout.\n" ); return -1; /* fixme */ } else if (FD_ISSET( vid, &rdset )) { err = ioctl( vid, VIDIOC_DQBUF, cur ); if (err) { D_PERROR( "DirectFB/Video4Linux2: VIDIOC_DQBUF.\n" ); return -1; /* fixme */ } } return 0; } static void * V4L2_Thread( DirectThread *thread, void *ctx ) { int type = V4L2_BUF_TYPE_VIDEO_CAPTURE; int i, err; IDirectFBVideoProvider_V4L_data *data = (IDirectFBVideoProvider_V4L_data *) ctx; CoreSurface *surface = data->destination; void *src, *dst; int dst_pitch, src_pitch, h; D_DEBUG( "DirectFB/Video4Linux2: %s started.\n", __FUNCTION__ ); src_pitch = DFB_BYTES_PER_LINE( surface->config.format, surface->config.size.w ); /* Queue all buffers */ for (i = 0; i < data->req.count; i++) { struct v4l2_buffer *vidbuf = &data->vidbuf[i]; if (!data->framebuffer_or_system) { vidbuf->m.offset = data->destinationlock.offset; } err = ioctl( data->fd, VIDIOC_QBUF, vidbuf ); if (err) { D_PERROR( "DirectFB/Video4Linux2: VIDIOC_QBUF.\n" ); return NULL; } } /* start streaming */ if (ioctl( data->fd, VIDIOC_STREAMON, &type )) { D_PERROR( "DirectFB/Video4Linux2: VIDIOC_STREAMON.\n" ); return NULL; /* fixme */ } while (data->running) { struct v4l2_buffer cur; if (wait_for_buffer( data->fd, &cur )) { return NULL; } if (data->framebuffer_or_system) { CoreSurfaceBufferLock lock; D_DEBUG( "DirectFB/Video4Linux2: index:%d, to system memory.\n", cur.index ); h = surface->config.size.h; src = data->ptr[cur.index]; dfb_surface_lock_buffer( surface, CSBR_BACK, CSAID_CPU, CSAF_WRITE, &lock ); dst = lock.addr; dst_pitch = lock.pitch; while (h--) { direct_memcpy( dst, src, src_pitch ); dst += dst_pitch; src += src_pitch; } if (surface->config.format == DSPF_I420) { h = surface->config.size.h; while (h--) { direct_memcpy( dst, src, src_pitch >> 1 ); dst += dst_pitch >> 1; src += src_pitch >> 1; } } else if (surface->config.format == DSPF_YV12) { h = surface->config.size.h >> 1; src += h * (src_pitch >> 1); while (h--) { direct_memcpy( dst, src, src_pitch >> 1 ); dst += dst_pitch >> 1; src += src_pitch >> 1; } h = surface->config.size.h >> 1; src -= 2 * h * (src_pitch >> 1); while (h--) { direct_memcpy( dst, src, src_pitch >> 1 ); dst += dst_pitch >> 1; src += src_pitch >> 1; } } else if (surface->config.format == DSPF_NV12 || surface->config.format == DSPF_NV21) { h = surface->config.size.h >> 1; while (h--) { direct_memcpy( dst, src, src_pitch ); dst += dst_pitch; src += src_pitch; } } dfb_surface_unlock_buffer( surface, &lock ); } else { D_DEBUG( "DirectFB/Video4Linux2: index:%d, to overlay surface\n", cur.index ); } if (data->callback) data->callback( data->ctx ); if (ioctl( data->fd, VIDIOC_QBUF, &cur )) { D_PERROR( "DirectFB/Video4Linux2: VIDIOC_QBUF.\n" ); return NULL; } } return NULL; } static DFBResult v4l2_playto( CoreSurface *surface, DFBRectangle *rect, IDirectFBVideoProvider_V4L_data *data ) { int palette; int err; int i; D_DEBUG( "DirectFB/Video4Linux2: %s...\n", __FUNCTION__ ); switch (surface->config.format) { case DSPF_YUY2: palette = V4L2_PIX_FMT_YUYV; break; case DSPF_UYVY: palette = V4L2_PIX_FMT_UYVY; break; case DSPF_I420: palette = V4L2_PIX_FMT_YUV420; break; case DSPF_YV12: palette = V4L2_PIX_FMT_YVU420; break; case DSPF_NV12: palette = V4L2_PIX_FMT_NV12; break; case DSPF_NV21: palette = V4L2_PIX_FMT_NV21; break; case DSPF_RGB332: palette = V4L2_PIX_FMT_RGB332; break; case DSPF_ARGB1555: palette = V4L2_PIX_FMT_RGB555; break; case DSPF_RGB16: palette = V4L2_PIX_FMT_RGB565; break; case DSPF_RGB24: palette = V4L2_PIX_FMT_BGR24; break; case DSPF_ARGB: case DSPF_AiRGB: case DSPF_RGB32: palette = V4L2_PIX_FMT_BGR32; break; default: return DFB_UNSUPPORTED; } err = dfb_surface_lock_buffer( surface, CSBR_BACK, CSAID_GPU, CSAF_WRITE, &data->destinationlock ); if (err) return err; data->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; data->fmt.fmt.pix.width = surface->config.size.w; data->fmt.fmt.pix.height = surface->config.size.h; data->fmt.fmt.pix.pixelformat = palette; data->fmt.fmt.pix.bytesperline = data->destinationlock.pitch; data->fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; /* fixme: we can do field based capture, too */ D_DEBUG( "DirectFB/Video4Linux2: surface->config.size.w:%d, surface->config.size.h:%d.\n", surface->config.size.w, surface->config.size.h ); err = ioctl( data->fd, VIDIOC_S_FMT, &data->fmt ); if (err) { D_PERROR( "DirectFB/Video4Linux2: VIDIOC_S_FMT.\n" ); dfb_surface_unlock_buffer( surface, &data->destinationlock ); return err; } if (data->fmt.fmt.pix.width != surface->config.size.w || data->fmt.fmt.pix.height != surface->config.size.h) { D_PERROR( "DirectFB/Video4Linux2: driver cannot fulfill application request.\n" ); dfb_surface_unlock_buffer( surface, &data->destinationlock ); return DFB_UNSUPPORTED; /* fixme */ } if (data->brightness.id) { ioctl( data->fd, VIDIOC_G_CTRL, &data->brightness ); } if (data->contrast.id) { ioctl( data->fd, VIDIOC_G_CTRL, &data->contrast ); } if (data->saturation.id) { ioctl( data->fd, VIDIOC_G_CTRL, &data->saturation ); } if (data->hue.id) { ioctl( data->fd, VIDIOC_G_CTRL, &data->hue ); } if (surface->config.caps & DSCAPS_SYSTEMONLY) { data->framebuffer_or_system = 1; data->req.memory = V4L2_MEMORY_MMAP; dfb_surface_unlock_buffer( surface, &data->destinationlock ); } else { struct v4l2_framebuffer fb; data->framebuffer_or_system = 0; data->req.memory = V4L2_MEMORY_OVERLAY; fb.base = (void *) dfb_gfxcard_memory_physical( NULL, 0 ); fb.fmt.width = surface->config.size.w; fb.fmt.height = surface->config.size.h; fb.fmt.pixelformat = palette; D_DEBUG( "w:%d, h:%d, bpl:%d, base:0x%08lx\n", fb.fmt.width, fb.fmt.height, fb.fmt.bytesperline, (unsigned long)fb.base ); if (ioctl( data->fd, VIDIOC_S_FBUF, &fb ) < 0) { DFBResult ret = errno2result( errno ); D_PERROR( "DirectFB/Video4Linux2: VIDIOC_S_FBUF failed, must run being root!\n" ); dfb_surface_unlock_buffer( surface, &data->destinationlock ); return ret; } } /* Ask Video Device for Buffers */ data->req.count = NUMBER_OF_BUFFERS; data->req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; err = ioctl( data->fd, VIDIOC_REQBUFS, &data->req ); if (err < 0 || data->req.count < NUMBER_OF_BUFFERS) { D_PERROR( "DirectFB/Video4Linux2: VIDIOC_REQBUFS: %d, %d.\n", err, data->req.count ); if (!data->framebuffer_or_system) dfb_surface_unlock_buffer( surface, &data->destinationlock ); return err; } /* Query each buffer and map it to the video device if necessary */ for (i = 0; i < data->req.count; i++) { struct v4l2_buffer *vidbuf = &data->vidbuf[i]; vidbuf->index = i; vidbuf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; err = ioctl( data->fd, VIDIOC_QUERYBUF, vidbuf ); if (err < 0) { D_PERROR( "DirectFB/Video4Linux2: VIDIOC_QUERYBUF.\n" ); if (!data->framebuffer_or_system) dfb_surface_unlock_buffer( surface, &data->destinationlock ); return err; } /* if (vidbuf->length == 0) { D_PERROR( "DirectFB/Video4Linux2: length is zero!\n" ); return -EINVAL; } */ if (data->framebuffer_or_system) { data->ptr[i] = mmap( 0, vidbuf->length, PROT_READ | PROT_WRITE, MAP_SHARED, data->fd, vidbuf->m.offset ); if (data->ptr[i] == MAP_FAILED) { D_PERROR( "DirectFB/Video4Linux2: mmap().\n" ); if (!data->framebuffer_or_system) dfb_surface_unlock_buffer( surface, &data->destinationlock ); return err; } } D_DEBUG( "DirectFB/Video4Linux2: len:0x%08x, %d => 0x%08x\n", vidbuf->length, i, (u32) data->ptr[i] ); } if (!data->cleanup) data->cleanup = dfb_core_cleanup_add( NULL, v4l_cleanup, data, true ); data->destination = surface; dfb_surface_attach( surface, v4l_systemsurface_listener, data, &data->reaction ); data->running = true; data->thread = direct_thread_create( DTT_DEFAULT, V4L2_Thread, data, "Video4Linux 2" ); return DFB_OK; } #endif