Blob Blame History Raw
/*
 * 1394-Based Digital Camera Control Library
 *
 * IIDC-over-USB using libusb backend for dc1394
 *
 * Written by David Moore <dcm@acm.org>
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <pthread.h>
#include <unistd.h>
#ifdef HAVE_MACOSX
#include <CoreFoundation/CoreFoundation.h>
#endif
#include "usb/usb.h"

// LIBUSB_CALL only defined for latest libusb versions.
#ifndef LIBUSB_CALL
#define LIBUSB_CALL
#endif

/* Callback whenever a bulk transfer finishes. */
static void
LIBUSB_CALL callback (struct libusb_transfer * transfer)
{
    struct usb_frame * f = transfer->user_data;
    platform_camera_t * craw = f->pcam;

    if (transfer->status == LIBUSB_TRANSFER_CANCELLED) {
        dc1394_log_warning ("usb: Bulk transfer %d cancelled", f->frame.id);
        return;
    }

    dc1394_log_debug ("usb: Bulk transfer %d complete, %d of %d bytes",
            f->frame.id, transfer->actual_length, transfer->length);
    int status = BUFFER_FILLED;
    if (transfer->actual_length < transfer->length)
        status = BUFFER_CORRUPT;

    if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
        dc1394_log_error ("usb: Bulk transfer %d failed with code %d",
                f->frame.id, transfer->status);
        status = BUFFER_ERROR;
    }

    pthread_mutex_lock (&craw->mutex);
    f->status = status;
    craw->frames_ready++;
    pthread_mutex_unlock (&craw->mutex);

    if (write (craw->notify_pipe[1], "+", 1)!=1) {
        dc1394_log_error ("usb: Failed to write to notify pipe");
        // we may need to set the status to BUFFER_ERROR here
    }
}

#ifdef HAVE_MACOSX
static void
socket_callback_usb (CFSocketRef s, CFSocketCallBackType type,
                 CFDataRef address, const void * data, void * info)
{
    platform_camera_t * craw = info;
    dc1394capture_t * capture = &(craw->capture);
    if (capture->callback) {
        capture->callback (craw->camera, capture->callback_user_data);
    }
}
#endif

static void *
capture_thread (void * arg)
{
    platform_camera_t * craw = arg;

    dc1394_log_debug ("usb: Helper thread starting");

    while (1) {
        struct timeval tv = {
            .tv_sec = 0,
            .tv_usec = 100000,
        };
        libusb_handle_events_timeout(craw->thread_context, &tv);
        pthread_mutex_lock (&craw->mutex);
        if (craw->kill_thread)
            break;
        pthread_mutex_unlock (&craw->mutex);
    }
    pthread_mutex_unlock (&craw->mutex);
    dc1394_log_debug ("usb: Helper thread ending");
    return NULL;
}

static dc1394error_t
init_frame(platform_camera_t *craw, int index, dc1394video_frame_t *proto)
{
    struct usb_frame *f = craw->frames + index;

    memcpy (&f->frame, proto, sizeof f->frame);
    f->frame.image = craw->buffer + index * proto->total_bytes;
    f->frame.id = index;
    f->transfer = libusb_alloc_transfer (0);
    f->pcam = craw;
    f->status = BUFFER_EMPTY;
    return DC1394_SUCCESS;
}

dc1394error_t
dc1394_usb_capture_setup(platform_camera_t *craw, uint32_t num_dma_buffers,
        uint32_t flags)
{
    dc1394video_frame_t proto;
    int i;
    dc1394camera_t * camera = craw->camera;

#ifdef HAVE_MACOSX
    dc1394capture_t * capture = &(craw->capture);
    CFSocketContext socket_context = { 0, craw, NULL, NULL, NULL };
#endif

    // if capture is already set, abort
    if (craw->capture_is_set > 0)
        return DC1394_CAPTURE_IS_RUNNING;


    if (flags & DC1394_CAPTURE_FLAGS_DEFAULT)
        flags = DC1394_CAPTURE_FLAGS_CHANNEL_ALLOC |
            DC1394_CAPTURE_FLAGS_BANDWIDTH_ALLOC;

    craw->flags = flags;

    if (capture_basic_setup(camera, &proto) != DC1394_SUCCESS) {
        dc1394_log_error("usb: Basic capture setup failed");
        dc1394_usb_capture_stop (craw);
        return DC1394_FAILURE;
    }

    if (pipe (craw->notify_pipe) < 0) {
        dc1394_usb_capture_stop (craw);
        return DC1394_FAILURE;
    }

#ifdef HAVE_MACOSX
    capture->socket = CFSocketCreateWithNative (NULL, craw->notify_pipe[0],
                                                kCFSocketReadCallBack, socket_callback_usb, &socket_context);
    /* Set flags so that the underlying fd is not closed with the socket */
    CFSocketSetSocketFlags (capture->socket,
                            CFSocketGetSocketFlags (capture->socket) & ~kCFSocketCloseOnInvalidate);
    capture->socket_source = CFSocketCreateRunLoopSource (NULL,
                                                          capture->socket, 0);
    if (!capture->run_loop)
        dc1394_usb_capture_schedule_with_runloop (craw,
                                              CFRunLoopGetCurrent (), kCFRunLoopCommonModes);
    CFRunLoopAddSource (capture->run_loop, capture->socket_source,
                        capture->run_loop_mode);
#endif

    craw->capture_is_set = 1;

    dc1394_log_debug ("usb: Frame size is %"PRId64, proto.total_bytes);

    craw->num_frames = num_dma_buffers;
    craw->current = -1;
    craw->frames_ready = 0;
    craw->queue_broken = 0;
    craw->buffer_size = proto.total_bytes * num_dma_buffers;
    craw->buffer = malloc (craw->buffer_size);
    if (craw->buffer == NULL) {
        dc1394_usb_capture_stop (craw);
        return DC1394_MEMORY_ALLOCATION_FAILURE;
    }

    craw->frames = calloc (num_dma_buffers, sizeof *craw->frames);
    if (craw->frames == NULL) {
        dc1394_usb_capture_stop (craw);
        return DC1394_MEMORY_ALLOCATION_FAILURE;
    }

    for (i = 0; i < num_dma_buffers; i++)
        init_frame(craw, i, &proto);

    if (libusb_init(&craw->thread_context) != 0) {
        dc1394_log_error ("usb: Failed to create thread USB context");
        dc1394_usb_capture_stop (craw);
        return DC1394_FAILURE;
    }

    uint8_t bus = libusb_get_bus_number (libusb_get_device (craw->handle));
    uint8_t addr = libusb_get_device_address (libusb_get_device (craw->handle));

    libusb_device **list, *dev;
    libusb_get_device_list (craw->thread_context, &list);
    for (i = 0, dev = list[0]; dev; dev = list[++i]) {
        if (libusb_get_bus_number (dev) == bus &&
                libusb_get_device_address (dev) == addr)
            break;
    }
    if (!dev) {
        libusb_free_device_list (list, 1);
        dc1394_log_error ("usb: capture thread failed to find device");
        dc1394_usb_capture_stop (craw);
        return DC1394_FAILURE;
    }

    if (libusb_open (dev, &craw->thread_handle) < 0) {
        libusb_free_device_list (list, 1);
        dc1394_log_error ("usb: capture thread failed to open device");
        dc1394_usb_capture_stop (craw);
        return DC1394_FAILURE;
    }
    libusb_free_device_list (list, 1);

    if (libusb_claim_interface (craw->thread_handle, 0) < 0) {
        dc1394_log_error ("usb: capture thread failed to claim interface");
        dc1394_usb_capture_stop (craw);
        return DC1394_FAILURE;
    }

    for (i = 0; i < craw->num_frames; i++) {
        struct usb_frame *f = craw->frames + i;
        libusb_fill_bulk_transfer (f->transfer, craw->thread_handle,
                0x81, f->frame.image, f->frame.total_bytes,
                callback, f, 0);
    }
    for (i = 0; i < craw->num_frames; i++) {
        if (libusb_submit_transfer (craw->frames[i].transfer) < 0) {
            dc1394_log_error ("usb: Failed to submit initial transfer %d", i);
            dc1394_usb_capture_stop (craw);
            return DC1394_FAILURE;
        }
    }

    if (pthread_mutex_init (&craw->mutex, NULL) < 0) {
        dc1394_usb_capture_stop (craw);
        return DC1394_FAILURE;
    }
    craw->mutex_created = 1;
    if (pthread_create (&craw->thread, NULL, capture_thread, craw) < 0) {
        dc1394_log_error ("usb: Failed to launch helper thread");
        dc1394_usb_capture_stop (craw);
        return DC1394_FAILURE;
    }
    craw->thread_created = 1;

    // if auto iso is requested, start ISO
    if (flags & DC1394_CAPTURE_FLAGS_AUTO_ISO) {
        dc1394_video_set_transmission(camera, DC1394_ON);
        craw->iso_auto_started = 1;
    }

    return DC1394_SUCCESS;
}

dc1394error_t
dc1394_usb_capture_stop(platform_camera_t *craw)
{
    dc1394camera_t * camera = craw->camera;
    int i;
#ifdef HAVE_MACOSX
    dc1394capture_t * capture = &(craw->capture);
#endif

    if (craw->capture_is_set == 0)
        return DC1394_CAPTURE_IS_NOT_SET;

    dc1394_log_debug ("usb: Capture stopping");

    // stop ISO if it was started automatically
    if (craw->iso_auto_started > 0) {
        dc1394_video_set_transmission(camera, DC1394_OFF);
        craw->iso_auto_started = 0;
    }

    if (craw->thread_created) {
#if 0
        for (i = 0; i < craw->num_frames; i++) {
            libusb_cancel_transfer (craw->frames[i].transfer);
        }
#endif
        pthread_mutex_lock (&craw->mutex);
        craw->kill_thread = 1;
        pthread_mutex_unlock (&craw->mutex);
        pthread_join (craw->thread, NULL);
        dc1394_log_debug ("usb: Joined with helper thread");
        craw->kill_thread = 0;
        craw->thread_created = 0;
    }

    if (craw->mutex_created) {
        pthread_mutex_destroy (&craw->mutex);
        craw->mutex_created = 0;
    }

    if (craw->thread_handle) {
        libusb_release_interface (craw->thread_handle, 0);
        libusb_close (craw->thread_handle);
        craw->thread_handle = NULL;
    }

    if (craw->thread_context) {
        libusb_exit (craw->thread_context);
        craw->thread_context = NULL;
    }

#ifdef HAVE_MACOSX
    if (capture->socket_source) {
        CFRunLoopRemoveSource (capture->run_loop, capture->socket_source,
                               capture->run_loop_mode);
        CFRelease (capture->socket_source);
    }
    capture->socket_source = NULL;

    if (capture->socket) {
        CFSocketInvalidate (capture->socket);
        CFRelease (capture->socket);
    }
    capture->socket = NULL;
#endif

    if (craw->frames) {
        for (i = 0; i < craw->num_frames; i++) {
            libusb_free_transfer (craw->frames[i].transfer);
        }
        free (craw->frames);
        craw->frames = NULL;
    }

    free (craw->buffer);
    craw->buffer = NULL;

    if (craw->notify_pipe[0] != 0 || craw->notify_pipe[1] != 0) {
        close (craw->notify_pipe[0]);
        close (craw->notify_pipe[1]);
    }
    craw->notify_pipe[0] = 0;
    craw->notify_pipe[1] = 0;

    craw->capture_is_set = 0;

    return DC1394_SUCCESS;
}

#define NEXT_BUFFER(c,i) (((i) == -1) ? 0 : ((i)+1)%(c)->num_frames)

dc1394error_t
dc1394_usb_capture_dequeue (platform_camera_t * craw,
        dc1394capture_policy_t policy, dc1394video_frame_t **frame_return)
{
    int next = NEXT_BUFFER (craw, craw->current);
    struct usb_frame * f = craw->frames + next;

    if ((policy < DC1394_CAPTURE_POLICY_MIN)
            || (policy > DC1394_CAPTURE_POLICY_MAX))
        return DC1394_INVALID_CAPTURE_POLICY;

    /* default: return NULL in case of failures or lack of frames */
    *frame_return = NULL;

    if (policy == DC1394_CAPTURE_POLICY_POLL) {
        int status;
        pthread_mutex_lock (&craw->mutex);
        status = f->status;
        pthread_mutex_unlock (&craw->mutex);
        if (status == BUFFER_EMPTY)
            return DC1394_SUCCESS;
    }

    if (craw->queue_broken)
        return DC1394_FAILURE;

    char ch;
    if (read (craw->notify_pipe[0], &ch, 1)!=1) {
        dc1394_log_error ("usb: Failed to read from notify pipe");
        return DC1394_FAILURE;
    }

    pthread_mutex_lock (&craw->mutex);
    if (f->status == BUFFER_EMPTY) {
        dc1394_log_error ("usb: Expected filled buffer");
        pthread_mutex_unlock (&craw->mutex);
        return DC1394_FAILURE;
    }
    craw->frames_ready--;
    f->frame.frames_behind = craw->frames_ready;
    pthread_mutex_unlock (&craw->mutex);

    craw->current = next;

    *frame_return = &f->frame;

    if (f->status == BUFFER_ERROR)
        return DC1394_FAILURE;

    return DC1394_SUCCESS;
}

dc1394error_t
dc1394_usb_capture_enqueue (platform_camera_t * craw,
        dc1394video_frame_t * frame)
{
    dc1394camera_t * camera = craw->camera;
    struct usb_frame * f = (struct usb_frame *) frame;

    if (frame->camera != camera) {
        dc1394_log_error("usb: Camera does not match frame's camera");
        return DC1394_INVALID_ARGUMENT_VALUE;
    }

    if (f->status == BUFFER_EMPTY) {
        dc1394_log_error ("usb: Frame is not enqueuable");
        return DC1394_FAILURE;
    }

    f->status = BUFFER_EMPTY;
    if (libusb_submit_transfer (f->transfer) != LIBUSB_SUCCESS) {
        craw->queue_broken = 1;
        return DC1394_FAILURE;
    }

    return DC1394_SUCCESS;
}

int
dc1394_usb_capture_get_fileno (platform_camera_t * craw)
{
    if (craw->notify_pipe[0] == 0 && craw->notify_pipe[1] == 0)
        return -1;

    return craw->notify_pipe[0];
}

dc1394bool_t
dc1394_usb_capture_is_frame_corrupt (platform_camera_t * craw,
        dc1394video_frame_t * frame)
{
    struct usb_frame * f = (struct usb_frame *) frame;

    if (f->status == BUFFER_CORRUPT || f->status == BUFFER_ERROR)
        return DC1394_TRUE;

    return DC1394_FALSE;
}


#ifdef HAVE_MACOSX
dc1394error_t
dc1394_usb_capture_schedule_with_runloop (platform_camera_t * craw,
        CFRunLoopRef run_loop, CFStringRef run_loop_mode)
{
    dc1394capture_t * capture = &(craw->capture);
    if (craw->capture_is_set) {
        dc1394_log_warning("schedule_with_runloop must be called before capture_setup");
        return DC1394_FAILURE;
    }

    capture->run_loop = run_loop;
    capture->run_loop_mode = run_loop_mode;
    return DC1394_SUCCESS;
}

dc1394error_t
dc1394_usb_capture_set_callback (platform_camera_t * craw,
                             dc1394capture_callback_t callback, void * user_data)
{
    dc1394capture_t * capture = &(craw->capture);
    capture->callback = callback;
    capture->callback_user_data = user_data;
    return DC1394_SUCCESS;
}
#endif