/usr/include/bmusb/bmusb.h is in libbmusb-dev 0.7.0-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 | #ifndef _BMUSB_H
#define _BMUSB_H
#include <libusb.h>
#include <stdint.h>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <deque>
#include <functional>
#include <map>
#include <mutex>
#include <set>
#include <stack>
#include <string>
#include <thread>
#include <vector>
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, <len> and <size> 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<std::unique_ptr<uint8_t[]>> freelist; // All of size <frame_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<void(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)>
frame_callback_t;
typedef std::function<void(libusb_device *dev)> card_connected_callback_t;
typedef std::function<void()> card_disconnected_callback_t;
class CaptureInterface {
public:
virtual ~CaptureInterface() {}
virtual std::map<uint32_t, VideoMode> 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<PixelFormat> 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<uint32_t, std::string> 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<uint32_t, std::string> 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<void()> init, std::function<void()> 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<PixelFormat> get_available_pixel_formats() const override
{
return std::set<PixelFormat>{ PixelFormat_8BitYCbCr, PixelFormat_10BitYCbCr };
}
void set_pixel_format(PixelFormat pixel_format) override;
PixelFormat get_current_pixel_format() const
{
return current_pixel_format;
}
std::map<uint32_t, VideoMode> 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<uint32_t, std::string> 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<uint32_t, std::string> 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<void()> init, std::function<void()> 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<QueuedFrame> *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<QueuedFrame> pending_video_frames;
std::deque<QueuedFrame> pending_audio_frames;
FrameAllocator *video_frame_allocator = nullptr;
FrameAllocator *audio_frame_allocator = nullptr;
std::unique_ptr<FrameAllocator> owned_video_frame_allocator;
std::unique_ptr<FrameAllocator> 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<bool> dequeue_thread_should_quit;
bool has_dequeue_callbacks = false;
std::function<void()> dequeue_init_callback = nullptr;
std::function<void()> dequeue_cleanup_callback = nullptr;
int current_register = 0;
static constexpr int NUM_BMUSB_REGISTERS = 60;
uint8_t register_file[NUM_BMUSB_REGISTERS];
// If <dev> is nullptr, will choose device number <card_index> from the list
// of available devices on the system. <dev> is not used after configure_card()
// (it will be unref-ed).
int card_index = -1;
libusb_device *dev = nullptr;
std::vector<libusb_transfer *> 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
|