diff options
-rw-r--r-- | include/xine.h | 77 | ||||
-rw-r--r-- | include/xine/video_out.h | 6 | ||||
-rw-r--r-- | src/video_out/video_out_vdpau.c | 238 | ||||
-rw-r--r-- | src/xine-engine/Makefile.am | 11 | ||||
-rw-r--r-- | src/xine-engine/post.c | 11 | ||||
-rw-r--r-- | src/xine-engine/video_out.c | 341 | ||||
-rw-r--r-- | src/xine-engine/xine.c | 11 |
7 files changed, 672 insertions, 23 deletions
diff --git a/include/xine.h b/include/xine.h index 0d8b176ff..d6f54b7d9 100644 --- a/include/xine.h +++ b/include/xine.h @@ -460,6 +460,83 @@ int xine_get_current_frame_data (xine_stream_t *stream, int64_t xine_get_current_vpts(xine_stream_t *stream) XINE_PROTECTED; +/* + * Continuous video frame grabbing feature. + * + * In opposite to the 'xine_get_current_frame' based snapshot function this grabbing + * feature allow continuous grabbing of last or next displayed video frame. + * Grabbed video frames are returned in simple three byte RGB format. + * + * Depending on the capabilities of the used video output driver video image data is + * taken as close as possible at the end of the video processing chain. Thus a returned + * video image could contain the blended OSD data, is deinterlaced, cropped and scaled + * and video properties like hue, sat could be applied. + * If a video output driver does not have a decent grabbing implementation then there + * is a generic fallback feature that grabs the video frame as they are taken from the video + * display queue (like the xine_get_current_frame' function). + * In this case color correct conversation to a RGB image incorporating source cropping + * and scaling to the requested grab size is also supported. + * + * The caller must first request a new video grab frame using the public 'xine_new_grab_video_frame' + * function. Then the caller should populate the frame with the wanted source cropping, grab image + * size and control flags. After that grab requests could be done by calling the supplied grab() feature + * of the frame. At the end a call to the supplied dispose() feature of the frame releases all needed + * resources. + * The caller should have acquired a port ticket while calling these features. + * + */ +#define HAVE_XINE_GRAB_VIDEO_FRAME 1 + +/* + * frame structure used for grabbing video frames of format RGB. + */ +typedef struct xine_grab_video_frame_s xine_grab_video_frame_t; +struct xine_grab_video_frame_s { + /* + * grab last/next displayed image. + * returns 0 if grab is successful, 1 on timeout and -1 on error + */ + int (*grab) (xine_grab_video_frame_t *self); + + /* + * free all resources. + */ + void (*dispose) (xine_grab_video_frame_t *self); + + /* + * Cropping of source image. Has to be specified by caller. + */ + int crop_left; + int crop_right; + int crop_top; + int crop_bottom; + + /* + * Parameters of returned RGB image. + * Caller can specify wanted frame size giving width and/or height a value > 0. + * In this case the grabbed image is scaled to the requested size. + * Otherwise the grab function returns the actual size of the grabbed image + * in width/height without scaling the image. + */ + int width, height; /* requested/returned size of image */ + uint8_t *img; /* returned RGB image data taking three bytes per pixel */ + int64_t vpts; /* virtual presentation timestamp (1/90000 sec) of returned frame */ + + int timeout; /* Max. time to wait for next displayed frame in milliseconds */ + int flags; /* Controlling flags. See XINE_GRAB_VIDEO_FRAME_FLAGS_* definitions */ +}; + +#define XINE_GRAB_VIDEO_FRAME_FLAGS_CONTINUOUS 0x01 /* optimize resource allocation for continuous frame grabbing */ +#define XINE_GRAB_VIDEO_FRAME_FLAGS_WAIT_NEXT 0x02 /* wait for next display frame instead of using last displayed frame */ + +#define XINE_GRAB_VIDEO_FRAME_DEFAULT_TIMEOUT 500 + +/* + * Allocate new grab video frame. Returns NULL on error. + */ +xine_grab_video_frame_t* xine_new_grab_video_frame (xine_stream_t *stream) XINE_PROTECTED; + + /********************************************************************* * media processing * *********************************************************************/ diff --git a/include/xine/video_out.h b/include/xine/video_out.h index 799e8f726..5a0401160 100644 --- a/include/xine/video_out.h +++ b/include/xine/video_out.h @@ -196,6 +196,9 @@ struct xine_video_port_s { uint32_t height, double ratio, int format, int flags); + /* create a new grab video frame */ + xine_grab_video_frame_t* (*new_grab_video_frame) (xine_video_port_t *self); + /* retrieves the last displayed frame (useful for taking snapshots) */ vo_frame_t* (*get_last_frame) (xine_video_port_t *self); @@ -388,6 +391,9 @@ struct vo_driver_s { */ int (*redraw_needed) (vo_driver_t *self); + /* Create a new grab video frame */ + xine_grab_video_frame_t* (*new_grab_video_frame)(vo_driver_t *self); + /* * free all resources, close driver */ diff --git a/src/video_out/video_out_vdpau.c b/src/video_out/video_out_vdpau.c index 3bb2bd652..69dcbae9e 100644 --- a/src/video_out/video_out_vdpau.c +++ b/src/video_out/video_out_vdpau.c @@ -134,7 +134,9 @@ VdpVideoSurfaceGetParameters *vdp_video_surface_get_parameters; VdpOutputSurfaceCreate *vdp_output_surface_create; VdpOutputSurfaceDestroy *vdp_output_surface_destroy; VdpOutputSurfaceRenderBitmapSurface *vdp_output_surface_render_bitmap_surface; +VdpOutputSurfaceRenderOutputSurface *vdp_output_surface_render_output_surface; VdpOutputSurfacePutBitsNative *vdp_output_surface_put_bits; +VdpOutputSurfaceGetBitsNative *vdp_output_surface_get_bits; VdpVideoMixerCreate *vdp_video_mixer_create; VdpVideoMixerDestroy *vdp_video_mixer_destroy; @@ -291,6 +293,17 @@ static VdpStatus guarded_vdp_decoder_render(VdpDecoder decoder, VdpVideoSurface typedef struct { + xine_grab_video_frame_t grab_frame; + + vo_driver_t *vo_driver; + VdpOutputSurface render_surface; + int vdp_runtime_nr; + int width, height; + uint32_t *rgba; +} vdpau_grab_video_frame_t; + + +typedef struct { VdpBitmapSurface ovl_bitmap; uint32_t bitmap_width, bitmap_height; int ovl_w, ovl_h; /* overlay's width and height */ @@ -375,6 +388,10 @@ typedef struct { uint8_t init_queue; uint8_t queue_length; + vdpau_grab_video_frame_t *pending_grab_request; + pthread_mutex_t grab_lock; + pthread_cond_t grab_cond; + VdpVideoMixer video_mixer; VdpChromaType video_mixer_chroma; uint32_t video_mixer_width; @@ -1588,6 +1605,117 @@ static void vdpau_check_output_size( vo_driver_t *this_gen ) } +static void vdpau_grab_current_output_surface (vdpau_driver_t *this, int64_t vpts) +{ + pthread_mutex_lock(&this->grab_lock); + + vdpau_grab_video_frame_t *frame = this->pending_grab_request; + if (frame) { + VdpStatus st; + + this->pending_grab_request = NULL; + frame->grab_frame.vpts = -1; + + VdpOutputSurface grab_surface = this->output_surface[this->current_output_surface]; + int width = this->output_surface_width[this->current_output_surface]; + int height = this->output_surface_height[this->current_output_surface]; + + /* take cropping parameters into account */ + width = width - frame->grab_frame.crop_left - frame->grab_frame.crop_right; + height = height - frame->grab_frame.crop_top - frame->grab_frame.crop_bottom; + if (width < 1) + width = 1; + if (height < 1) + height = 1; + + /* if caller does not specify frame size we return the actual size of grabbed frame */ + if (frame->grab_frame.width <= 0) + frame->grab_frame.width = width; + if (frame->grab_frame.height <= 0) + frame->grab_frame.height = height; + + if (frame->vdp_runtime_nr != this->vdp_runtime_nr) + frame->render_surface = VDP_INVALID_HANDLE; + + if (frame->grab_frame.width != frame->width || frame->grab_frame.height != frame->height) { + free(frame->rgba); + free(frame->grab_frame.img); + frame->rgba = NULL; + frame->grab_frame.img = NULL; + + if (frame->render_surface != VDP_INVALID_HANDLE) { + st = vdp_output_surface_destroy(frame->render_surface); + frame->render_surface = VDP_INVALID_HANDLE; + if (st != VDP_STATUS_OK) { + fprintf(stderr, "vo_vdpau: Can't destroy grab render output surface: %s\n", vdp_get_error_string (st)); + pthread_cond_broadcast(&this->grab_cond); + pthread_mutex_unlock(&this->grab_lock); + return; + } + } + + frame->width = frame->grab_frame.width; + frame->height = frame->grab_frame.height; + } + + if (frame->rgba == NULL) { + frame->rgba = (uint32_t *) calloc(frame->width * frame->height, sizeof(uint32_t)); + if (frame->rgba == NULL) { + pthread_cond_broadcast(&this->grab_cond); + pthread_mutex_unlock(&this->grab_lock); + return; + } + } + if (frame->grab_frame.img == NULL) { + frame->grab_frame.img = (uint8_t *) calloc(frame->width * frame->height, 3); + if (frame->grab_frame.img == NULL) { + pthread_cond_broadcast(&this->grab_cond); + pthread_mutex_unlock(&this->grab_lock); + return; + } + } + + uint32_t pitches = frame->width * sizeof(uint32_t); + VdpRect src_rect = { frame->grab_frame.crop_left, frame->grab_frame.crop_top, width+frame->grab_frame.crop_left, height+frame->grab_frame.crop_top }; + + if (frame->width != width || frame->height != height) { + st = VDP_STATUS_OK; + if (frame->render_surface == VDP_INVALID_HANDLE) { + frame->vdp_runtime_nr = this->vdp_runtime_nr; + st = vdp_output_surface_create(vdp_device, VDP_RGBA_FORMAT_B8G8R8A8, frame->width, frame->height, &frame->render_surface); + } + if (st == VDP_STATUS_OK) { + st = vdp_output_surface_render_output_surface(frame->render_surface, NULL, grab_surface, &src_rect, NULL, NULL, VDP_OUTPUT_SURFACE_RENDER_ROTATE_0); + if (st == VDP_STATUS_OK) { + st = vdp_output_surface_get_bits(frame->render_surface, NULL, &frame->rgba, &pitches); + if (st == VDP_STATUS_OK) { + if (!(frame->grab_frame.flags & XINE_GRAB_VIDEO_FRAME_FLAGS_CONTINUOUS)) { + st = vdp_output_surface_destroy(frame->render_surface); + if (st != VDP_STATUS_OK) + fprintf(stderr, "vo_vdpau: Can't destroy grab render output surface: %s\n", vdp_get_error_string (st)); + frame->render_surface = VDP_INVALID_HANDLE; + } + } else + fprintf(stderr, "vo_vdpau: Can't get output surface bits for raw frame grabbing: %s\n", vdp_get_error_string (st)); + } else + fprintf(stderr, "vo_vdpau: Can't render output surface for raw frame grabbing: %s\n", vdp_get_error_string (st)); + } else + fprintf(stderr, "vo_vdpau: Can't create render output surface for raw frame grabbing: %s\n", vdp_get_error_string (st)); + } else { + st = vdp_output_surface_get_bits(grab_surface, &src_rect, &frame->rgba, &pitches); + if (st != VDP_STATUS_OK) + fprintf(stderr, "vo_vdpau: Can't get output surface bits for raw frame grabbing: %s\n", vdp_get_error_string (st)); + } + + if (st == VDP_STATUS_OK) + frame->grab_frame.vpts = vpts; + + pthread_cond_broadcast(&this->grab_cond); + } + + pthread_mutex_unlock(&this->grab_lock); +} + static void vdpau_display_frame (vo_driver_t *this_gen, vo_frame_t *frame_gen) { @@ -1811,6 +1939,7 @@ static void vdpau_display_frame (vo_driver_t *this_gen, vo_frame_t *frame_gen) if ( st != VDP_STATUS_OK ) fprintf(stderr, "vo_vdpau: vdp_video_mixer_render error : %s\n", vdp_get_error_string( st ) ); + vdpau_grab_current_output_surface( this, frame->vo_frame.vpts ); vdp_queue_get_time( vdp_queue, ¤t_time ); vdp_queue_display( vdp_queue, this->output_surface[this->current_output_surface], 0, 0, 0 ); /* display _now_ */ vdpau_shift_queue( this_gen ); @@ -1861,6 +1990,7 @@ static void vdpau_display_frame (vo_driver_t *this_gen, vo_frame_t *frame_gen) if ( st != VDP_STATUS_OK ) fprintf(stderr, "vo_vdpau: vdp_video_mixer_render error : %s\n", vdp_get_error_string( st ) ); + vdpau_grab_current_output_surface( this, frame->vo_frame.vpts ); vdp_queue_display( vdp_queue, this->output_surface[this->current_output_surface], 0, 0, 0 ); vdpau_shift_queue( this_gen ); } @@ -1990,6 +2120,101 @@ static void vdpau_get_property_min_max (vo_driver_t *this_gen, int property, int } +/* + * functions for grabbing RGB images from displayed frames + */ +static void vdpau_dispose_grab_video_frame(xine_grab_video_frame_t *frame_gen) +{ + vdpau_grab_video_frame_t *frame = (vdpau_grab_video_frame_t *) frame_gen; + vdpau_driver_t *this = (vdpau_driver_t *) frame->vo_driver; + + free(frame->grab_frame.img); + free(frame->rgba); + if (frame->render_surface != VDP_INVALID_HANDLE && frame->vdp_runtime_nr == this->vdp_runtime_nr) { + VdpStatus st; + st = vdp_output_surface_destroy(frame->render_surface); + if (st != VDP_STATUS_OK) + fprintf(stderr, "vo_vdpau: Can't destroy grab render output surface: %s\n", vdp_get_error_string (st) ); + } + free(frame); +} + +/* + * grab next displayed output surface. + * Note: This feature only supports grabbing of next displayed frame (implicit VO_GRAB_FRAME_FLAGS_WAIT_NEXT) + */ +static int vdpau_grab_grab_video_frame (xine_grab_video_frame_t *frame_gen) { + vdpau_grab_video_frame_t *frame = (vdpau_grab_video_frame_t *) frame_gen; + vdpau_driver_t *this = (vdpau_driver_t *) frame->vo_driver; + struct timeval tvnow, tvdiff, tvtimeout; + struct timespec ts; + + /* calculate absolute timeout time */ + tvdiff.tv_sec = frame->grab_frame.timeout / 1000; + tvdiff.tv_usec = frame->grab_frame.timeout % 1000; + tvdiff.tv_usec *= 1000; + gettimeofday(&tvnow, NULL); + timeradd(&tvnow, &tvdiff, &tvtimeout); + ts.tv_sec = tvtimeout.tv_sec; + ts.tv_nsec = tvtimeout.tv_usec; + ts.tv_nsec *= 1000; + + pthread_mutex_lock(&this->grab_lock); + + /* wait until other pending grab request is finished */ + while (this->pending_grab_request) { + if (pthread_cond_timedwait(&this->grab_cond, &this->grab_lock, &ts) == ETIMEDOUT) { + pthread_mutex_unlock(&this->grab_lock); + return 1; /* no frame available */ + } + } + + this->pending_grab_request = frame; + + /* wait until our request is finished */ + while (this->pending_grab_request) { + if (pthread_cond_timedwait(&this->grab_cond, &this->grab_lock, &ts) == ETIMEDOUT) { + this->pending_grab_request = NULL; + pthread_mutex_unlock(&this->grab_lock); + return 1; /* no frame available */ + } + } + + pthread_mutex_unlock(&this->grab_lock); + + if (frame->grab_frame.vpts == -1) + return -1; /* error happened */ + + /* convert ARGB image to RGB image */ + uint32_t *src = frame->rgba; + uint8_t *dst = frame->grab_frame.img; + int n = frame->width * frame->height; + while (n--) { + uint32_t rgba = *src++; + *dst++ = (uint8_t)(rgba >> 16); /*R*/ + *dst++ = (uint8_t)(rgba >> 8); /*G*/ + *dst++ = (uint8_t)(rgba); /*B*/ + } + + return 0; +} + + +static xine_grab_video_frame_t * vdpau_new_grab_video_frame(vo_driver_t *this) +{ + vdpau_grab_video_frame_t *frame = calloc(1, sizeof(vdpau_grab_video_frame_t)); + if (frame) { + frame->grab_frame.dispose = vdpau_dispose_grab_video_frame; + frame->grab_frame.grab = vdpau_grab_grab_video_frame; + frame->grab_frame.vpts = -1; + frame->grab_frame.timeout = XINE_GRAB_VIDEO_FRAME_DEFAULT_TIMEOUT; + frame->vo_driver = this; + frame->render_surface = VDP_INVALID_HANDLE; + } + + return (xine_grab_video_frame_t *) frame; +} + static int vdpau_gui_data_exchange (vo_driver_t *this_gen, int data_type, void *data) { @@ -2131,6 +2356,8 @@ static void vdpau_dispose (vo_driver_t *this_gen) if ( (vdp_device != VDP_INVALID_HANDLE) && vdp_device_destroy ) vdp_device_destroy( vdp_device ); + pthread_mutex_destroy(&this->grab_lock); + pthread_cond_destroy(&this->grab_cond); pthread_mutex_destroy(&this->drawable_lock); free (this); } @@ -2365,6 +2592,7 @@ static vo_driver_t *vdpau_open_plugin (video_driver_class_t *class_gen, const vo this->vo_driver.gui_data_exchange = vdpau_gui_data_exchange; this->vo_driver.dispose = vdpau_dispose; this->vo_driver.redraw_needed = vdpau_redraw_needed; + this->vo_driver.new_grab_video_frame = vdpau_new_grab_video_frame; this->surface_cleared_nr = 0; @@ -2481,9 +2709,15 @@ static vo_driver_t *vdpau_open_plugin (video_driver_class_t *class_gen, const vo st = vdp_get_proc_address( vdp_device, VDP_FUNC_ID_OUTPUT_SURFACE_RENDER_BITMAP_SURFACE , (void*)&vdp_output_surface_render_bitmap_surface ); if ( vdpau_init_error( st, "Can't get OUTPUT_SURFACE_RENDER_BITMAP_SURFACE proc address !!", &this->vo_driver, 1 ) ) return NULL; + st = vdp_get_proc_address( vdp_device, VDP_FUNC_ID_OUTPUT_SURFACE_RENDER_OUTPUT_SURFACE , (void*)&vdp_output_surface_render_output_surface ); + if ( vdpau_init_error( st, "Can't get OUTPUT_SURFACE_RENDER_OUTPUT_SURFACE proc address !!", &this->vo_driver, 1 ) ) + return NULL; st = vdp_get_proc_address( vdp_device, VDP_FUNC_ID_OUTPUT_SURFACE_PUT_BITS_NATIVE , (void*)&vdp_output_surface_put_bits ); if ( vdpau_init_error( st, "Can't get VDP_FUNC_ID_OUTPUT_SURFACE_PUT_BITS_NATIVE proc address !!", &this->vo_driver, 1 ) ) return NULL; + st = vdp_get_proc_address( vdp_device, VDP_FUNC_ID_OUTPUT_SURFACE_GET_BITS_NATIVE , (void*)&vdp_output_surface_get_bits ); + if ( vdpau_init_error( st, "Can't get VDP_FUNC_ID_OUTPUT_SURFACE_GET_BITS_NATIVE proc address !!", &this->vo_driver, 1 ) ) + return NULL; st = vdp_get_proc_address( vdp_device, VDP_FUNC_ID_VIDEO_MIXER_CREATE , (void*)&vdp_video_mixer_create ); if ( vdpau_init_error( st, "Can't get VIDEO_MIXER_CREATE proc address !!", &this->vo_driver, 1 ) ) return NULL; @@ -2866,6 +3100,10 @@ static vo_driver_t *vdpau_open_plugin (video_driver_class_t *class_gen, const vo this->vdp_runtime_nr = 1; + this->pending_grab_request = NULL; + pthread_mutex_init(&this->grab_lock, NULL); + pthread_cond_init(&this->grab_cond, NULL); + return &this->vo_driver; } diff --git a/src/xine-engine/Makefile.am b/src/xine-engine/Makefile.am index 15553380a..b081a4f30 100644 --- a/src/xine-engine/Makefile.am +++ b/src/xine-engine/Makefile.am @@ -1,12 +1,14 @@ include $(top_srcdir)/misc/Makefile.common include $(top_srcdir)/lib/Makefile.common -AM_CFLAGS = $(DEFAULT_OCFLAGS) $(X_CFLAGS) $(FT2_CFLAGS) $(FONTCONFIG_CFLAGS) \ +AM_CFLAGS = -I$(top_builddir)/src/video_out $(DEFAULT_OCFLAGS) $(X_CFLAGS) $(FT2_CFLAGS) $(FONTCONFIG_CFLAGS) \ $(AVUTIL_CFLAGS) $(VISIBILITY_FLAG) AM_CPPFLAGS = $(XDG_BASEDIR_CPPFLAGS) $(ZLIB_CPPFLAGS) -DXINE_LIBRARY_COMPILE XINEUTILS_LIB = $(top_builddir)/src/xine-utils/libxineutils.la +YUV_LIB = $(top_builddir)/src/video_out/libyuv2rgb.la + # FIXME: these are currently unused: EXTRA_DIST = lrb.c lrb.h accel_vdpau.h accel_xvmc.h @@ -33,11 +35,11 @@ libxine_la_SOURCES = xine.c metronom.c configfile.c buffer.c \ alphablend.c \ xine_private.h -libxine_la_DEPENDENCIES = $(XINEUTILS_LIB) $(XDG_BASEDIR_DEPS) \ +libxine_la_DEPENDENCIES = $(XINEUTILS_LIB) $(YUV_LIB) $(XDG_BASEDIR_DEPS) \ $(pthread_dep) $(LIBXINEPOSIX) \ libxine-interface.la libxine_la_LIBADD = $(PTHREAD_LIBS) $(DYNAMIC_LD_LIBS) $(LTLIBINTL) $(ZLIB_LIBS) \ - -lm $(XINEUTILS_LIB) $(LTLIBICONV) $(FT2_LIBS) $(FONTCONFIG_LIBS) \ + -lm $(XINEUTILS_LIB) $(YUV_LIB) $(LTLIBICONV) $(FT2_LIBS) $(FONTCONFIG_LIBS) \ $(LIBXINEPOSIX) $(RT_LIBS) $(NET_LIBS) $(XDG_BASEDIR_LIBS) \ $(AVUTIL_LIBS) @@ -60,6 +62,9 @@ clean-local: $(XINEUTILS_LIB): $(MAKE) -C $(top_builddir)/src/xine-utils libxineutils.la +$(YUV_LIB): + $(MAKE) -C $(top_builddir)/src/video_out libyuv2rgb.la + if WIN32 install-exec-local: cp -p $(DEF_FILE) $(DESTDIR)$(libdir) diff --git a/src/xine-engine/post.c b/src/xine-engine/post.c index d9b9fb209..30e61acd4 100644 --- a/src/xine-engine/post.c +++ b/src/xine-engine/post.c @@ -90,6 +90,16 @@ static vo_frame_t *post_video_get_last_frame(xine_video_port_t *port_gen) { return frame; } +static xine_grab_video_frame_t *post_video_new_grab_video_frame(xine_video_port_t *port_gen) { + post_video_port_t *port = (post_video_port_t *)port_gen; + xine_grab_video_frame_t *frame; + + if (port->port_lock) pthread_mutex_lock(port->port_lock); + frame = port->original_port->new_grab_video_frame(port->original_port); + if (port->port_lock) pthread_mutex_unlock(port->port_lock); + return frame; +} + static void post_video_enable_ovl(xine_video_port_t *port_gen, int ovl_enable) { post_video_port_t *port = (post_video_port_t *)port_gen; @@ -223,6 +233,7 @@ post_video_port_t *_x_post_intercept_video_port(post_plugin_t *post, xine_video_ port->new_port.open = post_video_open; port->new_port.get_frame = post_video_get_frame; port->new_port.get_last_frame = post_video_get_last_frame; + port->new_port.new_grab_video_frame = post_video_new_grab_video_frame; port->new_port.enable_ovl = post_video_enable_ovl; port->new_port.close = post_video_close; port->new_port.exit = post_video_exit; diff --git a/src/xine-engine/video_out.c b/src/xine-engine/video_out.c index bceb38a58..f348da3f5 100644 --- a/src/xine-engine/video_out.c +++ b/src/xine-engine/video_out.c @@ -49,6 +49,7 @@ #include <xine/video_out.h> #include <xine/metronom.h> #include <xine/xineutils.h> +#include <yuv2rgb.h> #define NUM_FRAME_BUFFERS 15 #define MAX_USEC_TO_SLEEP 20000 @@ -66,6 +67,24 @@ static vo_frame_t * crop_frame( xine_video_port_t *this_gen, vo_frame_t *img ); +typedef struct vos_grab_video_frame_s vos_grab_video_frame_t; +struct vos_grab_video_frame_s { + xine_grab_video_frame_t grab_frame; + + vos_grab_video_frame_t *next; + int finished; + xine_video_port_t *video_port; + vo_frame_t *vo_frame; + yuv2rgb_factory_t *yuv2rgb_factory; + yuv2rgb_t *yuv2rgb; + int vo_width, vo_height; + int grab_width, grab_height; + int y_stride, uv_stride; + int img_size; + uint8_t *img; +}; + + typedef struct { vo_frame_t *first; vo_frame_t *last; @@ -91,10 +110,13 @@ typedef struct { img_buf_fifo_t *free_img_buf_queue; img_buf_fifo_t *display_img_buf_queue; - pthread_mutex_t last_frame_mutex; - vo_frame_t *last_frame; vo_frame_t *img_backup; + vo_frame_t *last_frame; + vos_grab_video_frame_t *pending_grab_request; + pthread_mutex_t grab_lock; + pthread_cond_t grab_cond; + uint32_t video_loop_running:1; uint32_t video_opened:1; @@ -331,6 +353,288 @@ static void vo_frame_dec_lock (vo_frame_t *img) { pthread_mutex_unlock (&img->mutex); } + +/* + * functions for grabbing RGB images from displayed frames + */ +static void vo_dispose_grab_video_frame(xine_grab_video_frame_t *frame_gen) +{ + vos_grab_video_frame_t *frame = (vos_grab_video_frame_t *) frame_gen; + + if (frame->vo_frame) + vo_frame_dec_lock(frame->vo_frame); + + if (frame->yuv2rgb) + frame->yuv2rgb->dispose(frame->yuv2rgb); + + if (frame->yuv2rgb_factory) + frame->yuv2rgb_factory->dispose(frame->yuv2rgb_factory); + + free(frame->img); + free(frame->grab_frame.img); + free(frame); +} + + +static int vo_grab_grab_video_frame (xine_grab_video_frame_t *frame_gen) { + vos_grab_video_frame_t *frame = (vos_grab_video_frame_t *) frame_gen; + vos_t *this = (vos_t *) frame->video_port; + vo_frame_t *vo_frame; + int format, y_stride, uv_stride; + uint8_t *base[3]; + + if (frame->grab_frame.flags & XINE_GRAB_VIDEO_FRAME_FLAGS_WAIT_NEXT) { + struct timeval tvnow, tvdiff, tvtimeout; + struct timespec ts; + + /* calculate absolute timeout time */ + tvdiff.tv_sec = frame->grab_frame.timeout / 1000; + tvdiff.tv_usec = frame->grab_frame.timeout % 1000; + tvdiff.tv_usec *= 1000; + gettimeofday(&tvnow, NULL); + timeradd(&tvnow, &tvdiff, &tvtimeout); + ts.tv_sec = tvtimeout.tv_sec; + ts.tv_nsec = tvtimeout.tv_usec; + ts.tv_nsec *= 1000; + + pthread_mutex_lock(&this->grab_lock); + + /* insert grab request into grab queue */ + frame->next = this->pending_grab_request; + this->pending_grab_request = frame; + + /* wait until our request is finished */ + frame->finished = 0; + while (!frame->finished) { + if (pthread_cond_timedwait(&this->grab_cond, &this->grab_lock, &ts) == ETIMEDOUT) { + vos_grab_video_frame_t *prev = this->pending_grab_request; + while (prev) { + if (prev == frame) { + this->pending_grab_request = frame->next; + break; + } else if (prev->next == frame) { + prev->next = frame->next; + break; + } + prev = prev->next; + } + frame->next = NULL; + pthread_mutex_unlock(&this->grab_lock); + return 1; /* no frame available */ + } + } + + pthread_mutex_unlock(&this->grab_lock); + + vo_frame = frame->vo_frame; + frame->vo_frame = NULL; + if (!vo_frame) + return -1; /* error happened */ + } else { + pthread_mutex_lock(&this->grab_lock); + + /* use last displayed frame */ + vo_frame = this->last_frame; + if (!vo_frame) { + pthread_mutex_unlock(&this->grab_lock); + return 1; /* no frame available */ + } + if (vo_frame->format != XINE_IMGFMT_YV12 && vo_frame->format != XINE_IMGFMT_YUY2 && !vo_frame->proc_provide_standard_frame_data) { + pthread_mutex_unlock(&this->grab_lock); + return -1; /* error happened */ + } + vo_frame_inc_lock(vo_frame); + pthread_mutex_unlock(&this->grab_lock); + frame->grab_frame.vpts = vo_frame->vpts; + } + + int width = vo_frame->width; + int height = vo_frame->height; + + if (vo_frame->format == XINE_IMGFMT_YV12 || vo_frame->format == XINE_IMGFMT_YUY2) { + format = vo_frame->format; + y_stride = vo_frame->pitches[0]; + uv_stride = vo_frame->pitches[1]; + base[0] = vo_frame->base[0]; + base[1] = vo_frame->base[1]; + base[2] = vo_frame->base[2]; + } else { + /* retrieve standard format image data from output driver */ + xine_current_frame_data_t data; + memset(&data, 0, sizeof(data)); + vo_frame->proc_provide_standard_frame_data(vo_frame, &data); + if (data.img_size > frame->img_size) { + free(frame->img); + frame->img_size = data.img_size; + frame->img = calloc(data.img_size, sizeof(uint8_t)); + if (!frame->img) { + vo_frame_dec_lock(vo_frame); + return -1; /* error happened */ + } + } + data.img = frame->img; + vo_frame->proc_provide_standard_frame_data(vo_frame, &data); + format = data.format; + if (format == XINE_IMGFMT_YV12) { + y_stride = width; + uv_stride = width / 2; + base[0] = data.img; + base[1] = data.img + width * height; + base[2] = data.img + width * height + width * height / 4; + } else { // XINE_IMGFMT_YUY2 + y_stride = width * 2; + uv_stride = 0; + base[0] = data.img; + base[1] = NULL; + base[2] = NULL; + } + } + + /* take cropping parameters into account */ + int crop_left = (vo_frame->crop_left + frame->grab_frame.crop_left) & ~1; + int crop_right = (vo_frame->crop_right + frame->grab_frame.crop_right) & ~1; + int crop_top = vo_frame->crop_top + frame->grab_frame.crop_top; + int crop_bottom = vo_frame->crop_bottom + frame->grab_frame.crop_bottom; + + if (crop_left || crop_right || crop_top || crop_bottom) { + if ((width - crop_left - crop_right) >= 8) + width = width - crop_left - crop_right; + else + crop_left = crop_right = 0; + + if ((height - crop_top - crop_bottom) >= 8) + height = height - crop_top - crop_bottom; + else + crop_top = crop_bottom = 0; + + if (format == XINE_IMGFMT_YV12) { + base[0] += crop_top * y_stride + crop_left; + base[1] += crop_top/2 * uv_stride + crop_left/2; + base[2] += crop_top/2 * uv_stride + crop_left/2; + } else { // XINE_IMGFMT_YUY2 + base[0] += crop_top * y_stride + crop_left*2; + } + } + + /* if caller does not specify frame size we return the actual size of grabbed frame */ + if (frame->grab_frame.width <= 0) + frame->grab_frame.width = width; + if (frame->grab_frame.height <= 0) + frame->grab_frame.height = height; + + /* allocate grab frame image buffer */ + if (frame->grab_frame.width != frame->grab_width || frame->grab_frame.height != frame->grab_height) { + free(frame->grab_frame.img); + frame->grab_frame.img = NULL; + } + if (frame->grab_frame.img == NULL) { + frame->grab_frame.img = (uint8_t *) calloc(frame->grab_frame.width * frame->grab_frame.height, 3); + if (frame->grab_frame.img == NULL) { + vo_frame_dec_lock(vo_frame); + return -1; /* error happened */ + } + } + + /* initialize yuv2rgb factory */ + if (!frame->yuv2rgb_factory) { + frame->yuv2rgb_factory = yuv2rgb_factory_init(MODE_24_RGB, 0, NULL); + if (!frame->yuv2rgb_factory) { + vo_frame_dec_lock(vo_frame); + return -1; /* error happened */ + } + frame->yuv2rgb_factory->matrix_coefficients = 1; /* ITU-R Rec. 709 (1990) */ + frame->yuv2rgb_factory->set_csc_levels (frame->yuv2rgb_factory, 0, 128, 128); + } + + /* retrieve a yuv2rgb converter */ + if (!frame->yuv2rgb) { + frame->yuv2rgb = frame->yuv2rgb_factory->create_converter(frame->yuv2rgb_factory); + if (!frame->yuv2rgb) { + vo_frame_dec_lock(vo_frame); + return -1; /* error happened */ + } + } + + /* configure yuv2rgb converter */ + if (width != frame->vo_width || + height != frame->vo_height || + frame->grab_frame.width != frame->grab_width || + frame->grab_frame.height != frame->grab_height || + y_stride != frame->y_stride || + uv_stride != frame->uv_stride) { + frame->vo_width = width; + frame->vo_height = height; + frame->grab_width = frame->grab_frame.width; + frame->grab_height = frame->grab_frame.height; + frame->y_stride = y_stride; + frame->uv_stride = uv_stride; + frame->yuv2rgb->configure(frame->yuv2rgb, width, height, y_stride, uv_stride, frame->grab_width, frame->grab_height, frame->grab_width * 3); + } + + /* convert YUV to RGB image taking possible scaling into account */ + /* FIXME: have to swap U and V planes to get correct colors for YV12 frames?? */ + if(format == XINE_IMGFMT_YV12) + frame->yuv2rgb->yuv2rgb_fun(frame->yuv2rgb, frame->grab_frame.img, base[0], base[2], base[1]); + else + frame->yuv2rgb->yuy22rgb_fun(frame->yuv2rgb, frame->grab_frame.img, base[0]); + + vo_frame_dec_lock(vo_frame); + return 0; +} + + +static xine_grab_video_frame_t *vo_new_grab_video_frame(xine_video_port_t *this_gen) +{ + vos_grab_video_frame_t *frame = calloc(1, sizeof(vos_grab_video_frame_t)); + if (frame) { + frame->grab_frame.dispose = vo_dispose_grab_video_frame; + frame->grab_frame.grab = vo_grab_grab_video_frame; + frame->grab_frame.vpts = -1; + frame->grab_frame.timeout = XINE_GRAB_VIDEO_FRAME_DEFAULT_TIMEOUT; + frame->video_port = this_gen; + } + return (xine_grab_video_frame_t *)frame; +} + + +static void vo_grab_current_frame (vos_t *this, vo_frame_t *vo_frame, int64_t vpts) +{ + pthread_mutex_lock(&this->grab_lock); + + /* hold current frame for snapshot feature */ + if (this->last_frame) + vo_frame_dec_lock(this->last_frame); + vo_frame_inc_lock(vo_frame); + this->last_frame = vo_frame; + + /* process grab queue */ + vos_grab_video_frame_t *frame = this->pending_grab_request; + if (frame) { + while (frame) { + if (frame->vo_frame) + vo_frame_dec_lock(frame->vo_frame); + frame->vo_frame = NULL; + + if (vo_frame->format == XINE_IMGFMT_YV12 || vo_frame->format == XINE_IMGFMT_YUY2 || vo_frame->proc_provide_standard_frame_data) { + vo_frame_inc_lock(vo_frame); + frame->vo_frame = vo_frame; + frame->grab_frame.vpts = vpts; + } + + frame->finished = 1; + vos_grab_video_frame_t *next = frame->next; + frame->next = NULL; + frame = next; + } + + this->pending_grab_request = NULL; + pthread_cond_broadcast(&this->grab_cond); + } + + pthread_mutex_unlock(&this->grab_lock); +} + + /* call vo_driver->proc methods for the entire frame */ static void vo_frame_driver_proc(vo_frame_t *img) { @@ -1038,16 +1342,7 @@ static void overlay_and_display_frame (vos_t *this, this->video_loop_running && this->overlay_enabled); } - /* hold current frame for snapshot feature */ - pthread_mutex_lock(&this->last_frame_mutex); - - if( this->last_frame ) { - vo_frame_dec_lock( this->last_frame ); - } - vo_frame_inc_lock( img ); - this->last_frame = img; - - pthread_mutex_unlock(&this->last_frame_mutex); + vo_grab_current_frame (this, img, vpts); this->driver->display_frame (this->driver, img); @@ -1329,12 +1624,13 @@ static void *video_out_loop (void *this_gen) { vo_frame_dec_lock( this->img_backup ); this->img_backup = NULL; } + + pthread_mutex_lock(&this->grab_lock); if (this->last_frame) { - pthread_mutex_lock(&this->last_frame_mutex); vo_frame_dec_lock( this->last_frame ); this->last_frame = NULL; - pthread_mutex_unlock(&this->last_frame_mutex); } + pthread_mutex_unlock(&this->grab_lock); return NULL; } @@ -1705,11 +2001,12 @@ static void vo_exit (xine_video_port_t *this_gen) { free (this->free_img_buf_queue); free (this->display_img_buf_queue); - pthread_mutex_destroy(&this->last_frame_mutex); - pthread_cond_destroy(&this->trigger_drawing_cond); pthread_mutex_destroy(&this->trigger_drawing_mutex); + pthread_mutex_destroy(&this->grab_lock); + pthread_cond_destroy(&this->grab_cond); + free (this); } @@ -1717,13 +2014,13 @@ static vo_frame_t *vo_get_last_frame (xine_video_port_t *this_gen) { vos_t *this = (vos_t *) this_gen; vo_frame_t *last_frame; - pthread_mutex_lock(&this->last_frame_mutex); + pthread_mutex_lock(&this->grab_lock); last_frame = this->last_frame; if (last_frame) vo_frame_inc_lock(last_frame); - pthread_mutex_unlock(&this->last_frame_mutex); + pthread_mutex_unlock(&this->grab_lock); return last_frame; } @@ -1887,6 +2184,7 @@ xine_video_port_t *_x_vo_new_port (xine_t *xine, vo_driver_t *driver, int grabon this->vo.open = vo_open; this->vo.get_frame = vo_get_frame; this->vo.get_last_frame = vo_get_last_frame; + this->vo.new_grab_video_frame = vo_new_grab_video_frame; this->vo.close = vo_close; this->vo.exit = vo_exit; this->vo.get_capabilities = vo_get_capabilities; @@ -1906,10 +2204,13 @@ xine_video_port_t *_x_vo_new_port (xine_t *xine, vo_driver_t *driver, int grabon this->display_img_buf_queue = vo_new_img_buf_queue (); this->video_loop_running = 0; - pthread_mutex_init(&this->last_frame_mutex, NULL); - this->last_frame = NULL; this->img_backup = NULL; + this->last_frame = NULL; + this->pending_grab_request = NULL; + pthread_mutex_init(&this->grab_lock, NULL); + pthread_cond_init(&this->grab_cond, NULL); + this->overlay_source = _x_video_overlay_new_manager(xine); this->overlay_source->init (this->overlay_source); this->overlay_enabled = 1; diff --git a/src/xine-engine/xine.c b/src/xine-engine/xine.c index 73cd9ae7e..6e5001f35 100644 --- a/src/xine-engine/xine.c +++ b/src/xine-engine/xine.c @@ -2219,6 +2219,17 @@ int xine_get_current_frame (xine_stream_t *stream, int *width, int *height, return result; } +xine_grab_video_frame_t* xine_new_grab_video_frame (xine_stream_t *stream) { + xine_grab_video_frame_t *frame; + + if (stream->video_out->driver->new_grab_video_frame) + frame = stream->video_out->driver->new_grab_video_frame(stream->video_out->driver); + else + frame = stream->video_out->new_grab_video_frame(stream->video_out); + + return frame; +} + int xine_get_spu_lang (xine_stream_t *stream, int channel, char *lang) { /* Ask the demuxer first (e.g. TS extracts this information from |