From: Antonio Ospite Date: Sat, 20 Jul 2013 22:10:28 +0000 (+0200) Subject: am7xxx: implement am7xxx_send_image_async() X-Git-Tag: v0.1.4~20 X-Git-Url: https://git.ao2.it/libam7xxx.git/commitdiff_plain/860428c1b8ee26b327ec3c9fb8943fd17a49d755?ds=sidebyside;hp=d47271bd71355511535687f47e8a0f6c3784b8dc am7xxx: implement am7xxx_send_image_async() Implement am7xxx_send_image_async() the non-blocking version of am7xxx_send_image(). This way user programs can more easily overlap encoding and communication and achieve higher framerates. --- diff --git a/src/am7xxx.c b/src/am7xxx.c index f37ad70..3e6b461 100644 --- a/src/am7xxx.c +++ b/src/am7xxx.c @@ -130,6 +130,8 @@ static const struct am7xxx_usb_device_descriptor supported_devices[] = { struct _am7xxx_device { libusb_device_handle *usb_device; + struct libusb_transfer *transfer; + int transfer_completed; uint8_t buffer[AM7XXX_HEADER_WIRE_SIZE]; am7xxx_device_info *device_info; am7xxx_context *ctx; @@ -372,6 +374,117 @@ static int send_data(am7xxx_device *dev, uint8_t *buffer, unsigned int len) return 0; } +static void send_data_async_complete_cb(struct libusb_transfer *transfer) +{ + am7xxx_device *dev = (am7xxx_device *)(transfer->user_data); + int *completed = &(dev->transfer_completed); + int transferred = transfer->actual_length; + int ret; + + if (transferred != transfer->length) { + error(dev->ctx, "transferred: %d (expected %u)\n", + transferred, transfer->length); + } + + switch (transfer->status) { + case LIBUSB_TRANSFER_COMPLETED: + ret = 0; + break; + case LIBUSB_TRANSFER_TIMED_OUT: + ret = LIBUSB_ERROR_TIMEOUT; + break; + case LIBUSB_TRANSFER_STALL: + ret = LIBUSB_ERROR_PIPE; + break; + case LIBUSB_TRANSFER_OVERFLOW: + ret = LIBUSB_ERROR_OVERFLOW; + break; + case LIBUSB_TRANSFER_NO_DEVICE: + ret = LIBUSB_ERROR_NO_DEVICE; + break; + case LIBUSB_TRANSFER_ERROR: + case LIBUSB_TRANSFER_CANCELLED: + ret = LIBUSB_ERROR_IO; + break; + default: + error(dev->ctx, "unrecognised status code %d", transfer->status); + ret = LIBUSB_ERROR_OTHER; + } + + if (ret < 0) + error(dev->ctx, "libusb transfer failed: %s", + libusb_error_name(ret)); + + libusb_free_transfer(transfer); + transfer = NULL; + + *completed = 1; +} + +static inline void wait_for_trasfer_completed(am7xxx_device *dev) +{ + while (!dev->transfer_completed) { + int ret = libusb_handle_events_completed(dev->ctx->usb_context, + &(dev->transfer_completed)); + if (ret < 0) { + if (ret == LIBUSB_ERROR_INTERRUPTED) + continue; + error(dev->ctx, "libusb_handle_events failed: %s, cancelling transfer and retrying", + libusb_error_name(ret)); + libusb_cancel_transfer(dev->transfer); + continue; + } + } +} + +static int send_data_async(am7xxx_device *dev, uint8_t *buffer, unsigned int len) +{ + int ret; + uint8_t *transfer_buffer; + + dev->transfer = libusb_alloc_transfer(0); + if (dev->transfer == NULL) { + error(dev->ctx, "cannot allocate transfer (%s)\n", + strerror(errno)); + return -ENOMEM; + } + + /* Make a copy of the buffer so the caller can safely reuse it just + * after libusb_submit_transfer() has returned. This technique + * requires more allocations than a proper double-buffering approach + * but it takes a lot less code. */ + transfer_buffer = malloc(len); + if (transfer_buffer == NULL) { + error(dev->ctx, "cannot allocate transfer buffer (%s)\n", + strerror(errno)); + ret = -ENOMEM; + goto err; + } + memcpy(transfer_buffer, buffer, len); + + dev->transfer->flags |= LIBUSB_TRANSFER_FREE_BUFFER; + libusb_fill_bulk_transfer(dev->transfer, dev->usb_device, 0x1, + transfer_buffer, len, + send_data_async_complete_cb, dev, 0); + + /* wait for the previous transfer to complete */ + wait_for_trasfer_completed(dev); + + trace_dump_buffer(dev->ctx, "sending -->", buffer, len); + + dev->transfer_completed = 0; + ret = libusb_submit_transfer(dev->transfer); + if (ret < 0) + goto err; + + return 0; + +err: + libusb_free_transfer(dev->transfer); + dev->transfer = NULL; + return ret; +} + static void serialize_header(struct am7xxx_header *h, uint8_t *buffer) { uint8_t **buffer_iterator = &buffer; @@ -496,6 +609,7 @@ static am7xxx_device *add_new_device(am7xxx_context *ctx, new_device->ctx = ctx; new_device->desc = desc; + new_device->transfer_completed = 1; devices_list = &(ctx->devices_list); @@ -786,6 +900,7 @@ AM7XXX_PUBLIC int am7xxx_close_device(am7xxx_device *dev) return -EINVAL; } if (dev->usb_device) { + wait_for_trasfer_completed(dev); libusb_release_interface(dev->usb_device, dev->desc->interface_number); libusb_close(dev->usb_device); dev->usb_device = NULL; @@ -950,6 +1065,42 @@ AM7XXX_PUBLIC int am7xxx_send_image(am7xxx_device *dev, return send_data(dev, image, image_size); } +AM7XXX_PUBLIC int am7xxx_send_image_async(am7xxx_device *dev, + am7xxx_image_format format, + unsigned int width, + unsigned int height, + uint8_t *image, + unsigned int image_size) +{ + int ret; + struct am7xxx_header h = { + .packet_type = AM7XXX_PACKET_TYPE_IMAGE, + .direction = AM7XXX_DIRECTION_OUT, + .header_data_len = sizeof(struct am7xxx_image_header), + .unknown2 = 0x3e, + .unknown3 = 0x10, + .header_data = { + .image = { + .format = format, + .width = width, + .height = height, + .image_size = image_size, + }, + }, + }; + + ret = send_header(dev, &h); + if (ret < 0) + return ret; + + if (image == NULL || image_size == 0) { + warning(dev->ctx, "Not sending any data, check the 'image' or 'image_size' parameters\n"); + return 0; + } + + return send_data_async(dev, image, image_size); +} + AM7XXX_PUBLIC int am7xxx_set_power_mode(am7xxx_device *dev, am7xxx_power_mode power) { int ret; diff --git a/src/am7xxx.h b/src/am7xxx.h index 1c353ab..ec9869d 100644 --- a/src/am7xxx.h +++ b/src/am7xxx.h @@ -242,6 +242,32 @@ int am7xxx_send_image(am7xxx_device *dev, unsigned int image_size); /** + * Queue transfer of an image for display on an am7xxx device and return immediately. + * + * This is the function that actually makes the device display something. + * Static pictures can be sent just once and the device will keep showing them + * until another image get sent or some command resets or turns off the display. + * + * @note This _async() variant makes a copy of the image buffer, so the caller + * is free to reuse the buffer just after the function returns. + * + * @param[in] dev A pointer to the structure representing the device to get info of + * @param[in] format The format the image is in (see @link am7xxx_image_format @endlink enum) + * @param[in] width The width of the image + * @param[in] height The height of the image + * @param[in] image A buffer holding data in the format specified by the format parameter + * @param[in] image_size The size in bytes of the image buffer + * + * @return 0 on success, a negative value on error + */ +int am7xxx_send_image_async(am7xxx_device *dev, + am7xxx_image_format format, + unsigned int width, + unsigned int height, + unsigned char *image, + unsigned int image_size); + +/** * Set the power mode of an am7xxx device. * * @note When setting the mode to AM7XXX_POWER_OFF the display can't be turned