1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 /* 18 * Contains emulated camera service implementation. 19 */ 20 21 #include "qemu-common.h" 22 #include "android/globals.h" /* for android_hw */ 23 #include "android/hw-qemud.h" 24 #include "android/utils/misc.h" 25 #include "android/utils/system.h" 26 #include "android/utils/debug.h" 27 #include "android/camera/camera-capture.h" 28 #include "android/camera/camera-format-converters.h" 29 #include "android/camera/camera-service.h" 30 31 #define E(...) derror(__VA_ARGS__) 32 #define W(...) dwarning(__VA_ARGS__) 33 #define D(...) VERBOSE_PRINT(camera,__VA_ARGS__) 34 #define D_ACTIVE VERBOSE_CHECK(camera) 35 36 /* the T(...) macro is used to dump traffic */ 37 #define T_ACTIVE 0 38 39 #if T_ACTIVE 40 #define T(...) VERBOSE_PRINT(camera,__VA_ARGS__) 41 #else 42 #define T(...) ((void)0) 43 #endif 44 45 /* Defines name of the camera service. */ 46 #define SERVICE_NAME "camera" 47 48 /* Maximum number of supported emulated cameras. */ 49 #define MAX_CAMERA 8 50 51 /* Camera sevice descriptor. */ 52 typedef struct CameraServiceDesc CameraServiceDesc; 53 struct CameraServiceDesc { 54 /* Information about camera devices connected to the host. 55 * Note that once initialized, entries in this array are considered to be 56 * constant. */ 57 CameraInfo camera_info[MAX_CAMERA]; 58 /* Number of camera devices connected to the host. */ 59 int camera_count; 60 }; 61 62 /* One and only one camera service. */ 63 static CameraServiceDesc _camera_service_desc; 64 65 /******************************************************************************** 66 * Helper routines 67 *******************************************************************************/ 68 69 /* Extracts query name, and (optionally) query parameters from the query string. 70 * Param: 71 * query - Query string. Query string in the camera service are formatted as such: 72 * "<query name>[ <parameters>]", 73 * where parameters are optional, and if present, must be separated from the 74 * query name with a single ' '. See comments to get_token_value routine 75 * for the format of the parameters string. 76 * query_name - Upon success contains query name extracted from the query 77 * string. 78 * query_name_size - Buffer size for 'query_name' string. 79 * query_param - Upon success contains a pointer to the beginning of the query 80 * parameters. If query has no parameters, NULL will be passed back with 81 * this parameter. This parameter is optional and can be NULL. 82 * Return: 83 * 0 on success, or number of bytes required for query name if 'query_name' 84 * string buffer was too small to contain it. 85 */ 86 static int 87 _parse_query(const char* query, 88 char* query_name, 89 int query_name_size, 90 const char** query_param) 91 { 92 /* Extract query name. */ 93 const char* qend = strchr(query, ' '); 94 if (qend == NULL) { 95 qend = query + strlen(query); 96 } 97 if ((qend - query) >= query_name_size) { 98 return qend - query + 1; 99 } 100 memcpy(query_name, query, qend - query); 101 query_name[qend - query] = '\0'; 102 103 /* Calculate query parameters pointer (if needed) */ 104 if (query_param != NULL) { 105 if (*qend == ' ') { 106 qend++; 107 } 108 *query_param = (*qend == '\0') ? NULL : qend; 109 } 110 111 return 0; 112 } 113 114 /* Appends one string to another, growing the destination string buffer if 115 * needed. 116 * Param: 117 * str_buffer - Contains pointer to the destination string buffer. Content of 118 * this parameter can be NULL. Note that content of this parameter will 119 * change if string buffer has been reallocated. 120 * str_buf_size - Contains current buffer size of the string, addressed by 121 * 'str_buffer' parameter. Note that content of this parameter will change 122 * if string buffer has been reallocated. 123 * str - String to append. 124 * Return: 125 * 0 on success, or -1 on failure (memory allocation). 126 */ 127 static int 128 _append_string(char** str_buf, size_t* str_buf_size, const char* str) 129 { 130 const size_t offset = (*str_buf != NULL) ? strlen(*str_buf) : 0; 131 const size_t append_bytes = strlen(str) + 1; 132 133 /* Make sure these two match. */ 134 if (*str_buf == NULL) { 135 *str_buf_size = 0; 136 } 137 138 if ((offset + append_bytes) > *str_buf_size) { 139 /* Reallocate string, so it can fit what's being append to it. Note that 140 * we reallocate a bit bigger buffer than is needed in order to minimize 141 * number of memory allocation calls in case there are more "appends" 142 * coming. */ 143 const size_t required_mem = offset + append_bytes + 256; 144 char* new_buf = (char*)realloc(*str_buf, required_mem); 145 if (new_buf == NULL) { 146 E("%s: Unable to allocate %d bytes for a string", 147 __FUNCTION__, required_mem); 148 return -1; 149 } 150 *str_buf = new_buf; 151 *str_buf_size = required_mem; 152 } 153 memcpy(*str_buf + offset, str, append_bytes); 154 155 return 0; 156 } 157 158 /* Represents camera information as a string formatted as follows: 159 * 'name=<devname> channel=<num> pix=<format> facing=<direction> framedims=<widh1xheight1,...>\n' 160 * Param: 161 * ci - Camera information descriptor to convert into a string. 162 * str - Pointer to the string buffer where to save the converted camera 163 * information descriptor. On entry, content of this parameter can be NULL. 164 * Note that string buffer addressed with this parameter may be reallocated 165 * in this routine, so (if not NULL) it must contain a buffer allocated with 166 * malloc. The caller is responsible for freeing string buffer returned in 167 * this parameter. 168 * str_size - Contains byte size of the buffer addressed by 'str' parameter. 169 * Return: 170 * 0 on success, or != 0 on failure. 171 */ 172 static int 173 _camera_info_to_string(const CameraInfo* ci, char** str, size_t* str_size) { 174 int res; 175 int n; 176 char tmp[128]; 177 178 /* Append device name. */ 179 snprintf(tmp, sizeof(tmp), "name=%s ", ci->device_name); 180 res = _append_string(str, str_size, tmp); 181 if (res) { 182 return res; 183 } 184 /* Append input channel. */ 185 snprintf(tmp, sizeof(tmp), "channel=%d ", ci->inp_channel); 186 res = _append_string(str, str_size, tmp); 187 if (res) { 188 return res; 189 } 190 /* Append pixel format. */ 191 snprintf(tmp, sizeof(tmp), "pix=%d ", ci->pixel_format); 192 res = _append_string(str, str_size, tmp); 193 if (res) { 194 return res; 195 } 196 /* Append direction. */ 197 snprintf(tmp, sizeof(tmp), "dir=%s ", ci->direction); 198 res = _append_string(str, str_size, tmp); 199 if (res) { 200 return res; 201 } 202 /* Append supported frame sizes. */ 203 snprintf(tmp, sizeof(tmp), "framedims=%dx%d", 204 ci->frame_sizes[0].width, ci->frame_sizes[0].height); 205 res = _append_string(str, str_size, tmp); 206 if (res) { 207 return res; 208 } 209 for (n = 1; n < ci->frame_sizes_num; n++) { 210 snprintf(tmp, sizeof(tmp), ",%dx%d", 211 ci->frame_sizes[n].width, ci->frame_sizes[n].height); 212 res = _append_string(str, str_size, tmp); 213 if (res) { 214 return res; 215 } 216 } 217 218 /* Stringified camera properties should end with EOL. */ 219 return _append_string(str, str_size, "\n"); 220 } 221 222 /* Gets camera information matching a display name. 223 * Param: 224 * disp_name - Display name to match. 225 * arr - Array of camera informations. 226 * num - Number of elements in the array. 227 * Return: 228 * Matching camera information, or NULL if matching camera information for the 229 * given display name has not been found in the array. 230 */ 231 static CameraInfo* 232 _camera_info_get_by_display_name(const char* disp_name, CameraInfo* arr, int num) 233 { 234 int n; 235 for (n = 0; n < num; n++) { 236 if (!arr[n].in_use && arr[n].display_name != NULL && 237 !strcmp(arr[n].display_name, disp_name)) { 238 return &arr[n]; 239 } 240 } 241 return NULL; 242 } 243 244 /* Gets camera information matching a device name. 245 * Param: 246 * device_name - Device name to match. 247 * arr - Array of camera informations. 248 * num - Number of elements in the array. 249 * Return: 250 * Matching camera information, or NULL if matching camera information for the 251 * given device name has not been found in the array. 252 */ 253 static CameraInfo* 254 _camera_info_get_by_device_name(const char* device_name, CameraInfo* arr, int num) 255 { 256 int n; 257 for (n = 0; n < num; n++) { 258 if (arr[n].device_name != NULL && !strcmp(arr[n].device_name, device_name)) { 259 return &arr[n]; 260 } 261 } 262 return NULL; 263 } 264 265 /******************************************************************************** 266 * CameraServiceDesc API 267 *******************************************************************************/ 268 269 /* Initialized webcam emulation record in camera service descriptor. 270 * Param: 271 * csd - Camera service descriptor to initialize a record in. 272 * disp_name - Display name of a web camera ('webcam<N>') to use for emulation. 273 * dir - Direction ('back', or 'front') that emulated camera is facing. 274 * ci, ci_cnt - Array of webcam information for enumerated web cameras connected 275 * to the host. 276 */ 277 static void 278 _wecam_setup(CameraServiceDesc* csd, 279 const char* disp_name, 280 const char* dir, 281 CameraInfo* ci, 282 int ci_cnt) 283 { 284 /* Find webcam record in the list of enumerated web cameras. */ 285 CameraInfo* found = _camera_info_get_by_display_name(disp_name, ci, ci_cnt); 286 if (found == NULL) { 287 W("Camera name '%s' is not found in the list of connected cameras.\n" 288 "Use '-webcam-list' emulator option to obtain the list of connected camera names.\n", 289 disp_name); 290 return; 291 } 292 293 /* Save to the camera info array that will be used by the service. */ 294 memcpy(csd->camera_info + csd->camera_count, found, sizeof(CameraInfo)); 295 /* This camera is taken. */ 296 found->in_use = 1; 297 /* Update direction parameter. */ 298 if (csd->camera_info[csd->camera_count].direction != NULL) { 299 free(csd->camera_info[csd->camera_count].direction); 300 } 301 csd->camera_info[csd->camera_count].direction = ASTRDUP(dir); 302 D("Camera %d '%s' connected to '%s' facing %s using %.4s pixel format", 303 csd->camera_count, csd->camera_info[csd->camera_count].display_name, 304 csd->camera_info[csd->camera_count].device_name, 305 csd->camera_info[csd->camera_count].direction, 306 (const char*)(&csd->camera_info[csd->camera_count].pixel_format)); 307 csd->camera_count++; 308 } 309 310 /* Initializes camera service descriptor. 311 */ 312 static void 313 _camera_service_init(CameraServiceDesc* csd) 314 { 315 CameraInfo ci[MAX_CAMERA]; 316 int connected_cnt; 317 318 /* Enumerate camera devices connected to the host. */ 319 memset(ci, 0, sizeof(CameraInfo) * MAX_CAMERA); 320 memset(csd->camera_info, 0, sizeof(CameraInfo) * MAX_CAMERA); 321 csd->camera_count = 0; 322 323 /* Lets see if HW config uses web cameras. */ 324 if (memcmp(android_hw->hw_camera_back, "webcam", 6) && 325 memcmp(android_hw->hw_camera_front, "webcam", 6)) { 326 /* Web camera emulation is disabled. Skip enumeration of webcameras. */ 327 return; 328 } 329 330 /* Enumerate web cameras connected to the host. */ 331 connected_cnt = enumerate_camera_devices(ci, MAX_CAMERA); 332 if (connected_cnt <= 0) { 333 /* Nothing is connected - nothing to emulate. */ 334 return; 335 } 336 337 /* Set up back camera emulation. */ 338 if (!memcmp(android_hw->hw_camera_back, "webcam", 6)) { 339 _wecam_setup(csd, android_hw->hw_camera_back, "back", ci, connected_cnt); 340 } 341 342 /* Set up front camera emulation. */ 343 if (!memcmp(android_hw->hw_camera_front, "webcam", 6)) { 344 _wecam_setup(csd, android_hw->hw_camera_front, "front", ci, connected_cnt); 345 } 346 } 347 348 /* Gets camera information for the given camera device name. 349 * Param: 350 * cs - Initialized camera service descriptor. 351 * device_name - Camera's device name to look up the information for. 352 * Return: 353 * Camera information pointer on success, or NULL if no camera information has 354 * been found for the given device name. 355 */ 356 static CameraInfo* 357 _camera_service_get_camera_info_by_device_name(CameraServiceDesc* cs, 358 const char* device_name) 359 { 360 return _camera_info_get_by_device_name(device_name, cs->camera_info, 361 cs->camera_count); 362 } 363 364 /******************************************************************************** 365 * Helpers for handling camera client queries 366 *******************************************************************************/ 367 368 /* Formats paload size according to the protocol, and sends it to the client. 369 * To simplify endianess handling we convert payload size to an eight characters 370 * string, representing payload size value in hexadecimal format. 371 * Param: 372 * qc - Qemu client to send the payload size to. 373 * payload_size - Payload size to report to the client. 374 */ 375 static void 376 _qemu_client_reply_payload(QemudClient* qc, size_t payload_size) 377 { 378 char payload_size_str[9]; 379 snprintf(payload_size_str, sizeof(payload_size_str), "%08zx", payload_size); 380 qemud_client_send(qc, (const uint8_t*)payload_size_str, 8); 381 } 382 383 /* 384 * Prefixes for replies to camera client queries. 385 */ 386 387 /* Success, no data to send in reply. */ 388 #define OK_REPLY "ok" 389 /* Failure, no data to send in reply. */ 390 #define KO_REPLY "ko" 391 /* Success, there are data to send in reply. */ 392 #define OK_REPLY_DATA OK_REPLY ":" 393 /* Failure, there are data to send in reply. */ 394 #define KO_REPLY_DATA KO_REPLY ":" 395 396 /* Builds and sends a reply to a query. 397 * All replies to a query in camera service have a prefix indicating whether the 398 * query has succeeded ("ok"), or failed ("ko"). The prefix can be followed by 399 * extra data, containing response to the query. In case there are extra data, 400 * they are separated from the prefix with a ':' character. 401 * Param: 402 * qc - Qemu client to send the reply to. 403 * ok_ko - An "ok", or "ko" selector, where 0 is for "ko", and !0 is for "ok". 404 * extra - Optional extra query data. Can be NULL. 405 * extra_size - Extra data size. 406 */ 407 static void 408 _qemu_client_query_reply(QemudClient* qc, 409 int ok_ko, 410 const void* extra, 411 size_t extra_size) 412 { 413 const char* ok_ko_str; 414 size_t payload_size; 415 416 /* Make sure extra_size is 0 if extra is NULL. */ 417 if (extra == NULL && extra_size != 0) { 418 W("%s: 'extra' = NULL, while 'extra_size' = %d", 419 __FUNCTION__, (int)extra_size); 420 extra_size = 0; 421 } 422 423 /* Calculate total payload size, and select appropriate 'ok'/'ko' prefix */ 424 if (extra_size) { 425 /* 'extra' size + 2 'ok'/'ko' bytes + 1 ':' separator byte. */ 426 payload_size = extra_size + 3; 427 ok_ko_str = ok_ko ? OK_REPLY_DATA : KO_REPLY_DATA; 428 } else { 429 /* No extra data: just zero-terminated 'ok'/'ko'. */ 430 payload_size = 3; 431 ok_ko_str = ok_ko ? OK_REPLY : KO_REPLY; 432 } 433 434 /* Send payload size first. */ 435 _qemu_client_reply_payload(qc, payload_size); 436 /* Send 'ok[:]'/'ko[:]' next. Note that if there is no extra data, we still 437 * need to send a zero-terminator for 'ok'/'ko' string instead of the ':' 438 * separator. So, one way or another, the prefix is always 3 bytes. */ 439 qemud_client_send(qc, (const uint8_t*)ok_ko_str, 3); 440 /* Send extra data (if present). */ 441 if (extra != NULL) { 442 qemud_client_send(qc, (const uint8_t*)extra, extra_size); 443 } 444 } 445 446 /* Replies query success ("OK") back to the client. 447 * Param: 448 * qc - Qemu client to send the reply to. 449 * ok_str - An optional string containing query results. Can be NULL. 450 */ 451 static void 452 _qemu_client_reply_ok(QemudClient* qc, const char* ok_str) 453 { 454 _qemu_client_query_reply(qc, 1, ok_str, 455 (ok_str != NULL) ? (strlen(ok_str) + 1) : 0); 456 } 457 458 /* Replies query failure ("KO") back to the client. 459 * Param: 460 * qc - Qemu client to send the reply to. 461 * ko_str - An optional string containing reason for failure. Can be NULL. 462 */ 463 static void 464 _qemu_client_reply_ko(QemudClient* qc, const char* ko_str) 465 { 466 _qemu_client_query_reply(qc, 0, ko_str, 467 (ko_str != NULL) ? (strlen(ko_str) + 1) : 0); 468 } 469 470 /******************************************************************************** 471 * Camera Factory API 472 *******************************************************************************/ 473 474 /* Handles 'list' query received from the Factory client. 475 * Response to this query is a string that represents each connected camera in 476 * this format: 'name=devname framedims=widh1xheight1,widh2xheight2,widhNxheightN\n' 477 * Strings, representing each camera are separated with EOL symbol. 478 * Param: 479 * csd, client - Factory serivice, and client. 480 * Return: 481 * 0 on success, or != 0 on failure. 482 */ 483 static int 484 _factory_client_list_cameras(CameraServiceDesc* csd, QemudClient* client) 485 { 486 int n; 487 size_t reply_size = 0; 488 char* reply = NULL; 489 490 /* Lets see if there was anything found... */ 491 if (csd->camera_count == 0) { 492 /* No cameras connected to the host. Reply with "\n" */ 493 _qemu_client_reply_ok(client, "\n"); 494 return 0; 495 } 496 497 /* "Stringify" each camera information into the reply string. */ 498 for (n = 0; n < csd->camera_count; n++) { 499 const int res = 500 _camera_info_to_string(csd->camera_info + n, &reply, &reply_size); 501 if (res) { 502 if (reply != NULL) { 503 free(reply); 504 } 505 _qemu_client_reply_ko(client, "Memory allocation error"); 506 return res; 507 } 508 } 509 510 D("%s Replied: %s", __FUNCTION__, reply); 511 _qemu_client_reply_ok(client, reply); 512 free(reply); 513 514 return 0; 515 } 516 517 /* Handles a message received from the emulated camera factory client. 518 * Queries received here are represented as strings: 519 * 'list' - Queries list of cameras connected to the host. 520 * Param: 521 * opaque - Camera service descriptor. 522 * msg, msglen - Message received from the camera factory client. 523 * client - Camera factory client pipe. 524 */ 525 static void 526 _factory_client_recv(void* opaque, 527 uint8_t* msg, 528 int msglen, 529 QemudClient* client) 530 { 531 /* 532 * Emulated camera factory client queries. 533 */ 534 535 /* List cameras connected to the host. */ 536 static const char _query_list[] = "list"; 537 538 CameraServiceDesc* csd = (CameraServiceDesc*)opaque; 539 char query_name[64]; 540 const char* query_param = NULL; 541 542 /* Parse the query, extracting query name and parameters. */ 543 if (_parse_query((const char*)msg, query_name, sizeof(query_name), 544 &query_param)) { 545 E("%s: Invalid format in query '%s'", __FUNCTION__, (const char*)msg); 546 _qemu_client_reply_ko(client, "Invalid query format"); 547 return; 548 } 549 550 D("%s Camera factory query '%s'", __FUNCTION__, query_name); 551 552 /* Dispatch the query to an appropriate handler. */ 553 if (!strcmp(query_name, _query_list)) { 554 /* This is a "list" query. */ 555 _factory_client_list_cameras(csd, client); 556 } else { 557 E("%s: Unknown camera factory query name in '%s'", 558 __FUNCTION__, (const char*)msg); 559 _qemu_client_reply_ko(client, "Unknown query name"); 560 } 561 } 562 563 /* Emulated camera factory client has been disconnected from the service. */ 564 static void 565 _factory_client_close(void* opaque) 566 { 567 /* There is nothing to clean up here: factory service is just an alias for 568 * the "root" camera service, that doesn't require anything more, than camera 569 * dervice descriptor already provides. */ 570 } 571 572 /******************************************************************************** 573 * Camera client API 574 *******************************************************************************/ 575 576 /* Describes an emulated camera client. 577 */ 578 typedef struct CameraClient CameraClient; 579 struct CameraClient 580 { 581 /* Client name. 582 * On Linux this is the name of the camera device. 583 * On Windows this is the name of capturing window. 584 */ 585 char* device_name; 586 /* Input channel to use to connect to the camera. */ 587 int inp_channel; 588 /* Camera information. */ 589 const CameraInfo* camera_info; 590 /* Emulated camera device descriptor. */ 591 CameraDevice* camera; 592 /* Buffer allocated for video frames. 593 * Note that memory allocated for this buffer 594 * also contains preview framebuffer. */ 595 uint8_t* video_frame; 596 /* Preview frame buffer. 597 * This address points inside the 'video_frame' buffer. */ 598 uint16_t* preview_frame; 599 /* Byte size of the videoframe buffer. */ 600 size_t video_frame_size; 601 /* Byte size of the preview frame buffer. */ 602 size_t preview_frame_size; 603 /* Pixel format required by the guest. */ 604 uint32_t pixel_format; 605 /* Frame width. */ 606 int width; 607 /* Frame height. */ 608 int height; 609 /* Number of pixels in a frame buffer. */ 610 int pixel_num; 611 /* Status of video and preview frame cache. */ 612 int frames_cached; 613 }; 614 615 /* Frees emulated camera client descriptor. */ 616 static void 617 _camera_client_free(CameraClient* cc) 618 { 619 /* The only exception to the "read only" rule: we have to mark the camera 620 * as being not used when we destroy a service for it. */ 621 if (cc->camera_info != NULL) { 622 ((CameraInfo*)cc->camera_info)->in_use = 0; 623 } 624 if (cc->camera != NULL) { 625 camera_device_close(cc->camera); 626 } 627 if (cc->video_frame != NULL) { 628 free(cc->video_frame); 629 } 630 if (cc->device_name != NULL) { 631 free(cc->device_name); 632 } 633 634 AFREE(cc); 635 } 636 637 /* Creates descriptor for a connecting emulated camera client. 638 * Param: 639 * csd - Camera service descriptor. 640 * param - Client parameters. Must be formatted as described in comments to 641 * get_token_value routine, and must contain at least 'name' parameter, 642 * identifiying the camera device to create the service for. Also parameters 643 * may contain a decimal 'inp_channel' parameter, selecting the input 644 * channel to use when communicating with the camera device. 645 * Return: 646 * Emulated camera client descriptor on success, or NULL on failure. 647 */ 648 static CameraClient* 649 _camera_client_create(CameraServiceDesc* csd, const char* param) 650 { 651 CameraClient* cc; 652 CameraInfo* ci; 653 int res; 654 ANEW0(cc); 655 656 /* 657 * Parse parameter string, containing camera client properties. 658 */ 659 660 /* Pull required device name. */ 661 if (get_token_value_alloc(param, "name", &cc->device_name)) { 662 E("%s: Allocation failure, or required 'name' parameter is missing, or misformed in '%s'", 663 __FUNCTION__, param); 664 return NULL; 665 } 666 667 /* Pull optional input channel. */ 668 res = get_token_value_int(param, "inp_channel", &cc->inp_channel); 669 if (res != 0) { 670 if (res == -1) { 671 /* 'inp_channel' parameter has been ommited. Use default input 672 * channel, which is zero. */ 673 cc->inp_channel = 0; 674 } else { 675 E("%s: 'inp_channel' parameter is misformed in '%s'", 676 __FUNCTION__, param); 677 return NULL; 678 } 679 } 680 681 /* Get camera info for the emulated camera represented with this service. 682 * Array of camera information records has been created when the camera 683 * service was enumerating camera devices during the service initialization. 684 * By the camera service protocol, camera service clients must first obtain 685 * list of enumerated cameras via the 'list' query to the camera service, and 686 * then use device name reported in the list to connect to an emulated camera 687 * service. So, if camera information for the given device name is not found 688 * in the array, we fail this connection due to protocol violation. */ 689 ci = _camera_service_get_camera_info_by_device_name(csd, cc->device_name); 690 if (ci == NULL) { 691 E("%s: Cannot find camera info for device '%s'", 692 __FUNCTION__, cc->device_name); 693 _camera_client_free(cc); 694 return NULL; 695 } 696 697 /* We can't allow multiple camera services for a single camera device, Lets 698 * make sure that there is no client created for this camera. */ 699 if (ci->in_use) { 700 E("%s: Camera device '%s' is in use", __FUNCTION__, cc->device_name); 701 _camera_client_free(cc); 702 return NULL; 703 } 704 705 /* We're done. Set camera in use, and succeed the connection. */ 706 ci->in_use = 1; 707 cc->camera_info = ci; 708 709 D("%s: Camera service is created for device '%s' using input channel %d", 710 __FUNCTION__, cc->device_name, cc->inp_channel); 711 712 return cc; 713 } 714 715 /******************************************************************************** 716 * Camera client queries 717 *******************************************************************************/ 718 719 /* Client has queried conection to the camera. 720 * Param: 721 * cc - Queried camera client descriptor. 722 * qc - Qemu client for the emulated camera. 723 * param - Query parameters. There are no parameters expected for this query. 724 */ 725 static void 726 _camera_client_query_connect(CameraClient* cc, QemudClient* qc, const char* param) 727 { 728 if (cc->camera != NULL) { 729 /* Already connected. */ 730 W("%s: Camera '%s' is already connected", __FUNCTION__, cc->device_name); 731 _qemu_client_reply_ok(qc, "Camera is already connected"); 732 return; 733 } 734 735 /* Open camera device. */ 736 cc->camera = camera_device_open(cc->device_name, cc->inp_channel); 737 if (cc->camera == NULL) { 738 E("%s: Unable to open camera device '%s'", __FUNCTION__, cc->device_name); 739 _qemu_client_reply_ko(qc, "Unable to open camera device."); 740 return; 741 } 742 743 D("%s: Camera device '%s' is now connected", __FUNCTION__, cc->device_name); 744 745 _qemu_client_reply_ok(qc, NULL); 746 } 747 748 /* Client has queried disconection from the camera. 749 * Param: 750 * cc - Queried camera client descriptor. 751 * qc - Qemu client for the emulated camera. 752 * param - Query parameters. There are no parameters expected for this query. 753 */ 754 static void 755 _camera_client_query_disconnect(CameraClient* cc, 756 QemudClient* qc, 757 const char* param) 758 { 759 if (cc->camera == NULL) { 760 /* Already disconnected. */ 761 W("%s: Camera '%s' is already disconnected", __FUNCTION__, cc->device_name); 762 _qemu_client_reply_ok(qc, "Camera is not connected"); 763 return; 764 } 765 766 /* Before we can go ahead and disconnect, we must make sure that camera is 767 * not capturing frames. */ 768 if (cc->video_frame != NULL) { 769 E("%s: Cannot disconnect camera '%s' while it is not stopped", 770 __FUNCTION__, cc->device_name); 771 _qemu_client_reply_ko(qc, "Camera is not stopped"); 772 return; 773 } 774 775 /* Close camera device. */ 776 camera_device_close(cc->camera); 777 cc->camera = NULL; 778 779 D("Camera device '%s' is now disconnected", cc->device_name); 780 781 _qemu_client_reply_ok(qc, NULL); 782 } 783 784 /* Client has queried the client to start capturing video. 785 * Param: 786 * cc - Queried camera client descriptor. 787 * qc - Qemu client for the emulated camera. 788 * param - Query parameters. Parameters for this query must contain a 'dim', and 789 * a 'pix' parameters, where 'dim' must be "dim=<width>x<height>", and 'pix' 790 * must be "pix=<format>", where 'width' and 'height' must be numerical 791 * values for the capturing video frame width, and height, and 'format' must 792 * be a numerical value for the pixel format of the video frames expected by 793 * the client. 'format' must be one of the V4L2_PIX_FMT_XXX values. 794 */ 795 static void 796 _camera_client_query_start(CameraClient* cc, QemudClient* qc, const char* param) 797 { 798 char* w; 799 char dim[64]; 800 int width, height, pix_format; 801 802 /* Sanity check. */ 803 if (cc->camera == NULL) { 804 /* Not connected. */ 805 E("%s: Camera '%s' is not connected", __FUNCTION__, cc->device_name); 806 _qemu_client_reply_ko(qc, "Camera is not connected"); 807 return; 808 } 809 810 /* 811 * Parse parameters. 812 */ 813 814 if (param == NULL) { 815 E("%s: Missing parameters for the query", __FUNCTION__); 816 _qemu_client_reply_ko(qc, "Missing parameters for the query"); 817 return; 818 } 819 820 /* Pull required 'dim' parameter. */ 821 if (get_token_value(param, "dim", dim, sizeof(dim))) { 822 E("%s: Invalid or missing 'dim' parameter in '%s'", __FUNCTION__, param); 823 _qemu_client_reply_ko(qc, "Invalid or missing 'dim' parameter"); 824 return; 825 } 826 827 /* Pull required 'pix' parameter. */ 828 if (get_token_value_int(param, "pix", &pix_format)) { 829 E("%s: Invalid or missing 'pix' parameter in '%s'", __FUNCTION__, param); 830 _qemu_client_reply_ko(qc, "Invalid or missing 'pix' parameter"); 831 return; 832 } 833 834 /* Parse 'dim' parameter, and get requested frame width and height. */ 835 w = strchr(dim, 'x'); 836 if (w == NULL || w[1] == '\0') { 837 E("%s: Invalid 'dim' parameter in '%s'", __FUNCTION__, param); 838 _qemu_client_reply_ko(qc, "Invalid 'dim' parameter"); 839 return; 840 } 841 *w = '\0'; w++; 842 errno = 0; 843 width = strtoi(dim, NULL, 10); 844 height = strtoi(w, NULL, 10); 845 if (errno) { 846 E("%s: Invalid 'dim' parameter in '%s'", __FUNCTION__, param); 847 _qemu_client_reply_ko(qc, "Invalid 'dim' parameter"); 848 return; 849 } 850 851 /* After collecting capture parameters lets see if camera has already 852 * started, and if so, lets see if parameters match. */ 853 if (cc->video_frame != NULL) { 854 /* Already started. Match capture parameters. */ 855 if (cc->pixel_format != pix_format ||cc->width != width || 856 cc->height != height) { 857 /* Parameters match. Succeed the query. */ 858 W("%s: Camera '%s' is already started", __FUNCTION__, cc->device_name); 859 _qemu_client_reply_ok(qc, "Camera is already started"); 860 } else { 861 /* Parameters don't match. Fail the query. */ 862 E("%s: Camera '%s' is already started, and parameters don't match:\n" 863 "Current %.4s[%dx%d] != requested %.4s[%dx%d]", 864 __FUNCTION__, cc->device_name, (const char*)&cc->pixel_format, 865 cc->width, cc->height, (const char*)&pix_format, width, height); 866 _qemu_client_reply_ko(qc, 867 "Camera is already started with different capturing parameters"); 868 } 869 return; 870 } 871 872 /* 873 * Start the camera. 874 */ 875 876 /* Save capturing parameters. */ 877 cc->pixel_format = pix_format; 878 cc->width = width; 879 cc->height = height; 880 cc->pixel_num = cc->width * cc->height; 881 cc->frames_cached = 0; 882 883 /* Make sure that pixel format is known, and calculate video framebuffer size 884 * along the lines. */ 885 switch (cc->pixel_format) { 886 case V4L2_PIX_FMT_YUV420: 887 case V4L2_PIX_FMT_YVU420: 888 case V4L2_PIX_FMT_NV12: 889 case V4L2_PIX_FMT_NV21: 890 cc->video_frame_size = (cc->pixel_num * 12) / 8; 891 break; 892 893 default: 894 E("%s: Unknown pixel format %.4s", 895 __FUNCTION__, (char*)&cc->pixel_format); 896 _qemu_client_reply_ko(qc, "Pixel format is unknown"); 897 return; 898 } 899 900 /* Make sure that we have a converters between the original camera pixel 901 * format and the one that the client expects. Also a converter must exist 902 * for the preview window pixel format (RGB32) */ 903 if (!has_converter(cc->camera_info->pixel_format, cc->pixel_format) || 904 !has_converter(cc->camera_info->pixel_format, V4L2_PIX_FMT_RGB32)) { 905 E("%s: No conversion exist between %.4s and %.4s (or RGB32) pixel formats", 906 __FUNCTION__, (char*)&cc->camera_info->pixel_format, (char*)&cc->pixel_format); 907 _qemu_client_reply_ko(qc, "No conversion exist for the requested pixel format"); 908 return; 909 } 910 911 /* TODO: At the moment camera framework in the emulator requires RGB32 pixel 912 * format for preview window. So, we need to keep two framebuffers here: one 913 * for the video, and another for the preview window. Watch out when this 914 * changes (if changes). */ 915 cc->preview_frame_size = cc->pixel_num * 4; 916 917 /* Allocate buffer large enough to contain both, video and preview 918 * framebuffers. */ 919 cc->video_frame = 920 (uint8_t*)malloc(cc->video_frame_size + cc->preview_frame_size); 921 if (cc->video_frame == NULL) { 922 E("%s: Not enough memory for framebuffers %d + %d", 923 __FUNCTION__, cc->video_frame_size, cc->preview_frame_size); 924 _qemu_client_reply_ko(qc, "Out of memory"); 925 return; 926 } 927 928 /* Set framebuffer pointers. */ 929 cc->preview_frame = (uint16_t*)(cc->video_frame + cc->video_frame_size); 930 931 /* Start the camera. */ 932 if (camera_device_start_capturing(cc->camera, cc->camera_info->pixel_format, 933 cc->width, cc->height)) { 934 E("%s: Cannot start camera '%s' for %.4s[%dx%d]: %s", 935 __FUNCTION__, cc->device_name, (const char*)&cc->pixel_format, 936 cc->width, cc->height, strerror(errno)); 937 free(cc->video_frame); 938 cc->video_frame = NULL; 939 _qemu_client_reply_ko(qc, "Cannot start the camera"); 940 return; 941 } 942 943 D("%s: Camera '%s' is now started for %.4s[%dx%d]", 944 __FUNCTION__, cc->device_name, (char*)&cc->pixel_format, cc->width, 945 cc->height); 946 947 _qemu_client_reply_ok(qc, NULL); 948 } 949 950 /* Client has queried the client to stop capturing video. 951 * Param: 952 * cc - Queried camera client descriptor. 953 * qc - Qemu client for the emulated camera. 954 * param - Query parameters. There are no parameters expected for this query. 955 */ 956 static void 957 _camera_client_query_stop(CameraClient* cc, QemudClient* qc, const char* param) 958 { 959 if (cc->video_frame == NULL) { 960 /* Not started. */ 961 W("%s: Camera '%s' is not started", __FUNCTION__, cc->device_name); 962 _qemu_client_reply_ok(qc, "Camera is not started"); 963 return; 964 } 965 966 /* Stop the camera. */ 967 if (camera_device_stop_capturing(cc->camera)) { 968 E("%s: Cannot stop camera device '%s': %s", 969 __FUNCTION__, cc->device_name, strerror(errno)); 970 _qemu_client_reply_ko(qc, "Cannot stop camera device"); 971 return; 972 } 973 974 free(cc->video_frame); 975 cc->video_frame = NULL; 976 977 D("%s: Camera device '%s' is now stopped.", __FUNCTION__, cc->device_name); 978 _qemu_client_reply_ok(qc, NULL); 979 } 980 981 /* Client has queried next frame. 982 * Param: 983 * cc - Queried camera client descriptor. 984 * qc - Qemu client for the emulated camera. 985 * param - Query parameters. Parameters for this query are formatted as such: 986 * video=<size> preview=<size> whiteb=<red>,<green>,<blue> expcomp=<comp> 987 * where: 988 * - 'video', and 'preview' both must be decimal values, defining size of 989 * requested video, and preview frames respectively. Zero value for any 990 * of these parameters means that this particular frame is not requested. 991 * - whiteb contains float values required to calculate whilte balance. 992 * - expcomp contains a float value required to calculate exposure 993 * compensation. 994 */ 995 static void 996 _camera_client_query_frame(CameraClient* cc, QemudClient* qc, const char* param) 997 { 998 int video_size = 0; 999 int preview_size = 0; 1000 int repeat; 1001 ClientFrameBuffer fbs[2]; 1002 int fbs_num = 0; 1003 size_t payload_size; 1004 uint64_t tick; 1005 float r_scale = 1.0f, g_scale = 1.0f, b_scale = 1.0f, exp_comp = 1.0f; 1006 char tmp[256]; 1007 1008 /* Sanity check. */ 1009 if (cc->video_frame == NULL) { 1010 /* Not started. */ 1011 E("%s: Camera '%s' is not started", __FUNCTION__, cc->device_name); 1012 _qemu_client_reply_ko(qc, "Camera is not started"); 1013 return; 1014 } 1015 1016 /* Pull required parameters. */ 1017 if (get_token_value_int(param, "video", &video_size) || 1018 get_token_value_int(param, "preview", &preview_size)) { 1019 E("%s: Invalid or missing 'video', or 'preview' parameter in '%s'", 1020 __FUNCTION__, param); 1021 _qemu_client_reply_ko(qc, 1022 "Invalid or missing 'video', or 'preview' parameter"); 1023 return; 1024 } 1025 1026 /* Pull white balance values. */ 1027 if (!get_token_value(param, "whiteb", tmp, sizeof(tmp))) { 1028 if (sscanf(tmp, "%g,%g,%g", &r_scale, &g_scale, &b_scale) != 3) { 1029 D("Invalid value '%s' for parameter 'whiteb'", tmp); 1030 r_scale = g_scale = b_scale = 1.0f; 1031 } 1032 } 1033 1034 /* Pull exposure compensation. */ 1035 if (!get_token_value(param, "expcomp", tmp, sizeof(tmp))) { 1036 if (sscanf(tmp, "%g", &exp_comp) != 1) { 1037 D("Invalid value '%s' for parameter 'whiteb'", tmp); 1038 exp_comp = 1.0f; 1039 } 1040 } 1041 1042 /* Verify that framebuffer sizes match the ones that the started camera 1043 * operates with. */ 1044 if ((video_size != 0 && cc->video_frame_size != video_size) || 1045 (preview_size != 0 && cc->preview_frame_size != preview_size)) { 1046 E("%s: Frame sizes don't match for camera '%s':\n" 1047 "Expected %d for video, and %d for preview. Requested %d, and %d", 1048 __FUNCTION__, cc->device_name, cc->video_frame_size, 1049 cc->preview_frame_size, video_size, preview_size); 1050 _qemu_client_reply_ko(qc, "Frame size mismatch"); 1051 return; 1052 } 1053 1054 /* 1055 * Initialize framebuffer array for frame read. 1056 */ 1057 1058 if (video_size) { 1059 fbs[fbs_num].pixel_format = cc->pixel_format; 1060 fbs[fbs_num].framebuffer = cc->video_frame; 1061 fbs_num++; 1062 } 1063 if (preview_size) { 1064 /* TODO: Watch out for preview format changes! */ 1065 fbs[fbs_num].pixel_format = V4L2_PIX_FMT_RGB32; 1066 fbs[fbs_num].framebuffer = cc->preview_frame; 1067 fbs_num++; 1068 } 1069 1070 /* Capture new frame. */ 1071 tick = _get_timestamp(); 1072 repeat = camera_device_read_frame(cc->camera, fbs, fbs_num, 1073 r_scale, g_scale, b_scale, exp_comp); 1074 1075 /* Note that there is no (known) way how to wait on next frame being 1076 * available, so we could dequeue frame buffer from the device only when we 1077 * know it's available. Instead we're shooting in the dark, and quite often 1078 * device will response with EAGAIN, indicating that it doesn't have frame 1079 * ready. In turn, it means that the last frame we have obtained from the 1080 * device is still good, and we can reply with the cached frames. The only 1081 * case when we need to keep trying to obtain a new frame is when frame cache 1082 * is empty. To prevent ourselves from an indefinite loop in case device got 1083 * stuck on something (observed with some Microsoft devices) we will limit 1084 * the loop by 2 second time period (which is more than enough to obtain 1085 * something from the device) */ 1086 while (repeat == 1 && !cc->frames_cached && 1087 (_get_timestamp() - tick) < 2000000LL) { 1088 /* Sleep for 10 millisec before repeating the attempt. */ 1089 _camera_sleep(10); 1090 repeat = camera_device_read_frame(cc->camera, fbs, fbs_num, 1091 r_scale, g_scale, b_scale, exp_comp); 1092 } 1093 if (repeat == 1 && !cc->frames_cached) { 1094 /* Waited too long for the first frame. */ 1095 E("%s: Unable to obtain first video frame from the camera '%s' in %d milliseconds: %s.", 1096 __FUNCTION__, cc->device_name, 1097 (uint32_t)(_get_timestamp() - tick) / 1000, strerror(errno)); 1098 _qemu_client_reply_ko(qc, "Unable to obtain video frame from the camera"); 1099 return; 1100 } else if (repeat < 0) { 1101 /* An I/O error. */ 1102 E("%s: Unable to obtain video frame from the camera '%s': %s.", 1103 __FUNCTION__, cc->device_name, strerror(errno)); 1104 _qemu_client_reply_ko(qc, strerror(errno)); 1105 return; 1106 } 1107 1108 /* We have cached something... */ 1109 cc->frames_cached = 1; 1110 1111 /* 1112 * Build the reply. 1113 */ 1114 1115 /* Payload includes "ok:" + requested video and preview frames. */ 1116 payload_size = 3 + video_size + preview_size; 1117 1118 /* Send payload size first. */ 1119 _qemu_client_reply_payload(qc, payload_size); 1120 1121 /* After that send the 'ok:'. Note that if there is no frames sent, we should 1122 * use prefix "ok" instead of "ok:" */ 1123 if (video_size || preview_size) { 1124 qemud_client_send(qc, (const uint8_t*)"ok:", 3); 1125 } else { 1126 /* Still 3 bytes: zero terminator is required in this case. */ 1127 qemud_client_send(qc, (const uint8_t*)"ok", 3); 1128 } 1129 1130 /* After that send video frame (if requested). */ 1131 if (video_size) { 1132 qemud_client_send(qc, cc->video_frame, video_size); 1133 } 1134 1135 /* After that send preview frame (if requested). */ 1136 if (preview_size) { 1137 qemud_client_send(qc, (const uint8_t*)cc->preview_frame, preview_size); 1138 } 1139 } 1140 1141 /* Handles a message received from the emulated camera client. 1142 * Queries received here are represented as strings: 1143 * - 'connect' - Connects to the camera device (opens it). 1144 * - 'disconnect' - Disconnexts from the camera device (closes it). 1145 * - 'start' - Starts capturing video from the connected camera device. 1146 * - 'stop' - Stop capturing video from the connected camera device. 1147 * - 'frame' - Queries video and preview frames captured from the camera. 1148 * Param: 1149 * opaque - Camera service descriptor. 1150 * msg, msglen - Message received from the camera factory client. 1151 * client - Camera factory client pipe. 1152 */ 1153 static void 1154 _camera_client_recv(void* opaque, 1155 uint8_t* msg, 1156 int msglen, 1157 QemudClient* client) 1158 { 1159 /* 1160 * Emulated camera client queries. 1161 */ 1162 1163 /* Connect to the camera. */ 1164 static const char _query_connect[] = "connect"; 1165 /* Disconnect from the camera. */ 1166 static const char _query_disconnect[] = "disconnect"; 1167 /* Start video capturing. */ 1168 static const char _query_start[] = "start"; 1169 /* Stop video capturing. */ 1170 static const char _query_stop[] = "stop"; 1171 /* Query frame(s). */ 1172 static const char _query_frame[] = "frame"; 1173 1174 char query_name[64]; 1175 const char* query_param = NULL; 1176 CameraClient* cc = (CameraClient*)opaque; 1177 1178 /* 1179 * Emulated camera queries are formatted as such: 1180 * "<query name> [<parameters>]" 1181 */ 1182 1183 T("%s: Camera client query: '%s'", __FUNCTION__, (char*)msg); 1184 if (_parse_query((const char*)msg, query_name, sizeof(query_name), 1185 &query_param)) { 1186 E("%s: Invalid query '%s'", __FUNCTION__, (char*)msg); 1187 _qemu_client_reply_ko(client, "Invalid query"); 1188 return; 1189 } 1190 1191 /* Dispatch the query to an appropriate handler. */ 1192 if (!strcmp(query_name, _query_frame)) { 1193 /* A frame is queried. */ 1194 _camera_client_query_frame(cc, client, query_param); 1195 } else if (!strcmp(query_name, _query_connect)) { 1196 /* Camera connection is queried. */ 1197 _camera_client_query_connect(cc, client, query_param); 1198 } else if (!strcmp(query_name, _query_disconnect)) { 1199 /* Camera disnection is queried. */ 1200 _camera_client_query_disconnect(cc, client, query_param); 1201 } else if (!strcmp(query_name, _query_start)) { 1202 /* Start capturing is queried. */ 1203 _camera_client_query_start(cc, client, query_param); 1204 } else if (!strcmp(query_name, _query_stop)) { 1205 /* Stop capturing is queried. */ 1206 _camera_client_query_stop(cc, client, query_param); 1207 } else { 1208 E("%s: Unknown query '%s'", __FUNCTION__, (char*)msg); 1209 _qemu_client_reply_ko(client, "Unknown query"); 1210 } 1211 } 1212 1213 /* Emulated camera client has been disconnected from the service. */ 1214 static void 1215 _camera_client_close(void* opaque) 1216 { 1217 CameraClient* cc = (CameraClient*)opaque; 1218 1219 D("%s: Camera client for device '%s' on input channel %d is now closed", 1220 __FUNCTION__, cc->device_name, cc->inp_channel); 1221 1222 _camera_client_free(cc); 1223 } 1224 1225 /******************************************************************************** 1226 * Camera service API 1227 *******************************************************************************/ 1228 1229 /* Connects a client to the camera service. 1230 * There are two classes of the client that can connect to the service: 1231 * - Camera factory that is insterested only in listing camera devices attached 1232 * to the host. 1233 * - Camera device emulators that attach to the actual camera devices. 1234 * The distinction between these two classes is made by looking at extra 1235 * parameters passed in client_param variable. If it's NULL, or empty, the client 1236 * connects to a camera factory. Otherwise, parameters describe the camera device 1237 * the client wants to connect to. 1238 */ 1239 static QemudClient* 1240 _camera_service_connect(void* opaque, 1241 QemudService* serv, 1242 int channel, 1243 const char* client_param) 1244 { 1245 QemudClient* client = NULL; 1246 CameraServiceDesc* csd = (CameraServiceDesc*)opaque; 1247 1248 D("%s: Connecting camera client '%s'", 1249 __FUNCTION__, client_param ? client_param : "Factory"); 1250 if (client_param == NULL || *client_param == '\0') { 1251 /* This is an emulated camera factory client. */ 1252 client = qemud_client_new(serv, channel, client_param, csd, 1253 _factory_client_recv, _factory_client_close, 1254 NULL, NULL); 1255 } else { 1256 /* This is an emulated camera client. */ 1257 CameraClient* cc = _camera_client_create(csd, client_param); 1258 if (cc != NULL) { 1259 client = qemud_client_new(serv, channel, client_param, cc, 1260 _camera_client_recv, _camera_client_close, 1261 NULL, NULL); 1262 } 1263 } 1264 1265 return client; 1266 } 1267 1268 void 1269 android_camera_service_init(void) 1270 { 1271 static int _inited = 0; 1272 1273 if (!_inited) { 1274 _camera_service_init(&_camera_service_desc); 1275 QemudService* serv = qemud_service_register( SERVICE_NAME, 0, 1276 &_camera_service_desc, 1277 _camera_service_connect, 1278 NULL, NULL); 1279 if (serv == NULL) { 1280 derror("%s: Could not register '%s' service", 1281 __FUNCTION__, SERVICE_NAME); 1282 return; 1283 } 1284 D("%s: Registered '%s' qemud service", __FUNCTION__, SERVICE_NAME); 1285 } 1286 } 1287 1288 void 1289 android_list_web_cameras(void) 1290 { 1291 CameraInfo ci[MAX_CAMERA]; 1292 int connected_cnt; 1293 int i; 1294 1295 /* Enumerate camera devices connected to the host. */ 1296 connected_cnt = enumerate_camera_devices(ci, MAX_CAMERA); 1297 if (connected_cnt <= 0) { 1298 return; 1299 } 1300 1301 printf("List of web cameras connected to the computer:\n"); 1302 for (i = 0; i < connected_cnt; i++) { 1303 printf(" Camera '%s' is connected to device '%s' on channel %d using pixel format '%.4s'\n", 1304 ci[i].display_name, ci[i].device_name, ci[i].inp_channel, 1305 (const char*)&ci[i].pixel_format); 1306 } 1307 printf("\n"); 1308 } 1309