This commit is contained in:
Malte Jürgens 2023-03-19 14:39:15 +01:00
parent e919e3ea25
commit e6449ad807
No known key found for this signature in database
GPG Key ID: D29FBD5F93C0CFC3
5 changed files with 226 additions and 20 deletions

View File

@ -24,9 +24,6 @@ int main(int argc, char *argv[]) {
"Custom Discord client with the ability to stream audio on Linux"); "Custom Discord client with the ability to stream audio on Linux");
parser.addHelpOption(); parser.addHelpOption();
parser.addVersionOption(); parser.addVersionOption();
QCommandLineOption virtmicOption("virtmic", "Start the Virtual Microphone",
"target");
parser.addOption(virtmicOption);
QCommandLineOption degubOption("remote-debugging", QCommandLineOption degubOption("remote-debugging",
"Open Chromium Remote Debugging on port 9222"); "Open Chromium Remote Debugging on port 9222");
parser.addOption(degubOption); parser.addOption(degubOption);
@ -36,11 +33,6 @@ int main(int argc, char *argv[]) {
parser.process(app); parser.process(app);
if (parser.isSet(virtmicOption)) {
Virtmic::start(parser.value(virtmicOption));
return 0;
}
qputenv("QTWEBENGINE_CHROMIUM_FLAGS", qputenv("QTWEBENGINE_CHROMIUM_FLAGS",
"--enable-features=WebRTCPipeWireCapturer " + "--enable-features=WebRTCPipeWireCapturer " +
qgetenv("QTWEBENGINE_CHROMIUM_FLAGS")); qgetenv("QTWEBENGINE_CHROMIUM_FLAGS"));

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "centralwidget.h" #include "centralwidget.h"
#include "virtmic.h"
#include <QMainWindow> #include <QMainWindow>
#include <QMenu> #include <QMenu>

View File

@ -1,4 +1,5 @@
#include "streamdialog.h" #include "streamdialog.h"
#include "mainwindow.h"
#include "virtmic.h" #include "virtmic.h"
#include <QComboBox> #include <QComboBox>

View File

@ -1,13 +1,174 @@
#include "virtmic.h" #include "virtmic.h"
#include "log.h" #include "log.h"
#include <cr/channel.hpp> QThread virtmicThread;
#include <rohrkabel/channel/channel.hpp> std::unique_ptr<pipewire::sender<Virtmic::set_target, Virtmic::terminate>>
senderr;
std::unique_ptr<cr::receiver<Virtmic::new_targets>> receiverr;
namespace Virtmic { void Virtmic::instance() {
if (!virtmicThread.isRunning()) {
auto [main_sender, main_receiver] = cr::channel<new_targets>();
auto [pw_sender, pw_receiver] = pipewire::channel<set_target, terminate>();
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<cr::receiver<Virtmic::new_targets>>(
std::move(main_receiver));
senderr = std::make_unique<
pipewire::sender<Virtmic::set_target, Virtmic::terminate>>(
std::move(pw_sender));
}
}
QVector<QString> getTargets() { return QVector<QString>{}; } void Virtmic::setTarget(QString target) {
senderr.get()->send<Virtmic::set_target>({target});
}
void start(QString _target) {} void Virtmic::getTargets() { senderr.get()->send<Virtmic::get_targets>(); }
} // namespace Virtmic Virtmic::Virtmic(pipewire::receiver<set_target, terminate> receiver,
cr::sender<new_targets> sender) {
m_receiver = std::make_unique<pipewire::receiver<set_target, terminate>>(
std::move(receiver));
m_sender = std::make_unique<cr::sender<new_targets>>(std::move(sender));
virtual_mic = std::make_unique<pipewire::proxy>(
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<pipewire::registry_event::global>(
[&](const auto &global) { globalEvent(global); });
metadata_listener.on<pipewire::registry_event::global_removed>(
[&](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<pipewire::link>(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<pipewire::node>(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<pipewire::port>(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<pipewire::port>(std::move(port));
} else {
virt_fr = std::make_unique<pipewire::port>(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);
}
}

View File

@ -1,12 +1,63 @@
#pragma once #pragma once
#include <QString> #include <cr/channel.hpp>
#include <QVector>
#include <iostream> #include <iostream>
#include <rohrkabel/channel/channel.hpp>
#include <rohrkabel/main_loop.hpp>
#include <rohrkabel/registry/registry.hpp>
namespace Virtmic { #include <QMap>
#include <QScopedPointer>
#include <QStringList>
#include <QThread>
QVector<QString> getTargets(); class Virtmic : public QObject {
void start(QString _target); 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<set_target, terminate> receiver,
cr::sender<new_targets> sender);
void run();
private:
std::unique_ptr<pipewire::receiver<set_target, terminate>> m_receiver;
std::unique_ptr<cr::sender<new_targets>> 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<pipewire::registry_listener>();
std::unique_ptr<pipewire::proxy> virtual_mic;
std::map<uint32_t, pipewire::port> ports;
std::unique_ptr<pipewire::port> virt_fl, virt_fr;
std::map<uint32_t, pipewire::node_info> nodes;
std::map<uint32_t, pipewire::link> links;
void link();
void unlink();
void globalEvent(const pipewire::global &global);
void globalRemovedEvent(const std::uint32_t id);
};