| /********************************************************************* |
| * Software License Agreement (BSD License) |
| * |
| * Copyright (C) 2010-2012 Ken Tossell |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * * Neither the name of the author nor other contributors may be |
| * used to endorse or promote products derived from this software |
| * without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
| * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
| * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
| * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
| * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
| * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| *********************************************************************/ |
| /** |
| * @defgroup streaming Streaming control functions |
| * @brief Tools for creating, managing and consuming video streams |
| */ |
| |
| #include "libuvc/libuvc.h" |
| #include "libuvc/libuvc_internal.h" |
| #include "errno.h" |
| |
| #ifdef _MSC_VER |
| |
| #define DELTA_EPOCH_IN_MICROSECS 116444736000000000Ui64 |
| |
| // gettimeofday - get time of day for Windows; |
| // A gettimeofday implementation for Microsoft Windows; |
| // Public domain code, author "ponnada"; |
| int gettimeofday(struct timeval *tv, struct timezone *tz) |
| { |
| FILETIME ft; |
| unsigned __int64 tmpres = 0; |
| static int tzflag = 0; |
| if (NULL != tv) |
| { |
| GetSystemTimeAsFileTime(&ft); |
| tmpres |= ft.dwHighDateTime; |
| tmpres <<= 32; |
| tmpres |= ft.dwLowDateTime; |
| tmpres /= 10; |
| tmpres -= DELTA_EPOCH_IN_MICROSECS; |
| tv->tv_sec = (long)(tmpres / 1000000UL); |
| tv->tv_usec = (long)(tmpres % 1000000UL); |
| } |
| return 0; |
| } |
| #endif // _MSC_VER |
| uvc_frame_desc_t *uvc_find_frame_desc_stream(uvc_stream_handle_t *strmh, |
| uint16_t format_id, uint16_t frame_id); |
| uvc_frame_desc_t *uvc_find_frame_desc(uvc_device_handle_t *devh, |
| uint16_t format_id, uint16_t frame_id); |
| void *_uvc_user_caller(void *arg); |
| void _uvc_populate_frame(uvc_stream_handle_t *strmh); |
| |
| struct format_table_entry { |
| enum uvc_frame_format format; |
| uint8_t abstract_fmt; |
| uint8_t guid[16]; |
| int children_count; |
| enum uvc_frame_format *children; |
| }; |
| |
| struct format_table_entry *_get_format_entry(enum uvc_frame_format format) { |
| #define ABS_FMT(_fmt, _num, ...) \ |
| case _fmt: { \ |
| static enum uvc_frame_format _fmt##_children[] = __VA_ARGS__; \ |
| static struct format_table_entry _fmt##_entry = { \ |
| _fmt, 0, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, _num, _fmt##_children }; \ |
| return &_fmt##_entry; } |
| |
| #define FMT(_fmt, ...) \ |
| case _fmt: { \ |
| static struct format_table_entry _fmt##_entry = { \ |
| _fmt, 0, __VA_ARGS__, 0, NULL }; \ |
| return &_fmt##_entry; } |
| |
| switch(format) { |
| /* Define new formats here */ |
| ABS_FMT(UVC_FRAME_FORMAT_ANY, 2, |
| {UVC_FRAME_FORMAT_UNCOMPRESSED, UVC_FRAME_FORMAT_COMPRESSED}) |
| |
| ABS_FMT(UVC_FRAME_FORMAT_UNCOMPRESSED, 4, |
| {UVC_FRAME_FORMAT_YUYV, UVC_FRAME_FORMAT_UYVY, UVC_FRAME_FORMAT_GRAY8, |
| UVC_FRAME_FORMAT_GRAY16}) |
| FMT(UVC_FRAME_FORMAT_YUYV, |
| {'Y', 'U', 'Y', '2', 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}) |
| FMT(UVC_FRAME_FORMAT_UYVY, |
| {'U', 'Y', 'V', 'Y', 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}) |
| FMT(UVC_FRAME_FORMAT_GRAY8, |
| {'Y', '8', '0', '0', 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}) |
| FMT(UVC_FRAME_FORMAT_GRAY16, |
| {'Y', '1', '6', ' ', 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}) |
| FMT(UVC_FRAME_FORMAT_BY8, |
| {'B', 'Y', '8', ' ', 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}) |
| FMT(UVC_FRAME_FORMAT_BA81, |
| {'B', 'A', '8', '1', 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}) |
| FMT(UVC_FRAME_FORMAT_SGRBG8, |
| {'G', 'R', 'B', 'G', 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}) |
| FMT(UVC_FRAME_FORMAT_SGBRG8, |
| {'G', 'B', 'R', 'G', 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}) |
| FMT(UVC_FRAME_FORMAT_SRGGB8, |
| {'R', 'G', 'G', 'B', 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}) |
| FMT(UVC_FRAME_FORMAT_SBGGR8, |
| {'B', 'G', 'G', 'R', 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}) |
| ABS_FMT(UVC_FRAME_FORMAT_COMPRESSED, 1, |
| {UVC_FRAME_FORMAT_MJPEG}) |
| FMT(UVC_FRAME_FORMAT_MJPEG, |
| {'M', 'J', 'P', 'G'}) |
| |
| default: |
| return NULL; |
| } |
| |
| #undef ABS_FMT |
| #undef FMT |
| } |
| |
| static uint8_t _uvc_frame_format_matches_guid(enum uvc_frame_format fmt, uint8_t guid[16]) { |
| struct format_table_entry *format; |
| int child_idx; |
| |
| format = _get_format_entry(fmt); |
| if (!format) |
| return 0; |
| |
| if (!format->abstract_fmt && !memcmp(guid, format->guid, 16)) |
| return 1; |
| |
| for (child_idx = 0; child_idx < format->children_count; child_idx++) { |
| if (_uvc_frame_format_matches_guid(format->children[child_idx], guid)) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static enum uvc_frame_format uvc_frame_format_for_guid(uint8_t guid[16]) { |
| struct format_table_entry *format; |
| enum uvc_frame_format fmt; |
| |
| for (fmt = 0; fmt < UVC_FRAME_FORMAT_COUNT; ++fmt) { |
| format = _get_format_entry(fmt); |
| if (!format || format->abstract_fmt) |
| continue; |
| if (!memcmp(format->guid, guid, 16)) |
| return format->format; |
| } |
| |
| return UVC_FRAME_FORMAT_UNKNOWN; |
| } |
| |
| /** @internal |
| * Run a streaming control query |
| * @param[in] devh UVC device |
| * @param[in,out] ctrl Control block |
| * @param[in] probe Whether this is a probe query or a commit query |
| * @param[in] req Query type |
| */ |
| uvc_error_t uvc_query_stream_ctrl( |
| uvc_device_handle_t *devh, |
| uvc_stream_ctrl_t *ctrl, |
| uint8_t probe, |
| enum uvc_req_code req) { |
| uint8_t buf[34]; |
| size_t len; |
| uvc_error_t err; |
| |
| memset(buf, 0, sizeof(buf)); |
| |
| if (devh->info->ctrl_if.bcdUVC >= 0x0110) |
| len = 34; |
| else |
| len = 26; |
| |
| /* prepare for a SET transfer */ |
| if (req == UVC_SET_CUR) { |
| SHORT_TO_SW(ctrl->bmHint, buf); |
| buf[2] = ctrl->bFormatIndex; |
| buf[3] = ctrl->bFrameIndex; |
| INT_TO_DW(ctrl->dwFrameInterval, buf + 4); |
| SHORT_TO_SW(ctrl->wKeyFrameRate, buf + 8); |
| SHORT_TO_SW(ctrl->wPFrameRate, buf + 10); |
| SHORT_TO_SW(ctrl->wCompQuality, buf + 12); |
| SHORT_TO_SW(ctrl->wCompWindowSize, buf + 14); |
| SHORT_TO_SW(ctrl->wDelay, buf + 16); |
| INT_TO_DW(ctrl->dwMaxVideoFrameSize, buf + 18); |
| INT_TO_DW(ctrl->dwMaxPayloadTransferSize, buf + 22); |
| |
| if (len == 34) { |
| INT_TO_DW ( ctrl->dwClockFrequency, buf + 26 ); |
| buf[30] = ctrl->bmFramingInfo; |
| buf[31] = ctrl->bPreferredVersion; |
| buf[32] = ctrl->bMinVersion; |
| buf[33] = ctrl->bMaxVersion; |
| /** @todo support UVC 1.1 */ |
| } |
| } |
| |
| /* do the transfer */ |
| err = libusb_control_transfer( |
| devh->usb_devh, |
| req == UVC_SET_CUR ? 0x21 : 0xA1, |
| req, |
| probe ? (UVC_VS_PROBE_CONTROL << 8) : (UVC_VS_COMMIT_CONTROL << 8), |
| ctrl->bInterfaceNumber, |
| buf, len, 0 |
| ); |
| |
| if (err <= 0) { |
| return err; |
| } |
| |
| /* now decode following a GET transfer */ |
| if (req != UVC_SET_CUR) { |
| ctrl->bmHint = SW_TO_SHORT(buf); |
| ctrl->bFormatIndex = buf[2]; |
| ctrl->bFrameIndex = buf[3]; |
| ctrl->dwFrameInterval = DW_TO_INT(buf + 4); |
| ctrl->wKeyFrameRate = SW_TO_SHORT(buf + 8); |
| ctrl->wPFrameRate = SW_TO_SHORT(buf + 10); |
| ctrl->wCompQuality = SW_TO_SHORT(buf + 12); |
| ctrl->wCompWindowSize = SW_TO_SHORT(buf + 14); |
| ctrl->wDelay = SW_TO_SHORT(buf + 16); |
| ctrl->dwMaxVideoFrameSize = DW_TO_INT(buf + 18); |
| ctrl->dwMaxPayloadTransferSize = DW_TO_INT(buf + 22); |
| |
| if (len == 34) { |
| ctrl->dwClockFrequency = DW_TO_INT ( buf + 26 ); |
| ctrl->bmFramingInfo = buf[30]; |
| ctrl->bPreferredVersion = buf[31]; |
| ctrl->bMinVersion = buf[32]; |
| ctrl->bMaxVersion = buf[33]; |
| /** @todo support UVC 1.1 */ |
| } |
| else |
| ctrl->dwClockFrequency = devh->info->ctrl_if.dwClockFrequency; |
| |
| /* fix up block for cameras that fail to set dwMax* */ |
| if (ctrl->dwMaxVideoFrameSize == 0) { |
| uvc_frame_desc_t *frame = uvc_find_frame_desc(devh, ctrl->bFormatIndex, ctrl->bFrameIndex); |
| |
| if (frame) { |
| ctrl->dwMaxVideoFrameSize = frame->dwMaxVideoFrameBufferSize; |
| } |
| } |
| } |
| |
| return UVC_SUCCESS; |
| } |
| |
| /** @brief Reconfigure stream with a new stream format. |
| * @ingroup streaming |
| * |
| * This may be executed whether or not the stream is running. |
| * |
| * @param[in] strmh Stream handle |
| * @param[in] ctrl Control block, processed using {uvc_probe_stream_ctrl} or |
| * {uvc_get_stream_ctrl_format_size} |
| */ |
| uvc_error_t uvc_stream_ctrl(uvc_stream_handle_t *strmh, uvc_stream_ctrl_t *ctrl) { |
| uvc_error_t ret; |
| |
| if (strmh->stream_if->bInterfaceNumber != ctrl->bInterfaceNumber) |
| return UVC_ERROR_INVALID_PARAM; |
| |
| /* @todo Allow the stream to be modified without restarting the stream */ |
| if (strmh->running) |
| return UVC_ERROR_BUSY; |
| |
| ret = uvc_query_stream_ctrl(strmh->devh, ctrl, 0, UVC_SET_CUR); |
| if (ret != UVC_SUCCESS) |
| return ret; |
| |
| strmh->cur_ctrl = *ctrl; |
| return UVC_SUCCESS; |
| } |
| |
| /** @internal |
| * @brief Find the descriptor for a specific frame configuration |
| * @param stream_if Stream interface |
| * @param format_id Index of format class descriptor |
| * @param frame_id Index of frame descriptor |
| */ |
| static uvc_frame_desc_t *_uvc_find_frame_desc_stream_if(uvc_streaming_interface_t *stream_if, |
| uint16_t format_id, uint16_t frame_id) { |
| |
| uvc_format_desc_t *format = NULL; |
| uvc_frame_desc_t *frame = NULL; |
| |
| DL_FOREACH(stream_if->format_descs, format) { |
| if (format->bFormatIndex == format_id) { |
| DL_FOREACH(format->frame_descs, frame) { |
| if (frame->bFrameIndex == frame_id) |
| return frame; |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| uvc_frame_desc_t *uvc_find_frame_desc_stream(uvc_stream_handle_t *strmh, |
| uint16_t format_id, uint16_t frame_id) { |
| return _uvc_find_frame_desc_stream_if(strmh->stream_if, format_id, frame_id); |
| } |
| |
| /** @internal |
| * @brief Find the descriptor for a specific frame configuration |
| * @param devh UVC device |
| * @param format_id Index of format class descriptor |
| * @param frame_id Index of frame descriptor |
| */ |
| uvc_frame_desc_t *uvc_find_frame_desc(uvc_device_handle_t *devh, |
| uint16_t format_id, uint16_t frame_id) { |
| |
| uvc_streaming_interface_t *stream_if; |
| uvc_frame_desc_t *frame; |
| |
| DL_FOREACH(devh->info->stream_ifs, stream_if) { |
| frame = _uvc_find_frame_desc_stream_if(stream_if, format_id, frame_id); |
| if (frame) |
| return frame; |
| } |
| |
| return NULL; |
| } |
| |
| /** Get a negotiated streaming control block for some common parameters. |
| * @ingroup streaming |
| * |
| * @param[in] devh Device handle |
| * @param[in,out] ctrl Control block |
| * @param[in] format_class Type of streaming format |
| * @param[in] width Desired frame width |
| * @param[in] height Desired frame height |
| * @param[in] fps Frame rate, frames per second |
| */ |
| uvc_error_t uvc_get_stream_ctrl_format_size( |
| uvc_device_handle_t *devh, |
| uvc_stream_ctrl_t *ctrl, |
| enum uvc_frame_format cf, |
| int width, int height, |
| int fps) { |
| uvc_streaming_interface_t *stream_if; |
| |
| /* find a matching frame descriptor and interval */ |
| DL_FOREACH(devh->info->stream_ifs, stream_if) { |
| uvc_format_desc_t *format; |
| |
| DL_FOREACH(stream_if->format_descs, format) { |
| uvc_frame_desc_t *frame; |
| |
| if (!_uvc_frame_format_matches_guid(cf, format->guidFormat)) |
| continue; |
| |
| DL_FOREACH(format->frame_descs, frame) { |
| if (frame->wWidth != width || frame->wHeight != height) |
| continue; |
| |
| uint32_t *interval; |
| |
| ctrl->bInterfaceNumber = stream_if->bInterfaceNumber; |
| UVC_DEBUG("claiming streaming interface %d", stream_if->bInterfaceNumber ); |
| uvc_claim_if(devh, ctrl->bInterfaceNumber); |
| /* get the max values */ |
| uvc_query_stream_ctrl( devh, ctrl, 1, UVC_GET_MAX); |
| |
| if (frame->intervals) { |
| for (interval = frame->intervals; *interval; ++interval) { |
| // allow a fps rate of zero to mean "accept first rate available" |
| if (10000000 / *interval == (unsigned int) fps || fps == 0) { |
| |
| ctrl->bmHint = (1 << 0); /* don't negotiate interval */ |
| ctrl->bFormatIndex = format->bFormatIndex; |
| ctrl->bFrameIndex = frame->bFrameIndex; |
| ctrl->dwFrameInterval = *interval; |
| |
| goto found; |
| } |
| } |
| } else { |
| uint32_t interval_100ns = 10000000 / fps; |
| uint32_t interval_offset = interval_100ns - frame->dwMinFrameInterval; |
| |
| if (interval_100ns >= frame->dwMinFrameInterval |
| && interval_100ns <= frame->dwMaxFrameInterval |
| && !(interval_offset |
| && (interval_offset % frame->dwFrameIntervalStep))) { |
| |
| ctrl->bmHint = (1 << 0); |
| ctrl->bFormatIndex = format->bFormatIndex; |
| ctrl->bFrameIndex = frame->bFrameIndex; |
| ctrl->dwFrameInterval = interval_100ns; |
| |
| goto found; |
| } |
| } |
| } |
| } |
| } |
| |
| return UVC_ERROR_INVALID_MODE; |
| |
| found: |
| return uvc_probe_stream_ctrl(devh, ctrl); |
| } |
| |
| /** @internal |
| * Negotiate streaming parameters with the device |
| * |
| * @param[in] devh UVC device |
| * @param[in,out] ctrl Control block |
| */ |
| uvc_error_t uvc_probe_stream_ctrl( |
| uvc_device_handle_t *devh, |
| uvc_stream_ctrl_t *ctrl) { |
| |
| uvc_query_stream_ctrl( |
| devh, ctrl, 1, UVC_SET_CUR |
| ); |
| |
| uvc_query_stream_ctrl( |
| devh, ctrl, 1, UVC_GET_CUR |
| ); |
| |
| /** @todo make sure that worked */ |
| return UVC_SUCCESS; |
| } |
| |
| /** @internal |
| * @brief Swap the working buffer with the presented buffer and notify consumers |
| */ |
| void _uvc_swap_buffers(uvc_stream_handle_t *strmh) { |
| uint8_t *tmp_buf; |
| |
| pthread_mutex_lock(&strmh->cb_mutex); |
| |
| /* swap the buffers */ |
| tmp_buf = strmh->holdbuf; |
| strmh->hold_bytes = strmh->got_bytes; |
| strmh->holdbuf = strmh->outbuf; |
| strmh->outbuf = tmp_buf; |
| strmh->hold_last_scr = strmh->last_scr; |
| strmh->hold_pts = strmh->pts; |
| strmh->hold_seq = strmh->seq; |
| |
| pthread_cond_broadcast(&strmh->cb_cond); |
| pthread_mutex_unlock(&strmh->cb_mutex); |
| |
| strmh->seq++; |
| strmh->got_bytes = 0; |
| strmh->last_scr = 0; |
| strmh->pts = 0; |
| } |
| |
| /** @internal |
| * @brief Process a payload transfer |
| * |
| * Processes stream, places frames into buffer, signals listeners |
| * (such as user callback thread and any polling thread) on new frame |
| * |
| * @param payload Contents of the payload transfer, either a packet (isochronous) or a full |
| * transfer (bulk mode) |
| * @param payload_len Length of the payload transfer |
| */ |
| void _uvc_process_payload(uvc_stream_handle_t *strmh, uint8_t *payload, size_t payload_len) { |
| size_t header_len; |
| uint8_t header_info; |
| size_t data_len; |
| |
| /* magic numbers for identifying header packets from some iSight cameras */ |
| static uint8_t isight_tag[] = { |
| 0x11, 0x22, 0x33, 0x44, |
| 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xfa, 0xce |
| }; |
| |
| /* ignore empty payload transfers */ |
| if (payload_len == 0) |
| return; |
| |
| /* Certain iSight cameras have strange behavior: They send header |
| * information in a packet with no image data, and then the following |
| * packets have only image data, with no more headers until the next frame. |
| * |
| * The iSight header: len(1), flags(1 or 2), 0x11223344(4), |
| * 0xdeadbeefdeadface(8), ??(16) |
| */ |
| |
| if (strmh->devh->is_isight && |
| (payload_len < 14 || memcmp(isight_tag, payload + 2, sizeof(isight_tag))) && |
| (payload_len < 15 || memcmp(isight_tag, payload + 3, sizeof(isight_tag)))) { |
| /* The payload transfer doesn't have any iSight magic, so it's all image data */ |
| header_len = 0; |
| data_len = payload_len; |
| } else { |
| header_len = payload[0]; |
| |
| if (header_len > payload_len) { |
| UVC_DEBUG("bogus packet: actual_len=%zd, header_len=%zd\n", payload_len, header_len); |
| return; |
| } |
| |
| if (strmh->devh->is_isight) |
| data_len = 0; |
| else |
| data_len = payload_len - header_len; |
| } |
| |
| if (header_len < 2) { |
| header_info = 0; |
| } else { |
| /** @todo we should be checking the end-of-header bit */ |
| size_t variable_offset = 2; |
| |
| header_info = payload[1]; |
| |
| if (header_info & 0x40) { |
| UVC_DEBUG("bad packet: error bit set"); |
| return; |
| } |
| |
| if (strmh->fid != (header_info & 1) && strmh->got_bytes != 0) { |
| /* The frame ID bit was flipped, but we have image data sitting |
| around from prior transfers. This means the camera didn't send |
| an EOF for the last transfer of the previous frame. */ |
| _uvc_swap_buffers(strmh); |
| } |
| |
| strmh->fid = header_info & 1; |
| |
| if (header_info & (1 << 2)) { |
| strmh->pts = DW_TO_INT(payload + variable_offset); |
| variable_offset += 4; |
| } |
| |
| if (header_info & (1 << 3)) { |
| /** @todo read the SOF token counter */ |
| strmh->last_scr = DW_TO_INT(payload + variable_offset); |
| variable_offset += 6; |
| } |
| } |
| |
| if (data_len > 0) { |
| memcpy(strmh->outbuf + strmh->got_bytes, payload + header_len, data_len); |
| strmh->got_bytes += data_len; |
| |
| if (header_info & (1 << 1)) { |
| /* The EOF bit is set, so publish the complete frame */ |
| _uvc_swap_buffers(strmh); |
| } |
| } |
| } |
| |
| /** @internal |
| * @brief Stream transfer callback |
| * |
| * Processes stream, places frames into buffer, signals listeners |
| * (such as user callback thread and any polling thread) on new frame |
| * |
| * @param transfer Active transfer |
| */ |
| void LIBUSB_CALL _uvc_stream_callback(struct libusb_transfer *transfer) { |
| uvc_stream_handle_t *strmh = transfer->user_data; |
| |
| int resubmit = 1; |
| |
| switch (transfer->status) { |
| case LIBUSB_TRANSFER_COMPLETED: |
| if (transfer->num_iso_packets == 0) { |
| /* This is a bulk mode transfer, so it just has one payload transfer */ |
| _uvc_process_payload(strmh, transfer->buffer, transfer->actual_length); |
| } else { |
| /* This is an isochronous mode transfer, so each packet has a payload transfer */ |
| int packet_id; |
| |
| for (packet_id = 0; packet_id < transfer->num_iso_packets; ++packet_id) { |
| uint8_t *pktbuf; |
| struct libusb_iso_packet_descriptor *pkt; |
| |
| pkt = transfer->iso_packet_desc + packet_id; |
| |
| if (pkt->status != 0) { |
| UVC_DEBUG("bad packet (isochronous transfer); status: %d", pkt->status); |
| continue; |
| } |
| |
| pktbuf = libusb_get_iso_packet_buffer_simple(transfer, packet_id); |
| |
| _uvc_process_payload(strmh, pktbuf, pkt->actual_length); |
| |
| } |
| } |
| break; |
| case LIBUSB_TRANSFER_CANCELLED: |
| case LIBUSB_TRANSFER_ERROR: |
| case LIBUSB_TRANSFER_NO_DEVICE: { |
| int i; |
| UVC_DEBUG("not retrying transfer, status = %d", transfer->status); |
| pthread_mutex_lock(&strmh->cb_mutex); |
| |
| /* Mark transfer as deleted. */ |
| for(i=0; i < LIBUVC_NUM_TRANSFER_BUFS; i++) { |
| if(strmh->transfers[i] == transfer) { |
| UVC_DEBUG("Freeing transfer %d (%p)", i, transfer); |
| free(transfer->buffer); |
| libusb_free_transfer(transfer); |
| strmh->transfers[i] = NULL; |
| break; |
| } |
| } |
| if(i == LIBUVC_NUM_TRANSFER_BUFS ) { |
| UVC_DEBUG("transfer %p not found; not freeing!", transfer); |
| } |
| |
| resubmit = 0; |
| |
| pthread_cond_broadcast(&strmh->cb_cond); |
| pthread_mutex_unlock(&strmh->cb_mutex); |
| |
| break; |
| } |
| case LIBUSB_TRANSFER_TIMED_OUT: |
| case LIBUSB_TRANSFER_STALL: |
| case LIBUSB_TRANSFER_OVERFLOW: |
| UVC_DEBUG("retrying transfer, status = %d", transfer->status); |
| break; |
| } |
| |
| if ( resubmit ) { |
| if ( strmh->running ) { |
| libusb_submit_transfer(transfer); |
| } else { |
| int i; |
| pthread_mutex_lock(&strmh->cb_mutex); |
| |
| /* Mark transfer as deleted. */ |
| for(i=0; i < LIBUVC_NUM_TRANSFER_BUFS; i++) { |
| if(strmh->transfers[i] == transfer) { |
| UVC_DEBUG("Freeing orphan transfer %d (%p)", i, transfer); |
| free(transfer->buffer); |
| libusb_free_transfer(transfer); |
| strmh->transfers[i] = NULL; |
| } |
| } |
| if(i == LIBUVC_NUM_TRANSFER_BUFS ) { |
| UVC_DEBUG("orphan transfer %p not found; not freeing!", transfer); |
| } |
| |
| pthread_cond_broadcast(&strmh->cb_cond); |
| pthread_mutex_unlock(&strmh->cb_mutex); |
| } |
| } |
| } |
| |
| /** Begin streaming video from the camera into the callback function. |
| * @ingroup streaming |
| * |
| * @param devh UVC device |
| * @param ctrl Control block, processed using {uvc_probe_stream_ctrl} or |
| * {uvc_get_stream_ctrl_format_size} |
| * @param cb User callback function. See {uvc_frame_callback_t} for restrictions. |
| * @param flags Stream setup flags, currently undefined. Set this to zero. The lower bit |
| * is reserved for backward compatibility. |
| */ |
| uvc_error_t uvc_start_streaming( |
| uvc_device_handle_t *devh, |
| uvc_stream_ctrl_t *ctrl, |
| uvc_frame_callback_t *cb, |
| void *user_ptr, |
| uint8_t flags |
| ) { |
| uvc_error_t ret; |
| uvc_stream_handle_t *strmh; |
| |
| ret = uvc_stream_open_ctrl(devh, &strmh, ctrl); |
| if (ret != UVC_SUCCESS) |
| return ret; |
| |
| ret = uvc_stream_start(strmh, cb, user_ptr, flags); |
| if (ret != UVC_SUCCESS) { |
| uvc_stream_close(strmh); |
| return ret; |
| } |
| |
| return UVC_SUCCESS; |
| } |
| |
| /** Begin streaming video from the camera into the callback function. |
| * @ingroup streaming |
| * |
| * @deprecated The stream type (bulk vs. isochronous) will be determined by the |
| * type of interface associated with the uvc_stream_ctrl_t parameter, regardless |
| * of whether the caller requests isochronous streaming. Please switch to |
| * uvc_start_streaming(). |
| * |
| * @param devh UVC device |
| * @param ctrl Control block, processed using {uvc_probe_stream_ctrl} or |
| * {uvc_get_stream_ctrl_format_size} |
| * @param cb User callback function. See {uvc_frame_callback_t} for restrictions. |
| */ |
| uvc_error_t uvc_start_iso_streaming( |
| uvc_device_handle_t *devh, |
| uvc_stream_ctrl_t *ctrl, |
| uvc_frame_callback_t *cb, |
| void *user_ptr |
| ) { |
| return uvc_start_streaming(devh, ctrl, cb, user_ptr, 0); |
| } |
| |
| static uvc_stream_handle_t *_uvc_get_stream_by_interface(uvc_device_handle_t *devh, int interface_idx) { |
| uvc_stream_handle_t *strmh; |
| |
| DL_FOREACH(devh->streams, strmh) { |
| if (strmh->stream_if->bInterfaceNumber == interface_idx) |
| return strmh; |
| } |
| |
| return NULL; |
| } |
| |
| static uvc_streaming_interface_t *_uvc_get_stream_if(uvc_device_handle_t *devh, int interface_idx) { |
| uvc_streaming_interface_t *stream_if; |
| |
| DL_FOREACH(devh->info->stream_ifs, stream_if) { |
| if (stream_if->bInterfaceNumber == interface_idx) |
| return stream_if; |
| } |
| |
| return NULL; |
| } |
| |
| /** Open a new video stream. |
| * @ingroup streaming |
| * |
| * @param devh UVC device |
| * @param ctrl Control block, processed using {uvc_probe_stream_ctrl} or |
| * {uvc_get_stream_ctrl_format_size} |
| */ |
| uvc_error_t uvc_stream_open_ctrl(uvc_device_handle_t *devh, uvc_stream_handle_t **strmhp, uvc_stream_ctrl_t *ctrl) { |
| /* Chosen frame and format descriptors */ |
| uvc_stream_handle_t *strmh = NULL; |
| uvc_streaming_interface_t *stream_if; |
| uvc_error_t ret; |
| |
| UVC_ENTER(); |
| |
| if (_uvc_get_stream_by_interface(devh, ctrl->bInterfaceNumber) != NULL) { |
| ret = UVC_ERROR_BUSY; /* Stream is already opened */ |
| goto fail; |
| } |
| |
| stream_if = _uvc_get_stream_if(devh, ctrl->bInterfaceNumber); |
| if (!stream_if) { |
| ret = UVC_ERROR_INVALID_PARAM; |
| goto fail; |
| } |
| |
| strmh = calloc(1, sizeof(*strmh)); |
| if (!strmh) { |
| ret = UVC_ERROR_NO_MEM; |
| goto fail; |
| } |
| strmh->devh = devh; |
| strmh->stream_if = stream_if; |
| strmh->frame.library_owns_data = 1; |
| |
| ret = uvc_claim_if(strmh->devh, strmh->stream_if->bInterfaceNumber); |
| if (ret != UVC_SUCCESS) |
| goto fail; |
| |
| ret = uvc_stream_ctrl(strmh, ctrl); |
| if (ret != UVC_SUCCESS) |
| goto fail; |
| |
| // Set up the streaming status and data space |
| strmh->running = 0; |
| /** @todo take only what we need */ |
| strmh->outbuf = malloc( LIBUVC_XFER_BUF_SIZE ); |
| strmh->holdbuf = malloc( LIBUVC_XFER_BUF_SIZE ); |
| |
| pthread_mutex_init(&strmh->cb_mutex, NULL); |
| pthread_cond_init(&strmh->cb_cond, NULL); |
| |
| DL_APPEND(devh->streams, strmh); |
| |
| *strmhp = strmh; |
| |
| UVC_EXIT(0); |
| return UVC_SUCCESS; |
| |
| fail: |
| if(strmh) |
| free(strmh); |
| UVC_EXIT(ret); |
| return ret; |
| } |
| |
| /** Begin streaming video from the stream into the callback function. |
| * @ingroup streaming |
| * |
| * @param strmh UVC stream |
| * @param cb User callback function. See {uvc_frame_callback_t} for restrictions. |
| * @param flags Stream setup flags, currently undefined. Set this to zero. The lower bit |
| * is reserved for backward compatibility. |
| */ |
| uvc_error_t uvc_stream_start( |
| uvc_stream_handle_t *strmh, |
| uvc_frame_callback_t *cb, |
| void *user_ptr, |
| uint8_t flags |
| ) { |
| /* USB interface we'll be using */ |
| const struct libusb_interface *interface; |
| int interface_id; |
| char isochronous; |
| uvc_frame_desc_t *frame_desc; |
| uvc_format_desc_t *format_desc; |
| uvc_stream_ctrl_t *ctrl; |
| uvc_error_t ret; |
| /* Total amount of data per transfer */ |
| size_t total_transfer_size = 0; |
| struct libusb_transfer *transfer; |
| int transfer_id; |
| |
| ctrl = &strmh->cur_ctrl; |
| |
| UVC_ENTER(); |
| |
| if (strmh->running) { |
| UVC_EXIT(UVC_ERROR_BUSY); |
| return UVC_ERROR_BUSY; |
| } |
| |
| strmh->running = 1; |
| strmh->seq = 1; |
| strmh->fid = 0; |
| strmh->pts = 0; |
| strmh->last_scr = 0; |
| |
| frame_desc = uvc_find_frame_desc_stream(strmh, ctrl->bFormatIndex, ctrl->bFrameIndex); |
| if (!frame_desc) { |
| ret = UVC_ERROR_INVALID_PARAM; |
| goto fail; |
| } |
| format_desc = frame_desc->parent; |
| |
| strmh->frame_format = uvc_frame_format_for_guid(format_desc->guidFormat); |
| if (strmh->frame_format == UVC_FRAME_FORMAT_UNKNOWN) { |
| ret = UVC_ERROR_NOT_SUPPORTED; |
| goto fail; |
| } |
| |
| // Get the interface that provides the chosen format and frame configuration |
| interface_id = strmh->stream_if->bInterfaceNumber; |
| interface = &strmh->devh->info->config->interface[interface_id]; |
| |
| /* A VS interface uses isochronous transfers iff it has multiple altsettings. |
| * (UVC 1.5: 2.4.3. VideoStreaming Interface) */ |
| isochronous = interface->num_altsetting > 1; |
| |
| if (isochronous) { |
| /* For isochronous streaming, we choose an appropriate altsetting for the endpoint |
| * and set up several transfers */ |
| const struct libusb_interface_descriptor *altsetting = 0; |
| const struct libusb_endpoint_descriptor *endpoint; |
| /* The greatest number of bytes that the device might provide, per packet, in this |
| * configuration */ |
| size_t config_bytes_per_packet; |
| /* Number of packets per transfer */ |
| size_t packets_per_transfer = 0; |
| /* Size of packet transferable from the chosen endpoint */ |
| size_t endpoint_bytes_per_packet = 0; |
| /* Index of the altsetting */ |
| int alt_idx, ep_idx; |
| |
| config_bytes_per_packet = strmh->cur_ctrl.dwMaxPayloadTransferSize; |
| |
| /* Go through the altsettings and find one whose packets are at least |
| * as big as our format's maximum per-packet usage. Assume that the |
| * packet sizes are increasing. */ |
| for (alt_idx = 0; alt_idx < interface->num_altsetting; alt_idx++) { |
| altsetting = interface->altsetting + alt_idx; |
| endpoint_bytes_per_packet = 0; |
| |
| /* Find the endpoint with the number specified in the VS header */ |
| for (ep_idx = 0; ep_idx < altsetting->bNumEndpoints; ep_idx++) { |
| endpoint = altsetting->endpoint + ep_idx; |
| |
| if (endpoint->bEndpointAddress == format_desc->parent->bEndpointAddress) { |
| endpoint_bytes_per_packet = endpoint->wMaxPacketSize; |
| // wMaxPacketSize: [unused:2 (multiplier-1):3 size:11] |
| endpoint_bytes_per_packet = (endpoint_bytes_per_packet & 0x07ff) * |
| (((endpoint_bytes_per_packet >> 11) & 3) + 1); |
| break; |
| } |
| } |
| |
| if (endpoint_bytes_per_packet >= config_bytes_per_packet) { |
| /* Transfers will be at most one frame long: Divide the maximum frame size |
| * by the size of the endpoint and round up */ |
| packets_per_transfer = (ctrl->dwMaxVideoFrameSize + |
| endpoint_bytes_per_packet - 1) / endpoint_bytes_per_packet; |
| |
| /* But keep a reasonable limit: Otherwise we start dropping data */ |
| if (packets_per_transfer > 32) |
| packets_per_transfer = 32; |
| |
| total_transfer_size = packets_per_transfer * endpoint_bytes_per_packet; |
| break; |
| } |
| } |
| |
| /* If we searched through all the altsettings and found nothing usable */ |
| if (alt_idx == interface->num_altsetting) { |
| ret = UVC_ERROR_INVALID_MODE; |
| goto fail; |
| } |
| |
| /* Select the altsetting */ |
| ret = libusb_set_interface_alt_setting(strmh->devh->usb_devh, |
| altsetting->bInterfaceNumber, |
| altsetting->bAlternateSetting); |
| if (ret != UVC_SUCCESS) { |
| UVC_DEBUG("libusb_set_interface_alt_setting failed"); |
| goto fail; |
| } |
| |
| /* Set up the transfers */ |
| for (transfer_id = 0; transfer_id < LIBUVC_NUM_TRANSFER_BUFS; ++transfer_id) { |
| transfer = libusb_alloc_transfer(packets_per_transfer); |
| strmh->transfers[transfer_id] = transfer; |
| strmh->transfer_bufs[transfer_id] = malloc(total_transfer_size); |
| |
| libusb_fill_iso_transfer( |
| transfer, strmh->devh->usb_devh, format_desc->parent->bEndpointAddress, |
| strmh->transfer_bufs[transfer_id], |
| total_transfer_size, packets_per_transfer, _uvc_stream_callback, (void*) strmh, 5000); |
| |
| libusb_set_iso_packet_lengths(transfer, endpoint_bytes_per_packet); |
| } |
| } else { |
| for (transfer_id = 0; transfer_id < LIBUVC_NUM_TRANSFER_BUFS; |
| ++transfer_id) { |
| transfer = libusb_alloc_transfer(0); |
| strmh->transfers[transfer_id] = transfer; |
| strmh->transfer_bufs[transfer_id] = malloc ( |
| strmh->cur_ctrl.dwMaxPayloadTransferSize ); |
| libusb_fill_bulk_transfer ( transfer, strmh->devh->usb_devh, |
| format_desc->parent->bEndpointAddress, |
| strmh->transfer_bufs[transfer_id], |
| strmh->cur_ctrl.dwMaxPayloadTransferSize, _uvc_stream_callback, |
| ( void* ) strmh, 5000 ); |
| } |
| } |
| |
| strmh->user_cb = cb; |
| strmh->user_ptr = user_ptr; |
| |
| /* If the user wants it, set up a thread that calls the user's function |
| * with the contents of each frame. |
| */ |
| if (cb) { |
| pthread_create(&strmh->cb_thread, NULL, _uvc_user_caller, (void*) strmh); |
| } |
| |
| for (transfer_id = 0; transfer_id < LIBUVC_NUM_TRANSFER_BUFS; |
| transfer_id++) { |
| ret = libusb_submit_transfer(strmh->transfers[transfer_id]); |
| if (ret != UVC_SUCCESS) { |
| UVC_DEBUG("libusb_submit_transfer failed: %d",ret); |
| break; |
| } |
| } |
| |
| if ( ret != UVC_SUCCESS && transfer_id > 0 ) { |
| for ( ; transfer_id < LIBUVC_NUM_TRANSFER_BUFS; transfer_id++) { |
| free ( strmh->transfers[transfer_id]->buffer ); |
| libusb_free_transfer ( strmh->transfers[transfer_id]); |
| strmh->transfers[transfer_id] = 0; |
| } |
| ret = UVC_SUCCESS; |
| } |
| |
| UVC_EXIT(ret); |
| return ret; |
| fail: |
| strmh->running = 0; |
| UVC_EXIT(ret); |
| return ret; |
| } |
| |
| /** Begin streaming video from the stream into the callback function. |
| * @ingroup streaming |
| * |
| * @deprecated The stream type (bulk vs. isochronous) will be determined by the |
| * type of interface associated with the uvc_stream_ctrl_t parameter, regardless |
| * of whether the caller requests isochronous streaming. Please switch to |
| * uvc_stream_start(). |
| * |
| * @param strmh UVC stream |
| * @param cb User callback function. See {uvc_frame_callback_t} for restrictions. |
| */ |
| uvc_error_t uvc_stream_start_iso( |
| uvc_stream_handle_t *strmh, |
| uvc_frame_callback_t *cb, |
| void *user_ptr |
| ) { |
| return uvc_stream_start(strmh, cb, user_ptr, 0); |
| } |
| |
| /** @internal |
| * @brief User callback runner thread |
| * @note There should be at most one of these per currently streaming device |
| * @param arg Device handle |
| */ |
| void *_uvc_user_caller(void *arg) { |
| uvc_stream_handle_t *strmh = (uvc_stream_handle_t *) arg; |
| |
| uint32_t last_seq = 0; |
| |
| do { |
| pthread_mutex_lock(&strmh->cb_mutex); |
| |
| while (strmh->running && last_seq == strmh->hold_seq) { |
| pthread_cond_wait(&strmh->cb_cond, &strmh->cb_mutex); |
| } |
| |
| if (!strmh->running) { |
| pthread_mutex_unlock(&strmh->cb_mutex); |
| break; |
| } |
| |
| last_seq = strmh->hold_seq; |
| _uvc_populate_frame(strmh); |
| |
| pthread_mutex_unlock(&strmh->cb_mutex); |
| |
| strmh->user_cb(&strmh->frame, strmh->user_ptr); |
| } while(1); |
| |
| return NULL; // return value ignored |
| } |
| |
| /** @internal |
| * @brief Populate the fields of a frame to be handed to user code |
| * must be called with stream cb lock held! |
| */ |
| void _uvc_populate_frame(uvc_stream_handle_t *strmh) { |
| uvc_frame_t *frame = &strmh->frame; |
| uvc_frame_desc_t *frame_desc; |
| |
| /** @todo this stuff that hits the main config cache should really happen |
| * in start() so that only one thread hits these data. all of this stuff |
| * is going to be reopen_on_change anyway |
| */ |
| |
| frame_desc = uvc_find_frame_desc(strmh->devh, strmh->cur_ctrl.bFormatIndex, |
| strmh->cur_ctrl.bFrameIndex); |
| |
| frame->frame_format = strmh->frame_format; |
| |
| frame->width = frame_desc->wWidth; |
| frame->height = frame_desc->wHeight; |
| |
| switch (frame->frame_format) { |
| case UVC_FRAME_FORMAT_YUYV: |
| frame->step = frame->width * 2; |
| break; |
| case UVC_FRAME_FORMAT_MJPEG: |
| frame->step = 0; |
| break; |
| default: |
| frame->step = 0; |
| break; |
| } |
| |
| frame->sequence = strmh->hold_seq; |
| /** @todo set the frame time */ |
| // frame->capture_time |
| |
| /* copy the image data from the hold buffer to the frame (unnecessary extra buf?) */ |
| if (frame->data_bytes < strmh->hold_bytes) { |
| frame->data = realloc(frame->data, strmh->hold_bytes); |
| } |
| frame->data_bytes = strmh->hold_bytes; |
| memcpy(frame->data, strmh->holdbuf, frame->data_bytes); |
| |
| |
| |
| } |
| |
| /** Poll for a frame |
| * @ingroup streaming |
| * |
| * @param devh UVC device |
| * @param[out] frame Location to store pointer to captured frame (NULL on error) |
| * @param timeout_us >0: Wait at most N microseconds; 0: Wait indefinitely; -1: return immediately |
| */ |
| uvc_error_t uvc_stream_get_frame(uvc_stream_handle_t *strmh, |
| uvc_frame_t **frame, |
| int32_t timeout_us) { |
| time_t add_secs; |
| time_t add_nsecs; |
| struct timespec ts; |
| struct timeval tv; |
| |
| if (!strmh->running) |
| return UVC_ERROR_INVALID_PARAM; |
| |
| if (strmh->user_cb) |
| return UVC_ERROR_CALLBACK_EXISTS; |
| |
| pthread_mutex_lock(&strmh->cb_mutex); |
| |
| if (strmh->last_polled_seq < strmh->hold_seq) { |
| _uvc_populate_frame(strmh); |
| *frame = &strmh->frame; |
| strmh->last_polled_seq = strmh->hold_seq; |
| } else if (timeout_us != -1) { |
| if (timeout_us == 0) { |
| pthread_cond_wait(&strmh->cb_cond, &strmh->cb_mutex); |
| } else { |
| add_secs = timeout_us / 1000000; |
| add_nsecs = (timeout_us % 1000000) * 1000; |
| ts.tv_sec = 0; |
| ts.tv_nsec = 0; |
| |
| #if _POSIX_TIMERS > 0 |
| clock_gettime(CLOCK_REALTIME, &ts); |
| #else |
| gettimeofday(&tv, NULL); |
| ts.tv_sec = tv.tv_sec; |
| ts.tv_nsec = tv.tv_usec * 1000; |
| #endif |
| |
| ts.tv_sec += add_secs; |
| ts.tv_nsec += add_nsecs; |
| |
| /* pthread_cond_timedwait FAILS with EINVAL if ts.tv_nsec > 1000000000 (1 billion) |
| * Since we are just adding values to the timespec, we have to increment the seconds if nanoseconds is greater than 1 billion, |
| * and then re-adjust the nanoseconds in the correct range. |
| * */ |
| ts.tv_sec += ts.tv_nsec / 1000000000; |
| ts.tv_nsec = ts.tv_nsec % 1000000000; |
| |
| int err = pthread_cond_timedwait(&strmh->cb_cond, &strmh->cb_mutex, &ts); |
| |
| //TODO: How should we handle EINVAL? |
| switch(err){ |
| case EINVAL: |
| *frame = NULL; |
| return UVC_ERROR_OTHER; |
| case ETIMEDOUT: |
| *frame = NULL; |
| return UVC_ERROR_TIMEOUT; |
| } |
| } |
| |
| if (strmh->last_polled_seq < strmh->hold_seq) { |
| _uvc_populate_frame(strmh); |
| *frame = &strmh->frame; |
| strmh->last_polled_seq = strmh->hold_seq; |
| } else { |
| *frame = NULL; |
| } |
| } else { |
| *frame = NULL; |
| } |
| |
| pthread_mutex_unlock(&strmh->cb_mutex); |
| |
| return UVC_SUCCESS; |
| } |
| |
| /** @brief Stop streaming video |
| * @ingroup streaming |
| * |
| * Closes all streams, ends threads and cancels pollers |
| * |
| * @param devh UVC device |
| */ |
| void uvc_stop_streaming(uvc_device_handle_t *devh) { |
| uvc_stream_handle_t *strmh, *strmh_tmp; |
| |
| DL_FOREACH_SAFE(devh->streams, strmh, strmh_tmp) { |
| uvc_stream_close(strmh); |
| } |
| } |
| |
| /** @brief Stop stream. |
| * @ingroup streaming |
| * |
| * Stops stream, ends threads and cancels pollers |
| * |
| * @param devh UVC device |
| */ |
| uvc_error_t uvc_stream_stop(uvc_stream_handle_t *strmh) { |
| int i; |
| |
| if (!strmh->running) |
| return UVC_ERROR_INVALID_PARAM; |
| |
| strmh->running = 0; |
| |
| pthread_mutex_lock(&strmh->cb_mutex); |
| |
| for(i=0; i < LIBUVC_NUM_TRANSFER_BUFS; i++) { |
| if(strmh->transfers[i] != NULL) { |
| int res = libusb_cancel_transfer(strmh->transfers[i]); |
| if(res < 0 && res != LIBUSB_ERROR_NOT_FOUND ) { |
| free(strmh->transfers[i]->buffer); |
| libusb_free_transfer(strmh->transfers[i]); |
| strmh->transfers[i] = NULL; |
| } |
| } |
| } |
| |
| /* Wait for transfers to complete/cancel */ |
| do { |
| for(i=0; i < LIBUVC_NUM_TRANSFER_BUFS; i++) { |
| if(strmh->transfers[i] != NULL) |
| break; |
| } |
| if(i == LIBUVC_NUM_TRANSFER_BUFS ) |
| break; |
| pthread_cond_wait(&strmh->cb_cond, &strmh->cb_mutex); |
| } while(1); |
| // Kick the user thread awake |
| pthread_cond_broadcast(&strmh->cb_cond); |
| pthread_mutex_unlock(&strmh->cb_mutex); |
| |
| /** @todo stop the actual stream, camera side? */ |
| |
| if (strmh->user_cb) { |
| /* wait for the thread to stop (triggered by |
| * LIBUSB_TRANSFER_CANCELLED transfer) */ |
| pthread_join(strmh->cb_thread, NULL); |
| } |
| |
| return UVC_SUCCESS; |
| } |
| |
| /** @brief Close stream. |
| * @ingroup streaming |
| * |
| * Closes stream, frees handle and all streaming resources. |
| * |
| * @param strmh UVC stream handle |
| */ |
| void uvc_stream_close(uvc_stream_handle_t *strmh) { |
| if (strmh->running) |
| uvc_stream_stop(strmh); |
| |
| uvc_release_if(strmh->devh, strmh->stream_if->bInterfaceNumber); |
| |
| if (strmh->frame.data) |
| free(strmh->frame.data); |
| |
| free(strmh->outbuf); |
| free(strmh->holdbuf); |
| |
| pthread_cond_destroy(&strmh->cb_cond); |
| pthread_mutex_destroy(&strmh->cb_mutex); |
| |
| DL_DELETE(strmh->devh->streams, strmh); |
| free(strmh); |
| } |