Get PipeWire somewhat running
This commit is contained in:
parent
84f9a04272
commit
d756cc559e
11
build.sh
11
build.sh
@ -6,8 +6,8 @@ cd "$script_dir"
|
|||||||
CC=${CC:-gcc}
|
CC=${CC:-gcc}
|
||||||
CXX=${CXX:-g++}
|
CXX=${CXX:-g++}
|
||||||
|
|
||||||
opts="-O2 -g0 -DNDEBUG -Wall -Wextra -Wshadow"
|
opts="-O2 -g0 -DNDEBUG -Wall -Wextra -Wshadow -g -fpermissive"
|
||||||
[ -n "$DEBUG" ] && opts="-O0 -g3 -Wall -Wextra -Wshadow";
|
[ -n "$DEBUG" ] && opts="-O0 -g3 -Wall -Wextra -Wshadow -fpermissive";
|
||||||
|
|
||||||
build_wayland_protocol() {
|
build_wayland_protocol() {
|
||||||
wayland-scanner private-code external/wlr-export-dmabuf-unstable-v1.xml external/wlr-export-dmabuf-unstable-v1-protocol.c
|
wayland-scanner private-code external/wlr-export-dmabuf-unstable-v1.xml external/wlr-export-dmabuf-unstable-v1-protocol.c
|
||||||
@ -25,9 +25,10 @@ build_gsr_kms_server() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
build_gsr() {
|
build_gsr() {
|
||||||
dependencies="libavcodec libavformat libavutil x11 xcomposite xrandr libpulse libswresample libavfilter libva libcap libdrm wayland-egl wayland-client"
|
dependencies="libavcodec libavformat libavutil x11 xcomposite xrandr libpulse libswresample libavfilter libva libcap libdrm wayland-egl wayland-client libpipewire-0.3"
|
||||||
includes="$(pkg-config --cflags $dependencies)"
|
includes="$(pkg-config --cflags $dependencies)"
|
||||||
libs="$(pkg-config --libs $dependencies) -ldl -pthread -lm"
|
libs="$(pkg-config --libs $dependencies) -ldl -pthread -lm -lpipewire-0.3"
|
||||||
|
$CXX -c src/pipewire.cpp $opts $includes
|
||||||
$CC -c src/capture/capture.c $opts $includes
|
$CC -c src/capture/capture.c $opts $includes
|
||||||
$CC -c src/capture/nvfbc.c $opts $includes
|
$CC -c src/capture/nvfbc.c $opts $includes
|
||||||
$CC -c src/capture/xcomposite_cuda.c $opts $includes
|
$CC -c src/capture/xcomposite_cuda.c $opts $includes
|
||||||
@ -48,7 +49,7 @@ build_gsr() {
|
|||||||
$CXX -c src/sound.cpp $opts $includes
|
$CXX -c src/sound.cpp $opts $includes
|
||||||
$CXX -c src/main.cpp $opts $includes
|
$CXX -c src/main.cpp $opts $includes
|
||||||
$CXX -o gpu-screen-recorder capture.o nvfbc.o kms_client.o egl.o cuda.o xnvctrl.o overclock.o window_texture.o shader.o \
|
$CXX -o gpu-screen-recorder capture.o nvfbc.o kms_client.o egl.o cuda.o xnvctrl.o overclock.o window_texture.o shader.o \
|
||||||
color_conversion.o utils.o library_loader.o xcomposite_cuda.o xcomposite_vaapi.o kms_vaapi.o kms_cuda.o wlr-export-dmabuf-unstable-v1-protocol.o sound.o main.o $libs $opts
|
color_conversion.o utils.o library_loader.o xcomposite_cuda.o xcomposite_vaapi.o kms_vaapi.o kms_cuda.o wlr-export-dmabuf-unstable-v1-protocol.o sound.o pipewire.o main.o $libs $opts
|
||||||
}
|
}
|
||||||
|
|
||||||
build_wayland_protocol
|
build_wayland_protocol
|
||||||
|
24
flake.lock
Normal file
24
flake.lock
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1703013332,
|
||||||
|
"narHash": "sha256-+tFNwMvlXLbJZXiMHqYq77z/RfmpfpiI3yjL6o/Zo9M=",
|
||||||
|
"path": "/nix/store/50bgi74d890mpkp90w1jwc5g0dw4dccr-source",
|
||||||
|
"rev": "54aac082a4d9bb5bbc5c4e899603abfb76a3f6d6",
|
||||||
|
"type": "path"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
70
flake.nix
Normal file
70
flake.nix
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
description = "A very basic flake";
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs }: let
|
||||||
|
gsr = { stdenv
|
||||||
|
, lib
|
||||||
|
, fetchurl
|
||||||
|
, makeWrapper
|
||||||
|
, pkg-config
|
||||||
|
, libXcomposite
|
||||||
|
, libpulseaudio
|
||||||
|
, ffmpeg
|
||||||
|
, wayland
|
||||||
|
, libdrm
|
||||||
|
, libva
|
||||||
|
, libglvnd
|
||||||
|
, libXrandr
|
||||||
|
, pipewire
|
||||||
|
}:
|
||||||
|
|
||||||
|
stdenv.mkDerivation {
|
||||||
|
pname = "gpu-screen-recorder";
|
||||||
|
version = "unstable-2023-11-18";
|
||||||
|
|
||||||
|
# printf "r%s.%s\n" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
|
||||||
|
src = ./.;
|
||||||
|
#sourceRoot = ".";
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
pkg-config
|
||||||
|
makeWrapper
|
||||||
|
];
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
libXcomposite
|
||||||
|
libpulseaudio
|
||||||
|
ffmpeg
|
||||||
|
wayland
|
||||||
|
libdrm
|
||||||
|
libva
|
||||||
|
libXrandr
|
||||||
|
pipewire
|
||||||
|
];
|
||||||
|
|
||||||
|
buildPhase = ''
|
||||||
|
./build.sh
|
||||||
|
'';
|
||||||
|
|
||||||
|
postInstall = ''
|
||||||
|
install -Dt $out/bin gpu-screen-recorder gsr-kms-server
|
||||||
|
mkdir $out/bin/.wrapped
|
||||||
|
mv $out/bin/gpu-screen-recorder $out/bin/.wrapped/
|
||||||
|
makeWrapper "$out/bin/.wrapped/gpu-screen-recorder" "$out/bin/gpu-screen-recorder" \
|
||||||
|
--prefix LD_LIBRARY_PATH : ${libglvnd}/lib \
|
||||||
|
--prefix PATH : $out/bin
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "A screen recorder that has minimal impact on system performance by recording a window using the GPU only";
|
||||||
|
homepage = "https://git.dec05eba.com/gpu-screen-recorder/about/";
|
||||||
|
license = licenses.gpl3Only;
|
||||||
|
maintainers = with maintainers; [ babbaj ];
|
||||||
|
platforms = [ "x86_64-linux" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
packages.x86_64-linux.gsr = nixpkgs.legacyPackages.x86_64-linux.callPackage gsr {};
|
||||||
|
packages.x86_64-linux.default = nixpkgs.legacyPackages.x86_64-linux.callPackage gsr {};
|
||||||
|
};
|
||||||
|
}
|
1
include/pipewire.hpp
Normal file
1
include/pipewire.hpp
Normal file
@ -0,0 +1 @@
|
|||||||
|
void init_pipewire();
|
@ -23,6 +23,7 @@ extern "C" {
|
|||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
|
|
||||||
#include "../include/sound.hpp"
|
#include "../include/sound.hpp"
|
||||||
|
#include "../include/pipewire.hpp"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <libavutil/pixfmt.h>
|
#include <libavutil/pixfmt.h>
|
||||||
@ -1374,6 +1375,9 @@ struct Arg {
|
|||||||
};
|
};
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
|
init_pipewire();
|
||||||
|
|
||||||
|
return 0;
|
||||||
signal(SIGINT, stop_handler);
|
signal(SIGINT, stop_handler);
|
||||||
signal(SIGUSR1, save_replay_handler);
|
signal(SIGUSR1, save_replay_handler);
|
||||||
|
|
||||||
|
457
src/pipewire.cpp
Normal file
457
src/pipewire.cpp
Normal file
@ -0,0 +1,457 @@
|
|||||||
|
#include <pipewire/pipewire.h>
|
||||||
|
#include <spa/param/audio/format-utils.h>
|
||||||
|
#include <spa/debug/types.h>
|
||||||
|
#include <spa/param/audio/type-info.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#define STR(x) #x
|
||||||
|
#define AUDIO_CHANNELS 2
|
||||||
|
|
||||||
|
struct target_client {
|
||||||
|
const char *app_name;
|
||||||
|
const char *binary;
|
||||||
|
uint32_t id;
|
||||||
|
|
||||||
|
struct spa_hook client_listener;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct target_port {
|
||||||
|
uint32_t id;
|
||||||
|
struct target_node *node;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct target_node {
|
||||||
|
uint32_t client_id;
|
||||||
|
uint32_t id;
|
||||||
|
const char *app_name;
|
||||||
|
|
||||||
|
std::vector<struct target_port> ports;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sink_port {
|
||||||
|
uint32_t id;
|
||||||
|
const char* channel;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct data {
|
||||||
|
struct pw_core *core;
|
||||||
|
|
||||||
|
// The stream we will capture
|
||||||
|
struct pw_stream *stream;
|
||||||
|
|
||||||
|
// The context to use.
|
||||||
|
struct pw_context *context;
|
||||||
|
|
||||||
|
// Object to accessing global events.
|
||||||
|
struct pw_registry *registry;
|
||||||
|
|
||||||
|
// Listener for global events.
|
||||||
|
struct spa_hook registry_listener;
|
||||||
|
|
||||||
|
// The capture sink.
|
||||||
|
struct pw_proxy *sink_proxy;
|
||||||
|
|
||||||
|
// Listener for the sink events.
|
||||||
|
struct spa_hook sink_proxy_listener;
|
||||||
|
|
||||||
|
// The event loop to use.
|
||||||
|
struct pw_thread_loop *thread_loop;
|
||||||
|
|
||||||
|
// The id of the sink that we created.
|
||||||
|
uint32_t sink_id;
|
||||||
|
|
||||||
|
// The serial of the sink.
|
||||||
|
uint32_t sink_serial;
|
||||||
|
|
||||||
|
// Sequence number for forcing a server round trip
|
||||||
|
int seq;
|
||||||
|
|
||||||
|
std::vector<struct sink_port> sink_ports;
|
||||||
|
|
||||||
|
std::vector<struct target_client> targets;
|
||||||
|
std::vector<struct target_node> nodes;
|
||||||
|
std::vector<struct target_port> ports;
|
||||||
|
|
||||||
|
struct spa_audio_info format;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void on_process(void *userdata)
|
||||||
|
{
|
||||||
|
struct data *data = static_cast<struct data *>(userdata);
|
||||||
|
struct pw_buffer *b;
|
||||||
|
struct spa_buffer *buf;
|
||||||
|
|
||||||
|
if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) {
|
||||||
|
pw_log_warn("out of buffers: %m");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = b->buffer;
|
||||||
|
if (buf->datas[0].data == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
printf("got a frame of size %d\n", buf->datas[0].chunk->size);
|
||||||
|
|
||||||
|
pw_stream_queue_buffer(data->stream, b);
|
||||||
|
}
|
||||||
|
/* [on_process] */
|
||||||
|
|
||||||
|
static void on_param_changed(void *userdata, uint32_t id, const struct spa_pod *param)
|
||||||
|
{
|
||||||
|
struct data *data = static_cast<struct data *>(userdata);
|
||||||
|
|
||||||
|
if (param == NULL || id != SPA_PARAM_Format)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (spa_format_parse(param,
|
||||||
|
&data->format.media_type,
|
||||||
|
&data->format.media_subtype) < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (data->format.media_type != SPA_MEDIA_TYPE_audio ||
|
||||||
|
data->format.media_subtype != SPA_MEDIA_SUBTYPE_raw)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (spa_format_audio_raw_parse(param, &data->format.info.raw) < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
printf("got audio format:\n");
|
||||||
|
printf(" channels: %d\n", data->format.info.raw.channels);
|
||||||
|
printf(" rate: %d\n", data->format.info.raw.rate);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void register_target_client(struct data *data, uint32_t id, const char* app_name) {
|
||||||
|
struct target_client client = {};
|
||||||
|
client.binary = NULL;
|
||||||
|
client.app_name = strdup(app_name);
|
||||||
|
client.id = id;
|
||||||
|
|
||||||
|
data->targets.push_back(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
void register_target_node(struct data *data, uint32_t id, uint32_t client_id, const char* app_name) {
|
||||||
|
struct target_node node = {};
|
||||||
|
node.app_name = strdup(app_name);
|
||||||
|
node.id = id;
|
||||||
|
node.client_id = client_id;
|
||||||
|
|
||||||
|
data->nodes.push_back(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
void register_target_port(struct data *data, struct target_node *node, uint32_t id) {
|
||||||
|
struct target_port port = {};
|
||||||
|
port.id = id;
|
||||||
|
port.node = node;
|
||||||
|
|
||||||
|
data->ports.push_back(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void registry_event_global(void *raw_data, uint32_t id,
|
||||||
|
uint32_t permissions, const char *type, uint32_t version,
|
||||||
|
const struct spa_dict *props)
|
||||||
|
{
|
||||||
|
if (!type || !props)
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct data *data = static_cast<struct data *>(raw_data);
|
||||||
|
|
||||||
|
if (id == data->sink_id) {
|
||||||
|
const char *serial = spa_dict_lookup(props, PW_KEY_OBJECT_SERIAL);
|
||||||
|
if (!serial) {
|
||||||
|
data->sink_serial = 0;
|
||||||
|
printf("No serial found on capture sink\n");
|
||||||
|
} else {
|
||||||
|
data->sink_serial = strtoul(serial, NULL, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(type, PW_TYPE_INTERFACE_Port) == 0) {
|
||||||
|
const char *nid, *dir, *chn;
|
||||||
|
if (
|
||||||
|
!(nid = spa_dict_lookup(props, PW_KEY_NODE_ID)) ||
|
||||||
|
!(dir = spa_dict_lookup(props, PW_KEY_PORT_DIRECTION)) ||
|
||||||
|
!(chn = spa_dict_lookup(props, PW_KEY_AUDIO_CHANNEL))
|
||||||
|
) {
|
||||||
|
printf("One or more props not set\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t node_id = strtoul(nid, NULL, 10);
|
||||||
|
printf("Port: node id %u\n", node_id);
|
||||||
|
if (strcmp(dir, "in") == 0 && node_id == data->sink_id && data->sink_id != SPA_ID_INVALID) {
|
||||||
|
printf("=======\n");
|
||||||
|
printf("Found our own sink's port: %d sink_id %d channel %s\n", id, data->sink_id, chn);
|
||||||
|
printf("=======\n");
|
||||||
|
|
||||||
|
|
||||||
|
data->sink_ports.push_back(
|
||||||
|
{ id, strdup(chn), }
|
||||||
|
);
|
||||||
|
} else if (strcmp(dir, "out") == 0) {
|
||||||
|
if (data->sink_id == SPA_ID_INVALID) {
|
||||||
|
printf("Want to process port %d but sink_id is invalid\n", id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
struct target_node *n = NULL;
|
||||||
|
for (auto t : data->nodes) {
|
||||||
|
if (t.id == node_id) {
|
||||||
|
n = &t;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!n) {
|
||||||
|
printf("Target not found\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
printf("Target found\n");
|
||||||
|
|
||||||
|
uint32_t p = 0;
|
||||||
|
for (auto sink_port : data->sink_ports) {
|
||||||
|
printf("%s = %s\n", sink_port.channel, chn);
|
||||||
|
if (strcmp(sink_port.channel, chn) == 0) {
|
||||||
|
p = sink_port.id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!p) {
|
||||||
|
printf("Failed to find port for channel %s of port %d\n", chn, id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct pw_properties *link_props = pw_properties_new(
|
||||||
|
PW_KEY_OBJECT_LINGER, "false",
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
pw_properties_setf(link_props, PW_KEY_LINK_OUTPUT_NODE, "%u", node_id);
|
||||||
|
pw_properties_setf(link_props, PW_KEY_LINK_OUTPUT_PORT, "%u", id);
|
||||||
|
|
||||||
|
pw_properties_setf(link_props, PW_KEY_LINK_INPUT_NODE, "%u", data->sink_id);
|
||||||
|
pw_properties_setf(link_props, PW_KEY_LINK_INPUT_PORT, "%u", p);
|
||||||
|
|
||||||
|
printf(
|
||||||
|
"Connecting (%d, %d) -> (%d, %d)\n",
|
||||||
|
node_id, id,
|
||||||
|
data->sink_id, p
|
||||||
|
);
|
||||||
|
|
||||||
|
struct pw_proxy *link_proxy = static_cast<struct pw_proxy *>(
|
||||||
|
pw_core_create_object(
|
||||||
|
data->core, "link-factory",
|
||||||
|
PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &link_props->dict, 0
|
||||||
|
)
|
||||||
|
);
|
||||||
|
data->seq = pw_core_sync(data->core, PW_ID_CORE, data->seq);
|
||||||
|
pw_properties_free(link_props);
|
||||||
|
|
||||||
|
if (!link_proxy) {
|
||||||
|
printf("!!!!! Failed to connect port %u of node %u to capture sink\n", id, node_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
printf("Connected!\n");
|
||||||
|
}
|
||||||
|
} else if (strcmp(type, PW_TYPE_INTERFACE_Client) == 0) {
|
||||||
|
const char *client_app_name = spa_dict_lookup(props, PW_KEY_APP_NAME);
|
||||||
|
printf("Client: app name %s id %d\n", client_app_name, id);
|
||||||
|
register_target_client(
|
||||||
|
data,
|
||||||
|
id,
|
||||||
|
client_app_name
|
||||||
|
);
|
||||||
|
} else if (strcmp(type, PW_TYPE_INTERFACE_Node) == 0) {
|
||||||
|
const char *node_name, *media_class;
|
||||||
|
if (!(node_name = spa_dict_lookup(props, PW_KEY_NODE_NAME)) ||
|
||||||
|
!(media_class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Node: media_class %s node_app %s id %d\n", media_class, node_name, id);
|
||||||
|
if (strcmp(media_class, "Stream/Output/Audio") == 0) {
|
||||||
|
const char *node_app_name = spa_dict_lookup(props, PW_KEY_APP_NAME);
|
||||||
|
if (!node_app_name) {
|
||||||
|
node_app_name = node_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t client_id = 0;
|
||||||
|
const char *client_id_str = spa_dict_lookup(props, PW_KEY_CLIENT_ID);
|
||||||
|
if (client_id_str) {
|
||||||
|
client_id = strtoul(client_id_str, NULL, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
register_target_node(
|
||||||
|
data,
|
||||||
|
id,
|
||||||
|
client_id,
|
||||||
|
node_app_name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct pw_stream_events stream_events = {
|
||||||
|
PW_VERSION_STREAM_EVENTS,
|
||||||
|
.param_changed = on_param_changed,
|
||||||
|
.process = on_process,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct pw_registry_events registry_events = {
|
||||||
|
PW_VERSION_REGISTRY_EVENTS,
|
||||||
|
.global = registry_event_global,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void on_sink_proxy_bound(void *userdata, uint32_t global_id) {
|
||||||
|
struct data *data = static_cast<struct data*>(userdata);
|
||||||
|
data->sink_id = global_id;
|
||||||
|
printf("Got id %d\n", global_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_sink_proxy_error(void *data, int seq, int res, const char *message)
|
||||||
|
{
|
||||||
|
printf("[pipewire] App capture sink error: seq:%d res:%d :%s", seq, res, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct pw_proxy_events sink_proxy_events = {
|
||||||
|
PW_VERSION_PROXY_EVENTS,
|
||||||
|
.bound = on_sink_proxy_bound,
|
||||||
|
.error = on_sink_proxy_error,
|
||||||
|
};
|
||||||
|
|
||||||
|
void init_pipewire() {
|
||||||
|
struct data data = {
|
||||||
|
0,
|
||||||
|
sink_id: SPA_ID_INVALID,
|
||||||
|
sink_serial: 0,
|
||||||
|
seq: 0,
|
||||||
|
sink_ports: std::vector<struct sink_port> {},
|
||||||
|
targets: std::vector<struct target_client> {},
|
||||||
|
nodes: std::vector<struct target_node> {},
|
||||||
|
ports: std::vector<struct target_port> {},
|
||||||
|
};
|
||||||
|
const struct spa_pod *params[1];
|
||||||
|
uint8_t buffer[2048];
|
||||||
|
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
||||||
|
struct pw_properties *props;
|
||||||
|
pw_init(NULL, NULL);
|
||||||
|
|
||||||
|
data.thread_loop = pw_thread_loop_new("PipeWire thread loop", NULL);
|
||||||
|
pw_thread_loop_lock(data.thread_loop);
|
||||||
|
if (pw_thread_loop_start(data.thread_loop) < 0) {
|
||||||
|
printf("Failed to start thread loop");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.context = pw_context_new(pw_thread_loop_get_loop(data.thread_loop), NULL, 0);
|
||||||
|
data.core = pw_context_connect(data.context, NULL, 0);
|
||||||
|
pw_core_sync(data.core, PW_ID_CORE, 0);
|
||||||
|
//pw_thread_loop_wait(data.thread_loop);
|
||||||
|
pw_thread_loop_unlock(data.thread_loop);
|
||||||
|
|
||||||
|
props = pw_properties_new(
|
||||||
|
PW_KEY_MEDIA_TYPE, "Audio",
|
||||||
|
PW_KEY_MEDIA_CATEGORY, "Capture",
|
||||||
|
PW_KEY_MEDIA_ROLE, "Screen",
|
||||||
|
PW_KEY_NODE_NAME, "GSR",
|
||||||
|
PW_KEY_NODE_VIRTUAL, "true",
|
||||||
|
PW_KEY_AUDIO_CHANNELS, "" STR(AUDIO_CHANNELS) "",
|
||||||
|
SPA_KEY_AUDIO_POSITION, "FL,FR",
|
||||||
|
PW_KEY_FACTORY_NAME, "support.null-audio-sink",
|
||||||
|
PW_KEY_MEDIA_CLASS, "Audio/Sink/Internal",
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
data.sink_proxy = static_cast<pw_proxy *>(
|
||||||
|
pw_core_create_object(
|
||||||
|
data.core,
|
||||||
|
"adapter",
|
||||||
|
PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, &props->dict, 0
|
||||||
|
)
|
||||||
|
);
|
||||||
|
pw_proxy_add_listener(
|
||||||
|
data.sink_proxy,
|
||||||
|
&data.sink_proxy_listener,
|
||||||
|
&sink_proxy_events,
|
||||||
|
&data
|
||||||
|
);
|
||||||
|
data.registry = pw_core_get_registry(data.core, PW_VERSION_REGISTRY, 0);
|
||||||
|
printf("Got registry\n");
|
||||||
|
spa_zero(data.registry_listener);
|
||||||
|
pw_registry_add_listener(data.registry, &data.registry_listener, ®istry_events, &data);
|
||||||
|
printf("Listener registered\n");
|
||||||
|
|
||||||
|
printf("Waiting for id\n");
|
||||||
|
while (data.sink_id == SPA_ID_INVALID || data.sink_serial == 0) {
|
||||||
|
printf("Poll\n");
|
||||||
|
pw_loop_iterate(pw_thread_loop_get_loop(data.thread_loop), -1);
|
||||||
|
}
|
||||||
|
printf("Got id\n");
|
||||||
|
|
||||||
|
enum spa_audio_channel channels[8];
|
||||||
|
channels[0] = SPA_AUDIO_CHANNEL_FL;
|
||||||
|
channels[1] = SPA_AUDIO_CHANNEL_FL;
|
||||||
|
channels[2] = SPA_AUDIO_CHANNEL_UNKNOWN;
|
||||||
|
channels[3] = SPA_AUDIO_CHANNEL_UNKNOWN;
|
||||||
|
channels[4] = SPA_AUDIO_CHANNEL_UNKNOWN;
|
||||||
|
channels[5] = SPA_AUDIO_CHANNEL_UNKNOWN;
|
||||||
|
channels[6] = SPA_AUDIO_CHANNEL_UNKNOWN;
|
||||||
|
channels[7] = SPA_AUDIO_CHANNEL_UNKNOWN;
|
||||||
|
|
||||||
|
params[0] = spa_pod_builder_add_object(
|
||||||
|
&b,
|
||||||
|
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
|
||||||
|
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
|
||||||
|
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
|
||||||
|
SPA_FORMAT_AUDIO_channels, SPA_POD_Int(AUDIO_CHANNELS),
|
||||||
|
SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(enum spa_audio_channel), SPA_TYPE_Id, AUDIO_CHANNELS, channels),
|
||||||
|
SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(
|
||||||
|
8, SPA_AUDIO_FORMAT_U8, SPA_AUDIO_FORMAT_S16_LE, SPA_AUDIO_FORMAT_S32_LE,
|
||||||
|
SPA_AUDIO_FORMAT_F32_LE, SPA_AUDIO_FORMAT_U8P, SPA_AUDIO_FORMAT_S16P,
|
||||||
|
SPA_AUDIO_FORMAT_S32P, SPA_AUDIO_FORMAT_F32P
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
data.stream = pw_stream_new(
|
||||||
|
data.core,
|
||||||
|
"GSR",
|
||||||
|
pw_properties_new(
|
||||||
|
PW_KEY_NODE_NAME, "GSR",
|
||||||
|
PW_KEY_NODE_DESCRIPTION, "GSR Audio Capture",
|
||||||
|
PW_KEY_MEDIA_TYPE, "Audio",
|
||||||
|
PW_KEY_MEDIA_CATEGORY, "Capture",
|
||||||
|
PW_KEY_MEDIA_ROLE, "Production",
|
||||||
|
PW_KEY_NODE_WANT_DRIVER, "true",
|
||||||
|
PW_KEY_STREAM_CAPTURE_SINK, "true",
|
||||||
|
NULL
|
||||||
|
)
|
||||||
|
);
|
||||||
|
struct pw_properties *stream_props = pw_properties_new(NULL, NULL);
|
||||||
|
pw_properties_setf(stream_props, PW_KEY_TARGET_OBJECT, "%u", data.sink_serial);
|
||||||
|
pw_stream_update_properties(data.stream, &stream_props->dict);
|
||||||
|
pw_properties_free(stream_props);
|
||||||
|
|
||||||
|
pw_stream_connect(
|
||||||
|
data.stream,
|
||||||
|
PW_DIRECTION_INPUT,
|
||||||
|
PW_ID_ANY,
|
||||||
|
static_cast<pw_stream_flags>(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS),
|
||||||
|
params,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
struct spa_hook stream_listener;
|
||||||
|
pw_stream_add_listener(
|
||||||
|
data.stream,
|
||||||
|
&stream_listener,
|
||||||
|
&stream_events,
|
||||||
|
&data
|
||||||
|
);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
pw_loop_iterate(pw_thread_loop_get_loop(data.thread_loop), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pw_proxy_destroy((struct pw_proxy *) data.registry);
|
||||||
|
pw_proxy_destroy(data.sink_proxy);
|
||||||
|
pw_stream_destroy(data.stream);
|
||||||
|
pw_context_destroy(data.context);
|
||||||
|
pw_thread_loop_destroy(data.thread_loop);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user