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; 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)); params->header.Size = sizeof (KSSTREAM_HEADER) + 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 = GST_BUFFER_DATA (req->buf); 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); 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; } }
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; /* 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)); params->header.Size = sizeof (KSSTREAM_HEADER) + 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 = 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) goto error_ioctl; return TRUE; /* ERRORS */ error_ioctl: { gst_ks_video_device_parse_win32_error ("DeviceIoControl", GetLastError (), error_code, error_str); return FALSE; } }
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) { 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; } } 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; *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; 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; /* Assume it's a good frame */ *bytes_read = hdr->DataUsed; 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: %d 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_WARNING ("got an old frame (last_timestamp=%" GST_TIME_FORMAT ", timestamp=%" GST_TIME_FORMAT ")", GST_TIME_ARGS (priv->last_timestamp), GST_TIME_ARGS (timestamp)); *bytes_read = 0; } 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) 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); return GST_FLOW_OK; /* ERRORS */ error_request_failed: { 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_UNEXPECTED; } 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_WRONG_STATE; } error_get_result: { gst_ks_video_device_parse_win32_error ("GetOverlappedResult", GetLastError (), error_code, error_str); return GST_FLOW_ERROR; } }
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; 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); 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_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, &framing_ex, 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, &framing, &framing_size)) { *num_outstanding = framing->Frames; alignment = framing->FileAlignment; } else { GST_DEBUG ("query for ALLOCATORFRAMING failed"); } } GST_DEBUG ("num_outstanding: %d alignment: 0x%08x", *num_outstanding, 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); g_free (framing_ex); /* * TODO: We also need to respect alignment, but for now we just align * on FILE_512_BYTE_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))) { 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)) { GST_DEBUG ("current master clock handle: 0x%08x", *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))) { 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; } beach: { g_free (framing); if (ks_is_valid_handle (pin_handle)) CloseHandle (pin_handle); g_free (pin_conn); return INVALID_HANDLE_VALUE; } }
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_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_UNEXPECTED; } 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; } }