From 8e0a25a2663844905f3daf8fcf9bf9ec995d1074 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Tue, 26 Jul 2016 17:42:51 +0200 Subject: [PATCH 01/16] Move include files to bmusb/. --- Makefile | 2 +- bmusb.cpp | 2 +- bmusb.h | 333 ----------------------------------------------- fake_capture.cpp | 4 +- fake_capture.h | 104 --------------- main.cpp | 2 +- 6 files changed, 5 insertions(+), 442 deletions(-) delete mode 100644 bmusb.h delete mode 100644 fake_capture.h diff --git a/Makefile b/Makefile index a147176..46da303 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -CXXFLAGS := -std=gnu++14 -O2 -Wall -g $(shell pkg-config libusb-1.0 --cflags) -pthread +CXXFLAGS := -std=gnu++14 -O2 -Wall -I. -g $(shell pkg-config libusb-1.0 --cflags) -pthread LDFLAGS := $(shell pkg-config libusb-1.0 --libs) -pthread AR := ar RANLIB := ranlib diff --git a/bmusb.cpp b/bmusb.cpp index c4758f5..ec3eac2 100644 --- a/bmusb.cpp +++ b/bmusb.cpp @@ -21,7 +21,7 @@ #if HAS_MULTIVERSIONING #include #endif -#include "bmusb.h" +#include "bmusb/bmusb.h" #include #include diff --git a/bmusb.h b/bmusb.h deleted file mode 100644 index 506685d..0000000 --- a/bmusb.h +++ /dev/null @@ -1,333 +0,0 @@ -#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/fake_capture.cpp b/fake_capture.cpp index 9cb2afe..1410ef4 100644 --- a/fake_capture.cpp +++ b/fake_capture.cpp @@ -1,7 +1,7 @@ // A fake capture device that sends single-color frames at a given rate. // Mostly useful for testing themes without actually hooking up capture devices. -#include "fake_capture.h" +#include "bmusb/fake_capture.h" #include #include @@ -15,7 +15,7 @@ #endif #include -#include "bmusb.h" +#include "bmusb/bmusb.h" #define FRAME_SIZE (8 << 20) // 8 MB. diff --git a/fake_capture.h b/fake_capture.h deleted file mode 100644 index 93f1470..0000000 --- a/fake_capture.h +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef _FAKE_CAPTURE_H -#define _FAKE_CAPTURE_H 1 - -#include -#include -#include - -#include "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) diff --git a/main.cpp b/main.cpp index f1844cd..a50363a 100644 --- a/main.cpp +++ b/main.cpp @@ -1,6 +1,6 @@ #include #include -#include "bmusb.h" +#include "bmusb/bmusb.h" using namespace std; using namespace bmusb; -- 2.39.2 From 0d48ad939040ec561e67ab2329e3961843b3cf74 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Tue, 26 Jul 2016 17:42:51 +0200 Subject: [PATCH 02/16] Move include files to bmusb/. --- Makefile | 2 +- bmusb/bmusb.h | 333 +++++++++++++++++++++++++++++++++++++++++++ bmusb/fake_capture.h | 104 ++++++++++++++ 3 files changed, 438 insertions(+), 1 deletion(-) create mode 100644 bmusb/bmusb.h create mode 100644 bmusb/fake_capture.h 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) -- 2.39.2 From 92708386b8ea2a63b99c20e6a5f6beff3d9311ac Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Wed, 27 Jul 2016 21:04:32 +0200 Subject: [PATCH 03/16] Support hotplugging existing devices. --- bmusb.cpp | 3 ++- bmusb/bmusb.h | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/bmusb.cpp b/bmusb.cpp index ec3eac2..09473fd 100644 --- a/bmusb.cpp +++ b/bmusb.cpp @@ -51,6 +51,7 @@ using namespace std::placeholders; namespace bmusb { card_connected_callback_t BMUSBCapture::card_connected_callback = nullptr; +bool BMUSBCapture::hotplug_existing_devices = false; namespace { @@ -1346,7 +1347,7 @@ void BMUSBCapture::start_bm_thread() // coming back with errors, so only care about devices joining. if (card_connected_callback != nullptr) { if (libusb_hotplug_register_callback( - nullptr, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, LIBUSB_HOTPLUG_NO_FLAGS, + nullptr, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, hotplug_existing_devices ? LIBUSB_HOTPLUG_ENUMERATE : LIBUSB_HOTPLUG_NO_FLAGS, USB_VENDOR_BLACKMAGIC, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, &BMUSBCapture::cb_hotplug, nullptr, nullptr) < 0) { fprintf(stderr, "libusb_hotplug_register_callback() failed\n"); diff --git a/bmusb/bmusb.h b/bmusb/bmusb.h index 506685d..21cbc40 100644 --- a/bmusb/bmusb.h +++ b/bmusb/bmusb.h @@ -251,9 +251,11 @@ class BMUSBCapture : public CaptureInterface { // 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) + 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. @@ -299,6 +301,7 @@ class BMUSBCapture : public CaptureInterface { std::unique_ptr 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; -- 2.39.2 From 28c6df233b806a580281eb1ae242fa108af3eb09 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Wed, 27 Jul 2016 21:13:41 +0200 Subject: [PATCH 04/16] Add a function to get the number of USB cards. --- bmusb.cpp | 23 ++++++++++++++++++++++- bmusb/bmusb.h | 4 ++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/bmusb.cpp b/bmusb.cpp index 09473fd..fd323a6 100644 --- a/bmusb.cpp +++ b/bmusb.cpp @@ -884,6 +884,8 @@ void BMUSBCapture::usb_thread_func() } } +namespace { + struct USBCardDevice { uint16_t product; uint8_t bus, port; @@ -912,7 +914,7 @@ string get_card_description(int id, uint8_t bus, uint8_t port, uint16_t product) return buf; } -libusb_device_handle *open_card(int card_index, string *description) +vector find_all_cards() { libusb_device **devices; ssize_t num_devices = libusb_get_device_list(nullptr, &devices); @@ -950,6 +952,13 @@ libusb_device_handle *open_card(int card_index, string *description) return a.port < b.port; }); + return found_cards; +} + +libusb_device_handle *open_card(int card_index, string *description) +{ + vector found_cards = find_all_cards(); + for (size_t i = 0; i < found_cards.size(); ++i) { string tmp_description = get_card_description(i, found_cards[i].bus, found_cards[i].port, found_cards[i].product); fprintf(stderr, "%s\n", tmp_description.c_str()); @@ -1000,6 +1009,18 @@ libusb_device_handle *open_card(unsigned card_index, libusb_device *dev, string return devh; } +} // namespace + +int BMUSBCapture::num_cards() +{ + vector found_cards = find_all_cards(); + int ret = found_cards.size(); + for (size_t i = 0; i < found_cards.size(); ++i) { + libusb_unref_device(found_cards[i].device); + } + return ret; +} + void BMUSBCapture::configure_card() { if (video_frame_allocator == nullptr) { diff --git a/bmusb/bmusb.h b/bmusb/bmusb.h index 21cbc40..22a6655 100644 --- a/bmusb/bmusb.h +++ b/bmusb/bmusb.h @@ -175,6 +175,10 @@ class BMUSBCapture : public CaptureInterface { ~BMUSBCapture() {} + // Note: Cards could be unplugged and replugged between this call and + // actually opening the card (in configure_card()). + static int num_cards(); + 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; -- 2.39.2 From 8c728daab202458bba567bb4e5c1d0cbe2f8ab9c Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Wed, 27 Jul 2016 21:15:25 +0200 Subject: [PATCH 05/16] Fix int -> unsigned. --- bmusb.cpp | 4 ++-- bmusb/bmusb.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bmusb.cpp b/bmusb.cpp index fd323a6..7c21082 100644 --- a/bmusb.cpp +++ b/bmusb.cpp @@ -1011,10 +1011,10 @@ libusb_device_handle *open_card(unsigned card_index, libusb_device *dev, string } // namespace -int BMUSBCapture::num_cards() +unsigned BMUSBCapture::num_cards() { vector found_cards = find_all_cards(); - int ret = found_cards.size(); + unsigned ret = found_cards.size(); for (size_t i = 0; i < found_cards.size(); ++i) { libusb_unref_device(found_cards[i].device); } diff --git a/bmusb/bmusb.h b/bmusb/bmusb.h index 22a6655..f4af6aa 100644 --- a/bmusb/bmusb.h +++ b/bmusb/bmusb.h @@ -177,7 +177,7 @@ class BMUSBCapture : public CaptureInterface { // Note: Cards could be unplugged and replugged between this call and // actually opening the card (in configure_card()). - static int num_cards(); + static unsigned num_cards(); std::map get_available_video_modes() const override; uint32_t get_current_video_mode() const override; -- 2.39.2 From d53ab27c6945636a8031e1868c503adeb7dad557 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Wed, 27 Jul 2016 21:19:53 +0200 Subject: [PATCH 06/16] Properly initialize libusb in num_cards(). --- bmusb.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bmusb.cpp b/bmusb.cpp index 7c21082..e9a9125 100644 --- a/bmusb.cpp +++ b/bmusb.cpp @@ -1013,6 +1013,12 @@ libusb_device_handle *open_card(unsigned card_index, libusb_device *dev, string unsigned BMUSBCapture::num_cards() { + int rc = libusb_init(nullptr); + if (rc < 0) { + fprintf(stderr, "Error initializing libusb: %s\n", libusb_error_name(rc)); + exit(1); + } + vector found_cards = find_all_cards(); unsigned ret = found_cards.size(); for (size_t i = 0; i < found_cards.size(); ++i) { -- 2.39.2 From d51fd416b5c2784b03ae50bda59cd01a881bf7df Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Wed, 27 Jul 2016 21:46:37 +0200 Subject: [PATCH 07/16] Set libusb timeout to one second, to help shutdown if the thread is started with zero cards active. --- bmusb.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bmusb.cpp b/bmusb.cpp index e9a9125..cd4e59c 100644 --- a/bmusb.cpp +++ b/bmusb.cpp @@ -878,7 +878,8 @@ void BMUSBCapture::usb_thread_func() printf("couldn't set realtime priority for USB thread: %s\n", strerror(errno)); } while (!should_quit) { - int rc = libusb_handle_events(nullptr); + timeval sec { 1, 0 }; + int rc = libusb_handle_events_timeout(nullptr, &sec); if (rc != LIBUSB_SUCCESS) break; } -- 2.39.2 From b9433278b3cca82312d3785e2e75a57375a8683c Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Wed, 27 Jul 2016 21:25:11 +0200 Subject: [PATCH 08/16] Release v0.5. --- bmusb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bmusb.cpp b/bmusb.cpp index cd4e59c..5fda217 100644 --- a/bmusb.cpp +++ b/bmusb.cpp @@ -1,4 +1,4 @@ -// Intensity Shuttle USB3 capture driver, v0.4 +// Intensity Shuttle USB3 capture driver, v0.5 // 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) -- 2.39.2 From 70c3c8c164f11529ee443a70dbaae65a27308b60 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Fri, 29 Jul 2016 12:31:04 +0200 Subject: [PATCH 09/16] Fix a typo. --- fake_capture.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fake_capture.cpp b/fake_capture.cpp index 1410ef4..432cf42 100644 --- a/fake_capture.cpp +++ b/fake_capture.cpp @@ -31,7 +31,7 @@ namespace bmusb { namespace { // We don't bother with multiversioning for this, because SSE2 -// is on my default for all 64-bit compiles, which is really +// is on by default for all 64-bit compiles, which is really // the target user segment here. void memset2(uint8_t *s, const uint8_t c[2], size_t n) -- 2.39.2 From e0837a17b5a497476d67237c768836e51f8a4ce7 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Fri, 29 Jul 2016 12:32:16 +0200 Subject: [PATCH 10/16] Make FakeCapture capable of outputting audio (a simple sine tone). --- bmusb/fake_capture.h | 11 ++++++++-- fake_capture.cpp | 48 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/bmusb/fake_capture.h b/bmusb/fake_capture.h index 48dca06..17982ea 100644 --- a/bmusb/fake_capture.h +++ b/bmusb/fake_capture.h @@ -12,7 +12,7 @@ namespace bmusb { class FakeCapture : public CaptureInterface { public: - FakeCapture(unsigned width, unsigned height, unsigned fps, unsigned audio_frequency, int card_index); + FakeCapture(unsigned width, unsigned height, unsigned fps, unsigned audio_sample_frequency, int card_index, bool has_audio = false); ~FakeCapture(); // CaptureInterface. @@ -79,10 +79,17 @@ public: private: void producer_thread_func(); + void make_tone(int32_t *out, unsigned num_stereo_samples); - unsigned width, height, fps, audio_frequency; + unsigned width, height, fps, audio_sample_frequency; uint8_t y, cb, cr; + // sin(2 * pi * f / F) and similar for cos. Used for fast sine generation. + // Zero when no audio. + float audio_sin = 0.0f, audio_cos = 0.0f; + float audio_real = 0.0f, audio_imag = 0.0f; // Current state of the audio phaser. + float audio_ref_level; + bool has_dequeue_callbacks = false; std::function dequeue_init_callback = nullptr; std::function dequeue_cleanup_callback = nullptr; diff --git a/fake_capture.cpp b/fake_capture.cpp index 432cf42..961700e 100644 --- a/fake_capture.cpp +++ b/fake_capture.cpp @@ -86,8 +86,8 @@ void memset4(uint8_t *s, const uint8_t c[4], size_t n) } // namespace -FakeCapture::FakeCapture(unsigned width, unsigned height, unsigned fps, unsigned audio_frequency, int card_index) - : width(width), height(height), fps(fps), audio_frequency(audio_frequency) +FakeCapture::FakeCapture(unsigned width, unsigned height, unsigned fps, unsigned audio_sample_frequency, int card_index, bool has_audio) + : width(width), height(height), fps(fps), audio_sample_frequency(audio_sample_frequency) { char buf[256]; snprintf(buf, sizeof(buf), "Fake card %d", card_index + 1); @@ -96,6 +96,15 @@ FakeCapture::FakeCapture(unsigned width, unsigned height, unsigned fps, unsigned y = ys[card_index % NUM_COLORS]; cb = cbs[card_index % NUM_COLORS]; cr = crs[card_index % NUM_COLORS]; + + if (has_audio) { + audio_ref_level = pow(10.0f, -23.0f / 20.0f) * (1u << 31); // -23 dBFS (EBU R128 level). + + float freq = 440.0 * pow(2.0, card_index / 12.0); + sincosf(2 * M_PI * freq / audio_sample_frequency, &audio_sin, &audio_cos); + audio_real = audio_ref_level; + audio_imag = 0.0f; + } } FakeCapture::~FakeCapture() @@ -254,9 +263,16 @@ void FakeCapture::producer_thread_func() FrameAllocator::Frame audio_frame = audio_frame_allocator->alloc_frame(); if (audio_frame.data != nullptr) { - assert(audio_frame.size >= 2 * sizeof(uint32_t) * audio_frequency / fps); - audio_frame.len = 2 * sizeof(uint32_t) * audio_frequency / fps; - memset(audio_frame.data, 0, audio_frame.len); + const unsigned num_stereo_samples = audio_sample_frequency / fps; + assert(audio_frame.size >= 2 * sizeof(int32_t) * num_stereo_samples); + audio_frame.len = 2 * sizeof(int32_t) * num_stereo_samples; + + if (audio_sin == 0.0f) { + // Silence. + memset(audio_frame.data, 0, audio_frame.len); + } else { + make_tone((int32_t *)audio_frame.data, num_stereo_samples); + } } frame_callback(timecode++, @@ -268,4 +284,26 @@ void FakeCapture::producer_thread_func() } } +void FakeCapture::make_tone(int32_t *out, unsigned num_stereo_samples) +{ + int32_t *ptr = out; + float r = audio_real, i = audio_imag; + for (unsigned sample_num = 0; sample_num < num_stereo_samples; ++sample_num) { + int32_t s = lrintf(r); + *ptr++ = s; + *ptr++ = s; + + // Rotate the phaser by one sample. + float new_r = r * audio_cos - i * audio_sin; + float new_i = r * audio_sin + i * audio_cos; + r = new_r; + i = new_i; + } + + // Periodically renormalize to counteract precision issues. + double corr = audio_ref_level / hypot(r, i); + audio_real = r * corr; + audio_imag = i * corr; +} + } // namespace bmusb -- 2.39.2 From 05831781fb79ec7436280ccef6ef0fdabf105f33 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sat, 30 Jul 2016 18:45:42 +0200 Subject: [PATCH 11/16] Make a better error message on LIBUSB_ERROR_NOT_FOUND. --- bmusb.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bmusb.cpp b/bmusb.cpp index 5fda217..7f62a19 100644 --- a/bmusb.cpp +++ b/bmusb.cpp @@ -1105,6 +1105,11 @@ void BMUSBCapture::configure_card() 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)); + if (rc == LIBUSB_ERROR_NOT_FOUND) { + fprintf(stderr, "This is usually because the card came up in USB2 mode.\n"); + fprintf(stderr, "In particular, this tends to happen if you boot up with the\n"); + fprintf(stderr, "card plugged in; just unplug and replug it, and it usually works.\n"); + } exit(1); } rc = libusb_set_interface_alt_setting(devh, /*interface=*/0, /*alternate_setting=*/2); -- 2.39.2 From fad914ea7890709cc03418f4ce6f4c784cbd193d Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sat, 30 Jul 2016 18:41:25 +0200 Subject: [PATCH 12/16] Release v0.5.1. --- bmusb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bmusb.cpp b/bmusb.cpp index 7f62a19..080c0bb 100644 --- a/bmusb.cpp +++ b/bmusb.cpp @@ -1,4 +1,4 @@ -// Intensity Shuttle USB3 capture driver, v0.5 +// Intensity Shuttle USB3 capture driver, v0.5.1 // 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) -- 2.39.2 From a765e066b74ac52ff0abf239d430d6f8d83f792e Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sun, 11 Sep 2016 18:48:32 +0200 Subject: [PATCH 13/16] Make FakeCapture output 8-channel sound, to match the real cards. --- bmusb/fake_capture.h | 2 +- fake_capture.cpp | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/bmusb/fake_capture.h b/bmusb/fake_capture.h index 17982ea..ad432f7 100644 --- a/bmusb/fake_capture.h +++ b/bmusb/fake_capture.h @@ -79,7 +79,7 @@ public: private: void producer_thread_func(); - void make_tone(int32_t *out, unsigned num_stereo_samples); + void make_tone(int32_t *out, unsigned num_stereo_samples, unsigned num_channels); unsigned width, height, fps, audio_sample_frequency; uint8_t y, cb, cr; diff --git a/fake_capture.cpp b/fake_capture.cpp index 961700e..5f53e95 100644 --- a/fake_capture.cpp +++ b/fake_capture.cpp @@ -259,19 +259,19 @@ void FakeCapture::producer_thread_func() AudioFormat audio_format; audio_format.bits_per_sample = 32; - audio_format.num_channels = 2; + audio_format.num_channels = 8; FrameAllocator::Frame audio_frame = audio_frame_allocator->alloc_frame(); if (audio_frame.data != nullptr) { const unsigned num_stereo_samples = audio_sample_frequency / fps; - assert(audio_frame.size >= 2 * sizeof(int32_t) * num_stereo_samples); - audio_frame.len = 2 * sizeof(int32_t) * num_stereo_samples; + assert(audio_frame.size >= audio_format.num_channels * sizeof(int32_t) * num_stereo_samples); + audio_frame.len = audio_format.num_channels * sizeof(int32_t) * num_stereo_samples; if (audio_sin == 0.0f) { // Silence. memset(audio_frame.data, 0, audio_frame.len); } else { - make_tone((int32_t *)audio_frame.data, num_stereo_samples); + make_tone((int32_t *)audio_frame.data, num_stereo_samples, audio_format.num_channels); } } @@ -284,14 +284,15 @@ void FakeCapture::producer_thread_func() } } -void FakeCapture::make_tone(int32_t *out, unsigned num_stereo_samples) +void FakeCapture::make_tone(int32_t *out, unsigned num_stereo_samples, unsigned num_channels) { int32_t *ptr = out; float r = audio_real, i = audio_imag; for (unsigned sample_num = 0; sample_num < num_stereo_samples; ++sample_num) { int32_t s = lrintf(r); - *ptr++ = s; - *ptr++ = s; + for (unsigned i = 0; i < num_channels; ++i) { + *ptr++ = s; + } // Rotate the phaser by one sample. float new_r = r * audio_cos - i * audio_sin; -- 2.39.2 From 69b4f1db4fc485f28d6265a6adacb9a6baaa095f Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Tue, 20 Sep 2016 14:17:36 +0200 Subject: [PATCH 14/16] Explicitly declare both versions of add_to_frame_fastpath_core, to prevent GCC from inlining the wrong version. --- bmusb.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bmusb.cpp b/bmusb.cpp index 080c0bb..86779b4 100644 --- a/bmusb.cpp +++ b/bmusb.cpp @@ -536,6 +536,10 @@ const uint8_t *add_to_frame_fastpath(FrameAllocator::Frame *current_frame, const #else // defined(HAS_MULTIVERSIONING) +__attribute__((target("sse4.1"))) +const uint8_t *add_to_frame_fastpath_core(FrameAllocator::Frame *current_frame, const uint8_t *aligned_start, const uint8_t *limit, const char sync_char); + +__attribute__((target("avx2"))) const uint8_t *add_to_frame_fastpath_core(FrameAllocator::Frame *current_frame, const uint8_t *aligned_start, const uint8_t *limit, const char sync_char); // Does a memcpy and memchr in one to reduce processing time. -- 2.39.2 From 4d581348943463cdb7297d05ed1fe0c1fcbd90c0 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Thu, 29 Sep 2016 18:36:14 +0200 Subject: [PATCH 15/16] Build and install a shared library. --- Makefile | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 075aca8..ee0ba10 100644 --- a/Makefile +++ b/Makefile @@ -1,29 +1,43 @@ CXXFLAGS := -std=gnu++14 -O2 -Wall -I. -g $(shell pkg-config libusb-1.0 --cflags) -pthread LDFLAGS := $(shell pkg-config libusb-1.0 --libs) -pthread AR := ar +LN := ln RANLIB := ranlib INSTALL := install PREFIX := /usr/local LIB := libbmusb.a +SODEV := libbmusb.so +SONAME := libbmusb.so.1 +SOLIB := libbmusb.so.1.0.0 -all: $(LIB) main +all: $(LIB) $(SOLIB) main + +%.pic.o : %.cpp + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -fPIC -o $@ -c $^ main: bmusb.o main.o $(CXX) -o main $^ $(LDFLAGS) +# Static library. $(LIB): bmusb.o fake_capture.o $(AR) rc $@ $^ $(RANLIB) $@ +# Shared library. +$(SOLIB): bmusb.pic.o fake_capture.pic.o + $(CXX) -shared -Wl,-soname,$(SONAME) -o $@ $^ $(LDFLAGS) + clean: - $(RM) bmusb.o main.o fake_capture.o $(LIB) main + $(RM) bmusb.o main.o fake_capture.o bmusb.pic.o fake_capture.pic.o $(LIB) $(SOLIB) main install: all $(INSTALL) -m 755 -o root -g root -d \ $(DESTDIR)$(PREFIX)/lib \ $(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 $(LIB) $(SOLIB) $(DESTDIR)$(PREFIX)/lib + $(LN) -sf $(SOLIB) $(DESTDIR)$(PREFIX)/lib/$(SONAME) + $(LN) -sf $(SOLIB) $(DESTDIR)$(PREFIX)/lib/$(SODEV) $(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 -- 2.39.2 From 1066d2975e1c824bef6b9c9555c7d9f4f1a890a9 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Thu, 29 Sep 2016 18:36:37 +0200 Subject: [PATCH 16/16] Release v0.5.2. --- bmusb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bmusb.cpp b/bmusb.cpp index 86779b4..2900709 100644 --- a/bmusb.cpp +++ b/bmusb.cpp @@ -1,4 +1,4 @@ -// Intensity Shuttle USB3 capture driver, v0.5.1 +// Intensity Shuttle USB3 capture driver, v0.5.2 // 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) -- 2.39.2