Commit a5c00441 authored by Jean-Baptiste Mardelle's avatar Jean-Baptiste Mardelle
Browse files

WIP: improved multistream audio workflow

- Allow selecting multiple streams
- Allow renaming streams from clip properties panel
parent 425cce14
Pipeline #19962 passed with stage
in 9 minutes and 30 seconds
......@@ -2373,7 +2373,7 @@ void Bin::showClipProperties(const std::shared_ptr<ProjectClip> &clip, bool forc
}
m_propertiesPanel->setProperty("clipId", clip->AbstractProjectItem::clipId());
// Setup timeline targets
emit setupTargets(clip->hasVideo(), clip->audioStreams());
emit setupTargets(clip->hasVideo(), clip->activeStreams());
auto *lay = static_cast<QVBoxLayout *>(m_propertiesPanel->layout());
if (lay == nullptr) {
lay = new QVBoxLayout(m_propertiesPanel);
......@@ -2420,6 +2420,17 @@ void Bin::reloadMonitorStreamIfActive(const QString &id)
}
}
void Bin::updateTargets(const QString &id)
{
if (m_monitor->activeClipId() != id) {
return;
}
std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(id);
if (clip) {
emit setupTargets(clip->hasVideo(), clip->activeStreams());
}
}
QStringList Bin::getBinFolderClipIds(const QString &id) const
{
QStringList ids;
......
......@@ -221,6 +221,8 @@ public:
void reloadMonitorIfActive(const QString &id);
/** @brief refresh monitor stream selector */
void reloadMonitorStreamIfActive(const QString &id);
/** @brief Update timeline targets according to selected audio streams */
void updateTargets(const QString &id);
void doMoveClip(const QString &id, const QString &newParentId);
void doMoveFolder(const QString &id, const QString &newParentId);
......
......@@ -983,7 +983,7 @@ void ProjectClip::setProperties(const QMap<QString, QString> &properties, bool r
QStringLiteral("force_colorspace"), QStringLiteral("force_tff"), QStringLiteral("force_progressive"), QStringLiteral("video_delay")
};
QStringList forceReloadProperties{QStringLiteral("autorotate"), QStringLiteral("templatetext"), QStringLiteral("resource"),
QStringLiteral("force_fps"), QStringLiteral("set.test_image"), QStringLiteral("set.test_audio"),
QStringLiteral("force_fps"), QStringLiteral("set.test_image"),
QStringLiteral("video_index")};
QStringList keys{QStringLiteral("luma_duration"), QStringLiteral("luma_file"), QStringLiteral("fade"), QStringLiteral("ttl"),
QStringLiteral("softness"), QStringLiteral("crop"), QStringLiteral("animation")};
......@@ -1102,6 +1102,15 @@ void ProjectClip::setProperties(const QMap<QString, QString> &properties, bool r
updateTimelineClips(updateRoles);
}
} else {
if (properties.contains(QStringLiteral("kdenlive:active_streams")) && m_audioInfo) {
// Clip is a multi audio stream and currently in clip monitor, update target tracks
m_audioInfo->updateActiveStreams(properties.value(QStringLiteral("kdenlive:active_streams")));
pCore->bin()->updateTargets(clipId());
if (!audioStreamChanged) {
pCore->bin()->reloadMonitorStreamIfActive(clipId());
refreshPanel = true;
}
}
if (audioStreamChanged) {
refreshAudioInfo();
audioThumbReady();
......@@ -1564,3 +1573,16 @@ void ProjectClip::setClipStatus(AbstractProjectItem::CLIPSTATUS status)
AbstractProjectItem::IconOverlay);
}
}
void ProjectClip::renameAudioStream(int id, QString name)
{
if (m_audioInfo) {
m_audioInfo->renameStream(id, name);
QString prop = QString("kdenlive:streamname.%1").arg(id);
m_masterProducer->set(prop.toUtf8().constData(), name.toUtf8().constData());
if (m_audioInfo->activeStreams().keys().contains(id)) {
pCore->bin()->updateTargets(clipId());
}
pCore->bin()->reloadMonitorStreamIfActive(clipId());
}
}
......@@ -230,6 +230,9 @@ public:
*/
int getAudioStreamFfmpegIndex(int mltStream);
void setClipStatus(AbstractProjectItem::CLIPSTATUS status) override;
/** @brief Rename an audio stream for this clip
*/
void renameAudioStream(int id, QString name) override;
protected:
friend class ClipModel;
......
......@@ -33,35 +33,45 @@ AudioStreamInfo::AudioStreamInfo(const std::shared_ptr<Mlt::Producer> &producer,
memset(property, 0, 200);
snprintf(property, sizeof(property), "meta.media.%d.codec.channels", ix);
int chan = producer->get_int(property);
QString channelDescription = QString("%1|").arg(streamIndex++);
switch (chan) {
case 1:
channelDescription.append(i18n("Mono "));
break;
case 2:
channelDescription.append(i18n("Stereo "));
break;
default:
channelDescription.append(i18n("%1 channels ", chan));
break;
}
// Frequency
memset(property, 0, 200);
snprintf(property, sizeof(property), "meta.media.%d.codec.sample_rate", ix);
QString frequency(producer->get(property));
if (frequency.endsWith(QLatin1String("000"))) {
frequency.chop(3);
frequency.append(i18n("kHz "));
snprintf(property, sizeof(property), "kdenlive:streamname.%d", ix);
QString channelDescription = producer->get(property);
if (channelDescription.isEmpty()) {
channelDescription = QString("%1|").arg(streamIndex++);
switch (chan) {
case 1:
channelDescription.append(i18n("Mono "));
break;
case 2:
channelDescription.append(i18n("Stereo "));
break;
default:
channelDescription.append(i18n("%1 channels ", chan));
break;
}
// Frequency
memset(property, 0, 200);
snprintf(property, sizeof(property), "meta.media.%d.codec.sample_rate", ix);
QString frequency(producer->get(property));
if (frequency.endsWith(QLatin1String("000"))) {
frequency.chop(3);
frequency.append(i18n("kHz "));
} else {
frequency.append(i18n("Hz "));
}
channelDescription.append(frequency);
memset(property, 0, 200);
snprintf(property, sizeof(property), "meta.media.%d.codec.name", ix);
channelDescription.append(producer->get(property));
} else {
frequency.append(i18n("Hz "));
streamIndex++;
}
channelDescription.append(frequency);
memset(property, 0, 200);
snprintf(property, sizeof(property), "meta.media.%d.codec.name", ix);
channelDescription.append(producer->get(property));
m_audioStreams.insert(ix, channelDescription);
}
}
QString active = producer->get("kdenlive:active_streams");
updateActiveStreams(active);
if (audioStreamIndex > -1) {
QByteArray key;
key = QStringLiteral("meta.media.%1.codec.sample_fmt").arg(audioStreamIndex).toLocal8Bit();
......@@ -75,7 +85,6 @@ AudioStreamInfo::AudioStreamInfo(const std::shared_ptr<Mlt::Producer> &producer,
key = QStringLiteral("meta.media.%1.codec.channels").arg(audioStreamIndex).toLocal8Bit();
m_channels = producer->get_int(key.data());
setAudioIndex(producer, m_audioStreamIndex);
}
}
......@@ -97,6 +106,19 @@ QMap <int, QString> AudioStreamInfo::streams() const
return m_audioStreams;
}
QMap <int, QString> AudioStreamInfo::activeStreams() const
{
QMap <int, QString> active;
QMapIterator<int, QString> i(m_audioStreams);
while (i.hasNext()) {
i.next();
if (m_activeStreams.contains(i.key())) {
active.insert(i.key(), i.value());
}
}
return active;
}
int AudioStreamInfo::bitrate() const
{
return m_bitRate;
......@@ -137,3 +159,25 @@ void AudioStreamInfo::setAudioIndex(const std::shared_ptr<Mlt::Producer> &produc
}
}
void AudioStreamInfo::updateActiveStreams(const QString &activeStreams)
{
// -1 = disable all audio
// empty = enable all audio
m_activeStreams.clear();
if (activeStreams.isEmpty()) {
m_activeStreams = m_audioStreams.keys();
return;
}
QStringList st = activeStreams.split(QLatin1Char(';'));
for (const QString &s : st) {
m_activeStreams << s.toInt();
}
}
void AudioStreamInfo::renameStream(int ix, const QString streamName)
{
if (m_audioStreams.contains(ix)) {
m_audioStreams.insert(ix, streamName);
}
}
......@@ -29,6 +29,7 @@ public:
int samplingRate() const;
int channels() const;
QMap <int, QString> streams() const;
QMap <int, QString> activeStreams() const;
int bitrate() const;
const QString &samplingFormat() const;
int audio_index() const;
......@@ -36,10 +37,13 @@ public:
void dumpInfo() const;
void setAudioIndex(const std::shared_ptr<Mlt::Producer> &producer, int ix);
QMap<int, QString> streamInfo(Mlt::Properties sourceProperties);
void updateActiveStreams(const QString &activeStreams);
void renameStream(int ix, const QString streamName);
private:
int m_audioStreamIndex;
QMap <int, QString> m_audioStreams;
QList <int> m_activeStreams;
int m_ffmpegAudioIndex;
int m_samplingRate;
int m_channels;
......
......@@ -1047,6 +1047,14 @@ QMap <int, QString> ClipController::audioStreams() const
return {};
}
QMap <int, QString> ClipController::activeStreams() const
{
if (m_audioInfo) {
return m_audioInfo->activeStreams();
}
return {};
}
int ClipController::audioStreamsCount() const
{
if (m_audioInfo) {
......@@ -1054,3 +1062,4 @@ int ClipController::audioStreamsCount() const
}
return 0;
}
......@@ -97,6 +97,9 @@ public:
/** @brief Returns this clip's producer. */
virtual std::shared_ptr<Mlt::Producer> thumbProducer() = 0;
/** @brief Rename an audio stream. */
virtual void renameAudioStream(int id, QString name) = 0;
/** @brief Returns the clip's duration */
GenTime getPlaytime() const;
......@@ -207,8 +210,10 @@ public:
/** @brief Append an effect to this producer's effect list */
bool addEffect(const QString &effectId);
/** @brief Returns the list of audio streams indexes for this clip */
/** @brief Returns the list of all audio streams indexes for this clip */
QMap <int, QString> audioStreams() const;
/** @brief Returns the list of active audio streams indexes for this clip */
QMap <int, QString> activeStreams() const;
/** @brief Returns the count of audio streams for this clip */
int audioStreamsCount() const;
......
......@@ -64,6 +64,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <QTextEdit>
#include <QToolBar>
#include <QUrl>
#include <QListWidgetItem>
#include <QVBoxLayout>
AnalysisTree::AnalysisTree(QWidget *parent)
......@@ -168,6 +169,7 @@ ClipPropertiesController::ClipPropertiesController(ClipController *controller, Q
, m_properties(new Mlt::Properties(controller->properties()))
, m_audioStream(nullptr)
, m_textEdit(nullptr)
, m_audioStreamsView(nullptr)
{
m_controller->mirrorOriginalProperties(m_sourceProperties);
setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
......@@ -182,6 +184,7 @@ ClipPropertiesController::ClipPropertiesController(ClipController *controller, Q
m_tabWidget->setDocumentMode(true);
m_tabWidget->setTabPosition(QTabWidget::East);
auto *forcePage = new QScrollArea(this);
auto *forceAudioPage = new QScrollArea(this);
m_propertiesPage = new QWidget(this);
m_markersPage = new QWidget(this);
m_metaPage = new QWidget(this);
......@@ -248,6 +251,11 @@ ClipPropertiesController::ClipPropertiesController(ClipController *controller, Q
// Force properties
auto *vbox = new QVBoxLayout;
vbox->setSpacing(0);
// Force Audio properties
auto *audioVbox = new QVBoxLayout;
audioVbox->setSpacing(0);
if (m_type == ClipType::Text || m_type == ClipType::SlideShow || m_type == ClipType::TextTemplate) {
QPushButton *editButton = new QPushButton(i18n("Edit Clip"), this);
connect(editButton, &QAbstractButton::clicked, this, &ClipPropertiesController::editClip);
......@@ -598,8 +606,10 @@ ClipPropertiesController::ClipPropertiesController(ClipController *controller, Q
// Audio index
QMap<int, QString> audioStreamsInfo = m_controller->audioStreams();
if (!audioStreamsInfo.isEmpty()) {
QList <int> enabledStreams = m_controller->activeStreams().keys();
QString vix = m_sourceProperties.get("audio_index");
m_originalProperties.insert(QStringLiteral("audio_index"), vix);
m_originalProperties.insert(QStringLiteral("kdenlive:active_streams"), m_sourceProperties.get("kdenlive:active_streams"));
hlay = new QHBoxLayout;
KDualAction *ac = new KDualAction(i18n("Disable audio"), i18n("Enable audio"), this);
......@@ -610,44 +620,94 @@ ClipPropertiesController::ClipPropertiesController(ClipController *controller, Q
tbv->setDefaultAction(ac);
tbv->setAutoRaise(true);
hlay->addWidget(tbv);
hlay->addWidget(new QLabel(i18n("Audio stream")));
m_audioStream = new QComboBox(this);
hlay->addWidget(new QLabel(i18n("Audio streams")));
audioVbox->addLayout(hlay);
m_audioStreamsView = new QListWidget(this);
m_audioStreamsView->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
audioVbox->addWidget(m_audioStreamsView);
//m_audioStream = new QComboBox(this);
QMapIterator<int, QString> i(audioStreamsInfo);
while (i.hasNext()) {
i.next();
m_audioStream->addItem(i.value(), i.key());
}
if (m_audioStream->count() > 1) {
m_audioStream->addItem(i18n("Merge all streams"), INT_MAX);
QListWidgetItem *item = new QListWidgetItem(i.value(), m_audioStreamsView);
item->setData(Qt::UserRole, i.key());
// Store oringinal name
item->setData(Qt::UserRole + 1, i.value());
item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
if (enabledStreams.contains(i.key())) {
item->setCheckState(Qt::Checked);
} else {
item->setCheckState(Qt::Unchecked);
}
}
if (!vix.isEmpty() && vix.toInt() != -1) {
m_audioStream->setCurrentIndex(m_audioStream->findData(QVariant(vix)));
if (audioStreamsInfo.count() > 1) {
QListWidgetItem *item = new QListWidgetItem(i18n("Merge all streams"), m_audioStreamsView);
item->setData(Qt::UserRole, INT_MAX);
item->setData(Qt::UserRole + 1, item->text());
item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
if (enabledStreams.contains(INT_MAX)) {
item->setCheckState(Qt::Checked);
} else {
item->setCheckState(Qt::Unchecked);
}
}
connect(m_audioStreamsView, &QListWidget::itemChanged, [this] (QListWidgetItem *item) {
if (!item) {
return;
}
bool checked = item->checkState() == Qt::Checked;
int streamId = item->data(Qt::UserRole).toInt();
bool streamModified = false;
QString currentStreams = m_originalProperties.value(QStringLiteral("kdenlive:active_streams"));
QStringList activeStreams = currentStreams.split(QLatin1Char(';'));
if (activeStreams.contains(QString::number(streamId))) {
if (!checked) {
// Stream was unselected
activeStreams.removeAll(QString::number(streamId));
streamModified = true;
}
} else if (checked) {
// Stream was selected
activeStreams << QString::number(streamId);
activeStreams.sort();
streamModified = true;
}
if (streamModified) {
if (activeStreams.isEmpty()) {
activeStreams << QStringLiteral("-1");
}
QMap<QString, QString> properties;
properties.insert(QStringLiteral("kdenlive:active_streams"), activeStreams.join(QLatin1Char(';')));
emit updateClipProperties(m_id, m_originalProperties, properties);
m_originalProperties = properties;
} else if (item->text() != item->data(Qt::UserRole + 1).toString()) {
// Rename event
QString txt = item->text();
int row = m_audioStreamsView->row(item) + 1;
if (!txt.startsWith(QString("%1|").arg(row))) {
txt.prepend(QString("%1|").arg(row));
}
m_controller->renameAudioStream(streamId, txt);
QSignalBlocker bk(m_audioStreamsView);
item->setText(txt);
item->setData(Qt::UserRole + 1, txt);
}
});
ac->setActive(vix.toInt() == -1);
m_audioStream->setEnabled(vix.toInt() > -1);
m_audioStream->setVisible(audioStreamsInfo.size() > 0);
connect(ac, &KDualAction::activeChanged, [this](bool activated) {
connect(ac, &KDualAction::activeChanged, [this, audioStreamsInfo](bool activated) {
QMap<QString, QString> properties;
int vindx = -1;
if (activated) {
m_audioStream->setEnabled(false);
properties.insert(QStringLiteral("kdenlive:active_streams"), QStringLiteral("-1"));
} else {
m_audioStream->setEnabled(true);
vindx = m_audioStream->currentData().toInt();
properties.insert(QStringLiteral("kdenlive:active_streams"), QString());
vindx = audioStreamsInfo.firstKey();
}
properties.insert(QStringLiteral("audio_index"), QString::number(vindx));
properties.insert(QStringLiteral("set.test_audio"), vindx > -1 ? QStringLiteral("0") : QStringLiteral("1"));
emit updateClipProperties(m_id, m_originalProperties, properties);
m_originalProperties = properties;
});
QObject::connect(m_audioStream, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [this]() {
QMap<QString, QString> properties;
properties.insert(QStringLiteral("audio_index"), QString::number(m_audioStream->currentData().toInt()));
emit updateClipProperties(m_id, m_originalProperties, properties);
m_originalProperties = properties;
});
hlay->addWidget(m_audioStream);
vbox->addLayout(hlay);
// Audio sync
hlay = new QHBoxLayout;
......@@ -668,7 +728,7 @@ ClipPropertiesController::ClipPropertiesController(ClipController *controller, Q
m_originalProperties = properties;
});
hlay->addWidget(spinSync);
vbox->addLayout(hlay);
audioVbox->addLayout(hlay);
}
// Colorspace
......@@ -712,13 +772,21 @@ ClipPropertiesController::ClipPropertiesController(ClipController *controller, Q
vbox->addLayout(hlay);
hlay->addStretch(10);
}
// Force properties page
QWidget *forceProp = new QWidget(this);
forceProp->setLayout(vbox);
forcePage->setWidget(forceProp);
forcePage->setWidgetResizable(true);
// Force audio properties page
QWidget *forceAudioProp = new QWidget(this);
forceAudioProp->setLayout(audioVbox);
forceAudioPage->setWidget(forceAudioProp);
forceAudioPage->setWidgetResizable(true);
vbox->addStretch(10);
m_tabWidget->addTab(m_propertiesPage, QString());
m_tabWidget->addTab(forcePage, QString());
m_tabWidget->addTab(forceAudioPage, QString());
m_tabWidget->addTab(m_markersPage, QString());
m_tabWidget->addTab(m_metaPage, QString());
m_tabWidget->addTab(m_analysisPage, QString());
......@@ -726,12 +794,14 @@ ClipPropertiesController::ClipPropertiesController(ClipController *controller, Q
m_tabWidget->setTabToolTip(0, i18n("File info"));
m_tabWidget->setTabIcon(1, QIcon::fromTheme(QStringLiteral("document-edit")));
m_tabWidget->setTabToolTip(1, i18n("Properties"));
m_tabWidget->setTabIcon(2, QIcon::fromTheme(QStringLiteral("bookmark-new")));
m_tabWidget->setTabToolTip(2, i18n("Markers"));
m_tabWidget->setTabIcon(3, QIcon::fromTheme(QStringLiteral("view-grid")));
m_tabWidget->setTabToolTip(3, i18n("Metadata"));
m_tabWidget->setTabIcon(4, QIcon::fromTheme(QStringLiteral("visibility")));
m_tabWidget->setTabToolTip(4, i18n("Analysis"));
m_tabWidget->setTabIcon(2, QIcon::fromTheme(QStringLiteral("audio-volume-high")));
m_tabWidget->setTabToolTip(2, i18n("Audio Properties"));
m_tabWidget->setTabIcon(3, QIcon::fromTheme(QStringLiteral("bookmark-new")));
m_tabWidget->setTabToolTip(3, i18n("Markers"));
m_tabWidget->setTabIcon(4, QIcon::fromTheme(QStringLiteral("view-grid")));
m_tabWidget->setTabToolTip(4, i18n("Metadata"));
m_tabWidget->setTabIcon(5, QIcon::fromTheme(QStringLiteral("visibility")));
m_tabWidget->setTabToolTip(5, i18n("Analysis"));
m_tabWidget->setCurrentIndex(KdenliveSettings::properties_panel_page());
if (m_type == ClipType::Color) {
m_tabWidget->setTabEnabled(0, false);
......@@ -776,13 +846,21 @@ void ClipPropertiesController::slotReloadProperties()
m_originalProperties.insert(QStringLiteral("kdenlive:proxy"), proxy);
emit proxyModified(proxy);
}
if (m_audioStream && m_audioStream->isEnabled()) {
if (m_audioStreamsView && m_audioStreamsView->count() > 0) {
int audio_ix = m_properties->get_int("audio_index");
m_originalProperties.insert(QStringLiteral("kdenlive:active_streams"), m_properties->get("kdenlive:active_streams"));
if (audio_ix != m_originalProperties.value(QStringLiteral("audio_index")).toInt()) {
QSignalBlocker bk(m_audioStream);
m_originalProperties.insert(QStringLiteral("audio_index"), QString::number(audio_ix));
// update combo
m_audioStream->setCurrentIndex(m_audioStream->findData(audio_ix));
//m_audioStream->setCurrentIndex(m_audioStream->findData(audio_ix));
}
QList <int> enabledStreams = m_controller->activeStreams().keys();
QSignalBlocker bk(m_audioStreamsView);
for (int ix = 0; ix < m_audioStreamsView->count(); ix++) {
QListWidgetItem *item = m_audioStreamsView->item(ix);
int stream = item->data(Qt::UserRole).toInt();
item->setCheckState(enabledStreams.contains(stream) ? Qt::Checked : Qt::Unchecked);
}
}
break;
......@@ -1390,14 +1468,14 @@ void ClipPropertiesController::slotTextChanged()
void ClipPropertiesController::slotDeleteSelectedMarkers()
{
if (m_tabWidget->currentIndex() == 2) {
if (m_tabWidget->currentIndex() == 3) {
slotDeleteMarker();
}
}
void ClipPropertiesController::slotSelectAllMarkers()
{
if (m_tabWidget->currentIndex() == 2) {
if (m_tabWidget->currentIndex() == 3) {
m_markerTree->selectAll();
}
}
......@@ -35,6 +35,7 @@ class QMimeData;
class QTextEdit;
class QLabel;
class QComboBox;
class QListWidget;
class AnalysisTree : public QTreeWidget
{
......@@ -114,6 +115,7 @@ private:
QTreeView *m_markerTree;
AnalysisTree *m_analysisTree;
QTextEdit *m_textEdit;
QListWidget *m_audioStreamsView;
void fillProperties();
signals:
......
......@@ -141,7 +141,6 @@ Monitor::Monitor(Kdenlive::MonitorId id, MonitorManager *manager, QWidget *paren
, m_contextMenu(nullptr)
, m_markerMenu(nullptr)
, m_audioChannels(nullptr)
, m_audioChannelsGroup(nullptr)
, m_loopClipTransition(true)
, m_editMarker(nullptr)
, m_forceSizeFactor(0)
......@@ -277,10 +276,48 @@ Monitor::Monitor(Kdenlive::MonitorId id, MonitorManager *manager, QWidget *paren
m_toolbar->addAction(manager->getAction(QStringLiteral("insert_project_tree")));
m_toolbar->setToolTip(i18n("Insert Zone to Project Bin"));
m_toolbar->addSeparator();
m_streamsButton = new QToolButton(this);
m_streamsButton->setPopupMode(QToolButton::InstantPopup);
m_streamsButton->setIcon(QIcon::fromTheme(QStringLiteral("speaker")));
m_streamAction = m_toolbar->addWidget(m_streamsButton);
m_audioChannels = new QMenu(this);
m_audioChannels->setIcon(QIcon::fromTheme(QStringLiteral("speaker")));
m_toolbar->addAction(m_audioChannels->menuAction());
m_audioChannels->menuAction()->setVisible(false);
m_streamsButton->setMenu(m_audioChannels);
m_streamAction->setVisible(false);
connect(m_audioChannels, &QMenu::triggered, [this] () {
m_audioChannels->show();
QList <QAction*> actions = m_audioChannels->actions();
QMap <int, QString> enabledStreams;
for (const auto act : actions) {
if (act->isChecked()) {
// Audio stream is selected
enabledStreams.insert(act->data().toInt(), act->text().remove(QLatin1Char('&')));
}
}
if (!enabledStreams.isEmpty()) {
// Only 1 stream wanted, easy
m_glMonitor->getControllerProxy()->setAudioStream(enabledStreams.first());
QMap <QString, QString> props;
props.insert(QStringLiteral("audio_index"), QString::number(enabledStreams.firstKey()));
if (enabledStreams.count() > 1) {
// Mix audio channels
}
QList <int> streams = enabledStreams.keys();
QStringList</