X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=mixer.cpp;h=6bc878074f2fa77e9ef9ef6da6157d54dc202eaa;hb=08a992d58c34da8d8bbd70226f7e85c9f30d9514;hp=80b0df1ea0df60d852bc530cb0d5535e99e2f92a;hpb=2fdcdafa707d407ad2eb5d34b0c81816950f827b;p=nageru diff --git a/mixer.cpp b/mixer.cpp index 80b0df1..6bc8780 100644 --- a/mixer.cpp +++ b/mixer.cpp @@ -27,11 +27,14 @@ #include #include #include +#include +#include #include "bmusb/bmusb.h" #include "context.h" #include "decklink_capture.h" #include "defs.h" +#include "fake_capture.h" #include "flags.h" #include "video_encoder.h" #include "pbo_frame_allocator.h" @@ -43,8 +46,10 @@ class QOpenGLContext; using namespace movit; using namespace std; using namespace std::placeholders; +using namespace bmusb; Mixer *global_mixer = nullptr; +bool uses_mlock = false; namespace { @@ -163,32 +168,51 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards) // Start listening for clients only once VideoEncoder has written its header, if any. httpd.start(9095); - // First try initializing the PCI devices, then USB, until we have the desired number of cards. + // First try initializing the fake devices, then PCI devices, then USB, + // until we have the desired number of cards. unsigned num_pci_devices = 0, num_usb_devices = 0; unsigned card_index = 0; - IDeckLinkIterator *decklink_iterator = CreateDeckLinkIteratorInstance(); - if (decklink_iterator != nullptr) { - for ( ; card_index < num_cards; ++card_index) { - IDeckLink *decklink; - if (decklink_iterator->Next(&decklink) != S_OK) { - break; - } + assert(global_flags.num_fake_cards >= 0); // Enforced in flags.cpp. + unsigned num_fake_cards = global_flags.num_fake_cards; + + assert(num_fake_cards <= num_cards); // Enforced in flags.cpp. + for ( ; card_index < num_fake_cards; ++card_index) { + configure_card(card_index, new FakeCapture(card_index), /*is_fake_capture=*/true); + } + + if (global_flags.num_fake_cards > 0) { + fprintf(stderr, "Initialized %d fake cards.\n", global_flags.num_fake_cards); + } - configure_card(card_index, format, new DeckLinkCapture(decklink, card_index)); - ++num_pci_devices; + if (card_index < num_cards) { + IDeckLinkIterator *decklink_iterator = CreateDeckLinkIteratorInstance(); + if (decklink_iterator != nullptr) { + for ( ; card_index < num_cards; ++card_index) { + IDeckLink *decklink; + if (decklink_iterator->Next(&decklink) != S_OK) { + break; + } + + configure_card(card_index, new DeckLinkCapture(decklink, card_index - num_fake_cards), /*is_fake_capture=*/false); + ++num_pci_devices; + } + decklink_iterator->Release(); + fprintf(stderr, "Found %d DeckLink PCI card(s).\n", num_pci_devices); + } else { + fprintf(stderr, "DeckLink drivers not found. Probing for USB cards only.\n"); } - decklink_iterator->Release(); - fprintf(stderr, "Found %d DeckLink PCI card(s).\n", num_pci_devices); - } else { - fprintf(stderr, "DeckLink drivers not found. Probing for USB cards only.\n"); } for ( ; card_index < num_cards; ++card_index) { - configure_card(card_index, format, new BMUSBCapture(card_index - num_pci_devices)); + BMUSBCapture *capture = new BMUSBCapture(card_index - num_pci_devices - num_fake_cards); + capture->set_card_disconnected_callback(bind(&Mixer::bm_hotplug_remove, this, card_index)); + configure_card(card_index, capture, /*is_fake_capture=*/false); ++num_usb_devices; } if (num_usb_devices > 0) { + has_bmusb_thread = true; + BMUSBCapture::set_card_connected_callback(bind(&Mixer::bm_hotplug_add, this, _1)); BMUSBCapture::start_bm_thread(); } @@ -245,13 +269,12 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards) locut.init(FILTER_HPF, 2); - // If --flat-audio is given, turn off everything that messes with the sound, - // except the final makeup gain. - if (global_flags.flat_audio) { - set_locut_enabled(false); - set_limiter_enabled(false); - set_compressor_enabled(false); - } + set_locut_enabled(global_flags.locut_enabled); + set_gain_staging_db(global_flags.initial_gain_staging_db); + set_gain_staging_auto(global_flags.gain_staging_auto); + set_compressor_enabled(global_flags.compressor_enabled); + set_limiter_enabled(global_flags.limiter_enabled); + set_final_makeup_gain_auto(global_flags.final_makeup_gain_auto); // hlen=16 is pretty low quality, but we use quite a bit of CPU otherwise, // and there's a limit to how important the peak meter is. @@ -266,7 +289,9 @@ Mixer::~Mixer() { resource_pool->release_glsl_program(cbcr_program_num); glDeleteBuffers(1, &cbcr_vbo); - BMUSBCapture::stop_bm_thread(); + if (has_bmusb_thread) { + BMUSBCapture::stop_bm_thread(); + } for (unsigned card_index = 0; card_index < num_cards; ++card_index) { { @@ -280,17 +305,33 @@ Mixer::~Mixer() video_encoder.reset(nullptr); } -void Mixer::configure_card(unsigned card_index, const QSurfaceFormat &format, CaptureInterface *capture) +void Mixer::configure_card(unsigned card_index, CaptureInterface *capture, bool is_fake_capture) { printf("Configuring card %d...\n", card_index); CaptureCard *card = &cards[card_index]; + if (card->capture != nullptr) { + card->capture->stop_dequeue_thread(); + delete card->capture; + } card->capture = capture; + card->is_fake_capture = is_fake_capture; card->capture->set_frame_callback(bind(&Mixer::bm_frame, this, card_index, _1, _2, _3, _4, _5, _6, _7)); - card->frame_allocator.reset(new PBOFrameAllocator(8 << 20, WIDTH, HEIGHT)); // 8 MB. + if (card->frame_allocator == nullptr) { + card->frame_allocator.reset(new PBOFrameAllocator(8 << 20, WIDTH, HEIGHT)); // 8 MB. + } card->capture->set_video_frame_allocator(card->frame_allocator.get()); - card->surface = create_surface(format); - card->resampling_queue.reset(new ResamplingQueue(card_index, OUTPUT_FREQUENCY, OUTPUT_FREQUENCY, 2)); + if (card->surface == nullptr) { + card->surface = create_surface_with_same_format(mixer_surface); + } + { + unique_lock lock(cards[card_index].audio_mutex); + card->resampling_queue.reset(new ResamplingQueue(card_index, OUTPUT_FREQUENCY, OUTPUT_FREQUENCY, 2)); + } + while (!card->new_frames.empty()) card->new_frames.pop(); + card->fractional_samples = 0; + card->last_timecode = -1; + card->next_local_pts = 0; card->capture->configure_card(); } @@ -360,6 +401,7 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, } int64_t frame_length = int64_t(TIMEBASE) * video_format.frame_rate_den / video_format.frame_rate_nom; + assert(frame_length > 0); size_t num_samples = (audio_frame.len > audio_offset) ? (audio_frame.len - audio_offset) / audio_format.num_channels / (audio_format.bits_per_sample / 8) : 0; if (num_samples > OUTPUT_FREQUENCY / 10) { @@ -482,6 +524,7 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, } userdata->last_interlaced = video_format.interlaced; userdata->last_has_signal = video_format.has_signal; + userdata->last_is_connected = video_format.is_connected; userdata->last_frame_rate_nom = video_format.frame_rate_nom; userdata->last_frame_rate_den = video_format.frame_rate_den; RefCountedFrame frame(video_frame); @@ -585,6 +628,17 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, } } +void Mixer::bm_hotplug_add(libusb_device *dev) +{ + lock_guard lock(hotplug_mutex); + hotplugged_cards.push_back(dev); +} + +void Mixer::bm_hotplug_remove(unsigned card_index) +{ + cards[card_index].new_frames_changed.notify_all(); +} + void Mixer::thread_func() { eglBindAPI(EGL_OPENGL_API); @@ -605,7 +659,6 @@ void Mixer::thread_func() bool has_new_frame[MAX_CARDS] = { false }; int num_samples[MAX_CARDS] = { 0 }; - // TODO: Add a timeout. unsigned master_card_index = theme->map_signal(master_clock_channel); assert(master_card_index < num_cards); @@ -614,6 +667,8 @@ void Mixer::thread_func() stats_dropped_frames += new_frames[master_card_index].dropped_frames; send_audio_level_callback(); + handle_hotplugged_cards(); + for (unsigned card_index = 0; card_index < num_cards; ++card_index) { if (card_index == master_card_index || !has_new_frame[card_index]) { continue; @@ -660,12 +715,39 @@ void Mixer::thread_func() double elapsed = now.tv_sec - start.tv_sec + 1e-9 * (now.tv_nsec - start.tv_nsec); if (frame % 100 == 0) { - printf("%d frames (%d dropped) in %.3f seconds = %.1f fps (%.1f ms/frame)\n", + printf("%d frames (%d dropped) in %.3f seconds = %.1f fps (%.1f ms/frame)", frame, stats_dropped_frames, elapsed, frame / elapsed, 1e3 * elapsed / frame); // chain->print_phase_timing(); + + // Check our memory usage, to see if we are close to our mlockall() + // limit (if at all set). + rusage used; + if (getrusage(RUSAGE_SELF, &used) == -1) { + perror("getrusage(RUSAGE_SELF)"); + assert(false); + } + + if (uses_mlock) { + rlimit limit; + if (getrlimit(RLIMIT_MEMLOCK, &limit) == -1) { + perror("getrlimit(RLIMIT_MEMLOCK)"); + assert(false); + } + + printf(", using %ld / %ld MB lockable memory (%.1f%%)", + long(used.ru_maxrss / 1024), + long(limit.rlim_cur / 1048576), + float(100.0 * (used.ru_maxrss * 1024.0) / limit.rlim_cur)); + } else { + printf(", using %ld MB memory (not locked)", + long(used.ru_maxrss / 1024)); + } + + printf("\n"); } + if (should_cut.exchange(false)) { // Test and clear. video_encoder->do_cut(frame); } @@ -688,9 +770,19 @@ void Mixer::thread_func() void Mixer::get_one_frame_from_each_card(unsigned master_card_index, CaptureCard::NewFrame new_frames[MAX_CARDS], bool has_new_frame[MAX_CARDS], int num_samples[MAX_CARDS]) { +start: // The first card is the master timer, so wait for it to have a new frame. + // TODO: Add a timeout. unique_lock lock(bmusb_mutex); - cards[master_card_index].new_frames_changed.wait(lock, [this, master_card_index]{ return !cards[master_card_index].new_frames.empty(); }); + cards[master_card_index].new_frames_changed.wait(lock, [this, master_card_index]{ return !cards[master_card_index].new_frames.empty() || cards[master_card_index].capture->get_disconnected(); }); + + if (cards[master_card_index].new_frames.empty()) { + // We were woken up, but not due to a new frame. Deal with it + // and then restart. + assert(cards[master_card_index].capture->get_disconnected()); + handle_hotplugged_cards(); + goto start; + } for (unsigned card_index = 0; card_index < num_cards; ++card_index) { CaptureCard *card = &cards[card_index]; @@ -725,6 +817,52 @@ void Mixer::get_one_frame_from_each_card(unsigned master_card_index, CaptureCard } } +void Mixer::handle_hotplugged_cards() +{ + // Check for cards that have been disconnected since last frame. + for (unsigned card_index = 0; card_index < num_cards; ++card_index) { + CaptureCard *card = &cards[card_index]; + if (card->capture->get_disconnected()) { + fprintf(stderr, "Card %u went away, replacing with a fake card.\n", card_index); + configure_card(card_index, new FakeCapture(card_index), /*is_fake_capture=*/true); + card->queue_length_policy.reset(card_index); + card->capture->start_bm_capture(); + } + } + + // Check for cards that have been connected since last frame. + vector hotplugged_cards_copy; + { + lock_guard lock(hotplug_mutex); + swap(hotplugged_cards, hotplugged_cards_copy); + } + for (libusb_device *new_dev : hotplugged_cards_copy) { + // Look for a fake capture card where we can stick this in. + int free_card_index = -1; + for (unsigned card_index = 0; card_index < num_cards; ++card_index) { + if (cards[card_index].is_fake_capture) { + free_card_index = int(card_index); + break; + } + } + + if (free_card_index == -1) { + fprintf(stderr, "New card plugged in, but no free slots -- ignoring.\n"); + libusb_unref_device(new_dev); + } else { + // BMUSBCapture takes ownership. + fprintf(stderr, "New card plugged in, choosing slot %d.\n", free_card_index); + CaptureCard *card = &cards[free_card_index]; + BMUSBCapture *capture = new BMUSBCapture(free_card_index, new_dev); + configure_card(free_card_index, capture, /*is_fake_capture=*/false); + card->queue_length_policy.reset(free_card_index); + capture->set_card_disconnected_callback(bind(&Mixer::bm_hotplug_remove, this, free_card_index)); + capture->start_bm_capture(); + } + } +} + + void Mixer::schedule_audio_resampling_tasks(unsigned dropped_frames, int num_samples_per_frame, int length_per_frame) { // Resample the audio as needed, including from previously dropped frames.