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