diff options
| author | Stanislaw Halik <sthalik@misaki.pl> | 2019-05-05 12:34:01 +0200 | 
|---|---|---|
| committer | Stanislaw Halik <sthalik@misaki.pl> | 2019-05-06 03:42:13 +0200 | 
| commit | 6eda8a85b84c4e661a8763429ae1978f8da7f9dd (patch) | |
| tree | 5169e1bf6779f7442235f36a206a91e224cda05d /video-ps3eye/PS3EYEDriver/urb.cpp | |
| parent | 12dfd6dcf60d9fefef7f8723fb9bc5a21fdb5b61 (diff) | |
video/ps3eye: WIP
Diffstat (limited to 'video-ps3eye/PS3EYEDriver/urb.cpp')
| -rw-r--r-- | video-ps3eye/PS3EYEDriver/urb.cpp | 307 | 
1 files changed, 307 insertions, 0 deletions
diff --git a/video-ps3eye/PS3EYEDriver/urb.cpp b/video-ps3eye/PS3EYEDriver/urb.cpp new file mode 100644 index 00000000..7e931490 --- /dev/null +++ b/video-ps3eye/PS3EYEDriver/urb.cpp @@ -0,0 +1,307 @@ +#include "urb.hpp" +#include "singleton.hpp" +#include "frame-queue.hpp" +#include "internal.hpp" + +#include <algorithm> +#include <cstring> + +/* Values for bmHeaderInfo (Video and Still Image Payload Headers, 2.4.3.3) */ +enum { +    UVC_STREAM_EOH = (1 << 7), +    UVC_STREAM_ERR = (1 << 6), +    UVC_STREAM_STI = (1 << 5), +    UVC_STREAM_RES = (1 << 4), +    UVC_STREAM_SCR = (1 << 3), +    UVC_STREAM_PTS = (1 << 2), +    UVC_STREAM_EOF = (1 << 1), +    UVC_STREAM_FID = (1 << 0), +}; + +void USB_CALLBACK URBDesc::transfer_completed_callback(struct libusb_transfer *xfr) +{ +    URBDesc *urb = reinterpret_cast<URBDesc*>(xfr->user_data); +    enum libusb_transfer_status status = xfr->status; + +    switch (status) +    { +    case LIBUSB_TRANSFER_TIMED_OUT: +        urb->frame_add(URBDesc::DISCARD_PACKET, nullptr, 0); +        break; +    case LIBUSB_TRANSFER_COMPLETED: +        urb->pkt_scan(xfr->buffer, xfr->actual_length); +        if (int error = libusb_submit_transfer(xfr); error < 0) { +            debug("libusb_submit_transfer(%d), exiting\n", error); +            urb->close_transfers(); +        } +        break; +    default: +        debug("transfer 0x%p failed(%d)\n", (void*)xfr, status); +        urb->close_transfers(); +        break; +    case LIBUSB_TRANSFER_CANCELLED: +        urb->transfer_cancelled(); +        debug("transfer 0x%p cancelled\n", (void*)xfr); +        break; +    } + +    //debug("length:%u, actual_length:%u\n", xfr->length, xfr->actual_length); +} + +URBDesc::~URBDesc() +{ +    close_transfers(); +    free_transfers(); +} + +bool URBDesc::start_transfers(libusb_device_handle* handle, uint32_t curr_frame_size) +{ +    // Initialize the frame queue +    frame_size = curr_frame_size; +    frame_queue = std::make_shared<FrameQueue>(frame_size); + +    // Initialize the current frame pointer to the start of the buffer; it will be updated as frames are completed and pushed onto the frame queue +    cur_frame_start = frame_queue->ptr(); +    cur_frame_data_len = 0; + +    // Find the bulk transfer endpoint +    uint8_t bulk_endpoint = find_ep(libusb_get_device(handle)); +    libusb_clear_halt(handle, bulk_endpoint); + +    // Allocate the transfer buffer +    memset(transfer_buffer, 0, sizeof(transfer_buffer)); + +    bool res = true; +    for (int index = 0; index < NUM_TRANSFERS; ++index) +    { +        // Create & submit the transfer +        xfers[index] = libusb_alloc_transfer(0); + +        if (!xfers[index]) +        { +            debug("libusb_alloc_transfer failed\n"); +            res = false; +            break; +        } + +        libusb_fill_bulk_transfer(xfers[index], handle, bulk_endpoint, transfer_buffer + index * TRANSFER_SIZE, TRANSFER_SIZE, transfer_completed_callback, reinterpret_cast<void*>(this), 0); + +        if (int status = libusb_submit_transfer(xfers[index]); status != LIBUSB_SUCCESS) +        { +            debug("libusb_submit_transfer status %d\n", status); +            libusb_free_transfer(xfers[index]); +            xfers[index] = nullptr; +            res = false; +            break; +        } + +        num_active_transfers++; +    } + +    last_pts = 0; +    last_fid = 0; + +    USBMgr::instance().camera_started(); + +    return res; +} + +void URBDesc::close_transfers() +{ +    std::unique_lock<std::mutex> lock(num_active_transfers_mutex); + +    if (teardown) +        return; +    teardown = true; + +    // Cancel any pending transfers +    for (int i = 0; i < NUM_TRANSFERS; ++i) +    { +        if (!xfers[i]) +            continue; +        enum libusb_error status = (enum libusb_error)libusb_cancel_transfer(xfers[i]); +        if (status) +            debug("libusb_cancel_transfer error(%d) %s\n", status, libusb_strerror(status)); +    } +    num_active_transfers_condition.notify_one(); +} + +void URBDesc::free_transfers() +{ +    std::unique_lock<std::mutex> lock(num_active_transfers_mutex); + +    // Wait for cancelation to finish +    num_active_transfers_condition.wait(lock, [this] { return num_active_transfers == 0; }); + +    // Free completed transfers +    for (libusb_transfer*& xfer : xfers) +    { +        if (!xfer) +            continue; +        libusb_free_transfer(xfer); +        xfer = nullptr; +    } + +    USBMgr::instance().camera_stopped(); +} + +int URBDesc::transfer_cancelled() +{ +    std::lock_guard<std::mutex> lock(num_active_transfers_mutex); +    int refcnt = --num_active_transfers; +    num_active_transfers_condition.notify_one(); + +    return refcnt; +} + +void URBDesc::frame_add(enum gspca_packet_type packet_type, const uint8_t* data, int len) +{ +    if (packet_type == FIRST_PACKET) +        cur_frame_data_len = 0; +    else +        switch(last_packet_type)  // ignore warning. +        { +        case DISCARD_PACKET: +            if (packet_type == LAST_PACKET) { +                last_packet_type = packet_type; +                cur_frame_data_len = 0; +            } +            return; +        case LAST_PACKET: +            return; +        default: +            break; +        } + +    /* append the packet to the frame buffer */ +    if (len > 0) +    { +        if(cur_frame_data_len + len > frame_size) +        { +            packet_type = DISCARD_PACKET; +            cur_frame_data_len = 0; +        } else { +            memcpy(cur_frame_start+cur_frame_data_len, data, (size_t) len); +            cur_frame_data_len += len; +        } +    } + +    last_packet_type = packet_type; + +    if (packet_type == LAST_PACKET) { +        cur_frame_data_len = 0; +        cur_frame_start = frame_queue->Enqueue(); +        //debug("frame completed %d\n", frame_complete_ind); +    } +} + +void URBDesc::pkt_scan(uint8_t* data, int len) +{ +    uint32_t this_pts; +    uint16_t this_fid; +    int remaining_len = len; +    constexpr int payload_len = 2048; // bulk type + +    do { +        len = std::min(remaining_len, payload_len); + +        /* Payloads are prefixed with a UVC-style header.  We +           consider a frame to start when the FID toggles, or the PTS +           changes.  A frame ends when EOF is set, and we've received +           the correct number of bytes. */ + +        /* Verify UVC header.  Header length is always 12 */ +        if (data[0] != 12 || len < 12) { +            debug("bad header\n"); +            goto discard; +        } + +        /* Check errors */ +        if (data[1] & UVC_STREAM_ERR) { +            debug("payload error\n"); +            goto discard; +        } + +        /* Extract PTS and FID */ +        if (!(data[1] & UVC_STREAM_PTS)) { +            debug("PTS not present\n"); +            goto discard; +        } + +        this_pts = (data[5] << 24) | (data[4] << 16) | (data[3] << 8) | data[2]; +        this_fid = (uint16_t) ((data[1] & UVC_STREAM_FID) ? 1 : 0); + +        /* If PTS or FID has changed, start a new frame. */ +        if (this_pts != last_pts || this_fid != last_fid) { +            if (last_packet_type == INTER_PACKET) +            { +                /* The last frame was incomplete, so don't keep it or we will glitch */ +                frame_add(DISCARD_PACKET, nullptr, 0); +            } +            last_pts = this_pts; +            last_fid = this_fid; +            frame_add(FIRST_PACKET, data + 12, len - 12); +        } /* If this packet is marked as EOF, end the frame */ +        else if (data[1] & UVC_STREAM_EOF) +        { +            last_pts = 0; +            if (cur_frame_data_len + len - 12 != frame_size) +                goto discard; +            frame_add(LAST_PACKET, data + 12, len - 12); +        } else { +            /* Add the data from this payload */ +            frame_add(INTER_PACKET, data + 12, len - 12); +        } + +        /* Done this payload */ +        goto scan_next; + +discard: +        /* Discard data until a new frame starts. */ +        frame_add(DISCARD_PACKET, nullptr, 0); +scan_next: +        remaining_len -= len; +        data += len; +    } while (remaining_len > 0); +} + +/* + * look for an input transfer endpoint in an alternate setting + * libusb_endpoint_descriptor + */ +uint8_t URBDesc::find_ep(struct libusb_device *device) +{ +    const struct libusb_interface_descriptor *altsetting = nullptr; +    const struct libusb_endpoint_descriptor *ep; +    struct libusb_config_descriptor *config = nullptr; +    int i; +    uint8_t ep_addr = 0; + +    libusb_get_active_config_descriptor(device, &config); + +    if (!config) return 0; + +    for (i = 0; i < config->bNumInterfaces; i++) { +        altsetting = config->interface[i].altsetting; +        if (altsetting[0].bInterfaceNumber == 0) { +            break; +        } +    } + +    if (altsetting) +        for (i = 0; i < altsetting->bNumEndpoints; i++) { +            ep = &altsetting->endpoint[i]; +            if ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_BULK +                && ep->wMaxPacketSize != 0) +            { +                ep_addr = ep->bEndpointAddress; +                break; +            } +        } + +    libusb_free_config_descriptor(config); + +    return ep_addr; +} + +  | 
