X-Git-Url: http://47.100.26.94:8080/?a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjni%2Flibuvc-0.0.6%2Fsrc%2Fstream.c;fp=app%2Fsrc%2Fmain%2Fjni%2Flibuvc-0.0.6%2Fsrc%2Fstream.c;h=d30962866b6f1018247868e0a339acf5b80ac2dd;hb=577426ccc66649228285c4a0b6ba8752836059c6;hp=0000000000000000000000000000000000000000;hpb=b7202442677d0cf41e6e59870cd6c34e5619b8b9;p=rtmpclient.git diff --git a/app/src/main/jni/libuvc-0.0.6/src/stream.c b/app/src/main/jni/libuvc-0.0.6/src/stream.c new file mode 100644 index 0000000..d309628 --- /dev/null +++ b/app/src/main/jni/libuvc-0.0.6/src/stream.c @@ -0,0 +1,1288 @@ +/********************************************************************* +* 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); +}