stream pushing ok without access permission of /dev/video0
[rtmpclient.git] / app / src / main / jni / libuvc / src / stream.c
diff --git a/app/src/main/jni/libuvc/src/stream.c b/app/src/main/jni/libuvc/src/stream.c
new file mode 100644 (file)
index 0000000..74bb260
--- /dev/null
@@ -0,0 +1,1893 @@
+/*********************************************************************
+ *********************************************************************/
+/*********************************************************************
+ * modified some function to avoid crash, support Android
+ * Copyright (C) 2014-2016 saki@serenegiant All rights reserved.
+ *********************************************************************/
+/*********************************************************************
+ * 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
+ */
+
+#define LOCAL_DEBUG 0
+
+#define LOG_TAG "libuvc/stream"
+#if 1  // デバッグ情報を出さない時1
+       #ifndef LOG_NDEBUG
+               #define LOG_NDEBUG              // LOGV/LOGD/MARKを出力しない時
+               #endif
+       #undef USE_LOGALL                       // 指定したLOGxだけを出力
+#else
+       #define USE_LOGALL
+       #undef LOG_NDEBUG
+       #undef NDEBUG
+       #define GET_RAW_DESCRIPTOR
+#endif
+
+#include <assert.h>            // XXX add assert for debugging
+
+#include "libuvc/libuvc.h"
+#include "libuvc/libuvc_internal.h"
+
+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);
+static void *_uvc_user_caller(void *arg);
+static 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, ...) \
+    case _fmt: { \
+    static enum uvc_frame_format _fmt##_children[] = __VA_ARGS__; \
+    static struct format_table_entry _fmt##_entry = { \
+      _fmt, 0, {}, ARRAYSIZE(_fmt##_children), _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,
+               {UVC_FRAME_FORMAT_UNCOMPRESSED, UVC_FRAME_FORMAT_COMPRESSED})
+
+       ABS_FMT(UVC_FRAME_FORMAT_UNCOMPRESSED,
+               {UVC_FRAME_FORMAT_YUYV, UVC_FRAME_FORMAT_UYVY, UVC_FRAME_FORMAT_GRAY8})
+       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_BY8,
+       {'B', 'Y', '8', ' ', 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71})
+
+       ABS_FMT(UVC_FRAME_FORMAT_COMPRESSED,
+               {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 (UNLIKELY(!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[48];        // XXX support UVC 1.1 & 1.5
+       size_t len;
+       uvc_error_t err;
+
+       memset(buf, 0, sizeof(buf));    // bzero(buf, sizeof(buf));     // bzero is deprecated
+
+       const uint16_t bcdUVC = devh->info->ctrl_if.bcdUVC;
+       if (bcdUVC >= 0x0150)
+               len = 48;
+       else if (bcdUVC >= 0x0110)
+               len = 34;
+       else
+               len = 26;
+//     LOGI("bcdUVC:%x,req:0x%02x,probe:%d", bcdUVC, req, probe);
+       /* 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 > 26) { // len == 34
+                       // XXX add to support UVC 1.1
+                       INT_TO_DW(ctrl->dwClockFrequency, buf + 26);
+                       buf[30] = ctrl->bmFramingInfo;
+                       buf[31] = ctrl->bPreferedVersion;
+                       buf[32] = ctrl->bMinVersion;
+                       buf[33] = ctrl->bMaxVersion;
+                       if (len == 48) {
+                               // XXX add to support UVC1.5
+                               buf[34] = ctrl->bUsage;
+                               buf[35] = ctrl->bBitDepthLuma;
+                               buf[36] = ctrl->bmSettings;
+                               buf[37] = ctrl->bMaxNumberOfRefFramesPlus1;
+                               SHORT_TO_SW(ctrl->bmRateControlModes, buf + 38);
+                               LONG_TO_QW(ctrl->bmLayoutPerStream, buf + 40);
+                       }
+               }
+       }
+
+       /* 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 (UNLIKELY(err <= 0)) {
+               // when libusb_control_transfer returned error or transfer bytes was zero.
+               if (!err) {
+                       UVC_DEBUG("libusb_control_transfer transfered zero length data");
+                       err = UVC_ERROR_OTHER;
+               }
+               return err;
+       }
+       if (err < len) {
+#if !defined(__LP64__)
+               LOGE("transfered bytes is smaller than data bytes:%d expected %d", err, len);
+#else
+               LOGE("transfered bytes is smaller than data bytes:%d expected %ld", err, len);
+#endif
+               return UVC_ERROR_OTHER;
+       }
+       /* 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 > 26) { // len == 34
+                       // XXX add to support UVC 1.1
+                       ctrl->dwClockFrequency = DW_TO_INT(buf + 26);
+                       ctrl->bmFramingInfo = buf[30];
+                       ctrl->bPreferedVersion = buf[31];
+                       ctrl->bMinVersion = buf[32];
+                       ctrl->bMaxVersion = buf[33];
+                       if (len >= 48) {
+                               // XXX add to support UVC1.5
+                               ctrl->bUsage = buf[34];
+                               ctrl->bBitDepthLuma = buf[35];
+                               ctrl->bmSettings = buf[36];
+                               ctrl->bMaxNumberOfRefFramesPlus1 = buf[37];
+                               ctrl->bmRateControlModes = SW_TO_SHORT(buf + 38);
+                               ctrl->bmLayoutPerStream = QW_TO_LONG(buf + 40);
+                       }
+               }
+
+               /* fix up block for cameras that fail to set dwMax */
+               if (!ctrl->dwMaxVideoFrameSize) {
+                       LOGW("fix up block for cameras that fail to set dwMax");
+                       uvc_frame_desc_t *frame_desc = uvc_find_frame_desc(devh,
+                                       ctrl->bFormatIndex, ctrl->bFrameIndex);
+
+                       if (frame_desc) {
+                               ctrl->dwMaxVideoFrameSize = frame_desc->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 (UNLIKELY(strmh->stream_if->bInterfaceNumber != ctrl->bInterfaceNumber))
+               return UVC_ERROR_INVALID_PARAM;
+
+       /* @todo Allow the stream to be modified without restarting the stream */
+       if (UNLIKELY(strmh->running))
+               return UVC_ERROR_BUSY;
+
+       ret = uvc_query_stream_ctrl(strmh->devh, ctrl, 0, UVC_SET_CUR); // commit query
+       if (UNLIKELY(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_error_t uvc_get_frame_desc(uvc_device_handle_t *devh,
+               uvc_stream_ctrl_t *ctrl, uvc_frame_desc_t **desc) {
+
+       *desc = uvc_find_frame_desc(devh, ctrl->bFormatIndex, ctrl->bFrameIndex);
+       return *desc ? UVC_SUCCESS : UVC_ERROR_INVALID_PARAM;
+}
+
+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;
+}
+
+static void _uvc_print_streaming_interface_one(uvc_streaming_interface_t *stream_if) {
+//     struct uvc_device_info *parent;
+//     struct uvc_streaming_interface *prev, *next;
+       MARK("bInterfaceNumber:%d", stream_if->bInterfaceNumber);
+       uvc_print_format_desc_one(stream_if->format_descs, NULL);
+       MARK("bEndpointAddress:%d", stream_if->bEndpointAddress);
+       MARK("bTerminalLink:%d", stream_if->bTerminalLink);
+}
+
+static uvc_error_t _prepare_stream_ctrl(uvc_device_handle_t *devh, uvc_stream_ctrl_t *ctrl) {
+       // XXX some camera may need to call uvc_query_stream_ctrl with UVC_GET_CUR/UVC_GET_MAX/UVC_GET_MIN
+       // before negotiation otherwise stream stall. added by saki
+       uvc_error_t result = uvc_query_stream_ctrl(devh, ctrl, 1, UVC_GET_CUR); // probe query
+       if (LIKELY(!result)) {
+               result = uvc_query_stream_ctrl(devh, ctrl, 1, UVC_GET_MIN);                     // probe query
+               if (LIKELY(!result)) {
+                       result = uvc_query_stream_ctrl(devh, ctrl, 1, UVC_GET_MAX);             // probe query
+                       if (UNLIKELY(result))
+                               LOGE("uvc_query_stream_ctrl:UVC_GET_MAX:err=%d", result);       // XXX 最大値の方を後で取得しないとだめ
+               } else {
+                       LOGE("uvc_query_stream_ctrl:UVC_GET_MIN:err=%d", result);
+               }
+       } else {
+               LOGE("uvc_query_stream_ctrl:UVC_GET_CUR:err=%d", result);
+       }
+#if 0
+       if (UNLIKELY(result)) {
+               enum uvc_error_code_control error_code;
+               uvc_get_error_code(devh, &error_code, UVC_GET_CUR);
+               LOGE("uvc_query_stream_ctrl:ret=%d,err_code=%d", result, error_code);
+               uvc_print_format_desc(devh->info->stream_ifs->format_descs, NULL);
+       }
+#endif
+       return result;
+}
+
+static uvc_error_t _uvc_get_stream_ctrl_format(uvc_device_handle_t *devh,
+       uvc_streaming_interface_t *stream_if, uvc_stream_ctrl_t *ctrl, uvc_format_desc_t *format,
+       const int width, const int height,
+       const int min_fps, const int max_fps) {
+
+       ENTER();
+
+       int i;
+       uvc_frame_desc_t *frame;
+
+       ctrl->bInterfaceNumber = stream_if->bInterfaceNumber;
+       uvc_error_t result = uvc_claim_if(devh, ctrl->bInterfaceNumber);
+       if (UNLIKELY(result)) {
+               LOGE("uvc_claim_if:err=%d", result);
+               goto fail;
+       }
+       for (i = 0; i < 2; i++) {
+               result = _prepare_stream_ctrl(devh, ctrl);
+       }
+       if (UNLIKELY(result)) {
+               LOGE("_prepare_stream_ctrl:err=%d", result);
+               goto fail;
+       }
+#if 0
+       // XXX add check ctrl values
+       uint64_t bmaControl = stream_if->bmaControls[format->bFormatIndex - 1];
+       if (bmaControl & 0x001) {       // wKeyFrameRate
+               if (UNLIKELY(!ctrl->wKeyFrameRate)) {
+                       LOGE("wKeyFrameRate should be set");
+                       RETURN(UVC_ERROR_INVALID_MODE, uvc_error_t);
+               }
+       }
+       if (bmaControl & 0x002) {       // wPFrameRate
+               if (UNLIKELY(!ctrl->wPFrameRate)) {
+                       LOGE("wPFrameRate should be set");
+                       RETURN(UVC_ERROR_INVALID_MODE, uvc_error_t);
+               }
+       }
+       if (bmaControl & 0x004) {       // wCompQuality
+               if (UNLIKELY(!ctrl->wCompQuality)) {
+                       LOGE("wCompQuality should be set");
+                       RETURN(UVC_ERROR_INVALID_MODE, uvc_error_t);
+               }
+       }
+       if (bmaControl & 0x008) {       // wCompWindowSize
+               if (UNLIKELY(!ctrl->wCompWindowSize)) {
+                       LOGE("wCompWindowSize should be set");
+                       RETURN(UVC_ERROR_INVALID_MODE, uvc_error_t);
+               }
+       }
+#endif
+       DL_FOREACH(format->frame_descs, frame)
+       {
+               if (frame->wWidth != width || frame->wHeight != height)
+                       continue;
+
+               uint32_t *interval;
+
+               if (frame->intervals) {
+                       for (interval = frame->intervals; *interval; ++interval) {
+                               if (UNLIKELY(!(*interval))) continue;
+                               uint32_t it = 10000000 / *interval;
+                               LOGV("it:%d", it);
+                               if ((it >= (uint32_t) min_fps) && (it <= (uint32_t) max_fps)) {
+                                       ctrl->bmHint = (1 << 0); /* don't negotiate interval */
+                                       ctrl->bFormatIndex = format->bFormatIndex;
+                                       ctrl->bFrameIndex = frame->bFrameIndex;
+                                       ctrl->dwFrameInterval = *interval;
+
+                                       goto found;
+                               }
+                       }
+               } else {
+                       int32_t fps;
+                       for (fps = max_fps; fps >= min_fps; fps--) {
+                               if (UNLIKELY(!fps)) continue;
+                               uint32_t interval_100ns = 10000000 / fps;
+                               uint32_t interval_offset = interval_100ns - frame->dwMinFrameInterval;
+                               LOGV("fps:%d", fps);
+                               if (interval_100ns >= frame->dwMinFrameInterval
+                                       && interval_100ns <= frame->dwMaxFrameInterval
+                                       && !(interval_offset
+                                               && (interval_offset % frame->dwFrameIntervalStep) ) ) {
+                                       ctrl->bmHint = (1 << 0); /* don't negotiate interval */
+                                       ctrl->bFormatIndex = format->bFormatIndex;
+                                       ctrl->bFrameIndex = frame->bFrameIndex;
+                                       ctrl->dwFrameInterval = interval_100ns;
+
+                                       goto found;
+                               }
+                       }
+               }
+       }
+       result = UVC_ERROR_INVALID_MODE;
+fail:
+       uvc_release_if(devh, ctrl->bInterfaceNumber);
+       RETURN(result, uvc_error_t);
+
+found:
+       RETURN(UVC_SUCCESS, uvc_error_t);
+}
+
+/** 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] cf 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) {
+
+       return uvc_get_stream_ctrl_format_size_fps(devh, ctrl, cf, width, height, fps, fps);
+}
+
+/** 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] cf Type of streaming format
+ * @param[in] width Desired frame width
+ * @param[in] height Desired frame height
+ * @param[in] min_fps Frame rate, minimum frames per second, this value is included
+ * @param[in] max_fps Frame rate, maximum frames per second, this value is included
+ */
+uvc_error_t uvc_get_stream_ctrl_format_size_fps(uvc_device_handle_t *devh,
+               uvc_stream_ctrl_t *ctrl, enum uvc_frame_format cf, int width,
+               int height, int min_fps, int max_fps) {
+
+       ENTER();
+
+       uvc_streaming_interface_t *stream_if;
+       uvc_error_t result;
+
+       memset(ctrl, 0, sizeof(*ctrl)); // XXX add
+       /* find a matching frame descriptor and interval */
+       uvc_format_desc_t *format;
+       DL_FOREACH(devh->info->stream_ifs, stream_if)
+       {
+               DL_FOREACH(stream_if->format_descs, format)
+               {
+                       if (!_uvc_frame_format_matches_guid(cf, format->guidFormat))
+                               continue;
+
+                       result = _uvc_get_stream_ctrl_format(devh, stream_if, ctrl, format, width, height, min_fps, max_fps);
+                       if (!result) {  // UVC_SUCCESS
+                               goto found;
+                       }
+               }
+       }
+
+       RETURN(UVC_ERROR_INVALID_MODE, uvc_error_t);
+
+found:
+       RETURN(uvc_probe_stream_ctrl(devh, ctrl), uvc_error_t);
+}
+
+/** @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_error_t err;
+
+       err = uvc_claim_if(devh, ctrl->bInterfaceNumber);
+       if (UNLIKELY(err)) {
+               LOGE("uvc_claim_if:err=%d", err);
+               return err;
+       }
+
+       err = uvc_query_stream_ctrl(devh, ctrl, 1, UVC_SET_CUR);        // probe query
+       if (UNLIKELY(err)) {
+               LOGE("uvc_query_stream_ctrl(UVC_SET_CUR):err=%d", err);
+               return err;
+       }
+
+       err = uvc_query_stream_ctrl(devh, ctrl, 1, UVC_GET_CUR);        // probe query ここでエラーが返ってくる
+       if (UNLIKELY(err)) {
+               LOGE("uvc_query_stream_ctrl(UVC_GET_CUR):err=%d", err);
+               return err;
+       }
+
+       return UVC_SUCCESS;
+}
+
+/** @internal
+ * @brief Swap the working buffer with the presented buffer and notify consumers
+ */
+static 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_bfh_err = strmh->bfh_err;   // XXX
+               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;
+       strmh->bfh_err = 0;     // XXX
+}
+
+static void _uvc_delete_transfer(struct libusb_transfer *transfer) {
+       ENTER();
+
+//     MARK("");
+       uvc_stream_handle_t *strmh = transfer->user_data;
+       if (UNLIKELY(!strmh)) EXIT();           // XXX
+       int i;
+
+       pthread_mutex_lock(&strmh->cb_mutex);   // XXX crash while calling uvc_stop_streaming
+       {
+               // Mark transfer as deleted.
+               for (i = 0; i < LIBUVC_NUM_TRANSFER_BUFS; i++) {
+                       if (strmh->transfers[i] == transfer) {
+                               libusb_cancel_transfer(strmh->transfers[i]);    // XXX 20141112追加
+                               UVC_DEBUG("Freeing transfer %d (%p)", i, transfer);
+                               free(transfer->buffer);
+                               libusb_free_transfer(transfer);
+                               strmh->transfers[i] = NULL;
+                               break;
+                       }
+               }
+               if (UNLIKELY(i == LIBUVC_NUM_TRANSFER_BUFS)) {
+                       UVC_DEBUG("transfer %p not found; not freeing!", transfer);
+               }
+
+               pthread_cond_broadcast(&strmh->cb_cond);
+       }
+       pthread_mutex_unlock(&strmh->cb_mutex);
+       EXIT();
+}
+
+#define USE_EOF
+
+/** @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
+ */
+static void _uvc_process_payload(uvc_stream_handle_t *strmh, const uint8_t *payload, size_t const payload_len) {
+       size_t header_len;
+       uint8_t header_info;
+       size_t data_len;
+       struct libusb_iso_packet_descriptor *pkt;
+       uvc_vc_error_code_control_t vc_error_code;
+       uvc_vs_error_code_control_t vs_error_code;
+
+       // magic numbers for identifying header packets from some iSight cameras
+       static const uint8_t isight_tag[] = {
+               0x11, 0x22, 0x33, 0x44,
+               0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xfa, 0xce
+       };
+
+       // ignore empty payload transfers
+       if (UNLIKELY(!payload || !payload_len || !strmh->outbuf))
+               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 (UNLIKELY(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 (UNLIKELY(header_len > payload_len)) {
+                       strmh->bfh_err |= UVC_STREAM_ERR;
+                       UVC_DEBUG("bogus packet: actual_len=%zd, header_len=%zd\n", payload_len, header_len);
+                       return;
+               }
+
+               if (UNLIKELY(strmh->devh->is_isight))
+                       data_len = 0;
+               else
+                       data_len = payload_len - header_len;
+       }
+
+       if (UNLIKELY(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 (UNLIKELY(header_info & UVC_STREAM_ERR)) {
+//                     strmh->bfh_err |= UVC_STREAM_ERR;
+                       UVC_DEBUG("bad packet: error bit set");
+                       libusb_clear_halt(strmh->devh->usb_devh, strmh->stream_if->bEndpointAddress);
+//                     uvc_vc_get_error_code(strmh->devh, &vc_error_code, UVC_GET_CUR);
+                       uvc_vs_get_error_code(strmh->devh, &vs_error_code, UVC_GET_CUR);
+//                     return;
+               }
+
+               if ((strmh->fid != (header_info & UVC_STREAM_FID)) && strmh->got_bytes) {
+                       /* 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 & UVC_STREAM_FID;
+
+               if (header_info & UVC_STREAM_PTS) {
+                       // XXX saki some camera may send broken packet or failed to receive all data
+                       if (LIKELY(variable_offset + 4 <= header_len)) {
+                               strmh->pts = DW_TO_INT(payload + variable_offset);
+                               variable_offset += 4;
+                       } else {
+                               MARK("bogus packet: header info has UVC_STREAM_PTS, but no data");
+                               strmh->pts = 0;
+                       }
+               }
+
+               if (header_info & UVC_STREAM_SCR) {
+                       // @todo read the SOF token counter
+                       // XXX saki some camera may send broken packet or failed to receive all data
+                       if (LIKELY(variable_offset + 4 <= header_len)) {
+                               strmh->last_scr = DW_TO_INT(payload + variable_offset);
+                               variable_offset += 4;
+                       } else {
+                               MARK("bogus packet: header info has UVC_STREAM_SCR, but no data");
+                               strmh->last_scr = 0;
+                       }
+               }
+       }
+
+       if (LIKELY(data_len > 0)) {
+               if (LIKELY(strmh->got_bytes + data_len < strmh->size_buf)) {
+                       memcpy(strmh->outbuf + strmh->got_bytes, payload + header_len, data_len);
+                       strmh->got_bytes += data_len;
+               } else {
+                       strmh->bfh_err |= UVC_STREAM_ERR;
+               }
+
+               if (header_info & UVC_STREAM_EOF/*(1 << 1)*/) {
+                       // The EOF bit is set, so publish the complete frame
+                       _uvc_swap_buffers(strmh);
+               }
+       }
+}
+
+#if 0
+static inline void _uvc_process_payload_iso(uvc_stream_handle_t *strmh, struct libusb_transfer *transfer) {
+       /* 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++) {
+               struct libusb_iso_packet_descriptor *pkt = transfer->iso_packet_desc + packet_id;
+
+               if UNLIKELY(pkt->status) {
+//                     UVC_DEBUG("bad packet:status=%d,actual_length=%d", pkt->status, pkt->actual_length);
+                       MARK("bad packet:status=%d,actual_length=%d", pkt->status, pkt->actual_length);
+                       continue;
+               }
+               if UNLIKELY(!pkt->actual_length) {
+                       MARK("zero packet (transfer):");
+                       continue;
+               }
+               // libusb_get_iso_packet_buffer_simple will return NULL
+               uint8_t *pktbuf = libusb_get_iso_packet_buffer_simple(transfer, packet_id);
+               _uvc_process_payload(strmh, pktbuf, pkt->actual_length);
+       }
+}
+#else
+static inline void _uvc_process_payload_iso(uvc_stream_handle_t *strmh, struct libusb_transfer *transfer) {
+       /* per packet */
+       uint8_t *pktbuf;
+       uint8_t check_header;
+       size_t header_len;
+       uint8_t header_info;
+       struct libusb_iso_packet_descriptor *pkt;
+
+       /* magic numbers for identifying header packets from some iSight cameras */
+       static const uint8_t isight_tag[] = {
+               0x11, 0x22, 0x33, 0x44, 0xde, 0xad,
+               0xbe, 0xef, 0xde, 0xad, 0xfa, 0xce };
+       int packet_id;
+       uvc_vc_error_code_control_t vc_error_code;
+       uvc_vs_error_code_control_t vs_error_code;
+
+       for (packet_id = 0; packet_id < transfer->num_iso_packets; ++packet_id) {
+               check_header = 1;
+
+               pkt = transfer->iso_packet_desc + packet_id;
+
+               if (UNLIKELY(pkt->status != 0)) {
+                       MARK("bad packet:status=%d,actual_length=%d", pkt->status, pkt->actual_length);
+                       strmh->bfh_err |= UVC_STREAM_ERR;
+                       libusb_clear_halt(strmh->devh->usb_devh, strmh->stream_if->bEndpointAddress);
+//                     uvc_vc_get_error_code(strmh->devh, &vc_error_code, UVC_GET_CUR);
+//                     uvc_vs_get_error_code(strmh->devh, &vs_error_code, UVC_GET_CUR);
+                       continue;
+               }
+
+               if (UNLIKELY(!pkt->actual_length)) {    // why transfered byte is zero...
+//                     MARK("zero packet (transfer):");
+//                     strmh->bfh_err |= UVC_STREAM_ERR;       // don't set this flag here
+                       continue;
+               }
+               // XXX accessing to pktbuf could lead to crash on the original implementation
+               // because the substances of pktbuf will be deleted in uvc_stream_stop.
+               pktbuf = libusb_get_iso_packet_buffer_simple(transfer, packet_id);
+               if (LIKELY(pktbuf)) {   // XXX add null check because libusb_get_iso_packet_buffer_simple could return null
+//                     assert(pktbuf < transfer->buffer + transfer->length - 1);       // XXX
+#ifdef __ANDROID__
+                       // XXX optimaization because this flag never become true on Android devices
+                       if (UNLIKELY(strmh->devh->is_isight))
+#else
+                       if (strmh->devh->is_isight)
+#endif
+                       {
+                               if (pkt->actual_length < 30
+                                       || (memcmp(isight_tag, pktbuf + 2, sizeof(isight_tag))
+                                               && memcmp(isight_tag, pktbuf + 3, sizeof(isight_tag)))) {
+                                       check_header = 0;
+                                       header_len = 0;
+                               } else {
+                                       header_len = pktbuf[0];
+                               }
+                       } else {
+                               header_len = pktbuf[0]; // Header length field of Stream Header
+                       }
+
+                       if (LIKELY(check_header)) {
+                               header_info = pktbuf[1];
+                               if (UNLIKELY(header_info & UVC_STREAM_ERR)) {
+//                                     strmh->bfh_err |= UVC_STREAM_ERR;
+                                       MARK("bad packet:status=0x%2x", header_info);
+                                       libusb_clear_halt(strmh->devh->usb_devh, strmh->stream_if->bEndpointAddress);
+//                                     uvc_vc_get_error_code(strmh->devh, &vc_error_code, UVC_GET_CUR);
+                                       uvc_vs_get_error_code(strmh->devh, &vs_error_code, UVC_GET_CUR);
+                                       continue;
+                               }
+#ifdef USE_EOF
+                               if ((strmh->fid != (header_info & UVC_STREAM_FID)) && strmh->got_bytes) {       // got_bytesを取ると殆ど画面更新されない
+                               /* 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 or some frames losted. */
+                                       _uvc_swap_buffers(strmh);
+                               }
+                               strmh->fid = header_info & UVC_STREAM_FID;
+#else
+                               if (strmh->fid != (header_info & UVC_STREAM_FID)) {     // when FID is toggled
+                                       _uvc_swap_buffers(strmh);
+                                       strmh->fid = header_info & UVC_STREAM_FID;
+                               }
+#endif
+                               if (header_info & UVC_STREAM_PTS) {
+                                       // XXX saki some camera may send broken packet or failed to receive all data
+                                       if (LIKELY(header_len >= 6)) {
+                                               strmh->pts = DW_TO_INT(pktbuf + 2);
+                                       } else {
+                                               MARK("bogus packet: header info has UVC_STREAM_PTS, but no data");
+                                               strmh->pts = 0;
+                                       }
+                               }
+
+                               if (header_info & UVC_STREAM_SCR) {
+                                       // XXX saki some camera may send broken packet or failed to receive all data
+                                       if (LIKELY(header_len >= 10)) {
+                                               strmh->last_scr = DW_TO_INT(pktbuf + 6);
+                                       } else {
+                                               MARK("bogus packet: header info has UVC_STREAM_SCR, but no data");
+                                               strmh->last_scr = 0;
+                                       }
+                               }
+
+#ifdef __ANDROID__     // XXX optimaization because this flag never become true on Android devices
+                               if (UNLIKELY(strmh->devh->is_isight))
+                                       continue; // don't look for data after an iSight header
+#else
+                               if (strmh->devh->is_isight) {
+                                       MARK("is_isight");
+                                       continue; // don't look for data after an iSight header
+                               }
+#endif
+                       } // if LIKELY(check_header)
+
+                       if (UNLIKELY(pkt->actual_length < header_len)) {
+                               /* Bogus packet received */
+                               strmh->bfh_err |= UVC_STREAM_ERR;
+                               MARK("bogus packet: actual_len=%d, header_len=%zd", pkt->actual_length, header_len);
+                               continue;
+                       }
+
+                       // XXX original implementation could lead to trouble because unsigned values
+                       // and there calculated value never become minus.
+                       // therefor changed to "if (pkt->actual_length > header_len)"
+                       // from "if (pkt->actual_length - header_len > 0)"
+                       if (LIKELY(pkt->actual_length > header_len)) {
+                               const size_t odd_bytes = pkt->actual_length - header_len;
+                               assert(strmh->got_bytes + odd_bytes < strmh->size_buf);
+                               assert(strmh->outbuf);
+                               assert(pktbuf);
+                               memcpy(strmh->outbuf + strmh->got_bytes, pktbuf + header_len, odd_bytes);
+                               strmh->got_bytes += odd_bytes;
+                       }
+#ifdef USE_EOF
+                       if ((pktbuf[1] & UVC_STREAM_EOF) && strmh->got_bytes != 0) {
+                               /* The EOF bit is set, so publish the complete frame */
+                               _uvc_swap_buffers(strmh);
+                       }
+#endif
+               } else {        // if (LIKELY(pktbuf))
+                       strmh->bfh_err |= UVC_STREAM_ERR;
+                       MARK("libusb_get_iso_packet_buffer_simple returned null");
+                       continue;
+               }
+       }       // for
+}
+#endif
+
+/** @internal
+ * @brief Isochronous 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
+ */
+static void _uvc_stream_callback(struct libusb_transfer *transfer) {
+       if UNLIKELY(!transfer) return;
+
+       uvc_stream_handle_t *strmh = transfer->user_data;
+       if UNLIKELY(!strmh) return;
+
+       int resubmit = 1;
+
+#ifndef NDEBUG
+       static int cnt = 0;
+       if UNLIKELY((++cnt % 1000) == 0)
+               MARK("cnt=%d", cnt);
+#endif
+       switch (transfer->status) {
+       case LIBUSB_TRANSFER_COMPLETED:
+               if (!transfer->num_iso_packets) {
+                       /* 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 */
+                       _uvc_process_payload_iso(strmh, transfer);
+               }
+           break;
+       case LIBUSB_TRANSFER_NO_DEVICE:
+               strmh->running = 0;     // this needs for unexpected disconnect of cable otherwise hangup
+               // pass through to following lines
+       case LIBUSB_TRANSFER_CANCELLED:
+       case LIBUSB_TRANSFER_ERROR:
+               UVC_DEBUG("not retrying transfer, status = %d", transfer->status);
+//             MARK("not retrying transfer, status = %d", transfer->status);
+//             _uvc_delete_transfer(transfer);
+               resubmit = 0;
+               break;
+       case LIBUSB_TRANSFER_TIMED_OUT:
+       case LIBUSB_TRANSFER_STALL:
+       case LIBUSB_TRANSFER_OVERFLOW:
+               UVC_DEBUG("retrying transfer, status = %d", transfer->status);
+//             MARK("retrying transfer, status = %d", transfer->status);
+               break;
+       }
+
+       if (LIKELY(strmh->running && resubmit)) {
+               libusb_submit_transfer(transfer);
+       } else {
+               // XXX delete non-reusing transfer
+               // real implementation of deleting transfer moves to _uvc_delete_transfer
+               _uvc_delete_transfer(transfer);
+       }
+}
+
+#if 0
+/** @internal
+ * @brief Isochronous 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
+ */
+static void _uvc_iso_callback(struct libusb_transfer *transfer) {
+       uvc_stream_handle_t *strmh;
+       int packet_id;
+
+       /* per packet */
+       uint8_t *pktbuf;
+       uint8_t check_header;
+       size_t header_len;      // XXX unsigned int header_len
+       uint8_t header_info;
+       struct libusb_iso_packet_descriptor *pkt;
+
+       /* magic numbers for identifying header packets from some iSight cameras */
+       static const uint8_t isight_tag[] = {
+               0x11, 0x22, 0x33, 0x44, 0xde, 0xad,
+               0xbe, 0xef, 0xde, 0xad, 0xfa, 0xce };
+
+       strmh = transfer->user_data;
+#ifndef NDEBUG
+       static int cnt = 0;
+       if ((++cnt % 1000) == 0)
+               MARK("cnt=%d", cnt);
+#endif
+       switch (transfer->status) {
+       case LIBUSB_TRANSFER_COMPLETED:
+               if (UNLIKELY(!transfer->num_iso_packets))
+                       MARK("num_iso_packets is zero");
+               for (packet_id = 0; packet_id < transfer->num_iso_packets; ++packet_id) {
+                       check_header = 1;
+
+                       pkt = transfer->iso_packet_desc + packet_id;
+
+                       if (UNLIKELY(pkt->status != 0)) {
+                               MARK("bad packet:status=%d,actual_length=%d", pkt->status, pkt->actual_length);
+                               strmh->bfh_err |= UVC_STREAM_ERR;
+                               continue;
+                       }
+
+                       if (UNLIKELY(!pkt->actual_length)) {    // why transfered byte is zero...
+//                             MARK("zero packet (transfer):");
+//                             strmh->bfh_err |= UVC_STREAM_ERR;       // don't set this flag here
+                               continue;
+                       }
+                       // XXX accessing to pktbuf could lead to crash on the original implementation
+                       // because the substances of pktbuf will be deleted in uvc_stream_stop.
+                       pktbuf = libusb_get_iso_packet_buffer_simple(transfer, packet_id);
+                       if (LIKELY(pktbuf)) {   // XXX add null check because libusb_get_iso_packet_buffer_simple could return null
+//                             assert(pktbuf < transfer->buffer + transfer->length - 1);       // XXX
+#ifdef __ANDROID__
+                               // XXX optimaization because this flag never become true on Android devices
+                               if (UNLIKELY(strmh->devh->is_isight))
+#else
+                               if (strmh->devh->is_isight)
+#endif
+                               {
+                                       if (pkt->actual_length < 30
+                                               || (memcmp(isight_tag, pktbuf + 2, sizeof(isight_tag))
+                                                       && memcmp(isight_tag, pktbuf + 3, sizeof(isight_tag)))) {
+                                               check_header = 0;
+                                               header_len = 0;
+                                       } else {
+                                               header_len = pktbuf[0];
+                                       }
+                               } else {
+                                       header_len = pktbuf[0]; // Header length field of Stream Header
+                               }
+
+                               if (LIKELY(check_header)) {
+                                       header_info = pktbuf[1];
+                                       if (UNLIKELY(header_info & UVC_STREAM_ERR)) {
+                                               strmh->bfh_err |= UVC_STREAM_ERR;
+                                               MARK("bad packet");
+//                                             libusb_clear_halt(strmh->devh->usb_devh, strmh->stream_if->bEndpointAddress);
+                                               uvc_vc_get_error_code(strmh->devh, &vc_error_code, UVC_GET_CUR);
+                                               uvc_vs_get_error_code(strmh->devh, &vs_error_code, UVC_GET_CUR);
+                                               continue;
+                                       }
+#ifdef USE_EOF
+                                       if ((strmh->fid != (header_info & UVC_STREAM_FID)) && strmh->got_bytes) {       // got_bytesを取ると殆ど画面更新されない
+                                       /* 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 or some frames losted. */
+                                               _uvc_swap_buffers(strmh);
+                                       }
+                                       strmh->fid = header_info & UVC_STREAM_FID;
+#else
+                                       if (strmh->fid != (header_info & UVC_STREAM_FID)) {     // when FID is toggled
+                                               _uvc_swap_buffers(strmh);
+                                               strmh->fid = header_info & UVC_STREAM_FID;
+                                       }
+#endif
+                                       if (header_info & UVC_STREAM_PTS) {
+                                               // XXX saki some camera may send broken packet or failed to receive all data
+                                               if (LIKELY(header_len >= 6)) {
+                                                       strmh->pts = DW_TO_INT(pktbuf + 2);
+                                               } else {
+                                                       MARK("bogus packet: header info has UVC_STREAM_PTS, but no data");
+                                                       strmh->pts = 0;
+                                               }
+                                       }
+
+                                       if (header_info & UVC_STREAM_SCR) {
+                                               // XXX saki some camera may send broken packet or failed to receive all data
+                                               if (LIKELY(header_len >= 10)) {
+                                                       strmh->last_scr = DW_TO_INT(pktbuf + 6);
+                                               } else {
+                                                       MARK("bogus packet: header info has UVC_STREAM_SCR, but no data");
+                                                       strmh->last_scr = 0;
+                                               }
+                                       }
+
+#ifdef __ANDROID__     // XXX optimaization because this flag never become true on Android devices
+                                       if (UNLIKELY(strmh->devh->is_isight))
+                                               continue; // don't look for data after an iSight header
+#else
+                                       if (strmh->devh->is_isight) {
+                                               MARK("is_isight");
+                                               continue; // don't look for data after an iSight header
+                                       }
+#endif
+                               } // if LIKELY(check_header)
+
+                               if (UNLIKELY(pkt->actual_length < header_len)) {
+                                       /* Bogus packet received */
+                                       strmh->bfh_err |= UVC_STREAM_ERR;
+                                       MARK("bogus packet: actual_len=%d, header_len=%zd", pkt->actual_length, header_len);
+                                       continue;
+                               }
+
+                               // XXX original implementation could lead to trouble because unsigned values
+                               // and there calculated value never become minus.
+                               // therefor changed to "if (pkt->actual_length > header_len)"
+                               // from "if (pkt->actual_length - header_len > 0)"
+                               if (LIKELY(pkt->actual_length > header_len)) {
+                                       const size_t odd_bytes = pkt->actual_length - header_len;
+                                       assert(strmh->got_bytes + odd_bytes < strmh->size_buf);
+                                       assert(strmh->outbuf);
+                                       assert(pktbuf);
+                                       memcpy(strmh->outbuf + strmh->got_bytes, pktbuf + header_len, odd_bytes);
+                                       strmh->got_bytes += odd_bytes;
+                               }
+#ifdef USE_EOF
+                               if ((pktbuf[1] & STREAM_HEADER_BFH_EOF) && strmh->got_bytes != 0) {
+                                       /* The EOF bit is set, so publish the complete frame */
+                                       _uvc_swap_buffers(strmh);
+                               }
+#endif
+                       } else {        // if (LIKELY(pktbuf))
+                               strmh->bfh_err |= UVC_STREAM_ERR;
+                               MARK("libusb_get_iso_packet_buffer_simple returned null");
+                               continue;
+                       }
+               }       // for
+               break;
+       case LIBUSB_TRANSFER_NO_DEVICE:
+               strmh->running = 0;     // this needs for unexpected disconnect of cable otherwise hangup
+       case LIBUSB_TRANSFER_CANCELLED:
+       case LIBUSB_TRANSFER_ERROR:
+               UVC_DEBUG("not retrying transfer, status = %d", transfer->status);
+//             MARK("not retrying transfer, status = %d", transfer->status);
+               _uvc_delete_transfer(transfer);
+               break;
+       case LIBUSB_TRANSFER_TIMED_OUT:
+       case LIBUSB_TRANSFER_STALL:
+       case LIBUSB_TRANSFER_OVERFLOW:
+               UVC_DEBUG("retrying transfer, status = %d", transfer->status);
+//             MARK("retrying transfer, status = %d", transfer->status);
+               break;
+       }
+
+       if (LIKELY(strmh->running)) {
+               libusb_submit_transfer(transfer);
+       } else {
+               // XXX delete non-reusing transfer
+               // real implementation of deleting transfer moves to _uvc_delete_transfer
+               _uvc_delete_transfer(transfer);
+       }
+}
+#endif
+
+/** 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) {
+       return uvc_start_streaming_bandwidth(devh, ctrl, cb, user_ptr, 0, flags);
+}
+
+/** 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 bandwidth_factor [0.0f, 1.0f]
+ * @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_bandwidth(uvc_device_handle_t *devh,
+               uvc_stream_ctrl_t *ctrl, uvc_frame_callback_t *cb, void *user_ptr,
+               float bandwidth_factor,
+               uint8_t flags) {
+       uvc_error_t ret;
+       uvc_stream_handle_t *strmh;
+
+       ret = uvc_stream_open_ctrl(devh, &strmh, ctrl);
+       if (UNLIKELY(ret != UVC_SUCCESS))
+               return ret;
+
+       ret = uvc_stream_start_bandwidth(strmh, cb, user_ptr, bandwidth_factor, flags);
+       if (UNLIKELY(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_bandwidth(devh, ctrl, cb, user_ptr, 0.0f, 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 (UNLIKELY(_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 (UNLIKELY(!stream_if)) {
+               ret = UVC_ERROR_INVALID_PARAM;
+               goto fail;
+       }
+
+       strmh = calloc(1, sizeof(*strmh));
+       if (UNLIKELY(!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 (UNLIKELY(ret != UVC_SUCCESS))
+               goto fail;
+
+       ret = uvc_stream_ctrl(strmh, ctrl);
+       if (UNLIKELY(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);
+       strmh->size_buf = LIBUVC_XFER_BUF_SIZE; // xxx for boundary check
+
+       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) {
+       return uvc_stream_start_bandwidth(strmh, cb, user_ptr, 0, flags);
+}
+
+/** 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 bandwidth_factor [0.0f, 1.0f]
+ * @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_bandwidth(uvc_stream_handle_t *strmh,
+               uvc_frame_callback_t *cb, void *user_ptr, float bandwidth_factor, 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;
+       struct libusb_transfer *transfer;
+       int transfer_id;
+
+       ctrl = &strmh->cur_ctrl;
+
+       UVC_ENTER();
+
+       if (UNLIKELY(strmh->running)) {
+               UVC_EXIT(UVC_ERROR_BUSY);
+               return UVC_ERROR_BUSY;
+       }
+
+       strmh->running = 1;
+       strmh->seq = 0;
+       strmh->fid = 0;
+       strmh->pts = 0;
+       strmh->last_scr = 0;
+       strmh->bfh_err = 0;     // XXX
+
+       frame_desc = uvc_find_frame_desc_stream(strmh, ctrl->bFormatIndex, ctrl->bFrameIndex);
+       if (UNLIKELY(!frame_desc)) {
+               ret = UVC_ERROR_INVALID_PARAM;
+               LOGE("UVC_ERROR_INVALID_PARAM");
+               goto fail;
+       }
+       format_desc = frame_desc->parent;
+
+       strmh->frame_format = uvc_frame_format_for_guid(format_desc->guidFormat);
+       if (UNLIKELY(strmh->frame_format == UVC_FRAME_FORMAT_UNKNOWN)) {
+               ret = UVC_ERROR_NOT_SUPPORTED;
+               LOGE("unlnown frame format");
+               goto fail;
+       }
+       const uint32_t dwMaxVideoFrameSize = ctrl->dwMaxVideoFrameSize <= frame_desc->dwMaxVideoFrameBufferSize
+               ? ctrl->dwMaxVideoFrameSize : frame_desc->dwMaxVideoFrameBufferSize;
+
+       // 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 if it has multiple altsettings.
+        * (UVC 1.5: 2.4.3. VideoStreaming Interface, on page 19) */
+       isochronous = interface->num_altsetting > 1;
+
+       if (isochronous) {
+               MARK("isochronous transfer mode:num_altsetting=%d", interface->num_altsetting);
+               /* For isochronous streaming, we choose an appropriate altsetting for the endpoint
+                * and set up several transfers */
+               const struct libusb_interface_descriptor *altsetting;
+               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;
+               /* Total amount of data per transfer */
+               size_t total_transfer_size;
+               /* Size of packet transferable from the chosen endpoint */
+               size_t endpoint_bytes_per_packet;
+               /* Index of the altsetting */
+               int alt_idx, ep_idx;
+
+               struct libusb_transfer *transfer;
+               int transfer_id;
+               
+               if ((bandwidth_factor > 0) && (bandwidth_factor < 1.0f)) {
+                       config_bytes_per_packet = (size_t)(strmh->cur_ctrl.dwMaxPayloadTransferSize * bandwidth_factor);
+                       if (!config_bytes_per_packet) {
+                               config_bytes_per_packet = strmh->cur_ctrl.dwMaxPayloadTransferSize;
+                       }
+               } else {
+                       config_bytes_per_packet = strmh->cur_ctrl.dwMaxPayloadTransferSize;
+               }
+//#if !defined(__LP64__)
+//             LOGI("config_bytes_per_packet=%d", config_bytes_per_packet);
+//#else
+//             LOGI("config_bytes_per_packet=%ld", config_bytes_per_packet);
+//#endif
+               if (UNLIKELY(!config_bytes_per_packet)) {       // XXX added to privent zero divided exception at the following code
+                       ret = UVC_ERROR_IO;
+                       LOGE("config_bytes_per_packet is zero");
+                       goto fail;
+               }
+
+               /* 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. */
+               const int num_alt = interface->num_altsetting - 1;
+               for (alt_idx = 0; alt_idx <= num_alt ; 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]
+                                       // bit10…0:           maximum packet size
+                                       // bit12…11:  the number of additional transaction opportunities per microframe for high-speed
+                                       //                              00 = None (1 transaction per microframe)
+                                       //                              01 = 1 additional (2 per microframe)
+                                       //                              10 = 2 additional (3 per microframe)
+                                       //                              11 = Reserved
+                                       endpoint_bytes_per_packet
+                                               = (endpoint_bytes_per_packet & 0x07ff)
+                                                       * (((endpoint_bytes_per_packet >> 11) & 3) + 1);
+                                       break;
+                               }
+                       }
+                       // XXX config_bytes_per_packet should not be zero otherwise zero divided exception occur
+                       if (LIKELY(endpoint_bytes_per_packet)) {
+                               if ( (endpoint_bytes_per_packet >= config_bytes_per_packet)
+                                       || (alt_idx == num_alt) ) {     // XXX always match to last altsetting for buggy device
+                                       /* 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 = (dwMaxVideoFrameSize
+                                                       + endpoint_bytes_per_packet - 1)
+                                                       / endpoint_bytes_per_packet;            // XXX cashed by zero divided exception occured
+
+                                       /* 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 (UNLIKELY(!endpoint_bytes_per_packet)) {
+                       LOGE("endpoint_bytes_per_packet is zero");
+                       ret = UVC_ERROR_INVALID_MODE;
+                       goto fail;
+               }
+               if (UNLIKELY(!total_transfer_size)) {
+                       LOGE("total_transfer_size is zero");
+                       ret = UVC_ERROR_INVALID_MODE;
+                       goto fail;
+               }
+
+               /* If we searched through all the altsettings and found nothing usable */
+/*             if (UNLIKELY(alt_idx == interface->num_altsetting)) {   // XXX never hit this condition
+                       UVC_DEBUG("libusb_set_interface_alt_setting failed");
+                       ret = UVC_ERROR_INVALID_MODE;
+                       goto fail;
+               } */
+
+               /* Select the altsetting */
+               MARK("Select the altsetting");
+               ret = libusb_set_interface_alt_setting(strmh->devh->usb_devh,
+                               altsetting->bInterfaceNumber, altsetting->bAlternateSetting);
+               if (UNLIKELY(ret != UVC_SUCCESS)) {
+                       UVC_DEBUG("libusb_set_interface_alt_setting failed");
+                       goto fail;
+               }
+
+               /* Set up the transfers */
+               MARK("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 {
+               MARK("bulk transfer mode");
+               /** prepare for bulk transfer */
+               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.
+        */
+       MARK("create callback thread");
+       if LIKELY(cb) {
+               pthread_create(&strmh->cb_thread, NULL, _uvc_user_caller, (void*) strmh);
+       }
+       MARK("submit transfers");
+       for (transfer_id = 0; transfer_id < LIBUVC_NUM_TRANSFER_BUFS; transfer_id++) {
+               ret = libusb_submit_transfer(strmh->transfers[transfer_id]);
+               if (UNLIKELY(ret != UVC_SUCCESS)) {
+                       UVC_DEBUG("libusb_submit_transfer failed");
+                       break;
+               }
+       }
+
+       if (UNLIKELY(ret != UVC_SUCCESS)) {
+               /** @todo clean up transfers and memory */
+               goto fail;
+       }
+
+       UVC_EXIT(ret);
+       return ret;
+fail:
+       LOGE("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
+ */
+static void *_uvc_user_caller(void *arg) {
+       uvc_stream_handle_t *strmh = (uvc_stream_handle_t *) arg;
+
+       uint32_t last_seq = 0;
+
+       for (; 1 ;) {
+               pthread_mutex_lock(&strmh->cb_mutex);
+               {
+                       for (; strmh->running && (last_seq == strmh->hold_seq) ;) {
+                               pthread_cond_wait(&strmh->cb_cond, &strmh->cb_mutex);
+                       }
+
+                       if (UNLIKELY(!strmh->running)) {
+                               pthread_mutex_unlock(&strmh->cb_mutex);
+                               break;
+                       }
+
+                       last_seq = strmh->hold_seq;
+                       if (LIKELY(!strmh->hold_bfh_err))       // XXX
+                               _uvc_populate_frame(strmh);
+               }
+               pthread_mutex_unlock(&strmh->cb_mutex);
+
+               if (LIKELY(!strmh->hold_bfh_err))       // XXX
+                       strmh->user_cb(&strmh->frame, strmh->user_ptr); // call user callback function
+       }
+
+       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) {
+       size_t alloc_size = strmh->cur_ctrl.dwMaxVideoFrameSize;
+       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;
+       // XXX set actual_bytes to zero when erro bits is on
+       frame->actual_bytes = LIKELY(!strmh->hold_bfh_err) ? strmh->hold_bytes : 0;
+
+       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;
+       }
+
+       /* copy the image data from the hold buffer to the frame (unnecessary extra buf?) */
+       if (UNLIKELY(frame->data_bytes < strmh->hold_bytes)) {
+               frame->data = realloc(frame->data, strmh->hold_bytes);  // TODO add error handling when failed realloc
+               frame->data_bytes = strmh->hold_bytes;
+       }
+       memcpy(frame->data, strmh->holdbuf, strmh->hold_bytes/*frame->data_bytes*/);    // XXX
+
+       /** @todo set the frame time */
+}
+
+/** 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 (UNLIKELY(!strmh->running))
+               return UVC_ERROR_INVALID_PARAM;
+
+       if (UNLIKELY(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) {
+                               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(&strmh->cb_cond, &strmh->cb_mutex, &ts);
+                       }
+
+                       if (LIKELY(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;
+
+       UVC_ENTER();
+       DL_FOREACH_SAFE(devh->streams, strmh, strmh_tmp)
+       {
+               uvc_stream_close(strmh);
+       }
+       UVC_EXIT_VOID();
+}
+
+/** @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;
+       ENTER();
+
+       if (!strmh) RETURN(UVC_SUCCESS, uvc_error_t);
+
+       if (UNLIKELY(!strmh->running)) {
+               UVC_EXIT(UVC_ERROR_INVALID_PARAM);
+               RETURN(UVC_ERROR_INVALID_PARAM, uvc_error_t);
+       }
+
+       strmh->running = 0;
+
+       pthread_mutex_lock(&strmh->cb_mutex);
+       {
+               for (i = 0; i < LIBUVC_NUM_TRANSFER_BUFS; i++) {
+                       if (strmh->transfers[i]) {
+                               int res = libusb_cancel_transfer(strmh->transfers[i]);
+                               if ((res < 0) && (res != LIBUSB_ERROR_NOT_FOUND)) {
+                                       UVC_DEBUG("libusb_cancel_transfer failed");
+                                       // XXX originally freed buffers and transfer here
+                                       // but this could lead to crash in _uvc_callback
+                                       // therefore we comment out these lines
+                                       // and free these objects in _uvc_iso_callback when strmh->running is false
+/*                                     free(strmh->transfers[i]->buffer);
+                                       libusb_free_transfer(strmh->transfers[i]);
+                                       strmh->transfers[i] = NULL; */
+                               }
+                       }
+               }
+
+               /* Wait for transfers to complete/cancel */
+               for (; 1 ;) {
+                       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);
+               }
+               // 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, uvc_error_t);
+}
+
+/** @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) {
+       UVC_ENTER();
+
+       if (!strmh) { UVC_EXIT_VOID(); };
+
+       if (strmh->running)
+               uvc_stream_stop(strmh);
+
+       uvc_release_if(strmh->devh, strmh->stream_if->bInterfaceNumber);
+
+       if (strmh->frame.data) {
+               free(strmh->frame.data);
+               strmh->frame.data = NULL;
+       }
+
+       if (strmh->outbuf) {
+               free(strmh->outbuf);
+               strmh->outbuf = NULL;
+       }
+       if (strmh->holdbuf) {
+               free(strmh->holdbuf);
+               strmh->holdbuf = NULL;
+       }
+
+       pthread_cond_destroy(&strmh->cb_cond);
+       pthread_mutex_destroy(&strmh->cb_mutex);
+
+       DL_DELETE(strmh->devh->streams, strmh);
+       free(strmh);
+
+       UVC_EXIT_VOID();
+}