From e5b9eff03d96d79c93a33d354b51c1afe2f44d59 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Wed, 28 Sep 2016 20:08:30 +0200 Subject: [PATCH] Reenable simple audio. Now that most of the multichannel work has been done, we can put the wraps back on and reenable the simple audio model from 1.3.x, which is less flexible but also easier for the casual user. Users can choose between the old model (simple) and the new (multichannel) through a UI toggle, or with a command-line flag; they map to the same processing code. --- audio_mixer.cpp | 61 +++++++++++++++---- audio_mixer.h | 23 +++++++ flags.cpp | 10 +++- flags.h | 1 + glwidget.cpp | 20 ++++--- mainwindow.cpp | 141 +++++++++++++++++++++++++++++++++---------- mainwindow.h | 3 + mixer.h | 10 ---- ui_mainwindow.ui | 152 ++++++++++++++++++++++++++++++----------------- 9 files changed, 302 insertions(+), 119 deletions(-) diff --git a/audio_mixer.cpp b/audio_mixer.cpp index a0628fe..506e78d 100644 --- a/audio_mixer.cpp +++ b/audio_mixer.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #ifdef __SSE__ #include #endif @@ -179,8 +180,9 @@ AudioMixer::AudioMixer(unsigned num_cards) set_final_makeup_gain_auto(global_flags.final_makeup_gain_auto); alsa_pool.init(); - InputMapping new_input_mapping; if (!global_flags.input_mapping_filename.empty()) { + current_mapping_mode = MappingMode::MULTICHANNEL; + InputMapping new_input_mapping; if (!load_input_mapping_from_file(get_devices(), global_flags.input_mapping_filename, &new_input_mapping)) { @@ -188,18 +190,13 @@ AudioMixer::AudioMixer(unsigned num_cards) global_flags.input_mapping_filename.c_str()); exit(1); } + set_input_mapping(new_input_mapping); } else { - // Generate a very simple, default input mapping. - InputMapping::Bus input; - input.name = "Main"; - input.device.type = InputSourceType::CAPTURE_CARD; - input.device.index = 0; - input.source_channel[0] = 0; - input.source_channel[1] = 1; - - new_input_mapping.buses.push_back(input); + set_simple_input(/*card_index=*/0); + if (global_flags.multichannel_mapping_mode) { + current_mapping_mode = MappingMode::MULTICHANNEL; + } } - set_input_mapping(new_input_mapping); r128.init(2, OUTPUT_FREQUENCY); r128.integr_start(); @@ -888,10 +885,52 @@ void AudioMixer::serialize_device(DeviceSpec device_spec, DeviceSpecProto *devic } } +void AudioMixer::set_simple_input(unsigned card_index) +{ + InputMapping new_input_mapping; + InputMapping::Bus input; + input.name = "Main"; + input.device.type = InputSourceType::CAPTURE_CARD; + input.device.index = card_index; + input.source_channel[0] = 0; + input.source_channel[1] = 1; + + new_input_mapping.buses.push_back(input); + + lock_guard lock(audio_mutex); + current_mapping_mode = MappingMode::SIMPLE; + set_input_mapping_lock_held(new_input_mapping); + fader_volume_db[0] = 0.0f; +} + +unsigned AudioMixer::get_simple_input() const +{ + lock_guard lock(audio_mutex); + if (input_mapping.buses.size() == 1 && + input_mapping.buses[0].device.type == InputSourceType::CAPTURE_CARD && + input_mapping.buses[0].source_channel[0] == 0 && + input_mapping.buses[0].source_channel[1] == 1) { + return input_mapping.buses[0].device.index; + } else { + return numeric_limits::max(); + } +} + void AudioMixer::set_input_mapping(const InputMapping &new_input_mapping) { lock_guard lock(audio_mutex); + set_input_mapping_lock_held(new_input_mapping); + current_mapping_mode = MappingMode::MULTICHANNEL; +} +AudioMixer::MappingMode AudioMixer::get_mapping_mode() const +{ + lock_guard lock(audio_mutex); + return current_mapping_mode; +} + +void AudioMixer::set_input_mapping_lock_held(const InputMapping &new_input_mapping) +{ map> interesting_channels; for (const InputMapping::Bus &bus : new_input_mapping.buses) { if (bus.device.type == InputSourceType::CAPTURE_CARD || diff --git a/audio_mixer.h b/audio_mixer.h index 1570d26..789cd1d 100644 --- a/audio_mixer.h +++ b/audio_mixer.h @@ -90,7 +90,28 @@ public: // Note: The card should be held (currently this isn't enforced, though). void serialize_device(DeviceSpec device_spec, DeviceSpecProto *device_spec_proto); + enum class MappingMode { + // A single bus, only from a video card (no ALSA devices), + // only channel 1 and 2, locked to +0 dB. Note that this is + // only an UI abstraction around exactly the same audio code + // as MULTICHANNEL; it's just less flexible. + SIMPLE, + + // Full, arbitrary mappings. + MULTICHANNEL + }; + + // Automatically sets mapping mode to MappingMode::SIMPLE. + void set_simple_input(unsigned card_index); + + // If mapping mode is not representable as a MappingMode::SIMPLE type + // mapping, returns numeric_limits::max(). + unsigned get_simple_input() const; + + // Implicitly sets mapping mode to MappingMode::MULTICHANNEL. void set_input_mapping(const InputMapping &input_mapping); + + MappingMode get_mapping_mode() const; InputMapping get_input_mapping() const; void set_locut_cutoff(float cutoff_hz) @@ -296,6 +317,7 @@ private: void measure_bus_levels(unsigned bus_index, const std::vector &left, const std::vector &right); void send_audio_level_callback(); std::vector get_active_devices() const; + void set_input_mapping_lock_held(const InputMapping &input_mapping); unsigned num_cards; @@ -339,6 +361,7 @@ private: double final_makeup_gain = 1.0; // Under compressor_mutex. Read/write by the user. Note: Not in dB, we want the numeric precision so that we can change it slowly. bool final_makeup_gain_auto = true; // Under compressor_mutex. + MappingMode current_mapping_mode; // Under audio_mutex. InputMapping input_mapping; // Under audio_mutex. std::atomic fader_volume_db[MAX_BUSES] {{ 0.0f }}; float last_fader_volume_db[MAX_BUSES] { 0.0f }; // Under audio_mutex. diff --git a/flags.cpp b/flags.cpp index 996107b..b22d025 100644 --- a/flags.cpp +++ b/flags.cpp @@ -13,7 +13,8 @@ Flags global_flags; // Long options that have no corresponding short option. enum LongOption { - OPTION_FAKE_CARDS_AUDIO = 1000, + OPTION_MULTICHANNEL = 1000, + OPTION_FAKE_CARDS_AUDIO, OPTION_HTTP_UNCOMPRESSED_VIDEO, OPTION_HTTP_X264_VIDEO, OPTION_X264_PRESET, @@ -55,7 +56,8 @@ void usage() fprintf(stderr, " -v, --va-display=SPEC VA-API device for H.264 encoding\n"); fprintf(stderr, " ($DISPLAY spec or /dev/dri/render* path)\n"); fprintf(stderr, " -m, --map-signal=SIGNAL,CARD set a default card mapping (can be given multiple times)\n"); - fprintf(stderr, " -M, --input-mapping=FILE start with the given audio input mapping\n"); + fprintf(stderr, " -M, --input-mapping=FILE start with the given audio input mapping (implies --multichannel)\n"); + fprintf(stderr, " --multichannel start in multichannel audio mapping mode\n"); fprintf(stderr, " --fake-cards-audio make fake (disconnected) cards output a simple tone\n"); fprintf(stderr, " --http-uncompressed-video send uncompressed NV12 video to HTTP clients\n"); fprintf(stderr, " --http-x264-video send x264-compressed video to HTTP clients\n"); @@ -103,6 +105,7 @@ void parse_flags(int argc, char * const argv[]) { "map-signal", required_argument, 0, 'm' }, { "input-mapping", required_argument, 0, 'M' }, { "va-display", required_argument, 0, 'v' }, + { "multichannel", no_argument, 0, OPTION_MULTICHANNEL }, { "fake-cards-audio", no_argument, 0, OPTION_FAKE_CARDS_AUDIO }, { "http-uncompressed-video", no_argument, 0, OPTION_HTTP_UNCOMPRESSED_VIDEO }, { "http-x264-video", no_argument, 0, OPTION_HTTP_X264_VIDEO }, @@ -172,6 +175,9 @@ void parse_flags(int argc, char * const argv[]) case 'M': global_flags.input_mapping_filename = optarg; break; + case OPTION_MULTICHANNEL: + global_flags.multichannel_mapping_mode = true; + break; case 'v': global_flags.va_display = optarg; break; diff --git a/flags.h b/flags.h index 8e68a66..5fed2a8 100644 --- a/flags.h +++ b/flags.h @@ -36,6 +36,7 @@ struct Flags { std::vector x264_extra_param; // In “key[,value]” format. bool enable_alsa_output = true; std::map default_stream_mapping; + bool multichannel_mapping_mode = false; // Implicitly true if input_mapping_filename is nonempty. std::string input_mapping_filename; // Empty for none. }; extern Flags global_flags; diff --git a/glwidget.cpp b/glwidget.cpp index ec43353..9d5e02c 100644 --- a/glwidget.cpp +++ b/glwidget.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -224,13 +225,16 @@ void GLWidget::show_context_menu(unsigned signal_num, const QPoint &pos) // --- End of card-dependent choices --- // 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); + QAction *audio_source_action = nullptr; + if (global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::SIMPLE) { + audio_source_action = new QAction("Use as audio source", &menu); + audio_source_action->setCheckable(true); + if (global_audio_mixer->get_simple_input() == signal_num) { + audio_source_action->setChecked(true); + audio_source_action->setEnabled(false); + } + menu.addAction(audio_source_action); } - menu.addAction(audio_source_action); // And a master clock selector. QAction *master_clock_action = new QAction("Use as master clock", &menu); @@ -243,8 +247,8 @@ void GLWidget::show_context_menu(unsigned signal_num, const QPoint &pos) // Show the menu and look at the result. QAction *selected_item = menu.exec(global_pos); - if (selected_item == audio_source_action) { - global_mixer->set_audio_source(signal_num); + if (audio_source_action != nullptr && selected_item == audio_source_action) { + global_audio_mixer->set_simple_input(signal_num); } else if (selected_item == master_clock_action) { global_mixer->set_master_clock(signal_num); } else if (selected_item != nullptr) { diff --git a/mainwindow.cpp b/mainwindow.cpp index e62594d..9e4ad0d 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -153,6 +154,10 @@ MainWindow::MainWindow() disk_free_label->setStyleSheet("QLabel {padding-right: 5px;}"); ui->menuBar->setCornerWidget(disk_free_label); + QActionGroup *audio_mapping_group = new QActionGroup(this); + ui->simple_audio_mode->setActionGroup(audio_mapping_group); + ui->multichannel_audio_mode->setActionGroup(audio_mapping_group); + ui->me_live->set_output(Mixer::OUTPUT_LIVE); ui->me_preview->set_output(Mixer::OUTPUT_PREVIEW); @@ -160,6 +165,8 @@ MainWindow::MainWindow() connect(ui->cut_action, &QAction::triggered, this, &MainWindow::cut_triggered); connect(ui->exit_action, &QAction::triggered, this, &MainWindow::exit_triggered); connect(ui->about_action, &QAction::triggered, this, &MainWindow::about_triggered); + connect(ui->simple_audio_mode, &QAction::triggered, this, &MainWindow::simple_audio_mode_triggered); + connect(ui->multichannel_audio_mode, &QAction::triggered, this, &MainWindow::multichannel_audio_mode_triggered); connect(ui->input_mapping_action, &QAction::triggered, this, &MainWindow::input_mapping_triggered); if (global_flags.x264_video_to_http) { @@ -191,7 +198,9 @@ MainWindow::MainWindow() // And bind the same to PgUp/PgDown. auto switch_page = [this]{ - ui->audio_views->setCurrentIndex(1 - ui->audio_views->currentIndex()); + if (global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::MULTICHANNEL) { + ui->audio_views->setCurrentIndex(1 - ui->audio_views->currentIndex()); + } }; connect(new QShortcut(QKeySequence::MoveToNextPage, this), &QShortcut::activated, switch_page); connect(new QShortcut(QKeySequence::MoveToPreviousPage, this), &QShortcut::activated, switch_page); @@ -240,8 +249,6 @@ void MainWindow::mixer_created(Mixer *mixer) connect(ui_display->wb_button, &QPushButton::clicked, bind(&MainWindow::wb_button_clicked, this, i)); } - setup_audio_miniview(); - setup_audio_expanded_view(); global_audio_mixer->set_state_changed_callback(bind(&MainWindow::audio_state_changed, this)); slave_knob(ui->locut_cutoff_knob, ui->locut_cutoff_knob_2); @@ -250,42 +257,38 @@ void MainWindow::mixer_created(Mixer *mixer) slave_checkbox(ui->makeup_gain_auto_checkbox, ui->makeup_gain_auto_checkbox_2); slave_checkbox(ui->limiter_enabled, ui->limiter_enabled_2); + reset_audio_mapping_ui(); + // TODO: Fetch all of the values these for completeness, // not just the enable knobs implied by flags. -#if 0 - // TODO: Reenable for simple audio. - ui->locut_enabled->setChecked(global_audio_mixer->get_locut_enabled(0)); + ui->limiter_enabled->setChecked(global_audio_mixer->get_limiter_enabled()); + ui->makeup_gain_auto_checkbox->setChecked(global_audio_mixer->get_final_makeup_gain_auto()); + + // Controls used only for simple audio fetch their state from the first bus. + constexpr unsigned simple_bus_index = 0; + if (global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::SIMPLE) { + ui->locut_enabled->setChecked(global_audio_mixer->get_locut_enabled(simple_bus_index)); + ui->gainstaging_knob->setValue(global_audio_mixer->get_gain_staging_db(simple_bus_index)); + ui->gainstaging_auto_checkbox->setChecked(global_audio_mixer->get_gain_staging_auto(simple_bus_index)); + ui->compressor_enabled->setChecked(global_audio_mixer->get_compressor_enabled(simple_bus_index)); + ui->compressor_threshold_db_display->setText( + QString::fromStdString(format_db(mixer->get_audio_mixer()->get_compressor_threshold_dbfs(simple_bus_index), DB_WITH_SIGN))); + } connect(ui->locut_enabled, &QCheckBox::stateChanged, [this](int state){ - global_audio_mixer->set_locut_enabled(0, state == Qt::Checked); + global_audio_mixer->set_locut_enabled(simple_bus_index, state == Qt::Checked); }); - ui->gainstaging_knob->setValue(global_audio_mixer->get_gain_staging_db()); - ui->gainstaging_auto_checkbox->setChecked(global_audio_mixer->get_gain_staging_auto()); - ui->compressor_enabled->setChecked(global_audio_mixer->get_compressor_enabled()); - connect(ui->gainstaging_knob, &QAbstractSlider::valueChanged, this, &MainWindow::gain_staging_knob_changed); - connect(ui->gainstaging_auto_checkbox, &QCheckBox::stateChanged, [this](int state){ - global_audio_mixer->set_gain_staging_auto(state == Qt::Checked); + connect(ui->gainstaging_knob, &QAbstractSlider::valueChanged, + bind(&MainWindow::gain_staging_knob_changed, this, simple_bus_index, _1)); + connect(ui->gainstaging_auto_checkbox, &QCheckBox::stateChanged, [this, simple_bus_index](int state){ + global_audio_mixer->set_gain_staging_auto(simple_bus_index, state == Qt::Checked); }); - ui->compressor_threshold_db_display->setText( - QString::fromStdString(format_db(mixer->get_audio_mixer()->get_compressor_threshold_dbfs(), DB_WITH_SIGN))); - ui->compressor_threshold_db_display->setText(buf); - connect(ui->compressor_threshold_knob, &QDial::valueChanged, this, &MainWindow::compressor_threshold_knob_changed); - connect(ui->compressor_enabled, &QCheckBox::stateChanged, [this](int state){ - global_audio_mixer->set_compressor_enabled(state == Qt::Checked); + connect(ui->compressor_threshold_knob, &QDial::valueChanged, + bind(&MainWindow::compressor_threshold_knob_changed, this, simple_bus_index, _1)); + connect(ui->compressor_enabled, &QCheckBox::stateChanged, [this, simple_bus_index](int state){ + global_audio_mixer->set_compressor_enabled(simple_bus_index, state == Qt::Checked); }); -#else - ui->locut_enabled->setVisible(false); - ui->gainstaging_label->setVisible(false); - ui->gainstaging_knob->setVisible(false); - ui->gainstaging_db_display->setVisible(false); - ui->gainstaging_auto_checkbox->setVisible(false); - ui->compressor_threshold_label->setVisible(false); - ui->compressor_threshold_knob->setVisible(false); - ui->compressor_threshold_db_display->setVisible(false); - ui->compressor_enabled->setVisible(false); -#endif - ui->limiter_enabled->setChecked(global_audio_mixer->get_limiter_enabled()); - ui->makeup_gain_auto_checkbox->setChecked(global_audio_mixer->get_final_makeup_gain_auto()); + // Global mastering controls. QString limiter_threshold_label( QString::fromStdString(format_db(mixer->get_audio_mixer()->get_limiter_threshold_dbfs(), DB_WITH_SIGN))); ui->limiter_threshold_db_display->setText(limiter_threshold_label); @@ -320,6 +323,33 @@ void MainWindow::mixer_created(Mixer *mixer) sigaction(SIGUSR1, &act, nullptr); } +void MainWindow::reset_audio_mapping_ui() +{ + bool simple = (global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::SIMPLE); + + ui->simple_audio_mode->setChecked(simple); + ui->multichannel_audio_mode->setChecked(!simple); + ui->input_mapping_action->setEnabled(!simple); + + ui->locut_enabled->setVisible(simple); + ui->gainstaging_label->setVisible(simple); + ui->gainstaging_knob->setVisible(simple); + ui->gainstaging_db_display->setVisible(simple); + ui->gainstaging_auto_checkbox->setVisible(simple); + ui->compressor_threshold_label->setVisible(simple); + ui->compressor_threshold_knob->setVisible(simple); + ui->compressor_threshold_db_display->setVisible(simple); + ui->compressor_enabled->setVisible(simple); + + setup_audio_miniview(); + setup_audio_expanded_view(); + + if (simple) { + ui->audio_views->setCurrentIndex(0); + } + ui->compact_header->setVisible(!simple); +} + void MainWindow::setup_audio_miniview() { // Remove any existing channels. @@ -329,6 +359,10 @@ void MainWindow::setup_audio_miniview() } audio_miniviews.clear(); + if (global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::SIMPLE) { + return; + } + // Set up brand new ones from the input mapping. InputMapping mapping = global_audio_mixer->get_input_mapping(); audio_miniviews.resize(mapping.buses.size()); @@ -368,6 +402,10 @@ void MainWindow::setup_audio_expanded_view() } audio_expanded_views.clear(); + if (global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::SIMPLE) { + return; + } + // Set up brand new ones from the input mapping. InputMapping mapping = global_audio_mixer->get_input_mapping(); audio_expanded_views.resize(mapping.buses.size()); @@ -468,6 +506,43 @@ void MainWindow::about_triggered() AboutDialog().exec(); } +void MainWindow::simple_audio_mode_triggered() +{ + if (global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::SIMPLE) { + return; + } + unsigned card_index = global_audio_mixer->get_simple_input(); + if (card_index == numeric_limits::max()) { + QMessageBox::StandardButton reply = + QMessageBox::question(this, + "Mapping too complex", + "The current audio mapping is too complicated to be representable in simple mode, " + "and will be discarded if you proceed. Really go to simple audio mode?", + QMessageBox::Yes | QMessageBox::No); + if (reply == QMessageBox::No) { + ui->simple_audio_mode->setChecked(false); + ui->multichannel_audio_mode->setChecked(true); + return; + } + card_index = 0; + } + global_audio_mixer->set_simple_input(/*card_index=*/card_index); + reset_audio_mapping_ui(); +} + +void MainWindow::multichannel_audio_mode_triggered() +{ + if (global_audio_mixer->get_mapping_mode() == AudioMixer::MappingMode::MULTICHANNEL) { + return; + } + + // Take the generated input mapping from the simple input, + // and set it as a normal multichannel mapping, which causes + // the mode to go to multichannel. + global_audio_mixer->set_input_mapping(global_audio_mixer->get_input_mapping()); + reset_audio_mapping_ui(); +} + void MainWindow::input_mapping_triggered() { if (InputMappingDialog().exec() == QDialog::Accepted) { @@ -714,7 +789,7 @@ void MainWindow::relayout() remaining_height -= ui->vertical_layout->spacing(); // The label above the audio strip. - double compact_label_height = ui->compact_label->geometry().height() + + double compact_label_height = ui->compact_label->minimumHeight() + ui->compact_audio_layout->spacing(); remaining_height -= compact_label_height; diff --git a/mainwindow.h b/mainwindow.h index 374bd49..77742bf 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -40,6 +40,8 @@ public slots: void x264_bitrate_triggered(); void exit_triggered(); void about_triggered(); + void simple_audio_mode_triggered(); + void multichannel_audio_mode_triggered(); void input_mapping_triggered(); void transition_clicked(int transition_number); void channel_clicked(int channel_number); @@ -58,6 +60,7 @@ public slots: void relayout(); private: + void reset_audio_mapping_ui(); void setup_audio_miniview(); void setup_audio_expanded_view(); bool eventFilter(QObject *watched, QEvent *event) override; diff --git a/mixer.h b/mixer.h index f5e72e4..39b259b 100644 --- a/mixer.h +++ b/mixer.h @@ -199,16 +199,6 @@ public: return theme->map_signal(channel); } - unsigned get_audio_source() const - { - return audio_source_channel; - } - - void set_audio_source(unsigned channel) - { - audio_source_channel = channel; - } - unsigned get_master_clock() const { return master_clock_channel; diff --git a/ui_mainwindow.ui b/ui_mainwindow.ui index 256390a..67bf75c 100644 --- a/ui_mainwindow.ui +++ b/ui_mainwindow.ui @@ -539,62 +539,82 @@ 0 - - - 0 + + + + 8 + 0 + - - - - Compact audio view - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - true - - - - 15 - 15 - - - - ... - - - true - - - Qt::LeftArrow - - - - - - - - 15 - 15 - - - - ... - - - true - - - Qt::RightArrow - - - - + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Compact audio view + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + true + + + + 15 + 15 + + + + ... + + + true + + + Qt::LeftArrow + + + + + + + + 15 + 15 + + + + ... + + + true + + + Qt::RightArrow + + + + + @@ -1311,6 +1331,9 @@ &Audio + + + @@ -1342,6 +1365,25 @@ &Input mapping… + + + true + + + true + + + Simple + + + + + true + + + Multichannel + + -- 2.39.2