winks: performance improvements

* Make the driver write directly into each GstBuffer to avoid memcpy().
* Don't memset() the buffer before reusing it.
* Recycle memory by keeping two spare buffers. Two because the sink
  downstream may keep a ref to the previous buffer.

Note that we align buffers on highest possible byte boundary (4096) so we
don't have to take into account what kind of alignment the driver requires.
This commit is contained in:
Ole André Vadla Ravnås 2009-09-07 16:45:57 +02:00
parent f2b4d8990d
commit cddfa50d92
3 changed files with 225 additions and 111 deletions

View file

@ -26,15 +26,23 @@
#define READ_TIMEOUT (10 * 1000)
#define MJPEG_MAX_PADDING 128
#define MAX_OUTSTANDING_FRAMES 128
#define BUFFER_ALIGNMENT 512
#define KS_BUFFER_ALIGNMENT 4096
#define DEFAULT_DEVICE_PATH NULL
GST_DEBUG_CATEGORY_EXTERN (gst_ks_debug);
#define GST_CAT_DEFAULT gst_ks_debug
#define GST_DEBUG_IS_ENABLED() \
#define GST_DEBUG_IS_ENABLED() \
(gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_DEBUG)
#define UNREF_BUFFER(b) \
G_STMT_START { \
if (*(b) != NULL) { \
gst_buffer_unref (*(b)); \
*(b) = NULL; \
} \
} G_STMT_END
enum
{
@ -52,8 +60,7 @@ typedef struct
typedef struct
{
KSSTREAM_READ_PARAMS params;
guint8 *buf_unaligned;
guint8 *buf;
GstBuffer *buf;
OVERLAPPED overlapped;
} ReadRequest;
@ -84,8 +91,9 @@ struct _GstKsVideoDevicePrivate
gulong num_requests;
GArray *requests;
GArray *request_events;
GstBuffer *spare_buffers[2];
GstClockTime last_timestamp;
} GstKsVideoDevicePrivate;
};
#define GST_KS_VIDEO_DEVICE_GET_PRIVATE(o) ((o)->priv)
@ -96,6 +104,7 @@ static void gst_ks_video_device_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_ks_video_device_reset_caps (GstKsVideoDevice * self);
static guint gst_ks_video_device_get_frame_size (GstKsVideoDevice * self);
GST_BOILERPLATE (GstKsVideoDevice, gst_ks_video_device, GObject, G_TYPE_OBJECT);
@ -258,11 +267,16 @@ gst_ks_video_device_clear_buffers (GstKsVideoDevice * self)
}
/* Clean up */
for (i = 0; i < G_N_ELEMENTS (priv->spare_buffers); i++) {
gst_buffer_unref (priv->spare_buffers[i]);
priv->spare_buffers[i] = NULL;
}
for (i = 0; i < priv->requests->len; i++) {
ReadRequest *req = &g_array_index (priv->requests, ReadRequest, i);
HANDLE ev = g_array_index (priv->request_events, HANDLE, i);
g_free (req->buf_unaligned);
gst_buffer_unref (req->buf);
if (ev)
CloseHandle (ev);
@ -293,12 +307,16 @@ gst_ks_video_device_prepare_buffers (GstKsVideoDevice * self)
frame_size = gst_ks_video_device_get_frame_size (self);
for (i = 0; i < G_N_ELEMENTS (priv->spare_buffers); i++) {
priv->spare_buffers[i] = self->allocfunc (frame_size, KS_BUFFER_ALIGNMENT,
self->allocfunc_data);
}
for (i = 0; i < priv->num_requests; i++) {
ReadRequest req = { 0, };
req.buf_unaligned = g_malloc (frame_size + BUFFER_ALIGNMENT - 1);
req.buf = (guint8 *) (((gsize) req.buf_unaligned + BUFFER_ALIGNMENT - 1)
& ~(BUFFER_ALIGNMENT - 1));
req.buf = self->allocfunc (frame_size, KS_BUFFER_ALIGNMENT,
self->allocfunc_data);
req.overlapped.hEvent = CreateEvent (NULL, TRUE, FALSE, NULL);
@ -332,6 +350,20 @@ gst_ks_video_device_dump_supported_property_sets (GstKsVideoDevice * self,
}
}
GstKsVideoDevice *
gst_ks_video_device_new (const gchar * device_path, GstKsClock * clock,
GstKsAllocFunction allocfunc, gpointer allocfunc_data)
{
GstKsVideoDevice *device;
device = g_object_new (GST_TYPE_KS_VIDEO_DEVICE,
"device-path", device_path, "clock", clock, NULL);
device->allocfunc = allocfunc;
device->allocfunc_data = allocfunc_data;
return device;
}
gboolean
gst_ks_video_device_open (GstKsVideoDevice * self)
{
@ -556,8 +588,8 @@ gst_ks_video_device_create_pin (GstKsVideoDevice * self,
g_free (framing_ex);
/*
* TODO: We also need to respect alignment, but for now we just align
* on FILE_512_BYTE_ALIGNMENT.
* TODO: We also need to respect alignment, but for now we just assume
* that allocfunc provides the appropriate alignment...
*/
/* Set the memory transport to use. */
@ -818,7 +850,7 @@ gst_ks_video_device_set_state (GstKsVideoDevice * self, KSSTATE state)
return TRUE;
}
guint
static guint
gst_ks_video_device_get_frame_size (GstKsVideoDevice * self)
{
GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
@ -854,6 +886,41 @@ gst_ks_video_device_get_latency (GstKsVideoDevice * self,
return TRUE;
}
static gboolean
gst_ks_read_request_pick_buffer (GstKsVideoDevice * self, ReadRequest * req)
{
GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
gboolean buffer_found = FALSE;
guint i;
buffer_found = gst_buffer_is_writable (req->buf);
for (i = 0; !buffer_found && i < G_N_ELEMENTS (priv->spare_buffers); i++) {
if (gst_buffer_is_writable (priv->spare_buffers[i])) {
GstBuffer *hold;
hold = req->buf;
req->buf = priv->spare_buffers[i];
priv->spare_buffers[i] = hold;
buffer_found = TRUE;
}
}
if (!buffer_found) {
gst_buffer_unref (req->buf);
req->buf = self->allocfunc (gst_ks_video_device_get_frame_size (self),
KS_BUFFER_ALIGNMENT, self->allocfunc_data);
}
if (req->buf != NULL) {
GST_BUFFER_FLAGS (req->buf) = 0;
return TRUE;
} else {
return FALSE;
}
}
static gboolean
gst_ks_video_device_request_frame (GstKsVideoDevice * self, ReadRequest * req,
gulong * error_code, gchar ** error_str)
@ -864,6 +931,9 @@ gst_ks_video_device_request_frame (GstKsVideoDevice * self, ReadRequest * req,
BOOL success;
DWORD bytes_returned = 0;
if (!gst_ks_read_request_pick_buffer (self, req))
goto error_pick_buffer;
/* Reset the OVERLAPPED structure */
event = req->overlapped.hEvent;
memset (&req->overlapped, 0, sizeof (OVERLAPPED));
@ -877,18 +947,9 @@ gst_ks_video_device_request_frame (GstKsVideoDevice * self, ReadRequest * req,
params->header.PresentationTime.Numerator = 1;
params->header.PresentationTime.Denominator = 1;
params->header.FrameExtent = gst_ks_video_device_get_frame_size (self);
params->header.Data = req->buf;
params->header.Data = GST_BUFFER_DATA (req->buf);
params->frame_info.ExtendedHeaderSize = sizeof (KS_FRAME_INFO);
/*
* Clear the buffer like DirectShow does
*
* REVISIT: Could probably remove this later, for now it's here to help
* track down the case where we capture old frames. This has been
* observed with UVC cameras, presumably with some system load.
*/
memset (params->header.Data, 0, params->header.FrameExtent);
success = DeviceIoControl (priv->pin_handle, IOCTL_KS_READ_STREAM, NULL, 0,
params, params->header.Size, &bytes_returned, &req->overlapped);
if (!success && GetLastError () != ERROR_IO_PENDING)
@ -897,6 +958,14 @@ gst_ks_video_device_request_frame (GstKsVideoDevice * self, ReadRequest * req,
return TRUE;
/* ERRORS */
error_pick_buffer:
{
if (error_code != NULL)
*error_code = 0;
if (error_str != NULL)
*error_str = NULL;
return FALSE;
}
error_ioctl:
{
gst_ks_video_device_parse_win32_error ("DeviceIoControl", GetLastError (),
@ -905,10 +974,57 @@ error_ioctl:
}
}
static void
gst_ks_video_device_correct_or_drop_frame (GstKsVideoDevice * self,
GstBuffer ** frame_buf)
{
GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
gboolean valid = FALSE;
guint padding = 0;
guint8 *data;
guint data_size;
if (!priv->is_mjpeg)
return;
/*
* Workaround for cameras/drivers that intermittently provide us
* with incomplete or corrupted MJPEG frames.
*
* Happens with for instance Microsoft LifeCam VX-7000.
*/
data = GST_BUFFER_DATA (*frame_buf);
data_size = GST_BUFFER_SIZE (*frame_buf);
if (data_size > MJPEG_MAX_PADDING) {
/* JFIF SOI marker */
if (data[0] == 0xff && data[1] == 0xd8) {
guint8 *p = data + data_size - 2;
/* JFIF EOI marker (but skip any padding) */
while (padding < MJPEG_MAX_PADDING - 1 - 2 && !valid) {
if (p[0] == 0xff && p[1] == 0xd9) {
valid = TRUE;
} else {
padding++;
p--;
}
}
}
}
if (valid) {
GST_BUFFER_SIZE (*frame_buf) -= padding;
} else {
gst_buffer_unref (*frame_buf);
*frame_buf = NULL;
}
}
GstFlowReturn
gst_ks_video_device_read_frame (GstKsVideoDevice * self, guint8 * buf,
gulong buf_size, gulong * bytes_read, GstClockTime * presentation_time,
gulong * error_code, gchar ** error_str)
gst_ks_video_device_read_frame (GstKsVideoDevice * self, GstBuffer ** buf,
GstClockTime * presentation_time, gulong * error_code, gchar ** error_str)
{
GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self);
guint req_idx;
@ -930,6 +1046,8 @@ gst_ks_video_device_read_frame (GstKsVideoDevice * self, guint8 * buf,
}
}
*buf = NULL;
do {
/* Wait for either a request to complete, a cancel or a timeout */
wait_ret = WaitForMultipleObjects (priv->request_events->len,
@ -943,8 +1061,6 @@ gst_ks_video_device_read_frame (GstKsVideoDevice * self, guint8 * buf,
if (WaitForSingleObject (priv->cancel_event, 0) == WAIT_OBJECT_0)
goto error_cancel;
*bytes_read = 0;
/* Find the last ReadRequest that finished and get the result, immediately
* re-issuing each request that has completed. */
for (req_idx = wait_ret - WAIT_OBJECT_0;
@ -977,8 +1093,13 @@ gst_ks_video_device_read_frame (GstKsVideoDevice * self, guint8 * buf,
if (hdr->OptionsFlags & KSSTREAM_HEADER_OPTIONSF_DURATIONVALID)
duration = hdr->Duration * 100;
/* Assume it's a good frame */
*bytes_read = hdr->DataUsed;
UNREF_BUFFER (buf);
if (G_LIKELY (hdr->DataUsed != 0)) {
/* Assume it's a good frame */
GST_BUFFER_SIZE (req->buf) = hdr->DataUsed;
*buf = gst_buffer_ref (req->buf);
}
if (G_LIKELY (presentation_time != NULL))
*presentation_time = timestamp;
@ -1006,64 +1127,31 @@ gst_ks_video_device_read_frame (GstKsVideoDevice * self, guint8 * buf,
", timestamp=%" GST_TIME_FORMAT ")",
GST_TIME_ARGS (priv->last_timestamp),
GST_TIME_ARGS (timestamp));
*bytes_read = 0;
UNREF_BUFFER (buf);
} else {
priv->last_timestamp = timestamp;
}
}
if (*bytes_read > 0) {
/* Grab the frame data */
g_assert (buf_size >= hdr->DataUsed);
memcpy (buf, req->buf, hdr->DataUsed);
if (priv->is_mjpeg) {
/*
* Workaround for cameras/drivers that intermittently provide us
* with incomplete or corrupted MJPEG frames.
*
* Happens with for instance Microsoft LifeCam VX-7000.
*/
gboolean valid = FALSE;
guint padding = 0;
/* JFIF SOI marker */
if (*bytes_read > MJPEG_MAX_PADDING
&& buf[0] == 0xff && buf[1] == 0xd8) {
guint8 *p = buf + *bytes_read - 2;
/* JFIF EOI marker (but skip any padding) */
while (padding < MJPEG_MAX_PADDING - 1 - 2 && !valid) {
if (p[0] == 0xff && p[1] == 0xd9) {
valid = TRUE;
} else {
padding++;
p--;
}
}
}
if (valid)
*bytes_read -= padding;
else
*bytes_read = 0;
}
}
} else if (GetLastError () != ERROR_OPERATION_ABORTED)
if (*buf != NULL)
gst_ks_video_device_correct_or_drop_frame (self, buf);
} else if (GetLastError () != ERROR_OPERATION_ABORTED) {
goto error_get_result;
}
/* Submit a new request immediately */
if (!gst_ks_video_device_request_frame (self, req, error_code, error_str))
goto error_request_failed;
}
} while (*bytes_read == 0);
} while (*buf == NULL);
return GST_FLOW_OK;
/* ERRORS */
error_request_failed:
{
UNREF_BUFFER (buf);
return GST_FLOW_ERROR;
}
error_timeout:

View file

@ -20,6 +20,8 @@
#ifndef __GST_KS_VIDEO_DEVICE_H__
#define __GST_KS_VIDEO_DEVICE_H__
#include "gstksclock.h"
#include <gst/gst.h>
#include <windows.h>
@ -42,10 +44,15 @@ typedef struct _GstKsVideoDevice GstKsVideoDevice;
typedef struct _GstKsVideoDeviceClass GstKsVideoDeviceClass;
typedef struct _GstKsVideoDevicePrivate GstKsVideoDevicePrivate;
typedef GstBuffer * (* GstKsAllocFunction)(guint buf_size, guint alignment, gpointer user_data);
struct _GstKsVideoDevice
{
GObject parent;
GstKsAllocFunction allocfunc;
gpointer allocfunc_data;
GstKsVideoDevicePrivate *priv;
};
@ -56,6 +63,7 @@ struct _GstKsVideoDeviceClass
GType gst_ks_video_device_get_type (void);
GstKsVideoDevice * gst_ks_video_device_new (const gchar * device_path, GstKsClock * clock, GstKsAllocFunction allocfunc, gpointer allocfunc_data);
gboolean gst_ks_video_device_open (GstKsVideoDevice * self);
void gst_ks_video_device_close (GstKsVideoDevice * self);
@ -65,11 +73,10 @@ gboolean gst_ks_video_device_set_caps (GstKsVideoDevice * self, GstCaps * caps);
gboolean gst_ks_video_device_set_state (GstKsVideoDevice * self, KSSTATE state);
guint gst_ks_video_device_get_frame_size (GstKsVideoDevice * self);
GstClockTime gst_ks_video_device_get_duration (GstKsVideoDevice * self);
gboolean gst_ks_video_device_get_latency (GstKsVideoDevice * self, GstClockTime * min_latency, GstClockTime * max_latency);
GstFlowReturn gst_ks_video_device_read_frame (GstKsVideoDevice * self, guint8 * buf, gulong buf_size, gulong * bytes_read, GstClockTime * presentation_time, gulong * error_code, gchar ** error_str);
GstFlowReturn gst_ks_video_device_read_frame (GstKsVideoDevice * self, GstBuffer ** buf, GstClockTime * presentation_time, gulong * error_code, gchar ** error_str);
void gst_ks_video_device_postprocess_frame (GstKsVideoDevice * self, guint8 * buf, guint buf_size);
void gst_ks_video_device_cancel (GstKsVideoDevice * self);
void gst_ks_video_device_cancel_stop (GstKsVideoDevice * self);

View file

@ -155,6 +155,8 @@ static gboolean gst_ks_video_src_unlock_stop (GstBaseSrc * basesrc);
static GstFlowReturn gst_ks_video_src_create (GstPushSrc * pushsrc,
GstBuffer ** buffer);
static GstBuffer *gst_ks_video_src_alloc_buffer (guint size, guint alignment,
gpointer user_data);
GST_BOILERPLATE_FULL (GstKsVideoSrc, gst_ks_video_src, GstPushSrc,
GST_TYPE_PUSH_SRC, gst_ks_video_src_init_interfaces);
@ -524,8 +526,8 @@ gst_ks_video_src_open_device (GstKsVideoSrc * self)
priv->ksclock = NULL;
}
device = g_object_new (GST_TYPE_KS_VIDEO_DEVICE,
"clock", priv->ksclock, "device-path", entry->path, NULL);
device = gst_ks_video_device_new (entry->path, priv->ksclock,
gst_ks_video_src_alloc_buffer, self);
}
ks_device_entry_free (entry);
@ -991,13 +993,10 @@ gst_ks_video_src_update_statistics (GstKsVideoSrc * self)
}
static GstFlowReturn
gst_ks_video_src_create (GstPushSrc * pushsrc, GstBuffer ** buffer)
gst_ks_video_src_create (GstPushSrc * pushsrc, GstBuffer ** buf)
{
GstKsVideoSrc *self = GST_KS_VIDEO_SRC (pushsrc);
GstKsVideoSrcPrivate *priv = GST_KS_VIDEO_SRC_GET_PRIVATE (self);
guint buf_size;
GstCaps *caps;
GstBuffer *buf = NULL;
GstFlowReturn result;
GstClockTime presentation_time;
gulong error_code;
@ -1008,18 +1007,6 @@ gst_ks_video_src_create (GstPushSrc * pushsrc, GstBuffer ** buffer)
if (!gst_ks_video_device_has_caps (priv->device))
goto error_no_caps;
buf_size = gst_ks_video_device_get_frame_size (priv->device);
g_assert (buf_size);
caps = gst_pad_get_negotiated_caps (GST_BASE_SRC_PAD (self));
if (caps == NULL)
goto error_no_caps;
result = gst_pad_alloc_buffer (GST_BASE_SRC_PAD (self), priv->offset,
buf_size, caps, &buf);
gst_caps_unref (caps);
if (G_UNLIKELY (result != GST_FLOW_OK))
goto error_alloc_buffer;
if (G_UNLIKELY (!priv->running)) {
KS_WORKER_LOCK (priv);
priv->worker_pending_run = TRUE;
@ -1034,25 +1021,24 @@ gst_ks_video_src_create (GstPushSrc * pushsrc, GstBuffer ** buffer)
}
do {
gulong bytes_read;
if (*buf != NULL) {
gst_buffer_unref (*buf);
*buf = NULL;
}
result = gst_ks_video_device_read_frame (priv->device,
GST_BUFFER_DATA (buf), buf_size, &bytes_read, &presentation_time,
&error_code, &error_str);
result = gst_ks_video_device_read_frame (priv->device, buf,
&presentation_time, &error_code, &error_str);
if (G_UNLIKELY (result != GST_FLOW_OK))
goto error_read_frame;
GST_BUFFER_SIZE (buf) = bytes_read;
}
while (!gst_ks_video_src_timestamp_buffer (self, buf, presentation_time));
while (!gst_ks_video_src_timestamp_buffer (self, *buf, presentation_time));
if (G_UNLIKELY (priv->do_stats))
gst_ks_video_src_update_statistics (self);
gst_ks_video_device_postprocess_frame (priv->device,
GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
GST_BUFFER_DATA (*buf), GST_BUFFER_SIZE (*buf));
*buffer = buf;
return GST_FLOW_OK;
/* ERRORS */
@ -1071,31 +1057,64 @@ error_start_capture:
return GST_FLOW_ERROR;
}
error_alloc_buffer:
{
GST_ELEMENT_ERROR (self, CORE, PAD, ("alloc_buffer failed"), (NULL));
return result;
}
error_read_frame:
{
if (result == GST_FLOW_ERROR) {
GST_ELEMENT_ERROR (self, RESOURCE, READ,
("read failed: %s [0x%08x]", error_str, error_code),
("gst_ks_video_device_read_frame failed"));
}
else if (result == GST_FLOW_UNEXPECTED) {
if (error_str != NULL) {
GST_ELEMENT_ERROR (self, RESOURCE, READ,
("read failed: %s [0x%08x]", error_str, error_code),
("gst_ks_video_device_read_frame failed"));
}
} else if (result == GST_FLOW_UNEXPECTED) {
GST_ELEMENT_ERROR (self, RESOURCE, READ,
("read failed"), ("gst_ks_video_device_read_frame failed"));
}
g_free (error_str);
gst_buffer_unref (buf);
return result;
}
}
static GstBuffer *
gst_ks_video_src_alloc_buffer (guint size, guint alignment, gpointer user_data)
{
GstKsVideoSrc *self = GST_KS_VIDEO_SRC (user_data);
GstBuffer *buf;
GstCaps *caps;
GstFlowReturn flow_ret;
caps = gst_pad_get_negotiated_caps (GST_BASE_SRC_PAD (self));
if (caps == NULL)
goto error_no_caps;
flow_ret = gst_pad_alloc_buffer (GST_BASE_SRC_PAD (self), 0,
size + (alignment - 1), caps, &buf);
gst_caps_unref (caps);
if (G_UNLIKELY (flow_ret != GST_FLOW_OK))
goto error_alloc_buffer;
GST_BUFFER_DATA (buf) =
GSIZE_TO_POINTER ((GPOINTER_TO_SIZE (GST_BUFFER_DATA (buf)) + (alignment -
1)) & ~(alignment - 1));
GST_BUFFER_SIZE (buf) = size;
return buf;
error_no_caps:
{
GST_ELEMENT_ERROR (self, CORE, NEGOTIATION,
("not negotiated"), ("maybe setcaps failed?"));
return NULL;
}
error_alloc_buffer:
{
GST_ELEMENT_ERROR (self, CORE, PAD, ("alloc_buffer failed"), (NULL));
return NULL;
}
}
static gboolean
plugin_init (GstPlugin * plugin)
{