mirror of
https://repo.dec05eba.com/gpu-screen-recorder
synced 2026-04-02 17:51:42 +09:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f23308444a | ||
|
|
fadf9b64de | ||
|
|
e6f1d47eef | ||
|
|
7af4f106e7 | ||
|
|
a26aa2dd3e | ||
|
|
8364aaadad | ||
|
|
5f3a14d3f6 | ||
|
|
0129ab140d | ||
|
|
0fff47cc58 | ||
|
|
902556b143 | ||
|
|
6024a54551 | ||
|
|
23122ce9b0 | ||
|
|
f071d8c373 | ||
|
|
9bfeb95e39 | ||
|
|
ae2929d4f7 | ||
|
|
d9eb44fae0 | ||
|
|
d9f61602d0 | ||
|
|
a60fa9b68d | ||
|
|
ec092f20c8 | ||
|
|
d12f312bc1 | ||
|
|
34f0eeebcd | ||
|
|
c63c1cfae3 | ||
|
|
7e8d6b3f33 | ||
|
|
0d1560c128 | ||
|
|
5c14babb80 | ||
|
|
ce4a8574f8 |
13
README.md
13
README.md
@@ -26,7 +26,7 @@ Supported image formats:
|
||||
* JPEG
|
||||
* PNG
|
||||
|
||||
This software works on X11 and Wayland on AMD, Intel and NVIDIA.
|
||||
This software works on X11 and Wayland on AMD, Intel and NVIDIA. Replay data is stored in RAM, not disk.
|
||||
### TEMPORARY ISSUES
|
||||
1) Videos are in variable framerate format. Use MPV to play such videos, otherwise you might experience stuttering in the video if you are using a buggy video player. You can try saving the video into a .mkv file instead as some software may have better support for .mkv files (such as kdenlive). You can use the "-fm cfr" option to to use constant framerate mode.
|
||||
2) FLAC audio codec is disabled at the moment because of temporary issues.
|
||||
@@ -68,7 +68,8 @@ Here are some known unofficial packages:
|
||||
* Nix: [NixOS wiki](https://wiki.nixos.org/wiki/Gpu-screen-recorder)
|
||||
* openSUSE: [openSUSE software repository](https://software.opensuse.org/package/gpu-screen-recorder)
|
||||
* Fedora: [Copr](https://copr.fedorainfracloud.org/coprs/brycensranch/gpu-screen-recorder-git/)
|
||||
* OpenMandriva: [gpu-screen-recorder](https://github.com/OpenMandrivaAssociation/gpu-screen-recorder/tree/master)
|
||||
* OpenMandriva: [gpu-screen-recorder](https://github.com/OpenMandrivaAssociation/gpu-screen-recorder)
|
||||
* Solus: [gpu-screen-recorder](https://github.com/getsolus/packages/tree/main/packages/g/gpu-screen-recorder)
|
||||
|
||||
# Dependencies
|
||||
GPU Screen Recorder uses meson build system so you need to install `meson` to build GPU Screen Recorder.
|
||||
@@ -151,12 +152,8 @@ You have to reboot your computer after installing GPU Screen Recorder for the fi
|
||||
# Examples
|
||||
Look at the [scripts](https://git.dec05eba.com/gpu-screen-recorder/tree/scripts) directory for script examples. For example if you want to automatically save a recording/replay into a folder with the same name as the game you are recording.
|
||||
|
||||
# Reporting bugs
|
||||
Issues are reported on this Github page: [https://github.com/dec05eba/gpu-screen-recorder-issues](https://github.com/dec05eba/gpu-screen-recorder-issues).
|
||||
# Contributing patches
|
||||
See [https://git.dec05eba.com/?p=about](https://git.dec05eba.com/?p=about) for contribution steps.
|
||||
# Donations
|
||||
See [https://git.dec05eba.com/?p=about](https://git.dec05eba.com/?p=about) for donation options.
|
||||
# Reporting bugs, contributing patches, questions or donation
|
||||
See [https://git.dec05eba.com/?p=about](https://git.dec05eba.com/?p=about).
|
||||
|
||||
# Demo
|
||||
[](https://www.youtube.com/watch?v=n5tm0g01n6A)
|
||||
|
||||
15
TODO
15
TODO
@@ -178,7 +178,7 @@ Test if `xrandr --output DP-1 --scale 1.5` captures correct size on nvidia.
|
||||
|
||||
Fix cursor position and scale when scaling x11 display.
|
||||
|
||||
Support surround audio in application audio recording. Right now only stereo sound is supported.
|
||||
Support surround audio in application audio recording. Right now only stereo and mono sound is supported.
|
||||
|
||||
Support application audio recording without pulseaudio combined sink.
|
||||
|
||||
@@ -204,6 +204,7 @@ Ffmpeg fixed black bars in videos on amd when using hevc and when recording at s
|
||||
https://github.com/FFmpeg/FFmpeg/commit/d0facac679faf45d3356dff2e2cb382580d7a521
|
||||
Disable gpu screen recorder black bar handling when using hevc on amd when the libavcodec version is the one that comes after those commits.
|
||||
Also consider the mesa version, to see if the gpu supports this.
|
||||
The version is libavcodec >= 61.28.100
|
||||
|
||||
Use opengl compute shader instead of graphics shader. This might allow for better performance when games are using 100% of graphics unit which might fix issue with 100% gpu usage causing gpu screen recorder to run slow when not using vaapi to convert rgb to nv12(?).
|
||||
|
||||
@@ -232,4 +233,14 @@ When adding vulkan video support add VK_VIDEO_ENCODE_TUNING_MODE_LOW_LATENCY_KHR
|
||||
Implement screenshot without invoking opengl (which is slow to start on some systems).
|
||||
|
||||
Automatically use desktop portal on wayland when hdr is enabled (or night light) by checking if kms hdr metadata exists, if hdr video codec is not used.
|
||||
Or maybe do this in the ui?
|
||||
Or maybe do this in the ui?
|
||||
|
||||
Detect if cached portal session token is no longer valid (this can happen if the user switches to another wayland compositor).
|
||||
|
||||
Support reconnecting (and setting things up again) if the audio server is restarted (for both device recording and app recording).
|
||||
|
||||
Find out how nvidia-smi fixes nvenc not working on opensuse and do that ourselves instead of relying on nvidia-smi that is not always installed.
|
||||
|
||||
Pulseaudio code: add "running" variable to loops to allow stopping the running code when quitting.
|
||||
|
||||
Scale screenshot frame libswscale or implement lanczos shader for improved scaline for video as well.
|
||||
|
||||
@@ -37,7 +37,11 @@ void gsr_dbus_deinit(gsr_dbus *self);
|
||||
/* The follow functions should be called in order to setup ScreenCast properly */
|
||||
/* These functions that return an int return the response status code */
|
||||
int gsr_dbus_screencast_create_session(gsr_dbus *self, char **session_handle);
|
||||
int gsr_dbus_screencast_select_sources(gsr_dbus *self, const char *session_handle, gsr_portal_capture_type capture_type, gsr_portal_cursor_mode cursor_mode);
|
||||
/*
|
||||
|capture_type| is a bitmask of gsr_portal_capture_type values. gsr_portal_capture_type values that are not supported by the desktop portal will be ignored.
|
||||
|gsr_portal_cursor_mode| is a bitmask of gsr_portal_cursor_mode values. gsr_portal_cursor_mode values that are not supported will be ignored.
|
||||
*/
|
||||
int gsr_dbus_screencast_select_sources(gsr_dbus *self, const char *session_handle, uint32_t capture_type, uint32_t cursor_mode);
|
||||
int gsr_dbus_screencast_start(gsr_dbus *self, const char *session_handle, uint32_t *pipewire_node);
|
||||
bool gsr_dbus_screencast_open_pipewire_remote(gsr_dbus *self, const char *session_handle, int *pipewire_fd);
|
||||
const char* gsr_dbus_screencast_get_restore_token(gsr_dbus *self);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#define GSR_PIPEWIRE_AUDIO_MAX_STREAM_NODES 128
|
||||
#define GSR_PIPEWIRE_AUDIO_MAX_PORTS 256
|
||||
#define GSR_PIPEWIRE_AUDIO_MAX_LINKS 256
|
||||
#define GSR_PIPEWIRE_AUDIO_MAX_REQUESTED_LINKS 32
|
||||
#define GSR_PIPEWIRE_AUDIO_MAX_VIRTUAL_SINKS 32
|
||||
|
||||
@@ -37,14 +38,31 @@ typedef struct {
|
||||
char *name;
|
||||
} gsr_pipewire_audio_port;
|
||||
|
||||
typedef struct {
|
||||
uint32_t id;
|
||||
uint32_t output_node_id;
|
||||
uint32_t input_node_id;
|
||||
} gsr_pipewire_audio_link;
|
||||
|
||||
typedef enum {
|
||||
GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_STREAM, /* Application */
|
||||
GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_SINK /* Combined (virtual) sink */
|
||||
} gsr_pipewire_audio_link_input_type;
|
||||
|
||||
typedef enum {
|
||||
GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_STANDARD,
|
||||
GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_DEFAULT_OUTPUT,
|
||||
GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_DEFAULT_INPUT
|
||||
} gsr_pipewire_audio_requested_type;
|
||||
|
||||
typedef struct {
|
||||
char **output_names;
|
||||
int num_output_names;
|
||||
char *name;
|
||||
gsr_pipewire_audio_requested_type type;
|
||||
} gsr_pipewire_audio_requested_output;
|
||||
|
||||
typedef struct {
|
||||
gsr_pipewire_audio_requested_output *outputs;
|
||||
int num_outputs;
|
||||
char *input_name;
|
||||
bool inverted;
|
||||
gsr_pipewire_audio_node_type output_type;
|
||||
@@ -60,12 +78,21 @@ typedef struct {
|
||||
struct spa_hook registry_listener;
|
||||
int server_version_sync;
|
||||
|
||||
struct pw_proxy *metadata_proxy;
|
||||
struct spa_hook metadata_listener;
|
||||
struct spa_hook metadata_proxy_listener;
|
||||
char default_output_device_name[128];
|
||||
char default_input_device_name[128];
|
||||
|
||||
gsr_pipewire_audio_node stream_nodes[GSR_PIPEWIRE_AUDIO_MAX_STREAM_NODES];
|
||||
int num_stream_nodes;
|
||||
|
||||
gsr_pipewire_audio_port ports[GSR_PIPEWIRE_AUDIO_MAX_PORTS];
|
||||
int num_ports;
|
||||
|
||||
gsr_pipewire_audio_link links[GSR_PIPEWIRE_AUDIO_MAX_LINKS];
|
||||
int num_links;
|
||||
|
||||
gsr_pipewire_audio_requested_link requested_links[GSR_PIPEWIRE_AUDIO_MAX_REQUESTED_LINKS];
|
||||
int num_requested_links;
|
||||
|
||||
@@ -118,6 +145,8 @@ bool gsr_pipewire_audio_add_link_from_apps_to_sink_inverted(gsr_pipewire_audio *
|
||||
If a device or a new device starts outputting audio after this function is called and the device name matches
|
||||
then it will automatically link the audio sources.
|
||||
|source_names| and |sink_name_input| are case-insensitive matches.
|
||||
|source_names| can include "default_output" or "default_input" to use the default output/input
|
||||
and it will automatically switch when the default output/input is changed in system audio settings.
|
||||
*/
|
||||
bool gsr_pipewire_audio_add_link_from_sources_to_sink(gsr_pipewire_audio *self, const char **source_names, int num_source_names, const char *sink_name_input);
|
||||
|
||||
|
||||
@@ -61,6 +61,9 @@ typedef enum {
|
||||
|
||||
/*
|
||||
Get a sound device by name, returning the device into the |device| parameter.
|
||||
|device_name| can be a device name or "default_output" or "default_input".
|
||||
If the device name is "default_output" or "default_input" then it will automatically switch which
|
||||
device is records from when the default output/input is changed in the system audio settings.
|
||||
Returns 0 on success, or a negative value on failure.
|
||||
*/
|
||||
int sound_device_get_by_name(SoundDevice *device, const char *device_name, const char *description, unsigned int num_channels, unsigned int period_frame_size, AudioFormat audio_format);
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
|
||||
typedef struct {
|
||||
int drmfd;
|
||||
drmModePlaneResPtr planes;
|
||||
} gsr_drm;
|
||||
|
||||
typedef struct {
|
||||
@@ -289,21 +288,31 @@ static int drm_prime_handles_to_fds(gsr_drm *drm, drmModeFB2Ptr drmfb, int *fb_f
|
||||
return GSR_KMS_MAX_DMA_BUFS;
|
||||
}
|
||||
|
||||
static int kms_get_fb(gsr_drm *drm, gsr_kms_response *response, connector_to_crtc_map *c2crtc_map) {
|
||||
static int kms_get_fb(gsr_drm *drm, gsr_kms_response *response) {
|
||||
int result = -1;
|
||||
|
||||
response->result = KMS_RESULT_OK;
|
||||
response->err_msg[0] = '\0';
|
||||
response->num_items = 0;
|
||||
|
||||
for(uint32_t i = 0; i < drm->planes->count_planes && response->num_items < GSR_KMS_MAX_ITEMS; ++i) {
|
||||
connector_to_crtc_map c2crtc_map;
|
||||
c2crtc_map.num_maps = 0;
|
||||
map_crtc_to_connector_ids(drm, &c2crtc_map);
|
||||
|
||||
drmModePlaneResPtr planes = drmModeGetPlaneResources(drm->drmfd);
|
||||
if(!planes) {
|
||||
fprintf(stderr, "kms server error: failed to get plane resources, error: %s\n", strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
|
||||
for(uint32_t i = 0; i < planes->count_planes && response->num_items < GSR_KMS_MAX_ITEMS; ++i) {
|
||||
drmModePlanePtr plane = NULL;
|
||||
drmModeFB2Ptr drmfb = NULL;
|
||||
|
||||
plane = drmModeGetPlane(drm->drmfd, drm->planes->planes[i]);
|
||||
plane = drmModeGetPlane(drm->drmfd, planes->planes[i]);
|
||||
if(!plane) {
|
||||
response->result = KMS_RESULT_FAILED_TO_GET_PLANE;
|
||||
snprintf(response->err_msg, sizeof(response->err_msg), "failed to get drm plane with id %u, error: %s\n", drm->planes->planes[i], strerror(errno));
|
||||
snprintf(response->err_msg, sizeof(response->err_msg), "failed to get drm plane with id %u, error: %s\n", planes->planes[i], strerror(errno));
|
||||
fprintf(stderr, "kms server error: %s\n", response->err_msg);
|
||||
goto next;
|
||||
}
|
||||
@@ -346,7 +355,7 @@ static int kms_get_fb(gsr_drm *drm, gsr_kms_response *response, connector_to_crt
|
||||
|
||||
const int item_index = response->num_items;
|
||||
|
||||
const connector_crtc_pair *crtc_pair = get_connector_pair_by_crtc_id(c2crtc_map, plane->crtc_id);
|
||||
const connector_crtc_pair *crtc_pair = get_connector_pair_by_crtc_id(&c2crtc_map, plane->crtc_id);
|
||||
if(crtc_pair && crtc_pair->hdr_metadata_blob_id) {
|
||||
response->items[item_index].has_hdr_metadata = get_hdr_metadata(drm->drmfd, crtc_pair->hdr_metadata_blob_id, &response->items[item_index].hdr_metadata);
|
||||
} else {
|
||||
@@ -389,6 +398,11 @@ static int kms_get_fb(gsr_drm *drm, gsr_kms_response *response, connector_to_crt
|
||||
drmModeFreePlane(plane);
|
||||
}
|
||||
|
||||
done:
|
||||
|
||||
if(planes)
|
||||
drmModeFreePlaneResources(planes);
|
||||
|
||||
if(response->num_items > 0)
|
||||
response->result = KMS_RESULT_OK;
|
||||
|
||||
@@ -499,7 +513,6 @@ int main(int argc, char **argv) {
|
||||
int socket_fd = 0;
|
||||
gsr_drm drm;
|
||||
drm.drmfd = 0;
|
||||
drm.planes = NULL;
|
||||
|
||||
if(argc != 3) {
|
||||
fprintf(stderr, "usage: gsr-kms-server <domain_socket_path> <card_path>\n");
|
||||
@@ -532,17 +545,6 @@ int main(int argc, char **argv) {
|
||||
fprintf(stderr, "kms server warning: drmSetClientCap DRM_CLIENT_CAP_ATOMIC failed, error: %s. The wrong monitor may be captured as a result\n", strerror(errno));
|
||||
}
|
||||
|
||||
drm.planes = drmModeGetPlaneResources(drm.drmfd);
|
||||
if(!drm.planes) {
|
||||
fprintf(stderr, "kms server error: failed to get plane resources, error: %s\n", strerror(errno));
|
||||
res = 2;
|
||||
goto done;
|
||||
}
|
||||
|
||||
connector_to_crtc_map c2crtc_map;
|
||||
c2crtc_map.num_maps = 0;
|
||||
map_crtc_to_connector_ids(&drm, &c2crtc_map);
|
||||
|
||||
fprintf(stderr, "kms server info: connecting to the client\n");
|
||||
bool connected = false;
|
||||
const double connect_timeout_sec = 5.0;
|
||||
@@ -642,7 +644,7 @@ int main(int argc, char **argv) {
|
||||
response.version = GSR_KMS_PROTOCOL_VERSION;
|
||||
response.num_items = 0;
|
||||
|
||||
if(kms_get_fb(&drm, &response, &c2crtc_map) == 0) {
|
||||
if(kms_get_fb(&drm, &response) == 0) {
|
||||
if(send_msg_to_client(socket_fd, &response) == -1)
|
||||
fprintf(stderr, "kms server error: failed to respond to client KMS_REQUEST_TYPE_GET_KMS request\n");
|
||||
} else {
|
||||
@@ -681,8 +683,6 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
done:
|
||||
if(drm.planes)
|
||||
drmModeFreePlaneResources(drm.planes);
|
||||
if(drm.drmfd > 0)
|
||||
close(drm.drmfd);
|
||||
if(socket_fd > 0)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
project('gpu-screen-recorder', ['c', 'cpp'], version : '5.1.2', default_options : ['warning_level=2'])
|
||||
project('gpu-screen-recorder', ['c', 'cpp'], version : '5.2.3', default_options : ['warning_level=2'])
|
||||
|
||||
add_project_arguments('-Wshadow', language : ['c', 'cpp'])
|
||||
if get_option('buildtype') == 'debug'
|
||||
@@ -22,8 +22,8 @@ src = [
|
||||
'src/codec_query/vaapi.c',
|
||||
'src/codec_query/vulkan.c',
|
||||
'src/window/window.c',
|
||||
'src/window/window_x11.c',
|
||||
'src/window/window_wayland.c',
|
||||
'src/window/x11.c',
|
||||
'src/window/wayland.c',
|
||||
'src/egl.c',
|
||||
'src/cuda.c',
|
||||
'src/xnvctrl.c',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "gpu-screen-recorder"
|
||||
type = "executable"
|
||||
version = "5.1.2"
|
||||
version = "5.2.3"
|
||||
platforms = ["posix"]
|
||||
|
||||
[config]
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
window=$(xdotool selectwindow)
|
||||
window_name=$(xdotool getwindowname "$window" || xdotool getwindowclassname "$window" || echo "Game")
|
||||
window_name="$(echo "$window_name" | tr '/\\' '_')"
|
||||
gpu-screen-recorder -w "$window" -f 60 -c mkv -a default_output -r 60 -o "$HOME/Videos/Replays/$window_name"
|
||||
gpu-screen-recorder -w "$window" -f 60 -c mkv -a default_output -bm cbr -q 45000 -r 60 -o "$HOME/Videos/Replays/$window_name"
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
pidof -q gpu-screen-recorder && exit 0
|
||||
video_path="$HOME/Videos"
|
||||
mkdir -p "$video_path"
|
||||
gpu-screen-recorder -w screen -f 60 -a default_output -c mkv -r 30 -o "$video_path"
|
||||
gpu-screen-recorder -w screen -f 60 -a default_output -c mkv -bm cbr -q 45000 -r 30 -o "$video_path"
|
||||
|
||||
34
src/dbus.c
34
src/dbus.c
@@ -614,9 +614,41 @@ int gsr_dbus_screencast_create_session(gsr_dbus *self, char **session_handle) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gsr_dbus_screencast_select_sources(gsr_dbus *self, const char *session_handle, gsr_portal_capture_type capture_type, gsr_portal_cursor_mode cursor_mode) {
|
||||
static uint32_t unset_unsupported_capture_types(uint32_t requested_capture_types, uint32_t available_capture_types) {
|
||||
if(!(available_capture_types & GSR_PORTAL_CAPTURE_TYPE_MONITOR))
|
||||
requested_capture_types &= ~GSR_PORTAL_CAPTURE_TYPE_MONITOR;
|
||||
if(!(available_capture_types & GSR_PORTAL_CAPTURE_TYPE_WINDOW))
|
||||
requested_capture_types &= ~GSR_PORTAL_CAPTURE_TYPE_WINDOW;
|
||||
if(!(available_capture_types & GSR_PORTAL_CAPTURE_TYPE_VIRTUAL))
|
||||
requested_capture_types &= ~GSR_PORTAL_CAPTURE_TYPE_VIRTUAL;
|
||||
return requested_capture_types;
|
||||
}
|
||||
|
||||
static uint32_t unset_unsupported_cursor_modes(uint32_t requested_cursor_modes, uint32_t available_cursor_modes) {
|
||||
if(!(available_cursor_modes & GSR_PORTAL_CURSOR_MODE_HIDDEN))
|
||||
requested_cursor_modes &= ~GSR_PORTAL_CURSOR_MODE_HIDDEN;
|
||||
if(!(available_cursor_modes & GSR_PORTAL_CURSOR_MODE_EMBEDDED))
|
||||
requested_cursor_modes &= ~GSR_PORTAL_CURSOR_MODE_EMBEDDED;
|
||||
if(!(available_cursor_modes & GSR_PORTAL_CURSOR_MODE_METADATA))
|
||||
requested_cursor_modes &= ~GSR_PORTAL_CURSOR_MODE_METADATA;
|
||||
return requested_cursor_modes;
|
||||
}
|
||||
|
||||
int gsr_dbus_screencast_select_sources(gsr_dbus *self, const char *session_handle, uint32_t capture_type, uint32_t cursor_mode) {
|
||||
assert(session_handle);
|
||||
|
||||
uint32_t available_source_types = 0;
|
||||
gsr_dbus_desktop_portal_get_property(self, "org.freedesktop.portal.ScreenCast", "AvailableSourceTypes", &available_source_types);
|
||||
if(available_source_types == 0)
|
||||
fprintf(stderr, "gsr error: gsr_dbus_screencast_select_sources: no source types are available\n");
|
||||
capture_type = unset_unsupported_capture_types(capture_type, available_source_types);
|
||||
|
||||
uint32_t available_cursor_modes = 0;
|
||||
gsr_dbus_desktop_portal_get_property(self, "org.freedesktop.portal.ScreenCast", "AvailableCursorModes", &available_cursor_modes);
|
||||
if(available_cursor_modes == 0)
|
||||
fprintf(stderr, "gsr error: gsr_dbus_screencast_select_sources: no cursors modes are available\n");
|
||||
cursor_mode = unset_unsupported_cursor_modes(cursor_mode, available_cursor_modes);
|
||||
|
||||
char handle_token[64];
|
||||
gsr_dbus_portal_get_unique_handle_token(self, handle_token, sizeof(handle_token));
|
||||
|
||||
|
||||
10
src/egl.c
10
src/egl.c
@@ -455,6 +455,16 @@ bool gsr_egl_load(gsr_egl *self, gsr_window *window, bool is_monitor_capture, bo
|
||||
}
|
||||
|
||||
gsr_egl_disable_vsync(self);
|
||||
|
||||
if(self->gpu_info.vendor == GSR_GPU_VENDOR_NVIDIA) {
|
||||
/* This fixes nvenc codecs unable to load on openSUSE tumbleweed because of a cuda error. Don't ask me why */
|
||||
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
|
||||
if(inside_flatpak)
|
||||
system("flatpak-spawn --host -- nvidia-smi -f /dev/null");
|
||||
else
|
||||
system("nvidia-smi -f /dev/null");
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
fail:
|
||||
|
||||
22
src/main.cpp
22
src/main.cpp
@@ -16,8 +16,8 @@ extern "C" {
|
||||
#include "../include/codec_query/nvenc.h"
|
||||
#include "../include/codec_query/vaapi.h"
|
||||
#include "../include/codec_query/vulkan.h"
|
||||
#include "../include/window/window_x11.h"
|
||||
#include "../include/window/window_wayland.h"
|
||||
#include "../include/window/x11.h"
|
||||
#include "../include/window/wayland.h"
|
||||
#include "../include/egl.h"
|
||||
#include "../include/utils.h"
|
||||
#include "../include/damage.h"
|
||||
@@ -2480,16 +2480,16 @@ static gsr_color_range image_format_to_color_range(gsr_image_format image_format
|
||||
static int video_quality_to_image_quality_value(VideoQuality video_quality) {
|
||||
switch(video_quality) {
|
||||
case VideoQuality::MEDIUM:
|
||||
return 60;
|
||||
return 75;
|
||||
case VideoQuality::HIGH:
|
||||
return 70;
|
||||
return 85;
|
||||
case VideoQuality::VERY_HIGH:
|
||||
return 80;
|
||||
return 90;
|
||||
case VideoQuality::ULTRA:
|
||||
return 95;
|
||||
return 97;
|
||||
}
|
||||
assert(false);
|
||||
return 80;
|
||||
return 90;
|
||||
}
|
||||
|
||||
// TODO: 10-bit and hdr.
|
||||
@@ -2539,10 +2539,12 @@ static void capture_image_to_file(const char *filepath, std::string &window_str,
|
||||
bool should_stop_error = false;
|
||||
egl->glClear(0);
|
||||
|
||||
while(true) {
|
||||
while(running) {
|
||||
should_stop_error = false;
|
||||
if(gsr_capture_should_stop(capture, &should_stop_error))
|
||||
if(gsr_capture_should_stop(capture, &should_stop_error)) {
|
||||
running = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// It can fail, for example when capturing portal and the target is a monitor that hasn't been updated.
|
||||
// Desktop portal wont refresh the image until there is an update.
|
||||
@@ -2644,14 +2646,12 @@ static std::vector<MergedAudioInputs> parse_audio_inputs(const AudioDevices &aud
|
||||
fprintf(stderr, "Error: -a default_output was specified but no default audio output is specified in the audio server\n");
|
||||
_exit(2);
|
||||
}
|
||||
request_audio_input.name = audio_devices.default_output;
|
||||
match = true;
|
||||
} else if(request_audio_input.name == "default_input") {
|
||||
if(audio_devices.default_input.empty()) {
|
||||
fprintf(stderr, "Error: -a default_input was specified but no default audio input is specified in the audio server\n");
|
||||
_exit(2);
|
||||
}
|
||||
request_audio_input.name = audio_devices.default_input;
|
||||
match = true;
|
||||
} else {
|
||||
const bool name_is_existing_audio_device = get_audio_device_by_name(audio_devices.audio_inputs, request_audio_input.name.c_str()) != nullptr;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "../include/pipewire_audio.h"
|
||||
|
||||
#include <pipewire/pipewire.h>
|
||||
#include <pipewire/extensions/metadata.h>
|
||||
#include <pipewire/impl-module.h>
|
||||
|
||||
static void on_core_info_cb(void *user_data, const struct pw_core_info *info) {
|
||||
gsr_pipewire_audio *self = user_data;
|
||||
@@ -44,13 +46,106 @@ static gsr_pipewire_audio_port* gsr_pipewire_audio_get_node_port_by_name(gsr_pip
|
||||
}
|
||||
|
||||
static bool requested_link_matches_name_case_insensitive(const gsr_pipewire_audio_requested_link *requested_link, const char *name) {
|
||||
for(int i = 0; i < requested_link->num_output_names; ++i) {
|
||||
if(strcasecmp(requested_link->output_names[i], name) == 0)
|
||||
for(int i = 0; i < requested_link->num_outputs; ++i) {
|
||||
if(requested_link->outputs[i].type == GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_STANDARD && strcasecmp(requested_link->outputs[i].name, name) == 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool requested_link_has_type(const gsr_pipewire_audio_requested_link *requested_link, gsr_pipewire_audio_requested_type type) {
|
||||
for(int i = 0; i < requested_link->num_outputs; ++i) {
|
||||
if(requested_link->outputs[i].type == type)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void gsr_pipewire_get_node_input_port_by_type(gsr_pipewire_audio *self, const gsr_pipewire_audio_node *input_node, gsr_pipewire_audio_link_input_type input_type,
|
||||
const gsr_pipewire_audio_port **input_fl_port, const gsr_pipewire_audio_port **input_fr_port)
|
||||
{
|
||||
*input_fl_port = NULL;
|
||||
*input_fr_port = NULL;
|
||||
|
||||
switch(input_type) {
|
||||
case GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_STREAM: {
|
||||
*input_fl_port = gsr_pipewire_audio_get_node_port_by_name(self, input_node->id, "input_FL");
|
||||
*input_fr_port = gsr_pipewire_audio_get_node_port_by_name(self, input_node->id, "input_FR");
|
||||
break;
|
||||
}
|
||||
case GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_SINK: {
|
||||
*input_fl_port = gsr_pipewire_audio_get_node_port_by_name(self, input_node->id, "playback_FL");
|
||||
*input_fr_port = gsr_pipewire_audio_get_node_port_by_name(self, input_node->id, "playback_FR");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void gsr_pipewire_get_node_output_port_by_type(gsr_pipewire_audio *self, const gsr_pipewire_audio_node *output_node, gsr_pipewire_audio_node_type output_type,
|
||||
const gsr_pipewire_audio_port **output_fl_port, const gsr_pipewire_audio_port **output_fr_port)
|
||||
{
|
||||
*output_fl_port = NULL;
|
||||
*output_fr_port = NULL;
|
||||
|
||||
switch(output_type) {
|
||||
case GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_OUTPUT:
|
||||
*output_fl_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "output_FL");
|
||||
*output_fr_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "output_FR");
|
||||
break;
|
||||
case GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_INPUT:
|
||||
*output_fl_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "monitor_FL");
|
||||
*output_fr_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "monitor_FR");
|
||||
break;
|
||||
case GSR_PIPEWIRE_AUDIO_NODE_TYPE_SINK_OR_SOURCE: {
|
||||
*output_fl_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "monitor_FL");
|
||||
*output_fr_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "monitor_FR");
|
||||
if(!*output_fl_port || !*output_fr_port) {
|
||||
*output_fl_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "capture_FL");
|
||||
*output_fr_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "capture_FR");
|
||||
}
|
||||
if(!*output_fl_port || !*output_fr_port) {
|
||||
const gsr_pipewire_audio_port *output_mono_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "monitor_MONO");
|
||||
if(!output_mono_port)
|
||||
output_mono_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "capture_MONO");
|
||||
|
||||
if(output_mono_port) {
|
||||
*output_fl_port = output_mono_port;
|
||||
*output_fr_port = output_mono_port;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void gsr_pipewire_audio_establish_link(gsr_pipewire_audio *self, const gsr_pipewire_audio_port *input_fl_port, const gsr_pipewire_audio_port *input_fr_port,
|
||||
const gsr_pipewire_audio_port *output_fl_port, const gsr_pipewire_audio_port *output_fr_port)
|
||||
{
|
||||
// TODO: Detect if link already exists before so we dont create these proxies when not needed
|
||||
|
||||
//fprintf(stderr, "linking!\n");
|
||||
// TODO: error check and cleanup
|
||||
{
|
||||
struct pw_properties *props = pw_properties_new(NULL, NULL);
|
||||
pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%u", output_fl_port->id);
|
||||
pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%u", input_fl_port->id);
|
||||
// TODO: Clean this up when removing node
|
||||
struct pw_proxy *proxy = pw_core_create_object(self->core, "link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &props->dict, 0);
|
||||
//self->server_version_sync = pw_core_sync(self->core, PW_ID_CORE, self->server_version_sync);
|
||||
pw_properties_free(props);
|
||||
}
|
||||
|
||||
{
|
||||
struct pw_properties *props = pw_properties_new(NULL, NULL);
|
||||
pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%u", output_fr_port->id);
|
||||
pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%u", input_fr_port->id);
|
||||
// TODO: Clean this up when removing node
|
||||
struct pw_proxy *proxy = pw_core_create_object(self->core, "link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &props->dict, 0);
|
||||
//self->server_version_sync = pw_core_sync(self->core, PW_ID_CORE, self->server_version_sync);
|
||||
pw_properties_free(props);
|
||||
}
|
||||
}
|
||||
|
||||
static void gsr_pipewire_audio_create_link(gsr_pipewire_audio *self, const gsr_pipewire_audio_requested_link *requested_link) {
|
||||
const gsr_pipewire_audio_node_type requested_link_node_type = requested_link->input_type == GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_STREAM ? GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_INPUT : GSR_PIPEWIRE_AUDIO_NODE_TYPE_SINK_OR_SOURCE;
|
||||
const gsr_pipewire_audio_node *stream_input_node = gsr_pipewire_audio_get_node_by_name_case_insensitive(self, requested_link->input_name, requested_link_node_type);
|
||||
@@ -59,20 +154,7 @@ static void gsr_pipewire_audio_create_link(gsr_pipewire_audio *self, const gsr_p
|
||||
|
||||
const gsr_pipewire_audio_port *input_fl_port = NULL;
|
||||
const gsr_pipewire_audio_port *input_fr_port = NULL;
|
||||
|
||||
switch(requested_link->input_type) {
|
||||
case GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_STREAM: {
|
||||
input_fl_port = gsr_pipewire_audio_get_node_port_by_name(self, stream_input_node->id, "input_FL");
|
||||
input_fr_port = gsr_pipewire_audio_get_node_port_by_name(self, stream_input_node->id, "input_FR");
|
||||
break;
|
||||
}
|
||||
case GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_SINK: {
|
||||
input_fl_port = gsr_pipewire_audio_get_node_port_by_name(self, stream_input_node->id, "playback_FL");
|
||||
input_fr_port = gsr_pipewire_audio_get_node_port_by_name(self, stream_input_node->id, "playback_FR");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
gsr_pipewire_get_node_input_port_by_type(self, stream_input_node, requested_link->input_type, &input_fl_port, &input_fr_port);
|
||||
if(!input_fl_port || !input_fr_port)
|
||||
return;
|
||||
|
||||
@@ -92,68 +174,206 @@ static void gsr_pipewire_audio_create_link(gsr_pipewire_audio *self, const gsr_p
|
||||
|
||||
const gsr_pipewire_audio_port *output_fl_port = NULL;
|
||||
const gsr_pipewire_audio_port *output_fr_port = NULL;
|
||||
|
||||
switch(requested_link->output_type) {
|
||||
case GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_OUTPUT:
|
||||
output_fl_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "output_FL");
|
||||
output_fr_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "output_FR");
|
||||
break;
|
||||
case GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_INPUT:
|
||||
output_fl_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "monitor_FL");
|
||||
output_fr_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "monitor_FR");
|
||||
break;
|
||||
case GSR_PIPEWIRE_AUDIO_NODE_TYPE_SINK_OR_SOURCE: {
|
||||
output_fl_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "monitor_FL");
|
||||
output_fr_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "monitor_FR");
|
||||
if(!output_fl_port || !output_fr_port) {
|
||||
output_fl_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "capture_FL");
|
||||
output_fr_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "capture_FR");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
gsr_pipewire_get_node_output_port_by_type(self, output_node, requested_link->output_type, &output_fl_port, &output_fr_port);
|
||||
if(!output_fl_port || !output_fr_port)
|
||||
continue;
|
||||
|
||||
// TODO: Detect if link already exists before so we dont create these proxies when not needed
|
||||
|
||||
//fprintf(stderr, "linking!\n");
|
||||
// TODO: error check and cleanup
|
||||
{
|
||||
struct pw_properties *props = pw_properties_new(NULL, NULL);
|
||||
pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%u", output_fl_port->id);
|
||||
pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%u", input_fl_port->id);
|
||||
// TODO: Clean this up when removing node
|
||||
struct pw_proxy *proxy = pw_core_create_object(self->core, "link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &props->dict, 0);
|
||||
//self->server_version_sync = pw_core_sync(self->core, PW_ID_CORE, self->server_version_sync);
|
||||
pw_properties_free(props);
|
||||
}
|
||||
|
||||
{
|
||||
struct pw_properties *props = pw_properties_new(NULL, NULL);
|
||||
pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%u", output_fr_port->id);
|
||||
pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%u", input_fr_port->id);
|
||||
// TODO: Clean this up when removing node
|
||||
struct pw_proxy *proxy = pw_core_create_object(self->core, "link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &props->dict, 0);
|
||||
//self->server_version_sync = pw_core_sync(self->core, PW_ID_CORE, self->server_version_sync);
|
||||
pw_properties_free(props);
|
||||
}
|
||||
gsr_pipewire_audio_establish_link(self, input_fl_port, input_fr_port, output_fl_port, output_fr_port);
|
||||
}
|
||||
}
|
||||
|
||||
static void gsr_pipewire_audio_create_links(gsr_pipewire_audio *self) {
|
||||
for(int j = 0; j < self->num_requested_links; ++j) {
|
||||
gsr_pipewire_audio_create_link(self, &self->requested_links[j]);
|
||||
for(int i = 0; i < self->num_requested_links; ++i) {
|
||||
gsr_pipewire_audio_create_link(self, &self->requested_links[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void gsr_pipewire_audio_create_link_for_default_devices(gsr_pipewire_audio *self, const gsr_pipewire_audio_requested_link *requested_link, gsr_pipewire_audio_requested_type default_device_type) {
|
||||
if(default_device_type == GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_STANDARD)
|
||||
return;
|
||||
|
||||
const char *device_name = default_device_type == GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_DEFAULT_OUTPUT ? self->default_output_device_name : self->default_input_device_name;
|
||||
if(device_name[0] == '\0')
|
||||
return;
|
||||
|
||||
if(!requested_link_has_type(requested_link, default_device_type))
|
||||
return;
|
||||
|
||||
const gsr_pipewire_audio_node_type requested_link_node_type = requested_link->input_type == GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_STREAM ? GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_INPUT : GSR_PIPEWIRE_AUDIO_NODE_TYPE_SINK_OR_SOURCE;
|
||||
const gsr_pipewire_audio_node *stream_input_node = gsr_pipewire_audio_get_node_by_name_case_insensitive(self, requested_link->input_name, requested_link_node_type);
|
||||
if(!stream_input_node)
|
||||
return;
|
||||
|
||||
const gsr_pipewire_audio_port *input_fl_port = NULL;
|
||||
const gsr_pipewire_audio_port *input_fr_port = NULL;
|
||||
gsr_pipewire_get_node_input_port_by_type(self, stream_input_node, requested_link->input_type, &input_fl_port, &input_fr_port);
|
||||
if(!input_fl_port || !input_fr_port)
|
||||
return;
|
||||
|
||||
const gsr_pipewire_audio_node *stream_output_node = gsr_pipewire_audio_get_node_by_name_case_insensitive(self, device_name, GSR_PIPEWIRE_AUDIO_NODE_TYPE_SINK_OR_SOURCE);
|
||||
if(!stream_output_node)
|
||||
return;
|
||||
|
||||
const gsr_pipewire_audio_port *output_fl_port = NULL;
|
||||
const gsr_pipewire_audio_port *output_fr_port = NULL;
|
||||
gsr_pipewire_get_node_output_port_by_type(self, stream_output_node, requested_link->output_type, &output_fl_port, &output_fr_port);
|
||||
if(!output_fl_port || !output_fr_port)
|
||||
return;
|
||||
|
||||
gsr_pipewire_audio_establish_link(self, input_fl_port, input_fr_port, output_fl_port, output_fr_port);
|
||||
//fprintf(stderr, "establishing a link from %u to %u\n", stream_output_node->id, stream_input_node->id);
|
||||
}
|
||||
|
||||
static void gsr_pipewire_audio_create_links_for_default_devices(gsr_pipewire_audio *self, gsr_pipewire_audio_requested_type default_device_type) {
|
||||
for(int i = 0; i < self->num_requested_links; ++i) {
|
||||
gsr_pipewire_audio_create_link_for_default_devices(self, &self->requested_links[i], default_device_type);
|
||||
}
|
||||
}
|
||||
|
||||
static void gsr_pipewire_audio_destroy_links_by_output_to_input(gsr_pipewire_audio *self, uint32_t output_node_id, uint32_t input_node_id) {
|
||||
for(int i = 0; i < self->num_links; ++i) {
|
||||
if(self->links[i].output_node_id == output_node_id && self->links[i].input_node_id == input_node_id)
|
||||
pw_registry_destroy(self->registry, self->links[i].id);
|
||||
}
|
||||
}
|
||||
|
||||
static void gsr_pipewire_destroy_default_device_link(gsr_pipewire_audio *self, const gsr_pipewire_audio_requested_link *requested_link, gsr_pipewire_audio_requested_type default_device_type) {
|
||||
if(default_device_type == GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_STANDARD)
|
||||
return;
|
||||
|
||||
const char *device_name = default_device_type == GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_DEFAULT_OUTPUT ? self->default_output_device_name : self->default_input_device_name;
|
||||
if(device_name[0] == '\0')
|
||||
return;
|
||||
|
||||
if(!requested_link_has_type(requested_link, default_device_type))
|
||||
return;
|
||||
|
||||
/* default_output and default_input can be the same device. In that case both are the same link and we dont want to remove the link */
|
||||
const gsr_pipewire_audio_requested_type opposite_device_type = default_device_type == GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_DEFAULT_OUTPUT ? GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_DEFAULT_INPUT : GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_DEFAULT_OUTPUT;
|
||||
const char *opposite_device_name = opposite_device_type == GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_DEFAULT_OUTPUT ? self->default_output_device_name : self->default_input_device_name;
|
||||
if(requested_link_has_type(requested_link, opposite_device_type) && strcmp(device_name, opposite_device_name) == 0)
|
||||
return;
|
||||
|
||||
const gsr_pipewire_audio_node_type requested_link_node_type = requested_link->input_type == GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_STREAM ? GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_INPUT : GSR_PIPEWIRE_AUDIO_NODE_TYPE_SINK_OR_SOURCE;
|
||||
const gsr_pipewire_audio_node *stream_input_node = gsr_pipewire_audio_get_node_by_name_case_insensitive(self, requested_link->input_name, requested_link_node_type);
|
||||
if(!stream_input_node)
|
||||
return;
|
||||
|
||||
const gsr_pipewire_audio_node *stream_output_node = gsr_pipewire_audio_get_node_by_name_case_insensitive(self, device_name, GSR_PIPEWIRE_AUDIO_NODE_TYPE_SINK_OR_SOURCE);
|
||||
if(!stream_output_node)
|
||||
return;
|
||||
|
||||
if(requested_link_matches_name_case_insensitive(requested_link, stream_output_node->name))
|
||||
return;
|
||||
|
||||
gsr_pipewire_audio_destroy_links_by_output_to_input(self, stream_output_node->id, stream_input_node->id);
|
||||
//fprintf(stderr, "destroying a link from %u to %u\n", stream_output_node->id, stream_input_node->id);
|
||||
}
|
||||
|
||||
static void gsr_pipewire_destroy_default_device_links(gsr_pipewire_audio *self, gsr_pipewire_audio_requested_type default_device_type) {
|
||||
for(int i = 0; i < self->num_requested_links; ++i) {
|
||||
gsr_pipewire_destroy_default_device_link(self, &self->requested_links[i], default_device_type);
|
||||
}
|
||||
}
|
||||
|
||||
static bool json_get_value(const char *json_str, const char *key, char *value, size_t value_size) {
|
||||
char key_full[32];
|
||||
const int key_full_size = snprintf(key_full, sizeof(key_full), "\"%s\":", key);
|
||||
const char *start = strstr(json_str, key_full);
|
||||
if(!start)
|
||||
return false;
|
||||
|
||||
start += key_full_size;
|
||||
const char *value_start = strchr(start, '"');
|
||||
if(!value_start)
|
||||
return false;
|
||||
|
||||
value_start += 1;
|
||||
const char *value_end = strchr(value_start, '"');
|
||||
if(!value_end)
|
||||
return false;
|
||||
|
||||
snprintf(value, value_size, "%.*s", (int)(value_end - value_start), value_start);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int on_metadata_property_cb(void *data, uint32_t id, const char *key, const char *type, const char *value) {
|
||||
(void)type;
|
||||
gsr_pipewire_audio *self = data;
|
||||
|
||||
if(id == PW_ID_CORE && key && value) {
|
||||
char value_decoded[128];
|
||||
if(strcmp(key, "default.audio.sink") == 0) {
|
||||
if(json_get_value(value, "name", value_decoded, sizeof(value_decoded)) && strcmp(value_decoded, self->default_output_device_name) != 0) {
|
||||
gsr_pipewire_destroy_default_device_links(self, GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_DEFAULT_OUTPUT);
|
||||
snprintf(self->default_output_device_name, sizeof(self->default_output_device_name), "%s", value_decoded);
|
||||
gsr_pipewire_audio_create_links_for_default_devices(self, GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_DEFAULT_OUTPUT);
|
||||
}
|
||||
} else if(strcmp(key, "default.audio.source") == 0) {
|
||||
if(json_get_value(value, "name", value_decoded, sizeof(value_decoded)) && strcmp(value_decoded, self->default_input_device_name) != 0) {
|
||||
gsr_pipewire_destroy_default_device_links(self, GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_DEFAULT_INPUT);
|
||||
snprintf(self->default_input_device_name, sizeof(self->default_input_device_name), "%s", value_decoded);
|
||||
gsr_pipewire_audio_create_links_for_default_devices(self, GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_DEFAULT_INPUT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pw_metadata_events metadata_events = {
|
||||
PW_VERSION_METADATA_EVENTS,
|
||||
.property = on_metadata_property_cb,
|
||||
};
|
||||
|
||||
static void on_metadata_proxy_removed_cb(void *data) {
|
||||
gsr_pipewire_audio *self = data;
|
||||
if(self->metadata_proxy) {
|
||||
pw_proxy_destroy(self->metadata_proxy);
|
||||
self->metadata_proxy = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void on_metadata_proxy_destroy_cb(void *data) {
|
||||
gsr_pipewire_audio *self = data;
|
||||
|
||||
spa_hook_remove(&self->metadata_listener);
|
||||
spa_hook_remove(&self->metadata_proxy_listener);
|
||||
spa_zero(self->metadata_listener);
|
||||
spa_zero(self->metadata_proxy_listener);
|
||||
|
||||
self->metadata_proxy = NULL;
|
||||
}
|
||||
|
||||
static const struct pw_proxy_events metadata_proxy_events = {
|
||||
PW_VERSION_PROXY_EVENTS,
|
||||
.removed = on_metadata_proxy_removed_cb,
|
||||
.destroy = on_metadata_proxy_destroy_cb,
|
||||
};
|
||||
|
||||
static bool gsr_pipewire_audio_listen_on_metadata(gsr_pipewire_audio *self, uint32_t id) {
|
||||
if(self->metadata_proxy) {
|
||||
pw_proxy_destroy(self->metadata_proxy);
|
||||
self->metadata_proxy = NULL;
|
||||
}
|
||||
|
||||
self->metadata_proxy = pw_registry_bind(self->registry, id, PW_TYPE_INTERFACE_Metadata, PW_VERSION_METADATA, 0);
|
||||
if(!self->metadata_proxy) {
|
||||
fprintf(stderr, "gsr error: gsr_pipewire_audio_listen_on_metadata: failed to bind to registry\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
pw_proxy_add_object_listener(self->metadata_proxy, &self->metadata_listener, &metadata_events, self);
|
||||
pw_proxy_add_listener(self->metadata_proxy, &self->metadata_proxy_listener, &metadata_proxy_events, self);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void registry_event_global(void *data, uint32_t id, uint32_t permissions,
|
||||
const char *type, uint32_t version,
|
||||
const struct spa_dict *props)
|
||||
{
|
||||
//fprintf(stderr, "add: id: %d, type: %s\n", (int)id, type);
|
||||
if (props == NULL)
|
||||
if(!props || !type)
|
||||
return;
|
||||
|
||||
//pw_properties_new_dict(props);
|
||||
@@ -162,7 +382,7 @@ static void registry_event_global(void *data, uint32_t id, uint32_t permissions,
|
||||
if(strcmp(type, PW_TYPE_INTERFACE_Node) == 0) {
|
||||
const char *node_name = spa_dict_lookup(props, PW_KEY_NODE_NAME);
|
||||
const char *media_class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
|
||||
//fprintf(stderr, " node name: %s, media class: %s\n", node_name, media_class);
|
||||
//fprintf(stderr, " node id: %u, node name: %s, media class: %s\n", id, node_name, media_class);
|
||||
const bool is_stream_output = media_class && strcmp(media_class, "Stream/Output/Audio") == 0;
|
||||
const bool is_stream_input = media_class && strcmp(media_class, "Stream/Input/Audio") == 0;
|
||||
const bool is_sink = media_class && strcmp(media_class, "Audio/Sink") == 0;
|
||||
@@ -206,6 +426,7 @@ static void registry_event_global(void *data, uint32_t id, uint32_t permissions,
|
||||
//fprintf(stderr, " port name: %s, node id: %d, direction: %s\n", port_name, node_id_num, port_direction);
|
||||
char *port_name_copy = strdup(port_name);
|
||||
if(port_name_copy) {
|
||||
//fprintf(stderr, " port id: %u, node id: %u, name: %s\n", id, node_id_num, port_name_copy);
|
||||
self->ports[self->num_ports].id = id;
|
||||
self->ports[self->num_ports].node_id = node_id_num;
|
||||
self->ports[self->num_ports].direction = direction;
|
||||
@@ -217,6 +438,25 @@ static void registry_event_global(void *data, uint32_t id, uint32_t permissions,
|
||||
} else if(self->num_ports >= GSR_PIPEWIRE_AUDIO_MAX_PORTS) {
|
||||
fprintf(stderr, "gsr error: reached the maximum amount of audio ports\n");
|
||||
}
|
||||
} else if(strcmp(type, PW_TYPE_INTERFACE_Link) == 0) {
|
||||
const char *output_node = spa_dict_lookup(props, PW_KEY_LINK_OUTPUT_NODE);
|
||||
const char *input_node = spa_dict_lookup(props, PW_KEY_LINK_INPUT_NODE);
|
||||
|
||||
const uint32_t output_node_id_num = output_node ? atoi(output_node) : 0;
|
||||
const uint32_t input_node_id_num = input_node ? atoi(input_node) : 0;
|
||||
if(self->num_links < GSR_PIPEWIRE_AUDIO_MAX_LINKS && output_node_id_num > 0 && input_node_id_num > 0) {
|
||||
//fprintf(stderr, " new link (%u): %u -> %u\n", id, output_node_id_num, input_node_id_num);
|
||||
self->links[self->num_links].id = id;
|
||||
self->links[self->num_links].output_node_id = output_node_id_num;
|
||||
self->links[self->num_links].input_node_id = input_node_id_num;
|
||||
++self->num_links;
|
||||
} else if(self->num_ports >= GSR_PIPEWIRE_AUDIO_MAX_LINKS) {
|
||||
fprintf(stderr, "gsr error: reached the maximum amount of audio links\n");
|
||||
}
|
||||
} else if(strcmp(type, PW_TYPE_INTERFACE_Metadata) == 0) {
|
||||
const char *name = spa_dict_lookup(props, PW_KEY_METADATA_NAME);
|
||||
if(name && strcmp(name, "default") == 0)
|
||||
gsr_pipewire_audio_listen_on_metadata(self, id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,9 +466,7 @@ static bool gsr_pipewire_audio_remove_node_by_id(gsr_pipewire_audio *self, uint3
|
||||
continue;
|
||||
|
||||
free(self->stream_nodes[i].name);
|
||||
for(int j = i + 1; j < self->num_stream_nodes; ++j) {
|
||||
self->stream_nodes[j - 1] = self->stream_nodes[j];
|
||||
}
|
||||
self->stream_nodes[i] = self->stream_nodes[self->num_stream_nodes - 1];
|
||||
--self->num_stream_nodes;
|
||||
return true;
|
||||
}
|
||||
@@ -241,15 +479,25 @@ static bool gsr_pipewire_audio_remove_port_by_id(gsr_pipewire_audio *self, uint3
|
||||
continue;
|
||||
|
||||
free(self->ports[i].name);
|
||||
for(int j = i + 1; j < self->num_ports; ++j) {
|
||||
self->ports[j - 1] = self->ports[j];
|
||||
}
|
||||
self->ports[i] = self->ports[self->num_ports - 1];
|
||||
--self->num_ports;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool gsr_pipewire_audio_remove_link_by_id(gsr_pipewire_audio *self, uint32_t link_id) {
|
||||
for(int i = 0; i < self->num_links; ++i) {
|
||||
if(self->links[i].id != link_id)
|
||||
continue;
|
||||
|
||||
self->links[i] = self->links[self->num_links - 1];
|
||||
--self->num_links;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void registry_event_global_remove(void *data, uint32_t id) {
|
||||
//fprintf(stderr, "remove: %d\n", (int)id);
|
||||
gsr_pipewire_audio *self = (gsr_pipewire_audio*)data;
|
||||
@@ -262,6 +510,11 @@ static void registry_event_global_remove(void *data, uint32_t id) {
|
||||
//fprintf(stderr, "removed port\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if(gsr_pipewire_audio_remove_link_by_id(self, id)) {
|
||||
//fprintf(stderr, "removed link\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct pw_registry_events registry_events = {
|
||||
@@ -289,6 +542,8 @@ bool gsr_pipewire_audio_init(gsr_pipewire_audio *self) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pw_context_load_module(self->context, "libpipewire-module-link-factory", NULL, NULL);
|
||||
|
||||
if(pw_thread_loop_start(self->thread_loop) < 0) {
|
||||
fprintf(stderr, "gsr error: gsr_pipewire_audio_init: failed to start thread\n");
|
||||
gsr_pipewire_audio_deinit(self);
|
||||
@@ -307,11 +562,12 @@ bool gsr_pipewire_audio_init(gsr_pipewire_audio *self) {
|
||||
// TODO: Error check
|
||||
pw_core_add_listener(self->core, &self->core_listener, &core_events, self);
|
||||
|
||||
self->server_version_sync = pw_core_sync(self->core, PW_ID_CORE, 0);
|
||||
pw_thread_loop_wait(self->thread_loop);
|
||||
|
||||
self->registry = pw_core_get_registry(self->core, PW_VERSION_REGISTRY, 0);
|
||||
pw_registry_add_listener(self->registry, &self->registry_listener, ®istry_events, self);
|
||||
|
||||
self->server_version_sync = pw_core_sync(self->core, PW_ID_CORE, 0);
|
||||
pw_thread_loop_wait(self->thread_loop);
|
||||
pw_thread_loop_unlock(self->thread_loop);
|
||||
return true;
|
||||
}
|
||||
@@ -330,6 +586,15 @@ void gsr_pipewire_audio_deinit(gsr_pipewire_audio *self) {
|
||||
}
|
||||
self->num_virtual_sink_proxies = 0;
|
||||
|
||||
if(self->metadata_proxy) {
|
||||
spa_hook_remove(&self->metadata_listener);
|
||||
spa_hook_remove(&self->metadata_proxy_listener);
|
||||
pw_proxy_destroy(self->metadata_proxy);
|
||||
spa_zero(self->metadata_listener);
|
||||
spa_zero(self->metadata_proxy_listener);
|
||||
self->metadata_proxy = NULL;
|
||||
}
|
||||
|
||||
if(self->core) {
|
||||
pw_core_disconnect(self->core);
|
||||
self->core = NULL;
|
||||
@@ -355,11 +620,13 @@ void gsr_pipewire_audio_deinit(gsr_pipewire_audio *self) {
|
||||
}
|
||||
self->num_ports = 0;
|
||||
|
||||
self->num_links = 0;
|
||||
|
||||
for(int i = 0; i < self->num_requested_links; ++i) {
|
||||
for(int j = 0; j < self->requested_links[i].num_output_names; ++j) {
|
||||
free(self->requested_links[i].output_names[j]);
|
||||
for(int j = 0; j < self->requested_links[i].num_outputs; ++j) {
|
||||
free(self->requested_links[i].outputs[j].name);
|
||||
}
|
||||
free(self->requested_links[i].output_names);
|
||||
free(self->requested_links[i].outputs);
|
||||
free(self->requested_links[i].input_name);
|
||||
}
|
||||
self->num_requested_links = 0;
|
||||
@@ -428,14 +695,14 @@ static bool string_remove_suffix(char *str, const char *suffix) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool gsr_pipewire_audio_add_link_from_apps_to_output(gsr_pipewire_audio *self, const char **output_names, int num_output_names, const char *input_name, gsr_pipewire_audio_node_type output_type, gsr_pipewire_audio_link_input_type input_type, bool inverted) {
|
||||
static bool gsr_pipewire_audio_add_links_to_output(gsr_pipewire_audio *self, const char **output_names, int num_output_names, const char *input_name, gsr_pipewire_audio_node_type output_type, gsr_pipewire_audio_link_input_type input_type, bool inverted) {
|
||||
if(self->num_requested_links >= GSR_PIPEWIRE_AUDIO_MAX_REQUESTED_LINKS) {
|
||||
fprintf(stderr, "gsr error: reached the maximum amount of audio links\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
char **output_names_copy = calloc(num_output_names, sizeof(char*));
|
||||
if(!output_names_copy)
|
||||
gsr_pipewire_audio_requested_output *outputs = calloc(num_output_names, sizeof(gsr_pipewire_audio_requested_output));
|
||||
if(!outputs)
|
||||
return false;
|
||||
|
||||
char *input_name_copy = strdup(input_name);
|
||||
@@ -446,23 +713,34 @@ static bool gsr_pipewire_audio_add_link_from_apps_to_output(gsr_pipewire_audio *
|
||||
string_remove_suffix(input_name_copy, ".monitor");
|
||||
|
||||
for(int i = 0; i < num_output_names; ++i) {
|
||||
output_names_copy[i] = strdup(output_names[i]);
|
||||
if(!output_names_copy[i])
|
||||
outputs[i].name = strdup(output_names[i]);
|
||||
if(!outputs[i].name)
|
||||
goto error;
|
||||
|
||||
if(output_type == GSR_PIPEWIRE_AUDIO_NODE_TYPE_SINK_OR_SOURCE)
|
||||
string_remove_suffix(output_names_copy[i], ".monitor");
|
||||
outputs[i].type = GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_STANDARD;
|
||||
if(output_type == GSR_PIPEWIRE_AUDIO_NODE_TYPE_SINK_OR_SOURCE) {
|
||||
string_remove_suffix(outputs[i].name, ".monitor");
|
||||
|
||||
if(strcmp(outputs[i].name, "default_output") == 0)
|
||||
outputs[i].type = GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_DEFAULT_OUTPUT;
|
||||
else if(strcmp(outputs[i].name, "default_input") == 0)
|
||||
outputs[i].type = GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_DEFAULT_INPUT;
|
||||
else
|
||||
outputs[i].type = GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_STANDARD;
|
||||
}
|
||||
}
|
||||
|
||||
pw_thread_loop_lock(self->thread_loop);
|
||||
self->requested_links[self->num_requested_links].output_names = output_names_copy;
|
||||
self->requested_links[self->num_requested_links].num_output_names = num_output_names;
|
||||
self->requested_links[self->num_requested_links].outputs = outputs;
|
||||
self->requested_links[self->num_requested_links].num_outputs = num_output_names;
|
||||
self->requested_links[self->num_requested_links].input_name = input_name_copy;
|
||||
self->requested_links[self->num_requested_links].output_type = output_type;
|
||||
self->requested_links[self->num_requested_links].input_type = input_type;
|
||||
self->requested_links[self->num_requested_links].inverted = inverted;
|
||||
++self->num_requested_links;
|
||||
gsr_pipewire_audio_create_link(self, &self->requested_links[self->num_requested_links - 1]);
|
||||
gsr_pipewire_audio_create_link_for_default_devices(self, &self->requested_links[self->num_requested_links - 1], GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_DEFAULT_OUTPUT);
|
||||
gsr_pipewire_audio_create_link_for_default_devices(self, &self->requested_links[self->num_requested_links - 1], GSR_PIPEWIRE_AUDIO_REQUESTED_TYPE_DEFAULT_INPUT);
|
||||
pw_thread_loop_unlock(self->thread_loop);
|
||||
|
||||
return true;
|
||||
@@ -470,30 +748,30 @@ static bool gsr_pipewire_audio_add_link_from_apps_to_output(gsr_pipewire_audio *
|
||||
error:
|
||||
free(input_name_copy);
|
||||
for(int i = 0; i < num_output_names; ++i) {
|
||||
free(output_names_copy[i]);
|
||||
free(outputs[i].name);
|
||||
}
|
||||
free(output_names_copy);
|
||||
free(outputs);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool gsr_pipewire_audio_add_link_from_apps_to_stream(gsr_pipewire_audio *self, const char **app_names, int num_app_names, const char *stream_name_input) {
|
||||
return gsr_pipewire_audio_add_link_from_apps_to_output(self, app_names, num_app_names, stream_name_input, GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_OUTPUT, GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_STREAM, false);
|
||||
return gsr_pipewire_audio_add_links_to_output(self, app_names, num_app_names, stream_name_input, GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_OUTPUT, GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_STREAM, false);
|
||||
}
|
||||
|
||||
bool gsr_pipewire_audio_add_link_from_apps_to_stream_inverted(gsr_pipewire_audio *self, const char **app_names, int num_app_names, const char *stream_name_input) {
|
||||
return gsr_pipewire_audio_add_link_from_apps_to_output(self, app_names, num_app_names, stream_name_input, GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_OUTPUT, GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_STREAM, true);
|
||||
return gsr_pipewire_audio_add_links_to_output(self, app_names, num_app_names, stream_name_input, GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_OUTPUT, GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_STREAM, true);
|
||||
}
|
||||
|
||||
bool gsr_pipewire_audio_add_link_from_apps_to_sink(gsr_pipewire_audio *self, const char **app_names, int num_app_names, const char *sink_name_input) {
|
||||
return gsr_pipewire_audio_add_link_from_apps_to_output(self, app_names, num_app_names, sink_name_input, GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_OUTPUT, GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_SINK, false);
|
||||
return gsr_pipewire_audio_add_links_to_output(self, app_names, num_app_names, sink_name_input, GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_OUTPUT, GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_SINK, false);
|
||||
}
|
||||
|
||||
bool gsr_pipewire_audio_add_link_from_apps_to_sink_inverted(gsr_pipewire_audio *self, const char **app_names, int num_app_names, const char *sink_name_input) {
|
||||
return gsr_pipewire_audio_add_link_from_apps_to_output(self, app_names, num_app_names, sink_name_input, GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_OUTPUT, GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_SINK, true);
|
||||
return gsr_pipewire_audio_add_links_to_output(self, app_names, num_app_names, sink_name_input, GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_OUTPUT, GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_SINK, true);
|
||||
}
|
||||
|
||||
bool gsr_pipewire_audio_add_link_from_sources_to_sink(gsr_pipewire_audio *self, const char **source_names, int num_source_names, const char *sink_name_input) {
|
||||
return gsr_pipewire_audio_add_link_from_apps_to_output(self, source_names, num_source_names, sink_name_input, GSR_PIPEWIRE_AUDIO_NODE_TYPE_SINK_OR_SOURCE, GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_SINK, false);
|
||||
return gsr_pipewire_audio_add_links_to_output(self, source_names, num_source_names, sink_name_input, GSR_PIPEWIRE_AUDIO_NODE_TYPE_SINK_OR_SOURCE, GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_SINK, false);
|
||||
}
|
||||
|
||||
void gsr_pipewire_audio_for_each_app(gsr_pipewire_audio *self, gsr_pipewire_audio_app_query_callback callback, void *userdata) {
|
||||
|
||||
@@ -413,6 +413,7 @@ static void renegotiate_format(void *data, uint64_t expirations) {
|
||||
uint8_t params_buffer[4096];
|
||||
struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer));
|
||||
if (!gsr_pipewire_video_build_format_params(self, &pod_builder, params, &num_video_formats)) {
|
||||
fprintf(stderr, "gsr error: renegotiate_format: failed to build formats\n");
|
||||
pw_thread_loop_unlock(self->thread_loop);
|
||||
return;
|
||||
}
|
||||
@@ -509,6 +510,9 @@ static bool gsr_pipewire_video_setup_stream(gsr_pipewire_video *self) {
|
||||
// TODO: Error check
|
||||
pw_core_add_listener(self->core, &self->core_listener, &core_events, self);
|
||||
|
||||
self->server_version_sync = pw_core_sync(self->core, PW_ID_CORE, 0);
|
||||
pw_thread_loop_wait(self->thread_loop);
|
||||
|
||||
gsr_pipewire_video_init_modifiers(self);
|
||||
|
||||
// TODO: Cleanup?
|
||||
@@ -519,9 +523,6 @@ static bool gsr_pipewire_video_setup_stream(gsr_pipewire_video *self) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
self->server_version_sync = pw_core_sync(self->core, PW_ID_CORE, 0);
|
||||
pw_thread_loop_wait(self->thread_loop);
|
||||
|
||||
self->stream = pw_stream_new(self->core, "com.dec05eba.gpu_screen_recorder",
|
||||
pw_properties_new(PW_KEY_MEDIA_TYPE, "Video",
|
||||
PW_KEY_MEDIA_CATEGORY, "Capture",
|
||||
|
||||
206
src/sound.cpp
206
src/sound.cpp
@@ -8,12 +8,16 @@ extern "C" {
|
||||
#include <string.h>
|
||||
#include <cmath>
|
||||
#include <time.h>
|
||||
#include <mutex>
|
||||
|
||||
#include <pulse/pulseaudio.h>
|
||||
#include <pulse/mainloop.h>
|
||||
#include <pulse/xmalloc.h>
|
||||
#include <pulse/error.h>
|
||||
|
||||
#define RECONNECT_TRY_TIMEOUT_SECONDS 0.5
|
||||
#define DEVICE_NAME_MAX_SIZE 128
|
||||
|
||||
#define CHECK_DEAD_GOTO(p, rerror, label) \
|
||||
do { \
|
||||
if (!(p)->context || !PA_CONTEXT_IS_GOOD(pa_context_get_state((p)->context)) || \
|
||||
@@ -29,6 +33,12 @@ extern "C" {
|
||||
} \
|
||||
} while(false);
|
||||
|
||||
enum class DeviceType {
|
||||
STANDARD,
|
||||
DEFAULT_OUTPUT,
|
||||
DEFAULT_INPUT
|
||||
};
|
||||
|
||||
struct pa_handle {
|
||||
pa_context *context;
|
||||
pa_stream *stream;
|
||||
@@ -42,6 +52,19 @@ struct pa_handle {
|
||||
|
||||
int operation_success;
|
||||
double latency_seconds;
|
||||
|
||||
pa_buffer_attr attr;
|
||||
pa_sample_spec ss;
|
||||
|
||||
std::mutex reconnect_mutex;
|
||||
DeviceType device_type;
|
||||
char stream_name[256];
|
||||
bool reconnect;
|
||||
double reconnect_last_tried_seconds;
|
||||
|
||||
char device_name[DEVICE_NAME_MAX_SIZE];
|
||||
char default_output_device_name[DEVICE_NAME_MAX_SIZE];
|
||||
char default_input_device_name[DEVICE_NAME_MAX_SIZE];
|
||||
};
|
||||
|
||||
static void pa_sound_device_free(pa_handle *p) {
|
||||
@@ -71,22 +94,110 @@ static void pa_sound_device_free(pa_handle *p) {
|
||||
pa_xfree(p);
|
||||
}
|
||||
|
||||
static void subscribe_update_default_devices(pa_context*, const pa_server_info *server_info, void *userdata) {
|
||||
pa_handle *handle = (pa_handle*)userdata;
|
||||
std::lock_guard<std::mutex> lock(handle->reconnect_mutex);
|
||||
|
||||
if(server_info->default_sink_name) {
|
||||
// TODO: Size check
|
||||
snprintf(handle->default_output_device_name, sizeof(handle->default_output_device_name), "%s.monitor", server_info->default_sink_name);
|
||||
if(handle->device_type == DeviceType::DEFAULT_OUTPUT && strcmp(handle->device_name, handle->default_output_device_name) != 0) {
|
||||
handle->reconnect = true;
|
||||
handle->reconnect_last_tried_seconds = clock_get_monotonic_seconds();
|
||||
// TODO: Size check
|
||||
snprintf(handle->device_name, sizeof(handle->device_name), "%s", handle->default_output_device_name);
|
||||
}
|
||||
}
|
||||
|
||||
if(server_info->default_source_name) {
|
||||
// TODO: Size check
|
||||
snprintf(handle->default_input_device_name, sizeof(handle->default_input_device_name), "%s", server_info->default_source_name);
|
||||
if(handle->device_type == DeviceType::DEFAULT_INPUT && strcmp(handle->device_name, handle->default_input_device_name) != 0) {
|
||||
handle->reconnect = true;
|
||||
handle->reconnect_last_tried_seconds = clock_get_monotonic_seconds();
|
||||
// TODO: Size check
|
||||
snprintf(handle->device_name, sizeof(handle->device_name), "%s", handle->default_input_device_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
|
||||
(void)idx;
|
||||
pa_handle *handle = (pa_handle*)userdata;
|
||||
if((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SERVER) {
|
||||
pa_operation *pa = pa_context_get_server_info(c, subscribe_update_default_devices, handle);
|
||||
if(pa)
|
||||
pa_operation_unref(pa);
|
||||
}
|
||||
}
|
||||
|
||||
static void store_default_devices(pa_context*, const pa_server_info *server_info, void *userdata) {
|
||||
pa_handle *handle = (pa_handle*)userdata;
|
||||
if(server_info->default_sink_name)
|
||||
snprintf(handle->default_output_device_name, sizeof(handle->default_output_device_name), "%s.monitor", server_info->default_sink_name);
|
||||
if(server_info->default_source_name)
|
||||
snprintf(handle->default_input_device_name, sizeof(handle->default_input_device_name), "%s", server_info->default_source_name);
|
||||
}
|
||||
|
||||
static bool startup_get_default_devices(pa_handle *p, const char *device_name) {
|
||||
pa_operation *pa = pa_context_get_server_info(p->context, store_default_devices, p);
|
||||
while(pa) {
|
||||
pa_operation_state state = pa_operation_get_state(pa);
|
||||
if(state == PA_OPERATION_DONE) {
|
||||
pa_operation_unref(pa);
|
||||
break;
|
||||
} else if(state == PA_OPERATION_CANCELLED) {
|
||||
pa_operation_unref(pa);
|
||||
return false;
|
||||
}
|
||||
pa_mainloop_iterate(p->mainloop, 1, NULL);
|
||||
}
|
||||
|
||||
if(p->default_output_device_name[0] == '\0') {
|
||||
fprintf(stderr, "Error: failed to find default audio output device\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(strcmp(device_name, "default_output") == 0) {
|
||||
snprintf(p->device_name, sizeof(p->device_name), "%s", p->default_output_device_name);
|
||||
p->device_type = DeviceType::DEFAULT_OUTPUT;
|
||||
} else if(strcmp(device_name, "default_input") == 0) {
|
||||
snprintf(p->device_name, sizeof(p->device_name), "%s", p->default_input_device_name);
|
||||
p->device_type = DeviceType::DEFAULT_INPUT;
|
||||
} else {
|
||||
snprintf(p->device_name, sizeof(p->device_name), "%s", device_name);
|
||||
p->device_type = DeviceType::STANDARD;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static pa_handle* pa_sound_device_new(const char *server,
|
||||
const char *name,
|
||||
const char *dev,
|
||||
const char *device_name,
|
||||
const char *stream_name,
|
||||
const pa_sample_spec *ss,
|
||||
const pa_buffer_attr *attr,
|
||||
int *rerror) {
|
||||
pa_handle *p;
|
||||
int error = PA_ERR_INTERNAL, r;
|
||||
int error = PA_ERR_INTERNAL;
|
||||
pa_operation *pa = NULL;
|
||||
|
||||
p = pa_xnew0(pa_handle, 1);
|
||||
p->attr = *attr;
|
||||
p->ss = *ss;
|
||||
snprintf(p->stream_name, sizeof(p->stream_name), "%s", stream_name);
|
||||
|
||||
p->reconnect = true;
|
||||
p->reconnect_last_tried_seconds = clock_get_monotonic_seconds() - 1000.0;
|
||||
p->default_output_device_name[0] = '\0';
|
||||
p->default_input_device_name[0] = '\0';
|
||||
p->device_type = DeviceType::STANDARD;
|
||||
|
||||
const int buffer_size = attr->fragsize;
|
||||
void *buffer = malloc(buffer_size);
|
||||
if(!buffer) {
|
||||
fprintf(stderr, "failed to allocate buffer for audio\n");
|
||||
fprintf(stderr, "Error: failed to allocate buffer for audio\n");
|
||||
*rerror = -1;
|
||||
return NULL;
|
||||
}
|
||||
@@ -120,32 +231,13 @@ static pa_handle* pa_sound_device_new(const char *server,
|
||||
pa_mainloop_iterate(p->mainloop, 1, NULL);
|
||||
}
|
||||
|
||||
if (!(p->stream = pa_stream_new(p->context, stream_name, ss, NULL))) {
|
||||
error = pa_context_errno(p->context);
|
||||
if(!startup_get_default_devices(p, device_name))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
r = pa_stream_connect_record(p->stream, dev, attr,
|
||||
(pa_stream_flags_t)(PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_ADJUST_LATENCY|PA_STREAM_AUTO_TIMING_UPDATE));
|
||||
|
||||
if (r < 0) {
|
||||
error = pa_context_errno(p->context);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
pa_stream_state_t state = pa_stream_get_state(p->stream);
|
||||
|
||||
if (state == PA_STREAM_READY)
|
||||
break;
|
||||
|
||||
if (!PA_STREAM_IS_GOOD(state)) {
|
||||
error = pa_context_errno(p->context);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pa_mainloop_iterate(p->mainloop, 1, NULL);
|
||||
}
|
||||
pa_context_set_subscribe_callback(p->context, subscribe_cb, p);
|
||||
pa = pa_context_subscribe(p->context, PA_SUBSCRIPTION_MASK_SERVER, NULL, NULL);
|
||||
if(pa)
|
||||
pa_operation_unref(pa);
|
||||
|
||||
return p;
|
||||
|
||||
@@ -156,10 +248,65 @@ fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool pa_sound_device_should_reconnect(pa_handle *p, double now, char *device_name, size_t device_name_size) {
|
||||
std::lock_guard<std::mutex> lock(p->reconnect_mutex);
|
||||
if(p->reconnect && now - p->reconnect_last_tried_seconds >= RECONNECT_TRY_TIMEOUT_SECONDS) {
|
||||
p->reconnect_last_tried_seconds = now;
|
||||
// TODO: Size check
|
||||
snprintf(device_name, device_name_size, "%s", p->device_name);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool pa_sound_device_handle_reconnect(pa_handle *p, char *device_name, size_t device_name_size, double now) {
|
||||
int r;
|
||||
if(!pa_sound_device_should_reconnect(p, now, device_name, device_name_size))
|
||||
return true;
|
||||
|
||||
if(p->stream) {
|
||||
pa_stream_disconnect(p->stream);
|
||||
pa_stream_unref(p->stream);
|
||||
p->stream = NULL;
|
||||
}
|
||||
|
||||
if(!(p->stream = pa_stream_new(p->context, p->stream_name, &p->ss, NULL))) {
|
||||
//pa_context_errno(p->context);
|
||||
return false;
|
||||
}
|
||||
|
||||
r = pa_stream_connect_record(p->stream, device_name, &p->attr,
|
||||
(pa_stream_flags_t)(PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_ADJUST_LATENCY|PA_STREAM_AUTO_TIMING_UPDATE));
|
||||
|
||||
if(r < 0) {
|
||||
//pa_context_errno(p->context);
|
||||
return false;
|
||||
}
|
||||
|
||||
for(;;) {
|
||||
pa_stream_state_t state = pa_stream_get_state(p->stream);
|
||||
|
||||
if(state == PA_STREAM_READY)
|
||||
break;
|
||||
|
||||
if(!PA_STREAM_IS_GOOD(state)) {
|
||||
//pa_context_errno(p->context);
|
||||
return false;
|
||||
}
|
||||
|
||||
pa_mainloop_iterate(p->mainloop, 1, NULL);
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(p->reconnect_mutex);
|
||||
p->reconnect = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int pa_sound_device_read(pa_handle *p, double timeout_seconds) {
|
||||
assert(p);
|
||||
|
||||
const double start_time = clock_get_monotonic_seconds();
|
||||
char device_name[DEVICE_NAME_MAX_SIZE];
|
||||
|
||||
bool success = false;
|
||||
int r = 0;
|
||||
@@ -167,6 +314,9 @@ static int pa_sound_device_read(pa_handle *p, double timeout_seconds) {
|
||||
pa_usec_t latency = 0;
|
||||
int negative = 0;
|
||||
|
||||
if(!pa_sound_device_handle_reconnect(p, device_name, sizeof(device_name), start_time))
|
||||
goto fail;
|
||||
|
||||
CHECK_DEAD_GOTO(p, rerror, fail);
|
||||
|
||||
while (p->output_index < p->output_length) {
|
||||
@@ -276,7 +426,7 @@ int sound_device_get_by_name(SoundDevice *device, const char *device_name, const
|
||||
int error = 0;
|
||||
pa_handle *handle = pa_sound_device_new(nullptr, description, device_name, description, &ss, &buffer_attr, &error);
|
||||
if(!handle) {
|
||||
fprintf(stderr, "pa_sound_device_new() failed: %s. Audio input device %s might not be valid\n", pa_strerror(error), device_name);
|
||||
fprintf(stderr, "Error: pa_sound_device_new() failed: %s. Audio input device %s might not be valid\n", pa_strerror(error), device_name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "../../include/window/window_wayland.h"
|
||||
#include "../../include/window/wayland.h"
|
||||
|
||||
#include "../../include/vec2.h"
|
||||
#include "../../include/defs.h"
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "../../include/window/window_x11.h"
|
||||
#include "../../include/window/x11.h"
|
||||
|
||||
#include "../../include/vec2.h"
|
||||
#include "../../include/defs.h"
|
||||
Reference in New Issue
Block a user