Get PipeWire somewhat running

This commit is contained in:
PapaTutuWawa 2023-12-27 19:50:50 +01:00
parent 84f9a04272
commit d756cc559e
6 changed files with 562 additions and 5 deletions

View File

@ -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
View 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
View 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
View File

@ -0,0 +1 @@
void init_pipewire();

View File

@ -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
View 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, &registry_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);
}