diff --git a/src/main.cpp b/src/main.cpp index 61f3ada..e660ae0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,9 +24,6 @@ int main(int argc, char *argv[]) { "Custom Discord client with the ability to stream audio on Linux"); parser.addHelpOption(); parser.addVersionOption(); - QCommandLineOption virtmicOption("virtmic", "Start the Virtual Microphone", - "target"); - parser.addOption(virtmicOption); QCommandLineOption degubOption("remote-debugging", "Open Chromium Remote Debugging on port 9222"); parser.addOption(degubOption); @@ -36,11 +33,6 @@ int main(int argc, char *argv[]) { parser.process(app); - if (parser.isSet(virtmicOption)) { - Virtmic::start(parser.value(virtmicOption)); - return 0; - } - qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--enable-features=WebRTCPipeWireCapturer " + qgetenv("QTWEBENGINE_CHROMIUM_FLAGS")); diff --git a/src/mainwindow.h b/src/mainwindow.h index d85c249..297789c 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -1,6 +1,7 @@ #pragma once #include "centralwidget.h" +#include "virtmic.h" #include #include diff --git a/src/streamdialog.cpp b/src/streamdialog.cpp index e383dea..7b647ad 100644 --- a/src/streamdialog.cpp +++ b/src/streamdialog.cpp @@ -1,4 +1,5 @@ #include "streamdialog.h" +#include "mainwindow.h" #include "virtmic.h" #include diff --git a/src/virtmic.cpp b/src/virtmic.cpp index 97a372c..d7e9285 100644 --- a/src/virtmic.cpp +++ b/src/virtmic.cpp @@ -1,13 +1,174 @@ #include "virtmic.h" #include "log.h" -#include -#include +QThread virtmicThread; +std::unique_ptr> + senderr; +std::unique_ptr> receiverr; -namespace Virtmic { +void Virtmic::instance() { + if (!virtmicThread.isRunning()) { + auto [main_sender, main_receiver] = cr::channel(); + auto [pw_sender, pw_receiver] = pipewire::channel(); + Virtmic *virtmic = + new Virtmic(std::move(pw_receiver), std::move(main_sender)); + virtmic->moveToThread(&virtmicThread); + virtmicThread.start(); + QMetaObject::invokeMethod(virtmic, "run"); + receiverr = std::make_unique>( + std::move(main_receiver)); + senderr = std::make_unique< + pipewire::sender>( + std::move(pw_sender)); + } +} -QVector getTargets() { return QVector{}; } +void Virtmic::setTarget(QString target) { + senderr.get()->send({target}); +} -void start(QString _target) {} +void Virtmic::getTargets() { senderr.get()->send(); } -} // namespace Virtmic +Virtmic::Virtmic(pipewire::receiver receiver, + cr::sender sender) { + m_receiver = std::make_unique>( + std::move(receiver)); + m_sender = std::make_unique>(std::move(sender)); + virtual_mic = std::make_unique( + std::move(*core.create("adapter", + {{"node.name", "discord-screenaudio-virtmic"}, + {"media.class", "Audio/Source/Virtual"}, + {"factory.name", "support.null-audio-sink"}, + {"audio.channels", "2"}, + {"audio.position", "FL,FR"}}, + pipewire::node::type, pipewire::node::version, + pipewire::update_strategy::none) + .get())); + metadata_listener.on( + [&](const auto &global) { globalEvent(global); }); + metadata_listener.on( + [&](const std::uint32_t id) { globalRemovedEvent(id); }); +} + +void Virtmic::run() { + while (true) { + main_loop.run(); + } +} + +void Virtmic::link() { + for (const auto &[port_id, port] : ports) { + auto port_info = port.info(); + if (!virt_fl || !virt_fr) + continue; + + if (links.count(port_id)) + continue; + + if (port_info.direction == pipewire::port_direction::input) + continue; + + if (!port_info.props.count("node.id")) + continue; + + auto parent_id = std::stoul(port_info.props["node.id"]); + + if (!nodes.count(parent_id)) + continue; + + auto &parent = nodes[parent_id]; + QString name; + if (parent.props.count("application.name") && + parent.props["application.name"] != "") + name = QString::fromStdString(parent.props["application.name"]); + else + name = QString::fromStdString(parent.props["application.process.binary"]); + + if (name == target || + (target == "[All Desktop Audio]" && !EXCLUDE_TARGETS.contains(name))) { + auto fl = port_info.props["audio.channel"] == "FL"; + links.emplace(port_id, + *core.create_simple(fl ? virt_fl->info().id + : virt_fr->info().id, + port_id) + .get()); + qDebug(virtmicLog) << QString("Link: %1:%2 -> %3") + .arg(name) + .arg(port_id) + .arg(fl ? virt_fl->info().id + : virt_fr->info().id) + .toUtf8() + .data(); + } + } +} + +void Virtmic::unlink() { links.clear(); } + +void Virtmic::globalEvent(const pipewire::global &global) { + if (global.type == pipewire::node::type) { + auto node = *reg.bind(global.id).get(); + auto info = node.info(); + std::string name; + if (info.props.count("application.name") && + info.props["application.name"] != "") + name = info.props["application.name"]; + else if (info.props.count("application.process.binary")) { + name = info.props["application.process.binary"]; + } else + return; + qDebug(virtmicLog) << QString("Added: %1") + .arg(QString::fromStdString(name)) + .toUtf8() + .data(); + + if (!nodes.count(global.id)) { + nodes.emplace(global.id, node.info()); + link(); + } + } + if (global.type == pipewire::port::type) { + auto port = *reg.bind(global.id).get(); + auto info = port.info(); + + if (info.props.count("node.id")) { + auto node_id = std::stoul(info.props["node.id"]); + + if (node_id == virtual_mic.get()->id() && + info.direction == pipewire::port_direction::input) { + if (info.props["audio.channel"] == "FL") { + virt_fl = std::make_unique(std::move(port)); + } else { + virt_fr = std::make_unique(std::move(port)); + } + } else { + ports.emplace(global.id, std::move(port)); + } + + link(); + } + } +} + +void Virtmic::globalRemovedEvent(const std::uint32_t id) { + if (nodes.count(id)) { + auto info = nodes.at(id); + std::string name; + if (info.props.count("application.name") && + info.props["application.name"] != "") + name = info.props["application.name"]; + else + name = info.props["application.process.binary"]; + qDebug(virtmicLog) << QString("Removed: %1") + .arg(QString::fromStdString(name)) + .toUtf8() + .data(); + nodes.erase(id); + } + if (ports.count(id)) { + ports.erase(id); + } + if (links.count(id)) { + links.erase(id); + } +} diff --git a/src/virtmic.h b/src/virtmic.h index 361f240..55cd300 100644 --- a/src/virtmic.h +++ b/src/virtmic.h @@ -1,12 +1,63 @@ #pragma once -#include -#include +#include #include +#include +#include +#include -namespace Virtmic { +#include +#include +#include +#include -QVector getTargets(); -void start(QString _target); +class Virtmic : public QObject { + Q_OBJECT +public: + static void setTarget(QString target); + static void getTargets(); -} // namespace Virtmic +public: + struct set_target { + QString name; + }; + struct get_targets {}; + struct terminate {}; + struct new_targets { + QStringList targets; + }; + +protected: + static void instance(); + +protected: + Virtmic(pipewire::receiver receiver, + cr::sender sender); + void run(); + +private: + std::unique_ptr> m_receiver; + std::unique_ptr> m_sender; + + const QStringList EXCLUDE_TARGETS{"Chromium input", "discord-screenaudio"}; + QString target; + + pipewire::main_loop main_loop = pipewire::main_loop(); + pipewire::context context = pipewire::context(main_loop); + pipewire::core core = pipewire::core(context); + pipewire::registry reg = pipewire::registry(core); + + pipewire::registry_listener metadata_listener = + reg.listen(); + std::unique_ptr virtual_mic; + + std::map ports; + std::unique_ptr virt_fl, virt_fr; + std::map nodes; + std::map links; + + void link(); + void unlink(); + void globalEvent(const pipewire::global &global); + void globalRemovedEvent(const std::uint32_t id); +};