]> git.sesse.net Git - bmusb/blobdiff - bmusb.cpp
Add a function to get video format details.
[bmusb] / bmusb.cpp
index 6867805e719e4c6983af99063f343cbb2dd94b1f..74adca86c14c603c2c0855c41ba319849365638a 100644 (file)
--- a/bmusb.cpp
+++ b/bmusb.cpp
@@ -52,10 +52,16 @@ atomic<bool> should_quit;
 
 FrameAllocator::~FrameAllocator() {}
 
-#define NUM_QUEUED_FRAMES 16
+// Audio is more important than video, and also much cheaper.
+// By having many more audio frames available, hopefully if something
+// starts to drop, we'll have CPU load go down (from not having to
+// process as much video) before we have to drop audio.
+#define NUM_QUEUED_VIDEO_FRAMES 16
+#define NUM_QUEUED_AUDIO_FRAMES 64
+
 class MallocFrameAllocator : public FrameAllocator {
 public:
-       MallocFrameAllocator(size_t frame_size);
+       MallocFrameAllocator(size_t frame_size, size_t num_queued_frames);
        Frame alloc_frame() override;
        void release_frame(Frame frame) override;
 
@@ -66,10 +72,10 @@ private:
        stack<unique_ptr<uint8_t[]>> freelist;  // All of size <frame_size>.
 };
 
-MallocFrameAllocator::MallocFrameAllocator(size_t frame_size)
+MallocFrameAllocator::MallocFrameAllocator(size_t frame_size, size_t num_queued_frames)
        : frame_size(frame_size)
 {
-       for (int i = 0; i < NUM_QUEUED_FRAMES; ++i) {
+       for (size_t i = 0; i < num_queued_frames; ++i) {
                freelist.push(unique_ptr<uint8_t[]>(new uint8_t[frame_size]));
        }
 }
@@ -93,6 +99,9 @@ FrameAllocator::Frame MallocFrameAllocator::alloc_frame()
 
 void MallocFrameAllocator::release_frame(Frame frame)
 {
+       if (frame.overflow > 0) {
+               printf("%d bytes overflow after last (malloc) frame\n", int(frame.overflow));
+       }
        unique_lock<mutex> lock(freelist_mutex);
        freelist.push(unique_ptr<uint8_t[]>(frame.data));
 }
@@ -111,6 +120,7 @@ bool uint16_less_than_with_wraparound(uint16_t a, uint16_t b)
 
 void BMUSBCapture::queue_frame(uint16_t format, uint16_t timecode, FrameAllocator::Frame frame, deque<QueuedFrame> *q)
 {
+       unique_lock<mutex> lock(queue_lock);
        if (!q->empty() && !uint16_less_than_with_wraparound(q->back().timecode, timecode)) {
                printf("Blocks going backwards: prev=0x%04x, cur=0x%04x (dropped)\n",
                        q->back().timecode, timecode);
@@ -122,11 +132,7 @@ void BMUSBCapture::queue_frame(uint16_t format, uint16_t timecode, FrameAllocato
        qf.format = format;
        qf.timecode = timecode;
        qf.frame = frame;
-
-       {
-               unique_lock<mutex> lock(queue_lock);
-               q->push_back(move(qf));
-       }
+       q->push_back(move(qf));
        queues_not_empty.notify_one();  // might be spurious
 }
 
@@ -153,18 +159,26 @@ void BMUSBCapture::dequeue_thread_func()
                unique_lock<mutex> lock(queue_lock);
                queues_not_empty.wait(lock, [this]{ return dequeue_thread_should_quit || (!pending_video_frames.empty() && !pending_audio_frames.empty()); });
 
+               if (dequeue_thread_should_quit) break;
+
                uint16_t video_timecode = pending_video_frames.front().timecode;
                uint16_t audio_timecode = pending_audio_frames.front().timecode;
-               if (video_timecode < audio_timecode) {
+               if (uint16_less_than_with_wraparound(video_timecode, audio_timecode)) {
                        printf("Video block 0x%04x without corresponding audio block, dropping.\n",
                                video_timecode);
-                       video_frame_allocator->release_frame(pending_video_frames.front().frame);
+                       QueuedFrame video_frame = pending_video_frames.front();
                        pending_video_frames.pop_front();
-               } else if (audio_timecode < video_timecode) {
-                       printf("Audio block 0x%04x without corresponding video block, dropping.\n",
+                       lock.unlock();
+                       video_frame_allocator->release_frame(video_frame.frame);
+               } else if (uint16_less_than_with_wraparound(audio_timecode, video_timecode)) {
+                       printf("Audio block 0x%04x without corresponding video block, sending blank frame.\n",
                                audio_timecode);
-                       audio_frame_allocator->release_frame(pending_audio_frames.front().frame);
+                       QueuedFrame audio_frame = pending_audio_frames.front();
                        pending_audio_frames.pop_front();
+                       lock.unlock();
+                       frame_callback(audio_timecode,
+                                      FrameAllocator::Frame(), 0, 0x0000,
+                                      audio_frame.frame, AUDIO_HEADER_SIZE, audio_frame.format);
                } else {
                        QueuedFrame video_frame = pending_video_frames.front();
                        QueuedFrame audio_frame = pending_audio_frames.front();
@@ -195,6 +209,21 @@ void BMUSBCapture::start_new_frame(const uint8_t *start)
        uint16_t timecode = (start[1] << 8) | start[0];
 
        if (current_video_frame.len > 0) {
+               // If format is 0x0800 (no signal), add a fake (empty) audio
+               // frame to get it out of the queue.
+               // TODO: Figure out if there are other formats that come with
+               // no audio, and treat them the same.
+               if (format == 0x0800) {
+                       FrameAllocator::Frame fake_audio_frame = audio_frame_allocator->alloc_frame();
+                       if (fake_audio_frame.data == nullptr) {
+                               // Oh well, it's just a no-signal frame anyway.
+                               printf("Couldn't allocate fake audio frame, also dropping no-signal video frame.\n");
+                               current_video_frame.owner->release_frame(current_video_frame);
+                               current_video_frame = video_frame_allocator->alloc_frame();
+                               return;
+                       }
+                       queue_frame(format, timecode, fake_audio_frame, &pending_audio_frames);
+               }
                //dump_frame();
                queue_frame(format, timecode, current_video_frame, &pending_video_frames);
        }
@@ -263,8 +292,13 @@ void add_to_frame(FrameAllocator::Frame *current_frame, const char *frame_type_n
 
        int bytes = end - start;
        if (current_frame->len + bytes > current_frame->size) {
-               printf("%d bytes overflow after last %s frame\n",
-                       int(current_frame->len + bytes - current_frame->size), frame_type_name);
+               current_frame->overflow = current_frame->len + bytes - current_frame->size;
+               current_frame->len = current_frame->size;
+               if (current_frame->overflow > 1048576) {
+                       printf("%d bytes overflow after last %s frame\n",
+                               int(current_frame->overflow), frame_type_name);
+                       current_frame->overflow = 0;
+               }
                //dump_frame();
        } else {
                if (current_frame->interleaved) {
@@ -605,8 +639,9 @@ void BMUSBCapture::cb_xfr(struct libusb_transfer *xfr)
        }
 #endif
 
-       if (libusb_submit_transfer(xfr) < 0) {
-               fprintf(stderr, "error re-submitting URB\n");
+       int rc = libusb_submit_transfer(xfr);
+       if (rc < 0) {
+               fprintf(stderr, "error re-submitting URB: %s\n", libusb_error_name(rc));
                exit(1);
        }
 }
@@ -626,13 +661,87 @@ void BMUSBCapture::usb_thread_func()
        }
 }
 
+struct USBCardDevice {
+       uint16_t product;
+       uint8_t bus, port;
+       libusb_device *device;
+};
+
+libusb_device_handle *open_card(int card_index)
+{      
+       libusb_device **devices;
+       ssize_t num_devices = libusb_get_device_list(nullptr, &devices);
+       if (num_devices == -1) {
+               fprintf(stderr, "Error finding USB devices\n");
+               exit(1);
+       }
+       vector<USBCardDevice> found_cards;
+       for (ssize_t i = 0; i < num_devices; ++i) {
+               libusb_device_descriptor desc;
+                if (libusb_get_device_descriptor(devices[i], &desc) < 0) {
+                       fprintf(stderr, "Error getting device descriptor for device %d\n", int(i));
+                       exit(1);
+               }
+
+               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)) {
+                       libusb_unref_device(devices[i]);
+                       continue;
+               }
+
+               found_cards.push_back({ desc.idProduct, bus, port, devices[i] });
+       }
+       libusb_free_device_list(devices, 0);
+
+       // Sort the devices to get a consistent ordering.
+       sort(found_cards.begin(), found_cards.end(), [](const USBCardDevice &a, const USBCardDevice &b) {
+               if (a.product != b.product)
+                       return a.product < b.product;
+               if (a.bus != b.bus)
+                       return a.bus < b.bus;
+               return a.port < b.port;
+       });
+
+       for (size_t i = 0; i < found_cards.size(); ++i) {
+               fprintf(stderr, "Card %d: Bus %03u Device %03u ", int(i), found_cards[i].bus, found_cards[i].port);
+               if (found_cards[i].product == 0xbd3b) {
+                       fprintf(stderr, "Intensity Shuttle\n");
+               } else if (found_cards[i].product == 0xbd4f) {
+                       fprintf(stderr, "UltraStudio SDI\n");
+               } else {
+                       assert(false);
+               }
+       }
+
+       if (size_t(card_index) >= found_cards.size()) {
+               fprintf(stderr, "Could not open card %d (only %d found)\n", card_index, int(found_cards.size()));
+               exit(1);
+       }
+
+       libusb_device_handle *devh;
+       int rc = libusb_open(found_cards[card_index].device, &devh);
+       if (rc < 0) {
+               fprintf(stderr, "Error opening card %d: %s\n", card_index, libusb_error_name(rc));
+               exit(1);
+       }
+
+       for (size_t i = 0; i < found_cards.size(); ++i) {
+               libusb_unref_device(found_cards[i].device);
+       }
+
+       return devh;
+}
+
 void BMUSBCapture::configure_card()
 {
        if (video_frame_allocator == nullptr) {
-               set_video_frame_allocator(new MallocFrameAllocator(FRAME_SIZE));  // FIXME: leak.
+               set_video_frame_allocator(new MallocFrameAllocator(FRAME_SIZE, NUM_QUEUED_VIDEO_FRAMES));  // FIXME: leak.
        }
        if (audio_frame_allocator == nullptr) {
-               set_audio_frame_allocator(new MallocFrameAllocator(65536));  // FIXME: leak.
+               set_audio_frame_allocator(new MallocFrameAllocator(65536, NUM_QUEUED_AUDIO_FRAMES));  // FIXME: leak.
        }
        dequeue_thread_should_quit = false;
        dequeue_thread = thread(&BMUSBCapture::dequeue_thread_func, this);
@@ -646,9 +755,7 @@ void BMUSBCapture::configure_card()
                exit(1);
        }
 
-       //struct libusb_device_handle *devh = libusb_open_device_with_vid_pid(nullptr, 0x1edb, 0xbd3b);
-       //struct libusb_device_handle *devh = libusb_open_device_with_vid_pid(nullptr, 0x1edb, 0xbd4f);
-       struct libusb_device_handle *devh = libusb_open_device_with_vid_pid(nullptr, vid, pid);
+       libusb_device_handle *devh = open_card(card_index);
        if (!devh) {
                fprintf(stderr, "Error finding USB device\n");
                exit(1);
@@ -699,7 +806,7 @@ void BMUSBCapture::configure_card()
        }
        rc = libusb_set_interface_alt_setting(devh, /*interface=*/0, /*alternate_setting=*/2);
        if (rc < 0) {
-               fprintf(stderr, "Error setting alternate 1: %s\n", libusb_error_name(rc));
+               fprintf(stderr, "Error setting alternate 2: %s\n", libusb_error_name(rc));
                exit(1);
        }
 #if 0
@@ -872,7 +979,7 @@ void BMUSBCapture::configure_card()
        // set up isochronous transfers for audio and video
        for (int e = 3; e <= 4; ++e) {
                //int num_transfers = (e == 3) ? 6 : 6;
-               int num_transfers = 6;
+               int num_transfers = 10;
                for (int i = 0; i < num_transfers; ++i) {
                        int num_iso_pack, size;
                        if (e == 3) {
@@ -886,7 +993,7 @@ void BMUSBCapture::configure_card()
                                        size &= ~1023;
                                        size += 1024;
                                }
-                               num_iso_pack = (2 << 18) / size;  // 512 kB.
+                               num_iso_pack = (2 << 16) / size;  // 128 kB.
                                printf("Picking %d packets of 0x%x bytes each\n", num_iso_pack, size);
                        } else {
                                size = 0xc0;
@@ -956,3 +1063,89 @@ void BMUSBCapture::stop_bm_thread()
        should_quit = true;
        usb_thread.join();
 }
+
+struct VideoFormatEntry {
+       uint16_t normalized_video_format;
+       int width, height;
+       int frame_rate_den, frame_rate_nom;
+       bool interlaced;
+};
+
+bool decode_video_format(uint16_t video_format, int *width, int *height, int *frame_rate_den, int *frame_rate_nom, bool *interlaced)
+{
+       *interlaced = false;
+
+       if (video_format == 0x0800) {
+               // No video signal. These green pseudo-frames seem to come at about 30.13 Hz.
+               // It's a strange thing, but what can you do.
+               // FIXME: find the dimensions.
+               *frame_rate_nom = 3013;
+               *frame_rate_den = 100;
+               return true;
+       }
+       if ((video_format & 0xe800) != 0xe800) {
+               printf("Video format 0x%04x does not appear to be a video format. Assuming 60 Hz.\n",
+                       video_format);
+               *width = 0;
+               *height = 0;
+               *frame_rate_nom = 60;
+               *frame_rate_den = 1;
+               return false;
+       }
+
+       // NTSC (480i59.94, I suppose). A special case, see below.
+       if (video_format == 0xe901 || video_format == 0xe9c1 || video_format == 0xe801) {
+               *width = 640;
+               *height = 480;
+               *frame_rate_nom = 60000;
+               *frame_rate_den = 1001;
+               *interlaced = true;
+               return true;
+       }
+
+       // PAL (576i50, I suppose). A special case, see below.
+       if (video_format == 0xe909) {
+               *width = 720;
+               *height = 576;
+               *frame_rate_nom = 50;
+               *frame_rate_den = 1;
+               *interlaced = true;
+               return true;
+       }
+
+       // 0x8 seems to be a flag about availability of deep color on the input,
+       // except when it's not (e.g. it's the only difference between NTSC
+       // and PAL). Rather confusing. But we clear it here nevertheless, because
+       // usually it doesn't mean anything.
+       uint16_t normalized_video_format = video_format & ~0xe808;
+       constexpr VideoFormatEntry entries[] = {
+               { 0x0143, 1280,  720,    50,    1, false },  // 720p50.
+               { 0x0103, 1280,  720,    60,    1, false },  // 720p60.
+               { 0x0121, 1280,  720, 60000, 1001, false },  // 720p59.94.
+               { 0x01c3, 1920, 1080,    30,    1, false },  // 1080p30.
+               { 0x0003, 1920, 1080,    30,    1,  true },  // 1080i60.
+               { 0x01e1, 1920, 1080, 30000, 1001, false },  // 1080p29.97.
+               { 0x0021, 1920, 1080, 30000, 1001,  true },  // 1080i59.94.
+               { 0x0063, 1920, 1080,    25,    1, false },  // 1080p25.
+               { 0x0043, 1920, 1080,    25,    1,  true },  // 1080p50.
+               { 0x008e, 1920, 1080,    24,    1, false },  // 1080p24.
+               { 0x00a1, 1920, 1080, 24000, 1001, false },  // 1080p23.98.
+       };
+       for (const VideoFormatEntry &entry : entries) {
+               if (normalized_video_format == entry.normalized_video_format) {
+                       *width = entry.width;
+                       *height = entry.height;
+                       *frame_rate_nom = entry.frame_rate_nom;
+                       *frame_rate_den = entry.frame_rate_den;
+                       *interlaced = entry.interlaced;
+                       return true;
+               }
+       }
+
+       printf("Unknown video format 0x%04x. Assuming 720p60.\n", video_format);
+       *width = 1280;
+       *height = 720;
+       *frame_rate_nom = 60;
+       *frame_rate_den = 1;
+       return false;
+}