summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/xine.h77
-rw-r--r--include/xine/video_out.h6
-rw-r--r--src/video_out/video_out_vdpau.c238
-rw-r--r--src/xine-engine/Makefile.am11
-rw-r--r--src/xine-engine/post.c11
-rw-r--r--src/xine-engine/video_out.c341
-rw-r--r--src/xine-engine/xine.c11
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, &current_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