/usr/include/bmusb/bmusb.h is in libbmusb-dev 0.5.4-1.
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 | #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 <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.
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;
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;
};
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;
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::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.
bool disconnected = false;
};
} // namespace bmusb
#endif
|