X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=bmusb.h;h=506685dc4c6a7401d17143cdb611315981c7b187;hb=8503a5c99e244f38d38659e9d43b5834b97a6d1b;hp=96a2257f8549279dc0000c7b31dfb2df8709e62a;hpb=cd863aa837174f6a3fa5862323d82dab8f0030f8;p=bmusb diff --git a/bmusb.h b/bmusb.h index 96a2257..506685d 100644 --- a/bmusb.h +++ b/bmusb.h @@ -1,16 +1,22 @@ #ifndef _BMUSB_H #define _BMUSB_H +#include #include #include #include #include #include +#include #include +#include +#include #include #include -struct libusb_transfer; +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 @@ -25,6 +31,7 @@ class FrameAllocator { 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; @@ -55,61 +62,208 @@ class FrameAllocator { 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; 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 + 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 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; + + 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 { +class BMUSBCapture : public CaptureInterface { public: - BMUSBCapture(int vid = 0x1edb, int pid = 0xbd3b) - : vid(vid), pid(pid) + BMUSBCapture(int card_index, libusb_device *dev = nullptr) + : card_index(card_index), dev(dev) { } + ~BMUSBCapture() {} + + 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) + 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() + FrameAllocator *get_video_frame_allocator() override { return video_frame_allocator; } // Does not take ownership. - void set_audio_frame_allocator(FrameAllocator *allocator) + 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() + FrameAllocator *get_audio_frame_allocator() override { return audio_frame_allocator; } - void set_frame_callback(frame_callback_t callback) + 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) + void set_dequeue_thread_callbacks(std::function init, std::function cleanup) override { dequeue_init_callback = init; dequeue_cleanup_callback = cleanup; has_dequeue_callbacks = true; } - void configure_card(); - void start_bm_capture(); - void stop_dequeue_thread(); + // 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) + { + card_connected_callback = callback; + } + + // 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; @@ -125,6 +279,11 @@ class BMUSBCapture { 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; @@ -136,7 +295,11 @@ class BMUSBCapture { 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; + card_disconnected_callback_t card_disconnected_callback = nullptr; std::thread dequeue_thread; std::atomic dequeue_thread_should_quit; @@ -149,8 +312,22 @@ class BMUSBCapture { static constexpr int NUM_BMUSB_REGISTERS = 60; uint8_t register_file[NUM_BMUSB_REGISTERS]; - int vid, pid; + // 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. + + bool disconnected = false; }; +} // namespace bmusb + #endif