Nageru is a live video mixer, based around the standard M/E workflow.
-Features (those marked with * are still in progress or not started yet):
+Features:
- High performance on modest hardware (720p60 with two input streams
on my Thinkpad X240[1]); almost all pixel processing is done on the GPU.
for intermediate calculations, dithered output.
- Proper sound support: Syncing of multiple unrelated sources through
- high-quality resampling, mixing (*), cue out for headphones,
- dynamic range compression, fixed EQ, level meters conforming to EBU R128.
+ high-quality resampling, freely selectable input, cue out for headphones,
+ dynamic range compression, simple EQ (lowpass), level meters conforming
+ to EBU R128.
- Theme engine encapsulating the design demands of each individual
event; Lua code is responsible for setting up the pixel processing
emit clicked();
}
-void GLWidget::show_context_menu(int signal_num, const QPoint &pos)
+void GLWidget::show_context_menu(unsigned signal_num, const QPoint &pos)
{
QPoint global_pos = mapToGlobal(pos);
QMenu menu;
+
+ // Add an action for each card.
QActionGroup group(&menu);
unsigned num_cards = global_mixer->get_num_cards();
action->setData(card_index);
menu.addAction(action);
}
+
+ menu.addSeparator();
+
+ // Add an audio source selector.
+ QAction *audio_source_action = new QAction("Use as audio source", &menu);
+ audio_source_action->setCheckable(true);
+ if (global_mixer->get_audio_source() == signal_num) {
+ audio_source_action->setChecked(true);
+ audio_source_action->setEnabled(false);
+ }
+ menu.addAction(audio_source_action);
+
QAction *selected_item = menu.exec(global_pos);
- if (selected_item) {
+ if (selected_item == audio_source_action) {
+ global_mixer->set_audio_source(signal_num);
+ } else if (selected_item != nullptr) {
unsigned card_index = selected_item->data().toInt(nullptr);
global_mixer->set_signal_mapping(signal_num, card_index);
}
void resolution_updated(Mixer::Output output);
private slots:
- void show_context_menu(int signal_num, const QPoint &pos);
+ void show_context_menu(unsigned signal_num, const QPoint &pos);
private:
Mixer::Output output;
{
vector<float> samples_card;
vector<float> samples_out;
+
+ // TODO: Allow mixing audio from several sources.
+ unsigned selected_audio_card = theme->map_signal(audio_source_channel);
+ assert(selected_audio_card < num_cards);
+
for (unsigned card_index = 0; card_index < num_cards; ++card_index) {
samples_card.resize(num_samples * 2);
{
printf("Card %d reported previous underrun.\n", card_index);
}
}
- // TODO: Allow using audio from the other card(s) as well.
- if (card_index == 0) {
+ if (card_index == selected_audio_card) {
samples_out = move(samples_card);
}
}
return theme->map_signal(channel);
}
+ unsigned get_audio_source() const
+ {
+ return audio_source_channel;
+ }
+
+ void set_audio_source(unsigned channel)
+ {
+ audio_source_channel = channel;
+ }
+
void set_signal_mapping(int signal, int card)
{
return theme->set_signal_mapping(signal, card);
QSurface *mixer_surface, *h264_encoder_surface;
std::unique_ptr<movit::ResourcePool> resource_pool;
std::unique_ptr<Theme> theme;
+ std::atomic<unsigned> audio_source_channel{0};
std::unique_ptr<movit::EffectChain> display_chain;
GLuint cbcr_program_num; // Owned by <resource_pool>.
std::unique_ptr<H264Encoder> h264_encoder;