pax_global_header00006660000000000000000000000064131130502120014476gustar00rootroot0000000000000052 comment=32043c95d3b9f8cb97d6d28b9996fa1bec2ce11b bmusb-0.7.0/000077500000000000000000000000001311305021200126125ustar00rootroot00000000000000bmusb-0.7.0/.gitignore000066400000000000000000000000151311305021200145760ustar00rootroot00000000000000main *.o *.d bmusb-0.7.0/COPYING000066400000000000000000000432541311305021200136550ustar00rootroot00000000000000 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. Copyright (C) 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. , 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. bmusb-0.7.0/Makefile000066400000000000000000000023741311305021200142600ustar00rootroot00000000000000CXXFLAGS := -std=gnu++14 -O2 -Wall -I. -g $(shell pkg-config libusb-1.0 --cflags) -pthread LDFLAGS := $(shell pkg-config libusb-1.0 --libs) -pthread AR := ar LN := ln RANLIB := ranlib INSTALL := install PREFIX := /usr/local LIB := libbmusb.a SODEV := libbmusb.so SONAME := libbmusb.so.4 SOLIB := libbmusb.so.4.0.0 all: $(LIB) $(SOLIB) main %.pic.o : %.cpp $(CXX) $(CPPFLAGS) $(CXXFLAGS) -fPIC -o $@ -c $^ main: bmusb.o main.o $(CXX) -o main $^ $(LDFLAGS) # Static library. $(LIB): bmusb.o fake_capture.o $(AR) rc $@ $^ $(RANLIB) $@ # Shared library. $(SOLIB): bmusb.pic.o fake_capture.pic.o $(CXX) -shared -Wl,-soname,$(SONAME) -o $@ $^ $(LDFLAGS) clean: $(RM) bmusb.o main.o fake_capture.o bmusb.pic.o fake_capture.pic.o $(LIB) $(SOLIB) main install: all $(INSTALL) -m 755 -o root -g root -d \ $(DESTDIR)$(PREFIX)/lib \ $(DESTDIR)$(PREFIX)/lib/pkgconfig \ $(DESTDIR)$(PREFIX)/include/bmusb $(INSTALL) -m 755 -o root -g root $(LIB) $(SOLIB) $(DESTDIR)$(PREFIX)/lib $(LN) -sf $(SOLIB) $(DESTDIR)$(PREFIX)/lib/$(SONAME) $(LN) -sf $(SOLIB) $(DESTDIR)$(PREFIX)/lib/$(SODEV) $(INSTALL) -m 755 -o root -g root bmusb/bmusb.h bmusb/fake_capture.h $(DESTDIR)$(PREFIX)/include/bmusb $(INSTALL) -m 644 -o root -g root bmusb.pc $(DESTDIR)$(PREFIX)/lib/pkgconfig bmusb-0.7.0/README000066400000000000000000000042101311305021200134670ustar00rootroot00000000000000bmusb is a free driver for BlackMagic's Intensity Shuttle and UltraStudio SDI USB3 cards, which have no official Linux driver. (The two seem to speak exactly the same protocol.) It runs in userspace through usbfs, which may mean it could also probably run on FreeBSD, but it's untested. Current tested features (note, some of these are not exposed in the driver except by changing the source code): * HDMI and SDI capture on a variety of modes including 720p60, 1080p30 and 1080i60. 1080p60 is unfortunately not supported, despite earlier (now retracted) promises by Blackmagic that it would come in a future firmware revision (this is unlike the Thunderbolt versions, where some older firmware revisions _do_ support it). * 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 BlackMagic cards follow a protocol whose exact format is still unknown, and the driver is still in beta stage. (The API/ABI is nearing stability, but is still not really locked.) It seems to want about 10–15% of one CPU core; a significant chunk of this is copying data from the kernel over to userspace, which can be skipped by means of zerocopy USB if you have a very recent libusb (>= 1.0.21) and a recent kernel (>= 4.6.0). There's a decode step which also takes some time and memory bandwidth, but it supports custom memory allocators, so that once the USB packets are available to userspace, you can decode directly into e.g. pinned GPU memory. 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 been tested with various firmware versions; they seem to behave mostly the same. There is currently no tool to upgrade or downgrade the firmware on the card. bmusb is Copyright 2015 Steinar H. Gunderson and licensed under the GNU General Public License, version 2, or at your option, any later version. See the COPYING file. bmusb-0.7.0/bmusb.cpp000066400000000000000000001446141311305021200144400ustar00rootroot00000000000000// Intensity Shuttle USB3 capture driver, v0.7.0 // Can download 8-bit and 10-bit UYVY/v210-ish 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. #if (defined(__i386__) || defined(__x86_64__)) && defined(__GNUC__) #define HAS_MULTIVERSIONING 1 #endif #include #include #include #include #include #include #include #include #include #include #include #if HAS_MULTIVERSIONING #include #endif #include "bmusb/bmusb.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace std::chrono; using namespace std::placeholders; #define USB_VENDOR_BLACKMAGIC 0x1edb #define MIN_WIDTH 640 #define HEADER_SIZE 44 //#define HEADER_SIZE 0 #define AUDIO_HEADER_SIZE 4 #define FRAME_SIZE (8 << 20) // 8 MB. #define USB_VIDEO_TRANSFER_SIZE (128 << 10) // 128 kB. namespace bmusb { card_connected_callback_t BMUSBCapture::card_connected_callback = nullptr; bool BMUSBCapture::hotplug_existing_devices = false; namespace { FILE *audiofp; thread usb_thread; atomic should_quit; int v210_stride(int width) { return (width + 5) / 6 * 4 * sizeof(uint32_t); } int find_xfer_size_for_width(PixelFormat pixel_format, 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. // Note that for 10-bit input, you'll need to increase size accordingly. int stride; if (pixel_format == PixelFormat_10BitYCbCr) { stride = v210_stride(width); } else { stride = width * sizeof(uint16_t); } int size = stride * 6; if (size % 1024 != 0) { size &= ~1023; size += 1024; } return size; } void change_xfer_size_for_width(PixelFormat pixel_format, int width, libusb_transfer *xfr) { assert(width >= MIN_WIDTH); size_t size = find_xfer_size_for_width(pixel_format, 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); } } 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; }; // Get details for the given video format; returns false if detection was incomplete. bool decode_video_format(uint16_t video_format, VideoFormat *decoded_video_format) { decoded_video_format->id = video_format; decoded_video_format->interlaced = false; // TODO: Add these for all formats as we find them. decoded_video_format->extra_lines_top = decoded_video_format->extra_lines_bottom = decoded_video_format->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. decoded_video_format->width = 720; decoded_video_format->height = 525; decoded_video_format->stride = 720 * 2; decoded_video_format->extra_lines_top = 0; decoded_video_format->extra_lines_bottom = 0; decoded_video_format->frame_rate_nom = 3013; decoded_video_format->frame_rate_den = 100; decoded_video_format->has_signal = false; return true; } if ((video_format & 0xe000) != 0xe000) { printf("Video format 0x%04x does not appear to be a video format. Assuming 60 Hz.\n", video_format); decoded_video_format->width = 0; decoded_video_format->height = 0; decoded_video_format->stride = 0; decoded_video_format->extra_lines_top = 0; decoded_video_format->extra_lines_bottom = 0; decoded_video_format->frame_rate_nom = 60; decoded_video_format->frame_rate_den = 1; decoded_video_format->has_signal = false; return false; } decoded_video_format->has_signal = true; // NTSC (480i59.94, I suppose). A special case, see below. if ((video_format & ~0x0800) == 0xe101 || (video_format & ~0x0800) == 0xe1c1 || (video_format & ~0x0800) == 0xe001) { decoded_video_format->width = 720; decoded_video_format->height = 480; if (video_format & 0x0800) { decoded_video_format->stride = 720 * 2; } else { decoded_video_format->stride = v210_stride(720); } decoded_video_format->extra_lines_top = 17; decoded_video_format->extra_lines_bottom = 28; decoded_video_format->frame_rate_nom = 30000; decoded_video_format->frame_rate_den = 1001; decoded_video_format->second_field_start = 280; decoded_video_format->interlaced = true; return true; } // PAL (576i50, I suppose). A special case, see below. if ((video_format & ~0x0800) == 0xe109 || (video_format & ~0x0800) == 0xe1c9 || (video_format & ~0x0800) == 0xe009 || (video_format & ~0x0800) == 0xe3e9 || (video_format & ~0x0800) == 0xe3e1) { decoded_video_format->width = 720; decoded_video_format->height = 576; if (video_format & 0x0800) { decoded_video_format->stride = 720 * 2; } else { decoded_video_format->stride = v210_stride(720); } decoded_video_format->extra_lines_top = 22; decoded_video_format->extra_lines_bottom = 27; decoded_video_format->frame_rate_nom = 25; decoded_video_format->frame_rate_den = 1; decoded_video_format->second_field_start = 335; decoded_video_format->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. 0x0800 appears to be 8-bit input // (as opposed to 10-bit). // // 0x4 is a flag I've only seen from the D4. I don't know what it is. uint16_t normalized_video_format = video_format & ~0xe80c; constexpr VideoFormatEntry entries[] = { { 0x01f1, 720, 480, 0, 40, 5, 60000, 1001, false }, // 480p59.94 (believed). { 0x0131, 720, 576, 0, 44, 5, 50, 1, false }, // 576p50. { 0x0151, 720, 576, 0, 44, 5, 50, 1, false }, // 576p50. { 0x0011, 720, 576, 0, 44, 5, 50, 1, false }, // 576p50 (5:4). { 0x0143, 1280, 720, 0, 25, 5, 50, 1, false }, // 720p50. { 0x0103, 1280, 720, 0, 25, 5, 60, 1, false }, // 720p60. { 0x0125, 1280, 720, 0, 25, 5, 60, 1, false }, // 720p60. { 0x0121, 1280, 720, 0, 25, 5, 60000, 1001, false }, // 720p59.94. { 0x01c3, 1920, 1080, 0, 41, 4, 30, 1, false }, // 1080p30. { 0x0003, 1920, 1080, 583, 20, 25, 30, 1, true }, // 1080i60. { 0x01e1, 1920, 1080, 0, 41, 4, 30000, 1001, false }, // 1080p29.97. { 0x0021, 1920, 1080, 583, 20, 25, 30000, 1001, true }, // 1080i59.94. { 0x0063, 1920, 1080, 0, 41, 4, 25, 1, false }, // 1080p25. { 0x0043, 1920, 1080, 583, 20, 25, 25, 1, true }, // 1080i50. { 0x0083, 1920, 1080, 0, 41, 4, 24, 1, false }, // 1080p24. { 0x00a1, 1920, 1080, 0, 41, 4, 24000, 1001, false }, // 1080p23.98. }; for (const VideoFormatEntry &entry : entries) { if (normalized_video_format == entry.normalized_video_format) { decoded_video_format->width = entry.width; decoded_video_format->height = entry.height; if (video_format & 0x0800) { decoded_video_format->stride = entry.width * 2; } else { decoded_video_format->stride = v210_stride(entry.width); } decoded_video_format->second_field_start = entry.second_field_start; decoded_video_format->extra_lines_top = entry.extra_lines_top; decoded_video_format->extra_lines_bottom = entry.extra_lines_bottom; decoded_video_format->frame_rate_nom = entry.frame_rate_nom; decoded_video_format->frame_rate_den = entry.frame_rate_den; decoded_video_format->interlaced = entry.interlaced; return true; } } printf("Unknown video format 0x%04x (normalized 0x%04x). Assuming 720p60.\n", video_format, normalized_video_format); decoded_video_format->width = 1280; decoded_video_format->height = 720; decoded_video_format->stride = 1280 * 2; decoded_video_format->frame_rate_nom = 60; decoded_video_format->frame_rate_den = 1; return false; } // There are seemingly no direct indicators of sample rate; you just get // one frame's worth and have to guess from that. int guess_sample_rate(const VideoFormat &video_format, size_t len, int default_rate) { size_t num_samples = len / 3 / 8; size_t num_samples_per_second = num_samples * video_format.frame_rate_nom / video_format.frame_rate_den; // See if we match or are very close to any of the mandatory HDMI sample rates. const int candidate_sample_rates[] = { 32000, 44100, 48000 }; for (int rate : candidate_sample_rates) { if (abs(int(num_samples_per_second) - rate) < 50) { return rate; } } fprintf(stderr, "%ld samples at %d/%d fps (%ld Hz) matches no known sample rate, keeping capture at %d Hz\n", num_samples, video_format.frame_rate_nom, video_format.frame_rate_den, num_samples_per_second, default_rate); return default_rate; } } // namespace FrameAllocator::~FrameAllocator() {} MallocFrameAllocator::MallocFrameAllocator(size_t frame_size, size_t num_queued_frames) : frame_size(frame_size) { for (size_t i = 0; i < num_queued_frames; ++i) { freelist.push(unique_ptr(new uint8_t[frame_size])); } } FrameAllocator::Frame MallocFrameAllocator::alloc_frame() { Frame vf; vf.owner = this; unique_lock 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) { if (frame.overflow > 0) { printf("%d bytes overflow after last (malloc) frame\n", int(frame.overflow)); } unique_lock lock(freelist_mutex); freelist.push(unique_ptr(frame.data)); } 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 BMUSBCapture::queue_frame(uint16_t format, uint16_t timecode, FrameAllocator::Frame frame, deque *q) { unique_lock 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); frame.owner->release_frame(frame); return; } QueuedFrame qf; qf.format = format; qf.timecode = timecode; qf.frame = frame; 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 BMUSBCapture::dequeue_thread_func() { char thread_name[16]; snprintf(thread_name, sizeof(thread_name), "bmusb_dequeue_%d", card_index); pthread_setname_np(pthread_self(), thread_name); if (has_dequeue_callbacks) { dequeue_init_callback(); } size_t last_sample_rate = 48000; while (!dequeue_thread_should_quit) { 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; AudioFormat audio_format; audio_format.bits_per_sample = 24; audio_format.num_channels = 8; audio_format.sample_rate = last_sample_rate; if (uint16_less_than_with_wraparound(video_timecode, audio_timecode)) { printf("Video block 0x%04x without corresponding audio block, dropping.\n", video_timecode); 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); QueuedFrame audio_frame = pending_audio_frames.front(); pending_audio_frames.pop_front(); lock.unlock(); audio_format.id = audio_frame.format; // Use the video format of the pending frame. QueuedFrame video_frame = pending_video_frames.front(); VideoFormat video_format; decode_video_format(video_frame.format, &video_format); frame_callback(audio_timecode, FrameAllocator::Frame(), 0, video_format, audio_frame.frame, AUDIO_HEADER_SIZE, audio_format); } 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 VideoFormat video_format; audio_format.id = audio_frame.format; if (decode_video_format(video_frame.format, &video_format)) { if (audio_frame.frame.len != 0) { audio_format.sample_rate = guess_sample_rate(video_format, audio_frame.frame.len, last_sample_rate); last_sample_rate = audio_format.sample_rate; } frame_callback(video_timecode, video_frame.frame, HEADER_SIZE, video_format, audio_frame.frame, AUDIO_HEADER_SIZE, audio_format); } else { video_frame_allocator->release_frame(video_frame.frame); audio_format.sample_rate = last_sample_rate; frame_callback(video_timecode, FrameAllocator::Frame(), 0, video_format, audio_frame.frame, AUDIO_HEADER_SIZE, audio_format); } } } if (has_dequeue_callbacks) { dequeue_cleanup_callback(); } } void BMUSBCapture::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) { current_video_frame.received_timestamp = steady_clock::now(); // 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); // 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. VideoFormat video_format; if (decode_video_format(format, &video_format)) { assumed_frame_width = video_format.width; } } //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 BMUSBCapture::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) { current_audio_frame.received_timestamp = steady_clock::now(); //dump_audio_block(); queue_frame(format, timecode, current_audio_frame, &pending_audio_frames); } //printf("Found audio block start, format 0x%04x timecode 0x%04x\n", // format, timecode); current_audio_frame = audio_frame_allocator->alloc_frame(); } #if 0 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 (unsigned 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(" "); } } #endif void memcpy_interleaved(uint8_t *dest1, uint8_t *dest2, const uint8_t *src, size_t n) { assert(n % 2 == 0); uint8_t *dptr1 = dest1; uint8_t *dptr2 = dest2; for (size_t i = 0; i < n; i += 2) { *dptr1++ = *src++; *dptr2++ = *src++; } } void add_to_frame(FrameAllocator::Frame *current_frame, const char *frame_type_name, const uint8_t *start, const uint8_t *end) { if (current_frame->data == nullptr || current_frame->len > current_frame->size || start == end) { return; } int bytes = end - start; if (current_frame->len + bytes > current_frame->size) { 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) { uint8_t *data = current_frame->data + current_frame->len / 2; uint8_t *data2 = current_frame->data2 + current_frame->len / 2; if (current_frame->len % 2 == 1) { ++data; swap(data, data2); } if (bytes % 2 == 1) { *data++ = *start++; swap(data, data2); ++current_frame->len; --bytes; } memcpy_interleaved(data, data2, start, bytes); current_frame->len += bytes; } else { memcpy(current_frame->data + current_frame->len, start, bytes); current_frame->len += bytes; } } } #if 0 void avx2_dump(const char *name, __m256i n) { printf("%-10s:", name); printf(" %02x", _mm256_extract_epi8(n, 0)); printf(" %02x", _mm256_extract_epi8(n, 1)); printf(" %02x", _mm256_extract_epi8(n, 2)); printf(" %02x", _mm256_extract_epi8(n, 3)); printf(" %02x", _mm256_extract_epi8(n, 4)); printf(" %02x", _mm256_extract_epi8(n, 5)); printf(" %02x", _mm256_extract_epi8(n, 6)); printf(" %02x", _mm256_extract_epi8(n, 7)); printf(" "); printf(" %02x", _mm256_extract_epi8(n, 8)); printf(" %02x", _mm256_extract_epi8(n, 9)); printf(" %02x", _mm256_extract_epi8(n, 10)); printf(" %02x", _mm256_extract_epi8(n, 11)); printf(" %02x", _mm256_extract_epi8(n, 12)); printf(" %02x", _mm256_extract_epi8(n, 13)); printf(" %02x", _mm256_extract_epi8(n, 14)); printf(" %02x", _mm256_extract_epi8(n, 15)); printf(" "); printf(" %02x", _mm256_extract_epi8(n, 16)); printf(" %02x", _mm256_extract_epi8(n, 17)); printf(" %02x", _mm256_extract_epi8(n, 18)); printf(" %02x", _mm256_extract_epi8(n, 19)); printf(" %02x", _mm256_extract_epi8(n, 20)); printf(" %02x", _mm256_extract_epi8(n, 21)); printf(" %02x", _mm256_extract_epi8(n, 22)); printf(" %02x", _mm256_extract_epi8(n, 23)); printf(" "); printf(" %02x", _mm256_extract_epi8(n, 24)); printf(" %02x", _mm256_extract_epi8(n, 25)); printf(" %02x", _mm256_extract_epi8(n, 26)); printf(" %02x", _mm256_extract_epi8(n, 27)); printf(" %02x", _mm256_extract_epi8(n, 28)); printf(" %02x", _mm256_extract_epi8(n, 29)); printf(" %02x", _mm256_extract_epi8(n, 30)); printf(" %02x", _mm256_extract_epi8(n, 31)); printf("\n"); } #endif #ifndef HAS_MULTIVERSIONING const uint8_t *add_to_frame_fastpath(FrameAllocator::Frame *current_frame, const uint8_t *start, const uint8_t *limit, const char sync_char) { // No fast path possible unless we have multiversioning. return start; } #else // defined(HAS_MULTIVERSIONING) __attribute__((target("sse4.1"))) const uint8_t *add_to_frame_fastpath_core(FrameAllocator::Frame *current_frame, const uint8_t *aligned_start, const uint8_t *limit, const char sync_char); __attribute__((target("avx2"))) const uint8_t *add_to_frame_fastpath_core(FrameAllocator::Frame *current_frame, const uint8_t *aligned_start, const uint8_t *limit, const char sync_char); // Does a memcpy and memchr in one to reduce processing time. // Note that the benefit is somewhat limited if your L3 cache is small, // as you'll (unfortunately) spend most of the time loading the data // from main memory. // // Complicated cases are left to the slow path; it basically stops copying // up until the first instance of "sync_char" (usually a bit before, actually). // This is fine, since 0x00 bytes shouldn't really show up in normal picture // data, and what we really need this for is the 00 00 ff ff marker in video data. __attribute__((target("default"))) const uint8_t *add_to_frame_fastpath(FrameAllocator::Frame *current_frame, const uint8_t *start, const uint8_t *limit, const char sync_char) { // No fast path possible unless we have SSE 4.1 or higher. return start; } __attribute__((target("sse4.1", "avx2"))) const uint8_t *add_to_frame_fastpath(FrameAllocator::Frame *current_frame, const uint8_t *start, const uint8_t *limit, const char sync_char) { if (current_frame->data == nullptr || current_frame->len > current_frame->size || start == limit) { return start; } size_t orig_bytes = limit - start; if (orig_bytes < 128) { // Don't bother. return start; } // Don't read more bytes than we can write. limit = min(limit, start + (current_frame->size - current_frame->len)); // Align end to 32 bytes. limit = (const uint8_t *)(intptr_t(limit) & ~31); if (start >= limit) { return start; } // Process [0,31] bytes, such that start gets aligned to 32 bytes. const uint8_t *aligned_start = (const uint8_t *)(intptr_t(start + 31) & ~31); if (aligned_start != start) { const uint8_t *sync_start = (const uint8_t *)memchr(start, sync_char, aligned_start - start); if (sync_start == nullptr) { add_to_frame(current_frame, "", start, aligned_start); } else { add_to_frame(current_frame, "", start, sync_start); return sync_start; } } // Make the length a multiple of 64. if (current_frame->interleaved) { if (((limit - aligned_start) % 64) != 0) { limit -= 32; } assert(((limit - aligned_start) % 64) == 0); } return add_to_frame_fastpath_core(current_frame, aligned_start, limit, sync_char); } __attribute__((target("avx2"))) const uint8_t *add_to_frame_fastpath_core(FrameAllocator::Frame *current_frame, const uint8_t *aligned_start, const uint8_t *limit, const char sync_char) { const __m256i needle = _mm256_set1_epi8(sync_char); const __restrict __m256i *in = (const __m256i *)aligned_start; if (current_frame->interleaved) { __restrict __m256i *out1 = (__m256i *)(current_frame->data + (current_frame->len + 1) / 2); __restrict __m256i *out2 = (__m256i *)(current_frame->data2 + current_frame->len / 2); if (current_frame->len % 2 == 1) { swap(out1, out2); } __m256i shuffle_cw = _mm256_set_epi8( 15, 13, 11, 9, 7, 5, 3, 1, 14, 12, 10, 8, 6, 4, 2, 0, 15, 13, 11, 9, 7, 5, 3, 1, 14, 12, 10, 8, 6, 4, 2, 0); while (in < (const __m256i *)limit) { // Note: For brevity, comments show lanes as if they were 2x64-bit (they're actually 2x128). __m256i data1 = _mm256_stream_load_si256(in); // AaBbCcDd EeFfGgHh __m256i data2 = _mm256_stream_load_si256(in + 1); // IiJjKkLl MmNnOoPp __m256i found1 = _mm256_cmpeq_epi8(data1, needle); __m256i found2 = _mm256_cmpeq_epi8(data2, needle); __m256i found = _mm256_or_si256(found1, found2); data1 = _mm256_shuffle_epi8(data1, shuffle_cw); // ABCDabcd EFGHefgh data2 = _mm256_shuffle_epi8(data2, shuffle_cw); // IJKLijkl MNOPmnop data1 = _mm256_permute4x64_epi64(data1, 0b11011000); // ABCDEFGH abcdefgh data2 = _mm256_permute4x64_epi64(data2, 0b11011000); // IJKLMNOP ijklmnop __m256i lo = _mm256_permute2x128_si256(data1, data2, 0b00100000); __m256i hi = _mm256_permute2x128_si256(data1, data2, 0b00110001); _mm256_storeu_si256(out1, lo); // Store as early as possible, even if the data isn't used. _mm256_storeu_si256(out2, hi); if (!_mm256_testz_si256(found, found)) { break; } in += 2; ++out1; ++out2; } current_frame->len += (uint8_t *)in - aligned_start; } else { __m256i *out = (__m256i *)(current_frame->data + current_frame->len); while (in < (const __m256i *)limit) { __m256i data = _mm256_load_si256(in); _mm256_storeu_si256(out, data); // Store as early as possible, even if the data isn't used. __m256i found = _mm256_cmpeq_epi8(data, needle); if (!_mm256_testz_si256(found, found)) { break; } ++in; ++out; } current_frame->len = (uint8_t *)out - current_frame->data; } //printf("managed to fastpath %ld/%ld bytes\n", (const uint8_t *)in - (const uint8_t *)aligned_start, orig_bytes); return (const uint8_t *)in; } __attribute__((target("sse4.1"))) const uint8_t *add_to_frame_fastpath_core(FrameAllocator::Frame *current_frame, const uint8_t *aligned_start, const uint8_t *limit, const char sync_char) { const __m128i needle = _mm_set1_epi8(sync_char); const __m128i *in = (const __m128i *)aligned_start; if (current_frame->interleaved) { __m128i *out1 = (__m128i *)(current_frame->data + (current_frame->len + 1) / 2); __m128i *out2 = (__m128i *)(current_frame->data2 + current_frame->len / 2); if (current_frame->len % 2 == 1) { swap(out1, out2); } __m128i mask_lower_byte = _mm_set1_epi16(0x00ff); while (in < (const __m128i *)limit) { __m128i data1 = _mm_load_si128(in); __m128i data2 = _mm_load_si128(in + 1); __m128i data1_lo = _mm_and_si128(data1, mask_lower_byte); __m128i data2_lo = _mm_and_si128(data2, mask_lower_byte); __m128i data1_hi = _mm_srli_epi16(data1, 8); __m128i data2_hi = _mm_srli_epi16(data2, 8); __m128i lo = _mm_packus_epi16(data1_lo, data2_lo); _mm_storeu_si128(out1, lo); // Store as early as possible, even if the data isn't used. __m128i hi = _mm_packus_epi16(data1_hi, data2_hi); _mm_storeu_si128(out2, hi); __m128i found1 = _mm_cmpeq_epi8(data1, needle); __m128i found2 = _mm_cmpeq_epi8(data2, needle); if (!_mm_testz_si128(found1, found1) || !_mm_testz_si128(found2, found2)) { break; } in += 2; ++out1; ++out2; } current_frame->len += (uint8_t *)in - aligned_start; } else { __m128i *out = (__m128i *)(current_frame->data + current_frame->len); while (in < (const __m128i *)limit) { __m128i data = _mm_load_si128(in); _mm_storeu_si128(out, data); // Store as early as possible, even if the data isn't used. __m128i found = _mm_cmpeq_epi8(data, needle); if (!_mm_testz_si128(found, found)) { break; } ++in; ++out; } current_frame->len = (uint8_t *)out - current_frame->data; } //printf("managed to fastpath %ld/%ld bytes\n", (const uint8_t *)in - (const uint8_t *)aligned_start, orig_bytes); return (const uint8_t *)in; } #endif // defined(HAS_MULTIVERSIONING) void decode_packs(const libusb_transfer *xfr, const char *sync_pattern, int sync_length, FrameAllocator::Frame *current_frame, const char *frame_type_name, function start_callback) { int offset = 0; for (int 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 uint8_t *start = xfr->buffer + offset; const uint8_t *limit = start + pack->actual_length; while (start < limit) { // Usually runs only one iteration. start = add_to_frame_fastpath(current_frame, start, limit, sync_pattern[0]); if (start == limit) break; assert(start < limit); const unsigned char* start_next_frame = (const unsigned char *)memmem(start, limit - start, sync_pattern, sync_length); if (start_next_frame == nullptr) { // add the rest of the buffer add_to_frame(current_frame, frame_type_name, start, limit); break; } else { add_to_frame(current_frame, frame_type_name, start, start_next_frame); start = start_next_frame + sync_length; // skip sync start_callback(start); } } #if 0 dump_pack(xfr, offset, pack); #endif offset += pack->length; } } void BMUSBCapture::cb_xfr(struct libusb_transfer *xfr) { 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); } assert(xfr->user_data != nullptr); BMUSBCapture *usb = static_cast(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)); } 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->current_pixel_format, usb->assumed_frame_width, xfr); } } 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(usb->register_file + usb->current_register, buf, 4); usb->current_register = (usb->current_register + 4) % NUM_BMUSB_REGISTERS; if (usb->current_register == 0) { // read through all of them printf("register dump:"); for (int i = 0; i < NUM_BMUSB_REGISTERS; i += 4) { printf(" 0x%02x%02x%02x%02x", usb->register_file[i], usb->register_file[i + 1], usb->register_file[i + 2], usb->register_file[i + 3]); } printf("\n"); } libusb_fill_control_setup(xfr->buffer, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN, /*request=*/214, /*value=*/0, /*index=*/usb->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 int rc = libusb_submit_transfer(xfr); if (rc < 0) { fprintf(stderr, "error re-submitting URB: %s\n", libusb_error_name(rc)); exit(1); } } 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; memset(¶m, 0, sizeof(param)); param.sched_priority = 1; if (sched_setscheduler(0, SCHED_RR, ¶m) == -1) { printf("couldn't set realtime priority for USB thread: %s\n", strerror(errno)); } pthread_setname_np(pthread_self(), "bmusb_usb_drv"); while (!should_quit) { timeval sec { 1, 0 }; int rc = libusb_handle_events_timeout(nullptr, &sec); if (rc != LIBUSB_SUCCESS) break; } } namespace { struct USBCardDevice { uint16_t product; uint8_t bus, port; 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; } vector find_all_cards() { 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 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 == USB_VENDOR_BLACKMAGIC && desc.idProduct == 0xbd3b) && !(desc.idVendor == USB_VENDOR_BLACKMAGIC && 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; }); return found_cards; } libusb_device_handle *open_card(int card_index, string *description) { vector found_cards = find_all_cards(); for (size_t i = 0; i < found_cards.size(); ++i) { 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 = tmp_description; } } 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; } 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; } } // namespace unsigned BMUSBCapture::num_cards() { int rc = libusb_init(nullptr); if (rc < 0) { fprintf(stderr, "Error initializing libusb: %s\n", libusb_error_name(rc)); exit(1); } vector found_cards = find_all_cards(); unsigned ret = found_cards.size(); for (size_t i = 0; i < found_cards.size(); ++i) { libusb_unref_device(found_cards[i].device); } return ret; } void BMUSBCapture::set_pixel_format(PixelFormat pixel_format) { current_pixel_format = pixel_format; update_capture_mode(); } void BMUSBCapture::configure_card() { if (video_frame_allocator == nullptr) { owned_video_frame_allocator.reset(new MallocFrameAllocator(FRAME_SIZE, NUM_QUEUED_VIDEO_FRAMES)); set_video_frame_allocator(owned_video_frame_allocator.get()); } if (audio_frame_allocator == nullptr) { owned_audio_frame_allocator.reset(new MallocFrameAllocator(65536, NUM_QUEUED_AUDIO_FRAMES)); set_audio_frame_allocator(owned_audio_frame_allocator.get()); } dequeue_thread_should_quit = false; dequeue_thread = thread(&BMUSBCapture::dequeue_thread_func, this); int rc; struct libusb_transfer *xfr; rc = libusb_init(nullptr); if (rc < 0) { fprintf(stderr, "Error initializing libusb: %s\n", libusb_error_name(rc)); exit(1); } 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); } 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); } #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); const libusb_interface *interface = &config->interface[interface_number]; for (int altsetting = 0; altsetting < interface->num_altsetting; ++altsetting) { const libusb_interface_descriptor *interface_desc = &interface->altsetting[altsetting]; printf(" alternate setting %d\n", interface_desc->bAlternateSetting); 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); } } } #endif 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. // // There's also alternate settings 3 and 4, which seem to be // like 1 and 2 except they advertise less bandwidth needed. 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)); if (rc == LIBUSB_ERROR_NOT_FOUND) { fprintf(stderr, "This is usually because the card came up in USB2 mode.\n"); fprintf(stderr, "In particular, this tends to happen if you boot up with the\n"); fprintf(stderr, "card plugged in; just unplug and replug it, and it usually works.\n"); } exit(1); } rc = libusb_set_interface_alt_setting(devh, /*interface=*/0, /*alternate_setting=*/2); if (rc < 0) { fprintf(stderr, "Error setting alternate 2: %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? // // Bottom 16 bits of this register seem to be firmware version number (possibly not all all of them). // // 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. // // all of this is on request 214/215. other requests (192, 219, // 222, 223, 224) are used for firmware upgrade. Probably best to // stay out of it unless you know what you're doing. // // // 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?? update_capture_mode(); 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_OUT, 215, 0, 0x80000100 }, //{ LIBUSB_ENDPOINT_OUT, 215, 0, 0x09000000 }, { LIBUSB_ENDPOINT_OUT, 215, 24, 0x73c60001 }, // latch for frame start? { LIBUSB_ENDPOINT_IN, 214, 24, 0 }, // }; for (unsigned 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); } 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 // 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); xfr->user_data = this; 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); xfr->user_data = this; 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); xfr->user_data = this; //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 = 6; for (int i = 0; i < num_transfers; ++i) { size_t buf_size; int num_iso_pack, size; if (e == 3) { // Allocate for minimum width (because that will give us the most // number of packets, so we don't need to reallocate, but we'll // default to 720p for the first frame. size = find_xfer_size_for_width(PixelFormat_8BitYCbCr, 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; assert(size_t(num_bytes) <= buf_size); #if LIBUSB_API_VERSION >= 0x01000105 uint8_t *buf = libusb_dev_mem_alloc(devh, num_bytes); #else uint8_t *buf = nullptr; #endif if (buf == nullptr) { fprintf(stderr, "Failed to allocate persistent DMA memory "); #if LIBUSB_API_VERSION >= 0x01000105 fprintf(stderr, "(probably too old kernel; use 4.6.0 or newer).\n"); #else fprintf(stderr, "(compiled against too old libusb-1.0).\n"); #endif fprintf(stderr, "Will go slower, and likely fail due to memory fragmentation after a few hours.\n"); 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, 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(current_pixel_format, assumed_frame_width, xfr); } iso_xfrs.push_back(xfr); } } } void BMUSBCapture::start_bm_capture() { int i = 0; for (libusb_transfer *xfr : iso_xfrs) { int 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); } } #if 0 libusb_release_interface(devh, 0); out: if (devh) libusb_close(devh); libusb_exit(nullptr); return rc; #endif } void BMUSBCapture::stop_dequeue_thread() { dequeue_thread_should_quit = true; queues_not_empty.notify_all(); dequeue_thread.join(); } 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, hotplug_existing_devices ? LIBUSB_HOTPLUG_ENUMERATE : 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); } void BMUSBCapture::stop_bm_thread() { should_quit = true; usb_thread.join(); } map BMUSBCapture::get_available_video_modes() const { // The USB3 cards autodetect, and seem to have no provision for forcing modes. VideoMode auto_mode; auto_mode.name = "Autodetect"; auto_mode.autodetect = true; return {{ 0, auto_mode }}; } uint32_t BMUSBCapture::get_current_video_mode() const { return 0; // Matches get_available_video_modes(). } void BMUSBCapture::set_video_mode(uint32_t video_mode_id) { assert(video_mode_id == 0); // Matches get_available_video_modes(). } std::map BMUSBCapture::get_available_video_inputs() const { return { { 0x00000000, "HDMI/SDI" }, { 0x02000000, "Component" }, { 0x04000000, "Composite" }, { 0x06000000, "S-video" } }; } void BMUSBCapture::set_video_input(uint32_t video_input_id) { assert((video_input_id & ~0x06000000) == 0); current_video_input = video_input_id; update_capture_mode(); } std::map BMUSBCapture::get_available_audio_inputs() const { return { { 0x00000000, "Embedded" }, { 0x10000000, "Analog" } }; } void BMUSBCapture::set_audio_input(uint32_t audio_input_id) { assert((audio_input_id & ~0x10000000) == 0); current_audio_input = audio_input_id; update_capture_mode(); } void BMUSBCapture::update_capture_mode() { if (devh == nullptr) { return; } // Clearing the 0x08000000 bit seems to change the capture format (other source?). uint32_t mode = htonl(0x09000000 | current_video_input | current_audio_input); if (current_pixel_format == PixelFormat_8BitYCbCr) { mode |= htonl(0x20000000); } else { assert(current_pixel_format == PixelFormat_10BitYCbCr); } int rc = libusb_control_transfer(devh, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT, /*request=*/215, /*value=*/0, /*index=*/0, (unsigned char *)&mode, sizeof(mode), /*timeout=*/0); if (rc < 0) { fprintf(stderr, "Error on setting mode: %s\n", libusb_error_name(rc)); exit(1); } } } // namespace bmusb bmusb-0.7.0/bmusb.pc000066400000000000000000000004501311305021200142450ustar00rootroot00000000000000prefix=/usr/local exec_prefix=${prefix} libdir=${exec_prefix}/lib includedir=${prefix}/include Name: bmusb Description: userspace driver for Blackmagic USB3 video capture cards Version: 0.6.0 Cflags: -I${includedir} Libs: -L${libdir} -lbmusb Requires.private: libusb-1.0 Libs.private: -lpthread bmusb-0.7.0/bmusb/000077500000000000000000000000001311305021200137225ustar00rootroot00000000000000bmusb-0.7.0/bmusb/bmusb.h000066400000000000000000000335711311305021200152140ustar00rootroot00000000000000#ifndef _BMUSB_H #define _BMUSB_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace bmusb { class BMUSBCapture; // 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; uint8_t *data2 = nullptr; // Only if interleaved == true. size_t len = 0; // Number of bytes we actually have. size_t size = 0; // Number of bytes we have room for. size_t overflow = 0; void *userdata = nullptr; FrameAllocator *owner = nullptr; // If set to true, every other byte will go to data and to data2. // If so, and are still about the number of total bytes // so if size == 1024, there's 512 bytes in data and 512 in data2. // // This doesn't really make any sense if you asked for the // 10BitYCbCr pixel format. bool interleaved = false; // At what point this frame was received. Note that this marks the // _end_ of the frame being received, not the beginning. // Thus, if you want to measure latency, you'll also need to include // the time the frame actually took to transfer (usually 1/fps, // ie., the frames are typically transferred in real time). std::chrono::steady_clock::time_point received_timestamp = std::chrono::steady_clock::time_point::min(); }; 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; }; // 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, size_t num_queued_frames); Frame alloc_frame() override; void release_frame(Frame frame) override; private: size_t frame_size; std::mutex freelist_mutex; std::stack> freelist; // All of size . }; // Represents an input mode you can tune a card to. struct VideoMode { std::string name; bool autodetect = false; // If true, all the remaining fields are irrelevant. unsigned width = 0, height = 0; unsigned frame_rate_num = 0, frame_rate_den = 0; bool interlaced = false; }; // Represents the format of an actual frame coming in. // Note: Frame rate is _frame_ rate, not field rate. So 1080i60 gets 30/1, _not_ 60/1. // "second_field_start" is only valid for interlaced modes. If it is 1, // the two fields are actually stored interlaced (ie., every other line). // If not, each field is stored consecutively, and it signifies how many lines // from the very top of the frame there are before the second field // starts (so it will always be >= height/2 + extra_lines_top). struct VideoFormat { uint16_t id = 0; // For debugging/logging only. unsigned width = 0, height = 0, second_field_start = 0; unsigned extra_lines_top = 0, extra_lines_bottom = 0; unsigned frame_rate_nom = 0, frame_rate_den = 0; unsigned stride = 0; // In bytes, assuming no interleaving. bool interlaced = false; bool has_signal = false; bool is_connected = true; // If false, then has_signal makes no sense. }; struct AudioFormat { uint16_t id = 0; // For debugging/logging only. unsigned bits_per_sample = 0; unsigned num_channels = 0; unsigned sample_rate = 48000; }; enum PixelFormat { // 8-bit 4:2:2 in the standard Cb Y Cr Y order (UYVY). // This is the default. PixelFormat_8BitYCbCr, // 10-bit 4:2:2 in v210 order. Six pixels (six Y', three Cb, // three Cr) are packed into four 32-bit little-endian ints // in the following pattern (see e.g. the DeckLink documentation // for reference): // // A B G R // ----------------- // X Cr0 Y0 Cb0 // X Y2 Cb2 Y1 // X Cb4 Y3 Cr2 // X Y5 Cr4 Y4 // // If you read in RGB order and ignore the unused top bits, // this is essentially Cb Y Cr Y order, just like UYVY is. // // Note that unlike true v210, there is no guarantee about // 128-byte line alignment (or lack thereof); you should check // the stride member of VideoFormat. PixelFormat_10BitYCbCr, // 8-bit 4:4:4:4 BGRA (in that order). bmusb itself doesn't // produce this, but it is useful to represent e.g. synthetic inputs. PixelFormat_8BitBGRA, // 8-bit 4:2:0, 4:2:2, 4:4:4 or really anything else, planar // (ie., first all Y', then all Cb, then all Cr). bmusb doesn't // produce this, nor does it specify a mechanism to describe // the precise details of the format. PixelFormat_8BitYCbCrPlanar }; typedef std::function frame_callback_t; typedef std::function card_connected_callback_t; typedef std::function card_disconnected_callback_t; class CaptureInterface { public: virtual ~CaptureInterface() {} virtual std::map get_available_video_modes() const = 0; virtual uint32_t get_current_video_mode() const = 0; virtual void set_video_mode(uint32_t video_mode_id) = 0; // TODO: Add a way to query this based on mode? virtual std::set get_available_pixel_formats() const = 0; virtual void set_pixel_format(PixelFormat pixel_format) = 0; virtual PixelFormat get_current_pixel_format() const = 0; virtual std::map get_available_video_inputs() const = 0; virtual void set_video_input(uint32_t video_input_id) = 0; virtual uint32_t get_current_video_input() const = 0; virtual std::map get_available_audio_inputs() const = 0; virtual void set_audio_input(uint32_t audio_input_id) = 0; virtual uint32_t get_current_audio_input() const = 0; // Does not take ownership. virtual void set_video_frame_allocator(FrameAllocator *allocator) = 0; virtual FrameAllocator *get_video_frame_allocator() = 0; // Does not take ownership. virtual void set_audio_frame_allocator(FrameAllocator *allocator) = 0; virtual FrameAllocator *get_audio_frame_allocator() = 0; virtual void set_frame_callback(frame_callback_t callback) = 0; // Needs to be run before configure_card(). virtual void set_dequeue_thread_callbacks(std::function init, std::function cleanup) = 0; // Only valid after configure_card(). virtual std::string get_description() const = 0; virtual void configure_card() = 0; virtual void start_bm_capture() = 0; virtual void stop_dequeue_thread() = 0; // If a card is disconnected, it cannot come back; you should call stop_dequeue_thread() // and delete it. virtual bool get_disconnected() const = 0; }; // The actual capturing class, representing capture from a single card. class BMUSBCapture : public CaptureInterface { public: BMUSBCapture(int card_index, libusb_device *dev = nullptr) : card_index(card_index), dev(dev) { } ~BMUSBCapture() {} // Note: Cards could be unplugged and replugged between this call and // actually opening the card (in configure_card()). static unsigned num_cards(); std::set get_available_pixel_formats() const override { return std::set{ PixelFormat_8BitYCbCr, PixelFormat_10BitYCbCr }; } void set_pixel_format(PixelFormat pixel_format) override; PixelFormat get_current_pixel_format() const { return current_pixel_format; } std::map get_available_video_modes() const override; uint32_t get_current_video_mode() const override; void set_video_mode(uint32_t video_mode_id) override; virtual std::map get_available_video_inputs() const override; virtual void set_video_input(uint32_t video_input_id) override; virtual uint32_t get_current_video_input() const override { return current_video_input; } virtual std::map get_available_audio_inputs() const override; virtual void set_audio_input(uint32_t audio_input_id) override; virtual uint32_t get_current_audio_input() const override { return current_audio_input; } // Does not take ownership. void set_video_frame_allocator(FrameAllocator *allocator) override { video_frame_allocator = allocator; if (owned_video_frame_allocator.get() != allocator) { owned_video_frame_allocator.reset(); } } FrameAllocator *get_video_frame_allocator() override { return video_frame_allocator; } // Does not take ownership. void set_audio_frame_allocator(FrameAllocator *allocator) override { audio_frame_allocator = allocator; if (owned_audio_frame_allocator.get() != allocator) { owned_audio_frame_allocator.reset(); } } FrameAllocator *get_audio_frame_allocator() override { return audio_frame_allocator; } void set_frame_callback(frame_callback_t callback) override { frame_callback = callback; } // Needs to be run before configure_card(). void set_dequeue_thread_callbacks(std::function init, std::function cleanup) override { dequeue_init_callback = init; dequeue_cleanup_callback = cleanup; has_dequeue_callbacks = true; } // Only valid after configure_card(). std::string get_description() const override { return description; } void configure_card() override; void start_bm_capture() override; void stop_dequeue_thread() override; bool get_disconnected() const override { return disconnected; } // TODO: It's rather messy to have these outside the interface. static void start_bm_thread(); static void stop_bm_thread(); // Hotplug event (for devices being inserted between start_bm_thread() // and stop_bm_thread()); entirely optional, but must be set before // start_bm_capture(). Note that your callback should do as little work // as possible, since the callback comes from the main USB handling // thread, which is very time-sensitive. // // The callback function transfers ownership. If you don't want to hold // on to the device given to you in the callback, you need to call // libusb_unref_device(). static void set_card_connected_callback(card_connected_callback_t callback, bool hotplug_existing_devices_arg = false) { card_connected_callback = callback; hotplug_existing_devices = hotplug_existing_devices_arg; } // Similar to set_card_connected_callback(), with the same caveats. // (Note that this is set per-card and not global, as it is logically // connected to an existing BMUSBCapture object.) void set_card_disconnected_callback(card_disconnected_callback_t callback) { card_disconnected_callback = callback; } private: struct QueuedFrame { uint16_t timecode; uint16_t format; FrameAllocator::Frame frame; }; void start_new_audio_block(const uint8_t *start); void start_new_frame(const uint8_t *start); void queue_frame(uint16_t format, uint16_t timecode, FrameAllocator::Frame frame, std::deque *q); void dequeue_thread_func(); static void usb_thread_func(); static void cb_xfr(struct libusb_transfer *xfr); static int cb_hotplug(libusb_context *ctx, libusb_device *dev, libusb_hotplug_event event, void *user_data); void update_capture_mode(); std::string description; FrameAllocator::Frame current_video_frame; FrameAllocator::Frame current_audio_frame; std::mutex queue_lock; std::condition_variable queues_not_empty; std::deque pending_video_frames; std::deque pending_audio_frames; FrameAllocator *video_frame_allocator = nullptr; FrameAllocator *audio_frame_allocator = nullptr; std::unique_ptr owned_video_frame_allocator; std::unique_ptr owned_audio_frame_allocator; frame_callback_t frame_callback = nullptr; static card_connected_callback_t card_connected_callback; static bool hotplug_existing_devices; card_disconnected_callback_t card_disconnected_callback = nullptr; std::thread dequeue_thread; std::atomic dequeue_thread_should_quit; bool has_dequeue_callbacks = false; std::function dequeue_init_callback = nullptr; std::function dequeue_cleanup_callback = nullptr; int current_register = 0; static constexpr int NUM_BMUSB_REGISTERS = 60; uint8_t register_file[NUM_BMUSB_REGISTERS]; // If is nullptr, will choose device number from the list // of available devices on the system. is not used after configure_card() // (it will be unref-ed). int card_index = -1; libusb_device *dev = nullptr; std::vector iso_xfrs; int assumed_frame_width = 1280; libusb_device_handle *devh = nullptr; uint32_t current_video_input = 0x00000000; // HDMI/SDI. uint32_t current_audio_input = 0x00000000; // Embedded. PixelFormat current_pixel_format = PixelFormat_8BitYCbCr; bool disconnected = false; }; } // namespace bmusb #endif bmusb-0.7.0/bmusb/fake_capture.h000066400000000000000000000070661311305021200165350ustar00rootroot00000000000000#ifndef _FAKE_CAPTURE_H #define _FAKE_CAPTURE_H 1 #include #include #include #include "bmusb/bmusb.h" namespace bmusb { class FakeCapture : public CaptureInterface { public: FakeCapture(unsigned width, unsigned height, unsigned fps, unsigned audio_sample_frequency, int card_index, bool has_audio = false); ~FakeCapture(); // CaptureInterface. void set_video_frame_allocator(FrameAllocator *allocator) override { video_frame_allocator = allocator; if (owned_video_frame_allocator.get() != allocator) { owned_video_frame_allocator.reset(); } } FrameAllocator *get_video_frame_allocator() override { return video_frame_allocator; } // Does not take ownership. void set_audio_frame_allocator(FrameAllocator *allocator) override { audio_frame_allocator = allocator; if (owned_audio_frame_allocator.get() != allocator) { owned_audio_frame_allocator.reset(); } } FrameAllocator *get_audio_frame_allocator() override { return audio_frame_allocator; } void set_frame_callback(frame_callback_t callback) override { frame_callback = callback; } void set_dequeue_thread_callbacks(std::function init, std::function cleanup) override { dequeue_init_callback = init; dequeue_cleanup_callback = cleanup; has_dequeue_callbacks = true; } std::string get_description() const override { return description; } void configure_card() override; void start_bm_capture() override; void stop_dequeue_thread() override; bool get_disconnected() const override { return false; } std::set get_available_pixel_formats() const override { return std::set{ PixelFormat_8BitYCbCr, PixelFormat_10BitYCbCr }; } void set_pixel_format(PixelFormat pixel_format) override { current_pixel_format = pixel_format; } PixelFormat get_current_pixel_format() const { return current_pixel_format; } std::map get_available_video_modes() const override; void set_video_mode(uint32_t video_mode_id) override; uint32_t get_current_video_mode() const override { return 0; } std::map get_available_video_inputs() const override; void set_video_input(uint32_t video_input_id) override; uint32_t get_current_video_input() const override { return 0; } std::map get_available_audio_inputs() const override; void set_audio_input(uint32_t audio_input_id) override; uint32_t get_current_audio_input() const override { return 0; } private: void producer_thread_func(); void make_tone(int32_t *out, unsigned num_stereo_samples, unsigned num_channels); unsigned width, height, fps, audio_sample_frequency; PixelFormat current_pixel_format = PixelFormat_8BitYCbCr; int card_index; uint8_t y, cb, cr; // sin(2 * pi * f / F) and similar for cos. Used for fast sine generation. // Zero when no audio. float audio_sin = 0.0f, audio_cos = 0.0f; float audio_real = 0.0f, audio_imag = 0.0f; // Current state of the audio phaser. float audio_ref_level; bool has_dequeue_callbacks = false; std::function dequeue_init_callback = nullptr; std::function dequeue_cleanup_callback = nullptr; FrameAllocator *video_frame_allocator = nullptr; FrameAllocator *audio_frame_allocator = nullptr; std::unique_ptr owned_video_frame_allocator; std::unique_ptr owned_audio_frame_allocator; frame_callback_t frame_callback = nullptr; std::string description; std::atomic producer_thread_should_quit{false}; std::thread producer_thread; }; } // namespace bmusb #endif // !defined(_FAKE_CAPTURE_H) bmusb-0.7.0/fake_capture.cpp000066400000000000000000000224621311305021200157550ustar00rootroot00000000000000// A fake capture device that sends single-color frames at a given rate. // Mostly useful for testing themes without actually hooking up capture devices. #include "bmusb/fake_capture.h" #include #include #include #include #include #include #include #include #if __SSE2__ #include #endif #include #include #include "bmusb/bmusb.h" #define FRAME_SIZE (8 << 20) // 8 MB. // Pure-color inputs: Red, green, blue, white. #define NUM_COLORS 4 constexpr uint8_t ys[NUM_COLORS] = { 63, 173, 32, 235 }; constexpr uint8_t cbs[NUM_COLORS] = { 102, 42, 240, 128 }; constexpr uint8_t crs[NUM_COLORS] = { 240, 26, 118, 128 }; using namespace std; using namespace std::chrono; namespace bmusb { namespace { // We don't bother with multiversioning for this, because SSE2 // is on by default for all 64-bit compiles, which is really // the target user segment here. void memset2(uint8_t *s, const uint8_t c[2], size_t n) { size_t i = 0; #if __SSE2__ const uint8_t c_expanded[16] = { c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1], c[0], c[1] }; __m128i cc = *(__m128i *)c_expanded; __m128i *out = (__m128i *)s; for ( ; i < (n & ~15); i += 16) { _mm_storeu_si128(out++, cc); _mm_storeu_si128(out++, cc); } s = (uint8_t *)out; #endif for ( ; i < n; ++i) { *s++ = c[0]; *s++ = c[1]; } } void memset4(uint8_t *s, const uint8_t c[4], size_t n) { size_t i = 0; #if __SSE2__ const uint8_t c_expanded[16] = { c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3], c[0], c[1], c[2], c[3] }; __m128i cc = *(__m128i *)c_expanded; __m128i *out = (__m128i *)s; for ( ; i < (n & ~7); i += 8) { _mm_storeu_si128(out++, cc); _mm_storeu_si128(out++, cc); } s = (uint8_t *)out; #endif for ( ; i < n; ++i) { *s++ = c[0]; *s++ = c[1]; *s++ = c[2]; *s++ = c[3]; } } void memset16(uint8_t *s, const uint32_t c[4], size_t n) { size_t i = 0; #if __SSE2__ __m128i cc = *(__m128i *)c; __m128i *out = (__m128i *)s; for ( ; i < (n & ~1); i += 2) { _mm_storeu_si128(out++, cc); _mm_storeu_si128(out++, cc); } s = (uint8_t *)out; #endif for ( ; i < n; ++i) { memcpy(s, c, 16); s += 16; } } } // namespace FakeCapture::FakeCapture(unsigned width, unsigned height, unsigned fps, unsigned audio_sample_frequency, int card_index, bool has_audio) : width(width), height(height), fps(fps), audio_sample_frequency(audio_sample_frequency), card_index(card_index) { char buf[256]; snprintf(buf, sizeof(buf), "Fake card %d", card_index + 1); description = buf; y = ys[card_index % NUM_COLORS]; cb = cbs[card_index % NUM_COLORS]; cr = crs[card_index % NUM_COLORS]; if (has_audio) { audio_ref_level = pow(10.0f, -23.0f / 20.0f) * (1u << 31); // -23 dBFS (EBU R128 level). float freq = 440.0 * pow(2.0, card_index / 12.0); sincosf(2 * M_PI * freq / audio_sample_frequency, &audio_sin, &audio_cos); audio_real = audio_ref_level; audio_imag = 0.0f; } } FakeCapture::~FakeCapture() { if (has_dequeue_callbacks) { dequeue_cleanup_callback(); } } void FakeCapture::configure_card() { if (video_frame_allocator == nullptr) { owned_video_frame_allocator.reset(new MallocFrameAllocator(FRAME_SIZE, NUM_QUEUED_VIDEO_FRAMES)); set_video_frame_allocator(owned_video_frame_allocator.get()); } if (audio_frame_allocator == nullptr) { owned_audio_frame_allocator.reset(new MallocFrameAllocator(65536, NUM_QUEUED_AUDIO_FRAMES)); set_audio_frame_allocator(owned_audio_frame_allocator.get()); } } void FakeCapture::start_bm_capture() { producer_thread_should_quit = false; producer_thread = thread(&FakeCapture::producer_thread_func, this); } void FakeCapture::stop_dequeue_thread() { producer_thread_should_quit = true; producer_thread.join(); } std::map FakeCapture::get_available_video_modes() const { VideoMode mode; char buf[256]; snprintf(buf, sizeof(buf), "%ux%u", width, height); mode.name = buf; mode.autodetect = false; mode.width = width; mode.height = height; mode.frame_rate_num = fps; mode.frame_rate_den = 1; mode.interlaced = false; return {{ 0, mode }}; } std::map FakeCapture::get_available_video_inputs() const { return {{ 0, "Fake video input (single color)" }}; } std::map FakeCapture::get_available_audio_inputs() const { return {{ 0, "Fake audio input (silence)" }}; } void FakeCapture::set_video_mode(uint32_t video_mode_id) { assert(video_mode_id == 0); } void FakeCapture::set_video_input(uint32_t video_input_id) { assert(video_input_id == 0); } void FakeCapture::set_audio_input(uint32_t audio_input_id) { assert(audio_input_id == 0); } namespace { void add_time(double t, timespec *ts) { ts->tv_nsec += lrint(t * 1e9); ts->tv_sec += ts->tv_nsec / 1000000000; ts->tv_nsec %= 1000000000; } bool timespec_less_than(const timespec &a, const timespec &b) { return make_pair(a.tv_sec, a.tv_nsec) < make_pair(b.tv_sec, b.tv_nsec); } } // namespace void FakeCapture::producer_thread_func() { char thread_name[16]; snprintf(thread_name, sizeof(thread_name), "FakeCapture_%d", card_index); pthread_setname_np(pthread_self(), thread_name); uint16_t timecode = 0; if (has_dequeue_callbacks) { dequeue_init_callback(); } timespec next_frame; clock_gettime(CLOCK_MONOTONIC, &next_frame); add_time(1.0 / fps, &next_frame); while (!producer_thread_should_quit) { timespec now; clock_gettime(CLOCK_MONOTONIC, &now); if (timespec_less_than(now, next_frame)) { // Wait until the next frame. if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_frame, nullptr) == -1) { if (errno == EINTR) continue; // Re-check the flag and then sleep again. perror("clock_nanosleep"); exit(1); } } else { // We've seemingly missed a frame. If we're more than one second behind, // reset the timer; otherwise, just keep going. timespec limit = next_frame; ++limit.tv_sec; if (!timespec_less_than(now, limit)) { fprintf(stderr, "More than one second of missed fake frames; resetting clock.\n"); next_frame = now; } } steady_clock::time_point timestamp = steady_clock::now(); // Figure out when the next frame is to be, then compute the current one. add_time(1.0 / fps, &next_frame); VideoFormat video_format; video_format.width = width; video_format.height = height; if (current_pixel_format == PixelFormat_10BitYCbCr) { video_format.stride = (width + 5) / 6 * 4 * sizeof(uint32_t); } else { video_format.stride = width * 2; } video_format.frame_rate_nom = fps; video_format.frame_rate_den = 1; video_format.has_signal = true; video_format.is_connected = false; FrameAllocator::Frame video_frame = video_frame_allocator->alloc_frame(); if (video_frame.data != nullptr) { assert(video_frame.size >= width * height * 2); if (video_frame.interleaved) { assert(current_pixel_format == PixelFormat_8BitYCbCr); uint8_t cbcr[] = { cb, cr }; memset2(video_frame.data, cbcr, width * height / 2); memset(video_frame.data2, y, width * height); } else { if (current_pixel_format == PixelFormat_10BitYCbCr) { // Just use the 8-bit-values shifted left by 2. // It's not 100% correct, but it's close enough. uint32_t pix[4]; pix[0] = (cb << 2) | (y << 12) | (cr << 22); pix[1] = (y << 2) | (cb << 12) | ( y << 22); pix[2] = (cr << 2) | (y << 12) | (cb << 22); pix[3] = (y << 2) | (cr << 12) | ( y << 22); memset16(video_frame.data, pix, video_format.stride * height / sizeof(pix)); } else { uint8_t ycbcr[] = { y, cb, y, cr }; memset4(video_frame.data, ycbcr, width * height / 2); } } video_frame.len = video_format.stride * height; video_frame.received_timestamp = timestamp; } AudioFormat audio_format; audio_format.bits_per_sample = 32; audio_format.num_channels = 8; FrameAllocator::Frame audio_frame = audio_frame_allocator->alloc_frame(); if (audio_frame.data != nullptr) { const unsigned num_stereo_samples = audio_sample_frequency / fps; assert(audio_frame.size >= audio_format.num_channels * sizeof(int32_t) * num_stereo_samples); audio_frame.len = audio_format.num_channels * sizeof(int32_t) * num_stereo_samples; audio_frame.received_timestamp = timestamp; if (audio_sin == 0.0f) { // Silence. memset(audio_frame.data, 0, audio_frame.len); } else { make_tone((int32_t *)audio_frame.data, num_stereo_samples, audio_format.num_channels); } } frame_callback(timecode++, video_frame, 0, video_format, audio_frame, 0, audio_format); } if (has_dequeue_callbacks) { dequeue_cleanup_callback(); } } void FakeCapture::make_tone(int32_t *out, unsigned num_stereo_samples, unsigned num_channels) { int32_t *ptr = out; float r = audio_real, i = audio_imag; for (unsigned sample_num = 0; sample_num < num_stereo_samples; ++sample_num) { int32_t s = lrintf(r); for (unsigned i = 0; i < num_channels; ++i) { *ptr++ = s; } // Rotate the phaser by one sample. float new_r = r * audio_cos - i * audio_sin; float new_i = r * audio_sin + i * audio_cos; r = new_r; i = new_i; } // Periodically renormalize to counteract precision issues. double corr = audio_ref_level / hypot(r, i); audio_real = r * corr; audio_imag = i * corr; } } // namespace bmusb bmusb-0.7.0/format.cpp000066400000000000000000000016021311305021200146050ustar00rootroot00000000000000#include struct mode { char name[32]; int value; }; static const mode foo[] = { "NTSC ", 0xe901, "NTSC ", 0xe9c1, "NTSC ", 0xe801, "NTSC 23.98 ", 0xe901, "PAL ", 0xe909, "PAL 5:4 ", 0xe819, "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)); } } bmusb-0.7.0/formats.txt000066400000000000000000000015141311305021200150270ustar00rootroot000000000000000x0800 - no signal 0xe819 - 576p60 (720x576) 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?) 480p60 - 0xe9f1 (unsure if this is 60 or 59.94) NTSC - 0xe901 (also seen 0xe9c1, 0xe801) NTSC 23.98 - 0xe901 PAL - 0xe909 (also seen 0xe9c9, 0xebe9, 0xebe1) 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 (also seen 0xe903) unknown video modes from the D4: 640x424 30 fps - 0xe931 640x424 25 fps - 0xe939 theories: video - 0xe800 deep color - 0x0008 dropframe - 0x0004 bmusb-0.7.0/main.cpp000066400000000000000000000034141311305021200142440ustar00rootroot00000000000000#include #include #include "bmusb/bmusb.h" using namespace std; using namespace bmusb; BMUSBCapture *usb; void check_frame_stability(uint16_t timecode, FrameAllocator::Frame video_frame, size_t video_offset, VideoFormat video_format, FrameAllocator::Frame audio_frame, size_t audio_offset, AudioFormat 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.id, audio_end - audio_start, audio_format.id); 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=%ld, cur=%ld)\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=%ld, cur=%ld)\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; usb->get_video_frame_allocator()->release_frame(video_frame); usb->get_audio_frame_allocator()->release_frame(audio_frame); } int main(int argc, char **argv) { usb = new BMUSBCapture(0); // First card. usb->set_frame_callback(check_frame_stability); usb->configure_card(); BMUSBCapture::start_bm_thread(); usb->start_bm_capture(); sleep(1000000); }