From: Steinar H. Gunderson Date: Tue, 26 Jul 2016 15:42:51 +0000 (+0200) Subject: Move include files to bmusb/. X-Git-Tag: 0.5~6 X-Git-Url: https://git.sesse.net/?a=commitdiff_plain;h=0d48ad939040ec561e67ab2329e3961843b3cf74;p=bmusb Move include files to bmusb/. --- diff --git a/Makefile b/Makefile index 46da303..075aca8 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,6 @@ install: all $(DESTDIR)$(PREFIX)/lib/pkgconfig \ $(DESTDIR)$(PREFIX)/include/bmusb $(INSTALL) -m 755 -o root -g root $(LIB) $(DESTDIR)$(PREFIX)/lib - $(INSTALL) -m 755 -o root -g root bmusb.h fake_capture.h $(DESTDIR)$(PREFIX)/include/bmusb + $(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 diff --git a/bmusb/bmusb.h b/bmusb/bmusb.h new file mode 100644 index 0000000..506685d --- /dev/null +++ b/bmusb/bmusb.h @@ -0,0 +1,333 @@ +#ifndef _BMUSB_H +#define _BMUSB_H + +#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. + bool interleaved = false; + }; + + 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; 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 + 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 : public CaptureInterface { + public: + 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) 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) + { + 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; + 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; + 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. + + bool disconnected = false; +}; + +} // namespace bmusb + +#endif diff --git a/bmusb/fake_capture.h b/bmusb/fake_capture.h new file mode 100644 index 0000000..48dca06 --- /dev/null +++ b/bmusb/fake_capture.h @@ -0,0 +1,104 @@ +#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_frequency, int card_index); + ~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::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(); + + unsigned width, height, fps, audio_frequency; + uint8_t y, cb, cr; + + 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)