aboutsummaryrefslogtreecommitdiffstats
path: root/o3000.c
blob: dd99f97c26271df731b379680074e8dbdbf751fe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
/**
* @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_portable.h"
#include "o3000_private.h"
#include "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	


/**
 * Minimum video cache size.
 * It's equal to the maximum image size which would appear.
 */
#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;
	
	o3000_log(new_session, O3000_LOG_INFO, "%s: O-3000 driver version %s\n", __func__, O3000_VERSION);
	
	
	// 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);
	}
}

/** @} */