X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=bmusb.cpp;h=fc38520f5dcee7a3d0b71d2126e9b6692c4ef087;hb=2a06d276da04a363f0a7d4043b319229b0a5fd7f;hp=0582b99631b5d30de538adb5e50b12002504e66e;hpb=adfd480956f743d2e499b3ea8eb5a9a8c5f02b5c;p=bmusb diff --git a/bmusb.cpp b/bmusb.cpp index 0582b99..fc38520 100644 --- a/bmusb.cpp +++ b/bmusb.cpp @@ -33,23 +33,50 @@ using namespace std; using namespace std::placeholders; -#define WIDTH 1280 -#define HEIGHT 750 /* 30 lines ancillary data? */ -//#define WIDTH 1920 -//#define HEIGHT 1125 /* ??? lines ancillary data? */ +#define MIN_WIDTH 640 #define HEADER_SIZE 44 //#define HEADER_SIZE 0 #define AUDIO_HEADER_SIZE 4 -//#define FRAME_SIZE (WIDTH * HEIGHT * 2 + HEADER_SIZE) // UYVY -//#define FRAME_SIZE (WIDTH * HEIGHT * 2 * 4 / 3 + HEADER_SIZE) // v210 -#define FRAME_SIZE (8 << 20) +#define FRAME_SIZE (8 << 20) // 8 MB. +#define USB_VIDEO_TRANSFER_SIZE (128 << 10) // 128 kB. + +namespace { FILE *audiofp; thread usb_thread; atomic should_quit; +int find_xfer_size_for_width(int width) +{ + // Video seems to require isochronous packets scaled with the width; + // seemingly six lines is about right, rounded up to the required 1kB + // multiple. + int size = width * 2 * 6; + // Note that for 10-bit input, you'll need to increase size accordingly. + //size = size * 4 / 3; + if (size % 1024 != 0) { + size &= ~1023; + size += 1024; + } + return size; +} + +void change_xfer_size_for_width(int width, libusb_transfer *xfr) +{ + assert(width >= MIN_WIDTH); + size_t size = find_xfer_size_for_width(width); + int num_iso_pack = xfr->length / size; + if (num_iso_pack != xfr->num_iso_packets || + size != xfr->iso_packet_desc[0].length) { + xfr->num_iso_packets = num_iso_pack; + libusb_set_iso_packet_lengths(xfr, size); + } +} + +} // namespace + FrameAllocator::~FrameAllocator() {} // Audio is more important than video, and also much cheaper. @@ -159,13 +186,17 @@ void BMUSBCapture::dequeue_thread_func() unique_lock 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 (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(); + 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); @@ -222,6 +253,15 @@ void BMUSBCapture::start_new_frame(const uint8_t *start) } //dump_frame(); queue_frame(format, timecode, current_video_frame, &pending_video_frames); + + // Update the assumed frame width. We might be one frame too late on format changes, + // but it's much better than asking the user to choose manually. + unsigned width, height, second_field_start, extra_lines_top, extra_lines_bottom, frame_rate_nom, frame_rate_den; + bool interlaced; + if (decode_video_format(format, &width, &height, &second_field_start, &extra_lines_top, &extra_lines_bottom, + &frame_rate_nom, &frame_rate_den, &interlaced)) { + assumed_frame_width = width; + } } //printf("Found frame start, format 0x%04x timecode 0x%04x, previous frame length was %d/%d\n", // format, timecode, @@ -593,6 +633,9 @@ void BMUSBCapture::cb_xfr(struct libusb_transfer *xfr) decode_packs(xfr, "DeckLinkAudioResyncT", 20, &usb->current_audio_frame, "audio", bind(&BMUSBCapture::start_new_audio_block, usb, _1)); } else { decode_packs(xfr, "\x00\x00\xff\xff", 4, &usb->current_video_frame, "video", bind(&BMUSBCapture::start_new_frame, usb, _1)); + + // Update the transfer with the new assumed width, if we're in the process of changing formats. + change_xfer_size_for_width(usb->assumed_frame_width, xfr); } } if (xfr->type == LIBUSB_TRANSFER_TYPE_CONTROL) { @@ -763,6 +806,8 @@ void BMUSBCapture::configure_card() fprintf(stderr, "Error getting configuration: %s\n", libusb_error_name(rc)); exit(1); } + +#if 0 printf("%d interface\n", config->bNumInterfaces); for (int interface_number = 0; interface_number < config->bNumInterfaces; ++interface_number) { printf(" interface %d\n", interface_number); @@ -776,6 +821,7 @@ void BMUSBCapture::configure_card() } } } +#endif rc = libusb_set_configuration(devh, /*configuration=*/1); if (rc < 0) { @@ -904,12 +950,18 @@ void BMUSBCapture::configure_card() fprintf(stderr, "Error on control %d: %s\n", ctrls[req].index, libusb_error_name(rc)); exit(1); } - + + if (ctrls[req].index == 16 && rc == 4) { + printf("Card firmware version: 0x%02x%02x\n", value[2], value[3]); + } + +#if 0 printf("rc=%d: ep=%d@%d %d -> 0x", rc, ctrls[req].endpoint, ctrls[req].request, ctrls[req].index); for (int i = 0; i < rc; ++i) { printf("%02x", value[i]); } printf("\n"); +#endif } #if 0 @@ -977,26 +1029,22 @@ void BMUSBCapture::configure_card() //int num_transfers = (e == 3) ? 6 : 6; int num_transfers = 10; for (int i = 0; i < num_transfers; ++i) { + size_t buf_size; int num_iso_pack, size; if (e == 3) { - // Video seems to require isochronous packets scaled with the width; - // seemingly six lines is about right, rounded up to the required 1kB - // multiple. - size = WIDTH * 2 * 6; - // Note that for 10-bit input, you'll need to increase size accordingly. - //size = size * 4 / 3; - if (size % 1024 != 0) { - size &= ~1023; - size += 1024; - } - num_iso_pack = (2 << 16) / size; // 128 kB. - printf("Picking %d packets of 0x%x bytes each\n", num_iso_pack, size); + // Allocate for minimum width (because that will give us the most + // number of packets, so we don't need to reallocated, but we'll + // default to 720p for the first frame. + size = find_xfer_size_for_width(MIN_WIDTH); + num_iso_pack = USB_VIDEO_TRANSFER_SIZE / size; + buf_size = USB_VIDEO_TRANSFER_SIZE; } else { size = 0xc0; num_iso_pack = 80; + buf_size = num_iso_pack * size; } - int num_bytes = num_iso_pack * size; - uint8_t *buf = new uint8_t[num_bytes]; + assert(size_t(num_iso_pack * size) <= buf_size); + uint8_t *buf = new uint8_t[buf_size]; xfr = libusb_alloc_transfer(num_iso_pack); if (!xfr) { @@ -1005,10 +1053,15 @@ void BMUSBCapture::configure_card() } int ep = LIBUSB_ENDPOINT_IN | e; - libusb_fill_iso_transfer(xfr, devh, ep, buf, num_bytes, + libusb_fill_iso_transfer(xfr, devh, ep, buf, buf_size, num_iso_pack, cb_xfr, nullptr, 0); libusb_set_iso_packet_lengths(xfr, size); xfr->user_data = this; + + if (e == 3) { + change_xfer_size_for_width(assumed_frame_width, xfr); + } + iso_xfrs.push_back(xfr); } } @@ -1016,10 +1069,8 @@ void BMUSBCapture::configure_card() void BMUSBCapture::start_bm_capture() { - printf("starting capture\n"); int i = 0; for (libusb_transfer *xfr : iso_xfrs) { - printf("submitting transfer...\n"); int rc = libusb_submit_transfer(xfr); ++i; if (rc < 0) { @@ -1059,3 +1110,109 @@ void BMUSBCapture::stop_bm_thread() should_quit = true; usb_thread.join(); } + +struct VideoFormatEntry { + uint16_t normalized_video_format; + unsigned width, height, second_field_start; + unsigned extra_lines_top, extra_lines_bottom; + unsigned frame_rate_nom, frame_rate_den; + bool interlaced; +}; + +bool decode_video_format(uint16_t video_format, unsigned *width, unsigned *height, unsigned *second_field_start, + unsigned *extra_lines_top, unsigned *extra_lines_bottom, + unsigned *frame_rate_nom, unsigned *frame_rate_den, bool *interlaced) +{ + *interlaced = false; + + // TODO: Add these for all formats as we find them. + *extra_lines_top = *extra_lines_bottom = *second_field_start = 0; + + 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. + *width = 720; + *height = 525; + *extra_lines_top = 0; + *extra_lines_bottom = 0; + *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; + *extra_lines_top = 0; + *extra_lines_bottom = 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 = 720; + *height = 480; + *extra_lines_top = 17; + *extra_lines_bottom = 28; + *frame_rate_nom = 30000; + *frame_rate_den = 1001; + *second_field_start = 280; + *interlaced = true; + return true; + } + + // PAL (576i50, I suppose). A special case, see below. + if (video_format == 0xe909 || video_format == 0xe9c9 || video_format == 0xe809 || video_format == 0xebe9) { + *width = 720; + *height = 576; + *extra_lines_top = 22; + *extra_lines_bottom = 27; + *frame_rate_nom = 25; + *frame_rate_den = 1; + *second_field_start = 335; + *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, 0, 25, 5, 50, 1, false }, // 720p50. + { 0x0103, 1280, 720, 0, 25, 5, 60, 1, false }, // 720p60. + { 0x0121, 1280, 720, 0, 25, 5, 60000, 1001, false }, // 720p59.94. + { 0x01c3, 1920, 1080, 0, 0, 0, 30, 1, false }, // 1080p30. + { 0x0003, 1920, 1080, 583, 20, 25, 30, 1, true }, // 1080i60. + { 0x01e1, 1920, 1080, 0, 0, 0, 30000, 1001, false }, // 1080p29.97. + { 0x0021, 1920, 1080, 583, 20, 25, 30000, 1001, true }, // 1080i59.94. + { 0x0063, 1920, 1080, 0, 0, 0, 25, 1, false }, // 1080p25. + { 0x0043, 1920, 1080, 0, 0, 0, 25, 1, true }, // 1080p50. + { 0x008e, 1920, 1080, 0, 0, 0, 24, 1, false }, // 1080p24. + { 0x00a1, 1920, 1080, 0, 0, 0, 24000, 1001, false }, // 1080p23.98. + }; + for (const VideoFormatEntry &entry : entries) { + if (normalized_video_format == entry.normalized_video_format) { + *width = entry.width; + *height = entry.height; + *second_field_start = entry.second_field_start; + *extra_lines_top = entry.extra_lines_top; + *extra_lines_bottom = entry.extra_lines_bottom; + *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; +}