Added ability to stream specific monitor on Linux

This commit is contained in:
kiralycraft 2021-02-28 15:52:47 +02:00
parent 415dec37ad
commit 7abcfc0390
4 changed files with 463 additions and 302 deletions

View File

@ -94,6 +94,7 @@ else()
xcb xcb
xcb-shm xcb-shm
xcb-xfixes xcb-xfixes
Xrandr
${X11_LIBRARIES} ${X11_LIBRARIES}
evdev evdev
pulse pulse

View File

@ -93,6 +93,9 @@
# adapter_name = Radeon RX 580 Series # adapter_name = Radeon RX 580 Series
# output_name = \\.\DISPLAY1 # output_name = \\.\DISPLAY1
# !! Linux only !!
# Set the display number to stream. I have no idea how they are numbered. They start from 0.
linux_monitor_id = 3
############################################### ###############################################
# FFmpeg software encoding parameters # FFmpeg software encoding parameters
@ -102,7 +105,7 @@
# Constant Rate Factor. Between 1 and 52. It allows QP to go up during motion and down with still image, resulting in constant perceived quality # Constant Rate Factor. Between 1 and 52. It allows QP to go up during motion and down with still image, resulting in constant perceived quality
# Higher value means more compression, but less quality # Higher value means more compression, but less quality
# If crf == 0, then use QP directly instead # If crf == 0, then use QP directly instead
# crf = 0 crf = 18
# Quantitization Parameter # Quantitization Parameter
# Higher value means more compression, but less quality # Higher value means more compression, but less quality
@ -113,7 +116,7 @@
# Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually # Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually
# worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest # worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest
# value that can reliably encode at your desired streaming settings on your hardware. # value that can reliably encode at your desired streaming settings on your hardware.
# min_threads = 1 min_threads = 1
# Allows the client to request HEVC Main or HEVC Main10 video streams. # Allows the client to request HEVC Main or HEVC Main10 video streams.
# HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding. # HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding.
@ -121,14 +124,14 @@
# If set to 1, Sunshine will not advertise support for HEVC # If set to 1, Sunshine will not advertise support for HEVC
# If set to 2, Sunshine will advertise support for HEVC Main profile # If set to 2, Sunshine will advertise support for HEVC Main profile
# If set to 3, Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles # If set to 3, Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles
# hevc_mode = 0 hevc_mode = 0
# Force a specific encoder, otherwise Sunshine will use the first encoder that is available # Force a specific encoder, otherwise Sunshine will use the first encoder that is available
# supported encoders: # supported encoders:
# nvenc # nvenc
# software # software
# #
# encoder = nvenc # encoder = vaapi
##################################### Software ##################################### ##################################### Software #####################################
# See x264 --fullhelp for the different presets # See x264 --fullhelp for the different presets
# sw_preset = superfast # sw_preset = superfast

View File

@ -29,6 +29,8 @@ struct video_t {
std::string encoder; std::string encoder;
std::string adapter_name; std::string adapter_name;
std::string output_name; std::string output_name;
int linux_monitor_id; //Only used on linux
}; };
struct audio_t { struct audio_t {

View File

@ -14,6 +14,7 @@
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <X11/Xutil.h> #include <X11/Xutil.h>
#include <X11/extensions/Xfixes.h> #include <X11/extensions/Xfixes.h>
#include <X11/extensions/Xrandr.h>
#include <xcb/shm.h> #include <xcb/shm.h>
#include <xcb/xfixes.h> #include <xcb/xfixes.h>
#include <sys/ipc.h> #include <sys/ipc.h>
@ -26,11 +27,12 @@
#include "sunshine/config.h" #include "sunshine/config.h"
#include "sunshine/main.h" #include "sunshine/main.h"
namespace platf { namespace platf
{
using namespace std::literals; using namespace std::literals;
void freeImage(XImage *); void freeImage(XImage*);
void freeX(XFixesCursorImage *); void freeX(XFixesCursorImage*);
using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>; using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>;
using xcb_connect_t = util::safe_ptr<xcb_connection_t, xcb_disconnect>; using xcb_connect_t = util::safe_ptr<xcb_connection_t, xcb_disconnect>;
@ -41,407 +43,560 @@ using xdisplay_t = util::safe_ptr_v2<Display, int, XCloseDisplay>;
using ximg_t = util::safe_ptr<XImage, freeImage>; using ximg_t = util::safe_ptr<XImage, freeImage>;
using xcursor_t = util::safe_ptr<XFixesCursorImage, freeX>; using xcursor_t = util::safe_ptr<XFixesCursorImage, freeX>;
class shm_id_t { class shm_id_t
{
public: public:
shm_id_t() : id { -1 } {} shm_id_t() : id { -1 }
shm_id_t(int id) : id {id } {} {
shm_id_t(shm_id_t &&other) noexcept : id(other.id) { }
other.id = -1; shm_id_t(int id) : id { id }
} {
}
shm_id_t(shm_id_t &&other) noexcept : id(other.id)
{
other.id = -1;
}
~shm_id_t() { ~shm_id_t()
if(id != -1) { {
shmctl(id, IPC_RMID, nullptr); if (id != -1)
id = -1; {
} shmctl(id, IPC_RMID, nullptr);
} id = -1;
int id; }
}
int id;
}; };
class shm_data_t { class shm_data_t
{
public: public:
shm_data_t() : data {(void*)-1 } {} shm_data_t() : data { (void*) -1 }
shm_data_t(void *data) : data {data } {} {
}
shm_data_t(void *data) : data { data }
{
}
shm_data_t(shm_data_t &&other) noexcept : data(other.data) { shm_data_t(shm_data_t &&other) noexcept : data(other.data)
other.data = (void*)-1; {
} other.data = (void*) -1;
}
~shm_data_t() { ~shm_data_t()
if((std::uintptr_t)data != -1) { {
shmdt(data); if ((std::uintptr_t) data != -1)
data = (void*)-1; {
} shmdt(data);
} data = (void*) -1;
}
}
void *data; void *data;
}; };
struct x11_img_t : public img_t { struct x11_img_t: public img_t
ximg_t img; {
ximg_t img;
}; };
struct shm_img_t : public img_t { struct shm_img_t: public img_t
~shm_img_t() override { {
delete[] data; ~shm_img_t() override
data = nullptr; {
} delete[] data;
data = nullptr;
}
}; };
void blend_cursor(Display *display, img_t &img) { void blend_cursor(Display *display, img_t &img, int offsetX,int offsetY)
xcursor_t overlay { XFixesGetCursorImage(display) }; {
xcursor_t overlay { XFixesGetCursorImage(display) };
if(!overlay) { if (!overlay)
BOOST_LOG(error) << "Couldn't get cursor from XFixesGetCursorImage"sv; {
return; BOOST_LOG(error)
} << "Couldn't get cursor from XFixesGetCursorImage"sv;
return;
}
overlay->x -= overlay->xhot; overlay->x -= overlay->xhot;
overlay->y -= overlay->yhot; overlay->y -= overlay->yhot;
overlay->x = std::max((short)0, overlay->x); overlay->x -= offsetX;
overlay->y = std::max((short)0, overlay->y); overlay->y -= offsetY;
auto pixels = (int*)img.data; overlay->x = std::max((short) 0, overlay->x);
overlay->y = std::max((short) 0, overlay->y);
auto screen_height = img.height; auto pixels = (int*) img.data;
auto screen_width = img.width;
auto delta_height = std::min<uint16_t>(overlay->height, std::max(0, screen_height - overlay->y)); auto screen_height = img.height;
auto delta_width = std::min<uint16_t>(overlay->width, std::max(0, screen_width - overlay->x)); auto screen_width = img.width;
for(auto y = 0; y < delta_height; ++y) {
auto overlay_begin = &overlay->pixels[y * overlay->width]; auto delta_height = std::min<uint16_t>(overlay->height,std::max(0, screen_height - overlay->y));
auto overlay_end = &overlay->pixels[y * overlay->width + delta_width]; auto delta_width = std::min<uint16_t>(overlay->width,std::max(0, screen_width - overlay->x));
for (auto y = 0; y < delta_height; ++y)
{
auto overlay_begin = &overlay->pixels[y * overlay->width];
auto overlay_end = &overlay->pixels[y * overlay->width + delta_width];
auto pixels_begin = &pixels[(y + overlay->y) * (img.row_pitch / img.pixel_pitch) + overlay->x]; auto pixels_begin = &pixels[(y + overlay->y)* (img.row_pitch / img.pixel_pitch) + overlay->x];
std::for_each(overlay_begin, overlay_end, [&](long pixel) {
int *pixel_p = (int*)&pixel;
auto colors_in = (uint8_t*)pixels_begin; std::for_each(overlay_begin, overlay_end,[&](long pixel)
{
int *pixel_p = (int*) &pixel;
auto alpha = (*(uint*)pixel_p) >> 24u; auto colors_in = (uint8_t*) pixels_begin;
if(alpha == 255) {
*pixels_begin = *pixel_p; auto alpha = (*(uint*) pixel_p) >> 24u;
} if (alpha == 255)
else { {
auto colors_out = (uint8_t*)pixel_p; *pixels_begin = *pixel_p;
colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255/2) / 255; }
colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255/2) / 255; else
colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255/2) / 255; {
} auto colors_out = (uint8_t*) pixel_p;
++pixels_begin; colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) +255 /2) / 255;
}); colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255;
} colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255;
}
++pixels_begin;
});
}
} }
struct x11_attr_t : public display_t { struct x11_attr_t: public display_t
x11_attr_t() : xdisplay {XOpenDisplay(nullptr) }, xwindow { }, xattr {} { {
if(!xdisplay) { xdisplay_t xdisplay;
BOOST_LOG(fatal) << "Could not open x11 display"sv; Window xwindow;
log_flush(); XWindowAttributes xattr;
std::abort();
}
xwindow = DefaultRootWindow(xdisplay.get()); Display* displayDisplay;
refresh(); /*
* Remember last X (NOT the streamed monitor!) size. This way we can trigger reinitialization if the dimensions changed while streaming
*/
int lastWidth,lastHeight;
width = xattr.width; /*
height = xattr.height; * Offsets for when streaming a specific monitor. By default, they are 0.
} */
int displayOffsetX,displayOffsetY;
void refresh() { x11_attr_t() : xdisplay { displayDisplay = XOpenDisplay(nullptr) }, xwindow { }, xattr { }
XGetWindowAttributes(xdisplay.get(), xwindow, &xattr); {
} XInitThreads();
if (!xdisplay)
{
BOOST_LOG(fatal)
<< "Could not open x11 display"sv;
log_flush();
std::abort();
}
capture_e snapshot(img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) override { xwindow = DefaultRootWindow(xdisplay.get());
refresh();
if(width != xattr.width || height != xattr.height) { refresh();
return capture_e::reinit;
}
XImage *img { XGetImage( int streamedMonitor = config::video.linux_monitor_id;
xdisplay.get(),
xwindow,
0, 0,
xattr.width, xattr.height,
AllPlanes, ZPixmap)
};
auto img_out = (x11_img_t*)img_out_base; if (streamedMonitor != -1) //If the value has been set at all
img_out->width = img->width; {
img_out->height = img->height; BOOST_LOG(warning) << "Configuring selected monitor to stream. If it fails here, you may need Xrandr >= 1.5"sv;
img_out->data = (uint8_t*)img->data; XRRScreenResources *screenr = XRRGetScreenResources(displayDisplay, xwindow);
img_out->row_pitch = img->bytes_per_line; // This is the key right here. Use XRRScreenResources::noutput
img_out->pixel_pitch = img->bits_per_pixel / 8; int output = screenr->noutput;
img_out->img.reset(img);
if(cursor) { if (streamedMonitor >= output)
blend_cursor(xdisplay.get(), *img_out_base); {
} BOOST_LOG(error)<< "Could not stream selected display number because there aren't so many."sv;
}
else
{
XRROutputInfo* out_info = XRRGetOutputInfo(displayDisplay, screenr, screenr->outputs[streamedMonitor]);
if (NULL == out_info || out_info->connection != RR_Connected)
{
BOOST_LOG(error)<< "Could not stream selected display because it doesn't seem to be connected"sv;
}
else
{
XRRCrtcInfo* crt_info = XRRGetCrtcInfo(displayDisplay, screenr, out_info->crtc);
BOOST_LOG(info)<<"Streaming display: "<<out_info->name<<" with res "<<crt_info->width<<" x "<<crt_info->height<<+" offset by "<<crt_info->x<<" x "<<crt_info->y<<"."sv;
return capture_e::ok; width = crt_info -> width;
} height = crt_info -> height;
displayOffsetX = crt_info -> x;
displayOffsetY = crt_info -> y;
std::shared_ptr<img_t> alloc_img() override { XRRFreeCrtcInfo(crt_info);
return std::make_shared<x11_img_t>(); }
} XRRFreeOutputInfo(out_info);
}
XRRFreeScreenResources(screenr);
}
else
{
width = xattr.width;
height = xattr.height;
}
int dummy_img(img_t *img) override { lastWidth = xattr.width;
snapshot(img, 0s, true); lastHeight = xattr.height;
return 0; }
}
xdisplay_t xdisplay; /**
Window xwindow; * Called when the display attributes should change.
XWindowAttributes xattr; */
void refresh()
{
XGetWindowAttributes(xdisplay.get(), xwindow, &xattr); //Update xattr's
}
capture_e snapshot(img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) override
{
refresh();
capture_e toReturn;
if (xattr.width != lastWidth || xattr.height != lastHeight) //The whole X server changed, so we gotta reinit everything
{
BOOST_LOG(warning)<< "X dimensions changed in non-SHM mode, request reinit"sv;
toReturn = capture_e::reinit;
}
else
{
XImage *img { XGetImage(xdisplay.get(), xwindow, displayOffsetX, displayOffsetY, width,height, AllPlanes, ZPixmap) };
auto img_out = (x11_img_t*) img_out_base;
img_out->width = img->width;
img_out->height = img->height;
img_out->data = (uint8_t*) img->data;
img_out->row_pitch = img->bytes_per_line;
img_out->pixel_pitch = img->bits_per_pixel / 8;
img_out->img.reset(img);
if (cursor)
{
blend_cursor(xdisplay.get(), *img_out_base,displayOffsetX,displayOffsetY);
}
toReturn = capture_e::ok;
}
return toReturn;
}
std::shared_ptr<img_t> alloc_img() override
{
return std::make_shared<x11_img_t>();
}
int dummy_img(img_t *img) override
{
snapshot(img, 0s, true);
return 0;
}
}; };
struct shm_attr_t : public x11_attr_t { struct shm_attr_t: public x11_attr_t
xdisplay_t shm_xdisplay; // Prevent race condition with x11_attr_t::xdisplay {
xcb_connect_t xcb; xdisplay_t shm_xdisplay; // Prevent race condition with x11_attr_t::xdisplay
xcb_screen_t *display; xcb_connect_t xcb;
std::uint32_t seg; xcb_screen_t *display;
std::uint32_t seg;
shm_id_t shm_id; shm_id_t shm_id;
shm_data_t data; shm_data_t data;
util::TaskPool::task_id_t refresh_task_id; util::TaskPool::task_id_t refresh_task_id;
void delayed_refresh() { void delayed_refresh()
refresh(); {
refresh();
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id; refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh,2s, this).task_id;
} }
shm_attr_t() : x11_attr_t(), shm_xdisplay {XOpenDisplay(nullptr) } { shm_attr_t() : x11_attr_t(), shm_xdisplay { XOpenDisplay(nullptr) }
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id; {
} refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh,2s, this).task_id;
}
~shm_attr_t() override { ~shm_attr_t() override
while(!task_pool.cancel(refresh_task_id)); {
} while (!task_pool.cancel(refresh_task_id));
}
capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) override { capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) override
if(width != xattr.width || height != xattr.height) { {
return capture_e::reinit; capture_e toReturn;
} if (xattr.width != lastWidth || xattr.height != lastHeight) //The whole X server changed, so we gotta reinit everything
{
BOOST_LOG(warning)<< "X dimensions changed in SHM mode, request reinit"sv;
toReturn = capture_e::reinit;
}
else
{
auto img_cookie = xcb_shm_get_image_unchecked(xcb.get(), display->root,displayOffsetX, displayOffsetY, width, height, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, seg, 0);
auto img_cookie = xcb_shm_get_image_unchecked( xcb_img_t img_reply { xcb_shm_get_image_reply(xcb.get(), img_cookie,nullptr) };
xcb.get(), if (!img_reply)
display->root, {
0, 0, BOOST_LOG(error)
width, height, << "Could not get image reply"sv;
~0, return capture_e::reinit;
XCB_IMAGE_FORMAT_Z_PIXMAP, }
seg,
0
);
xcb_img_t img_reply { xcb_shm_get_image_reply(xcb.get(), img_cookie, nullptr) }; std::copy_n((std::uint8_t*) data.data, frame_size(), img->data);
if(!img_reply) {
BOOST_LOG(error) << "Could not get image reply"sv;
return capture_e::reinit;
}
std::copy_n((std::uint8_t*)data.data, frame_size(), img->data); if (cursor)
{
blend_cursor(shm_xdisplay.get(), *img,displayOffsetX,displayOffsetY);
}
if(cursor) { toReturn = capture_e::ok;
blend_cursor(shm_xdisplay.get(), *img); }
} return toReturn;
}
return capture_e::ok; std::shared_ptr<img_t> alloc_img() override
} {
auto img = std::make_shared<shm_img_t>();
img->width = width;
img->height = height;
img->pixel_pitch = 4;
img->row_pitch = img->pixel_pitch * width;
img->data = new std::uint8_t[height * img->row_pitch];
std::shared_ptr<img_t> alloc_img() override { return img;
auto img = std::make_shared<shm_img_t>(); }
img->width = width;
img->height = height;
img->pixel_pitch = 4;
img->row_pitch = img->pixel_pitch * width;
img->data = new std::uint8_t[height * img->row_pitch];
return img; int dummy_img(platf::img_t *img) override
} {
return 0;
}
int dummy_img(platf::img_t *img) override { int init()
return 0; {
} shm_xdisplay.reset(XOpenDisplay(nullptr));
xcb.reset(xcb_connect(nullptr, nullptr));
if (xcb_connection_has_error(xcb.get()))
{
return -1;
}
int init() { if (!xcb_get_extension_data(xcb.get(), &xcb_shm_id)->present)
shm_xdisplay.reset(XOpenDisplay(nullptr)); {
xcb.reset(xcb_connect(nullptr, nullptr)); BOOST_LOG(error)
if(xcb_connection_has_error(xcb.get())) { << "Missing SHM extension"sv;
return -1;
}
if(!xcb_get_extension_data(xcb.get(), &xcb_shm_id)->present) { return -1;
BOOST_LOG(error) << "Missing SHM extension"sv; }
return -1; auto iter = xcb_setup_roots_iterator(xcb_get_setup(xcb.get()));
} display = iter.data;
seg = xcb_generate_id(xcb.get());
auto iter = xcb_setup_roots_iterator(xcb_get_setup(xcb.get())); shm_id.id = shmget(IPC_PRIVATE, frame_size(), IPC_CREAT | 0777);
display = iter.data; if (shm_id.id == -1)
seg = xcb_generate_id(xcb.get()); {
BOOST_LOG(error)
<< "shmget failed"sv;
return -1;
}
shm_id.id = shmget(IPC_PRIVATE, frame_size(), IPC_CREAT | 0777); xcb_shm_attach(xcb.get(), seg, shm_id.id, false);
if(shm_id.id == -1) { data.data = shmat(shm_id.id, nullptr, 0);
BOOST_LOG(error) << "shmget failed"sv;
return -1;
}
xcb_shm_attach(xcb.get(), seg, shm_id.id, false); if ((uintptr_t) data.data == -1)
data.data = shmat(shm_id.id, nullptr, 0); {
BOOST_LOG(error)
<< "shmat failed"sv;
if ((uintptr_t)data.data == -1) { return -1;
BOOST_LOG(error) << "shmat failed"sv; }
return -1; /*
} * Commented out resetting of the sizes when intializing X in SHM mode. It might be wrong. Expect issues. This is the default mode, so poisoning those variables is not desired
*/
// width = display->width_in_pixels;
// height = display->height_in_pixels;
width = display->width_in_pixels; return 0;
height = display->height_in_pixels; }
return 0; std::uint32_t frame_size()
} {
return width * height * 4;
std::uint32_t frame_size() { }
return width * height * 4;
}
}; };
struct mic_attr_t : public mic_t { struct mic_attr_t: public mic_t
pa_sample_spec ss; {
util::safe_ptr<pa_simple, pa_simple_free> mic; pa_sample_spec ss;
util::safe_ptr<pa_simple, pa_simple_free> mic;
explicit mic_attr_t(pa_sample_format format, std::uint32_t sample_rate, std::uint8_t channels) : ss { format, sample_rate, channels }, mic {} {} explicit mic_attr_t(pa_sample_format format, std::uint32_t sample_rate,
capture_e sample(std::vector<std::int16_t> &sample_buf) override { std::uint8_t channels) : ss { format, sample_rate, channels }, mic { }
auto sample_size = sample_buf.size(); {
}
capture_e sample(std::vector<std::int16_t> &sample_buf) override
{
auto sample_size = sample_buf.size();
auto buf = sample_buf.data(); auto buf = sample_buf.data();
int status; int status;
if(pa_simple_read(mic.get(), buf, sample_size * 2, &status)) { if (pa_simple_read(mic.get(), buf, sample_size * 2, &status))
BOOST_LOG(error) << "pa_simple_read() failed: "sv << pa_strerror(status); {
BOOST_LOG(error)
<< "pa_simple_read() failed: "sv << pa_strerror(status);
return capture_e::error; return capture_e::error;
} }
return capture_e::ok; return capture_e::ok;
} }
}; };
std::shared_ptr<display_t> shm_display() { std::shared_ptr<display_t> shm_display()
auto shm = std::make_shared<shm_attr_t>(); {
auto shm = std::make_shared<shm_attr_t>();
if(shm->init()) { if (shm->init())
return nullptr; {
} return nullptr;
}
return shm; return shm;
} }
std::shared_ptr<display_t> display(platf::dev_type_e hwdevice_type) { std::shared_ptr<display_t> display(platf::dev_type_e hwdevice_type)
if(hwdevice_type != platf::dev_type_e::none) { {
return nullptr; if (hwdevice_type != platf::dev_type_e::none)
} {
BOOST_LOG(error)<< "Could not initialize display with the given hw device type."sv;
return nullptr;
}
auto shm_disp = shm_display(); auto shm_disp = shm_display(); //Attempt to use shared memory X11 to avoid copying the frame
if(!shm_disp) { if (!shm_disp)
return std::make_shared<x11_attr_t>(); {
} return std::make_shared<x11_attr_t>(); //Fallback to standard way if else
}
return shm_disp; return shm_disp;
} }
std::unique_ptr<mic_t> microphone(std::uint32_t sample_rate) { std::unique_ptr<mic_t> microphone(std::uint32_t sample_rate)
auto mic = std::make_unique<mic_attr_t>(PA_SAMPLE_S16LE, sample_rate, 2); {
auto mic = std::make_unique<mic_attr_t>(PA_SAMPLE_S16LE, sample_rate, 2);
int status; int status;
const char *audio_sink = "@DEFAULT_MONITOR@"; const char *audio_sink = "@DEFAULT_MONITOR@";
if(!config::audio.sink.empty()) { if (!config::audio.sink.empty())
audio_sink = config::audio.sink.c_str(); {
} audio_sink = config::audio.sink.c_str();
}
mic->mic.reset( mic->mic.reset(
pa_simple_new(nullptr, "sunshine", pa_stream_direction_t::PA_STREAM_RECORD, audio_sink, "sunshine-record", &mic->ss, nullptr, nullptr, &status) pa_simple_new(nullptr, "sunshine",
); pa_stream_direction_t::PA_STREAM_RECORD, audio_sink,
"sunshine-record", &mic->ss, nullptr, nullptr, &status));
if(!mic->mic) { if (!mic->mic)
auto err_str = pa_strerror(status); {
BOOST_LOG(error) << "pa_simple_new() failed: "sv << err_str; auto err_str = pa_strerror(status);
BOOST_LOG(error)
<< "pa_simple_new() failed: "sv << err_str;
log_flush(); log_flush();
std::abort(); std::abort();
} }
return mic; return mic;
} }
ifaddr_t get_ifaddrs() { ifaddr_t get_ifaddrs()
ifaddrs *p { nullptr }; {
ifaddrs *p { nullptr };
getifaddrs(&p); getifaddrs(&p);
return ifaddr_t { p }; return ifaddr_t { p };
} }
std::string from_sockaddr(const sockaddr *const ip_addr) { std::string from_sockaddr(const sockaddr *const ip_addr)
char data[INET6_ADDRSTRLEN]; {
char data[INET6_ADDRSTRLEN];
auto family = ip_addr->sa_family; auto family = ip_addr->sa_family;
if(family == AF_INET6) { if (family == AF_INET6)
inet_ntop(AF_INET6, &((sockaddr_in6*)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN); {
} inet_ntop(AF_INET6, &((sockaddr_in6*) ip_addr)->sin6_addr, data,
INET6_ADDRSTRLEN);
}
if(family == AF_INET) { if (family == AF_INET)
inet_ntop(AF_INET, &((sockaddr_in*)ip_addr)->sin_addr, data, INET_ADDRSTRLEN); {
} inet_ntop(AF_INET, &((sockaddr_in*) ip_addr)->sin_addr, data,
INET_ADDRSTRLEN);
}
return std::string { data }; return std::string { data };
} }
std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const ip_addr) { std::pair<std::uint16_t, std::string> from_sockaddr_ex(
char data[INET6_ADDRSTRLEN]; const sockaddr *const ip_addr)
{
char data[INET6_ADDRSTRLEN];
auto family = ip_addr->sa_family; auto family = ip_addr->sa_family;
std::uint16_t port; std::uint16_t port;
if(family == AF_INET6) { if (family == AF_INET6)
inet_ntop(AF_INET6, &((sockaddr_in6*)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN); {
port = ((sockaddr_in6*)ip_addr)->sin6_port; inet_ntop(AF_INET6, &((sockaddr_in6*) ip_addr)->sin6_addr, data,
} INET6_ADDRSTRLEN);
port = ((sockaddr_in6*) ip_addr)->sin6_port;
}
if(family == AF_INET) { if (family == AF_INET)
inet_ntop(AF_INET, &((sockaddr_in*)ip_addr)->sin_addr, data, INET_ADDRSTRLEN); {
port = ((sockaddr_in*)ip_addr)->sin_port; inet_ntop(AF_INET, &((sockaddr_in*) ip_addr)->sin_addr, data,
} INET_ADDRSTRLEN);
port = ((sockaddr_in*) ip_addr)->sin_port;
}
return { port, std::string { data } }; return
{ port, std::string
{ data}};
} }
std::string get_mac_address(const std::string_view &address) { std::string get_mac_address(const std::string_view &address)
auto ifaddrs = get_ifaddrs(); {
for(auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) { auto ifaddrs = get_ifaddrs();
if(pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) { for (auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next)
std::ifstream mac_file("/sys/class/net/"s + pos->ifa_name + "/address"); {
if(mac_file.good()) { if (pos->ifa_addr && address == from_sockaddr(pos->ifa_addr))
std::string mac_address; {
std::getline(mac_file, mac_address); std::ifstream mac_file(
return mac_address; "/sys/class/net/"s + pos->ifa_name + "/address");
} if (mac_file.good())
} {
} std::string mac_address;
BOOST_LOG(warning) << "Unable to find MAC address for "sv << address; std::getline(mac_file, mac_address);
return "00:00:00:00:00:00"s; return mac_address;
}
}
}
BOOST_LOG(warning)
<< "Unable to find MAC address for "sv << address;
return "00:00:00:00:00:00"s;
} }
void freeImage(XImage *p) { void freeImage(XImage *p)
XDestroyImage(p); {
XDestroyImage(p);
} }
void freeX(XFixesCursorImage *p) { void freeX(XFixesCursorImage *p)
XFree(p); {
XFree(p);
} }
} }