From 028966201779b9b7d8f46b18c9d8234f88777a86 Mon Sep 17 00:00:00 2001 From: loki Date: Mon, 30 Dec 2019 11:09:35 +0100 Subject: [PATCH] Use SHM module if available for capturing display --- CMakeLists.txt | 3 + sunshine/platform/common.h | 27 +- sunshine/platform/linux.cpp | 437 +++++++++++++++++++----------- sunshine/platform/linux_evdev.cpp | 34 +-- sunshine/video.cpp | 16 +- 5 files changed, 324 insertions(+), 193 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f0c452e..8ffdac58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,9 @@ set(PLATFORM_TARGET_FILES set(PLATFORM_LIBRARIES Xfixes Xtst + xcb + xcb-shm + xcb-xfixes ${X11_LIBRARIES} evdev pulse diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index 187a01e7..8e7ca0ce 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -10,31 +10,36 @@ namespace platf { -void freeDisplay(void*); -void freeImage(void*); +struct img_t { +public: + std::uint8_t *data; + std::int32_t width; + std::int32_t height; + + virtual ~img_t() {}; +}; + +class display_t { +public: + virtual std::unique_ptr snapshot(bool cursor) = 0; + virtual ~display_t() {}; +}; + void freeAudio(void*); void freeMic(void*); void freeInput(void*); -using display_t = util::safe_ptr; -using img_t = util::safe_ptr; using mic_t = util::safe_ptr; using audio_t = util::safe_ptr; using input_t = util::safe_ptr; std::string get_local_ip(); -void terminate_process(std::uint64_t handle); - mic_t microphone(); audio_t audio(mic_t &mic, std::uint32_t sample_size); -display_t display(); -img_t snapshot(display_t &display_void, bool cursor); -int32_t img_width(img_t &); -int32_t img_height(img_t &); +std::unique_ptr display(); -uint8_t *img_data(img_t &); int16_t *audio_data(audio_t &); input_t input(); diff --git a/sunshine/platform/linux.cpp b/sunshine/platform/linux.cpp index 10474d61..929cba11 100644 --- a/sunshine/platform/linux.cpp +++ b/sunshine/platform/linux.cpp @@ -11,8 +11,11 @@ #include #include #include -#include #include +#include +#include +#include +#include #include #include @@ -23,7 +26,285 @@ namespace platf { using namespace std::literals; +void freeImage(XImage *); + using ifaddr_t = util::safe_ptr; +using xcb_connect_t = util::safe_ptr; +using xcb_img_t = util::c_ptr; +using xcb_cursor_img = util::c_ptr; + +using xdisplay_t = util::safe_ptr_v2; +using ximg_t = util::safe_ptr; + +class shm_id_t { +public: + 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() { + if(id != -1) { + shmctl(id, IPC_RMID, nullptr); + id = -1; + } + } + int id; +}; + +class shm_data_t { +public: + shm_data_t() : data {(void*)-1 } {} + shm_data_t(void *data) : data {data } {} + shm_data_t(shm_data_t &&other) noexcept : data(other.data) { + other.data = (void*)-1; + } + + ~shm_data_t() { + if((std::uintptr_t)data != -1) { + shmdt(data); + data = (void*)-1; + } + } + + void *data; +}; + +struct x11_img_t : public img_t { + x11_img_t(std::uint8_t *data, std::int32_t width, std::int32_t height, XImage *img) : img { img } { + this->data = data; + this->width = width; + this->height = height; + } + + ximg_t img; +}; + +struct shm_img_t : public img_t { + ~shm_img_t() override { + if(data) { + delete(data); + } + } +}; + +void blend_cursor(Display *display, std::uint8_t *img_data, int width, int height) { + XFixesCursorImage *overlay = XFixesGetCursorImage(display); + overlay->x -= overlay->xhot; + overlay->y -= overlay->yhot; + + overlay->x = std::max((short)0, overlay->x); + overlay->y = std::max((short)0, overlay->y); + + auto pixels = (int*)img_data; + + auto screen_height = height; + auto screen_width = width; + + auto delta_height = std::min(overlay->height, std::max(0, screen_height - overlay->y)); + auto delta_width = std::min(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) * screen_width + overlay->x]; + std::for_each(overlay_begin, overlay_end, [&](long pixel) { + int *pixel_p = (int*)&pixel; + + auto colors_in = (uint8_t*)pixels_begin; + + auto alpha = (*(uint*)pixel_p) >> 24u; + if(alpha == 255) { + *pixels_begin = *pixel_p; + } + else { + auto colors_out = (uint8_t*)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; + colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255/2) / 255; + } + ++pixels_begin; + }); + } +} +struct x11_attr_t : public display_t { + x11_attr_t() : display {XOpenDisplay(nullptr) }, window {DefaultRootWindow(display.get()) }, attr {} { + refresh(); + } + + void refresh() { + XGetWindowAttributes(display.get(), window, &attr); + } + + std::unique_ptr snapshot(bool cursor) { + refresh(); + XImage *img { XGetImage( + display.get(), + window, + 0, 0, + attr.width, attr.height, + AllPlanes, ZPixmap) + }; + + if(!cursor) { + return std::make_unique((std::uint8_t*)img->data, img->width, img->height, img); + } + + blend_cursor(display.get(), (std::uint8_t*)img->data, img->width, img->height); + return std::make_unique((std::uint8_t*)img->data, img->width, img->height, img); + } + + xdisplay_t display; + Window window; + XWindowAttributes attr; +}; + +struct shm_attr_t : display_t { + xdisplay_t xdisplay; + + xcb_connect_t xcb; + xcb_screen_t *display; + std::uint32_t seg; + + shm_id_t shm_id; + + shm_data_t data; + + std::unique_ptr snapshot(bool cursor) override { + auto img_cookie = xcb_shm_get_image_unchecked( + xcb.get(), + display->root, + 0, 0, + display->width_in_pixels, display->height_in_pixels, + ~0, + XCB_IMAGE_FORMAT_Z_PIXMAP, + seg, + 0 + ); + + xcb_img_t img_reply { xcb_shm_get_image_reply(xcb.get(), img_cookie, nullptr) }; + if(!img_reply) { + std::cout << "FATAL ERROR: Could not get image reply"sv << std::endl; + std::abort(); + } + + auto img = std::make_unique(); + img->data = new std::uint8_t[frame_size()]; + img->width = display->width_in_pixels; + img->height = display->height_in_pixels; + + std::copy((std::uint8_t*)data.data, (std::uint8_t*)data.data + frame_size(), img->data); + + if(!cursor) { + return img; + } + + blend_cursor(xdisplay.get(), img->data, img->width, img->height); + + return img; + } + + std::uint32_t frame_size() { + return display->height_in_pixels * display->width_in_pixels * 4; + } +}; + +struct mic_attr_t { + pa_sample_spec ss; + util::safe_ptr mic; +}; + +std::unique_ptr shm_display() { + auto shm = std::make_unique(); + + shm->xdisplay.reset(XOpenDisplay(nullptr)); + shm->xcb.reset(xcb_connect(nullptr, nullptr)); + if(xcb_connection_has_error(shm->xcb.get())) { + return nullptr; + } + + if(!xcb_get_extension_data(shm->xcb.get(), &xcb_shm_id)->present) { + std::cout << "Missing SHM extension"sv << std::endl; + + return nullptr; + } + + auto iter = xcb_setup_roots_iterator(xcb_get_setup(shm->xcb.get())); + shm->display = iter.data; + shm->seg = xcb_generate_id(shm->xcb.get()); + + shm->shm_id.id = shmget(IPC_PRIVATE, shm->frame_size(), IPC_CREAT | 0777); + if(shm->shm_id.id == -1) { + std::cout << "shmget failed"sv << std::endl; + return nullptr; + } + + xcb_shm_attach(shm->xcb.get(), shm->seg, shm->shm_id.id, false); + shm->data.data = shmat(shm->shm_id.id, nullptr, 0); + + if ((uintptr_t)shm->data.data == -1) { + std::cout << "shmat failed"sv << std::endl; + + return nullptr; + } + + return shm; +} + +std::unique_ptr display() { + auto shm_disp = shm_display(); + + if(!shm_disp) { + return std::unique_ptr { new x11_attr_t {} }; + } + + return shm_disp; +} + +//FIXME: Pass frame_rate instead of hard coding it +mic_t microphone() { + mic_t mic { + new mic_attr_t { + { PA_SAMPLE_S16LE, 48000, 2 }, + { } + } + }; + + int error; + mic_attr_t *mic_attr = (mic_attr_t*)mic.get(); + mic_attr->mic.reset( + pa_simple_new(nullptr, "sunshine", pa_stream_direction_t::PA_STREAM_RECORD, nullptr, "sunshine_record", &mic_attr->ss, nullptr, nullptr, &error) + ); + + if(!mic_attr->mic) { + auto err_str = pa_strerror(error); + std::cout << "pa_simple_new() failed: "sv << err_str << std::endl; + + exit(1); + } + + return mic; +} + +audio_t audio(mic_t &mic, std::uint32_t buf_size) { + auto mic_attr = (mic_attr_t*)mic.get(); + + audio_t result { new std::uint8_t[buf_size] }; + + auto buf = (std::uint8_t*)result.get(); + int error; + if(pa_simple_read(mic_attr->mic.get(), buf, buf_size, &error)) { + std::cout << "pa_simple_read() failed: "sv << pa_strerror(error) << std::endl; + } + + return result; +} + +std::int16_t *audio_data(audio_t &audio) { + return (int16_t*)audio.get(); +} ifaddr_t get_ifaddrs() { ifaddrs *p { nullptr }; @@ -74,7 +355,7 @@ std::string get_local_ip(int family) { (family_f[1] && pos->ifa_addr->sa_family == AF_INET6) ){ ip_addr = from_sockaddr(pos->ifa_addr); - break; + break; } } } @@ -84,156 +365,8 @@ std::string get_local_ip(int family) { std::string get_local_ip() { return get_local_ip(AF_INET); } -void terminate_process(std::uint64_t handle) { - kill((pid_t)handle, SIGTERM); -} - -struct display_attr_t { - display_attr_t() : display { XOpenDisplay(nullptr) }, window { DefaultRootWindow(display) }, attr {} { - refresh(); - } - - ~display_attr_t() { - XCloseDisplay(display); - } - - void refresh() { - XGetWindowAttributes(display, window, &attr); - } - - Display *display; - Window window; - XWindowAttributes attr; -}; - -struct mic_attr_t { - pa_sample_spec ss; - util::safe_ptr mic; -}; - -display_t display() { - return display_t { new display_attr_t {} }; -} - -img_t snapshot(display_t &display_void, bool cursor) { - auto &display = *((display_attr_t*)display_void.get()); - - display.refresh(); - XImage *img { XGetImage( - display.display, - display.window, - 0, 0, - display.attr.width, display.attr.height, - AllPlanes, ZPixmap) - }; - - if(!cursor) { - return img_t { img }; - } - - XFixesCursorImage *overlay = XFixesGetCursorImage(display.display); - overlay->x -= overlay->xhot; - overlay->y -= overlay->yhot; - - overlay->x = std::max((short)0, overlay->x); - overlay->y = std::max((short)0, overlay->y); - - auto pixels = (int*)img->data; - - auto screen_height = display.attr.height; - auto screen_width = display.attr.width; - - auto delta_height = std::min(overlay->height, std::max(0, screen_height - overlay->y)); - auto delta_width = std::min(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) * screen_width + overlay->x]; - std::for_each(overlay_begin, overlay_end, [&](long pixel) { - int *pixel_p = (int*)&pixel; - - auto colors_in = (uint8_t*)pixels_begin; - - auto alpha = (*(uint*)pixel_p) >> 24u; - if(alpha == 255) { - *pixels_begin = *pixel_p; - } - else { - auto colors_out = (uint8_t*)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; - colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255/2) / 255; - } - ++pixels_begin; - }); - } - - return img_t { img }; -} - -uint8_t *img_data(img_t &img) { - return (uint8_t*)((XImage*)img.get())->data; -} - -int32_t img_width(img_t &img) { - return ((XImage*)img.get())->width; -} - -int32_t img_height(img_t &img) { - return ((XImage*)img.get())->height; -} - -//FIXME: Pass frame_rate instead of hard coding it -mic_t microphone() { - mic_t mic { - new mic_attr_t { - { PA_SAMPLE_S16LE, 48000, 2 }, - { } - } - }; - - int error; - mic_attr_t *mic_attr = (mic_attr_t*)mic.get(); - mic_attr->mic.reset( - pa_simple_new(nullptr, "sunshine", pa_stream_direction_t::PA_STREAM_RECORD, nullptr, "sunshine_record", &mic_attr->ss, nullptr, nullptr, &error) - ); - - if(!mic_attr->mic) { - auto err_str = pa_strerror(error); - std::cout << "pa_simple_new() failed: "sv << err_str << std::endl; - - exit(1); - } - - return mic; -} - -audio_t audio(mic_t &mic, std::uint32_t buf_size) { - auto mic_attr = (mic_attr_t*)mic.get(); - - audio_t result { new std::uint8_t[buf_size] }; - - auto buf = (std::uint8_t*)result.get(); - int error; - if(pa_simple_read(mic_attr->mic.get(), buf, buf_size, &error)) { - std::cout << "pa_simple_read() failed: "sv << pa_strerror(error) << std::endl; - } - - return result; -} - -std::int16_t *audio_data(audio_t &audio) { - return (int16_t*)audio.get(); -} - -void freeDisplay(void*p) { - delete (display_attr_t*)p; -} - -void freeImage(void*p) { - XDestroyImage((XImage*)p); +void freeImage(XImage *p) { + XDestroyImage(p); } void freeMic(void*p) { diff --git a/sunshine/platform/linux_evdev.cpp b/sunshine/platform/linux_evdev.cpp index 72ab6820..935bf715 100644 --- a/sunshine/platform/linux_evdev.cpp +++ b/sunshine/platform/linux_evdev.cpp @@ -18,6 +18,7 @@ using namespace std::literals; using evdev_t = util::safe_ptr; using uinput_t = util::safe_ptr; +using keyboard_t = util::safe_ptr_v2; struct input_raw_t { evdev_t gamepad_dev; uinput_t gamepad_input; @@ -25,22 +26,7 @@ struct input_raw_t { evdev_t mouse_dev; uinput_t mouse_input; - display_t display; -}; - -//TODO: Use libevdev for keyboard and mouse, then any mention of X11 can be removed from linux_evdev.cpp -struct display_attr_t { - display_attr_t() : display { XOpenDisplay(nullptr) }, window { DefaultRootWindow(display) }, attr {} { - XGetWindowAttributes(display, window, &attr); - } - - ~display_attr_t() { - XCloseDisplay(display); - } - - Display *display; - Window window; - XWindowAttributes attr; + keyboard_t keyboard; }; void move_mouse(input_t &input, int deltaX, int deltaY) { @@ -203,17 +189,17 @@ uint16_t keysym(uint16_t modcode) { } void keyboard(input_t &input, uint16_t modcode, bool release) { - auto &disp = *((display_attr_t *) ((input_raw_t*)input.get())->display.get()); - KeyCode kc = XKeysymToKeycode(disp.display, keysym(modcode)); + auto &keyboard = ((input_raw_t*)input.get())->keyboard; + KeyCode kc = XKeysymToKeycode(keyboard.get(), keysym(modcode)); if(!kc) { return; } - XTestFakeKeyEvent(disp.display, kc, !release, 0); + XTestFakeKeyEvent(keyboard.get(), kc, !release, 0); - XSync(disp.display, 0); - XFlush(disp.display); + XSync(keyboard.get(), 0); + XFlush(keyboard.get()); } namespace gp { @@ -425,6 +411,11 @@ input_t input() { input_t result { new input_raw_t() }; auto &gp = *(input_raw_t*)result.get(); + gp.keyboard.reset(XOpenDisplay(nullptr)); + if(!gp.keyboard) { + return nullptr; + } + if(gamepad(gp)) { return nullptr; } @@ -445,7 +436,6 @@ input_t input() { std::filesystem::create_symlink(libevdev_uinput_get_devnode(gp.mouse_input.get()), mouse_path); std::filesystem::create_symlink(libevdev_uinput_get_devnode(gp.gamepad_input.get()), gamepad_path); - gp.display = display(); return result; } diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 4f1dd1f0..9458b450 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -34,7 +34,7 @@ void free_packet(AVPacket *packet) { using ctx_t = util::safe_ptr; using frame_t = util::safe_ptr; using sws_t = util::safe_ptr; -using img_event_t = std::shared_ptr>; +using img_event_t = std::shared_ptr>>; auto open_codec(ctx_t &ctx, AVCodec *codec, AVDictionary **options) { avcodec_open2(ctx.get(), codec, options); @@ -48,11 +48,11 @@ void encode(int64_t frame, ctx_t &ctx, sws_t &sws, frame_t &yuv_frame, platf::im av_frame_make_writable(yuv_frame.get()); const int linesizes[2] { - (int)(platf::img_width(img) * sizeof(int)), 0 + (int)(img.width * sizeof(int)), 0 }; - auto data = platf::img_data(img); - int ret = sws_scale(sws.get(), (uint8_t*const*)&data, linesizes, 0, platf::img_height(img), yuv_frame->data, yuv_frame->linesize); + auto data = img.data; + int ret = sws_scale(sws.get(), (uint8_t*const*)&data, linesizes, 0, img.height, yuv_frame->data, yuv_frame->linesize); if(ret <= 0) { exit(1); @@ -149,8 +149,8 @@ void encodeThread( // Initiate scaling context with correct height and width sws_t sws; while (auto img = images->pop()) { - auto new_width = platf::img_width(img); - auto new_height = platf::img_height(img); + auto new_width = img->width; + auto new_height = img->height; if(img_width != new_width || img_height != new_height) { img_width = new_width; @@ -177,7 +177,7 @@ void encodeThread( yuv_frame->pict_type = AV_PICTURE_TYPE_I; } - encode(frame++, ctx, sws, yuv_frame, img, packets); + encode(frame++, ctx, sws, yuv_frame, *img, packets); yuv_frame->pict_type = AV_PICTURE_TYPE_NONE; } @@ -197,7 +197,7 @@ void capture_display(packet_queue_t packets, idr_event_t idr_events, config_t co auto time_span = std::chrono::floor(1s) / framerate; while(packets->running()) { auto next_snapshot = std::chrono::steady_clock::now() + time_span; - auto img = platf::snapshot(disp, display_cursor); + auto img = disp->snapshot(display_cursor); images->raise(std::move(img)); img.reset();