]> git.sesse.net Git - bmusb/commitdiff
Initial checkin.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Tue, 1 Sep 2015 20:48:32 +0000 (22:48 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Tue, 1 Sep 2015 20:48:32 +0000 (22:48 +0200)
COPYING [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
bmusb.cpp [new file with mode: 0644]
bmusb.h [new file with mode: 0644]
format.cpp [new file with mode: 0644]
formats.txt [new file with mode: 0644]
main.cpp [new file with mode: 0644]

diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..d159169
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..d8c5399
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,8 @@
+CXXFLAGS := -std=gnu++14 -O2 -g $(shell pkg-config libusb-1.0 --cflags) -pthread
+LDFLAGS := $(shell pkg-config libusb-1.0 --libs) -pthread
+
+main: bmusb.o main.o
+       $(CXX) -o main $^ $(LDFLAGS)
+
+all: main
+
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..e5fb92c
--- /dev/null
+++ b/README
@@ -0,0 +1,33 @@
+bmusb is a free driver for BlackMagic's Intensity Shuttle USB3 card,
+which has no official Linux driver. It runs in userspace through usbfs,
+which may mean it could also probably run on FreeBSD, but it's untested.
+
+Current tested features:
+
+ * HDMI capture on 576p60, 720p60, 1080i60 (1080p60 is unfortunately not
+   supported in newer firmwares; I haven't tried older), plus 24, 50
+   and 59.97 Hz
+ * 8-channel 24-bit 48 kHz locked audio capture
+ * Analog audio capture, including setting levels
+ * 8-bit 4:2:2 and 10-bit 4:2:2 capture
+
+The Intensity Shuttle follows a protocol whose exact format is still
+unknown, and the driver is still in alpha stage. (There is no API or
+ABI stability, for one, and everything is really messy and uncommented.)
+In particular, it will often do something on init that makes the card
+seemingly reset and disconnect off the bus (and then reset).
+This being said, once it's actually up, I've done ten-hour 720p60
+captures on my Lenovo X240 without a single drop. It seems to want about
+10–15% of one CPU core.
+
+The driver itself lives in bmusb.cpp; main.cpp contains a very simple
+client that just checks for frame continuity. It's recommended to run
+as root or some other user that can run the USB thread at realtime
+priority, as USB3 isochronous transfers are very timing sensitive.
+
+The driver has only been tested with the 10.4.3 firmware, and there is
+currently no tool to upgrade or downgrade the firmware on the card.
+
+bmusb is Copyright 2015 Steinar H. Gunderson <sgunderson@bigfoot.com>
+and licensed under the GNU General Public License, version 2, or at your
+option, any later version. See the COPYING file.
diff --git a/bmusb.cpp b/bmusb.cpp
new file mode 100644 (file)
index 0000000..4b5f75a
--- /dev/null
+++ b/bmusb.cpp
@@ -0,0 +1,800 @@
+// Intensity Shuttle USB3 prototype capture driver, v0.3
+// Can download 8-bit and 10-bit UYVY/v210 frames from HDMI, quite stable
+// (can do captures for hours at a time with no drops), except during startup
+// 576p60/720p60/1080i60 works, 1080p60 does not work (firmware limitation)
+// Audio comes out as 8-channel 24-bit raw audio.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <libusb.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <algorithm>
+#include <functional>
+#include <memory>
+#include <deque>
+#include <utility>
+#include <mutex>
+#include <condition_variable>
+#include <thread>
+#include <stack>
+#include "bmusb.h"
+
+using namespace std;
+
+static int current_register = 0;
+
+#define NUM_REGISTERS 60
+uint8_t register_file[NUM_REGISTERS];
+
+#define WIDTH 1280
+#define HEIGHT 750  /* 30 lines ancillary data? */
+//#define WIDTH 1920
+//#define HEIGHT 1125  /* ??? lines ancillary data? */
+#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)
+
+FILE *audiofp;
+
+FrameAllocator::Frame current_video_frame;
+FrameAllocator::Frame current_audio_frame;
+
+struct QueuedFrame {
+       uint16_t timecode;
+       uint16_t format;
+       FrameAllocator::Frame frame;
+};
+
+mutex queue_lock;
+condition_variable queues_not_empty;
+deque<QueuedFrame> pending_video_frames;
+deque<QueuedFrame> pending_audio_frames;
+
+FrameAllocator::~FrameAllocator() {}
+
+#define NUM_QUEUED_FRAMES 8
+class MallocFrameAllocator : public FrameAllocator {
+public:
+       MallocFrameAllocator(size_t frame_size);
+       Frame alloc_frame() override;
+       void release_frame(Frame frame) override;
+
+private:
+       size_t frame_size;
+
+       mutex freelist_mutex;
+       stack<unique_ptr<uint8_t[]>> freelist;  // All of size <frame_size>.
+};
+
+MallocFrameAllocator::MallocFrameAllocator(size_t frame_size)
+       : frame_size(frame_size)
+{
+       for (int i = 0; i < NUM_QUEUED_FRAMES; ++i) {
+               freelist.push(unique_ptr<uint8_t[]>(new uint8_t[frame_size]));
+       }
+}
+
+FrameAllocator::Frame MallocFrameAllocator::alloc_frame()
+{
+       Frame vf;
+       vf.owner = this;
+
+       unique_lock<mutex> lock(freelist_mutex);  // Meh.
+       if (freelist.empty()) {
+               printf("Frame overrun (no more spare frames of size %ld), dropping frame!\n",
+                       frame_size);
+       } else {
+               vf.data = freelist.top().release();
+               vf.size = frame_size;
+               freelist.pop();  // Meh.
+       }
+       return vf;
+}
+
+void MallocFrameAllocator::release_frame(Frame frame)
+{
+       unique_lock<mutex> lock(freelist_mutex);
+       freelist.push(unique_ptr<uint8_t[]>(frame.data));
+}
+
+FrameAllocator *video_frame_allocator = nullptr;
+FrameAllocator *audio_frame_allocator = nullptr;
+frame_callback_t frame_callback = nullptr;
+
+bool uint16_less_than_with_wraparound(uint16_t a, uint16_t b)
+{
+       if (a == b) {
+               return false;
+       } else if (a < b) {
+               return (b - a < 0x8000);
+       } else {
+               int wrap_b = 0x10000 + int(b);
+               return (wrap_b - a < 0x8000);
+       }
+}
+
+void queue_frame(uint16_t format, uint16_t timecode, FrameAllocator::Frame frame, deque<QueuedFrame> *q)
+{
+       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);
+               frame.owner->release_frame(frame);
+               return;
+       }
+
+       QueuedFrame qf;
+       qf.format = format;
+       qf.timecode = timecode;
+       qf.frame = frame;
+
+       {
+               unique_lock<mutex> lock(queue_lock);
+               q->push_back(move(qf));
+       }
+       queues_not_empty.notify_one();  // might be spurious
+}
+
+void dump_frame(const char *filename, uint8_t *frame_start, size_t frame_len)
+{
+       FILE *fp = fopen(filename, "wb");
+       if (fwrite(frame_start + HEADER_SIZE, frame_len - HEADER_SIZE, 1, fp) != 1) {
+               printf("short write!\n");
+       }
+       fclose(fp);
+}
+
+void dump_audio_block(uint8_t *audio_start, size_t audio_len)
+{
+       fwrite(audio_start + AUDIO_HEADER_SIZE, 1, audio_len - AUDIO_HEADER_SIZE, audiofp);
+}
+
+void dequeue_thread()
+{
+       for ( ;; ) {
+               unique_lock<mutex> lock(queue_lock);
+               queues_not_empty.wait(lock, []{ return !pending_video_frames.empty() && !pending_audio_frames.empty(); });
+
+               uint16_t video_timecode = pending_video_frames.front().timecode;
+               uint16_t audio_timecode = pending_audio_frames.front().timecode;
+               if (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);
+                       pending_video_frames.pop_front();
+               } else if (audio_timecode < video_timecode) {
+                       printf("Audio block 0x%04x without corresponding video block, dropping.\n",
+                               audio_timecode);
+                       audio_frame_allocator->release_frame(pending_audio_frames.front().frame);
+                       pending_audio_frames.pop_front();
+               } else {
+                       QueuedFrame video_frame = pending_video_frames.front();
+                       QueuedFrame audio_frame = pending_audio_frames.front();
+                       pending_audio_frames.pop_front();
+                       pending_video_frames.pop_front();
+                       lock.unlock();
+
+#if 0
+                       char filename[255];
+                       snprintf(filename, sizeof(filename), "%04x%04x.uyvy", video_frame.format, video_timecode);
+                       dump_frame(filename, video_frame.frame.data, video_frame.data_len);
+                       dump_audio_block(audio_frame.frame.data, audio_frame.data_len); 
+#endif
+
+                       frame_callback(video_timecode,
+                                      video_frame.frame, HEADER_SIZE, video_frame.format,
+                                      audio_frame.frame, AUDIO_HEADER_SIZE, audio_frame.format);
+               }
+       }
+}
+
+void add_current_frame(const uint8_t *start, const uint8_t *end)
+{
+       if (current_video_frame.data == nullptr ||
+           current_video_frame.len > current_video_frame.size) return;
+       if (start == end) return;
+
+       int bytes = end - start;
+       if (current_video_frame.len + bytes > current_video_frame.size) {
+               printf("%d bytes overflow after last video frame\n", current_video_frame.len + bytes - current_video_frame.size);
+               //dump_frame();
+       } else {
+               memcpy(current_video_frame.data + current_video_frame.len, start, bytes);
+               current_video_frame.len += bytes;
+       }
+}
+
+void start_new_frame(const uint8_t *start)
+{
+       uint16_t format = (start[3] << 8) | start[2];
+       uint16_t timecode = (start[1] << 8) | start[0];
+
+       if (current_video_frame.len > 0) {
+               //dump_frame();
+               queue_frame(format, timecode, current_video_frame, &pending_video_frames);
+       }
+       //printf("Found frame start, format 0x%04x timecode 0x%04x, previous frame length was %d/%d\n",
+       //      format, timecode,
+       //      //start[7], start[6], start[5], start[4],
+       //      read_current_frame, FRAME_SIZE);
+
+       current_video_frame = video_frame_allocator->alloc_frame();
+       //if (current_video_frame.data == nullptr) {
+       //      read_current_frame = -1;
+       //} else {
+       //      read_current_frame = 0;
+       //}
+}
+
+void add_current_audio(const uint8_t *start, const uint8_t *end)
+{
+       if (current_audio_frame.data == nullptr ||
+           current_audio_frame.len > current_audio_frame.size) return;
+       if (start == end) return;
+
+       int bytes = end - start;
+       if (current_audio_frame.len + bytes > current_audio_frame.size) {
+               printf("%d bytes overflow after last audio block\n", current_audio_frame.len + bytes - current_audio_frame.size);
+               //dump_audio_block();
+       } else {
+               memcpy(current_audio_frame.data + current_audio_frame.len, start, bytes);
+               current_audio_frame.len += bytes;
+       }
+}
+
+void start_new_audio_block(const uint8_t *start)
+{
+       uint16_t format = (start[3] << 8) | start[2];
+       uint16_t timecode = (start[1] << 8) | start[0];
+       if (current_audio_frame.len > 0) {
+               //dump_audio_block();
+               queue_frame(format, timecode, current_audio_frame, &pending_audio_frames);
+       }
+       //printf("Found audio block start, format 0x%04x timecode 0x%04x, previous block length was %d\n",
+       //      format, timecode, read_current_audio_block);
+       current_audio_frame = audio_frame_allocator->alloc_frame();
+}
+
+static void dump_pack(const libusb_transfer *xfr, int offset, const libusb_iso_packet_descriptor *pack)
+{
+       //      printf("ISO pack%u length:%u, actual_length:%u, offset:%u\n", i, pack->length, pack->actual_length, offset);
+       for (int j = 0; j < pack->actual_length; j++) {
+       //for (int j = 0; j < min(pack->actual_length, 16u); j++) {
+               printf("%02x", xfr->buffer[j + offset]);
+               if ((j % 16) == 15)
+                       printf("\n");
+               else if ((j % 8) == 7)
+                       printf("  ");
+               else
+                       printf(" ");
+       }
+}
+
+void decode_packs(const libusb_transfer *xfr, const char *sync_pattern, int sync_length, function<void(const uint8_t *start, const uint8_t *end)> add_callback, function<void(const uint8_t *start)> start_callback)
+{
+       int offset = 0;
+       for (unsigned i = 0; i < xfr->num_iso_packets; i++) {
+               const libusb_iso_packet_descriptor *pack = &xfr->iso_packet_desc[i];
+
+               if (pack->status != LIBUSB_TRANSFER_COMPLETED) {
+                       fprintf(stderr, "Error: pack %u/%u status %d\n", i, xfr->num_iso_packets, pack->status);
+                       continue;
+//exit(5);
+               }
+
+               const unsigned char *iso_start = xfr->buffer + offset;
+               for (int iso_offset = 0; iso_offset < pack->actual_length; ) {  // Usually runs only one iteration.
+                       const unsigned char* start_next_frame = (const unsigned char *)memmem(iso_start + iso_offset, pack->actual_length - iso_offset, sync_pattern, sync_length);
+                       if (start_next_frame == nullptr) {
+                               // add the rest of the buffer
+                               add_callback(iso_start + iso_offset, iso_start + pack->actual_length);
+                               break;
+                       } else {
+                               add_callback(iso_start + iso_offset, start_next_frame);
+                               start_callback(start_next_frame + sync_length);
+
+                               int suboffset = start_next_frame - iso_start;
+                               iso_offset = suboffset + sync_length;  // skip sync
+                       }
+               }
+#if 0
+               dump_pack(xfr, offset, pack);
+#endif
+               offset += pack->length;
+       }
+}
+
+static void cb_xfr(struct libusb_transfer *xfr)
+{
+       if (xfr->status != LIBUSB_TRANSFER_COMPLETED) {
+               fprintf(stderr, "transfer status %d\n", xfr->status);
+               libusb_free_transfer(xfr);
+               exit(3);
+       }
+
+       if (xfr->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) {
+               if (xfr->endpoint == 0x84) {
+                       decode_packs(xfr, "DeckLinkAudioResyncT", 20, add_current_audio, start_new_audio_block);
+               } else {
+                       decode_packs(xfr, "\x00\x00\xff\xff", 4, add_current_frame, start_new_frame);
+               }
+       }
+       if (xfr->type == LIBUSB_TRANSFER_TYPE_CONTROL) {
+               const libusb_control_setup *setup = libusb_control_transfer_get_setup(xfr);
+               uint8_t *buf = libusb_control_transfer_get_data(xfr);
+#if 0
+               if (setup->wIndex == 44) {
+                       printf("read timer register: 0x%02x%02x%02x%02x\n", buf[0], buf[1], buf[2], buf[3]);
+               } else {
+                       printf("read register %2d:                      0x%02x%02x%02x%02x\n",
+                               setup->wIndex, buf[0], buf[1], buf[2], buf[3]);
+               }
+#else
+               memcpy(register_file + current_register, buf, 4);
+               current_register = (current_register + 4) % NUM_REGISTERS;
+               if (current_register == 0) {
+                       // read through all of them
+                       printf("register dump:");
+                       for (int i = 0; i < NUM_REGISTERS; i += 4) {
+                               printf(" 0x%02x%02x%02x%02x", register_file[i], register_file[i + 1], register_file[i + 2], register_file[i + 3]);
+                       }
+                       printf("\n");
+               }
+               libusb_fill_control_setup(xfr->buffer,
+                   LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN, /*request=*/214, /*value=*/0,
+                       /*index=*/current_register, /*length=*/4);
+#endif
+       }
+
+#if 0
+       printf("length:%u, actual_length:%u\n", xfr->length, xfr->actual_length);
+       for (i = 0; i < xfr->actual_length; i++) {
+               printf("%02x", xfr->buffer[i]);
+               if (i % 16)
+                       printf("\n");
+               else if (i % 8)
+                       printf("  ");
+               else
+                       printf(" ");
+       }
+#endif
+
+end:
+       if (libusb_submit_transfer(xfr) < 0) {
+               fprintf(stderr, "error re-submitting URB\n");
+               exit(1);
+       }
+}
+
+void usb_thread()
+{
+       printf("usb thread started\n");
+
+       sched_param param;
+       memset(&param, 0, sizeof(param));
+       param.sched_priority = 1;
+       if (sched_setscheduler(0, SCHED_RR, &param) == -1) {
+               printf("couldn't set realtime priority for USB thread: %s\n", strerror(errno));
+       }
+       while (true) {
+               int rc = libusb_handle_events(nullptr);
+               if (rc != LIBUSB_SUCCESS)
+                       break;
+       }
+}
+
+FrameAllocator *get_video_frame_allocator()
+{
+       return video_frame_allocator;
+}
+
+void set_video_frame_allocator(FrameAllocator *allocator)
+{
+       video_frame_allocator = allocator;
+}
+
+FrameAllocator *get_audio_frame_allocator()
+{
+       return audio_frame_allocator;
+}
+
+void set_audio_frame_allocator(FrameAllocator *allocator)
+{
+       audio_frame_allocator = allocator;
+}
+
+void set_frame_callback(frame_callback_t callback)
+{
+       frame_callback = callback;
+}
+
+void start_bm_capture()
+{
+       if (video_frame_allocator == nullptr) {
+               set_video_frame_allocator(new MallocFrameAllocator(FRAME_SIZE));  // FIXME: leak.
+       }
+       if (audio_frame_allocator == nullptr) {
+               set_audio_frame_allocator(new MallocFrameAllocator(65536));  // FIXME: leak.
+       }
+       thread(dequeue_thread).detach();
+
+       int rc;
+       struct libusb_transfer *xfr;
+       vector<libusb_transfer *> iso_xfrs;
+
+       rc = libusb_init(nullptr);
+       if (rc < 0) {
+               fprintf(stderr, "Error initializing libusb: %s\n", libusb_error_name(rc));
+               exit(1);
+       }
+
+       struct libusb_device_handle *devh = libusb_open_device_with_vid_pid(nullptr, 0x1edb, 0xbd3b);
+       if (!devh) {
+               fprintf(stderr, "Error finding USB device\n");
+               exit(1);
+       }
+
+       libusb_config_descriptor *config;
+       rc = libusb_get_config_descriptor(libusb_get_device(devh), /*config_index=*/0, &config);
+       if (rc < 0) {
+               fprintf(stderr, "Error getting configuration: %s\n", libusb_error_name(rc));
+               exit(1);
+       }
+       printf("%d interface\n", config->bNumInterfaces);
+       for (int interface_number = 0; interface_number < config->bNumInterfaces; ++interface_number) {
+               printf("  interface %d\n", interface_number);
+               const libusb_interface *interface = &config->interface[interface_number];
+               for (int altsetting = 0; altsetting < interface->num_altsetting; ++altsetting) {
+                       printf("    alternate setting %d\n", altsetting);
+                       const libusb_interface_descriptor *interface_desc = &interface->altsetting[altsetting];
+                       for (int endpoint_number = 0; endpoint_number < interface_desc->bNumEndpoints; ++endpoint_number) {
+                               const libusb_endpoint_descriptor *endpoint = &interface_desc->endpoint[endpoint_number];
+                               printf("        endpoint address 0x%02x\n", endpoint->bEndpointAddress);
+                       }
+               }
+       }
+
+       rc = libusb_set_configuration(devh, /*configuration=*/1);
+       if (rc < 0) {
+               fprintf(stderr, "Error setting configuration 1: %s\n", libusb_error_name(rc));
+               exit(1);
+       }
+
+       rc = libusb_claim_interface(devh, 0);
+       if (rc < 0) {
+               fprintf(stderr, "Error claiming interface 0: %s\n", libusb_error_name(rc));
+               exit(1);
+       }
+
+       // Alternate setting 1 is output, alternate setting 2 is input.
+       // Card is reset when switching alternates, so the driver uses
+       // this “double switch” when it wants to reset.
+       rc = libusb_set_interface_alt_setting(devh, /*interface=*/0, /*alternate_setting=*/1);
+       if (rc < 0) {
+               fprintf(stderr, "Error setting alternate 1: %s\n", libusb_error_name(rc));
+               exit(1);
+       }
+       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));
+               exit(1);
+       }
+#if 0
+       rc = libusb_set_interface_alt_setting(devh, /*interface=*/0, /*alternate_setting=*/1);
+       if (rc < 0) {
+               fprintf(stderr, "Error setting alternate 1: %s\n", libusb_error_name(rc));
+               exit(1);
+       }
+#endif
+
+#if 0
+       rc = libusb_claim_interface(devh, 3);
+       if (rc < 0) {
+               fprintf(stderr, "Error claiming interface 3: %s\n", libusb_error_name(rc));
+               exit(1);
+       }
+#endif
+
+       // theories:
+       //   44 is some kind of timer register (first 16 bits count upwards)
+       //   24 is some sort of watchdog?
+       //      you can seemingly set it to 0x73c60001 and that bit will eventually disappear
+       //      (or will go to 0x73c60010?), also seen 0x73c60100
+       //   12 also changes all the time, unclear why  
+       //   16 seems to be autodetected mode somehow
+       //      --    this is e00115e0 after reset?
+       //                    ed0115e0 after mode change [to output?]
+       //                    2d0015e0 after more mode change [to input]
+       //                    ed0115e0 after more mode change
+       //                    2d0015e0 after more mode change
+       //
+       //                    390115e0 seems to indicate we have signal
+       //         changes to 200115e0 when resolution changes/we lose signal, driver resets after a while
+       //
+       //                    200015e0 on startup
+       //         changes to 250115e0 when we sync to the signal
+       //
+       //    so only first 16 bits count, and 0x0100 is a mask for ok/stable signal?
+       //
+       //    28 and 32 seems to be analog audio input levels (one byte for each of the eight channels).
+       //    however, if setting 32 with HDMI embedded audio, it is immediately overwritten back (to 0xe137002a).
+       //
+       //    4, 8, 20 are unclear. seem to be some sort of bitmask, but we can set them to 0 with no apparent effect.
+       //    perhaps some of them are related to analog output?
+       //
+       //    36 can be set to 0 with no apparent effect (all of this tested on both video and audio),
+       //    but the driver sets it to 0x8036802a at some point.
+       //
+       // register 16:
+       // first byte is 0x39 for a stable 576p60 signal, 0x2d for a stable 720p60 signal, 0x20 for no signal
+       //
+       // theories:
+       //   0x01 - stable signal
+       //   0x04 - deep color
+       //   0x08 - unknown (audio??)
+       //   0x20 - 720p??
+       //   0x30 - 576p??
+
+       struct ctrl {
+               int endpoint;
+               int request;
+               int index;
+               uint32_t data;
+       };
+       static const ctrl ctrls[] = {
+               { LIBUSB_ENDPOINT_IN,  214, 16, 0 },
+               { LIBUSB_ENDPOINT_IN,  214,  0, 0 },
+               { LIBUSB_ENDPOINT_IN,  214,  0, 0 },
+               { LIBUSB_ENDPOINT_IN,  214,  4, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 16, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 20, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 24, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 28, 0 },
+               { LIBUSB_ENDPOINT_IN,  215, 32, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 36, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 40, 0 },
+               { LIBUSB_ENDPOINT_IN,  216, 44, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 48, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 52, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 40, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 40, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 40, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 24, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 40, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 40, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 40, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 24, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 40, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 40, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },  // packet 354
+               { LIBUSB_ENDPOINT_IN,  214, 24, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 12, 0 },
+               { LIBUSB_ENDPOINT_IN,  214, 40, 0 },
+               // more...
+               //{ LIBUSB_ENDPOINT_OUT, 215,  0, 0x80000100 },
+               //{ LIBUSB_ENDPOINT_OUT, 215,  0, 0x09000000 },  // wow, some kind of mode
+
+               // seems to capture on HDMI, clearing the 0x20000000 bit seems to activate 10-bit
+               // capture (v210).
+               // clearing the 0x08000000 bit seems to change the capture format (other source?)
+               // 0x10000000 = analog audio instead of embedded audio, it seems
+               // 0x3a000000 = component video? (analog audio)
+               // 0x3c000000 = composite video? (analog audio)
+               // 0x3e000000 = s-video? (analog audio)
+               { LIBUSB_ENDPOINT_OUT, 215,  0, 0x29000000 },
+               //{ LIBUSB_ENDPOINT_OUT, 215,  0, 0x09000000 },
+
+               //{ LIBUSB_ENDPOINT_OUT, 215, 28, 0xffffffff },
+               //{ LIBUSB_ENDPOINT_OUT, 215, 32, 0xffffffff },
+               //{ LIBUSB_ENDPOINT_OUT, 215, 28, 0x40404040 },
+               //{ LIBUSB_ENDPOINT_OUT, 215, 32, 0x40404040 },
+               //{ LIBUSB_ENDPOINT_OUT, 215, 36, 0x8036802a },
+               { LIBUSB_ENDPOINT_OUT, 215, 24, 0x73c60001 },  // latch for frame start?
+               //{ LIBUSB_ENDPOINT_OUT, 215, 24, 0x13370001 },  // latch for frame start?
+               { LIBUSB_ENDPOINT_IN,  214, 24, 0 },  // 
+               //{ LIBUSB_ENDPOINT_OUT, 215,  4, 0x00000000 },  // appears to have no e fect
+               //{ LIBUSB_ENDPOINT_OUT, 215,  8, 0x00000000 },  // appears to have no effect
+               //{ LIBUSB_ENDPOINT_OUT, 215, 20, 0x00000000 },  // appears to have no effect
+               //{ LIBUSB_ENDPOINT_OUT, 215, 28, 0x00000000 },  // appears to have no effect
+               //{ LIBUSB_ENDPOINT_OUT, 215, 32, 0x00000000 },  // appears to have no effect
+               //{ LIBUSB_ENDPOINT_OUT, 215, 36, 0x00000000 },  // appears to have no effect
+#if 0
+               { LIBUSB_ENDPOINT_OUT, 215,  0 },
+               { LIBUSB_ENDPOINT_OUT, 215,  0 },
+               { LIBUSB_ENDPOINT_OUT, 215, 28 },
+               { LIBUSB_ENDPOINT_OUT, 215, 32 },
+               { LIBUSB_ENDPOINT_OUT, 215, 36 },
+               { LIBUSB_ENDPOINT_OUT, 215, 24 },
+               { LIBUSB_ENDPOINT_OUT, 215, 24 },
+               { LIBUSB_ENDPOINT_OUT, 215, 24 },
+               { LIBUSB_ENDPOINT_OUT, 215, 24 },
+               { LIBUSB_ENDPOINT_OUT, 215, 24 },
+               { LIBUSB_ENDPOINT_OUT, 215, 24 },
+               { LIBUSB_ENDPOINT_OUT, 215, 24 },
+               { LIBUSB_ENDPOINT_OUT, 215, 24 },
+               { LIBUSB_ENDPOINT_OUT, 215, 24 },
+               { LIBUSB_ENDPOINT_OUT, 215, 24 },
+               { LIBUSB_ENDPOINT_OUT, 215,  0 },
+               { LIBUSB_ENDPOINT_OUT, 215, 24 },
+               { LIBUSB_ENDPOINT_OUT, 215, 24 },
+               { LIBUSB_ENDPOINT_OUT, 215, 24 },
+               { LIBUSB_ENDPOINT_OUT, 215, 24 },
+               { LIBUSB_ENDPOINT_OUT, 215, 24 },
+               { LIBUSB_ENDPOINT_OUT, 215, 24 },
+#endif
+       };
+
+       for (int req = 0; req < sizeof(ctrls) / sizeof(ctrls[0]); ++req) {
+               uint32_t flipped = htonl(ctrls[req].data);
+               static uint8_t value[4];
+               memcpy(value, &flipped, sizeof(flipped));
+               int size = sizeof(value);
+               //if (ctrls[req].request == 215) size = 0;
+               rc = libusb_control_transfer(devh, LIBUSB_REQUEST_TYPE_VENDOR | ctrls[req].endpoint,
+                       /*request=*/ctrls[req].request, /*value=*/0, /*index=*/ctrls[req].index, value, size, /*timeout=*/0);
+               if (rc < 0) {
+                       fprintf(stderr, "Error on control %d: %s\n", ctrls[req].index, libusb_error_name(rc));
+                       exit(1);
+               }
+               
+               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");
+       }
+
+#if 0
+       // DEBUG
+       for ( ;; ) {
+               static int my_index = 0;
+               static uint8_t value[4];
+               int size = sizeof(value);
+               rc = libusb_control_transfer(devh, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN,
+                       /*request=*/214, /*value=*/0, /*index=*/my_index, value, size, /*timeout=*/0);
+               if (rc < 0) {
+                       fprintf(stderr, "Error on control\n");
+                       exit(1);
+               }
+               printf("rc=%d index=%d: 0x", rc, my_index);
+               for (int i = 0; i < rc; ++i) {
+                       printf("%02x", value[i]);
+               }
+               printf("\n");
+       }
+#endif
+
+#if 0
+       // set up an asynchronous transfer of the timer register
+       static uint8_t cmdbuf[LIBUSB_CONTROL_SETUP_SIZE + 4];
+       static int completed = 0;
+
+       xfr = libusb_alloc_transfer(0);
+       libusb_fill_control_setup(cmdbuf,
+           LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN, /*request=*/214, /*value=*/0,
+               /*index=*/44, /*length=*/4);
+       libusb_fill_control_transfer(xfr, devh, cmdbuf, cb_xfr, &completed, 0);
+       libusb_submit_transfer(xfr);
+
+       // set up an asynchronous transfer of register 24
+       static uint8_t cmdbuf2[LIBUSB_CONTROL_SETUP_SIZE + 4];
+       static int completed2 = 0;
+
+       xfr = libusb_alloc_transfer(0);
+       libusb_fill_control_setup(cmdbuf2,
+           LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN, /*request=*/214, /*value=*/0,
+               /*index=*/24, /*length=*/4);
+       libusb_fill_control_transfer(xfr, devh, cmdbuf2, cb_xfr, &completed2, 0);
+       libusb_submit_transfer(xfr);
+#endif
+
+       // set up an asynchronous transfer of the register dump
+       static uint8_t cmdbuf3[LIBUSB_CONTROL_SETUP_SIZE + 4];
+       static int completed3 = 0;
+
+       xfr = libusb_alloc_transfer(0);
+       libusb_fill_control_setup(cmdbuf3,
+           LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN, /*request=*/214, /*value=*/0,
+               /*index=*/current_register, /*length=*/4);
+       libusb_fill_control_transfer(xfr, devh, cmdbuf3, cb_xfr, &completed3, 0);
+       //libusb_submit_transfer(xfr);
+
+       audiofp = fopen("audio.raw", "wb");
+
+       // 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;
+               for (int i = 0; i < num_transfers; ++i) {
+                       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 << 20) / size;  // 2 MB.
+                               printf("Picking %d packets of 0x%x bytes each\n", num_iso_pack, size);
+                       } else {
+                               size = 0xc0;
+                               num_iso_pack = 80;
+                       }
+                       int num_bytes = num_iso_pack * size;
+                       uint8_t *buf = new uint8_t[num_bytes];
+
+                       xfr = libusb_alloc_transfer(num_iso_pack);
+                       if (!xfr) {
+                               fprintf(stderr, "oom\n");
+                               exit(1);
+                       }
+
+                       int ep = LIBUSB_ENDPOINT_IN | e;
+                       libusb_fill_iso_transfer(xfr, devh, ep, buf, num_bytes,
+                               num_iso_pack, cb_xfr, nullptr, 0);
+                       libusb_set_iso_packet_lengths(xfr, size);
+                       iso_xfrs.push_back(xfr);
+               }
+       }
+
+       {
+               int i = 0;
+               for (libusb_transfer *xfr : iso_xfrs) {
+                       rc = libusb_submit_transfer(xfr);
+                       ++i;
+                       if (rc < 0) {
+                               //printf("num_bytes=%d\n", num_bytes);
+                               fprintf(stderr, "Error submitting iso to endpoint 0x%02x, number %d: %s\n",
+                                       xfr->endpoint, i, libusb_error_name(rc));
+                               exit(1);
+                       }
+               }
+       }
+
+       thread(usb_thread).detach();
+
+
+#if 0
+       libusb_release_interface(devh, 0);
+out:
+       if (devh)
+               libusb_close(devh);
+       libusb_exit(nullptr);
+       return rc;
+#endif
+}
diff --git a/bmusb.h b/bmusb.h
new file mode 100644 (file)
index 0000000..7cbbf14
--- /dev/null
+++ b/bmusb.h
@@ -0,0 +1,58 @@
+#ifndef _BMUSB_H
+#define _BMUSB_H
+
+#include <stdint.h>
+#include <functional>
+
+// An interface for frame allocators; if you do not specify one
+// (using set_video_frame_allocator), a default one that pre-allocates
+// a freelist of eight frames using new[] will be used. Specifying
+// your own can be useful if you have special demands for where you want the
+// frame to end up and don't want to spend the extra copy to get it there, for
+// instance GPU memory.
+class FrameAllocator {
+ public:
+       struct Frame {
+               uint8_t *data = nullptr;
+               size_t len = 0;  // Number of bytes we actually have.
+               size_t size = 0;  // Number of bytes we have room for.
+               void *userdata = nullptr;
+               FrameAllocator *owner = nullptr;
+       };
+
+       virtual ~FrameAllocator();
+
+       // Request a video frame. Note that this is called from the
+       // USB thread, which runs with realtime priority and is
+       // very sensitive to delays. Thus, you should not do anything
+       // here that might sleep, including calling malloc().
+       // (Taking a mutex is borderline.)
+       //
+       // The Frame object will be given to the frame callback,
+       // which is responsible for releasing the video frame back
+       // once it is usable for new frames (ie., it will no longer
+       // be read from). You can use the "userdata" pointer for
+       // whatever you want to identify this frame if you need to.
+       //
+       // Returning a Frame with data==nullptr is allowed;
+       // if so, the frame in progress will be dropped.
+       virtual Frame alloc_frame() = 0;
+
+       virtual void release_frame(Frame frame) = 0;
+};
+
+typedef std::function<void(uint16_t timecode,
+                           FrameAllocator::Frame video_frame, size_t video_offset, uint16_t video_format,
+                           FrameAllocator::Frame audio_frame, size_t audio_offset, uint16_t audio_format)>
+       frame_callback_t;
+
+void set_video_frame_allocator(FrameAllocator *allocator);  // Does not take ownership.
+FrameAllocator *get_video_frame_allocator();
+
+void set_audio_frame_allocator(FrameAllocator *allocator);  // Does not take ownership.
+FrameAllocator *get_audio_frame_allocator();
+
+void set_frame_callback(frame_callback_t callback);
+void start_bm_capture();
+
+#endif
diff --git a/format.cpp b/format.cpp
new file mode 100644 (file)
index 0000000..7faa8eb
--- /dev/null
@@ -0,0 +1,36 @@
+#include <stdio.h>
+
+struct mode {
+       char name[32];
+       int value;
+};
+
+static const mode foo[] = {
+       "NTSC        ", 0xe901,
+       "NTSC        ", 0xe9c1,
+       "NTSC        ", 0xe801,
+       "NTSC 23.98  ", 0xe901,
+       "PAL         ", 0xe909,
+       "1080p 23.98 ", 0xe8ad,
+       "1080p 24    ", 0xe88b,
+       "1080p 25    ", 0xe86b,
+       "1080p 29.97 ", 0xe9ed,
+       "1080p 30    ", 0xe9cb,
+       "1080i 50    ", 0xe84b,
+       "1080i 59.94 ", 0xe82d,
+       "1080i 60    ", 0xe80b,
+       "720p 50     ", 0xe94b,
+       "720p 50     ", 0xe943,
+       "720p 59.94  ", 0xe92d,
+       "720p 59.94  ", 0xe925,
+       "720p 60     ", 0xe90b,
+};
+
+int main(void)
+{
+       for (int i = 0; i < sizeof(foo) / sizeof(foo[0]); ++i) {
+               int value = foo[i].value;
+               printf("%-16s: mode=0x%04x, deep color=%d, dropframe=%d, hd_and_not_dropframe=%d, remainder=0x%04x\n",
+                       foo[i].name, value, !!(value & 0x8), !!(value & 0x4), !!(value & 0x2), value & ~(0xe800 | 0x8 | 0x4 | 0x2));
+       }
+}
diff --git a/formats.txt b/formats.txt
new file mode 100644 (file)
index 0000000..1f6bbbc
--- /dev/null
@@ -0,0 +1,29 @@
+0x0800 - no signal
+0xe819 - 576p60 (520x576)
+0xe82d - 1080i60 (deep color?)
+0xe925 - 720p60
+0xe92d - 720p60 deep color
+0xe943 - 720p50 from the scaler (deep color?)
+0xe90e - 720p59.94?? from the scaler (deep color?)
+
+
+NTSC        - 0xe901 (also seen 0xe9c1, 0xe801)
+NTSC 23.98  - 0xe901
+PAL         - 0xe909
+1080p 23.98 - 0xe8ad
+1080p 24    - 0xe88b
+1080p 25    - 0xe86b
+1080p 29.97 - 0xe9ed
+1080p 30    - 0xe9cb
+1080i 50    - 0xe84b
+1080i 59.94 - 0xe82d
+1080i 60    - 0xe80b
+720p 50     - 0xe94b (also seen 0xe943)
+720p 59.94  - 0xe92d (also seen 0xe925)
+720p 60     - 0xe90b
+
+theories:
+
+video       - 0xe800
+deep color  - 0x0008
+dropframe   - 0x0004
diff --git a/main.cpp b/main.cpp
new file mode 100644 (file)
index 0000000..a605b1a
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,43 @@
+#include <stdio.h>
+#include <unistd.h>
+#include "bmusb.h"
+
+using namespace std;
+
+void check_frame_stability(uint16_t timecode,
+                           FrameAllocator::Frame video_frame, size_t video_offset, uint16_t video_format,
+                           FrameAllocator::Frame audio_frame, size_t audio_offset, uint16_t audio_format)
+{
+       //printf("0x%04x: %d video bytes (format 0x%04x), %d audio bytes (format 0x%04x)\n",
+       //      timecode, video_end - video_start, video_format, audio_end - audio_start, audio_format);
+
+       static uint16_t last_timecode = 0;
+       static size_t last_video_bytes = 0;
+       static size_t last_audio_bytes = 0;
+
+       if (!(last_timecode == 0 && last_video_bytes == 0 && last_audio_bytes == 0)) {
+               if (timecode != (uint16_t)(last_timecode + 1)) {
+                       printf("0x%04x: Dropped %d frames\n", timecode, timecode - last_timecode - 1);
+               } else if (last_video_bytes != video_frame.len - video_offset) {
+                       printf("0x%04x: Video frame size changed (old=%d, cur=%d)\n", timecode,
+                               last_video_bytes, video_frame.len - video_offset);
+               } else if (last_audio_bytes != audio_frame.len - audio_offset) {
+                       printf("0x%04x: Audio block size changed (old=%d, cur=%d)\n", timecode,
+                               last_audio_bytes, audio_frame.len - audio_offset);
+               }
+       }
+       last_timecode = timecode;
+       last_video_bytes = video_frame.len - video_offset;
+       last_audio_bytes = audio_frame.len - audio_offset;
+
+       get_video_frame_allocator()->release_frame(video_frame);
+       get_audio_frame_allocator()->release_frame(audio_frame);
+}
+
+int main(int argc, char **argv)
+{
+       set_frame_callback(check_frame_stability);
+       start_bm_capture();
+       sleep(1000000);
+}
+