} // namespace
-AudioMixer::AudioMixer(unsigned num_capture_cards, unsigned num_ffmpeg_inputs)
- : num_capture_cards(num_capture_cards),
- num_ffmpeg_inputs(num_ffmpeg_inputs),
- ffmpeg_inputs(new AudioDevice[num_ffmpeg_inputs]),
- limiter(OUTPUT_FREQUENCY),
+AudioMixer::AudioMixer()
+ : limiter(OUTPUT_FREQUENCY),
correlation(OUTPUT_FREQUENCY)
{
for (unsigned bus_index = 0; bus_index < MAX_BUSES; ++bus_index) {
device->resampling_queue.reset();
} else {
device->resampling_queue.reset(new ResamplingQueue(
- device_spec, device->capture_frequency, OUTPUT_FREQUENCY, device->interesting_channels.size(),
+ spec_to_string(device_spec), device->capture_frequency, OUTPUT_FREQUENCY, device->interesting_channels.size(),
global_flags.audio_queue_length_ms * 0.001));
}
}
return &video_cards[device.index];
case InputSourceType::ALSA_INPUT:
return &alsa_inputs[device.index];
- case InputSourceType::FFMPEG_VIDEO_INPUT:
- return &ffmpeg_inputs[device.index];
case InputSourceType::SILENCE:
default:
assert(false);
memset(output, 0, num_samples * 2 * sizeof(*output));
} else {
assert(bus.device.type == InputSourceType::CAPTURE_CARD ||
- bus.device.type == InputSourceType::ALSA_INPUT ||
- bus.device.type == InputSourceType::FFMPEG_VIDEO_INPUT);
+ bus.device.type == InputSourceType::ALSA_INPUT);
const float *lsrc, *rsrc;
unsigned lstride, rstride;
float *dptr = output;
ret.push_back(device_spec);
}
}
- for (unsigned card_index = 0; card_index < num_ffmpeg_inputs; ++card_index) {
- const DeviceSpec device_spec{InputSourceType::FFMPEG_VIDEO_INPUT, card_index};
- if (!find_audio_device(device_spec)->interesting_channels.empty()) {
- ret.push_back(device_spec);
- }
- }
return ret;
}
lock_guard<timed_mutex> lock(audio_mutex);
map<DeviceSpec, DeviceInfo> devices;
- for (unsigned card_index = 0; card_index < num_capture_cards; ++card_index) {
+ for (unsigned card_index = 0; card_index < MAX_VIDEO_CARDS; ++card_index) {
const DeviceSpec spec{ InputSourceType::CAPTURE_CARD, card_index };
const AudioDevice *device = &video_cards[card_index];
DeviceInfo info;
info.display_name = device->display_name;
- info.num_channels = 8;
+ info.num_channels = device->num_channels;
devices.insert(make_pair(spec, info));
}
vector<ALSAPool::Device> available_alsa_devices = alsa_pool.get_devices();
info.alsa_address = device.address;
devices.insert(make_pair(spec, info));
}
- for (unsigned card_index = 0; card_index < num_ffmpeg_inputs; ++card_index) {
- const DeviceSpec spec{ InputSourceType::FFMPEG_VIDEO_INPUT, card_index };
- const AudioDevice *device = &ffmpeg_inputs[card_index];
- DeviceInfo info;
- info.display_name = device->display_name;
- info.num_channels = 2;
- devices.insert(make_pair(spec, info));
- }
return devices;
}
-void AudioMixer::set_display_name(DeviceSpec device_spec, const string &name)
+void AudioMixer::set_device_parameters(DeviceSpec device_spec, const std::string &display_name, CardType card_type, unsigned num_channels, bool active)
{
AudioDevice *device = find_audio_device(device_spec);
lock_guard<timed_mutex> lock(audio_mutex);
- device->display_name = name;
+ if (active || device->display_name.empty()) {
+ device->display_name = display_name;
+ }
+ device->card_type = card_type;
+ device->active = active;
+}
+
+bool AudioMixer::get_active(DeviceSpec device_spec)
+{
+ AudioDevice *device = find_audio_device(device_spec);
+
+ lock_guard<timed_mutex> lock(audio_mutex);
+ return device->active;
}
void AudioMixer::serialize_device(DeviceSpec device_spec, DeviceSpecProto *device_spec_proto)
case InputSourceType::ALSA_INPUT:
alsa_pool.serialize_device(device_spec.index, device_spec_proto);
break;
- case InputSourceType::FFMPEG_VIDEO_INPUT:
- device_spec_proto->set_type(DeviceSpecProto::FFMPEG_VIDEO_INPUT);
- device_spec_proto->set_index(device_spec.index);
- device_spec_proto->set_display_name(ffmpeg_inputs[device_spec.index].display_name);
- break;
}
}
void AudioMixer::set_simple_input(unsigned card_index)
{
- assert(card_index < num_capture_cards + num_ffmpeg_inputs);
+ assert(card_index < MAX_VIDEO_CARDS);
InputMapping new_input_mapping;
InputMapping::Bus input;
input.name = "Main";
- if (card_index >= num_capture_cards) {
- input.device = DeviceSpec{InputSourceType::FFMPEG_VIDEO_INPUT, card_index - num_capture_cards};
- } else {
- input.device = DeviceSpec{InputSourceType::CAPTURE_CARD, card_index};
- }
+ input.device = DeviceSpec{InputSourceType::CAPTURE_CARD, card_index};
input.source_channel[0] = 0;
input.source_channel[1] = 1;
input_mapping.buses[0].source_channel[0] == 0 &&
input_mapping.buses[0].source_channel[1] == 1) {
return input_mapping.buses[0].device.index;
- } else if (input_mapping.buses.size() == 1 &&
- input_mapping.buses[0].device.type == InputSourceType::FFMPEG_VIDEO_INPUT &&
- input_mapping.buses[0].source_channel[0] == 0 &&
- input_mapping.buses[0].source_channel[1] == 1) {
- return input_mapping.buses[0].device.index + num_capture_cards;
} else {
return numeric_limits<unsigned>::max();
}
map<DeviceSpec, set<unsigned>> interesting_channels;
for (const InputMapping::Bus &bus : new_input_mapping.buses) {
if (bus.device.type == InputSourceType::CAPTURE_CARD ||
- bus.device.type == InputSourceType::ALSA_INPUT ||
- bus.device.type == InputSourceType::FFMPEG_VIDEO_INPUT) {
+ bus.device.type == InputSourceType::ALSA_INPUT) {
for (unsigned channel = 0; channel < 2; ++channel) {
if (bus.source_channel[channel] != -1) {
interesting_channels[bus.device].insert(bus.source_channel[channel]);
if (bus.device.type == InputSourceType::SILENCE) {
metrics.labels.emplace_back("source_type", "silence");
} else if (bus.device.type == InputSourceType::CAPTURE_CARD) {
- metrics.labels.emplace_back("source_type", "capture_card");
+ AudioDevice *device = find_audio_device(bus.device);
+ if (device->card_type == CardType::FFMPEG_INPUT) {
+ metrics.labels.emplace_back("source_type", "ffmpeg_video_input");
+ } else {
+ metrics.labels.emplace_back("source_type", "capture_card");
+ }
} else if (bus.device.type == InputSourceType::ALSA_INPUT) {
metrics.labels.emplace_back("source_type", "alsa_input");
- } else if (bus.device.type == InputSourceType::FFMPEG_VIDEO_INPUT) {
- metrics.labels.emplace_back("source_type", "ffmpeg_video_input");
} else {
assert(false);
}
reset_resampler_mutex_held(device_spec);
}
}
- for (unsigned card_index = 0; card_index < num_ffmpeg_inputs; ++card_index) {
- const DeviceSpec device_spec{InputSourceType::FFMPEG_VIDEO_INPUT, card_index};
- AudioDevice *device = find_audio_device(device_spec);
- if (device->interesting_channels != interesting_channels[device_spec]) {
- device->interesting_channels = interesting_channels[device_spec];
- reset_resampler_mutex_held(device_spec);
- }
- }
input_mapping = new_input_mapping;
}
return true;
} else {
assert(bus.device.type == InputSourceType::CAPTURE_CARD ||
- bus.device.type == InputSourceType::ALSA_INPUT ||
- bus.device.type == InputSourceType::FFMPEG_VIDEO_INPUT);
+ bus.device.type == InputSourceType::ALSA_INPUT);
return bus.source_channel[0] == bus.source_channel[1];
}
}
+// This is perhaps not the most user-friendly output, but it's at least better
+// than the raw index. It would be nice to have it identical to
+// Mixer::description_for_card for capture cards, though.
+string AudioMixer::spec_to_string(DeviceSpec device_spec) const
+{
+ char buf[256];
+
+ switch (device_spec.type) {
+ case InputSourceType::SILENCE:
+ return "<silence>";
+ case InputSourceType::CAPTURE_CARD: {
+ const AudioDevice *device = find_audio_device(device_spec);
+ if (device->card_type == CardType::FFMPEG_INPUT) {
+ snprintf(buf, sizeof(buf), "Virtual capture card %u (%s)", device_spec.index, device->display_name.c_str());
+ } else {
+ snprintf(buf, sizeof(buf), "Capture card %u (%s)", device_spec.index, device->display_name.c_str());
+ }
+ return buf;
+ }
+ case InputSourceType::ALSA_INPUT:
+ snprintf(buf, sizeof(buf), "ALSA input %u", device_spec.index);
+ return buf;
+ default:
+ assert(false);
+ }
+}
+
+
AudioMixer *global_audio_mixer = nullptr;
metric_input_queue_safe_length_frames = safe_queue_length;
}
-Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
+Mixer::Mixer(const QSurfaceFormat &format)
: httpd(),
- num_cards(num_cards),
mixer_surface(create_surface(format)),
h264_encoder_surface(create_surface(format)),
decklink_output_surface(create_surface(format)),
}
// Must be instantiated after VideoEncoder has initialized global_flags.use_zerocopy.
- theme.reset(new Theme(global_flags.theme_filename, global_flags.theme_dirs, resource_pool.get(), num_cards));
+ theme.reset(new Theme(global_flags.theme_filename, global_flags.theme_dirs, resource_pool.get()));
// Must be instantiated after the theme, as the theme decides the number of FFmpeg inputs.
std::vector<FFmpegCapture *> video_inputs = theme->get_video_inputs();
- audio_mixer.reset(new AudioMixer(num_cards, video_inputs.size()));
+ audio_mixer.reset(new AudioMixer);
httpd.add_endpoint("/channels", bind(&Mixer::get_channels_json, this), HTTPD::ALLOW_ALL_ORIGINS);
for (int channel_idx = 0; channel_idx < theme->get_num_channels(); ++channel_idx) {
{
IDeckLinkIterator *decklink_iterator = CreateDeckLinkIteratorInstance();
if (decklink_iterator != nullptr) {
- for ( ; card_index < num_cards; ++card_index) {
+ for ( ; card_index < unsigned(global_flags.max_num_cards); ++card_index) {
IDeckLink *decklink;
if (decklink_iterator->Next(&decklink) != S_OK) {
break;
delete output;
output = nullptr;
}
- configure_card(card_index, capture, CardType::LIVE_CARD, output);
+ configure_card(card_index, capture, CardType::LIVE_CARD, output, /*is_srt_card=*/false);
++num_pci_devices;
}
decklink_iterator->Release();
}
unsigned num_usb_devices = BMUSBCapture::num_cards();
- for (unsigned usb_card_index = 0; usb_card_index < num_usb_devices && card_index < num_cards; ++usb_card_index, ++card_index) {
+ for (unsigned usb_card_index = 0; usb_card_index < num_usb_devices && card_index < unsigned(global_flags.max_num_cards); ++usb_card_index, ++card_index) {
BMUSBCapture *capture = new BMUSBCapture(usb_card_index);
capture->set_card_disconnected_callback(bind(&Mixer::bm_hotplug_remove, this, card_index));
- configure_card(card_index, capture, CardType::LIVE_CARD, /*output=*/nullptr);
+ configure_card(card_index, capture, CardType::LIVE_CARD, /*output=*/nullptr, /*is_srt_card=*/false);
}
fprintf(stderr, "Found %u USB card(s).\n", num_usb_devices);
+ // Fill up with fake cards for as long as we can, so that the FFmpeg
+ // and HTML cards always come last.
unsigned num_fake_cards = 0;
- for ( ; card_index < num_cards; ++card_index, ++num_fake_cards) {
- 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);
+#ifdef HAVE_CEF
+ size_t num_html_inputs = theme->get_html_inputs().size();
+#else
+ size_t num_html_inputs = 0;
+#endif
+ for ( ; card_index < MAX_VIDEO_CARDS - video_inputs.size() - num_html_inputs; ++card_index) {
+ // Only bother to activate fake capture cards to satisfy the minimum.
+ bool is_active = card_index < unsigned(global_flags.min_num_cards) || cards[card_index].force_active;
+ if (is_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);
+ ++num_fake_cards;
+ } else {
+ configure_card(card_index, nullptr, CardType::FAKE_CAPTURE, /*output=*/nullptr, /*is_srt_card=*/false);
+ }
}
if (num_fake_cards > 0) {
fprintf(stderr, "Initialized %u fake cards.\n", num_fake_cards);
}
- // Initialize all video inputs the theme asked for. Note that these are
- // all put _after_ the regular cards, which stop at <num_cards> - 1.
+ // Initialize all video inputs the theme asked for.
for (unsigned video_card_index = 0; video_card_index < video_inputs.size(); ++card_index, ++video_card_index) {
if (card_index >= MAX_VIDEO_CARDS) {
fprintf(stderr, "ERROR: Not enough card slots available for the videos the theme requested.\n");
abort();
}
- configure_card(card_index, video_inputs[video_card_index], CardType::FFMPEG_INPUT, /*output=*/nullptr);
+ configure_card(card_index, video_inputs[video_card_index], CardType::FFMPEG_INPUT, /*output=*/nullptr, /*is_srt_card=*/false);
video_inputs[video_card_index]->set_card_index(card_index);
}
num_video_inputs = video_inputs.size();
fprintf(stderr, "ERROR: Not enough card slots available for the HTML inputs the theme requested.\n");
abort();
}
- configure_card(card_index, html_inputs[html_card_index], CardType::CEF_INPUT, /*output=*/nullptr);
+ configure_card(card_index, html_inputs[html_card_index], CardType::CEF_INPUT, /*output=*/nullptr, /*is_srt_card=*/false);
html_inputs[html_card_index]->set_card_index(card_index);
}
num_html_inputs = html_inputs.size();
}
#endif
- for (unsigned card_index = 0; card_index < num_cards + num_video_inputs + num_html_inputs; ++card_index) {
+ for (unsigned card_index = 0; card_index < MAX_VIDEO_CARDS; ++card_index) {
cards[card_index].queue_length_policy.reset(card_index);
}
httpd.stop();
BMUSBCapture::stop_bm_thread();
- for (unsigned card_index = 0; card_index < num_cards + num_video_inputs + num_html_inputs; ++card_index) {
- cards[card_index].capture->stop_dequeue_thread();
+ for (unsigned card_index = 0; card_index < MAX_VIDEO_CARDS; ++card_index) {
+ if (cards[card_index].capture != nullptr) { // Active.
+ cards[card_index].capture->stop_dequeue_thread();
+ }
if (cards[card_index].output) {
cards[card_index].output->end_output();
cards[card_index].output.reset();
void Mixer::configure_card(unsigned card_index, CaptureInterface *capture, CardType card_type, DeckLinkOutput *output, bool is_srt_card)
{
- printf("Configuring card %d...\n", card_index);
+ bool is_active = capture != nullptr;
+ if (is_active) {
+ printf("Configuring card %d...\n", card_index);
+ } else {
+ assert(card_type == CardType::FAKE_CAPTURE);
+ }
CaptureCard *card = &cards[card_index];
if (card->capture != nullptr) {
pixel_format = PixelFormat_8BitYCbCr;
}
- card->capture->set_frame_callback(bind(&Mixer::bm_frame, this, card_index, _1, _2, _3, _4, _5, _6, _7));
- if (card->frame_allocator == nullptr) {
- card->frame_allocator.reset(new PBOFrameAllocator(pixel_format, 8 << 20, global_flags.width, global_flags.height, card_index, mjpeg_encoder.get())); // 8 MB.
- } else {
- // The format could have changed, but we cannot reset the allocator
- // and create a new one from scratch, since there may be allocated
- // frames from it that expect to call release_frame() on it.
- // Instead, ask the allocator to create new frames for us and discard
- // any old ones as they come back. This takes the mutex while
- // allocating, but nothing should really be sending frames in there
- // right now anyway (start_bm_capture() has not been called yet).
- card->frame_allocator->reconfigure(pixel_format, 8 << 20, global_flags.width, global_flags.height, card_index, mjpeg_encoder.get());
- }
- card->capture->set_video_frame_allocator(card->frame_allocator.get());
- if (card->surface == nullptr) {
- card->surface = create_surface_with_same_format(mixer_surface);
- }
- while (!card->new_frames.empty()) card->new_frames.pop_front();
- card->last_timecode = -1;
- card->capture->set_pixel_format(pixel_format);
- card->capture->configure_card();
-
- // NOTE: start_bm_capture() happens in thread_func().
+ if (is_active) {
+ card->capture->set_frame_callback(bind(&Mixer::bm_frame, this, card_index, _1, _2, _3, _4, _5, _6, _7));
+ if (card->frame_allocator == nullptr) {
+ card->frame_allocator.reset(new PBOFrameAllocator(pixel_format, 8 << 20, global_flags.width, global_flags.height, card_index, mjpeg_encoder.get())); // 8 MB.
+ } else {
+ // The format could have changed, but we cannot reset the allocator
+ // and create a new one from scratch, since there may be allocated
+ // frames from it that expect to call release_frame() on it.
+ // Instead, ask the allocator to create new frames for us and discard
+ // any old ones as they come back. This takes the mutex while
+ // allocating, but nothing should really be sending frames in there
+ // right now anyway (start_bm_capture() has not been called yet).
+ card->frame_allocator->reconfigure(pixel_format, 8 << 20, global_flags.width, global_flags.height, card_index, mjpeg_encoder.get());
+ }
+ card->capture->set_video_frame_allocator(card->frame_allocator.get());
+ if (card->surface == nullptr) {
+ card->surface = create_surface_with_same_format(mixer_surface);
+ }
+ while (!card->new_frames.empty()) card->new_frames.pop_front();
+ card->last_timecode = -1;
+ card->capture->set_pixel_format(pixel_format);
+ card->capture->configure_card();
+
+ // NOTE: start_bm_capture() happens in thread_func().
+ }
if (is_srt_card) {
assert(card_type == CardType::FFMPEG_INPUT);
}
DeviceSpec device;
- if (card_type == CardType::FFMPEG_INPUT && !is_srt_card) {
- device = DeviceSpec{InputSourceType::FFMPEG_VIDEO_INPUT, card_index - num_cards};
+ device = DeviceSpec{InputSourceType::CAPTURE_CARD, card_index};
+ audio_mixer->reset_resampler(device);
+ 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);
} else {
- device = DeviceSpec{InputSourceType::CAPTURE_CARD, card_index};
+ // Note: Keeps the previous name, if any.
+ char name[32];
+ 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->set_display_name(device, card->capture->get_description());
audio_mixer->trigger_state_changed_callback();
// Unregister old metrics, if any.
global_metrics.remove_if_exists("srt_receiver_delivery_delay_seconds", labels);
}
- // Register metrics.
- vector<pair<string, string>> labels;
- char card_name[64];
- snprintf(card_name, sizeof(card_name), "%d", card_index);
- labels.emplace_back("card", card_name);
+ if (is_active) {
+ // Register metrics.
+ vector<pair<string, string>> labels;
+ char card_name[64];
+ snprintf(card_name, sizeof(card_name), "%d", card_index);
+ labels.emplace_back("card", card_name);
- switch (card_type) {
- case CardType::LIVE_CARD:
- labels.emplace_back("cardtype", "live");
- break;
- case CardType::FAKE_CAPTURE:
- labels.emplace_back("cardtype", "fake");
- break;
- case CardType::FFMPEG_INPUT:
- if (is_srt_card) {
- labels.emplace_back("cardtype", "srt");
- } else {
- labels.emplace_back("cardtype", "ffmpeg");
+ switch (card_type) {
+ case CardType::LIVE_CARD:
+ labels.emplace_back("cardtype", "live");
+ break;
+ case CardType::FAKE_CAPTURE:
+ labels.emplace_back("cardtype", "fake");
+ break;
+ case CardType::FFMPEG_INPUT:
+ if (is_srt_card) {
+ labels.emplace_back("cardtype", "srt");
+ } else {
+ labels.emplace_back("cardtype", "ffmpeg");
+ }
+ break;
+ case CardType::CEF_INPUT:
+ labels.emplace_back("cardtype", "cef");
+ break;
+ default:
+ assert(false);
}
- break;
- case CardType::CEF_INPUT:
- labels.emplace_back("cardtype", "cef");
- break;
- default:
- assert(false);
- }
- card->jitter_history.register_metrics(labels);
- card->queue_length_policy.register_metrics(labels);
- global_metrics.add("input_received_frames", labels, &card->metric_input_received_frames);
- global_metrics.add("input_dropped_frames_jitter", labels, &card->metric_input_dropped_frames_jitter);
- global_metrics.add("input_dropped_frames_error", labels, &card->metric_input_dropped_frames_error);
- global_metrics.add("input_dropped_frames_resets", labels, &card->metric_input_resets);
- global_metrics.add("input_queue_length_frames", labels, &card->metric_input_queue_length_frames, Metrics::TYPE_GAUGE);
- global_metrics.add("input_queue_duped_frames", labels, &card->metric_input_duped_frames);
-
- global_metrics.add("input_has_signal_bool", labels, &card->metric_input_has_signal_bool, Metrics::TYPE_GAUGE);
- global_metrics.add("input_is_connected_bool", labels, &card->metric_input_is_connected_bool, Metrics::TYPE_GAUGE);
- global_metrics.add("input_interlaced_bool", labels, &card->metric_input_interlaced_bool, Metrics::TYPE_GAUGE);
- global_metrics.add("input_width_pixels", labels, &card->metric_input_width_pixels, Metrics::TYPE_GAUGE);
- global_metrics.add("input_height_pixels", labels, &card->metric_input_height_pixels, Metrics::TYPE_GAUGE);
- global_metrics.add("input_frame_rate_nom", labels, &card->metric_input_frame_rate_nom, Metrics::TYPE_GAUGE);
- global_metrics.add("input_frame_rate_den", labels, &card->metric_input_frame_rate_den, Metrics::TYPE_GAUGE);
- global_metrics.add("input_sample_rate_hz", labels, &card->metric_input_sample_rate_hz, Metrics::TYPE_GAUGE);
-
- if (is_srt_card) {
- // Global measurements (counters).
- global_metrics.add("srt_uptime_seconds", labels, &card->metric_srt_uptime_seconds);
- global_metrics.add("srt_send_duration_seconds", labels, &card->metric_srt_send_duration_seconds);
- global_metrics.add("srt_sent_bytes", labels, &card->metric_srt_sent_bytes);
- global_metrics.add("srt_received_bytes", labels, &card->metric_srt_received_bytes);
-
- vector<pair<string, string>> packet_labels = labels;
- packet_labels.emplace_back("type", "normal");
- global_metrics.add("srt_sent_packets", packet_labels, &card->metric_srt_sent_packets_normal);
- global_metrics.add("srt_received_packets", packet_labels, &card->metric_srt_received_packets_normal);
-
- packet_labels.back().second = "lost";
- global_metrics.add("srt_sent_packets", packet_labels, &card->metric_srt_sent_packets_lost);
- global_metrics.add("srt_received_packets", packet_labels, &card->metric_srt_received_packets_lost);
-
- packet_labels.back().second = "retransmitted";
- global_metrics.add("srt_sent_packets", packet_labels, &card->metric_srt_sent_packets_retransmitted);
- global_metrics.add("srt_sent_bytes", packet_labels, &card->metric_srt_sent_bytes_retransmitted);
-
- packet_labels.back().second = "ack";
- global_metrics.add("srt_sent_packets", packet_labels, &card->metric_srt_sent_packets_ack);
- global_metrics.add("srt_received_packets", packet_labels, &card->metric_srt_received_packets_ack);
+ card->jitter_history.register_metrics(labels);
+ card->queue_length_policy.register_metrics(labels);
+ global_metrics.add("input_received_frames", labels, &card->metric_input_received_frames);
+ global_metrics.add("input_dropped_frames_jitter", labels, &card->metric_input_dropped_frames_jitter);
+ global_metrics.add("input_dropped_frames_error", labels, &card->metric_input_dropped_frames_error);
+ global_metrics.add("input_dropped_frames_resets", labels, &card->metric_input_resets);
+ global_metrics.add("input_queue_length_frames", labels, &card->metric_input_queue_length_frames, Metrics::TYPE_GAUGE);
+ global_metrics.add("input_queue_duped_frames", labels, &card->metric_input_duped_frames);
+
+ global_metrics.add("input_has_signal_bool", labels, &card->metric_input_has_signal_bool, Metrics::TYPE_GAUGE);
+ global_metrics.add("input_is_connected_bool", labels, &card->metric_input_is_connected_bool, Metrics::TYPE_GAUGE);
+ global_metrics.add("input_interlaced_bool", labels, &card->metric_input_interlaced_bool, Metrics::TYPE_GAUGE);
+ global_metrics.add("input_width_pixels", labels, &card->metric_input_width_pixels, Metrics::TYPE_GAUGE);
+ global_metrics.add("input_height_pixels", labels, &card->metric_input_height_pixels, Metrics::TYPE_GAUGE);
+ global_metrics.add("input_frame_rate_nom", labels, &card->metric_input_frame_rate_nom, Metrics::TYPE_GAUGE);
+ global_metrics.add("input_frame_rate_den", labels, &card->metric_input_frame_rate_den, Metrics::TYPE_GAUGE);
+ global_metrics.add("input_sample_rate_hz", labels, &card->metric_input_sample_rate_hz, Metrics::TYPE_GAUGE);
- packet_labels.back().second = "nak";
- global_metrics.add("srt_sent_packets", packet_labels, &card->metric_srt_sent_packets_nak);
- global_metrics.add("srt_received_packets", packet_labels, &card->metric_srt_received_packets_nak);
-
- packet_labels.back().second = "dropped";
- global_metrics.add("srt_sent_packets", packet_labels, &card->metric_srt_sent_packets_dropped);
- global_metrics.add("srt_received_packets", packet_labels, &card->metric_srt_received_packets_dropped);
- global_metrics.add("srt_sent_bytes", packet_labels, &card->metric_srt_sent_bytes_dropped);
- global_metrics.add("srt_received_bytes", packet_labels, &card->metric_srt_received_bytes_dropped);
-
- packet_labels.back().second = "undecryptable";
- global_metrics.add("srt_received_packets", packet_labels, &card->metric_srt_received_packets_undecryptable);
- global_metrics.add("srt_received_bytes", packet_labels, &card->metric_srt_received_bytes_undecryptable);
-
- global_metrics.add("srt_filter_sent_extra_packets", labels, &card->metric_srt_filter_sent_packets);
- global_metrics.add("srt_filter_received_extra_packets", labels, &card->metric_srt_filter_received_extra_packets);
- global_metrics.add("srt_filter_received_rebuilt_packets", labels, &card->metric_srt_filter_received_rebuilt_packets);
- global_metrics.add("srt_filter_received_lost_packets", labels, &card->metric_srt_filter_received_lost_packets);
+ if (is_srt_card) {
+ // Global measurements (counters).
+ global_metrics.add("srt_uptime_seconds", labels, &card->metric_srt_uptime_seconds);
+ global_metrics.add("srt_send_duration_seconds", labels, &card->metric_srt_send_duration_seconds);
+ global_metrics.add("srt_sent_bytes", labels, &card->metric_srt_sent_bytes);
+ global_metrics.add("srt_received_bytes", labels, &card->metric_srt_received_bytes);
+
+ vector<pair<string, string>> packet_labels = labels;
+ packet_labels.emplace_back("type", "normal");
+ global_metrics.add("srt_sent_packets", packet_labels, &card->metric_srt_sent_packets_normal);
+ global_metrics.add("srt_received_packets", packet_labels, &card->metric_srt_received_packets_normal);
+
+ packet_labels.back().second = "lost";
+ global_metrics.add("srt_sent_packets", packet_labels, &card->metric_srt_sent_packets_lost);
+ global_metrics.add("srt_received_packets", packet_labels, &card->metric_srt_received_packets_lost);
+
+ packet_labels.back().second = "retransmitted";
+ global_metrics.add("srt_sent_packets", packet_labels, &card->metric_srt_sent_packets_retransmitted);
+ global_metrics.add("srt_sent_bytes", packet_labels, &card->metric_srt_sent_bytes_retransmitted);
+
+ packet_labels.back().second = "ack";
+ global_metrics.add("srt_sent_packets", packet_labels, &card->metric_srt_sent_packets_ack);
+ global_metrics.add("srt_received_packets", packet_labels, &card->metric_srt_received_packets_ack);
+
+ packet_labels.back().second = "nak";
+ global_metrics.add("srt_sent_packets", packet_labels, &card->metric_srt_sent_packets_nak);
+ global_metrics.add("srt_received_packets", packet_labels, &card->metric_srt_received_packets_nak);
+
+ packet_labels.back().second = "dropped";
+ global_metrics.add("srt_sent_packets", packet_labels, &card->metric_srt_sent_packets_dropped);
+ global_metrics.add("srt_received_packets", packet_labels, &card->metric_srt_received_packets_dropped);
+ global_metrics.add("srt_sent_bytes", packet_labels, &card->metric_srt_sent_bytes_dropped);
+ global_metrics.add("srt_received_bytes", packet_labels, &card->metric_srt_received_bytes_dropped);
+
+ packet_labels.back().second = "undecryptable";
+ global_metrics.add("srt_received_packets", packet_labels, &card->metric_srt_received_packets_undecryptable);
+ global_metrics.add("srt_received_bytes", packet_labels, &card->metric_srt_received_bytes_undecryptable);
+
+ global_metrics.add("srt_filter_sent_extra_packets", labels, &card->metric_srt_filter_sent_packets);
+ global_metrics.add("srt_filter_received_extra_packets", labels, &card->metric_srt_filter_received_extra_packets);
+ global_metrics.add("srt_filter_received_rebuilt_packets", labels, &card->metric_srt_filter_received_rebuilt_packets);
+ global_metrics.add("srt_filter_received_lost_packets", labels, &card->metric_srt_filter_received_lost_packets);
+
+ // Instant measurements (gauges).
+ global_metrics.add("srt_packet_sending_period_seconds", labels, &card->metric_srt_packet_sending_period_seconds, Metrics::TYPE_GAUGE);
+ global_metrics.add("srt_flow_window_packets", labels, &card->metric_srt_flow_window_packets, Metrics::TYPE_GAUGE);
+ global_metrics.add("srt_congestion_window_packets", labels, &card->metric_srt_congestion_window_packets, Metrics::TYPE_GAUGE);
+ global_metrics.add("srt_flight_size_packets", labels, &card->metric_srt_flight_size_packets, Metrics::TYPE_GAUGE);
+ global_metrics.add("srt_rtt_seconds", labels, &card->metric_srt_rtt_seconds, Metrics::TYPE_GAUGE);
+ global_metrics.add("srt_estimated_bandwidth_bits_per_second", labels, &card->metric_srt_estimated_bandwidth_bits_per_second, Metrics::TYPE_GAUGE);
+ global_metrics.add("srt_bandwidth_ceiling_bits_per_second", labels, &card->metric_srt_bandwidth_ceiling_bits_per_second, Metrics::TYPE_GAUGE);
+ global_metrics.add("srt_send_buffer_available_bytes", labels, &card->metric_srt_send_buffer_available_bytes, Metrics::TYPE_GAUGE);
+ global_metrics.add("srt_receive_buffer_available_bytes", labels, &card->metric_srt_receive_buffer_available_bytes, Metrics::TYPE_GAUGE);
+ global_metrics.add("srt_mss_bytes", labels, &card->metric_srt_mss_bytes, Metrics::TYPE_GAUGE);
+
+ global_metrics.add("srt_sender_unacked_packets", labels, &card->metric_srt_sender_unacked_packets, Metrics::TYPE_GAUGE);
+ global_metrics.add("srt_sender_unacked_bytes", labels, &card->metric_srt_sender_unacked_bytes, Metrics::TYPE_GAUGE);
+ global_metrics.add("srt_sender_unacked_timespan_seconds", labels, &card->metric_srt_sender_unacked_timespan_seconds, Metrics::TYPE_GAUGE);
+ global_metrics.add("srt_sender_delivery_delay_seconds", labels, &card->metric_srt_sender_delivery_delay_seconds, Metrics::TYPE_GAUGE);
+
+ global_metrics.add("srt_receiver_unacked_packets", labels, &card->metric_srt_receiver_unacked_packets, Metrics::TYPE_GAUGE);
+ global_metrics.add("srt_receiver_unacked_bytes", labels, &card->metric_srt_receiver_unacked_bytes, Metrics::TYPE_GAUGE);
+ global_metrics.add("srt_receiver_unacked_timespan_seconds", labels, &card->metric_srt_receiver_unacked_timespan_seconds, Metrics::TYPE_GAUGE);
+ global_metrics.add("srt_receiver_delivery_delay_seconds", labels, &card->metric_srt_receiver_delivery_delay_seconds, Metrics::TYPE_GAUGE);
+ }
- // Instant measurements (gauges).
- global_metrics.add("srt_packet_sending_period_seconds", labels, &card->metric_srt_packet_sending_period_seconds, Metrics::TYPE_GAUGE);
- global_metrics.add("srt_flow_window_packets", labels, &card->metric_srt_flow_window_packets, Metrics::TYPE_GAUGE);
- global_metrics.add("srt_congestion_window_packets", labels, &card->metric_srt_congestion_window_packets, Metrics::TYPE_GAUGE);
- global_metrics.add("srt_flight_size_packets", labels, &card->metric_srt_flight_size_packets, Metrics::TYPE_GAUGE);
- global_metrics.add("srt_rtt_seconds", labels, &card->metric_srt_rtt_seconds, Metrics::TYPE_GAUGE);
- global_metrics.add("srt_estimated_bandwidth_bits_per_second", labels, &card->metric_srt_estimated_bandwidth_bits_per_second, Metrics::TYPE_GAUGE);
- global_metrics.add("srt_bandwidth_ceiling_bits_per_second", labels, &card->metric_srt_bandwidth_ceiling_bits_per_second, Metrics::TYPE_GAUGE);
- global_metrics.add("srt_send_buffer_available_bytes", labels, &card->metric_srt_send_buffer_available_bytes, Metrics::TYPE_GAUGE);
- global_metrics.add("srt_receive_buffer_available_bytes", labels, &card->metric_srt_receive_buffer_available_bytes, Metrics::TYPE_GAUGE);
- global_metrics.add("srt_mss_bytes", labels, &card->metric_srt_mss_bytes, Metrics::TYPE_GAUGE);
-
- global_metrics.add("srt_sender_unacked_packets", labels, &card->metric_srt_sender_unacked_packets, Metrics::TYPE_GAUGE);
- global_metrics.add("srt_sender_unacked_bytes", labels, &card->metric_srt_sender_unacked_bytes, Metrics::TYPE_GAUGE);
- global_metrics.add("srt_sender_unacked_timespan_seconds", labels, &card->metric_srt_sender_unacked_timespan_seconds, Metrics::TYPE_GAUGE);
- global_metrics.add("srt_sender_delivery_delay_seconds", labels, &card->metric_srt_sender_delivery_delay_seconds, Metrics::TYPE_GAUGE);
-
- global_metrics.add("srt_receiver_unacked_packets", labels, &card->metric_srt_receiver_unacked_packets, Metrics::TYPE_GAUGE);
- global_metrics.add("srt_receiver_unacked_bytes", labels, &card->metric_srt_receiver_unacked_bytes, Metrics::TYPE_GAUGE);
- global_metrics.add("srt_receiver_unacked_timespan_seconds", labels, &card->metric_srt_receiver_unacked_timespan_seconds, Metrics::TYPE_GAUGE);
- global_metrics.add("srt_receiver_delivery_delay_seconds", labels, &card->metric_srt_receiver_delivery_delay_seconds, Metrics::TYPE_GAUGE);
- }
-
- card->labels = labels;
+ card->labels = labels;
+ } else {
+ card->labels.clear();
+ }
}
void Mixer::set_output_card_internal(int card_index)
lock.lock();
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());
+ configure_card(card_index, fake_capture, CardType::FAKE_CAPTURE, card->output.release(), /*is_srt_card=*/false);
card->queue_length_policy.reset(card_index);
card->capture->start_bm_capture();
desired_output_video_mode = output_video_mode = card->output->pick_video_mode(desired_output_video_mode);
}
}
-DeviceSpec card_index_to_device(unsigned card_index, unsigned num_cards)
-{
- if (card_index >= num_cards) {
- return DeviceSpec{InputSourceType::FFMPEG_VIDEO_INPUT, card_index - num_cards};
- } else {
- return DeviceSpec{InputSourceType::CAPTURE_CARD, card_index};
- }
-}
-
} // namespace
void Mixer::bm_frame(unsigned card_index, uint16_t timecode,
FrameAllocator::Frame video_frame, size_t video_offset, VideoFormat video_format,
FrameAllocator::Frame audio_frame, size_t audio_offset, AudioFormat audio_format)
{
- DeviceSpec device = card_index_to_device(card_index, num_cards);
+ DeviceSpec device{InputSourceType::CAPTURE_CARD, card_index};
CaptureCard *card = &cards[card_index];
++card->metric_input_received_frames;
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 && card->type != CardType::FFMPEG_INPUT) {
printf("%s: Dropping frame with implausible audio length (len=%d, offset=%d) [timecode=0x%04x video_len=%d video_offset=%d video_format=%x)\n",
- spec_to_string(device).c_str(), int(audio_frame.len), int(audio_offset),
+ description_for_card(card_index).c_str(), int(audio_frame.len), int(audio_offset),
timecode, int(video_frame.len), int(video_offset), video_format.id);
if (video_frame.owner) {
video_frame.owner->release_frame(video_frame);
if (dropped_frames > MAX_FPS * 2) {
fprintf(stderr, "%s lost more than two seconds (or time code jumping around; from 0x%04x to 0x%04x), resetting resampler\n",
- spec_to_string(device).c_str(), card->last_timecode, timecode);
+ description_for_card(card_index).c_str(), card->last_timecode, timecode);
audio_mixer->reset_resampler(device);
dropped_frames = 0;
++card->metric_input_resets;
} else if (dropped_frames > 0) {
// Insert silence as needed.
fprintf(stderr, "%s dropped %d frame(s) (before timecode 0x%04x), inserting silence.\n",
- spec_to_string(device).c_str(), dropped_frames, timecode);
+ description_for_card(card_index).c_str(), dropped_frames, timecode);
card->metric_input_dropped_frames_error += dropped_frames;
bool success;
video_frame.len - video_offset != expected_length) {
if (video_frame.len != 0) {
printf("%s: Dropping video frame with wrong length (%zu; expected %zu)\n",
- spec_to_string(device).c_str(), video_frame.len - video_offset, expected_length);
+ description_for_card(card_index).c_str(), video_frame.len - video_offset, expected_length);
}
if (video_frame.owner) {
video_frame.owner->release_frame(video_frame);
// Start the actual capture. (We don't want to do it before we're actually ready
// to process output frames.)
- for (unsigned card_index = 0; card_index < num_cards + num_video_inputs + num_html_inputs; ++card_index) {
- if (int(card_index) != output_card_index) {
+ for (unsigned card_index = 0; card_index < MAX_VIDEO_CARDS; ++card_index) {
+ if (int(card_index) != output_card_index && cards[card_index].capture != nullptr) {
cards[card_index].capture->start_bm_capture();
}
}
} else {
master_card_is_output = false;
master_card_index = theme->map_signal_to_card(master_clock_channel);
- assert(master_card_index < num_cards + num_video_inputs);
+ assert(master_card_index < MAX_VIDEO_CARDS);
}
handle_hotplugged_cards();
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);
stats_dropped_frames += output_frame_info.dropped_frames;
- for (unsigned card_index = 0; card_index < num_cards + num_video_inputs + num_html_inputs; ++card_index) {
- DeviceSpec device = card_index_to_device(card_index, num_cards);
+ for (unsigned card_index = 0; card_index < MAX_VIDEO_CARDS; ++card_index) {
if (card_index == master_card_index || !has_new_frame[card_index]) {
continue;
}
}
if (new_frames[card_index].dropped_frames > 0) {
printf("%s dropped %d frames before this\n",
- spec_to_string(device).c_str(), int(new_frames[card_index].dropped_frames));
+ description_for_card(card_index).c_str(), int(new_frames[card_index].dropped_frames));
}
}
continue;
}
- for (unsigned card_index = 0; card_index < num_cards + num_video_inputs + num_html_inputs; ++card_index) {
+ for (unsigned card_index = 0; card_index < MAX_VIDEO_CARDS; ++card_index) {
if (!has_new_frame[card_index] || new_frames[card_index].frame->len == 0)
continue;
goto start;
}
- for (unsigned card_index = 0; card_index < num_cards + num_video_inputs + num_html_inputs; ++card_index) {
+ for (unsigned card_index = 0; card_index < MAX_VIDEO_CARDS; ++card_index) {
CaptureCard *card = &cards[card_index];
if (card->new_frames.empty()) { // Starvation.
++card->metric_input_duped_frames;
output_jitter_history.frame_arrived(output_frame_info.frame_timestamp, output_frame_info.frame_duration, output_frame_info.dropped_frames);
}
- for (unsigned card_index = 0; card_index < num_cards + num_video_inputs + num_html_inputs; ++card_index) {
+ for (unsigned card_index = 0; card_index < MAX_VIDEO_CARDS; ++card_index) {
CaptureCard *card = &cards[card_index];
if (has_new_frame[card_index] &&
!input_card_is_master_clock(card_index, master_card_index) &&
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) {
+ for (unsigned card_index = 0; card_index < MAX_VIDEO_CARDS; ++card_index) {
CaptureCard *card = &cards[card_index];
- if (card->capture->get_disconnected()) {
+ 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);
+ 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();
}
}
+ // Count how many active cards we already have. Used below to check that we
+ // don't go past the max_cards limit set by the user. Note that (non-SRT) video
+ // and HTML “cards” don't count towards this limit.
+ int num_video_cards = 0;
+ for (unsigned card_index = 0; card_index < MAX_VIDEO_CARDS; ++card_index) {
+ CaptureCard *card = &cards[card_index];
+ if (card->type == CardType::LIVE_CARD || is_srt_card(card)) {
+ ++num_video_cards;
+ }
+ }
+
// Check for cards that have been connected since last frame.
vector<libusb_device *> hotplugged_cards_copy;
#ifdef HAVE_SRT
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) {
+ for (unsigned card_index = 0; card_index < MAX_VIDEO_CARDS; ++card_index) {
if (cards[card_index].is_fake_capture) {
free_card_index = card_index;
break;
}
}
- if (free_card_index == -1) {
+ if (free_card_index == -1 || num_video_cards >= global_flags.max_num_cards) {
fprintf(stderr, "New card plugged in, but no free slots -- ignoring.\n");
libusb_unref_device(new_dev);
} else {
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, CardType::LIVE_CARD, /*output=*/nullptr);
+ configure_card(free_card_index, capture, CardType::LIVE_CARD, /*output=*/nullptr, /*is_srt_card=*/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();
// same stream ID, if any exist -- and it multiple exist,
// take the one that disconnected the last.
int first_free_card_index = -1, last_matching_free_card_index = -1;
- for (unsigned card_index = 0; card_index < num_cards; ++card_index) {
+ for (unsigned card_index = 0; card_index < MAX_VIDEO_CARDS; ++card_index) {
CaptureCard *card = &cards[card_index];
if (!card->is_fake_capture) {
continue;
const int free_card_index = (last_matching_free_card_index != -1)
? last_matching_free_card_index : first_free_card_index;
- if (free_card_index == -1) {
+ if (free_card_index == -1 || num_video_cards >= global_flags.max_num_cards) {
if (stream_id.empty()) {
stream_id = "no name";
}
CaptureCard *card = &cards[free_card_index];
FFmpegCapture *capture = new FFmpegCapture(sock, stream_id);
capture->set_card_index(free_card_index);
- configure_card(free_card_index, capture, CardType::FFMPEG_INPUT, /*output=*/nullptr, /*override_card_as_live=*/true);
+ 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);
}
}
#endif
+
+ // Finally, newly forced-to-active fake capture cards.
+ for (unsigned card_index = 0; card_index < MAX_VIDEO_CARDS; ++card_index) {
+ CaptureCard *card = &cards[card_index];
+ 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->capture->start_bm_capture();
+ }
+ }
}
void Mixer::schedule_audio_resampling_tasks(unsigned dropped_frames, int num_samples_per_frame, int length_per_frame, bool is_preroll, steady_clock::time_point frame_timestamp)
{
// Resample the audio as needed, including from previously dropped frames.
- assert(num_cards > 0);
for (unsigned frame_num = 0; frame_num < dropped_frames + 1; ++frame_num) {
const bool dropped_frame = (frame_num != dropped_frames);
{
// Update Y'CbCr settings for all cards.
{
lock_guard<mutex> lock(card_mutex);
- for (unsigned card_index = 0; card_index < num_cards; ++card_index) {
+ for (unsigned card_index = 0; card_index < MAX_VIDEO_CARDS; ++card_index) {
YCbCrInterpretation *interpretation = &ycbcr_interpretation[card_index];
input_state.ycbcr_coefficients_auto[card_index] = interpretation->ycbcr_coefficients_auto;
input_state.ycbcr_coefficients[card_index] = interpretation->ycbcr_coefficients;
void Mixer::start_mode_scanning(unsigned card_index)
{
- assert(card_index < num_cards);
+ assert(card_index < MAX_VIDEO_CARDS);
+ if (cards[card_index].capture != nullptr) {
+ // Inactive card. Should never happen.
+ return;
+ }
if (is_mode_scanning[card_index]) {
return;
}
string Mixer::get_ffmpeg_filename(unsigned card_index) const
{
- assert(card_index >= num_cards && card_index < num_cards + num_video_inputs);
+ assert(card_index < MAX_VIDEO_CARDS);
+ assert(cards[card_index].type == CardType::FFMPEG_INPUT);
return ((FFmpegCapture *)(cards[card_index].capture.get()))->get_filename();
}
void Mixer::set_ffmpeg_filename(unsigned card_index, const string &filename) {
- assert(card_index >= num_cards && card_index < num_cards + num_video_inputs);
+ assert(card_index < MAX_VIDEO_CARDS);
+ assert(cards[card_index].type == CardType::FFMPEG_INPUT);
((FFmpegCapture *)(cards[card_index].capture.get()))->change_filename(filename);
}
}
#endif
+string Mixer::description_for_card(unsigned card_index)
+{
+ CaptureCard *card = &cards[card_index];
+ if (card->capture == nullptr) {
+ // Should never be called for inactive cards, but OK.
+ char buf[256];
+ snprintf(buf, sizeof(buf), "Inactive capture card %u", card_index);
+ return buf;
+ }
+ if (card->type != CardType::FFMPEG_INPUT) {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "Capture card %u (%s)", card_index, card->capture->get_description().c_str());
+ return buf;
+ }
+
+ // Number (non-SRT) FFmpeg inputs from zero, separately from the capture cards,
+ // since it's not too obvious for the user that they are “cards”.
+ unsigned ffmpeg_index = 0;
+ for (unsigned i = 0; i < card_index; ++i) {
+ CaptureCard *other_card = &cards[i];
+ if (other_card->type == CardType::FFMPEG_INPUT && !is_srt_card(other_card)) {
+ ++ffmpeg_index;
+ }
+ }
+ char buf[256];
+ snprintf(buf, sizeof(buf), "Video input %u (%s)", ffmpeg_index, card->capture->get_description().c_str());
+ return buf;
+}
+
+bool Mixer::is_srt_card(const Mixer::CaptureCard *card)
+{
+#ifdef HAVE_SRT
+ if (card->type == CardType::FFMPEG_INPUT) {
+ int srt_sock = static_cast<FFmpegCapture *>(card->capture.get())->get_srt_sock();
+ return srt_sock != -1;
+ }
+#endif
+ return false;
+}
+
mutex RefCountedGLsync::fence_lock;