aboutsummaryrefslogtreecommitdiffstats
path: root/o3000.c
diff options
context:
space:
mode:
Diffstat (limited to 'o3000.c')
-rw-r--r--o3000.c1431
1 files changed, 1431 insertions, 0 deletions
diff --git a/o3000.c b/o3000.c
new file mode 100644
index 0000000..1ad5676
--- /dev/null
+++ b/o3000.c
@@ -0,0 +1,1431 @@
+/**
+* @file o3000.c
+* @brief O-3000 Camera driver
+* @author Patrick Brunner - brunner@stettbacher.ch
+* @author Patrick Roth - roth@stettbacher.ch
+* @version 1.1
+* @date 2016-03-01
+* @copyright 2012-2016 Stettbacher Signal Processing AG
+*
+* @remarks
+*
+* <PRE>
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* License as published by the Free Software Foundation; either
+* version 2.1 of the License, or (at your option) any later version.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this library; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+* </PRE>
+*
+*/
+
+
+/**
+
+@mainpage O-3000 camera driver
+
+@section intro Introduction
+
+This document describes the Application Programming Interface (API) for the
+<b>Stettbacher Signal Processing USB O-3000 Camera</b>, further referenced as "the camera".</p>
+The driver delivers a generic user interface for accessing the camera and runs on different operating system like:
+<ul>
+<li> Linux </li>
+<li> Windows </li>
+<li> MAC </li>
+</ul>
+The driver supports session handling. Therfore it's able to handle several camera session on the same host system accessing from one
+or several user applications.
+
+@section api API
+
+Two groups of functions represent the interface:
+<ol>
+<li> A set of functions provided by the camera API which can be called from
+ the user application (see @ref api_fun). </li>
+<li> A set of callback functions the user application must implement and
+ register with the camera API (see @ref cb_fun). </li>
+</ol>
+
+@subsection api_fun API functions
+
+The basic approach using the camera driver is:
+<ol>
+<li> Implementing all required callback functions, see @ref cb_fun. </li>
+<li> Initialising the camera driver, see o3000_init(). </li>
+<li> Connecting to the camera driver, see o3000_connect(). </li>
+</ol>
+
+Once the user application is connected to the driver by calling o3000_connect(), it will block until:
+<ul>
+<li> The driver exits by calling o3000_exit(). </li>
+<li> The camera is disconnected from the system. </li>
+</ul>
+
+XML messages are transfered to the camera by calling o3000_send_xml(). If the camera sends a XML message to the host, the
+driver triggers the @ref xml_cb. The same concept is used, when the camera sends video data to the host. After receving a complete frame, the driver calls the
+@ref video_cb.
+
+@note The video driver triggers any callback from the thread context, where o3000_connect() is called from. This means that the o3000_send_xml() function should
+never be called from the Video, XML or Logging callback. Otherwise the driver will lock. It's important, that any XML message sent with o3000_send_xml() to the camera
+is called from a different thread context.
+
+
+
+@subsection cb_fun Callback functions
+
+@subsubsection video_cb Video handler
+
+The video handler is called by the camera driver whenever a complete frame
+is received. The video handler holds a pointer to the image
+data (the frame) and a pointer to a structure describing the image format.
+This handler is mandatory.
+
+@subsubsection xml_cb XML handler
+
+The XML handler is called by the camera driver whenever a XML message is
+received. The XML handler holds a pointer to a string buffer
+containing the XML message, and the length of that message. This handler is
+mandatory.
+
+@subsubsection log_cb Logging handler
+
+The Logging handler is called by the driver to print debug messages.
+The handler contains a pointer to a (zero-terminated) string buffer
+containing the log message. This handler is optional.
+@note The verbosity of the messages depends on the log level and is defined from @ref O3000_LOG_NONE to @ref O3000_LOG_VERBOSE.
+
+@section example Example
+
+A simplified example with C is shown below, focussing on the order in which the various
+camera driver functions should be called. It's up to the user to implement
+multithreading in order to avoid a deadlock due to the blocking nature of o3000_connect()!
+
+@code
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <o3000/o3000.h>
+
+
+static char xml_msg[] = {"<camera><stream></stream></camera>"};
+
+static void xml_handling(int id, char* msg, int len) {
+ // insert your code here
+}
+
+static void video_handling(int id, unsigned char* buf, struct img_header_t* img_header) {
+ // insert your code here
+}
+
+static void log_handling(int id, char* msg) {
+ // insert your code here
+}
+
+int main(int argc, char **argv) {
+
+ int ret = 0;
+ int num_camera, i;
+ int cam_session;
+
+
+ // setup camera session with 4 MByte video cache
+ cam_session = o3000_init(O3000_VID, O3000_PID, 1024*1024*4, xml_handling, video_handling, log_handling, O3000_LOG_ERROR);
+ if(cam_session < 0) {
+ exit(1);
+ }
+
+ num_camera = o3000_device_discovery(cam_session);
+ if(num_camera < 0) {
+ o3000_exit(cam_session);
+ exit(1);
+ }
+
+ if(num_camera == 0) {
+ printf("%s: no camera connected to the system\n", __func__);
+ o3000_exit(cam_session);
+ exit(1);
+ }
+
+ // establish connection to first available camera
+ for(i = 0; i < num_camera; i++) {
+ printf("%s: establish connection to camera %d\n", __func__, i);
+ ret = o3000_connect(cam_session, i, xml_msg, strlen(xml_msg));
+ if(ret == O3000_ERROR_BUSY) {
+ printf("%s: device %d is already in use\n", __func__, i);
+ }
+ else {
+ break;
+ }
+ }
+
+ o3000_exit(cam_session);
+ exit(0);
+}
+
+@endcode
+
+*/
+
+/* Video buffering concept
+
+At program initialisation, a number of transfer buffers is sent to libusb.
+Libusb handles the low-level interactions with the kernel. A transfer is split
+into a number of URBs. These URBs are sent to the kernel and returned to libusb
+with the data received. As soon as enough data was received, libusb calls the
+transfer callback, which processes the data. In this case, the transfer handler
+is called. As soon as the transfer is handled, the transfer is resubmitted to
+libusb, ready to be filled again.
+
+Each transfer points to a buffer, where received data is copied to. All transfer
+buffers T(x) and the frame buffer FB (see below) are contiguous, i.e. starting
+from T(0) (transfer 0) to the end of the frame buffer a linear memory region
+without holes is allocated.
+
+Memory Layout:
+
++---- start of video cache
+| +---- start of frame buffer
+| |
+| |
+V V
++------+------+------+------+------+------+--------------------+
+| T(0) | T(1) | T(2) | T(3) | .... | T(n) | Frame buffer |
++------+------+------+------+------+------+--------------------+
+
+The transfers are submitted starting from T(0). As they are queued, the order
+T(0), T(1), etc. is always maintained. After filling the last buffer T(n),
+the sequence starts over with T(0). No double-buffering takes place, as by the
+strict sequence order and the fact that a received buffer is processed first
+before resubmission there is no risk of overwriting unprocessed data.
+
+The transfer handler scans through the data received. Whenever a complete frame
+is found in the buffers, the video callback is called, which is supposed to
+process the frame.
+
+A special case applies, if an incomplete frame remains at the end of T(n), which
+will be continued with T(0) (and maybe subsequent transfers). In that situation,
+if the frame is completely received, the second part, starting from T(0) will
+be copied to the frame buffer. As the frame buffer is linked directly after T(n),
+the frame is now located in a linear part of the memory, which is a condition for
+the video callback function which expects a pointer to a linear buffer, holding
+the frame.
+
+An other special case occurs, when a snapshot is sent. The last portion of a snapshot
+is usually sent as a short bulk transfer packet, which means, that a subsequent snapshot
+or streaming frame will be aligned to the start of a transfer, leaving a gap of invalid
+data between two frames in the video cache.
+
+Depending on the transfer size chosen, a frame may extend over several transfers,
+or a transfer may contain several frames.
+
+*/
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "o3000/o3000_portable.h"
+#include "o3000_private.h"
+#include "o3000/o3000.h"
+#include "o3000_xfer_handler.h"
+
+
+#define CAM_IN_EP (1 | LIBUSB_ENDPOINT_IN) ///< endpoint for video data
+#define XML_IN_EP (2 | LIBUSB_ENDPOINT_IN) ///< endpoint for inbound XML messages
+#define XML_OUT_EP (1) ///< endpoint for outbound XML messages
+
+
+
+/**
+ * Inbound XML messages size
+ */
+#define XML_IN_BUF_SIZE 16384
+
+
+/**
+ * Maximum image size ever expected.
+ *
+ * @note The size must be a multiple of 512 bytes!
+ */
+#define MAX_IMAGE_SIZE (1280*964*2+IMAGE_HEADER_SIZE)
+
+/**
+ * Minimum video cache size.
+ * It's the double of the maximum image size ever expected.
+ */
+#define MIN_VIDEO_CACHE_SIZE (MAX_IMAGE_SIZE)
+
+
+/**
+ * maximum number of camera sessions
+ */
+#define MAX_NUM_SESSION 16
+
+
+/**
+ * The video cache is divided into several USB transfer queued at at kernel.
+ */
+#define NUM_USB_VIDEO_TRANSFER 10
+
+
+/**
+ * Session table
+ * Each table entry points to an active session. If the entry is NULL it's not used and can be used for a new session.
+ */
+static struct o3000_session_t *session_table[MAX_NUM_SESSION];
+
+/*
+ * Thread synchronization mutex
+ */
+static o3000_mutex_static_t session_lock = O3000_MUTEX_INITIALIZER;
+
+/**
+ * Maps loglevel numbers to descriptive strings
+ */
+static const char *dbg_strlevel[] = {
+ "NONE",
+ "ERROR",
+ "WARNING",
+ "INFO",
+ "DEBUG"
+};
+
+
+
+/**
+ * Return session by ID
+ *
+ * @param id session ID
+ * @return adress to session or NULL if not available
+ */
+static struct o3000_session_t *get_session(int id) {
+
+ struct o3000_session_t *session;
+
+ if(id < 0 || id >= MAX_NUM_SESSION) {
+ return NULL;
+ }
+ session = session_table[id];
+ if(session == NULL) {
+ return NULL;
+ }
+ return session;
+}
+
+
+/**
+ * Cleanup given session.
+ * Everything is cleaned up like:
+ * - closing current session
+ * - release USB transfers
+ * - release video cache and XML message buffer
+ *
+ * In the end, the current session is freed.
+ *
+ */
+static void cleanup(int id) {
+
+ struct o3000_session_t *session;
+ int i;
+
+
+ session = get_session(id);
+ if(session == NULL) {
+ return;
+ }
+
+ // release device list
+ if(session->device_list != NULL) {
+ o3000_log(session, O3000_LOG_DEBUG, "%s: cleanup USB device discovery list\n", __func__);
+ libusb_free_device_list(session->device_list, 1);
+ session->device_list = NULL;
+ session->num_device_list = 0;
+ }
+
+ // close USB device
+ if(session->dev != NULL) {
+ o3000_log(session, O3000_LOG_DEBUG, "%s: close USB device handler\n", __func__);
+ libusb_close(session->dev);
+ session->dev = NULL;
+ }
+
+ // release USB video transfer
+ for(i = 0; i < NUM_USB_VIDEO_TRANSFER; i++) {
+ if(session->transfer_data[i] != NULL) {
+ o3000_log(session, O3000_LOG_DEBUG, "%s: release USB video transfers %d\n", __func__, i);
+ libusb_free_transfer(session->transfer_data[i]);
+ session->transfer_data[i] = NULL;
+ }
+ }
+
+ // release USB transfer transfer array
+ if(session->transfer_data != NULL) {
+ o3000_log(session, O3000_LOG_DEBUG, "%s: release USB transfer array\n", __func__);
+ free(session->transfer_data);
+ session->transfer_data = NULL;
+ }
+
+ // release USB XML transfer for incoming messages
+ if(session->transfer_xml_in != NULL) {
+ o3000_log(session, O3000_LOG_DEBUG, "%s: release USB XML transfer for incoming messages\n", __func__);
+ libusb_free_transfer(session->transfer_xml_in);
+ session->transfer_xml_in = NULL;
+ }
+
+ // release XML buffer
+ if(session->xml_in_buf != NULL) {
+ o3000_log(session, O3000_LOG_DEBUG, "%s: release XML message buffer\n", __func__);
+ free(session->xml_in_buf);
+ session->xml_in_buf = NULL;
+ }
+
+ // release video cache
+ if(session->video_cache != NULL) {
+ o3000_log(session, O3000_LOG_DEBUG, "%s: release video cache\n", __func__);
+ free(session->video_cache);
+ session->video_cache = NULL;
+ }
+
+
+
+ // add more cleanup code here...
+ // ..
+
+
+
+ // close libusb context
+ if(session->libusb_ctx != NULL) {
+ o3000_log(session, O3000_LOG_DEBUG, "%s: release USB context\n", __func__);
+ libusb_exit(session->libusb_ctx);
+ session->libusb_ctx = NULL;
+ }
+
+ // finally release this session
+ if(session_table[id] != NULL) {
+ o3000_log(session, O3000_LOG_DEBUG, "%s: release USB session\n", __func__);
+ free(session_table[id]);
+ session_table[id] = NULL;
+ }
+}
+
+
+
+/**
+ * Check wether given USB device is a camera
+ *
+ * @param session session pointer
+ * @param device USB device to check
+ * @return TRUE if it's a camera otherwise FALSE
+ */
+static int is_camera(struct o3000_session_t *session, libusb_device *device) {
+
+ int ret, dev_of_interest;
+ struct libusb_device_descriptor desc;
+
+ ret = libusb_get_device_descriptor(device, &desc);
+ if(ret != 0) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: getting USB descriptor failed (code %d)\n", __func__, ret);
+ return FALSE;
+ }
+
+ dev_of_interest = FALSE;
+ if((desc.idVendor == session->vid) && (desc.idProduct == session->pid)) {
+ dev_of_interest = TRUE;
+ }
+
+ o3000_log(session, O3000_LOG_DEBUG, "%s: USB device detected (Device class %d, VID:PID 0x%x:0x%x, S/N %d) - %s\n",
+ __func__, desc.bDeviceClass, desc.idVendor, desc.idProduct, desc.iSerialNumber, dev_of_interest == TRUE ? "camera":"no camera");
+
+ return dev_of_interest;
+}
+
+
+/**
+ * Print a meaningful message for given transfer result.
+ *
+ * @param result libusb submit transfer return code.
+ * @return o3000 error code translation (see @ref o3000.h)
+ */
+static int print_transfer_result(struct o3000_session_t *session, int result) {
+
+ int error_code = O3000_ERROR_OTHER;
+
+ switch(result) {
+ case 0:
+ o3000_log(session, O3000_LOG_DEBUG, "%s: transfer successfully completed\n", __func__);
+ error_code = O3000_SUCCESS;
+ break;
+
+ case LIBUSB_ERROR_NO_DEVICE:
+ o3000_log(session, O3000_LOG_ERROR, "%s: device has been disconnected\n", __func__);
+ error_code = O3000_ERROR_NODEV;
+ break;
+
+ case LIBUSB_ERROR_BUSY:
+ o3000_log(session, O3000_LOG_ERROR, "%s: transfer has already been submitted\n", __func__);
+ error_code = O3000_ERROR_BUSY;
+ break;
+
+ case LIBUSB_ERROR_NOT_SUPPORTED:
+ o3000_log(session, O3000_LOG_ERROR, "%s: transfer flags not supported by the OS\n", __func__);
+ break;
+
+ default:
+ o3000_log(session, O3000_LOG_ERROR, "%s: other error (code %d)\n",__func__, result);
+ }
+ return error_code;
+}
+
+
+/**
+ * Print a meaningful message for given transfer status.
+ *
+ * @param status libusb status code
+ */
+static void print_transfer_status(struct o3000_session_t *session, enum libusb_transfer_status status) {
+ switch(status) {
+ case LIBUSB_TRANSFER_COMPLETED:
+ o3000_log(session, O3000_LOG_DEBUG, "%s: transfer successfully completed\n", __func__);
+ break;
+ case LIBUSB_TRANSFER_ERROR:
+ o3000_log(session, O3000_LOG_DEBUG, "%s: Error: transfer failed\n", __func__);
+ break;
+ case LIBUSB_TRANSFER_TIMED_OUT:
+ o3000_log(session, O3000_LOG_DEBUG, "transfer timed out\n", __func__);
+ break;
+ case LIBUSB_TRANSFER_CANCELLED:
+ o3000_log(session, O3000_LOG_DEBUG, "transfer was cancelled\n", __func__);
+ break;
+ case LIBUSB_TRANSFER_STALL:
+ o3000_log(session, O3000_LOG_DEBUG, "%s: endpoint stalled\n", __func__);
+ break;
+ case LIBUSB_TRANSFER_NO_DEVICE:
+ o3000_log(session, O3000_LOG_DEBUG, "%s: device has been disconnected\n", __func__);
+ break;
+ case LIBUSB_TRANSFER_OVERFLOW:
+ o3000_log(session, O3000_LOG_DEBUG, "%s: device sent more data than requested\n", __func__);
+ break;
+ default:
+ o3000_log(session, O3000_LOG_DEBUG, "%s: Error: unknown\n", __func__);
+ }
+}
+
+
+/**
+ * Check whether all transfers are cleanuped (freed).
+ * This includes all video transfers and the XML transfer.
+ *
+ * @param session current session
+ * @return FALSE if any transfer is pending for cleaning up; TRUE if all transfers are cleanuped
+ */
+static int allTransfersCleanup(struct o3000_session_t *session) {
+ int i;
+
+ if(session->transfer_xml_in != NULL) {
+ return FALSE;
+ }
+
+ for(i = 0; i < NUM_USB_VIDEO_TRANSFER; i++) {
+ if(session->transfer_data[i] != NULL) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+
+/**
+ * Cleanup current USB transfer (XML or video transfer).
+ *
+ * @param transfer current transfer to clean up
+ */
+static void cleanup_transfer(struct libusb_transfer *transfer) {
+
+ int i;
+ struct o3000_session_t *session = transfer->user_data;
+
+
+ session->cleanup_transfers = TRUE;
+
+ // print transfer status for debugging purposes
+ print_transfer_status(session, transfer->status);
+
+
+ if(transfer == session->transfer_xml_in) {
+ // release USB XML transfer
+ if(session->transfer_xml_in != NULL) {
+ libusb_free_transfer(session->transfer_xml_in);
+ o3000_log(session, O3000_LOG_INFO, "%s: cleanup XML transfer (%p)\n", __func__, session->transfer_xml_in);
+ session->transfer_xml_in = NULL;
+ transfer = NULL;
+ }
+ }
+ else {
+ // release USB video transfer
+ for(i = 0; i < NUM_USB_VIDEO_TRANSFER; i++) {
+ if(session->transfer_data[i] == transfer) {
+ libusb_free_transfer(session->transfer_data[i]);
+ o3000_log(session, O3000_LOG_INFO, "%s: cleanup video transfer %d (%p)\n", __func__, i, session->transfer_data[i]);
+ session->transfer_data[i] = NULL;
+ transfer = NULL;
+ break;
+ }
+ }
+ }
+
+ if(transfer != NULL) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: FIXME: This transfer %p should be cleaned!\n", __func__, transfer);
+ }
+
+ // reset running flag only when all transfers are cleaned up
+ if(allTransfersCleanup(session)) {
+ o3000_log(session, O3000_LOG_INFO, "%s: all USB transfers are cleaned up\n", __func__);
+ session->running = FALSE;
+ }
+}
+
+
+/**
+ * Transfer done callback for inbound XML.
+ *
+ * @note Transfer done only means submitted, not actually completed!
+ *
+ * @param transfer Pointer to XML transfer which has been submitted
+ */
+static void LIBUSB_CALL cb_xml_in_xfer_done(struct libusb_transfer *transfer) {
+
+ int retval;
+ struct o3000_session_t *session = transfer->user_data;
+
+
+ if(transfer->status == LIBUSB_TRANSFER_COMPLETED) {
+
+ // check whether string length is out of range
+ if(transfer->actual_length < 0 || transfer->actual_length >= XML_IN_BUF_SIZE) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: invalid XML message length (len = %d)\n", __func__, transfer->actual_length);
+ }
+ else {
+ session->xml_in_buf[transfer->actual_length] = '\0';
+ session->xml_cb(session->id, session->xml_in_buf, transfer->actual_length);
+ }
+
+ // resubmit transfer only if driver isn't cleaning up
+ if(session->cleanup_transfers == FALSE) {
+ retval = libusb_submit_transfer(session->transfer_xml_in);
+ if(retval != 0) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: resubmit XML transfer failed (code %d) --> cleanup\n", __func__, retval);
+ print_transfer_result(session, retval);
+ cleanup_transfer(transfer);
+ }
+ }
+ else {
+ o3000_log(session, O3000_LOG_INFO, "%s: cleaning up in progress\n", __func__);
+ cleanup_transfer(transfer);
+ }
+ }
+ else {
+ /*
+ * USB transfer is not completed!
+ * Do only clean up the transfer if the device has been disconnected or cleaning-up flag is set.
+ * Otherwise skip error and resubmit current transfer.
+ */
+ if(transfer->status == LIBUSB_TRANSFER_NO_DEVICE || session->cleanup_transfers == TRUE) {
+ o3000_log(session, O3000_LOG_INFO, "%s: cleaning up in progress (%s), no device (%s)\n",
+ __func__, session->cleanup_transfers == TRUE ? "yes":"no", transfer->status == LIBUSB_TRANSFER_NO_DEVICE ? "yes":"no");
+ cleanup_transfer(transfer);
+ }
+ else {
+ retval = libusb_submit_transfer(session->transfer_xml_in);
+ if(retval != 0) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: Could resubmit XML transfer (code %d)\n", __func__, retval);
+ print_transfer_result(session, retval);
+ cleanup_transfer(transfer);
+ }
+ }
+ }
+}
+
+
+/**
+ * Transfer done callback for video data.
+ *
+ * @note Transfer done only means submitted, not actually completed!
+ *
+ * @param transfer Pointer to transfer which just has been submitted
+ */
+static void LIBUSB_CALL cb_video_xfer_done(struct libusb_transfer *transfer) {
+
+ int retval;
+ struct o3000_session_t *session = transfer->user_data;
+
+
+ if(transfer->status == LIBUSB_TRANSFER_COMPLETED) {
+
+ if(transfer->actual_length > 0) {
+ handle_transfer(session, (uint8_t*)(transfer->buffer), transfer->actual_length);
+ }
+
+ // resubmit transfer only if driver is not exiting
+ if(session->cleanup_transfers == FALSE) {
+ retval = libusb_submit_transfer(transfer);
+ if(retval != 0) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: Could resubmit video transfer (code %d)\n", __func__, retval);
+ print_transfer_result(session, retval);
+ cleanup_transfer(transfer);
+ }
+ }
+ else {
+ o3000_log(session, O3000_LOG_INFO, "%s: cleaning up in progress\n", __func__);
+ cleanup_transfer(transfer);
+ }
+ }
+ else {
+ /*
+ * USB transfer is not completed!
+ * Do only clean up the transfer if the device has been disconnected or cleaning-up flag is set.
+ * Otherwise skip error and resubmit current transfer.
+ */
+ if(transfer->status == LIBUSB_TRANSFER_NO_DEVICE || session->cleanup_transfers == TRUE) {
+ o3000_log(session, O3000_LOG_INFO, "%s: cleaning up in progress (%s), no device (%s)\n",
+ __func__, session->cleanup_transfers == TRUE ? "yes":"no", transfer->status == LIBUSB_TRANSFER_NO_DEVICE ? "yes":"no");
+ cleanup_transfer(transfer);
+ }
+ else {
+ retval = libusb_submit_transfer(transfer);
+ if(retval != 0) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: Could resubmit video transfer (code %d)\n", __func__, retval);
+ print_transfer_result(session, retval);
+ cleanup_transfer(transfer);
+ }
+ }
+ }
+}
+
+
+/**
+ * Prepare USB transfers to receive video data and XML messages from device.
+ *
+ * @param session session pointer
+ * @return 0 on success, or error code (see @ref o3000.h)
+ */
+static int prepare_usb_transfers(struct o3000_session_t *session) {
+
+ int i, retval;
+ struct libusb_transfer *new_transfer;
+ int error_code = O3000_ERROR_OTHER;
+ uint8_t *xfer_buf;
+
+
+ // paranoia check
+ if(session->transfer_data == NULL) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: FIXME: pointer array used for video transfer is not allocated!\n", __func__);
+ return O3000_ERROR_OTHER;
+ }
+
+ /*
+ * setup video transfer array
+ */
+ for(i = 0; i < NUM_USB_VIDEO_TRANSFER; i++) {
+
+ // Allocate transfer
+ new_transfer = libusb_alloc_transfer(0);
+ if(new_transfer == NULL) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: Could not allocate video transfer %d\n", __func__, i);
+ error_code = O3000_ERROR_NOMEM;
+ goto _prepare_usb_transfer_abort;
+ }
+ session->transfer_data[i] = new_transfer;
+
+ // Video transfers are using the same callback but with own user data pointer pointing to current session definition.
+ xfer_buf = session->video_cache + i*(session->video_chunk_size);
+ libusb_fill_bulk_transfer(new_transfer, session->dev, CAM_IN_EP, xfer_buf, session->video_chunk_size, cb_video_xfer_done, session, 0);
+
+ // Submit transfer
+ retval = libusb_submit_transfer(new_transfer);
+ if(retval != 0) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: Could not submit video transfer %d (code %d)\n", __func__, i, retval);
+ error_code = print_transfer_result(session, retval);
+ goto _prepare_usb_transfer_abort;
+ }
+ o3000_log(session, O3000_LOG_DEBUG, "%s: video transfer %d allocated and submitted (%p)\n", __func__, i, new_transfer);
+ }
+
+ /*
+ * setup single XML transfer
+ */
+ new_transfer = libusb_alloc_transfer(0);
+ if(new_transfer == NULL) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: Could not allocate XML transfer\n", __func__);
+ error_code = O3000_ERROR_NOMEM;
+ goto _prepare_usb_transfer_abort;
+ }
+ session->transfer_xml_in = new_transfer;
+
+ // prepare and submit transfer
+ libusb_fill_bulk_transfer(new_transfer, session->dev, XML_IN_EP, (unsigned char*)(session->xml_in_buf), XML_IN_BUF_SIZE, cb_xml_in_xfer_done, session, 0);
+ retval = libusb_submit_transfer(new_transfer);
+ if(retval != 0) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: Could not submit XML transfer (code %d)\n", __func__, retval);
+ error_code = print_transfer_result(session, retval);
+ goto _prepare_usb_transfer_abort;
+ }
+
+ o3000_log(session, O3000_LOG_DEBUG, "%s: XML transfer allocated and submitted (%p)\n", __func__, new_transfer);
+ return 0;
+
+
+_prepare_usb_transfer_abort:
+ return error_code;
+}
+
+
+/**
+ * Send XML message to device.
+ *
+ * @param session current session
+ * @param msg message content
+ * @param msg_len message content length
+ * @return 0 on success, or error code (see @ref o3000.h)
+ */
+static int send_xml(struct o3000_session_t *session, const char *msg, int msg_len) {
+
+ int xml_msg_transferred, retval, ret_code;
+
+ // use timeout of 1 second
+ ret_code = 0;
+ retval = libusb_bulk_transfer(session->dev, XML_OUT_EP, (unsigned char*)msg, msg_len, &xml_msg_transferred, 1000);
+ if(retval != 0) {
+ if(retval == LIBUSB_ERROR_TIMEOUT) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: sending XML message failed (timeout)\n", __func__, retval);
+ ret_code = O3000_ERROR_USB_TRANSFER_TIMEOUT;
+ }
+ else if(retval == LIBUSB_ERROR_PIPE) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: sending XML message failed (pipe error)\n", __func__, retval);
+ ret_code = O3000_ERROR_USB_EP_HALTED;
+ }
+ else if(retval == LIBUSB_ERROR_NO_DEVICE) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: sending XML message failed (no device)\n", __func__, retval);
+ ret_code = O3000_ERROR_NODEV;
+ }
+ else {
+ o3000_log(session, O3000_LOG_ERROR, "%s: sending XML message failed (code %d)\n", __func__, retval);
+ ret_code = O3000_ERROR_OTHER;
+ }
+ }
+ else if(xml_msg_transferred != msg_len) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: less data are transfered to the device than expected (%d of %d bytes)\n", __func__, xml_msg_transferred, msg_len);
+ ret_code = O3000_ERROR_LESS_DATA;
+ }
+ return ret_code;
+}
+
+
+/**
+ * Send XML packet from host to device.
+ *
+ * @note Never call this function from the thread context o3000_connect() is called from!
+ *
+ * @param id session ID
+ * @param msg message content
+ * @param msg_len message content length
+ * @return 0 on success, or error code (see @ref o3000.h)
+ */
+int __stdcall o3000_send_xml(int id, const char *msg, int msg_len) {
+
+ struct o3000_session_t *session;
+
+ session = get_session(id);
+ if(session == NULL) {
+ return O3000_ERROR_INVALID_SESSION_ID;
+ }
+
+ if(msg == NULL) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: no message defined\n", __func__);
+ return O3000_ERROR_OTHER;
+ }
+
+ if(msg_len <= 0) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: invalid message length %d\n", __func__, msg_len);
+ return O3000_ERROR_OTHER;
+ }
+
+ if(session->running == FALSE) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: driver is not running\n", __func__);
+ return O3000_ERROR_DRV_NOT_CONNECTED;
+ }
+
+ if(session->cleanup_transfers == TRUE) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: driver is cleaning up\n", __func__);
+ return O3000_ERROR_DRV_NOT_CONNECTED;
+ }
+
+ return (send_xml(session, msg, msg_len));
+}
+
+
+/**
+ * Disconnect a currently claimed USB connection.
+ * Use this function if the driver is connected to an USB device. The o3000_connect() function-call is blocking now.
+ * After disconnecting, the function o3000_connect() returns. The session is not cleaned-up due to the driver is ready the reconnect
+ * to the same USB device by calling o3000_connect() again.
+ *
+ * @param id session ID
+ * @return 0 on success, or error code defined at @ref o3000.h.
+ */
+int __stdcall o3000_disconnect(int id) {
+
+ struct o3000_session_t *session;
+ int retval;
+
+
+ session = get_session(id);
+ if(session == NULL) {
+ return O3000_ERROR_INVALID_SESSION_ID;
+ }
+
+ if(session->running == FALSE) {
+ o3000_log(session, O3000_LOG_WARNING, "%s: driver is not connected!\n", __func__);
+ return O3000_ERROR_DRV_NOT_CONNECTED;
+ }
+
+ o3000_log(session, O3000_LOG_DEBUG, "%s: disconnect USB device (release interface)\n", __func__);
+ session->cleanup_transfers = TRUE;
+ session->disconnect_dev = TRUE;
+ retval = libusb_release_interface(session->dev, 0);
+ if(retval == LIBUSB_ERROR_NOT_FOUND) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: releasing interface failed: interface not claimed\n", __func__);
+ }
+ else if(retval == LIBUSB_ERROR_NO_DEVICE) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: releasing interface failed: device has been disconnected\n", __func__);
+ }
+ else if(retval != 0) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: releasing interface failed (code %d)\n", __func__, retval);
+ }
+ return retval;
+}
+
+
+/**
+ * Estabslish a connection to an existing device.
+ * If several devices are available on the same system, the device number is used. The number of devices are requested
+ * by o3000_get_num_device().
+ *
+ * NOTE
+ * This function will block until the USB device is disconnected manually or the driver is stopped by calling o3000_exit().
+ *
+ * @param id session ID
+ * @param device_nr device number to connect (use 0 to connect to first available device)
+ * @param config_msg configuration message to be sent to device after successful connection, or NULL if not used
+ * @param config_msg_len configuration message length
+ * @return 0 is returned only after calling dmm_exit() or dmm_disconnect(). Otherwise the error code defined at @ref o3000.h.
+ */
+int __stdcall o3000_connect(int id, int device_nr, char *config_msg, int config_msg_len) {
+
+ struct o3000_session_t *session;
+ int ret, i, dev_cnt, completed;
+ libusb_device *device;
+ libusb_device_handle *handle;
+ int error_code = 0;
+
+
+ session = get_session(id);
+ if(session == NULL) {
+ return O3000_ERROR_INVALID_SESSION_ID;
+ }
+
+ // reset all flags here
+ session->cleanup_transfers = FALSE;
+ session->disconnect_dev = FALSE;
+ session->release_session = FALSE;
+
+ // check whether device list is defined
+ if(session->device_list == NULL) {
+ ret = o3000_device_discovery(id);
+ if(ret < 0) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: device discovery failed\n", __func__);
+ return ret;
+ }
+ }
+
+ // look for device in question
+ dev_cnt = 0;
+ device = NULL;
+ for(i = 0; i < session->num_device_list; i++) {
+ if(is_camera(session, session->device_list[i]) == TRUE) {
+ if(device_nr == dev_cnt) {
+ device = session->device_list[i];
+ break;
+ }
+ dev_cnt++;
+ }
+ }
+
+ if(device == NULL) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: USB device not available\n", __func__);
+ error_code = O3000_ERROR_NODEV;
+ goto _connect_abort;
+ }
+
+ // now open the device
+ ret = libusb_open(device, &handle);
+ if(ret != 0) {
+ if(ret == LIBUSB_ERROR_NO_MEM) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: opening device failed: no memory\n", __func__);
+ error_code = O3000_ERROR_NOMEM;
+ }
+ else if(ret == LIBUSB_ERROR_ACCESS) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: opening device failed: no permission\n", __func__);
+ error_code = O3000_ERROR_ACCESS;
+ }
+ else if(ret == LIBUSB_ERROR_NO_DEVICE) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: opening device failed: no such device\n", __func__);
+ error_code = O3000_ERROR_NODEV;
+ }
+ else {
+ o3000_log(session, O3000_LOG_ERROR, "%s: opening device failed: other\n", __func__);
+ error_code = O3000_ERROR_OTHER;
+ }
+ goto _connect_abort;
+ }
+ session->dev = handle;
+
+ // now claim the interface
+ ret = libusb_claim_interface(handle, 0);
+ if(ret != 0) {
+ if(ret == LIBUSB_ERROR_NOT_FOUND) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: claiming device failed: requested device does not exist\n", __func__);
+ error_code = O3000_ERROR_NODEV;
+ }
+ else if(ret == LIBUSB_ERROR_BUSY) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: claiming device failed: device already in use\n", __func__);
+ error_code = O3000_ERROR_BUSY;
+ }
+ else if(ret == LIBUSB_ERROR_NO_DEVICE) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: claiming device failed: device has been disconnected\n", __func__);
+ error_code = O3000_ERROR_NODEV;
+ }
+ else {
+ o3000_log(session, O3000_LOG_ERROR, "%s: claiming device failed:other\n", __func__);
+ error_code = O3000_ERROR_OTHER;
+ }
+ goto _connect_abort;
+ }
+
+ // setup all USB transfers (video and XML)
+ if(prepare_usb_transfers(session)) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: preparing USB transfers failed\n", __func__);
+ error_code = O3000_ERROR_OTHER;
+ goto _connect_abort;
+ }
+
+ /*
+ * Send configuration message to device if available
+ */
+ if(config_msg != NULL && config_msg_len > 0) {
+ o3000_log(session, O3000_LOG_INFO, "%s: send configuration string\n", __func__);
+ ret = send_xml(session, config_msg, config_msg_len);
+ if(ret != 0) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: sending configuration string failed (code %d)\n", __func__, ret);
+ }
+ }
+
+ /*
+ * Finally enter main-loop
+ */
+ o3000_log(session, O3000_LOG_INFO, "%s: enter blocking loop for event handling\n", __func__);
+ session->running = TRUE;
+ while(session->running == TRUE) {
+ completed = 0;
+ ret = libusb_handle_events_completed(session->libusb_ctx, &completed);
+ if(ret != 0) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: USB event handling error (code %d)\n", __func__, ret);
+ break;
+ }
+ }
+
+
+ /*
+ * There are following reasons to get here:
+ *
+ * 1. o3000_disconnect() was called
+ * 2. o3000_exit() was called
+ * 3. the device isn't available anymore because it was unplugged, it does an USB hardware reset
+ *
+ * The current session is cleaned-up only if o3000_exit() has been called. Otherwise the session will further exist and a reconnection with o3000_connect() will be allowed.
+ * Actually, it doesn't make any sense to connect to an unavailable device. But to keep it simple, a session is cleaned-up only with o3000_exit() without any exception.
+ */
+ if(session->release_session == FALSE && session->disconnect_dev == FALSE) {
+ // device was unplugged (see reason 3 few lines above)
+ o3000_log(session, O3000_LOG_DEBUG, "%s: release interface\n", __func__);
+ ret = libusb_release_interface(session->dev, 0);
+ if(ret != 0) {
+ o3000_log(session, O3000_LOG_ERROR, "%s: releasing interface failed (error code %d)\n", __func__, ret);
+ }
+ ret = O3000_ERROR_NODEV;
+ }
+
+ // close USB device
+ if(session->dev != NULL) {
+ o3000_log(session, O3000_LOG_DEBUG, "%s: close USB device handler\n", __func__);
+ libusb_close(session->dev);
+ session->dev = NULL;
+ }
+
+ // release session due to o3000_exit() call
+ if(session->release_session == TRUE) {
+ o3000_log(session, O3000_LOG_DEBUG, "%s: cleanup session\n", __func__);
+ session->release_session = FALSE;
+ cleanup(id);
+ }
+ return ret;
+
+_connect_abort:
+ // close USB device
+ if(session->dev != NULL) {
+ o3000_log(session, O3000_LOG_DEBUG, "%s: close USB device handler\n", __func__);
+ libusb_close(session->dev);
+ session->dev = NULL;
+ }
+ return error_code;
+}
+
+
+/**
+ * The device discovery returns the number of USB devices in question.
+ * In fact, the number of devices with the session PID/VID are counted. After knowing the number of devices, any connection can be
+ * established with o3000_connect().
+ *
+ * @param id valid session ID
+ * @return number of USB device or a negative return code
+ */
+int __stdcall o3000_device_discovery(int id) {
+
+ int num_device, i, num_camera;
+ struct o3000_session_t *session;
+
+
+ session = get_session(id);
+ if(session == NULL) {
+ return O3000_ERROR_INVALID_SESSION_ID;
+ }
+
+ // check whether device list is allocated already
+ if(session->device_list != NULL) {
+ libusb_free_device_list(session->device_list, 1);
+ session->device_list = NULL;
+ }
+
+ // get list with all USB device currently attached to the system
+ session->num_device_list = 0;
+ num_device = libusb_get_device_list(session->libusb_ctx, &(session->device_list));
+ o3000_log(session, O3000_LOG_DEBUG, "%s: %d USB connected to the system\n", __func__, num_device);
+
+ if(num_device < 0) {
+ o3000_log(session, O3000_LOG_WARNING, "%s: currently no USB device attached to the system (code %d)\n", __func__, num_device);
+ return O3000_ERROR_NODEV;
+ }
+ session->num_device_list = num_device;
+
+ // count number of devices in question
+ num_camera = 0;
+ for(i = 0; i < num_device; i++) {
+ if(is_camera(session, session->device_list[i]) == TRUE) {
+ num_camera++;
+ }
+ }
+
+ o3000_log(session, O3000_LOG_INFO, "%s: number of cameras found: %d\n", __func__, num_camera);
+ return num_camera;
+}
+
+
+/**
+ * Get number of connected O-3000 cameras to the system.
+ *
+ * This function is thread save and can be called without an already opened
+ * session.
+ *
+ * @return number of connected cameras or negative error code (see @ref o3000.h)
+ */
+int __stdcall o3000_get_num_cam(void) {
+
+ libusb_context *libusb_ctx;
+ libusb_device **device_list;
+ int num_device, num_camera, i;
+ struct libusb_device_descriptor desc;
+
+ if(libusb_init(&libusb_ctx)) {
+ return O3000_ERROR_OTHER;
+ }
+
+ num_device = libusb_get_device_list(libusb_ctx, &device_list);
+
+ if(num_device < 0) {
+ libusb_exit(libusb_ctx);
+ return O3000_ERROR_NODEV;
+ }
+
+ // count number of devices in question
+ num_camera = 0;
+ for(i = 0; i < num_device; i++) {
+ if(libusb_get_device_descriptor(device_list[i], &desc)) {
+ libusb_free_device_list(device_list, 1);
+ libusb_exit(libusb_ctx);
+ return O3000_ERROR_OTHER;
+ }
+ if((desc.idVendor != O3000_VID) || (desc.idProduct != O3000_PID)) {
+ continue;
+ }
+ num_camera++;
+ }
+
+ libusb_free_device_list(device_list, 1);
+ libusb_exit(libusb_ctx);
+ return num_camera;
+}
+
+
+/**
+ * Logging function.
+ *
+ * Concatenates level and message and forwards the resulting string to the logging function.
+ *
+ * @param session session pointer
+ * @param level The logging level (from @ref O3000_LOG_NONE to @ref O3000_LOG_DEBUG)
+ * @param fmt The format string
+ */
+void __stdcall o3000_log(struct o3000_session_t *session, const int level, const char *fmt, ...) {
+
+ va_list args;
+ char str[1024];
+ char *str_ptr = str;
+ int level_int;
+ int max_level;
+
+ if(level > session->loglevel) {
+ return; // ignore messages beyond session loglevel
+ }
+
+ if(session->log_cb == NULL) {
+ // session has no callback registered; ignore request
+ return;
+ }
+
+ if(fmt == NULL) {
+ return;
+ }
+
+ // check range of level and limit (clamp) it
+ max_level = sizeof(dbg_strlevel)/sizeof(char*)-1;
+ level_int = (level > max_level) ? max_level : level;
+
+ str_ptr += sprintf(str_ptr, "%s: ", dbg_strlevel[level_int]);
+
+ va_start(args, fmt);
+ vsprintf(str, fmt, args);
+ va_end(args);
+
+ session->log_cb(session->id, str);
+}
+
+
+/**
+ * Initialize O-3000 session.
+ * A new O-3000 session is initialized by calling this function. Several buffers are allocated and the USB context is setup.
+ * The driver automatically divides the video cache to @ref NUM_USB_VIDEO_TRANSFER USB transfers. The video cache size must be at least
+ * an image frame size. It's recommended that the size is more the 2 times the maximum expected image size.
+ *
+ * A suitable video cache size is calculated by following example:
+ * Maximum expected image resolution: 1280x960 with 2 bytes/pixel
+ * Recommended video cache size: 1280*960*2*3 = 3686400 bytes = 3.5 MByte (3 times the image size)
+ *
+ * NOTE
+ * This function must be called before connecting to any O-3000 camera.
+ *
+ * @param vid USB vendor ID; use default VID if 0
+ * @param pid USB product ID; use default PID if 0
+ * @param video_cache_size Requested video cache size (in bytes); note: might be adjusted by the driver
+ * @param xml_cb XML receive callback
+ * @param video_cb Video receive callback
+ * @param log_cb Logging callback, or NULL if not used
+ * @param loglevel logging level (from @ref O3000_LOG_NONE to @ref O3000_LOG_VERBOSE)
+ * @return positive session ID starting from 0, or error code (see @ref o3000.h)
+ */
+int __stdcall o3000_init(int vid, int pid, unsigned int video_cache_size,
+ void __stdcall (*xml_cb)(int id, char* buf, int len),
+ void __stdcall (*video_cb)(int id, unsigned char* buf, struct img_header_t* img_header),
+ void __stdcall (*log_cb)(int id, char* msg),
+ int loglevel) {
+
+ int i, new_session_id, ret;
+ struct o3000_session_t *new_session;
+ int error_code = O3000_ERROR_OTHER;
+ int transfer_size, total_buffer_size;
+
+
+ // Validate callbacks
+ if(xml_cb == NULL || video_cb == NULL) {
+ return O3000_ERROR_NOCALLBACK;
+ }
+
+ /*
+ * Look for next free session
+ * Session table access must be atmic
+ */
+ new_session_id = -1;
+ o3000_mutex_static_lock(&session_lock);
+ for(i = 0; i < MAX_NUM_SESSION; i++) {
+ if(session_table[i] == NULL) {
+ new_session_id = i;
+ break;
+ }
+ }
+
+ if(new_session_id == -1) {
+ return O3000_ERROR_NO_FREE_SESSION;
+ }
+
+ // allocate session and save session data
+ new_session = calloc(1, sizeof(struct o3000_session_t));
+ if(new_session == NULL) {
+ return O3000_ERROR_NOMEM;
+ }
+ session_table[new_session_id] = new_session;
+ o3000_mutex_static_unlock(&session_lock);
+
+ new_session->id = new_session_id;
+ new_session->vid = ((vid == 0) ? O3000_VID : vid); // set default if vid not set
+ new_session->pid = ((pid == 0) ? O3000_PID : pid); // set default if vid not set
+
+ if(loglevel < O3000_LOG_NONE || loglevel > O3000_LOG_VERBOSE) {
+ new_session->loglevel = O3000_LOG_NONE;
+ }
+ else{
+ new_session->loglevel = loglevel;
+ }
+
+ new_session->xml_cb = xml_cb;
+ new_session->video_cb = video_cb;
+ new_session->log_cb = log_cb;
+
+
+ // open libusb context
+ ret = libusb_init(&(new_session->libusb_ctx));
+ if(ret != 0) {
+ o3000_log(new_session, O3000_LOG_ERROR, "%s: initializing libusb context failed (code %d)\n", __func__, ret);
+ error_code = O3000_ERROR_OTHER;
+ goto _o3000_init_abort;
+ }
+
+ /*
+ * If loglevel is set to O3000_LOG_VERBOSE, do set libusb loglevel to debug.
+ * This will flood the logging handler and is useful for debugging purposes only!
+ */
+ if(loglevel == O3000_LOG_VERBOSE) {
+ libusb_set_debug(new_session->libusb_ctx, LIBUSB_LOG_LEVEL_DEBUG);
+ }
+ else {
+ libusb_set_debug(new_session->libusb_ctx, LIBUSB_LOG_LEVEL_NONE);
+ }
+
+ // allocate XML buffer
+ if((new_session->xml_in_buf = malloc(XML_IN_BUF_SIZE)) == NULL) {
+ o3000_log(new_session, O3000_LOG_ERROR, "%s: failed to allocate XML buffer\n", __func__);
+ error_code = O3000_ERROR_NOMEM;
+ goto _o3000_init_abort;
+ }
+
+
+ /*
+ * The image size must be larger or equal to the maximum image size ever to be expected. The
+ * video cache is divided in several adjacent bulk transfers.
+ */
+ if(video_cache_size < MIN_VIDEO_CACHE_SIZE) {
+ video_cache_size = MIN_VIDEO_CACHE_SIZE;
+ }
+
+
+ // calculate video cache
+ transfer_size = video_cache_size/NUM_USB_VIDEO_TRANSFER;
+ if(transfer_size % 512) {
+ /*
+ * Do adjust USB bulk transfer size to a multiple of 512 bytes which is
+ * equal to the bulk payload size (at high-speed).
+ */
+ transfer_size = ((transfer_size/512)+1)*512;
+ }
+ video_cache_size = NUM_USB_VIDEO_TRANSFER * transfer_size;
+
+ // calculate and allocate total buffer size
+ total_buffer_size = video_cache_size + MAX_IMAGE_SIZE;
+ if((new_session->video_cache = malloc(total_buffer_size)) == NULL) {
+ o3000_log(new_session, O3000_LOG_ERROR, "%s: failed to allocate video cache\n", __func__);
+ error_code = O3000_ERROR_NOMEM;
+ goto _o3000_init_abort;
+ }
+ new_session->frame_buf = new_session->video_cache + video_cache_size;
+ new_session->video_cache_size = video_cache_size;
+ new_session->video_chunk_size = transfer_size;
+ o3000_log(new_session, O3000_LOG_DEBUG, "%s: video cache %d bytes (divided into %d chunks of %d bytes)\n",
+ __func__, video_cache_size, NUM_USB_VIDEO_TRANSFER, transfer_size);
+
+ // allocate video transfer array
+ new_session->transfer_data = calloc(1, sizeof(struct libusb_transfer*)*NUM_USB_VIDEO_TRANSFER);
+ if(new_session->transfer_data == NULL) {
+ o3000_log(new_session, O3000_LOG_ERROR, "%s: allocating USB video transfers failed\n", __func__);
+ error_code = O3000_ERROR_NOMEM;
+ goto _o3000_init_abort;
+ }
+
+ o3000_log(new_session, O3000_LOG_INFO, "%s: new session initialized (ID %d, video cache %d bytes, video chunk %d x %d bytes)\n",
+ __func__, new_session_id, video_cache_size, NUM_USB_VIDEO_TRANSFER, new_session->video_chunk_size);
+
+ // finally return the valid session ID
+ return new_session_id;
+
+_o3000_init_abort:
+ cleanup(new_session_id);
+ return error_code;
+}
+
+
+/**
+ * Exit O-3000 session.
+ * This function closes an active session and releases all system resources.
+ *
+ * @param id session ID
+ */
+void __stdcall o3000_exit(int id) {
+
+ struct o3000_session_t *session;
+
+
+ session = get_session(id);
+ if(session == NULL) {
+ return;
+ }
+
+ if(session->running == FALSE) {
+ o3000_log(session, O3000_LOG_INFO, "%s: driver not at main-loop --> cleanup session\n", __func__, id);
+ cleanup(id);
+ }
+ else {
+ o3000_log(session, O3000_LOG_DEBUG, "%s: driver at main-loop --> release USB interface\n", __func__, id);
+ session->release_session = TRUE;
+ o3000_disconnect(id);
+ }
+}
+
+/** @} */