]> git.sesse.net Git - bmusb/blobdiff - bmusb.cpp
Support card hotplugging (both add and remove).
[bmusb] / bmusb.cpp
index ed7a1f5caa42874909bf13804210322ee34942b5..e89610e7a5f2d7cc28a34ed1a22f326a5221bca5 100644 (file)
--- a/bmusb.cpp
+++ b/bmusb.cpp
@@ -7,6 +7,7 @@
 #include <assert.h>
 #include <errno.h>
 #include <libusb.h>
+#include <unistd.h>
 #include <netinet/in.h>
 #include <sched.h>
 #include <stdint.h>
@@ -34,6 +35,7 @@
 using namespace std;
 using namespace std::placeholders;
 
+#define USB_VENDOR_BLACKMAGIC 0x1edb
 #define MIN_WIDTH 640
 #define HEADER_SIZE 44
 //#define HEADER_SIZE 0
@@ -42,6 +44,8 @@ using namespace std::placeholders;
 #define FRAME_SIZE (8 << 20)  // 8 MB.
 #define USB_VIDEO_TRANSFER_SIZE (128 << 10)  // 128 kB.
 
+card_connected_callback_t BMUSBCapture::card_connected_callback = nullptr;
+
 namespace {
 
 FILE *audiofp;
@@ -616,8 +620,9 @@ void decode_packs(const libusb_transfer *xfr,
 
 void BMUSBCapture::cb_xfr(struct libusb_transfer *xfr)
 {
-       if (xfr->status != LIBUSB_TRANSFER_COMPLETED) {
-               fprintf(stderr, "transfer status %d\n", xfr->status);
+       if (xfr->status != LIBUSB_TRANSFER_COMPLETED &&
+           xfr->status != LIBUSB_TRANSFER_NO_DEVICE) {
+               fprintf(stderr, "error: transfer status %d\n", xfr->status);
                libusb_free_transfer(xfr);
                exit(3);
        }
@@ -625,6 +630,18 @@ void BMUSBCapture::cb_xfr(struct libusb_transfer *xfr)
        assert(xfr->user_data != nullptr);
        BMUSBCapture *usb = static_cast<BMUSBCapture *>(xfr->user_data);
 
+       if (xfr->status == LIBUSB_TRANSFER_NO_DEVICE) {
+               if (!usb->disconnected) {
+                       fprintf(stderr, "Device went away, stopping transfers.\n");
+                       usb->disconnected = true;
+                       if (usb->card_disconnected_callback) {
+                               usb->card_disconnected_callback();
+                       }
+               }
+               // Don't reschedule the transfer; the loop will stop by itself.
+               return;
+       }
+
        if (xfr->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) {
                if (xfr->endpoint == 0x84) {
                        decode_packs(xfr, "DeckLinkAudioResyncT", 20, &usb->current_audio_frame, "audio", bind(&BMUSBCapture::start_new_audio_block, usb, _1));
@@ -682,6 +699,26 @@ void BMUSBCapture::cb_xfr(struct libusb_transfer *xfr)
        }
 }
 
+int BMUSBCapture::cb_hotplug(libusb_context *ctx, libusb_device *dev, libusb_hotplug_event event, void *user_data)
+{
+       if (card_connected_callback != nullptr) {
+               libusb_device_descriptor desc;
+                if (libusb_get_device_descriptor(dev, &desc) < 0) {
+                       fprintf(stderr, "Error getting device descriptor for hotplugged device %p, killing hotplug\n", dev);
+                       libusb_unref_device(dev);
+                       return 1;
+               }
+
+               if ((desc.idVendor == USB_VENDOR_BLACKMAGIC && desc.idProduct == 0xbd3b) ||
+                   (desc.idVendor == USB_VENDOR_BLACKMAGIC && desc.idProduct == 0xbd4f)) {
+                       card_connected_callback(dev);  // Callback takes ownership.
+                       return 0;
+               }
+       }
+       libusb_unref_device(dev);
+       return 0;
+}
+
 void BMUSBCapture::usb_thread_func()
 {
        sched_param param;
@@ -703,8 +740,30 @@ struct USBCardDevice {
        libusb_device *device;
 };
 
+const char *get_product_name(uint16_t product)
+{
+       if (product == 0xbd3b) {
+               return "Intensity Shuttle";
+       } else if (product == 0xbd4f) {
+               return "UltraStudio SDI";
+       } else {
+               assert(false);
+               return nullptr;
+       }
+}
+
+string get_card_description(int id, uint8_t bus, uint8_t port, uint16_t product)
+{
+       const char *product_name = get_product_name(product);
+
+       char buf[256];
+       snprintf(buf, sizeof(buf), "USB card %d: Bus %03u Device %03u  %s",
+               id, bus, port, product_name);
+       return buf;
+}
+
 libusb_device_handle *open_card(int card_index, string *description)
-{      
+{
        libusb_device **devices;
        ssize_t num_devices = libusb_get_device_list(nullptr, &devices);
        if (num_devices == -1) {
@@ -722,8 +781,8 @@ libusb_device_handle *open_card(int card_index, string *description)
                uint8_t bus = libusb_get_bus_number(devices[i]);
                uint8_t port = libusb_get_port_number(devices[i]);
 
-               if (!(desc.idVendor == 0x1edb && desc.idProduct == 0xbd3b) &&
-                   !(desc.idVendor == 0x1edb && desc.idProduct == 0xbd4f)) {
+               if (!(desc.idVendor == USB_VENDOR_BLACKMAGIC && desc.idProduct == 0xbd3b) &&
+                   !(desc.idVendor == USB_VENDOR_BLACKMAGIC && desc.idProduct == 0xbd4f)) {
                        libusb_unref_device(devices[i]);
                        continue;
                }
@@ -742,22 +801,11 @@ libusb_device_handle *open_card(int card_index, string *description)
        });
 
        for (size_t i = 0; i < found_cards.size(); ++i) {
-               const char *product_name = nullptr;
-               if (found_cards[i].product == 0xbd3b) {
-                       product_name = "Intensity Shuttle";
-               } else if (found_cards[i].product == 0xbd4f) {
-                       product_name = "UltraStudio SDI";
-               } else {
-                       assert(false);
-               }
-
-               char buf[256];
-               snprintf(buf, sizeof(buf), "USB card %d: Bus %03u Device %03u  %s",
-                       int(i), found_cards[i].bus, found_cards[i].port, product_name);
+               string tmp_description = get_card_description(i, found_cards[i].bus, found_cards[i].port, found_cards[i].product);
+               fprintf(stderr, "%s\n", tmp_description.c_str());
                if (i == size_t(card_index)) {
-                       *description = buf;
+                       *description = tmp_description;
                }
-               fprintf(stderr, "%s\n", buf);
        }
 
        if (size_t(card_index) >= found_cards.size()) {
@@ -779,6 +827,29 @@ libusb_device_handle *open_card(int card_index, string *description)
        return devh;
 }
 
+libusb_device_handle *open_card(unsigned card_index, libusb_device *dev, string *description)
+{
+       uint8_t bus = libusb_get_bus_number(dev);
+       uint8_t port = libusb_get_port_number(dev);
+
+       libusb_device_descriptor desc;
+       if (libusb_get_device_descriptor(dev, &desc) < 0) {
+               fprintf(stderr, "Error getting device descriptor for device %p\n", dev);
+               exit(1);
+       }
+
+       *description = get_card_description(card_index, bus, port, desc.idProduct);
+
+       libusb_device_handle *devh;
+       int rc = libusb_open(dev, &devh);
+       if (rc < 0) {
+               fprintf(stderr, "Error opening card %p: %s\n", dev, libusb_error_name(rc));
+               exit(1);
+       }
+
+       return devh;
+}
+
 void BMUSBCapture::configure_card()
 {
        if (video_frame_allocator == nullptr) {
@@ -801,7 +872,12 @@ void BMUSBCapture::configure_card()
                exit(1);
        }
 
-       devh = open_card(card_index, &description);
+       if (dev == nullptr) {
+               devh = open_card(card_index, &description);
+       } else {
+               devh = open_card(card_index, dev, &description);
+               libusb_unref_device(dev);
+       }
        if (!devh) {
                fprintf(stderr, "Error finding USB device\n");
                exit(1);
@@ -1117,6 +1193,18 @@ void BMUSBCapture::stop_dequeue_thread()
 
 void BMUSBCapture::start_bm_thread()
 {
+       // Devices leaving are discovered by seeing the isochronous packets
+       // coming back with errors, so only care about devices joining.
+       if (card_connected_callback != nullptr) {
+               if (libusb_hotplug_register_callback(
+                       nullptr, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, LIBUSB_HOTPLUG_NO_FLAGS,
+                       USB_VENDOR_BLACKMAGIC, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY,
+                       &BMUSBCapture::cb_hotplug, nullptr, nullptr) < 0) {
+                       fprintf(stderr, "libusb_hotplug_register_callback() failed\n");
+                       exit(1);
+               }
+       }
+
        should_quit = false;
        usb_thread = thread(&BMUSBCapture::usb_thread_func);
 }