#include "shared/context.h"
#include "decklink_capture.h"
#include "decklink_output.h"
+#include "decklink_util.h"
#include "defs.h"
#include "shared/disk_space_estimator.h"
#include "ffmpeg_capture.h"
cbcr_width != userdata->last_cbcr_width[field] ||
cbcr_height != userdata->last_cbcr_height[field];
const bool recreate_v210_texture =
- global_flags.ten_bit_input &&
+ global_flags.bit_depth > 8 &&
(first || v210_width != userdata->last_v210_width[field] || height != userdata->last_height[field]);
if (recreate_main_texture) {
void JitterHistory::frame_arrived(steady_clock::time_point now, int64_t frame_duration, size_t dropped_frames)
{
+ if (frame_duration != last_duration) {
+ // If the frame rate changed, the input clock is also going to change,
+ // so our historical data doesn't make much sense anymore.
+ // Also, format changes typically introduce blips that are not representative
+ // of the typical frame stream. (We make the assumption that format changes
+ // don't happen all the time in regular use; if they did, we should probably
+ // rather keep the history so that we take jitter they may introduce into account.)
+ clear();
+ last_duration = frame_duration;
+ }
if (expected_timestamp > steady_clock::time_point::min()) {
expected_timestamp += dropped_frames * nanoseconds(frame_duration * 1000000000 / TIMEBASE);
double jitter_seconds = fabs(duration<double>(expected_timestamp - now).count());
}
void QueueLengthPolicy::update_policy(steady_clock::time_point now,
- steady_clock::time_point expected_next_frame,
+ steady_clock::time_point expected_next_input_frame,
int64_t input_frame_duration,
int64_t master_frame_duration,
double max_input_card_jitter_seconds,
// Figure out when we can expect the next frame for this card, assuming
// worst-case jitter (ie., the frame is maximally late).
- double seconds_until_next_frame = max(duration<double>(expected_next_frame - now).count() + max_input_card_jitter_seconds, 0.0);
+ double seconds_until_next_frame = max(duration<double>(expected_next_input_frame - now).count() + max_input_card_jitter_seconds, 0.0);
// How many times are the master card expected to tick in that time?
// We assume the master clock has worst-case jitter but not any rate
ycbcr_format.luma_coefficients = YCBCR_REC_601;
}
ycbcr_format.full_range = false;
- ycbcr_format.num_levels = 1 << global_flags.x264_bit_depth;
+ ycbcr_format.num_levels = 1 << global_flags.bit_depth;
ycbcr_format.cb_x_position = 0.0f;
ycbcr_format.cr_x_position = 0.0f;
ycbcr_format.cb_y_position = 0.5f;
// Display chain; shows the live output produced by the main chain (or rather, a copy of it).
display_chain.reset(new EffectChain(global_flags.width, global_flags.height, resource_pool.get()));
check_error();
- GLenum type = global_flags.x264_bit_depth > 8 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE;
+ GLenum type = global_flags.bit_depth > 8 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE;
display_input = new YCbCrInput(inout_format, ycbcr_format, global_flags.width, global_flags.height, YCBCR_INPUT_SPLIT_Y_AND_CBCR, type);
display_chain->add_input(display_input);
display_chain->add_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
break;
}
+ if (!decklink_card_is_active(decklink, card_index)) {
+ fprintf(stderr, "DeckLink card %u is inactive in current profile, skipping (try changing it in Desktop Video Setup)\n", card_index);
+ decklink->Release();
+ continue;
+ }
+
DeckLinkCapture *capture = new DeckLinkCapture(decklink, card_index);
DeckLinkOutput *output = new DeckLinkOutput(resource_pool.get(), decklink_output_surface, global_flags.width, global_flags.height, card_index);
- if (!output->set_device(decklink)) {
+ if (!output->set_device(decklink, capture->get_input())) {
delete output;
output = nullptr;
}
}
#endif
- for (unsigned card_index = 0; card_index < MAX_VIDEO_CARDS; ++card_index) {
- cards[card_index].queue_length_policy.reset(card_index);
- }
-
chroma_subsampler.reset(new ChromaSubsampler(resource_pool.get()));
- if (global_flags.ten_bit_input) {
+ if (global_flags.bit_depth > 8) {
if (!v210Converter::has_hardware_support()) {
fprintf(stderr, "ERROR: --ten-bit-input requires support for OpenGL compute shaders\n");
fprintf(stderr, " (OpenGL 4.3, or GL_ARB_compute_shader + GL_ARB_shader_image_load_store).\n");
v210_converter->precompile_shader(3840);
v210_converter->precompile_shader(4096);
}
- if (global_flags.ten_bit_output) {
+ if (global_flags.bit_depth > 8) {
if (!v210Converter::has_hardware_support()) {
fprintf(stderr, "ERROR: --ten-bit-output requires support for OpenGL compute shaders\n");
fprintf(stderr, " (OpenGL 4.3, or GL_ARB_compute_shader + GL_ARB_shader_image_load_store).\n");
if (global_flags.enable_alsa_output) {
alsa.reset(new ALSAOutput(OUTPUT_FREQUENCY, /*num_channels=*/2));
}
+ output_card_is_master = global_flags.output_card_is_master;
if (global_flags.output_card != -1) {
desired_output_card_index = global_flags.output_card;
set_output_card_internal(global_flags.output_card);
CaptureCard *card = &cards[card_index];
if (card->capture != nullptr) {
+ card_mutex.unlock(); // The dequeue thread could be waiting for bm_frame().
card->capture->stop_dequeue_thread();
+ card_mutex.lock();
}
card->capture.reset(capture);
card->is_fake_capture = (card_type == CardType::FAKE_CAPTURE);
pixel_format = capture->get_current_pixel_format();
} else if (card_type == CardType::CEF_INPUT) {
pixel_format = PixelFormat_8BitBGRA;
- } else if (global_flags.ten_bit_input) {
+ } else if (global_flags.bit_depth > 8) {
pixel_format = PixelFormat_10BitYCbCr;
} else {
pixel_format = PixelFormat_8BitYCbCr;
assert(card_type == CardType::FFMPEG_INPUT);
}
- DeviceSpec device;
- device = DeviceSpec{InputSourceType::CAPTURE_CARD, card_index};
- audio_mixer->reset_resampler(device);
+ DeviceSpec device{InputSourceType::CAPTURE_CARD, card_index};
unsigned num_channels = card_type == CardType::LIVE_CARD ? 8 : 2;
if (is_active) {
audio_mixer->set_device_parameters(device, card->capture->get_description(), card_type, num_channels, /*active=*/true);
snprintf(name, sizeof(name), "Fake card %u", card_index + 1);
audio_mixer->set_device_parameters(device, name, card_type, num_channels, /*active=*/false);
}
+ audio_mixer->reset_resampler(device);
audio_mixer->trigger_state_changed_callback();
// Unregister old metrics, if any.
card->parked_capture = move(card->capture);
CaptureInterface *fake_capture = new FakeCapture(global_flags.width, global_flags.height, FAKE_FPS, OUTPUT_FREQUENCY, card_index, global_flags.fake_cards_audio);
configure_card(card_index, fake_capture, CardType::FAKE_CAPTURE, card->output.release(), /*is_srt_card=*/false);
- card->queue_length_policy.reset(card_index);
+ card->jitter_history.clear();
card->capture->start_bm_capture();
desired_output_video_mode = output_video_mode = card->output->pick_video_mode(desired_output_video_mode);
- card->output->start_output(desired_output_video_mode, pts_int);
+ card->output->start_output(desired_output_video_mode, pts_int, /*is_master_card=*/output_card_is_master);
}
output_card_index = card_index;
output_jitter_history.clear();
// (Could be nonintegral, but resampling will save us then.)
const int silence_samples = OUTPUT_FREQUENCY * video_format.frame_rate_den / video_format.frame_rate_nom;
- if (dropped_frames > MAX_FPS * 2) {
+ if (dropped_frames > TYPICAL_FPS * 2) {
fprintf(stderr, "%s lost more than two seconds (or time code jumping around; from 0x%04x to 0x%04x), resetting resampler\n",
description_for_card(card_index).c_str(), card->last_timecode, timecode);
audio_mixer->reset_resampler(device);
DeckLinkOutput *output = cards[output_card_index].output.get();
output->end_output();
desired_output_video_mode = output_video_mode = output->pick_video_mode(desired_output_video_mode);
- output->start_output(desired_output_video_mode, pts_int);
+ output->start_output(desired_output_video_mode, pts_int, /*is_master_card=*/output_card_is_master);
+ }
+
+ {
+ lock_guard<mutex> lock(card_mutex);
+ handle_hotplugged_cards();
}
CaptureCard::NewFrame new_frames[MAX_VIDEO_CARDS];
bool master_card_is_output;
unsigned master_card_index;
- if (output_card_index != -1) {
+ if (output_card_index != -1 && output_card_is_master) {
master_card_is_output = true;
master_card_index = output_card_index;
} else {
assert(master_card_index < MAX_VIDEO_CARDS);
}
- handle_hotplugged_cards();
-
vector<int32_t> raw_audio[MAX_VIDEO_CARDS]; // For MJPEG encoding.
OutputFrameInfo output_frame_info = get_one_frame_from_each_card(master_card_index, master_card_is_output, new_frames, has_new_frame, raw_audio);
schedule_audio_resampling_tasks(output_frame_info.dropped_frames, output_frame_info.num_samples, output_frame_info.frame_duration, output_frame_info.is_preroll, output_frame_info.frame_timestamp);
// If the first card is reporting a corrupted or otherwise dropped frame,
// just increase the pts (skipping over this frame) and don't try to compute anything new.
- if (!master_card_is_output && new_frames[master_card_index].frame->len == 0) {
+ if (!master_card_is_output &&
+ new_frames[master_card_index].frame != nullptr && // Timeout.
+ new_frames[master_card_index].frame->len == 0) {
++stats_dropped_frames;
pts_int += new_frames[master_card_index].length;
continue;
bool Mixer::input_card_is_master_clock(unsigned card_index, unsigned master_card_index) const
{
- if (output_card_index != -1) {
+ if (output_card_index != -1 && output_card_is_master) {
// The output card (ie., cards[output_card_index].output) is the master clock,
// so no input card (ie., cards[card_index].capture) is.
return false;
Mixer::OutputFrameInfo Mixer::get_one_frame_from_each_card(unsigned master_card_index, bool master_card_is_output, CaptureCard::NewFrame new_frames[MAX_VIDEO_CARDS], bool has_new_frame[MAX_VIDEO_CARDS], vector<int32_t> raw_audio[MAX_VIDEO_CARDS])
{
OutputFrameInfo output_frame_info;
+ constexpr steady_clock::duration master_card_timeout = milliseconds(200);
start:
unique_lock<mutex> lock(card_mutex, defer_lock);
+ bool timed_out = false;
if (master_card_is_output) {
// Clocked to the output, so wait for it to be ready for the next frame.
cards[master_card_index].output->wait_for_frame(pts_int, &output_frame_info.dropped_frames, &output_frame_info.frame_duration, &output_frame_info.is_preroll, &output_frame_info.frame_timestamp);
lock.lock();
} else {
// Wait for the master card to have a new frame.
- // TODO: Add a timeout.
output_frame_info.is_preroll = false;
lock.lock();
- 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 (master_card_is_output) {
+ timed_out = !cards[master_card_index].new_frames_changed.wait_for(lock,
+ master_card_timeout,
+ [this, master_card_index] {
+ return !cards[master_card_index].new_frames.empty() ||
+ cards[master_card_index].capture == nullptr ||
+ cards[master_card_index].capture->get_disconnected();
+ });
+ if (timed_out) {
+ fprintf(stderr, "WARNING: Master card (%s) did not deliver a frame for %u ms, creating a fake one.\n",
+ description_for_card(master_card_index).c_str(),
+ unsigned(duration_cast<milliseconds>(master_card_timeout).count()));
+ }
+ }
+
+ if (timed_out) {
+ // The master card stalled for 200 ms (possible when it's e.g.
+ // an SRT card). Send a frame no matter what; this also makes sure
+ // any other cards get to empty their queues, and in general,
+ // that we make _some_ sort of forward progress.
+ handle_hotplugged_cards();
+ } else if (master_card_is_output) {
handle_hotplugged_cards();
} else 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());
+ assert(cards[master_card_index].capture == nullptr ||
+ cards[master_card_index].capture->get_disconnected());
handle_hotplugged_cards();
lock.unlock();
goto start;
raw_audio[card_index] = move(card->new_raw_audio);
}
- if (!master_card_is_output) {
+ if (timed_out) {
+ // Pretend the frame happened a while ago and was only processed now,
+ // so that we get the duration sort-of right. This isn't ideal.
+ output_frame_info.dropped_frames = 0; // Hard to define, really.
+ output_frame_info.frame_duration = lrint(TIMEBASE * duration<double>(master_card_timeout).count());
+ output_frame_info.frame_timestamp = steady_clock::now() - master_card_timeout;
+ } else if (!master_card_is_output) {
output_frame_info.frame_timestamp = new_frames[master_card_index].received_timestamp;
output_frame_info.dropped_frames = new_frames[master_card_index].dropped_frames;
output_frame_info.frame_duration = new_frames[master_card_index].length;
fractional_samples = num_samples_times_timebase % TIMEBASE;
assert(output_frame_info.num_samples >= 0);
+ if (timed_out) {
+ DeviceSpec device{InputSourceType::CAPTURE_CARD, master_card_index};
+ bool success;
+ do {
+ success = audio_mixer->add_silence(device, output_frame_info.num_samples, /*dropped_frames=*/0);
+ } while (!success);
+ }
+
return output_frame_info;
}
for (unsigned card_index = 0; card_index < MAX_VIDEO_CARDS; ++card_index) {
CaptureCard *card = &cards[card_index];
if (card->capture != nullptr && card->capture->get_disconnected()) {
- fprintf(stderr, "Card %u went away, replacing with a fake card.\n", card_index);
- FakeCapture *capture = new FakeCapture(global_flags.width, global_flags.height, FAKE_FPS, OUTPUT_FREQUENCY, card_index, global_flags.fake_cards_audio);
- configure_card(card_index, capture, CardType::FAKE_CAPTURE, /*output=*/nullptr, /*is_srt_card=*/false);
- card->queue_length_policy.reset(card_index);
- card->capture->start_bm_capture();
+ bool is_active = card_index < unsigned(global_flags.min_num_cards) || cards[card_index].force_active;
+ if (is_active) {
+ fprintf(stderr, "Card %u went away, replacing with a fake card.\n", card_index);
+ FakeCapture *capture = new FakeCapture(global_flags.width, global_flags.height, FAKE_FPS, OUTPUT_FREQUENCY, card_index, global_flags.fake_cards_audio);
+ configure_card(card_index, capture, CardType::FAKE_CAPTURE, /*output=*/nullptr, /*is_srt_card=*/false);
+ card->jitter_history.clear();
+ card->capture->start_bm_capture();
+ } else {
+ // NOTE: The theme might end up forcing the card back at some later point
+ // (ie., force_active is false now, but might immediately be true again on
+ // e.g. the next frame). That should be rare, though, so we don't bother
+ // adjusting the message.
+ fprintf(stderr, "Card %u went away, removing. (To keep a fake card, increase --num-cards.)\n", card_index);
+ theme->remove_card(card_index);
+ configure_card(card_index, /*capture=*/nullptr, CardType::FAKE_CAPTURE, /*output=*/nullptr, /*is_srt_card=*/false);
+ card->jitter_history.clear();
+ }
}
}
CaptureCard *card = &cards[free_card_index];
BMUSBCapture *capture = new BMUSBCapture(free_card_index, new_dev);
configure_card(free_card_index, capture, CardType::LIVE_CARD, /*output=*/nullptr, /*is_srt_card=*/false);
- card->queue_length_policy.reset(free_card_index);
+ card->jitter_history.clear();
capture->set_card_disconnected_callback(bind(&Mixer::bm_hotplug_remove, this, free_card_index));
capture->start_bm_capture();
}
#ifdef HAVE_SRT
// Same, for SRT inputs.
- // TODO: On disconnect and reconnect, we might want to use the stream ID
for (SRTSOCKET sock : hotplugged_srt_cards_copy) {
char name[256];
int namelen = sizeof(name);
configure_card(free_card_index, capture, CardType::FFMPEG_INPUT, /*output=*/nullptr, /*is_srt_card=*/true);
update_srt_stats(sock, card); // Initial zero stats.
card->last_srt_stream_id = stream_id;
- card->queue_length_policy.reset(free_card_index);
+ card->jitter_history.clear();
capture->set_card_disconnected_callback(bind(&Mixer::bm_hotplug_remove, this, free_card_index));
capture->start_bm_capture();
}
if (card->capture == nullptr && card->force_active) {
FakeCapture *capture = new FakeCapture(global_flags.width, global_flags.height, FAKE_FPS, OUTPUT_FREQUENCY, card_index, global_flags.fake_cards_audio);
configure_card(card_index, capture, CardType::FAKE_CAPTURE, /*output=*/nullptr, /*is_srt_card=*/false);
- card->queue_length_policy.reset(card_index);
+ card->jitter_history.clear();
card->capture->start_bm_capture();
}
}
output_ycbcr_format.chroma_subsampling_y = 1;
output_ycbcr_format.luma_coefficients = ycbcr_output_coefficients;
output_ycbcr_format.full_range = false;
- output_ycbcr_format.num_levels = 1 << global_flags.x264_bit_depth;
+ output_ycbcr_format.num_levels = 1 << global_flags.bit_depth;
chain->change_ycbcr_output_format(output_ycbcr_format);
// Render main chain. If we're using zerocopy Quick Sync encoding
GLuint y_tex, cbcr_full_tex, cbcr_tex;
GLuint y_copy_tex, cbcr_copy_tex = 0;
GLuint y_display_tex, cbcr_display_tex;
- GLenum y_type = (global_flags.x264_bit_depth > 8) ? GL_R16 : GL_R8;
- GLenum cbcr_type = (global_flags.x264_bit_depth > 8) ? GL_RG16 : GL_RG8;
+ GLenum y_type = (global_flags.bit_depth > 8) ? GL_R16 : GL_R8;
+ GLenum cbcr_type = (global_flags.bit_depth > 8) ? GL_RG16 : GL_RG8;
const bool is_zerocopy = video_encoder->is_zerocopy();
if (is_zerocopy) {
cbcr_full_tex = resource_pool->create_2d_texture(cbcr_type, global_flags.width, global_flags.height);
if (global_flags.srt_port >= 0) {
// There's seemingly no other reasonable way to wake up the thread
// (libsrt's epoll equivalent is busy-waiting).
- int sock = srt_socket(AF_INET6, 0, 0);
+ int sock = srt_create_socket();
if (sock != -1) {
sockaddr_in6 addr;
memset(&addr, 0, sizeof(addr));
void Mixer::start_mode_scanning(unsigned card_index)
{
assert(card_index < MAX_VIDEO_CARDS);
- if (cards[card_index].capture != nullptr) {
+ if (cards[card_index].capture == nullptr) {
// Inactive card. Should never happen.
return;
}
#ifdef HAVE_SRT
void Mixer::start_srt()
{
- SRTSOCKET sock = srt_socket(AF_INET6, 0, 0);
+ SRTSOCKET sock = srt_create_socket();
sockaddr_in6 addr;
memset(&addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;
}
break;
}
+ if (!global_flags.enable_srt) { // Runtime UI toggle.
+ // Perhaps not as good as never listening in the first place,
+ // but much simpler to turn on and off.
+ srt_close(clientsock);
+ continue;
+ }
lock_guard<mutex> lock(hotplug_mutex);
hotplugged_srt_cards.push_back(clientsock);
}