/* * Copyright (C) 2008 Ole André Vadla Ravnås * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "gstksvideodevice.h" #include "gstksclock.h" #include "kshelpers.h" #include "ksvideohelpers.h" #define READ_TIMEOUT (10 * 1000) #define MJPEG_MAX_PADDING 128 #define MAX_OUTSTANDING_FRAMES 128 #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() \ (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 { PROP_0, PROP_CLOCK, PROP_DEVICE_PATH, }; typedef struct { KSSTREAM_HEADER header; KS_FRAME_INFO frame_info; } KSSTREAM_READ_PARAMS; typedef struct { KSSTREAM_READ_PARAMS params; GstBuffer *buf; OVERLAPPED overlapped; } ReadRequest; struct _GstKsVideoDevicePrivate { gboolean open; KSSTATE state; GstKsClock *clock; gchar *dev_path; HANDLE filter_handle; GList *media_types; GstCaps *cached_caps; HANDLE cancel_event; KsVideoMediaType *cur_media_type; GstCaps *cur_fixed_caps; guint width; guint height; guint fps_n; guint fps_d; guint8 *rgb_swap_buf; gboolean is_muxed; HANDLE pin_handle; gboolean requests_submitted; gulong num_requests; GArray *requests; GArray *request_events; GstBuffer *spare_buffers[2]; GstClockTime last_timestamp; }; #define GST_KS_VIDEO_DEVICE_GET_PRIVATE(o) ((o)->priv) static void gst_ks_video_device_dispose (GObject * object); static void gst_ks_video_device_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); 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); G_DEFINE_TYPE (GstKsVideoDevice, gst_ks_video_device, G_TYPE_OBJECT); static GstKsVideoDeviceClass *parent_class = NULL; static void gst_ks_video_device_class_init (GstKsVideoDeviceClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); parent_class = g_type_class_peek_parent (klass); g_type_class_add_private (klass, sizeof (GstKsVideoDevicePrivate)); gobject_class->dispose = gst_ks_video_device_dispose; gobject_class->get_property = gst_ks_video_device_get_property; gobject_class->set_property = gst_ks_video_device_set_property; g_object_class_install_property (gobject_class, PROP_CLOCK, g_param_spec_object ("clock", "Clock to use", "Clock to use", GST_TYPE_KS_CLOCK, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_DEVICE_PATH, g_param_spec_string ("device-path", "Device Path", "The device path", DEFAULT_DEVICE_PATH, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); } static void gst_ks_video_device_init (GstKsVideoDevice * self) { GstKsVideoDevicePrivate *priv; self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GST_TYPE_KS_VIDEO_DEVICE, GstKsVideoDevicePrivate); priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); priv->open = FALSE; priv->state = KSSTATE_STOP; } static void gst_ks_video_device_dispose (GObject * object) { GstKsVideoDevice *self = GST_KS_VIDEO_DEVICE (object); GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); gst_ks_video_device_reset_caps (self); gst_ks_video_device_close (self); if (priv->clock != NULL) { g_object_unref (priv->clock); priv->clock = NULL; } G_OBJECT_CLASS (parent_class)->dispose (object); } static void gst_ks_video_device_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstKsVideoDevice *self = GST_KS_VIDEO_DEVICE (object); GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); switch (prop_id) { case PROP_CLOCK: g_value_set_object (value, priv->clock); break; case PROP_DEVICE_PATH: g_value_set_string (value, priv->dev_path); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_ks_video_device_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstKsVideoDevice *self = GST_KS_VIDEO_DEVICE (object); GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); switch (prop_id) { case PROP_CLOCK: if (priv->clock != NULL) g_object_unref (priv->clock); priv->clock = g_value_dup_object (value); break; case PROP_DEVICE_PATH: g_free (priv->dev_path); priv->dev_path = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_ks_video_device_parse_win32_error (const gchar * func_name, DWORD error_code, gulong * ret_error_code, gchar ** ret_error_str) { if (ret_error_code != NULL) *ret_error_code = error_code; if (ret_error_str != NULL) { GString *message; gchar buf[1480]; DWORD result; message = g_string_sized_new (1600); g_string_append_printf (message, "%s returned ", func_name); result = FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, error_code, 0, buf, sizeof (buf), NULL); if (result != 0) { g_string_append_printf (message, "0x%08x: %s", (guint) error_code, g_strchomp (buf)); } else { DWORD format_error_code = GetLastError (); g_string_append_printf (message, "<0x%08x (FormatMessage error code: %s)>", (guint) error_code, (format_error_code == ERROR_MR_MID_NOT_FOUND) ? "no system error message found" : "failed to retrieve system error message"); } *ret_error_str = message->str; g_string_free (message, FALSE); } } static void gst_ks_video_device_clear_buffers (GstKsVideoDevice * self) { GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); guint i; if (priv->requests == NULL) return; /* Join any pending requests */ for (i = 0; i < priv->num_requests; i++) { ReadRequest *req = &g_array_index (priv->requests, ReadRequest, i); HANDLE ev = g_array_index (priv->request_events, HANDLE, i); DWORD n; if (!GetOverlappedResult (priv->pin_handle, &req->overlapped, &n, FALSE)) { if (WaitForSingleObject (ev, 1000) == WAIT_OBJECT_0) GetOverlappedResult (priv->pin_handle, &req->overlapped, &n, FALSE); } } /* 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); gst_buffer_unref (req->buf); if (ev) CloseHandle (ev); } g_array_free (priv->requests, TRUE); priv->requests = NULL; g_array_free (priv->request_events, TRUE); priv->request_events = NULL; } static void gst_ks_video_device_prepare_buffers (GstKsVideoDevice * self) { GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); guint i; guint frame_size; g_assert (priv->cur_media_type != NULL); gst_ks_video_device_clear_buffers (self); priv->requests = g_array_sized_new (FALSE, TRUE, sizeof (ReadRequest), priv->num_requests); priv->request_events = g_array_sized_new (FALSE, TRUE, sizeof (HANDLE), priv->num_requests + 1); 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; memset (&req, '0', sizeof (ReadRequest)); req.buf = self->allocfunc (frame_size, KS_BUFFER_ALIGNMENT, self->allocfunc_data); req.overlapped.hEvent = CreateEvent (NULL, TRUE, FALSE, NULL); g_array_append_val (priv->requests, req); g_array_append_val (priv->request_events, req.overlapped.hEvent); } g_array_append_val (priv->request_events, priv->cancel_event); /* * 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. */ priv->last_timestamp = GST_CLOCK_TIME_NONE; } static void gst_ks_video_device_dump_supported_property_sets (GstKsVideoDevice * self, const gchar * obj_name, const GUID * propsets, gulong propsets_len) { guint i; GST_DEBUG ("%s supports %lu property set%s", obj_name, propsets_len, (propsets_len != 1) ? "s" : ""); for (i = 0; i < propsets_len; i++) { gchar *propset_name = ks_property_set_to_string (&propsets[i]); GST_DEBUG ("[%d] %s", i, propset_name); g_free (propset_name); } } 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) { GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); GUID *propsets = NULL; gulong propsets_len; GList *cur; g_assert (!priv->open); g_assert (priv->dev_path != NULL); /* * Open the filter. */ priv->filter_handle = CreateFile (priv->dev_path, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); if (!ks_is_valid_handle (priv->filter_handle)) goto error; /* * Query the filter for supported property sets. */ if (ks_object_get_supported_property_sets (priv->filter_handle, &propsets, &propsets_len)) { gst_ks_video_device_dump_supported_property_sets (self, "filter", propsets, propsets_len); g_free (propsets); } else { GST_DEBUG ("failed to query filter for supported property sets"); } /* * Probe for supported media types. */ priv->media_types = ks_video_probe_filter_for_caps (priv->filter_handle); priv->cached_caps = gst_caps_new_empty (); for (cur = priv->media_types; cur != NULL; cur = cur->next) { KsVideoMediaType *media_type = cur->data; gst_caps_append (priv->cached_caps, gst_caps_copy (media_type->translated_caps)); #if 1 { gchar *str; str = gst_caps_to_string (media_type->translated_caps); GST_DEBUG ("pin[%d]: found media type: %s", media_type->pin_id, str); g_free (str); } #endif } priv->cancel_event = CreateEvent (NULL, TRUE, FALSE, NULL); priv->open = TRUE; return TRUE; error: g_free (priv->dev_path); priv->dev_path = NULL; return FALSE; } void gst_ks_video_device_close (GstKsVideoDevice * self) { GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); GList *cur; gst_ks_video_device_reset_caps (self); g_free (priv->dev_path); priv->dev_path = NULL; if (ks_is_valid_handle (priv->filter_handle)) { CloseHandle (priv->filter_handle); priv->filter_handle = INVALID_HANDLE_VALUE; } for (cur = priv->media_types; cur != NULL; cur = cur->next) { KsVideoMediaType *mt = cur->data; ks_video_media_type_free (mt); } if (priv->media_types != NULL) { g_list_free (priv->media_types); priv->media_types = NULL; } if (priv->cached_caps != NULL) { gst_caps_unref (priv->cached_caps); priv->cached_caps = NULL; } if (ks_is_valid_handle (priv->cancel_event)) CloseHandle (priv->cancel_event); priv->cancel_event = INVALID_HANDLE_VALUE; priv->open = FALSE; } GstCaps * gst_ks_video_device_get_available_caps (GstKsVideoDevice * self) { GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); g_assert (priv->open); return gst_caps_ref (priv->cached_caps); } gboolean gst_ks_video_device_has_caps (GstKsVideoDevice * self) { GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); return (priv->cur_media_type != NULL) ? TRUE : FALSE; } static HANDLE gst_ks_video_device_create_pin (GstKsVideoDevice * self, KsVideoMediaType * media_type, gulong * num_outstanding) { GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); HANDLE pin_handle = INVALID_HANDLE_VALUE; KSPIN_CONNECT *pin_conn = NULL; DWORD ret; guint retry_count; GUID *propsets = NULL; gulong propsets_len; gboolean supports_mem_transport = FALSE; KSALLOCATOR_FRAMING *framing = NULL; gulong framing_size = sizeof (KSALLOCATOR_FRAMING); KSALLOCATOR_FRAMING_EX *framing_ex = NULL; gulong alignment; DWORD mem_transport; /* * Instantiate the pin. */ pin_conn = ks_video_create_pin_conn_from_media_type (media_type); for (retry_count = 0; retry_count != 5; retry_count++) { GST_DEBUG ("calling KsCreatePin with pin_id = %d", media_type->pin_id); ret = KsCreatePin (priv->filter_handle, pin_conn, GENERIC_READ, &pin_handle); if (ret != ERROR_NOT_READY) break; /* wait and retry, like the reference implementation does */ if (WaitForSingleObject (priv->cancel_event, 1000) == WAIT_OBJECT_0) goto cancelled; } if (ret != ERROR_SUCCESS) goto error_create_pin; GST_DEBUG ("KsCreatePin succeeded, pin %p created", pin_handle); g_free (pin_conn); pin_conn = NULL; /* * Query the pin for supported property sets. */ if (ks_object_get_supported_property_sets (pin_handle, &propsets, &propsets_len)) { guint i; gst_ks_video_device_dump_supported_property_sets (self, "pin", propsets, propsets_len); for (i = 0; i < propsets_len; i++) { if (IsEqualGUID (&propsets[i], &KSPROPSETID_MemoryTransport)) supports_mem_transport = TRUE; } g_free (propsets); } else { GST_DEBUG ("failed to query pin for supported property sets"); } /* * Figure out how many simultanous requests it prefers. * * This is really important as it depends on the driver and the device. * Doing too few will result in poor capture performance, whilst doing too * many will make some drivers crash really horribly and leave you with a * BSOD. I've experienced the latter with older Logitech drivers. */ *num_outstanding = 0; alignment = 0; if (ks_object_get_property (pin_handle, KSPROPSETID_Connection, KSPROPERTY_CONNECTION_ALLOCATORFRAMING_EX, (void *) &framing_ex, NULL, NULL)) { if (framing_ex->CountItems >= 1) { *num_outstanding = framing_ex->FramingItem[0].Frames; alignment = framing_ex->FramingItem[0].FileAlignment; } else { GST_DEBUG ("ignoring empty ALLOCATORFRAMING_EX"); } } else { GST_DEBUG ("query for ALLOCATORFRAMING_EX failed, trying " "ALLOCATORFRAMING"); if (ks_object_get_property (pin_handle, KSPROPSETID_Connection, KSPROPERTY_CONNECTION_ALLOCATORFRAMING, (void *) &framing, &framing_size, NULL)) { *num_outstanding = framing->Frames; alignment = framing->FileAlignment; } else { GST_DEBUG ("query for ALLOCATORFRAMING failed"); } } GST_DEBUG ("num_outstanding: %lu alignment: 0x%08x", *num_outstanding, (guint) alignment); if (*num_outstanding == 0 || *num_outstanding > MAX_OUTSTANDING_FRAMES) { GST_DEBUG ("setting number of allowable outstanding frames to 1"); *num_outstanding = 1; } g_free (framing); framing = NULL; g_free (framing_ex); framing_ex = NULL; /* * 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. */ if (supports_mem_transport) { mem_transport = 0; /* REVISIT: use the constant here */ if (!ks_object_set_property (pin_handle, KSPROPSETID_MemoryTransport, KSPROPERTY_MEMORY_TRANSPORT, &mem_transport, sizeof (mem_transport), NULL)) { GST_DEBUG ("failed to set memory transport, sticking with the default"); } } /* * Override the clock if we have one and the pin doesn't have any either. */ if (priv->clock != NULL) { HANDLE *cur_clock_handle = NULL; gulong cur_clock_handle_size = sizeof (HANDLE); if (ks_object_get_property (pin_handle, KSPROPSETID_Stream, KSPROPERTY_STREAM_MASTERCLOCK, (gpointer *) & cur_clock_handle, &cur_clock_handle_size, NULL)) { GST_DEBUG ("current master clock handle: %p", *cur_clock_handle); CloseHandle (*cur_clock_handle); g_free (cur_clock_handle); } else { HANDLE new_clock_handle = gst_ks_clock_get_handle (priv->clock); if (ks_object_set_property (pin_handle, KSPROPSETID_Stream, KSPROPERTY_STREAM_MASTERCLOCK, &new_clock_handle, sizeof (new_clock_handle), NULL)) { gst_ks_clock_prepare (priv->clock); } else { GST_WARNING ("failed to set pin's master clock"); } } } return pin_handle; /* ERRORS */ error_create_pin: { gchar *str; gst_ks_video_device_parse_win32_error ("KsCreatePin", ret, NULL, &str); GST_ERROR ("%s", str); g_free (str); goto beach; } cancelled: beach: { g_free (framing); g_free (framing_ex); if (ks_is_valid_handle (pin_handle)) CloseHandle (pin_handle); g_free (pin_conn); return INVALID_HANDLE_VALUE; } } static void gst_ks_video_device_close_current_pin (GstKsVideoDevice * self) { GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); if (!ks_is_valid_handle (priv->pin_handle)) return; gst_ks_video_device_set_state (self, KSSTATE_STOP, NULL); CloseHandle (priv->pin_handle); priv->pin_handle = INVALID_HANDLE_VALUE; } static void gst_ks_video_device_reset_caps (GstKsVideoDevice * self) { GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); gst_ks_video_device_close_current_pin (self); ks_video_media_type_free (priv->cur_media_type); priv->cur_media_type = NULL; priv->width = priv->height = priv->fps_n = priv->fps_d = 0; g_free (priv->rgb_swap_buf); priv->rgb_swap_buf = NULL; if (priv->cur_fixed_caps != NULL) { gst_caps_unref (priv->cur_fixed_caps); priv->cur_fixed_caps = NULL; } } gboolean gst_ks_video_device_set_caps (GstKsVideoDevice * self, GstCaps * caps) { GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); GList *cur; GstStructure *s; /* State to be committed on success */ KsVideoMediaType *media_type = NULL; gint width, height, fps_n, fps_d; HANDLE pin_handle = INVALID_HANDLE_VALUE; /* Reset? */ if (caps == NULL) { gst_ks_video_device_reset_caps (self); return TRUE; } /* Validate the caps */ if (!gst_caps_is_subset (caps, priv->cached_caps)) { gchar *string_caps = gst_caps_to_string (caps); gchar *string_c_caps = gst_caps_to_string (priv->cached_caps); GST_ERROR ("caps (%s) is not a subset of device caps (%s)", string_caps, string_c_caps); g_free (string_caps); g_free (string_c_caps); goto error; } for (cur = priv->media_types; cur != NULL; cur = cur->next) { KsVideoMediaType *mt = cur->data; if (gst_caps_is_subset (caps, mt->translated_caps)) { media_type = ks_video_media_type_dup (mt); break; } } if (media_type == NULL) goto error; s = gst_caps_get_structure (caps, 0); if (!gst_structure_get_int (s, "width", &width) || !gst_structure_get_int (s, "height", &height) || !gst_structure_get_fraction (s, "framerate", &fps_n, &fps_d)) { gst_structure_get_boolean (s, "systemstream", &priv->is_muxed); if (!priv->is_muxed) { GST_ERROR ("Failed to get width/height/fps"); goto error; } } else { if (!ks_video_fixate_media_type (media_type->range, media_type->format, width, height, fps_n, fps_d)) goto error; } if (priv->cur_media_type != NULL) { if (media_type->format_size == priv->cur_media_type->format_size && memcmp (media_type->format, priv->cur_media_type->format, priv->cur_media_type->format_size) == 0) { GST_DEBUG ("%s: re-using existing pin", G_STRFUNC); goto same_caps; } else { GST_DEBUG ("%s: re-creating pin", G_STRFUNC); } } gst_ks_video_device_close_current_pin (self); pin_handle = gst_ks_video_device_create_pin (self, media_type, &priv->num_requests); if (!ks_is_valid_handle (pin_handle)) { /* Re-create the old pin */ if (priv->cur_media_type != NULL) priv->pin_handle = gst_ks_video_device_create_pin (self, priv->cur_media_type, &priv->num_requests); goto error; } /* Commit state: no turning back past this */ gst_ks_video_device_reset_caps (self); priv->cur_media_type = media_type; priv->width = width; priv->height = height; priv->fps_n = fps_n; priv->fps_d = fps_d; if (gst_structure_has_name (s, "video/x-raw-rgb")) priv->rgb_swap_buf = g_malloc (media_type->sample_size / priv->height); else priv->rgb_swap_buf = NULL; priv->pin_handle = pin_handle; priv->cur_fixed_caps = gst_caps_copy (caps); return TRUE; error: { ks_video_media_type_free (media_type); return FALSE; } same_caps: { ks_video_media_type_free (media_type); return TRUE; } } gboolean gst_ks_video_device_set_state (GstKsVideoDevice * self, KSSTATE state, gulong * error_code) { GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); KSSTATE initial_state; gint addend; g_assert (priv->cur_media_type != NULL); if (state == priv->state) return TRUE; initial_state = priv->state; addend = (state > priv->state) ? 1 : -1; GST_DEBUG ("Initiating pin state change from %s to %s", ks_state_to_string (priv->state), ks_state_to_string (state)); while (priv->state != state) { KSSTATE next_state = priv->state + addend; /* Skip the ACQUIRE step on the way down like DirectShow does */ if (addend < 0 && next_state == KSSTATE_ACQUIRE) next_state = KSSTATE_STOP; GST_DEBUG ("Changing pin state from %s to %s", ks_state_to_string (priv->state), ks_state_to_string (next_state)); if (ks_object_set_connection_state (priv->pin_handle, next_state, error_code)) { priv->state = next_state; GST_DEBUG ("Changed pin state to %s", ks_state_to_string (priv->state)); if (priv->state == KSSTATE_PAUSE && addend > 0) gst_ks_video_device_prepare_buffers (self); else if (priv->state == KSSTATE_STOP && addend < 0) gst_ks_video_device_clear_buffers (self); } else { GST_WARNING ("Failed to change pin state to %s", ks_state_to_string (next_state)); return FALSE; } } GST_DEBUG ("Finished pin state change from %s to %s", ks_state_to_string (initial_state), ks_state_to_string (state)); return TRUE; } static guint gst_ks_video_device_get_frame_size (GstKsVideoDevice * self) { GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); g_assert (priv->cur_media_type != NULL); return priv->cur_media_type->sample_size; } GstClockTime gst_ks_video_device_get_duration (GstKsVideoDevice * self) { GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); g_assert (priv->cur_media_type != NULL); return gst_util_uint64_scale_int (GST_SECOND, priv->fps_d, priv->fps_n); } gboolean gst_ks_video_device_get_latency (GstKsVideoDevice * self, GstClockTime * min_latency, GstClockTime * max_latency) { GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); if (priv->cur_media_type == NULL) return FALSE; *min_latency = gst_util_uint64_scale_int (GST_SECOND, priv->fps_d, priv->fps_n); *max_latency = *min_latency; 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) && gst_buffer_is_all_memory_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]) && gst_buffer_is_all_memory_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) { GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); HANDLE event; KSSTREAM_READ_PARAMS *params; BOOL success; DWORD bytes_returned = 0; GstMapInfo info; 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)); req->overlapped.hEvent = event; /* Fill out KSSTREAM_HEADER and KS_FRAME_INFO */ params = &req->params; memset (params, 0, sizeof (KSSTREAM_READ_PARAMS)); if (!gst_buffer_map (req->buf, &info, GST_MAP_WRITE)) goto map_failed; params->header.Size = sizeof (KSSTREAM_HEADER); if (!priv->is_muxed) { params->header.Size += sizeof (KS_FRAME_INFO); } params->header.PresentationTime.Numerator = 1; params->header.PresentationTime.Denominator = 1; params->header.FrameExtent = gst_ks_video_device_get_frame_size (self); params->header.Data = info.data; params->frame_info.ExtendedHeaderSize = sizeof (KS_FRAME_INFO); success = DeviceIoControl (priv->pin_handle, IOCTL_KS_READ_STREAM, NULL, 0, params, params->header.Size, &bytes_returned, &req->overlapped); gst_buffer_unmap (req->buf, &info); if (!success && GetLastError () != ERROR_IO_PENDING) goto error_ioctl; 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 (), error_code, error_str); return FALSE; } map_failed: { return FALSE; } } GstFlowReturn 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; DWORD wait_ret; BOOL success; DWORD bytes_returned; g_assert (priv->cur_media_type != NULL); /* First time we're called, submit the requests. */ if (G_UNLIKELY (!priv->requests_submitted)) { priv->requests_submitted = TRUE; for (req_idx = 0; req_idx < priv->num_requests; req_idx++) { ReadRequest *req = &g_array_index (priv->requests, ReadRequest, req_idx); if (!gst_ks_video_device_request_frame (self, req, error_code, error_str)) goto error_request_failed; } } *buf = NULL; do { /* Wait for either a request to complete, a cancel or a timeout */ wait_ret = WaitForMultipleObjects (priv->request_events->len, (HANDLE *) priv->request_events->data, FALSE, READ_TIMEOUT); if (wait_ret == WAIT_TIMEOUT) goto error_timeout; else if (wait_ret == WAIT_FAILED) goto error_wait; /* Stopped? */ if (WaitForSingleObject (priv->cancel_event, 0) == WAIT_OBJECT_0) goto error_cancel; /* 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; req_idx < priv->num_requests; req_idx++) { ReadRequest *req = &g_array_index (priv->requests, ReadRequest, req_idx); /* * Completed? WaitForMultipleObjects() returns the lowest index if * multiple objects are in the signaled state, and we know that requests * are processed one by one so there's no point in looking further once * we've found the first that's non-signaled. */ if (WaitForSingleObject (req->overlapped.hEvent, 0) != WAIT_OBJECT_0) break; success = GetOverlappedResult (priv->pin_handle, &req->overlapped, &bytes_returned, TRUE); ResetEvent (req->overlapped.hEvent); if (success) { KSSTREAM_HEADER *hdr = &req->params.header; KS_FRAME_INFO *frame_info = &req->params.frame_info; GstClockTime timestamp = GST_CLOCK_TIME_NONE; GstClockTime duration = GST_CLOCK_TIME_NONE; if (hdr->OptionsFlags & KSSTREAM_HEADER_OPTIONSF_TIMEVALID) timestamp = hdr->PresentationTime.Time * 100; if (hdr->OptionsFlags & KSSTREAM_HEADER_OPTIONSF_DURATIONVALID) duration = hdr->Duration * 100; UNREF_BUFFER (buf); if (G_LIKELY (hdr->DataUsed != 0)) { /* Assume it's a good frame */ gst_buffer_set_size (req->buf, hdr->DataUsed); *buf = gst_buffer_ref (req->buf); } if (G_LIKELY (presentation_time != NULL)) *presentation_time = timestamp; if (G_UNLIKELY (GST_DEBUG_IS_ENABLED ())) { gchar *options_flags_str = ks_options_flags_to_string (hdr->OptionsFlags); GST_DEBUG ("PictureNumber=%" G_GUINT64_FORMAT ", DropCount=%" G_GUINT64_FORMAT ", PresentationTime=%" GST_TIME_FORMAT ", Duration=%" GST_TIME_FORMAT ", OptionsFlags=%s: %lu bytes", frame_info->PictureNumber, frame_info->DropCount, GST_TIME_ARGS (timestamp), GST_TIME_ARGS (duration), options_flags_str, hdr->DataUsed); g_free (options_flags_str); } /* Protect against old frames. This should never happen, see previous * comment on last_timestamp. */ if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (timestamp))) { if (G_UNLIKELY (GST_CLOCK_TIME_IS_VALID (priv->last_timestamp) && timestamp < priv->last_timestamp)) { GST_INFO ("got an old frame (last_timestamp=%" GST_TIME_FORMAT ", timestamp=%" GST_TIME_FORMAT ")", GST_TIME_ARGS (priv->last_timestamp), GST_TIME_ARGS (timestamp)); UNREF_BUFFER (buf); } else { priv->last_timestamp = timestamp; } } } 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 (*buf == NULL); return GST_FLOW_OK; /* ERRORS */ error_request_failed: { UNREF_BUFFER (buf); return GST_FLOW_ERROR; } error_timeout: { GST_DEBUG ("IOCTL_KS_READ_STREAM timed out"); if (error_code != NULL) *error_code = 0; if (error_str != NULL) *error_str = NULL; return GST_FLOW_CUSTOM_ERROR; } error_wait: { gst_ks_video_device_parse_win32_error ("WaitForMultipleObjects", GetLastError (), error_code, error_str); return GST_FLOW_ERROR; } error_cancel: { if (error_code != NULL) *error_code = 0; if (error_str != NULL) *error_str = NULL; return GST_FLOW_FLUSHING; } error_get_result: { gst_ks_video_device_parse_win32_error ("GetOverlappedResult", GetLastError (), error_code, error_str); return GST_FLOW_ERROR; } } gboolean gst_ks_video_device_postprocess_frame (GstKsVideoDevice * self, GstBuffer * buf) { GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); /* If it's RGB we need to flip the image */ if (priv->rgb_swap_buf != NULL) { GstMapInfo info; gint stride, line; guint8 *dst, *src; if (!gst_buffer_map (buf, &info, GST_MAP_READWRITE)) return FALSE; stride = info.size / priv->height; dst = info.data; src = info.data + info.size - stride; for (line = 0; line < priv->height / 2; line++) { memcpy (priv->rgb_swap_buf, dst, stride); memcpy (dst, src, stride); memcpy (src, priv->rgb_swap_buf, stride); dst += stride; src -= stride; } gst_buffer_unmap (buf, &info); } return TRUE; } void gst_ks_video_device_cancel (GstKsVideoDevice * self) { GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); SetEvent (priv->cancel_event); } void gst_ks_video_device_cancel_stop (GstKsVideoDevice * self) { GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); ResetEvent (priv->cancel_event); } gboolean gst_ks_video_device_stream_is_muxed (GstKsVideoDevice * self) { GstKsVideoDevicePrivate *priv = GST_KS_VIDEO_DEVICE_GET_PRIVATE (self); return priv->is_muxed; }