Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package wf-recorder for openSUSE:Factory checked in at 2023-08-23 14:58:41 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/wf-recorder (Old) and /work/SRC/openSUSE:Factory/.wf-recorder.new.1766 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "wf-recorder" Wed Aug 23 14:58:41 2023 rev:5 rq:1105455 version:0.4.0+git0 Changes: -------- --- /work/SRC/openSUSE:Factory/wf-recorder/wf-recorder.changes 2023-01-17 17:35:54.465386143 +0100 +++ /work/SRC/openSUSE:Factory/.wf-recorder.new.1766/wf-recorder.changes 2023-08-23 14:59:52.106231945 +0200 @@ -1,0 +2,21 @@ +Wed Aug 23 09:45:48 UTC 2023 - amme...@gmail.com + +- Update to version 0.4.0+git0: + * meson.build: bump version to 0.4.0 + * don't ask for overwrite for the char device (#141) + * add --no-dmabuf option (#225) + * Break early when exit_main_loop is set in encoder thread (#223) + * Dynamically increase number of used buffers (#221) + * Add X2RGB10 format mapping for DMA-BUFs (#224) + * Add support for 16 bit deep formats (#184) + * Revert "Use VP9+Opus on MKV for recordings" + * Revert "Use VP8+Vorbis on WebM for recordings (#202)" + * Use codec format which best matches input (#215) + * Retry capture on failure + * Use gbm_bo_create_with_modifiers when possible + * Reduce memory usage with DMA-BUF capture (#219) + * Use DMA-BUF with HW encoding (#206) + * Only flush when video codec has delay (#209) + * ci: add to safe directories so that git parse-rev works (#210) + +------------------------------------------------------------------- Old: ---- wf-recorder-0.3.0+git19.tar.gz New: ---- wf-recorder-0.4.0+git0.obscpio wf-recorder-0.4.0+git0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ wf-recorder.spec ++++++ --- /var/tmp/diff_new_pack.IJJEGe/_old 2023-08-23 14:59:52.842233260 +0200 +++ /var/tmp/diff_new_pack.IJJEGe/_new 2023-08-23 14:59:52.846233268 +0200 @@ -18,7 +18,7 @@ Name: wf-recorder -Version: 0.3.0+git19 +Version: 0.4.0+git0 Release: 0%{?dist} Summary: Utility program for screen recording of wlroots-based compositors License: MIT @@ -27,10 +27,12 @@ Source0: %{name}-%{version}.tar.gz BuildRequires: gcc BuildRequires: gcc-c++ -BuildRequires: meson >= 0.47.0 +BuildRequires: meson >= 0.54.0 BuildRequires: pkgconfig +BuildRequires: pkgconfig(gbm) BuildRequires: pkgconfig(libavcodec) BuildRequires: pkgconfig(libavdevice) +BuildRequires: pkgconfig(libavfilter) BuildRequires: pkgconfig(libavformat) BuildRequires: pkgconfig(libavutil) BuildRequires: pkgconfig(libpulse-simple) ++++++ _service ++++++ --- /var/tmp/diff_new_pack.IJJEGe/_old 2023-08-23 14:59:52.902233368 +0200 +++ /var/tmp/diff_new_pack.IJJEGe/_new 2023-08-23 14:59:52.906233375 +0200 @@ -2,12 +2,12 @@ <service name="obs_scm" mode="disabled"> <param name="scm">git</param> <param name="url">https://github.com/ammen99/wf-recorder.git</param> - <param name="revision">460d454b1efd380a3f732f6fd70c7a5e265381f6</param> + <param name="revision">7e42d6c4dcb650808cc9ec3b7c2375764e5a2662</param> <param name="versionformat">@PARENT_TAG@+git@TAG_OFFSET@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="versionrewrite-replacement">\1</param> <param name="changesgenerate">enable</param> - <param name="changesauthor">llyyr.pub...@gmail.com</param> + <param name="changesauthor">amme...@gmail.com</param> </service> <service mode="disabled" name="tar" /> <service mode="disabled" name="recompress"> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.IJJEGe/_old 2023-08-23 14:59:52.934233425 +0200 +++ /var/tmp/diff_new_pack.IJJEGe/_new 2023-08-23 14:59:52.938233432 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/ammen99/wf-recorder.git</param> - <param name="changesrevision">460d454b1efd380a3f732f6fd70c7a5e265381f6</param></service></servicedata> + <param name="changesrevision">7e42d6c4dcb650808cc9ec3b7c2375764e5a2662</param></service></servicedata> (No newline at EOF) ++++++ wf-recorder-0.3.0+git19.tar.gz -> wf-recorder-0.4.0+git0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wf-recorder-0.3.0+git19/.github/workflows/build.yaml new/wf-recorder-0.4.0+git0/.github/workflows/build.yaml --- old/wf-recorder-0.3.0+git19/.github/workflows/build.yaml 2023-01-02 10:18:42.000000000 +0100 +++ new/wf-recorder-0.4.0+git0/.github/workflows/build.yaml 2023-08-23 10:52:14.000000000 +0200 @@ -21,9 +21,11 @@ 'pkgconfig(wayland-client)' 'pkgconfig(wayland-protocols)' 'pkgconfig(libpulse-simple)' 'pkgconfig(libavutil)' 'pkgconfig(libavcodec)' 'pkgconfig(libavformat)' 'pkgconfig(libavdevice)' 'pkgconfig(libavfilter)' 'pkgconfig(libswresample)' + 'pkgconfig(gbm)' - uses: actions/checkout@v2 with: fetch-depth: 0 # Shallow clones speed things up + - run: git config --global --add safe.directory '*' # Needed for git rev-parse - name: meson configure run: meson ./Build - name: compile with ninja diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wf-recorder-0.3.0+git19/README.md new/wf-recorder-0.4.0+git0/README.md --- old/wf-recorder-0.3.0+git19/README.md 2023-01-02 10:18:42.000000000 +0100 +++ new/wf-recorder-0.4.0+git0/README.md 2023-08-23 10:52:14.000000000 +0200 @@ -63,12 +63,12 @@ meson build --prefix=/usr --buildtype=release ninja -C build ``` -Optionally configure with `-Ddefault_codec='codec'`. The default is libvpx. Now you can just run `./build/wf-recorder` or install it with `sudo ninja -C build install`. +Optionally configure with `-Ddefault_codec='codec'`. The default is libx264. Now you can just run `./build/wf-recorder` or install it with `sudo ninja -C build install`. The man page can be read with `man ./manpage/wf-recorder.1`. # Usage -In its simplest form, run `wf-recorder` to start recording and use Ctrl+C to stop. This will create a file called `recording.webm` in the current working directory using the default codec. +In its simplest form, run `wf-recorder` to start recording and use Ctrl+C to stop. This will create a file called `recording.mp4` in the current working directory using the default codec. Use `-f <filename>` to specify the output file. In case of multiple outputs, you'll first be prompted to select the output you want to record. If you know the output name beforehand, you can use the `-o <output name>` option. @@ -81,7 +81,7 @@ You can record screen and sound simultaneously with ``` -wf-recorder --audio --file=recording_with_audio.webm +wf-recorder --audio --file=recording_with_audio.mp4 ``` To specify an audio device, use the `-a<device>` or `--audio=<device>` options. @@ -108,8 +108,8 @@ wf-recorder --muxer=v4l2 --codec=rawvideo --file=/dev/video2 ``` -To use GPU encoding, use a VAAPI codec (for ex. `vp8_vaapi`) and specify a GPU device to use with the `-d` option: +To use GPU encoding, use a VAAPI codec (for ex. `h264_vaapi`) and specify a GPU device to use with the `-d` option: ``` -wf-recorder -f test-vaapi.webm -c vp8_vaapi -d /dev/dri/renderD128 +wf-recorder -f test-vaapi.mkv -c h264_vaapi -d /dev/dri/renderD128 ``` Some drivers report support for rgb0 data for vaapi input but really only support yuv planar formats. In this case, use the `-x yuv420p` or `--pixel-format yuv420p` option in addition to the vaapi options to convert the data to yuv planar data before sending it to the GPU. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wf-recorder-0.3.0+git19/manpage/wf-recorder.1 new/wf-recorder-0.4.0+git0/manpage/wf-recorder.1 --- old/wf-recorder-0.3.0+git19/manpage/wf-recorder.1 2023-01-02 10:18:42.000000000 +0100 +++ new/wf-recorder-0.4.0+git0/manpage/wf-recorder.1 2023-08-23 10:52:14.000000000 +0200 @@ -13,6 +13,7 @@ .Op Fl c, -codec Ar output_codec .Op Fl r, -framerate Ar framerate .Op Fl d, -device Ar encoding_device +.Op Fl -no-dmabuf .Op Fl D, -no-damage .Op Fl f Ar filename.ext .Op Fl F Ar filter_string @@ -43,7 +44,7 @@ .Ql Ctrl+C to stop. This will create a file called -.Ql recording.webm +.Ql recording.mp4 in the current working directory using the default .Ar codec. .Pp @@ -88,6 +89,11 @@ option in addition to the vaapi options to convert the data in software, before sending it to the GPU. .Pp +.It Fl -no-dmabuf +By default, wf-recorder will try to use only GPU buffers and copies if using a GPU encoder. +However, this can cause issues on some systems. +In such cases, this option will disable the GPU copy and force a CPU one. +.Pp .It Fl D , -no-damage By default, wf-recorder will request a new frame from the compositor only when the screen updates. This results in a much smaller output @@ -111,7 +117,7 @@ .Dl $ ffmpeg -muxers .Pp .It Fl F , -filter Ar filter_string -Set the ffmpeg filter to use. VAAPI requires `hwupload,scale_vaapi=nv12` to work. +Set the ffmpeg filter to use. VAAPI requires `scale_vaapi=format=nv12:out_range=full` to work. .Pp .It Fl g , -geometry Ar screen_geometry Selects a specific part of the screen. The format is "x,y WxH". @@ -165,7 +171,7 @@ .Dl $ wf-recorder -g "$(slurp)" .Pp You can record screen and sound simultaneously with -.Dl $ wf-recorder --audio --file=recording_with_audio.webm +.Dl $ wf-recorder --audio --file=recording_with_audio.mp4 .Pp To specify an audio device, use the .Fl -a<DEVICE> @@ -190,12 +196,12 @@ .Dl $ wf-recorder --muxer=v4l2 --codec=rawvideo --file=/dev/video2 .Pp To use GPU encoding, use a VAAPI codec (for ex. -.Ql vp8_vaapi +.Ql h264_vaapi ) and specify a GPU device to use with the .Fl d option: -.Dl $ wf-recorder -f test-vaapi.webm -c vp8_vaapi -d /dev/dri/renderD128 +.Dl $ wf-recorder -f test-vaapi.mkv -c h264_vaapi -d /dev/dri/renderD128 .Pp Some drivers report support for .Ql rgb0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wf-recorder-0.3.0+git19/meson.build new/wf-recorder-0.4.0+git0/meson.build --- old/wf-recorder-0.3.0+git19/meson.build 2023-01-02 10:18:42.000000000 +0100 +++ new/wf-recorder-0.4.0+git0/meson.build 2023-08-23 10:52:14.000000000 +0200 @@ -2,7 +2,7 @@ 'wf-recorder', 'c', 'cpp', - version: '0.3.0', + version: '0.4.0', license: 'MIT', meson_version: '>=0.54.0', default_options: [ @@ -43,7 +43,7 @@ project_sources = ['src/frame-writer.cpp', 'src/main.cpp', 'src/averr.c'] -wayland_client = dependency('wayland-client') +wayland_client = dependency('wayland-client', version: '>=1.20') wayland_protos = dependency('wayland-protocols', version: '>=1.14') pulse = dependency('libpulse-simple', required : get_option('pulse')) @@ -60,6 +60,7 @@ libavfilter = dependency('libavfilter') swr = dependency('libswresample') threads = dependency('threads') +gbm = dependency('gbm') conf_data.set('HAVE_LIBAVDEVICE', libavdevice.found()) @@ -75,7 +76,7 @@ dependencies = [ wayland_client, wayland_protos, libavutil, libavcodec, libavformat, libavdevice, libavfilter, - wf_protos, threads, pulse, swr + wf_protos, threads, pulse, swr, gbm ] executable('wf-recorder', project_sources, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wf-recorder-0.3.0+git19/meson_options.txt new/wf-recorder-0.4.0+git0/meson_options.txt --- old/wf-recorder-0.3.0+git19/meson_options.txt 2023-01-02 10:18:42.000000000 +0100 +++ new/wf-recorder-0.4.0+git0/meson_options.txt 2023-08-23 10:52:14.000000000 +0200 @@ -1,6 +1,6 @@ -option('default_codec', type: 'string', value: 'libvpx', description: 'Codec that will be used by default') -option('default_audio_codec', type: 'string', value: 'libvorbis', description: 'Audio codec that will be used by default') +option('default_codec', type: 'string', value: 'libx264', description: 'Codec that will be used by default') +option('default_audio_codec', type: 'string', value: 'aac', description: 'Audio codec that will be used by default') option('default_audio_sample_rate', type: 'integer', value: 48000, description: 'Audio sample rate that will be used by default') -option('default_container_format', type: 'string', value: 'webm', description: 'Container file format that will be used by default') +option('default_container_format', type: 'string', value: 'mkv', description: 'Container file format that will be used by default') option('fallback_audio_sample_fmt', type: 'string', value: 's16', description: 'Fallback audio sample format that will be used if wf-recorder cannot determine the sample formats supported by a codec') option('pulse', type: 'feature', value: 'auto', description: 'Enable Pulseaudio') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wf-recorder-0.3.0+git19/proto/meson.build new/wf-recorder-0.4.0+git0/proto/meson.build --- old/wf-recorder-0.3.0+git19/proto/meson.build 2023-01-02 10:18:42.000000000 +0100 +++ new/wf-recorder-0.4.0+git0/proto/meson.build 2023-08-23 10:52:14.000000000 +0200 @@ -15,7 +15,9 @@ client_protocols = [ [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], - 'wlr-screencopy-unstable-v1.xml' + [wl_protocol_dir, 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml'], + 'wlr-screencopy-unstable-v1.xml', + 'wl-drm.xml' ] wl_protos_client_src = [] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wf-recorder-0.3.0+git19/proto/wl-drm.xml new/wf-recorder-0.4.0+git0/proto/wl-drm.xml --- old/wf-recorder-0.3.0+git19/proto/wl-drm.xml 1970-01-01 01:00:00.000000000 +0100 +++ new/wf-recorder-0.4.0+git0/proto/wl-drm.xml 2023-08-23 10:52:14.000000000 +0200 @@ -0,0 +1,189 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="drm"> + + <copyright> + Copyright © 2008-2011 Kristian Høgsberg + Copyright © 2010-2011 Intel Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that\n the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + </copyright> + + <!-- drm support. This object is created by the server and published + using the display's global event. --> + <interface name="wl_drm" version="2"> + <enum name="error"> + <entry name="authenticate_fail" value="0"/> + <entry name="invalid_format" value="1"/> + <entry name="invalid_name" value="2"/> + </enum> + + <enum name="format"> + <!-- The drm format codes match the #defines in drm_fourcc.h. + The formats actually supported by the compositor will be + reported by the format event. New codes must not be added, + unless directly taken from drm_fourcc.h. --> + <entry name="c8" value="0x20203843"/> + <entry name="rgb332" value="0x38424752"/> + <entry name="bgr233" value="0x38524742"/> + <entry name="xrgb4444" value="0x32315258"/> + <entry name="xbgr4444" value="0x32314258"/> + <entry name="rgbx4444" value="0x32315852"/> + <entry name="bgrx4444" value="0x32315842"/> + <entry name="argb4444" value="0x32315241"/> + <entry name="abgr4444" value="0x32314241"/> + <entry name="rgba4444" value="0x32314152"/> + <entry name="bgra4444" value="0x32314142"/> + <entry name="xrgb1555" value="0x35315258"/> + <entry name="xbgr1555" value="0x35314258"/> + <entry name="rgbx5551" value="0x35315852"/> + <entry name="bgrx5551" value="0x35315842"/> + <entry name="argb1555" value="0x35315241"/> + <entry name="abgr1555" value="0x35314241"/> + <entry name="rgba5551" value="0x35314152"/> + <entry name="bgra5551" value="0x35314142"/> + <entry name="rgb565" value="0x36314752"/> + <entry name="bgr565" value="0x36314742"/> + <entry name="rgb888" value="0x34324752"/> + <entry name="bgr888" value="0x34324742"/> + <entry name="xrgb8888" value="0x34325258"/> + <entry name="xbgr8888" value="0x34324258"/> + <entry name="rgbx8888" value="0x34325852"/> + <entry name="bgrx8888" value="0x34325842"/> + <entry name="argb8888" value="0x34325241"/> + <entry name="abgr8888" value="0x34324241"/> + <entry name="rgba8888" value="0x34324152"/> + <entry name="bgra8888" value="0x34324142"/> + <entry name="xrgb2101010" value="0x30335258"/> + <entry name="xbgr2101010" value="0x30334258"/> + <entry name="rgbx1010102" value="0x30335852"/> + <entry name="bgrx1010102" value="0x30335842"/> + <entry name="argb2101010" value="0x30335241"/> + <entry name="abgr2101010" value="0x30334241"/> + <entry name="rgba1010102" value="0x30334152"/> + <entry name="bgra1010102" value="0x30334142"/> + <entry name="yuyv" value="0x56595559"/> + <entry name="yvyu" value="0x55595659"/> + <entry name="uyvy" value="0x59565955"/> + <entry name="vyuy" value="0x59555956"/> + <entry name="ayuv" value="0x56555941"/> + <entry name="xyuv8888" value="0x56555958"/> + <entry name="nv12" value="0x3231564e"/> + <entry name="nv21" value="0x3132564e"/> + <entry name="nv16" value="0x3631564e"/> + <entry name="nv61" value="0x3136564e"/> + <entry name="yuv410" value="0x39565559"/> + <entry name="yvu410" value="0x39555659"/> + <entry name="yuv411" value="0x31315559"/> + <entry name="yvu411" value="0x31315659"/> + <entry name="yuv420" value="0x32315559"/> + <entry name="yvu420" value="0x32315659"/> + <entry name="yuv422" value="0x36315559"/> + <entry name="yvu422" value="0x36315659"/> + <entry name="yuv444" value="0x34325559"/> + <entry name="yvu444" value="0x34325659"/> + <entry name="abgr16f" value="0x48344241"/> + <entry name="xbgr16f" value="0x48344258"/> + </enum> + + <!-- Call this request with the magic received from drmGetMagic(). + It will be passed on to the drmAuthMagic() or + DRIAuthConnection() call. This authentication must be + completed before create_buffer could be used. --> + <request name="authenticate"> + <arg name="id" type="uint"/> + </request> + + <!-- Create a wayland buffer for the named DRM buffer. The DRM + surface must have a name using the flink ioctl --> + <request name="create_buffer"> + <arg name="id" type="new_id" interface="wl_buffer"/> + <arg name="name" type="uint"/> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + <arg name="stride" type="uint"/> + <arg name="format" type="uint"/> + </request> + + <!-- Create a wayland buffer for the named DRM buffer. The DRM + surface must have a name using the flink ioctl --> + <request name="create_planar_buffer"> + <arg name="id" type="new_id" interface="wl_buffer"/> + <arg name="name" type="uint"/> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + <arg name="format" type="uint"/> + <arg name="offset0" type="int"/> + <arg name="stride0" type="int"/> + <arg name="offset1" type="int"/> + <arg name="stride1" type="int"/> + <arg name="offset2" type="int"/> + <arg name="stride2" type="int"/> + </request> + + <!-- Notification of the path of the drm device which is used by + the server. The client should use this device for creating + local buffers. Only buffers created from this device should + be be passed to the server using this drm object's + create_buffer request. --> + <event name="device"> + <arg name="name" type="string"/> + </event> + + <event name="format"> + <arg name="format" type="uint"/> + </event> + + <!-- Raised if the authenticate request succeeded --> + <event name="authenticated"/> + + <enum name="capability" since="2"> + <description summary="wl_drm capability bitmask"> + Bitmask of capabilities. + </description> + <entry name="prime" value="1" summary="wl_drm prime available"/> + </enum> + + <event name="capabilities"> + <arg name="value" type="uint"/> + </event> + + <!-- Version 2 additions --> + + <!-- Create a wayland buffer for the prime fd. Use for regular and planar + buffers. Pass 0 for offset and stride for unused planes. --> + <request name="create_prime_buffer" since="2"> + <arg name="id" type="new_id" interface="wl_buffer"/> + <arg name="name" type="fd"/> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + <arg name="format" type="uint"/> + <arg name="offset0" type="int"/> + <arg name="stride0" type="int"/> + <arg name="offset1" type="int"/> + <arg name="stride1" type="int"/> + <arg name="offset2" type="int"/> + <arg name="stride2" type="int"/> + </request> + + </interface> + +</protocol> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wf-recorder-0.3.0+git19/proto/wlr-screencopy-unstable-v1.xml new/wf-recorder-0.4.0+git0/proto/wlr-screencopy-unstable-v1.xml --- old/wf-recorder-0.3.0+git19/proto/wlr-screencopy-unstable-v1.xml 2023-01-02 10:18:42.000000000 +0100 +++ new/wf-recorder-0.4.0+git0/proto/wlr-screencopy-unstable-v1.xml 2023-08-23 10:52:14.000000000 +0200 @@ -38,7 +38,7 @@ interface version number is reset. </description> - <interface name="zwlr_screencopy_manager_v1" version="2"> + <interface name="zwlr_screencopy_manager_v1" version="3"> <description summary="manager to inform clients and begin capturing"> This object is a manager which offers requests to start capturing from a source. @@ -80,13 +80,18 @@ </request> </interface> - <interface name="zwlr_screencopy_frame_v1" version="2"> + <interface name="zwlr_screencopy_frame_v1" version="3"> <description summary="a frame ready for copy"> This object represents a single frame. - When created, a "buffer" event will be sent. The client will then be able - to send a "copy" request. If the capture is successful, the compositor - will send a "flags" followed by a "ready" event. + When created, a series of buffer events will be sent, each representing a + supported buffer type. The "buffer_done" event is sent afterwards to + indicate that all supported buffer types have been enumerated. The client + will then be able to send a "copy" request. If the capture is successful, + the compositor will send a "flags" followed by a "ready" event. + + For objects version 2 or lower, wl_shm buffers are always supported, ie. + the "buffer" event is guaranteed to be sent. If the capture failed, the "failed" event is sent. This can happen anytime before the "ready" event. @@ -96,14 +101,12 @@ </description> <event name="buffer"> - <description summary="buffer information"> - Provides information about the frame's buffer. This event is sent once - as soon as the frame is created. - - The client should then create a buffer with the provided attributes, and - send a "copy" request. + <description summary="wl_shm buffer information"> + Provides information about wl_shm buffer parameters that need to be + used for this frame. This event is sent once after the frame is created + if wl_shm buffers are supported. </description> - <arg name="format" type="uint" summary="buffer format"/> + <arg name="format" type="uint" enum="wl_shm.format" summary="buffer format"/> <arg name="width" type="uint" summary="buffer width"/> <arg name="height" type="uint" summary="buffer height"/> <arg name="stride" type="uint" summary="buffer stride"/> @@ -112,8 +115,9 @@ <request name="copy"> <description summary="copy the frame"> Copy the frame to the supplied buffer. The buffer must have a the - correct size, see zwlr_screencopy_frame_v1.buffer. The buffer needs to - have a supported format. + correct size, see zwlr_screencopy_frame_v1.buffer and + zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a + supported format. If the frame is successfully copied, a "flags" and a "ready" events are sent. Otherwise, a "failed" event is sent. @@ -203,5 +207,26 @@ <arg name="width" type="uint" summary="current width"/> <arg name="height" type="uint" summary="current height"/> </event> + + <!-- Version 3 additions --> + <event name="linux_dmabuf" since="3"> + <description summary="linux-dmabuf buffer information"> + Provides information about linux-dmabuf buffer parameters that need to + be used for this frame. This event is sent once after the frame is + created if linux-dmabuf buffers are supported. + </description> + <arg name="format" type="uint" summary="fourcc pixel format"/> + <arg name="width" type="uint" summary="buffer width"/> + <arg name="height" type="uint" summary="buffer height"/> + </event> + + <event name="buffer_done" since="3"> + <description summary="all buffer types reported"> + This event is sent once after all buffer events have been sent. + + The client should proceed to create a buffer of one of the supported + types, and send a "copy" request. + </description> + </event> </interface> </protocol> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wf-recorder-0.3.0+git19/src/averr.c new/wf-recorder-0.4.0+git0/src/averr.c --- old/wf-recorder-0.3.0+git19/src/averr.c 2023-01-02 10:18:42.000000000 +0100 +++ new/wf-recorder-0.4.0+git0/src/averr.c 2023-08-23 10:52:14.000000000 +0200 @@ -2,5 +2,7 @@ const char* averr(int err) { - return av_err2str(err); + static char buf[AV_ERROR_MAX_STRING_SIZE]; + av_make_error_string(buf, sizeof(buf), err); + return buf; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wf-recorder-0.3.0+git19/src/buffer-pool.hpp new/wf-recorder-0.4.0+git0/src/buffer-pool.hpp --- old/wf-recorder-0.3.0+git19/src/buffer-pool.hpp 1970-01-01 01:00:00.000000000 +0100 +++ new/wf-recorder-0.4.0+git0/src/buffer-pool.hpp 2023-08-23 10:52:14.000000000 +0200 @@ -0,0 +1,107 @@ +#pragma once + +#include <array> +#include <mutex> +#include <atomic> +#include <type_traits> + +class buffer_pool_buf +{ +public: + bool ready_capture() const + { + return released; + } + + bool ready_encode() const + { + return available; + } + + std::atomic<bool> released{true}; // if the buffer can be used to store new pending frames + std::atomic<bool> available{false}; // if the buffer can be used to feed the encoder +}; + +template <class T, int N> +class buffer_pool +{ +public: + static_assert(std::is_base_of<buffer_pool_buf, T>::value, "T must be subclass of buffer_pool_buf"); + + buffer_pool() + { + for (size_t i = 0; i < bufs_size; ++i) { + bufs[i] = new T; + } + } + + ~buffer_pool() + { + for (size_t i = 0; i < N; ++i) { + delete bufs[i]; + } + } + + size_t size() const + { + return N; + } + + const T* at(size_t i) const + { + return bufs[i]; + } + + T& capture() + { + std::lock_guard<std::mutex> lock(mutex); + return *bufs[capture_idx]; + } + + T& encode() + { + std::lock_guard<std::mutex> lock(mutex); + return *bufs[encode_idx]; + } + + // Signal that the current capture buffer has been successfully obtained + // from the compositor and select the next buffer to capture in. + T& next_capture() + { + std::lock_guard<std::mutex> lock(mutex); + bufs[capture_idx]->released = false; + bufs[capture_idx]->available = true; + size_t next = (capture_idx + 1) % bufs_size; + if (!bufs[next]->ready_capture() && bufs_size < N) { + bufs_size++; + next = (capture_idx + 1) % bufs_size; + for (size_t i = N - 1; i > next; --i) { + bufs[i] = bufs[i - 1]; + if (encode_idx == i - 1) { + encode_idx = i; + } + } + bufs[next] = new T; + } + capture_idx = next; + return *bufs[capture_idx]; + } + + // Signal that the encode buffer has been submitted for encoding + // and select the next buffer for encoding. + T& next_encode() + { + std::lock_guard<std::mutex> lock(mutex); + bufs[encode_idx]->available = false; + bufs[encode_idx]->released = true; + encode_idx = (encode_idx + 1) % bufs_size; + return *bufs[encode_idx]; + } + +private: + std::mutex mutex; + std::array<T*, N> bufs; + size_t bufs_size = 2; + size_t capture_idx = 0; + size_t encode_idx = 0; +}; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wf-recorder-0.3.0+git19/src/frame-writer.cpp new/wf-recorder-0.4.0+git0/src/frame-writer.cpp --- old/wf-recorder-0.3.0+git19/src/frame-writer.cpp 2023-01-02 10:18:42.000000000 +0100 +++ new/wf-recorder-0.4.0+git0/src/frame-writer.cpp 2023-08-23 10:52:14.000000000 +0200 @@ -10,6 +10,7 @@ #include <cstring> #include <sstream> #include "averr.h" +#include <gbm.h> static const AVRational US_RATIONAL{1,1000000} ; @@ -124,12 +125,48 @@ case INPUT_FORMAT_X2BGR10: return AV_PIX_FMT_X2BGR10LE; #endif + case INPUT_FORMAT_RGBX64: + return AV_PIX_FMT_RGBA64LE; + case INPUT_FORMAT_BGRX64: + return AV_PIX_FMT_BGRA64LE; +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 33, 101) + case INPUT_FORMAT_RGBX64F: + return AV_PIX_FMT_RGBAF16LE; +#endif + case INPUT_FORMAT_DMABUF: + return AV_PIX_FMT_VAAPI; default: std::cerr << "Unknown format: " << params.format << std::endl; std::exit(-1); } } +static const struct { + int drm; + AVPixelFormat av; +} drm_av_format_table [] = { + { GBM_FORMAT_ARGB8888, AV_PIX_FMT_BGRA }, + { GBM_FORMAT_XRGB8888, AV_PIX_FMT_BGR0 }, + { GBM_FORMAT_ABGR8888, AV_PIX_FMT_RGBA }, + { GBM_FORMAT_XBGR8888, AV_PIX_FMT_RGB0 }, + { GBM_FORMAT_RGBA8888, AV_PIX_FMT_ABGR }, + { GBM_FORMAT_RGBX8888, AV_PIX_FMT_0BGR }, + { GBM_FORMAT_BGRA8888, AV_PIX_FMT_ARGB }, + { GBM_FORMAT_BGRX8888, AV_PIX_FMT_0RGB }, + { GBM_FORMAT_XRGB2101010, AV_PIX_FMT_X2RGB10 }, +}; + +static AVPixelFormat get_drm_av_format(int fmt) +{ + for (size_t i = 0; i < sizeof(drm_av_format_table) / sizeof(drm_av_format_table[0]); ++i) { + if (drm_av_format_table[i].drm == fmt) { + return drm_av_format_table[i].av; + } + } + std::cerr << "Failed to find AV format for" << fmt; + return AV_PIX_FMT_RGBA; +} + AVPixelFormat FrameWriter::lookup_pixel_format(std::string pix_fmt) { AVPixelFormat fmt = av_get_pix_fmt(pix_fmt.c_str()); @@ -159,24 +196,19 @@ if (is_fmt_supported(in_fmt, codec->pix_fmts)) return in_fmt; - /* Otherwise, try to use the already tested YUV420p */ - if (is_fmt_supported(AV_PIX_FMT_YUV420P, codec->pix_fmts)) - return AV_PIX_FMT_YUV420P; - - /* Lastly, use the first supported format */ - return codec->pix_fmts[0]; + /* Choose the format supported by the codec which best approximates the + * input fmt. */ + AVPixelFormat best_format = AV_PIX_FMT_NONE; + for (int i = 0; codec->pix_fmts[i] != AV_PIX_FMT_NONE; i++) { + int loss = 0; + best_format = av_find_best_pix_fmt_of_2(best_format, + codec->pix_fmts[i], in_fmt, false, &loss); + } + return best_format; } void FrameWriter::init_video_filters(const AVCodec *codec) { - if (params.codec.find("vaapi") != std::string::npos) { - if (params.video_filter == "null") { - // Add `hwupload,scale_vaapi=format=nv12` by default - // It is necessary for conversion to a proper format. - params.video_filter = "hwupload,scale_vaapi=format=nv12"; - } - } - if (params.framerate != 0){ if (params.video_filter != "null" && params.video_filter.find("fps") == std::string::npos) { params.video_filter += ",fps=" + std::to_string(params.framerate); @@ -197,6 +229,32 @@ exit(-1); } + if (this->hw_device_context) { + this->hw_frame_context = av_hwframe_ctx_alloc(this->hw_device_context); + AVHWFramesContext *hwfc = reinterpret_cast<AVHWFramesContext*>(this->hw_frame_context->data); + hwfc->format = AV_PIX_FMT_VAAPI; + hwfc->sw_format = AV_PIX_FMT_NV12; + hwfc->width = params.width; + hwfc->height = params.height; + int err = av_hwframe_ctx_init(this->hw_frame_context); + if (err < 0) { + std::cerr << "Cannot create hw frames context: " << averr(err) << std::endl; + exit(-1); + } + + this->hw_frame_context_in = av_hwframe_ctx_alloc(this->hw_device_context); + hwfc = reinterpret_cast<AVHWFramesContext*>(this->hw_frame_context_in->data); + hwfc->format = AV_PIX_FMT_VAAPI; + hwfc->sw_format = get_drm_av_format(params.drm_format); + hwfc->width = params.width; + hwfc->height = params.height; + err = av_hwframe_ctx_init(this->hw_frame_context_in); + if (err < 0) { + std::cerr << "Cannot create hw frames context: " << averr(err) << std::endl; + exit(-1); + } + } + // Build the configuration of the 'buffer' filter. // See: ffmpeg -h filter=buffer // See: https://ffmpeg.org/ffmpeg-filters.html#buffer @@ -216,6 +274,17 @@ exit(-1); } + AVBufferSrcParameters *p = av_buffersrc_parameters_alloc(); + memset(p, 0, sizeof(*p)); + p->format = AV_PIX_FMT_NONE; + p->hw_frames_ctx = this->hw_frame_context_in; + err = av_buffersrc_parameters_set(this->videoFilterSourceCtx, p); + av_free(p); + if (err < 0) { + std::cerr << "Cannot set hwcontext filter in: " << averr(err) << std::endl;; + exit(-1); + } + err = avfilter_graph_create_filter(&this->videoFilterSinkCtx, sink, "Sink", NULL, NULL, this->videoFilterGraph); if (err < 0) { @@ -312,8 +381,6 @@ this->videoCodecCtx->framerate = filter_output->frame_rate; // can be 1/0 if unknown this->videoCodecCtx->sample_aspect_ratio = filter_output->sample_aspect_ratio; - this->hw_frame_context = av_buffersink_get_hw_frames_ctx( - this->videoFilterSinkCtx); avfilter_inout_free(&inputs); avfilter_inout_free(&outputs); } @@ -609,35 +676,8 @@ } } -AVFrame *FrameWriter::prepare_frame_data(const uint8_t* pixels, bool y_invert) -{ - /* Calculate data after y-inversion */ - int stride[] = {int(params.stride)}; - const uint8_t *formatted_pixels = pixels; - if (y_invert) - { - formatted_pixels += stride[0] * (params.height - 1); - stride[0] *= -1; - } - - auto frame = av_frame_alloc(); - if (!frame) { - std::cerr << "Failed to allocate frame!" << std::endl; - return NULL; - } - - frame->data[0] = (uint8_t*)formatted_pixels; - frame->linesize[0] = stride[0]; - frame->format = get_input_format(); - frame->width = params.width; - frame->height = params.height; - - return frame; -} - -bool FrameWriter::add_frame(const uint8_t* pixels, int64_t usec, bool y_invert) +bool FrameWriter::push_frame(AVFrame *frame, int64_t usec) { - auto frame = prepare_frame_data(pixels, y_invert); frame->pts = usec; // We use time_base = 1/US_RATE // Push the RGB frame into the filtergraph */ @@ -660,6 +700,7 @@ if (err == AVERROR(EAGAIN)) { // Not an error. No frame available. // Try again later. + av_frame_free(&filtered_frame); break; } else if (err == AVERROR_EOF) { // There will be no more output frames on this sink. @@ -687,6 +728,95 @@ return true; } +bool FrameWriter::add_frame(const uint8_t* pixels, int64_t usec, bool y_invert) +{ + /* Calculate data after y-inversion */ + int stride[] = {int(params.stride)}; + const uint8_t *formatted_pixels = pixels; + if (y_invert) + { + formatted_pixels += stride[0] * (params.height - 1); + stride[0] *= -1; + } + + auto frame = av_frame_alloc(); + if (!frame) { + std::cerr << "Failed to allocate frame!" << std::endl; + return false; + } + + frame->data[0] = (uint8_t*)formatted_pixels; + frame->linesize[0] = stride[0]; + frame->format = get_input_format(); + frame->width = params.width; + frame->height = params.height; + + return push_frame(frame, usec); +} + +bool FrameWriter::add_frame(struct gbm_bo *bo, int64_t usec, bool y_invert) +{ + if (y_invert) + { + std::cerr << "Y_INVERT not supported with dmabuf" << std::endl; + return false; + } + + auto frame = av_frame_alloc(); + if (!frame) + { + std::cerr << "Failed to allocate frame!" << std::endl; + return false; + } + + if (mapped_frames.find(bo) == mapped_frames.end()) { + auto vaapi_frame = av_frame_alloc(); + if (!vaapi_frame) { + std::cerr << "Failed to allocate frame!" << std::endl; + return false; + } + + AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor*) av_mallocz(sizeof(AVDRMFrameDescriptor)); + desc->nb_layers = 1; + desc->nb_objects = 1; + desc->objects[0].fd = gbm_bo_get_fd(bo); + desc->objects[0].format_modifier = gbm_bo_get_modifier(bo); + desc->objects[0].size = gbm_bo_get_stride(bo) * gbm_bo_get_height(bo); + desc->layers[0].format = gbm_bo_get_format(bo); + desc->layers[0].nb_planes = gbm_bo_get_plane_count(bo); + for (int i = 0; i < gbm_bo_get_plane_count(bo); ++i) { + desc->layers[0].planes[i].object_index = 0; + desc->layers[0].planes[i].pitch = gbm_bo_get_stride_for_plane(bo, i); + desc->layers[0].planes[i].offset = gbm_bo_get_offset(bo, i); + } + + frame->width = gbm_bo_get_width(bo); + frame->height = gbm_bo_get_height(bo); + frame->format = AV_PIX_FMT_DRM_PRIME; + frame->data[0] = reinterpret_cast<uint8_t*>(desc); + frame->buf[0] = av_buffer_create(frame->data[0], sizeof(*desc), + [](void *, uint8_t *data) { + av_free(data); + }, frame, 0); + + vaapi_frame->format = AV_PIX_FMT_VAAPI; + vaapi_frame->hw_frames_ctx = av_buffer_ref(this->hw_frame_context_in); + + int ret = av_hwframe_map(vaapi_frame, frame, AV_HWFRAME_MAP_READ); + av_frame_unref(frame); + if (ret < 0) + { + std::cerr << "Failed to map vaapi frame " << averr(ret) << std::endl; + return false; + } + + mapped_frames[bo] = vaapi_frame; + } + + av_frame_ref(frame, mapped_frames[bo]); + return push_frame(frame, usec); +} + #ifdef HAVE_PULSE #define SRC_RATE 1e6 #define DST_RATE 1e3 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wf-recorder-0.3.0+git19/src/frame-writer.hpp new/wf-recorder-0.4.0+git0/src/frame-writer.hpp --- old/wf-recorder-0.3.0+git19/src/frame-writer.hpp 2023-01-02 10:18:42.000000000 +0100 +++ new/wf-recorder-0.4.0+git0/src/frame-writer.hpp 2023-08-23 10:52:14.000000000 +0200 @@ -26,6 +26,7 @@ #include <libavutil/pixdesc.h> #include <libavutil/hwcontext.h> #include <libavutil/opt.h> + #include <libavutil/hwcontext_drm.h> } #include "config.h" @@ -39,6 +40,10 @@ INPUT_FORMAT_BGR565, INPUT_FORMAT_X2RGB10, INPUT_FORMAT_X2BGR10, + INPUT_FORMAT_RGBX64, + INPUT_FORMAT_BGRX64, + INPUT_FORMAT_RGBX64F, + INPUT_FORMAT_DMABUF, }; struct FrameWriterParams @@ -49,6 +54,7 @@ int stride; InputFormat format; + int drm_format; std::string video_filter = "null"; // dummy filter @@ -92,6 +98,9 @@ AVBufferRef *hw_device_context = NULL; AVBufferRef *hw_frame_context = NULL; + AVBufferRef *hw_frame_context_in = NULL; + + std::map<struct gbm_bo*, AVFrame*> mapped_frames; AVPixelFormat lookup_pixel_format(std::string pix_fmt); AVPixelFormat handle_buffersink_pix_fmt(const AVCodec *codec); @@ -112,15 +121,12 @@ void send_audio_pkt(AVFrame *frame); #endif void finish_frame(AVCodecContext *enc_ctx, AVPacket& pkt); - - /** - * Upload data to a frame. - */ - AVFrame *prepare_frame_data(const uint8_t* pixels, bool y_invert); + bool push_frame(AVFrame *frame, int64_t usec); public: FrameWriter(const FrameWriterParams& params); bool add_frame(const uint8_t* pixels, int64_t usec, bool y_invert); + bool add_frame(struct gbm_bo *bo, int64_t usec, bool y_invert); #ifdef HAVE_PULSE /* Buffer must have size get_audio_buffer_size() */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/wf-recorder-0.3.0+git19/src/main.cpp new/wf-recorder-0.4.0+git0/src/main.cpp --- old/wf-recorder-0.3.0+git19/src/main.cpp 2023-01-02 10:18:42.000000000 +0100 +++ new/wf-recorder-0.4.0+git0/src/main.cpp 2023-08-23 10:52:14.000000000 +0200 @@ -18,10 +18,15 @@ #include <signal.h> #include <unistd.h> #include <wayland-client-protocol.h> +#include <gbm.h> +#include <fcntl.h> #include "frame-writer.hpp" +#include "buffer-pool.hpp" #include "wlr-screencopy-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" +#include "linux-dmabuf-unstable-v1-client-protocol.h" +#include "wl-drm-client-protocol.h" #include "config.h" @@ -37,9 +42,15 @@ std::mutex frame_writer_mutex, frame_writer_pending_mutex; std::unique_ptr<FrameWriter> frame_writer; +static int drm_fd = -1; +static struct gbm_device *gbm_device = NULL; +static std::string drm_device_name; + static struct wl_shm *shm = NULL; static struct zxdg_output_manager_v1 *xdg_output_manager = NULL; static struct zwlr_screencopy_manager_v1 *screencopy_manager = NULL; +static struct zwp_linux_dmabuf_v1 *dmabuf = NULL; +static struct wl_drm *drm = NULL; void request_next_frame(); struct wf_recorder_output @@ -109,26 +120,23 @@ .description = handle_xdg_output_description }; -struct wf_buffer +struct wf_buffer : public buffer_pool_buf { - struct wl_buffer *wl_buffer; - void *data; + struct gbm_bo *bo = nullptr; + struct wl_buffer *wl_buffer = nullptr; + void *data = nullptr; enum wl_shm_format format; + int drm_format; int width, height, stride; bool y_invert; timespec presented; uint64_t base_usec; - - std::atomic<bool> released{true}; // if the buffer can be used to store new pending frames - std::atomic<bool> available{false}; // if the buffer can be used to feed the encoder }; std::atomic<bool> exit_main_loop{false}; -#define MAX_BUFFERS 16 -wf_buffer buffers[MAX_BUFFERS]; -size_t active_buffer = 0; +buffer_pool<wf_buffer, 16> buffers; bool buffer_copy_done = false; @@ -182,11 +190,17 @@ } static bool use_damage = true; +static bool use_dmabuf = false; +static bool use_hwupload = false; static void frame_handle_buffer(void *, struct zwlr_screencopy_frame_v1 *frame, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) { - auto& buffer = buffers[active_buffer]; + if (use_dmabuf) { + return; + } + + auto& buffer = buffers.capture(); buffer.format = (wl_shm_format)format; buffer.width = width; @@ -217,7 +231,7 @@ } static void frame_handle_flags(void*, struct zwlr_screencopy_frame_v1 *, uint32_t flags) { - buffers[active_buffer].y_invert = flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT; + buffers.capture().y_invert = flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT; } int32_t frame_failed_cnt = 0; @@ -225,7 +239,7 @@ static void frame_handle_ready(void *, struct zwlr_screencopy_frame_v1 *, uint32_t tv_sec_hi, uint32_t tv_sec_low, uint32_t tv_nsec) { - auto& buffer = buffers[active_buffer]; + auto& buffer = buffers.capture(); buffer_copy_done = true; buffer.presented.tv_sec = ((1ll * tv_sec_hi) << 32ll) | tv_sec_low; buffer.presented.tv_nsec = tv_nsec; @@ -235,6 +249,7 @@ static void frame_handle_failed(void *, struct zwlr_screencopy_frame_v1 *) { std::cerr << "Failed to copy frame, retrying..." << std::endl; ++frame_failed_cnt; + request_next_frame(); if (frame_failed_cnt > MAX_FRAME_FAILURES) { std::cerr << "Failed to copy frame too many times, exiting!" << std::endl; @@ -247,12 +262,128 @@ { } +static void dmabuf_created(void *data, struct zwp_linux_buffer_params_v1 *, + struct wl_buffer *wl_buffer) { + + auto& buffer = buffers.capture(); + buffer.wl_buffer = wl_buffer; + + zwlr_screencopy_frame_v1 *frame = (zwlr_screencopy_frame_v1*) data; + + if (use_damage) { + zwlr_screencopy_frame_v1_copy_with_damage(frame, buffer.wl_buffer); + } else { + zwlr_screencopy_frame_v1_copy(frame, buffer.wl_buffer); + } +} + +static void dmabuf_failed(void *, struct zwp_linux_buffer_params_v1 *) { + std::cerr << "Failed to create dmabuf" << std::endl; + exit_main_loop = true; +} + +static const struct zwp_linux_buffer_params_v1_listener params_listener = { + .created = dmabuf_created, + .failed = dmabuf_failed, +}; + +static wl_shm_format drm_to_wl_shm_format(uint32_t format) +{ + if (format == GBM_FORMAT_ARGB8888) { + return WL_SHM_FORMAT_ARGB8888; + } else if (format == GBM_FORMAT_XRGB8888) { + return WL_SHM_FORMAT_XRGB8888; + } else { + return (wl_shm_format)format; + } +} + +static void frame_handle_linux_dmabuf(void *, struct zwlr_screencopy_frame_v1 *frame, + uint32_t format, uint32_t width, uint32_t height) +{ + if (!use_dmabuf) { + return; + } + + auto& buffer = buffers.capture(); + + buffer.format = drm_to_wl_shm_format(format); + buffer.drm_format = format; + buffer.width = width; + buffer.height = height; + + if (!buffer.wl_buffer) { + const uint64_t modifier = 0; // DRM_FORMAT_MOD_LINEAR + buffer.bo = gbm_bo_create_with_modifiers(gbm_device, buffer.width, + buffer.height, format, &modifier, 1); + if (buffer.bo == NULL) + { + buffer.bo = gbm_bo_create(gbm_device, buffer.width, + buffer.height, format, GBM_BO_USE_LINEAR | GBM_BO_USE_RENDERING); + } + if (buffer.bo == NULL) + { + std::cerr << "Failed to create gbm bo" << std::endl; + exit_main_loop = true; + return; + } + + buffer.stride = gbm_bo_get_stride(buffer.bo); + + struct zwp_linux_buffer_params_v1 *params = + zwp_linux_dmabuf_v1_create_params(dmabuf); + + uint64_t mod = gbm_bo_get_modifier(buffer.bo); + zwp_linux_buffer_params_v1_add(params, + gbm_bo_get_fd(buffer.bo), 0, + gbm_bo_get_offset(buffer.bo, 0), + gbm_bo_get_stride(buffer.bo), + mod >> 32, mod & 0xffffffff); + + zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, frame); + + zwp_linux_buffer_params_v1_create(params, buffer.width, + buffer.height, format, 0); + } else { + if (use_damage) { + zwlr_screencopy_frame_v1_copy_with_damage(frame, buffer.wl_buffer); + } else { + zwlr_screencopy_frame_v1_copy(frame, buffer.wl_buffer); + } + } +} + +static void frame_handle_buffer_done(void *, struct zwlr_screencopy_frame_v1 *) { +} + static const struct zwlr_screencopy_frame_v1_listener frame_listener = { .buffer = frame_handle_buffer, .flags = frame_handle_flags, .ready = frame_handle_ready, .failed = frame_handle_failed, .damage = frame_handle_damage, + .linux_dmabuf = frame_handle_linux_dmabuf, + .buffer_done = frame_handle_buffer_done, +}; + +static void drm_handle_device(void *, struct wl_drm *, const char *name) { + drm_device_name = name; +} + +static void drm_handle_format(void *, struct wl_drm *, uint32_t) { +} + +static void drm_handle_authenticated(void *, struct wl_drm *) { +} + +static void drm_handle_capabilities(void *, struct wl_drm *, uint32_t) { +} + +static const struct wl_drm_listener drm_listener = { + .device = drm_handle_device, + .format = drm_handle_format, + .authenticated = drm_handle_authenticated, + .capabilities = drm_handle_capabilities, }; static void handle_global(void*, struct wl_registry *registry, @@ -272,13 +403,23 @@ else if (strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 0) { screencopy_manager = (zwlr_screencopy_manager_v1*) wl_registry_bind(registry, name, - &zwlr_screencopy_manager_v1_interface, 2); + &zwlr_screencopy_manager_v1_interface, 3); } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { xdg_output_manager = (zxdg_output_manager_v1*) wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, 2); // version 2 for name & description, if available } + else if (strcmp(interface, zwp_linux_dmabuf_v1_interface.name) == 0) + { + dmabuf = (zwp_linux_dmabuf_v1*) wl_registry_bind(registry, name, + &zwp_linux_dmabuf_v1_interface, 3); + } + else if (strcmp(interface, wl_drm_interface.name) == 0) + { + drm = (wl_drm*) wl_registry_bind(registry, name, &wl_drm_interface, 1); + wl_drm_add_listener(drm, &drm_listener, NULL); + } } static void handle_global_remove(void*, struct wl_registry *, uint32_t) { @@ -295,13 +436,11 @@ return ts.tv_sec * 1000000ll + 1ll * ts.tv_nsec / 1000ll; } -static int next_frame(int frame) -{ - return (frame + 1) % MAX_BUFFERS; -} - static InputFormat get_input_format(wf_buffer& buffer) { + if (use_dmabuf && !use_hwupload) { + return INPUT_FORMAT_DMABUF; + } switch (buffer.format) { case WL_SHM_FORMAT_ARGB8888: case WL_SHM_FORMAT_XRGB8888: @@ -321,6 +460,15 @@ case WL_SHM_FORMAT_ABGR2101010: case WL_SHM_FORMAT_XBGR2101010: return INPUT_FORMAT_X2BGR10; + case WL_SHM_FORMAT_ABGR16161616: + case WL_SHM_FORMAT_XBGR16161616: + return INPUT_FORMAT_RGBX64; + case WL_SHM_FORMAT_ARGB16161616: + case WL_SHM_FORMAT_XRGB16161616: + return INPUT_FORMAT_BGRX64; + case WL_SHM_FORMAT_ABGR16161616F: + case WL_SHM_FORMAT_XBGR16161616F: + return INPUT_FORMAT_RGBX64F; default: fprintf(stderr, "Unsupported buffer format %d, exiting.", buffer.format); std::exit(0); @@ -338,7 +486,6 @@ } pthread_sigmask(SIG_BLOCK, &sigset, NULL); - int last_encoded_frame = 0; #ifdef HAVE_PULSE std::unique_ptr<PulseReader> pr; #endif @@ -346,10 +493,14 @@ while(!exit_main_loop) { // wait for frame to become available - while(buffers[last_encoded_frame].available != true && !exit_main_loop) { + while(buffers.encode().ready_encode() != true && !exit_main_loop) { std::this_thread::sleep_for(std::chrono::microseconds(1000)); } - auto& buffer = buffers[last_encoded_frame]; + if (exit_main_loop) { + break; + } + + auto& buffer = buffers.encode(); frame_writer_pending_mutex.lock(); frame_writer_mutex.lock(); @@ -359,6 +510,7 @@ { /* This is the first time buffer attributes are available */ params.format = get_input_format(buffer); + params.drm_format = buffer.drm_format; params.width = buffer.width; params.height = buffer.height; params.stride = buffer.stride; @@ -375,18 +527,37 @@ #endif } - bool do_cont = frame_writer->add_frame((unsigned char*)buffer.data, - buffer.base_usec, buffer.y_invert); - if (!do_cont) { - break; + bool do_cont = false; + + if (use_dmabuf) { + if (use_hwupload) { + uint32_t stride = 0; + void *map_data = NULL; + void *data = gbm_bo_map(buffer.bo, 0, 0, buffer.width, buffer.height, + GBM_BO_TRANSFER_READ, &stride, &map_data); + if (!data) { + std::cerr << "Failed to map bo" << std::endl; + break; + } + do_cont = frame_writer->add_frame((unsigned char*)data, + buffer.base_usec, buffer.y_invert); + gbm_bo_unmap(buffer.bo, map_data); + } else { + do_cont = frame_writer->add_frame(buffer.bo, + buffer.base_usec, buffer.y_invert); + } + } else { + do_cont = frame_writer->add_frame((unsigned char*)buffer.data, + buffer.base_usec, buffer.y_invert); } frame_writer_mutex.unlock(); - buffer.available = false; - buffer.released = true; + if (!do_cont) { + break; + } - last_encoded_frame = next_frame(last_encoded_frame); + buffers.next_encode(); } std::lock_guard<std::mutex> lock(frame_writer_mutex); @@ -406,7 +577,7 @@ static bool user_specified_overwrite(std::string filename) { struct stat buffer; - if (stat (filename.c_str(), &buffer) == 0) + if (stat (filename.c_str(), &buffer) == 0 && !S_ISCHR(buffer.st_mode)) { std::string input; std::cout << "Output file \"" << filename << "\" exists. Overwrite? Y/n: "; @@ -438,6 +609,11 @@ exit(EXIT_FAILURE); } + if (use_dmabuf && dmabuf == NULL) { + fprintf(stderr, "compositor doesn't support linux-dmabuf-unstable-v1\n"); + exit(EXIT_FAILURE); + } + if (available_outputs.empty()) { fprintf(stderr, "no outputs available\n"); @@ -572,6 +748,10 @@ Some drivers report support for rgb0 data for vaapi input but really only support yuv. + --no-dmabuf By default, wf-recorder will try to use only GPU buffers and copies if + using a GPU encoder. However, this can cause issues on some systems. In such + cases, this option will disable the GPU copy and force a CPU one. + -D, --no-damage By default, wf-recorder will request a new frame from the compositor only when the screen updates. This results in a much smaller output file, which however has a variable refresh rate. When this option is @@ -605,7 +785,7 @@ -p <option_name>=<option_value> -F, --filter Specify the ffmpeg filter string to use. For example, - -F hwupload,scale_vaapi=format=nv12 is used for VAAPI. + -F scale_vaapi=format=nv12 is used for VAAPI. -b, --bframes This option is used to set the maximum number of b-frames to be used. If b-frames are not supported by your hardware, set this to 0. @@ -634,7 +814,7 @@ printf(R"( - wf-recorder Records the video. Use Ctrl+C to stop recording. - The video file will be stored as recording.webm in the + The video file will be stored as recording.mp4 in the current working directory. - wf-recorder -f <filename>.ext Records the video. Use Ctrl+C to stop recording. @@ -646,7 +826,7 @@ Video and Audio: - wf-recorder -a Records the video and audio. Use Ctrl+C to stop recording. - The video file will be stored as recording.webm in the + The video file will be stored as recording.mp4 in the current working directory. - wf-recorder -a -f <filename>.ext Records the video and audio. Use Ctrl+C to stop recording. @@ -715,6 +895,7 @@ constexpr const char* default_cmdline_output = "interactive"; std::string cmdline_output = default_cmdline_output; + bool force_no_dmabuf = false; struct option opts[] = { { "output", required_argument, NULL, 'o' }, @@ -730,6 +911,7 @@ { "sample-rate", required_argument, NULL, 'R' }, { "sample-format", required_argument, NULL, 'X' }, { "device", required_argument, NULL, 'd' }, + { "no-dmabuf", no_argument, NULL, '&' }, { "filter", required_argument, NULL, 'F' }, { "log", no_argument, NULL, 'l' }, { "audio", optional_argument, NULL, 'a' }, @@ -836,6 +1018,10 @@ parse_codec_opts(params.audio_codec_options, optarg); break; + case '&': + force_no_dmabuf = true; + break; + default: printf("Unsupported command line argument %s\n", optarg); } @@ -857,6 +1043,64 @@ wl_registry_add_listener(registry, ®istry_listener, NULL); sync_wayland(); + if (params.codec.find("vaapi") != std::string::npos) + { + std::cout << "using VA-API, trying to enable DMA-BUF capture..." << std::endl; + + // try compositor device if not explicitly set + if (params.hw_device.empty()) + { + params.hw_device = drm_device_name; + } + + // check we use same device as compositor + if (!params.hw_device.empty() && params.hw_device == drm_device_name && !force_no_dmabuf) + { + use_dmabuf = true; + } else if (force_no_dmabuf) { + std::cout << "Disabling DMA-BUF as requested on command line" << std::endl; + } else { + std::cout << "compositor running on different device, disabling DMA-BUF" << std::endl; + } + + // region with dmabuf not implemented in wlroots + if (selected_region.is_selected()) + { + use_dmabuf = false; + std::cout << "region capture not supported with DMA-BUF" << std::endl; + } + + if (params.video_filter == "null") + { + params.video_filter = "scale_vaapi=format=nv12:out_range=full"; + if (!use_dmabuf) + { + params.video_filter.insert(0, "hwupload,"); + } + } + + if (use_dmabuf) + { + std::cout << "enabled DMA-BUF capture, device " << params.hw_device.c_str() << std::endl; + + drm_fd = open(params.hw_device.c_str(), O_RDWR); + if (drm_fd < 0) + { + fprintf(stderr, "failed to open drm device: %m\n"); + return EXIT_FAILURE; + } + + gbm_device = gbm_create_device(drm_fd); + if (gbm_device == NULL) + { + fprintf(stderr, "failed to create gbm device: %m\n"); + return EXIT_FAILURE; + } + + use_hwupload = params.video_filter.find("hwupload") != std::string::npos; + } + } + check_has_protos(); load_output_info(); @@ -921,14 +1165,6 @@ timespec first_frame; first_frame.tv_sec = -1; - active_buffer = 0; - for (auto& buffer : buffers) - { - buffer.wl_buffer = NULL; - buffer.available = false; - buffer.released = true; - } - bool spawned_thread = false; std::thread writer_thread; @@ -940,7 +1176,7 @@ while(!exit_main_loop) { // wait for a free buffer - while(buffers[active_buffer].released != true) { + while(buffers.capture().ready_capture() != true) { std::this_thread::sleep_for(std::chrono::microseconds(500)); } @@ -955,7 +1191,7 @@ break; } - auto& buffer = buffers[active_buffer]; + auto& buffer = buffers.capture(); //std::cout << "first buffer at " << timespec_to_usec(get_ct()) / 1.0e6<< std::endl; if (!spawned_thread) @@ -973,18 +1209,24 @@ buffer.base_usec = timespec_to_usec(buffer.presented) - timespec_to_usec(first_frame); - buffer.released = false; - buffer.available = true; - - active_buffer = next_frame(active_buffer); + buffers.next_capture(); } - writer_thread.join(); + if (writer_thread.joinable()) + { + writer_thread.join(); + } - for (auto& buffer : buffers) + for (size_t i = 0; i < buffers.size(); ++i) { - if (buffer.wl_buffer) - wl_buffer_destroy(buffer.wl_buffer); + auto buffer = buffers.at(i); + if (buffer && buffer->wl_buffer) + wl_buffer_destroy(buffer->wl_buffer); + } + + if (gbm_device) { + gbm_device_destroy(gbm_device); + close(drm_fd); } return EXIT_SUCCESS; ++++++ wf-recorder.obsinfo ++++++ --- /var/tmp/diff_new_pack.IJJEGe/_old 2023-08-23 14:59:53.082233690 +0200 +++ /var/tmp/diff_new_pack.IJJEGe/_new 2023-08-23 14:59:53.086233697 +0200 @@ -1,5 +1,5 @@ name: wf-recorder -version: 0.3.0+git19 -mtime: 1672651122 -commit: 460d454b1efd380a3f732f6fd70c7a5e265381f6 +version: 0.4.0+git0 +mtime: 1692780734 +commit: 7e42d6c4dcb650808cc9ec3b7c2375764e5a2662