[FFmpeg-devel] [RFC PATCH 1/2] libavdevice/pipewiregrab: add pipewire based grab
Leo Izen
leo.izen at gmail.com
Thu Sep 21 01:25:16 EEST 2023
On 9/20/23 14:40, Abhishek Ojha wrote:
> This is an proof of concept for pipewire grab to enable screen capture
> on wayland. Add a new Linux capture based on [1] PipeWire and the
> [2] Desktop portal.
> This new capture starts by asking the Desktop portal for a screencapture
> session.There are quite a few D-Bus calls involved in this, but the key
> points are:
>
> 1. A connection to org.freedesktop.portal.ScreenCast is estabilished,
> and the available cursor modes are updated. Currently only embedded
> and hidden currsor mode enabled.
>
> 2. Call CreateSession via dbus call. This is the first step of the
> communication. Response callback return the status of created
> session.
>
> 3. Call SelectSources . This is when a system dialog pops up asking
> the user to either select a monitor (desktop capture).Only monitor
> capture is enabled in current implementation.
>
> 4. Call Start . This signals the compositor that it can setup a PipeWire
> stream, and start sending buffers.
>
> Above flow is implemented as per the [2] xdg-desktop-portal. Once flow
> is completed, pipewire fd is received and using this pipewire stream is
> created and receive buffer from the created stream.
>
> For cursor implementation, embedded cursor mode is enabled that means
> cursor metadata is not handled in current implementation and has no
> control over the cursor bitmap.
>
> gdbus/pipewire logic, this is based on obs-xdg, gstpipewire and
> pipewire examples, and initial pipewire grab logic, this is based on
> libavdevice/xcbgrab and libavdevice/v4l2
>
> This implementation shows the skeleton implementation and enables basic
> functionality. I'd like to hear opinions and suggestions to improve and
> properly use this.
>
> [1] https://pipewire.org/
> [2] https://github.com/flatpak/xdg-desktop-portal/
>
> Below are the arguments for pipewiregrab.
> ffplay -f pipewiregrab -draw_mouse 1 -i :0.0
>
> Signed-off-by: Abhishek Ojha <abhishek.ojha at savoirfairelinux.com>
> ---
> configure | 9 +
> libavdevice/Makefile | 1 +
> libavdevice/alldevices.c | 1 +
> libavdevice/pipewiregrab.c | 1836 ++++++++++++++++++++++++++++++++++++
> 4 files changed, 1847 insertions(+)
> create mode 100644 libavdevice/pipewiregrab.c
>
> diff --git a/configure b/configure
> index e40dcce09e..325b10484f 100755
> --- a/configure
> +++ b/configure
> @@ -299,6 +299,7 @@ External library support:
> --enable-libxcb-shm enable X11 grabbing shm communication [autodetect]
> --enable-libxcb-xfixes enable X11 grabbing mouse rendering [autodetect]
> --enable-libxcb-shape enable X11 grabbing shape rendering [autodetect]
> + --enable-libpipewire enable screen grabbing using pipewire [autodetect]
> --enable-libxvid enable Xvid encoding via xvidcore,
> native MPEG-4/Xvid encoder exists [no]
> --enable-libxml2 enable XML parsing using the C library libxml2, needed
> @@ -1788,6 +1789,8 @@ EXTERNAL_AUTODETECT_LIBRARY_LIST="
> libxcb_shm
> libxcb_shape
> libxcb_xfixes
> + libpipewire
> + libgio_unix
> lzma
> mediafoundation
> metal
> @@ -3621,6 +3624,7 @@ v4l2_outdev_suggest="libv4l2"
> vfwcap_indev_deps="vfw32 vfwcap_defines"
> xcbgrab_indev_deps="libxcb"
> xcbgrab_indev_suggest="libxcb_shm libxcb_shape libxcb_xfixes"
> +pipewiregrab_indev_deps="libpipewire libgio_unix pthreads"
> xv_outdev_deps="xlib_xv xlib_x11 xlib_xext"
>
> # protocols
> @@ -7041,6 +7045,11 @@ if enabled libxcb; then
> enabled libxcb_xfixes && check_pkg_config libxcb_xfixes xcb-xfixes xcb/xfixes.h xcb_xfixes_get_cursor_image
> fi
>
> +enabled libpipewire && check_pkg_config libpipewire "libpipewire-0.3 >= 0.3.40" pipewire/pipewire.h pw_init
> +if enabled libpipewire; then
> + enabled libgio_unix && check_pkg_config libgio_unix gio-unix-2.0 gio/gio.h g_main_loop_new
> +fi
> +
> check_func_headers "windows.h" CreateDIBSection "$gdigrab_indev_extralibs"
>
> # d3d11va requires linking directly to dxgi and d3d11 if not building for
> diff --git a/libavdevice/Makefile b/libavdevice/Makefile
> index c30449201d..f02960782d 100644
> --- a/libavdevice/Makefile
> +++ b/libavdevice/Makefile
> @@ -49,6 +49,7 @@ OBJS-$(CONFIG_V4L2_INDEV) += v4l2.o v4l2-common.o timefilter.o
> OBJS-$(CONFIG_V4L2_OUTDEV) += v4l2enc.o v4l2-common.o
> OBJS-$(CONFIG_VFWCAP_INDEV) += vfwcap.o
> OBJS-$(CONFIG_XCBGRAB_INDEV) += xcbgrab.o
> +OBJS-$(CONFIG_PIPEWIREGRAB_INDEV) += pipewiregrab.o
> OBJS-$(CONFIG_XV_OUTDEV) += xv.o
>
> # external libraries
> diff --git a/libavdevice/alldevices.c b/libavdevice/alldevices.c
> index 8a90fcb5d7..1fa8563df4 100644
> --- a/libavdevice/alldevices.c
> +++ b/libavdevice/alldevices.c
> @@ -53,6 +53,7 @@ extern const AVInputFormat ff_v4l2_demuxer;
> extern const FFOutputFormat ff_v4l2_muxer;
> extern const AVInputFormat ff_vfwcap_demuxer;
> extern const AVInputFormat ff_xcbgrab_demuxer;
> +extern const AVInputFormat ff_pipewiregrab_demuxer;
> extern const FFOutputFormat ff_xv_muxer;
>
> /* external libraries */
> diff --git a/libavdevice/pipewiregrab.c b/libavdevice/pipewiregrab.c
> new file mode 100644
> index 0000000000..5b96d43863
> --- /dev/null
> +++ b/libavdevice/pipewiregrab.c
> @@ -0,0 +1,1836 @@
> +/*
> + * PipeWire input grabber (ScreenCast)
> + * Copyright (C) 2023 Savoir-faire Linux, Inc.
> + *
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +
> +/**
> + * @file
> + * Pipewire Grab demuxer
> + * @author Abhishek Ojha <abhishek.ojha at savoirfairelinux.com>
> + */
> +
> +#include "config.h"
> +
> +#include <fcntl.h>
> +#include <linux/dma-buf.h>
> +#include <math.h>
> +#include <pthread.h>
> +#include <stdatomic.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/mman.h>
> +#include <sys/queue.h>
> +
> +#include "libavutil/internal.h"
> +#include "libavutil/mathematics.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/parseutils.h"
> +#include "libavutil/time.h"
> +
> +#include "libavformat/avformat.h"
> +#include "libavformat/internal.h"
> +
> +#include <pipewire/pipewire.h>
> +#include <pipewire/thread-loop.h>
> +#include <spa/debug/types.h>
> +#include <spa/param/video/format-utils.h>
> +#include <spa/param/video/type-info.h>
> +
> +#include <gio/gio.h>
> +#include <gio/gunixfdlist.h>
> +
> +#ifndef __USE_XOPEN2K8
> +#define F_DUPFD_CLOEXEC \
> + 1030 /* Duplicate file descriptor with close-on-exit set. */
> +#endif
> +
> +#define BYTES_PER_PIXEL 4 /* currently all formats assume 4 bytes per pixel */
> +#define REQUEST_PATH "/org/freedesktop/portal/desktop/request/%s/obs%u"
> +#define SESSION_PATH "/org/freedesktop/portal/desktop/session/%s/obs%u"
> +#define MAX_SPA_PARAM 4 /* max number of params for spa pod */
> +
> +#define CURSOR_META_SIZE(width, height) \
> + (sizeof(struct spa_meta_cursor) + sizeof(struct spa_meta_bitmap) + \
> + width * height * 4)
> +
> +/**
> + * Pipewire capture types
> + */
> +typedef enum {
> + DESKTOP_CAPTURE = 1,
> + WINDOW_CAPTURE = 2,
> +} pw_capture_type;
> +
> +/**
> + * Pipewire version structure
> + */
> +struct FFmpegPwVersion {
> + int major;
> + int minor;
> + int micro;
> +};
> +
> +/**
> + * Pipewire structure for frame processing
> + */
> +struct PwStreamAndBuffer {
> + AVFormatContext *ctx;
> + struct pw_stream *pw_stream;
> + struct pw_buffer *pw_buf;
> +};
> +
> +/**
> + * Pipewire supported cursor modes
> + */
> +enum PortalCursorMode {
> + PORTAL_CURSOR_MODE_HIDDEN = 1 << 0,
> + PORTAL_CURSOR_MODE_EMBEDDED = 1 << 1,
> + PORTAL_CURSOR_MODE_METADATA = 1 << 2,
> +};
> +
> +/**
> + * PipeWire Grab main structure
> + * Contains all necessary data that hold current state
> + * Initial state of this struct is allocated by libavdevice
> + * logic when declaring the AVInputFormat ff_pipewiregrab_demuxer.
> + * This structure is priv_data of AVFormatContext instance.
> + */
> +typedef struct PipewireGrabContext {
> + /** thread used to intialize/start pipewire logic */
> + pthread_t pipewire_pthread;
> +
> + /** conditional synchronization logic elecments between pipewire
> + * thread and libavdevice thread.
> + */
> + pthread_cond_t avstream_codec_cond_var;
> + pthread_mutex_t avstream_codec_mutex;
> + atomic_int avstream_codec_flag;
> +
> + pthread_mutex_t current_pkt_mutex;
> + AVPacket* current_pkt;
> +
> + GDBusConnection *connection;
> + GDBusProxy *proxy;
> + GCancellable *cancellable;
> +
> + char *sender_name;
> + char *session_handle;
> +
> + uint32_t pipewire_node;
> + int pipewire_fd;
> +
> + uint32_t available_cursor_modes;
> +
> + GMainLoop *glib_main_loop;
> + struct pw_thread_loop *thread_loop;
> + struct pw_context *context;
> +
> + struct pw_core *core;
> + struct spa_hook core_listener;
> +
> + struct pw_stream *stream;
> + struct spa_hook stream_listener;
> + struct spa_video_info format;
> +
> + pw_capture_type capture_type;
> + bool negotiated;
> +
> + /**< draw_mouse is to control the enable/disable mouse cursor */
> + int draw_mouse;
> +
> + /** cursor metadata */
> + struct {
> + bool visible;
> + bool valid;
bool should be int.
> + int x, y;
> + int hotspot_x, hotspot_y;
> + int width, height;
> + } cursor;
> +
> + /**< Width and height of the grab frame (private option) */
> + uint32_t width, height;
> +
> + /**< Size in bytes of the frame pixel data */
> + uint32_t frame_size;
size_t?
> + uint8_t Bpp;
> + enum AVPixelFormat av_pxl_format;
> + AVRational user_frame_rate;
> +
> + int64_t time_frame;
> + AVRational time_base;
> + int64_t frame_duration;
> +
> + const AVClass *class;
> +
> + const char *framerate;
> + struct FFmpegPwVersion server_version;
> + int server_version_sync;
> +} PipewireGrabContext;
> +
> +/**
> + * dbus's method/event marshalling structure
> + */
> +struct DbusCallData {
> + AVFormatContext *ctx;
> + char *request_path;
> + guint signal_id;
> + gulong cancelled_id;
> +};
> +
> +#define FOLLOW_CENTER -1
> +
> +#define OFFSET(x) offsetof(PipewireGrabContext, x)
> +#define D AV_OPT_FLAG_DECODING_PARAM
> +static const AVOption options[] = {
> + { "framerate",
> + "",
> + OFFSET(framerate),
> + AV_OPT_TYPE_STRING,
AV_OPT_TYPE_VIDEO_RATE points to an AVRational
> + { .str = "ntsc" },
> + 0,
> + 0,
> + D },
> + { "draw_mouse",
> + "Draw the mouse pointer.",
> + OFFSET(draw_mouse),
> + AV_OPT_TYPE_INT,
AV_OPT_TYPE_BOOL
> + { .i64 = 0 },
> + 0,
> + 1,
> + D },
> + { NULL },
> +};
> +
> +/**
> + * Function parse the pipewire version
> + * @param dst FmpegPwVersion that contains PipeWire version info
> + * @param version pipewire version
> + */
> +static bool parse_pw_version(struct FFmpegPwVersion *dst, const char *version)
Convention is to return int >= 0 on success and an int < 0 on failure. I
don't think we use the bool type in FFmpeg except when external
libraries require it.
> +{
> + int n_matches = sscanf(version, "%d.%d.%d", &dst->major, &dst->minor,
> + &dst->micro);
> + return n_matches == 3;
> +}
> +
> +/**
> + * Function update the pipewire version in private data
> + * params, then signal the blocked @ref pipewiregrab_read_header
> + *
> + * @param ctx AVFormatContext that contains PipeWire Grab main structure
> + * @param version pipewire version
> + */
> +static void update_pw_versions(AVFormatContext *ctx, const char *version)
> +{
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> + av_log(ctx, AV_LOG_DEBUG, "[pipewiregrab] Server version: %s\n", version);
> + av_log(ctx, AV_LOG_INFO, "[pipewiregrab] Library version: %s\n",
> + pw_get_library_version());
> + av_log(ctx, AV_LOG_DEBUG, "[pipewiregrab] Header version: %s\n",
> + pw_get_headers_version());
> +
> + if (!parse_pw_version(&pw_ctx->server_version, version))
> + av_log(ctx, AV_LOG_WARNING,
> + "[pipewiregrab] failed to parse server version");
> +}
> +
> +/**
> + * AVStream creator
> + * This function creates a valid AVStream based on pipewire's negotiated
> + * params, then signal the blocked @ref pipewiregrab_read_header
> + *
> + * @param ctx AVFormatContext that contains PipeWire Grab main structure
> + * @return 0 on success, AVERROR on failure
> + */
> +static int create_ffmpeg_stream(AVFormatContext *ctx)
> +{
> + int ret = 0;
> + int64_t frame_size_bits;
> + AVStream *avstream = avformat_new_stream(ctx, NULL);
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return AVERROR(EINVAL);
> + }
> +
If pw_ctw can be NULL here (which I don't think it can be, tbh), then
you leak the avstream you just created.
> + if (!avstream) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] avformat_new_stream failed!\n");
> + return AVERROR(ENOMEM);
> + }
> +
> + avstream->avg_frame_rate.num = pw_ctx->user_frame_rate.num;
> + avstream->avg_frame_rate.den = pw_ctx->user_frame_rate.den;
avstream->avg_frame_rate = pw_ctx->user_frame_rate;
> +
> + avpriv_set_pts_info(avstream, 64, 1, 1000000);
What's the point of storing the timebase if you don't use it?
> +
> + pw_ctx->time_base = (AVRational){ avstream->avg_frame_rate.den,
> + avstream->avg_frame_rate.num };
This won't compile with -pedantic. Use av_make_q instead.
> + pw_ctx->frame_duration = av_rescale_q(1, pw_ctx->time_base, AV_TIME_BASE_Q);
> + pw_ctx->time_frame = av_gettime_relative();
> +
> + frame_size_bits = (int64_t)pw_ctx->width * pw_ctx->height * pw_ctx->Bpp * 8;
> + avstream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
> + avstream->codecpar->codec_id = AV_CODEC_ID_RAWVIDEO;
> + avstream->codecpar->width = pw_ctx->width;
> + avstream->codecpar->height = pw_ctx->height;
> + avstream->codecpar->bit_rate =
> + av_rescale(frame_size_bits, avstream->avg_frame_rate.num,
> + avstream->avg_frame_rate.den);
> + avstream->codecpar->format = pw_ctx->av_pxl_format;
> +
> + atomic_store(&pw_ctx->avstream_codec_flag, 1);
> + pthread_cond_signal(&pw_ctx->avstream_codec_cond_var);
> +
> + return ret;
If ret can't be nonzero here, just put return 0 at the bottom and ditch ret.
> +}
> +
> +/**
> + * Pipewire manual abort
> + *
> + * @param user_data AVFormatContext that contains PipeWire Grab main structure
> + * @param message abort message
> + */
> +static void pipewiregrab_abort(void *user_data, const char *message)
> +{
> + AVFormatContext *ctx = user_data;
> + PipewireGrabContext *pw_ctx = NULL;
Unnecessary initialization.
> + if (!ctx) {
> + return;
> + }Code style, one-liners don't use {} braces.
> +
> + pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return;
> + }Is this something that actually might happen where AVFormatContext is
defined but not populated with priv_data? And if so, does the user need
to know about it? If not, you could do something like this:
if (!ctx || !ctx->priv_data)
return;
> +
> + atomic_store(&pw_ctx->avstream_codec_flag, 1);
> + pthread_cond_signal(&pw_ctx->avstream_codec_cond_var);
> +
> + av_log(ctx, AV_LOG_ERROR, "[pipewiregrab] Aborting: %s\n", message);
> +
> + if (pw_ctx->thread_loop != NULL)Convention is to avoid "!= NULL" in FFmpeg, and use standard C nonzero
checking.
> + pw_thread_loop_signal(pw_ctx->thread_loop, false);
> +
> + if (pw_ctx->glib_main_loop != NULL &&
Same.
> + g_main_loop_is_running(pw_ctx->glib_main_loop)) {
> + g_main_loop_quit(pw_ctx->glib_main_loop);
> + }
> +}
> +
> +/**
> + * Pipewire core info event
> + *
> + * @param user_data AVFormatContext that contains PipeWire Grab main structure
> + * @param info pw_core_info
> + */
> +static void on_core_info_cb(void *user_data, const struct pw_core_info *info)
> +{
> + AVFormatContext *ctx = user_data;
> + update_pw_versions(ctx, info->version);
> +}
> +
> +/**
> + * Pipewire core completion event
> + *
> + * @param user_data AVFormatContext that contains PipeWire Grab main structure
> + * @param id pipewire object id of calling
> + * @param seq pipewire object sequence
> + */
> +static void on_core_done_callback(void *user_data, uint32_t id, int seq)
> +{
> + AVFormatContext *ctx = user_data;
> + PipewireGrabContext *pw_ctx = NULL;
Unnecessary initialization.
> +
> + (void)seq;
What?
> +
> + if (!ctx) {
> + return;
> + }
Code style again.
> +
> + pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return;
> + }
> +
> + if (id == PW_ID_CORE) {
> + pw_thread_loop_signal(pw_ctx->thread_loop, false);
> + g_main_loop_quit(pw_ctx->glib_main_loop);
> + }
> +}
> +
> +/**
> + * Pipewire core error event
> + *
> + * @param user_data AVFormatContext that contains PipeWire Grab main structure
> + * @param id pipewire object id of calling
> + * @param seq pipewire object sequence
> + * @param res error number
> + * @param message error message
> + */
> +static void on_core_error_callback(void *user_data, uint32_t id, int seq,
> + int res, const char *message)
> +{
> + AVFormatContext *ctx = user_data;
> + PipewireGrabContext *pw_ctx = NULL;
Unnecessary initialization.
> + if (!ctx) {
> + return;
> + }
Code style.
> +
> + pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return;
> + }
> +
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Error id:%u seq:%d res:%d (%s): %s\n", id, seq, res,
> + g_strerror(res), message);
> +
> + pw_thread_loop_signal(pw_ctx->thread_loop, false);
> + g_main_loop_quit(pw_ctx->glib_main_loop);
> +}
> +
> +/**
> + * Register pipewire core event callbacks here
> + */
> +static const struct pw_core_events core_events = {
> + PW_VERSION_CORE_EVENTS,
> + .info = on_core_info_cb,
> + .done = on_core_done_callback,
> + .error = on_core_error_callback,
> +};
> +
> +/**
> + * helper function: create a new path to dbus desktop object request method
> + *
> + * @param ctx AVFormatContext that contains PipeWire Grab main structure
> + * @param out_path dbus token count
> + * @param out_token final object/interface path for request method
> + */
> +static void new_request_path(AVFormatContext *ctx, char **out_path,
> + char **out_token)
> +{
> + static uint32_t request_token_count = 0;
You need to store this in the private context. Static non-const fields
aren't going to work because multiple clients can call libavdevice at
the same time. Protecting it with a mutex won't work either.
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return;
> + }
> +
> + request_token_count++;
> +
> + if (out_token) {
> + *out_token = g_strdup_printf("obs%u", request_token_count);
> + }
Code style, braces.
> +
> + if (out_path) {
> + *out_path = g_strdup_printf(REQUEST_PATH, pw_ctx->sender_name,
> + request_token_count);
> + }Code style, braces.
> +}
> +
> +/**
> + * helper function: create new path to dbus active desktop session
> + *
> + * @param ctx AVFormatContext that contains PipeWire Grab main structure
> + * @param out_path dbus session token count
> + * @param out_token final object/interface path for active session
> + */
This looks suspiciously like the previous function, can the code be
deduplicated?
> +static void new_session_path(AVFormatContext *ctx, char **out_path,
> + char **out_token)
> +{
> + static uint32_t session_token_count = 0;
See earlier comments for above function.
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return;
> + }
> +
> + session_token_count++;
> +
> + if (out_token) {
> + *out_token = g_strdup_printf("obs%u", session_token_count);
> + }
> +
> + if (out_path) {
> + *out_path = g_strdup_printf(SESSION_PATH, pw_ctx->sender_name,
> + session_token_count);
> + }
> +}
> +
> +/**
> + * helper function: convert spa pixel format to av pixel format
> + *
> + * @param spa_format conatins spa format value
> + * @return AVPixelFormat
> + */
> +static enum AVPixelFormat
> +spa_pixel_format_to_av_pixel_format(uint32_t spa_format)
> +{
> + enum AVPixelFormat ret = AV_PIX_FMT_NONE;
> + switch (spa_format) {
> + case SPA_VIDEO_FORMAT_RGBA:
> + case SPA_VIDEO_FORMAT_RGBx:
> + ret = AV_PIX_FMT_RGBA;
> + break;
> +
Inline a return statement here, and return AV_PIX_FMT_NONE at the end of
the function.
> + case SPA_VIDEO_FORMAT_BGRA:
> + case SPA_VIDEO_FORMAT_BGRx:
> + ret = AV_PIX_FMT_BGRA;
> + break;
> +
> + default:
> + ret = AV_PIX_FMT_NONE;
> + }
> +
> + return ret;
> +}
> +
> +/**
> + * helper function: gracefully stop/destroy of pipewire objects
> + *
> + * @param ctx AVFormatContext that contains PipeWire Grab main structure
> + */
> +static void teardown_pipewire(AVFormatContext *ctx)
> +{
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return;
> + }
> +
> + if (pw_ctx->thread_loop) {
> + pw_thread_loop_unlock(pw_ctx->thread_loop);
> + pw_thread_loop_stop(pw_ctx->thread_loop);
> + }
You should nullify these various fields after you disconnect, so calling
this twice won't create errors.
> +
> + if (pw_ctx->stream) {
> + pw_stream_disconnect(pw_ctx->stream);
> + g_clear_pointer(&pw_ctx->stream, pw_stream_destroy);
> + }
> +
> + if (pw_ctx->core)
> + pw_core_disconnect(pw_ctx->core);
> +
> + if (pw_ctx->context)
> + pw_context_destroy(pw_ctx->context);
> +
> + if (pw_ctx->thread_loop) {
> + pw_thread_loop_destroy(pw_ctx->thread_loop);
> + }
Code style, braces.
> +
> + pw_ctx->negotiated = false;
> +}
> +
> +/**
> + * helper function: gracefully stop/destroy of dbus session
> + *
> + * @param ctx AVFormatContext that contains PipeWire Grab main structure
> + */
> +static void destroy_session(AVFormatContext *ctx)
> +{
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return;
> + }
> +
> + if (pw_ctx->session_handle) {
> + g_dbus_connection_call(
> + pw_ctx->connection, "org.freedesktop.portal.Desktop",
> + pw_ctx->session_handle, "org.freedesktop.portal.Session", "Close",
> + NULL, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
> +
> + g_clear_pointer(&pw_ctx->session_handle, g_free);
> + }
> +
> + g_cancellable_cancel(pw_ctx->cancellable);
> + g_clear_object(&pw_ctx->cancellable);
> + g_clear_object(&pw_ctx->connection);
> + g_clear_object(&pw_ctx->proxy);
> + g_clear_pointer(&pw_ctx->sender_name, g_free);
> +}
> +
> +/**
> + * helper function: gracefully stop/destroy of pipewire/dbus session
> + *
> + * @param ctx AVFormatContext that contains PipeWire Grab main structure
> + */
> +static void pipewire_destroy(AVFormatContext *ctx)
> +{
> + teardown_pipewire(ctx);
> + destroy_session(ctx);
> +}
> +
> +/**
> + * callback to stop/disconnect current dbus session
> + *
> + * @param ptr_dbus_call_data dbus marshalling structure
> + */
> +static void dbus_call_data_free(struct DbusCallData *ptr_dbus_call_data)
> +{
> + AVFormatContext *ctx = NULL;
> + PipewireGrabContext *pw_ctx = NULL;
Unnecessary initializations.
> +
> + if (!ptr_dbus_call_data)
> + return;
> +
> + ctx = ptr_dbus_call_data->ctx;
> + if (!ctx)
> + return;
> +
> + pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return;
> + }
> +
> + if (ptr_dbus_call_data->signal_id)
> + g_dbus_connection_signal_unsubscribe(pw_ctx->connection,
> + ptr_dbus_call_data->signal_id);
> +
> + if (ptr_dbus_call_data->cancelled_id > 0)
> + g_signal_handler_disconnect(pw_ctx->cancellable,
> + ptr_dbus_call_data->cancelled_id);
> +
> + g_clear_pointer(&ptr_dbus_call_data->request_path, g_free);
> + g_free(ptr_dbus_call_data);
> +}
> +
> +/**
> + * dbus callback of cancelled events
> + *
> + * @param cancellable not used
> + * @param user_data dbus marshalling structure
> + */
> +static void on_cancelled_callback(GCancellable *cancellable, gpointer user_data)
> +{
> + struct DbusCallData *ptr_dbus_call_data = user_data;
> + AVFormatContext *ctx = ptr_dbus_call_data->ctx;
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> +
> + (void)cancellable;
What?
> +
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return;
> + }
> +
> + av_log(ctx, AV_LOG_INFO, "[pipewiregrab] Screencast session cancelled!\n");
> +
> + g_dbus_connection_call(pw_ctx->connection, "org.freedesktop.portal.Desktop",
> + ptr_dbus_call_data->request_path,
> + "org.freedesktop.portal.Request", "Close", NULL,
> + NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
> +}
> +
> +/**
> + * pipewire callback of parameters changed events
> + *
> + * @param user_data dbus marshalling structure
> + * @param id contains chan param type
> + * @param param pointer to changed param structure
> + */
> +static void on_stream_param_changed_callback(void *user_data, uint32_t id,
> + const struct spa_pod *param)
> +{
> + struct spa_pod_builder pod_builder;
> + const struct spa_pod *params[MAX_SPA_PARAM];
> + uint32_t n_params = 0;
> + uint8_t params_buffer[4096];
> + int result;
> +
> + AVFormatContext *ctx = user_data;
> + PipewireGrabContext *pw_ctx = NULL;
> + if (!ctx) {
> + return;
> + }
Code style, braces.
> +
> + pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return;
> + }
> +
> + if (!param || id != SPA_PARAM_Format) {
> + av_log(ctx, AV_LOG_WARNING,
> + "[pipewiregrab] Ignoring none stream param format change!\n");
> + return;
> + }
> +
> + result = spa_format_parse(param, &pw_ctx->format.media_type,
> + &pw_ctx->format.media_subtype);
> + if (result < 0)
> + return;
> +
> + if (pw_ctx->format.media_type != SPA_MEDIA_TYPE_video ||
> + pw_ctx->format.media_subtype != SPA_MEDIA_SUBTYPE_raw)
> + return;
> +
> + spa_format_video_raw_parse(param, &pw_ctx->format.info.raw);
> +
> + av_log(ctx, AV_LOG_INFO, "[pipewiregrab] Negotiated format:\n");
> +
> + av_log(ctx, AV_LOG_INFO, "[pipewiregrab] Format: %d (%s)\n",
> + pw_ctx->format.info.raw.format,
> + spa_debug_type_find_name(spa_type_video_format,
> + pw_ctx->format.info.raw.format));
> + av_log(ctx, AV_LOG_INFO, "[pipewiregrab] Size: %dx%d\n",
> + pw_ctx->format.info.raw.size.width,
> + pw_ctx->format.info.raw.size.height);
> + av_log(ctx, AV_LOG_INFO, "[pipewiregrab] Framerate: %d/%d\n",
> + pw_ctx->format.info.raw.framerate.num,
> + pw_ctx->format.info.raw.framerate.denom);
> +
> + pw_ctx->width = pw_ctx->format.info.raw.size.width;
> + pw_ctx->height = pw_ctx->format.info.raw.size.height;
> + pw_ctx->Bpp = BYTES_PER_PIXEL;
> + pw_ctx->frame_size = pw_ctx->width * pw_ctx->height * pw_ctx->Bpp;
Check for overflow.
> + pw_ctx->av_pxl_format =
> + spa_pixel_format_to_av_pixel_format(pw_ctx->format.info.raw.format);
> +
> + /* Video crop */
> + pod_builder = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer));
> + params[n_params++] = spa_pod_builder_add_object(
> + &pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
> + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop),
> + SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_region)));
> +
> + /* Cursor */
> + params[n_params++] = spa_pod_builder_add_object(
> + &pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
> + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Cursor), SPA_PARAM_META_size,
> + SPA_POD_CHOICE_RANGE_Int(CURSOR_META_SIZE(64, 64),
> + CURSOR_META_SIZE(1, 1),
> + CURSOR_META_SIZE(1024, 1024)));
> +
> + /* Buffer options */
> + params[n_params++] = spa_pod_builder_add_object(
> + &pod_builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
> + SPA_PARAM_BUFFERS_dataType,
> + SPA_POD_Int((1 << SPA_DATA_MemPtr) | (1 << SPA_DATA_MemFd)));
> +
> + /* Meta header */
> + params[n_params++] = spa_pod_builder_add_object(
> + &pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
> + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
> + SPA_PARAM_META_size,
> + SPA_POD_Int(sizeof(struct spa_meta_header)));
> +
> + pw_stream_update_params(pw_ctx->stream, params, n_params);
> +
> + pw_ctx->negotiated = true;
> +
> + create_ffmpeg_stream(ctx);
This can fail, so you should return its value.
> +}
> +
> +/**
> + * pipewire callback of state changed events
> + *
> + * @param user_data dbus marshalling structure
> + * @param old pipewire stream old state
> + * @param state pipewire stream current state
> + * @param error received error information
> + */
> +static void on_stream_state_changed_callback(void *user_data,
> + enum pw_stream_state old,
> + enum pw_stream_state state,
> + const char *error)
> +{
> + AVFormatContext *ctx = user_data;
> +
> + (void)old;
> + (void)error;
What does this even do?
> +
> + if (!ctx) {
> + return;
> + }
Code style.
> +
> + av_log(ctx, AV_LOG_INFO, "[pipewiregrab] stream state: \"%s\"\n",
> + pw_stream_state_as_string(state));
> +}
> +
> +/**
> + * pipewire done callback
> + *
> + * @param user_data dbus marshalling structure
> + */
> +static void on_stream_trigger_done_callback(void *user_data)
Does pipewire accept null callbacks?
> +{
> + (void)user_data;
> + /* nothing to do for now */
> +}
> +
> +/**
> + * pipewire recycle buffer to put buffer back to queue and free
> + * allocated space after processing of frame
> + *
> + * @param opaque reference to PwStreamAndBuffer
> + * @param data spa_data for handling memeory mapping
> + */
> +static void pw_recycle(void *opaque, uint8_t *data)
> +{
> + enum pw_stream_state state;
> + struct PwStreamAndBuffer *buffer = (struct PwStreamAndBuffer *)opaque;
> + struct spa_buffer *spa_buf = buffer->pw_buf->buffer;
> + struct spa_data *d = &spa_buf->datas[0];
> +
> + if (d->type == SPA_DATA_MemFd) {
> + munmap(buffer->pw_buf->user_data, d->maxsize + d->mapoffset);
Where'd mmap come from?
> + }Code Style braces.
> +
> + /* only put buffer in queue when stream state is streaming */
> + state = pw_stream_get_state (buffer->pw_stream, NULL);
> + if (state == PW_STREAM_STATE_STREAMING) {
> + pw_stream_queue_buffer(buffer->pw_stream, buffer->pw_buf);
> + }
Code style, braces.
> + free(buffer);
How was this allocated? Possibly you want av_free.
> +}
> +
> +/**
> + * find most recent buffer received form pipewire
> + *
> + * @param pw_ctx PipewireGrabContext private data
> + * @return pw_buf most recent buffer
> + */
> +static struct pw_buffer* find_most_recent_buffer_and_recycle_olders(PipewireGrabContext *pw_ctx)
> +{
> + struct pw_buffer* pw_buf = NULL;
Misplaced *
> + while (true) {
while (1)
> + struct pw_buffer *aux = pw_stream_dequeue_buffer(pw_ctx->stream);
> + if (!aux) {
> + break;
> + }
> + if (pw_buf) {
> + pw_stream_queue_buffer(pw_ctx->stream, pw_buf);
> + }Code style braces;
> + pw_buf = aux;
> + }
> + return pw_buf;
> +}
> +
> +/**
> + * our data processing function
> + *
> + * @param user_data dbus marshalling structure
> + */
> +static void on_stream_process_callback(void *user_data)
> +{
> + struct spa_buffer *spa_buf = NULL;
> + struct pw_buffer *pw_buf = NULL;
> + uint8_t *map = NULL;
> + void *sdata = NULL;
> + struct spa_meta_header *header = NULL;
> + AVFormatContext *ctx = user_data;
> + PipewireGrabContext *pw_ctx = NULL;
> + AVPacket *pkt = NULL;
> + struct PwStreamAndBuffer* buffer = NULL;
> +
> + ctx = user_data;
> + if (!ctx) {
> + return;
> + }
Braces.
> +
> + pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return;
> + }
> +
> + pw_buf = find_most_recent_buffer_and_recycle_olders(pw_ctx);
> + if (pw_buf == NULL) {
> + av_log(ctx, AV_LOG_ERROR, "[pipewiregrab] Out of buffers!\n");
> + return;
> + }
!pw_buf, tho is there a reason you can't allocate one here?
> +
> + spa_buf = pw_buf->buffer;
> + header = spa_buffer_find_meta_data(spa_buf,
> + SPA_META_Header, sizeof(*header));
> + if (header && (header->flags & SPA_META_HEADER_FLAG_CORRUPTED)) {
> + av_log(ctx, AV_LOG_ERROR, "[pipewiregrab] buffer is corrupt");
> + pw_stream_queue_buffer(pw_ctx->stream, pw_buf);
> + return;
> + }
> +
> + if (pw_ctx->av_pxl_format == AV_PIX_FMT_NONE) {
> + av_log(ctx, AV_LOG_WARNING,
> + "[pipewiregrab] unsupported buffer format: %d\n",
> + pw_ctx->format.info.raw.format);
> + return;
> + }
> +
> + if (spa_buf->datas[0].type == SPA_DATA_MemFd ) {
> + map =
> + mmap(NULL, spa_buf->datas[0].maxsize + spa_buf->datas[0].mapoffset,
> + PROT_READ, MAP_PRIVATE, spa_buf->datas[0].fd, 0);
Why are we mmapping here? Are you prepared to catch SIGBUS?
> + if (map == MAP_FAILED) {
> + av_log(ctx, AV_LOG_ERROR, "[pipewiregrab] mmap failed! error %s\n",
> + g_strerror(errno));
> + return;
> + }
> + pw_buf->user_data = map;
> + sdata = SPA_PTROFF(map, spa_buf->datas[0].mapoffset, uint8_t);
> + } else if (spa_buf->datas[0].type == SPA_DATA_MemPtr) {
> + if (spa_buf->datas[0].data == NULL) {
> + av_log(ctx, AV_LOG_ERROR, "[pipewiregrab] no data!\n");
> + return;
> + }
> + map = NULL;
> + sdata = spa_buf->datas[0].data;
> + } else {
> + av_log(ctx, AV_LOG_ERROR, "[pipewiregrab] Buffer is not valid!\n");
> + return;
> + }
> +
> + /* Create a new AVPacket on top of the pw buffer */
> + pkt = av_packet_alloc();
> + if (pkt == NULL) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Failed to allocate new av packet\n");
> + return;
> + }
> +
> + buffer = (struct PwStreamAndBuffer*)malloc(sizeof(struct PwStreamAndBuffer));
> + if (!buffer) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Failed to allocate PwStreamAndBuffer entry\n");
> + av_packet_free(&pkt);
Better off having a cleanup at the end with goto end; or goto fail; or
something.
> + return;
> + }
> + buffer->ctx = ctx;
> + buffer->pw_stream = pw_ctx->stream;
> + buffer->pw_buf = pw_buf;
> +
> + pkt->buf = av_buffer_create(sdata, pw_ctx->frame_size, pw_recycle, buffer, 0);
This can fail.
> + pkt->data = sdata;
> + pkt->size = pw_ctx->frame_size;
> +
> + /* Free the last current_pkt and make the new frame available */
> + pthread_mutex_lock(&pw_ctx->current_pkt_mutex);
> + av_packet_unref(pw_ctx->current_pkt);
> + av_packet_ref(pw_ctx->current_pkt, pkt);
This can fail.
> + pthread_mutex_unlock(&pw_ctx->current_pkt_mutex);
> +
> + av_packet_free(&pkt);
> + return;
Return an int, in case of failure.
> +}
> +
> +static const struct pw_stream_events stream_events = {
> + PW_VERSION_STREAM_EVENTS,
> + .state_changed = on_stream_state_changed_callback,
> + .param_changed = on_stream_param_changed_callback,
> + .process = on_stream_process_callback,
> + .trigger_done = on_stream_trigger_done_callback,
> +};
> +
> +static struct DbusCallData *subscribe_to_signal(AVFormatContext *ctx,
> + const char *path,
> + GDBusSignalCallback callback)
> +{
> + struct DbusCallData *ptr_dbus_call_data;
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return NULL;
> + }
> +
> + ptr_dbus_call_data = g_new0(struct DbusCallData, 1);
Can you use av_calloc or av_mallocz here?
> + ptr_dbus_call_data->ctx = ctx;
> + ptr_dbus_call_data->request_path = g_strdup(path);
> + ptr_dbus_call_data->cancelled_id =
> + g_signal_connect(pw_ctx->cancellable, "cancelled",
> + G_CALLBACK(on_cancelled_callback),
> + ptr_dbus_call_data /* user_data */);
> + ptr_dbus_call_data->signal_id = g_dbus_connection_signal_subscribe(
> + pw_ctx->connection, "org.freedesktop.portal.Desktop" /*sender*/,
> + "org.freedesktop.portal.Request" /*interface_name*/,
> + "Response" /*member: dbus signal name*/,
> + ptr_dbus_call_data->request_path /*object_path*/, NULL,
> + G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, callback, ptr_dbus_call_data, NULL);
> +
> + return ptr_dbus_call_data;
> +}
> +
> +static int play_pipewire_stream(AVFormatContext *ctx)
> +{
> + int ret = 0;
> + const struct spa_pod *ptr_spa_pod[1];
It looks like you're using an array of 1 to stack-allocate the location
and make it static. Don't do that, this variable can disappear with the
stack frame.
> + uint8_t buffer[4096];
> + struct spa_pod_builder spa_pod_bldr = {
> + 0,
> + };
> +
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return ret;
> + }
> +
> + pw_ctx->thread_loop =
> + pw_thread_loop_new("[pipewiregrab] thread loop", NULL);
> + if (pw_ctx->thread_loop == NULL) {
!pw_ctx->thread_loop;
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] pw_thread_loop_new failed!\n");
> + ret = AVERROR(ENOMEM);
> + return ret;
> + }
> +
> + pw_ctx->context =
> + pw_context_new(pw_thread_loop_get_loop(pw_ctx->thread_loop), NULL, 0);
> + if (pw_ctx->context == NULL) {
Same here.
> + av_log(ctx, AV_LOG_ERROR, "[pipewiregrab] pw_context_new failed!\n");
> + ret = AVERROR(ENOMEM);
> + goto thread_loop_destroy;
> + }
> +
> + if (pw_thread_loop_start(pw_ctx->thread_loop) < 0) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] pw_thread_loop_start failed!\n");
> + ret = AVERROR(EFAULT);
> + goto context_destroy;
Why several goto here?
> + }
> +
> + pw_thread_loop_lock(pw_ctx->thread_loop);
> +
> + /* Core */
> + pw_ctx->core =
> + pw_context_connect_fd(pw_ctx->context,
> + fcntl(pw_ctx->pipewire_fd, F_DUPFD_CLOEXEC, 3),
> + NULL, 0);
> + if (pw_ctx->core == NULL) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] pw_context_connect_fd failed!\n");
> + ret = AVERROR(EFAULT);
> + pw_thread_loop_unlock(pw_ctx->thread_loop);
> + goto context_destroy;
> + }
> +
> + pw_core_add_listener(pw_ctx->core, &pw_ctx->core_listener, &core_events,
> + ctx /* user_data */);
> +
> + /* Stream */
> + pw_ctx->stream = pw_stream_new(
> + pw_ctx->core, "wayland grab",
> + pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", PW_KEY_MEDIA_CATEGORY,
> + "Capture", PW_KEY_MEDIA_ROLE, "Screen", NULL));
> +
> + if (pw_ctx->stream == NULL) {
> + av_log(ctx, AV_LOG_ERROR, "[pipewiregrab] pw_stream_new failed!\n");
> + ret = AVERROR(ENOMEM);
> + pw_thread_loop_unlock(pw_ctx->thread_loop);
> + goto core_disconnect;
> + }
> +
> + pw_stream_add_listener(pw_ctx->stream, &pw_ctx->stream_listener,
> + &stream_events, ctx /* user_data */);
> +
> + /* Stream parameters */
> + spa_pod_bldr = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
> + ptr_spa_pod[0] = spa_pod_builder_add_object(
See earlier comment about the stack frame.
> + &spa_pod_bldr, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
> + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
> + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
> + SPA_FORMAT_VIDEO_format,
> + SPA_POD_CHOICE_ENUM_Id(4, SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_RGBx,
> + SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_BGRA),
> + SPA_FORMAT_VIDEO_size,
> + SPA_POD_CHOICE_RANGE_Rectangle(&SPA_RECTANGLE(320, 240),
> + &SPA_RECTANGLE(1, 1),
> + &SPA_RECTANGLE(4096, 4096)),
> + SPA_FORMAT_VIDEO_framerate,
> + SPA_POD_CHOICE_RANGE_Fraction(
> + &SPA_FRACTION(pw_ctx->user_frame_rate.num,
> + pw_ctx->user_frame_rate.den),
> + &SPA_FRACTION(0, 1), &SPA_FRACTION(144, 1)));
> +
> + ret = pw_stream_connect(
> + pw_ctx->stream, PW_DIRECTION_INPUT, pw_ctx->pipewire_node,
> + PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS, ptr_spa_pod,
> + 1);
If pw_stream_connect somehow takes ownership of ptr_spa_pod[0] then you
should just use &ptr_spa_pod instead and don't declare it as an array.
> + av_log(ctx, AV_LOG_INFO, "[pipewiregrab] Starting screen capture ...\n");
> +
> + pw_thread_loop_unlock(pw_ctx->thread_loop);
> +
> + return ret;
> +
> +/*
> + * disconnect from the server/daemon
> + * destroy the core proxy object and will remove the proxies
> + * that might have been created on this connection
> + */
> +core_disconnect:
> + pw_core_disconnect(pw_ctx->core);
> +
> +context_destroy:
> + pw_context_destroy(pw_ctx->context);
> +
> +thread_loop_destroy:
> + pw_thread_loop_destroy(pw_ctx->thread_loop);
> +
Too many gotos here. One cleanup is fine.
> + return ret;
> +}
> +
> +static void on_pipewire_remote_opened_callback(GObject *source,
> + GAsyncResult *res,
> + gpointer user_data)
> +{
Since this function can fail it should have a return value.
> + g_autoptr(GUnixFDList) fd_list = NULL;
> + g_autoptr(GVariant) result = NULL;
> + g_autoptr(GError) error = NULL;
What is g_autoptr and why can't we use standard C?
> + int fd_index;
> +
> + AVFormatContext *ctx = user_data;
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return;
> + }
> +
> + result = g_dbus_proxy_call_with_unix_fd_list_finish(G_DBUS_PROXY(source),
> + &fd_list, res, &error);
> + if (error) {
> + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Error retrieving pipewire fd: %s\n",
> + error->message);
> +
> + return;
> + }
> +
> + g_variant_get(result, "(h)", &fd_index, &error);
> +
> + pw_ctx->pipewire_fd = g_unix_fd_list_get(fd_list, fd_index, &error);
> + if (error) {
> + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Error retrieving pipewire fd: %s\n",
> + error->message);
> +
> + return;
> + }
> +
> + play_pipewire_stream(ctx);
> +}
> +
> +static void open_pipewire_remote(AVFormatContext *ctx)
> +{
> + GVariantBuilder builder;
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return;
> + }
> +
> + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
> +
> + g_dbus_proxy_call_with_unix_fd_list(
> + pw_ctx->proxy, "OpenPipeWireRemote",
> + g_variant_new("(oa{sv})", pw_ctx->session_handle, &builder),
> + G_DBUS_CALL_FLAGS_NONE, -1, NULL, pw_ctx->cancellable,
> + on_pipewire_remote_opened_callback, ctx /* user_data */);
> +}
> +
> +static void on_start_response_received_callback(
> + GDBusConnection *connection, const char *sender_name,
> + const char *object_path, const char *interface_name,
> + const char *signal_name, GVariant *parameters, gpointer user_data)
> +{
> + g_autoptr(GVariant) stream_properties = NULL;
> + g_autoptr(GVariant) streams = NULL;
> + g_autoptr(GVariant) result = NULL;
> + GVariantIter iter;
> + uint32_t response;
> +
> + struct DbusCallData *ptr_dbus_call_data = user_data;
> + AVFormatContext *ctx = ptr_dbus_call_data->ctx;
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> +
> + (void)connection;
> + (void)sender_name;
> + (void)object_path;
> + (void)interface_name;
> + (void)signal_name;
What does this even do?
> +
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return;
> + }
> +
> + g_clear_pointer(&ptr_dbus_call_data, dbus_call_data_free);
If you're allocating it with av_mallocz you have to free it with av_free.
> +
> + g_variant_get(parameters, "(u at a{sv})", &response, &result);
> +
> + if (response != 0) {
if (response) {
> + av_log(
> + ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Failed to start screencast, denied or cancelled by user!\n");
> + pipewiregrab_abort(
> + ctx, "Failed to start screencast, denied or cancelled by user!");
> + return;
> + }
Since this function can fail, it should return a failure.
> +
> + streams = g_variant_lookup_value(result, "streams", G_VARIANT_TYPE_ARRAY);
> +
> + g_variant_iter_init(&iter, streams);
> + g_assert(g_variant_iter_n_children(&iter) == 1);No asserts. Use av_assert if you must, since it lets you control the
assert level on the configure line.
> +
> + g_variant_iter_loop(&iter, "(u at a{sv})", &pw_ctx->pipewire_node,
> + &stream_properties);
> +
> + av_log(ctx, AV_LOG_INFO,
> + "[pipewiregrab] Monitor selected, setting up screencast\n\n");
> +
> + open_pipewire_remote(ctx);
> +}
> +
> +static void on_started_callback(GObject *source, GAsyncResult *res,
> + gpointer user_data)
> +{
> + g_autoptr(GVariant) result = NULL;
> + g_autoptr(GError) error = NULL;
> + struct DbusCallData *ptr_dbus_call_data = user_data;
> + AVFormatContext *ctx = ptr_dbus_call_data->ctx;
> + (void)user_data;
> +
> + result = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
> + if (error) {
> + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Error selecting screencast source: %s\n",
> + error->message);
> +
> + return;This function can fail, so you should return an int.
> + }
> +}
> +
> +static void start(AVFormatContext *ctx)
> +{
> + g_autofree char *request_token = NULL;
> + g_autofree char *request_path = NULL;
> + GVariantBuilder builder;
> + struct DbusCallData *ptr_dbus_call_data;
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return;
> + }
> +
> + new_request_path(ctx, &request_path, &request_token);
> +
> + av_log(ctx, AV_LOG_WARNING, "[pipewiregrab] Asking for monitor…\n");
> +
> + ptr_dbus_call_data = subscribe_to_signal(
> + ctx /* user_data */, request_path, on_start_response_received_callback);
> + if (ptr_dbus_call_data == NULL) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] subscribe_to_signal failed!\n");
> + return;
> + }
This function can fail so it should return an int.
> +
> + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
> + g_variant_builder_add(&builder, "{sv}", "handle_token",
> + g_variant_new_string(request_token));
> +
> + g_dbus_proxy_call(pw_ctx->proxy, "Start",
> + g_variant_new("(osa{sv})", pw_ctx->session_handle, "",
> + &builder),
> + G_DBUS_CALL_FLAGS_NONE, -1, pw_ctx->cancellable,
> + on_started_callback, ptr_dbus_call_data /* user_data */);
> +}
> +
> +static void on_select_source_response_received_callback(
> + GDBusConnection *connection, const char *sender_name,
> + const char *object_path, const char *interface_name,
> + const char *signal_name, GVariant *parameters, gpointer user_data)
> +{
This looks like an earlier function. Can code be deduplicated?
> + g_autoptr(GVariant) ret = NULL;
> + uint32_t response;
> + struct DbusCallData *ptr_dbus_call_data = user_data;
> + AVFormatContext *ctx = ptr_dbus_call_data->ctx;
> + (void)connection;
> + (void)sender_name;
> + (void)object_path;
> + (void)interface_name;
> + (void)signal_name;
> +
> + av_log(ctx, AV_LOG_INFO,
> + "[pipewiregrab] Response to select source received\n");
> +
> + g_clear_pointer(&ptr_dbus_call_data, dbus_call_data_free);
> +
> + g_variant_get(parameters, "(u at a{sv})", &response, &ret);
> +
> + if (response != 0) {
> + av_log(
> + ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Failed to select source, denied or cancelled by user!\n");
> + pipewiregrab_abort(
> + ctx, "Failed to select source, denied or cancelled by user!");
> + return;
> + }
> +
> + start(ctx);
> +}
> +
> +static void on_source_selected_callback(GObject *source, GAsyncResult *res,
> + gpointer user_data)
> +{
This looks like an earlier function. Can code be deduplicated?
> + g_autoptr(GVariant) result = NULL;
> + g_autoptr(GError) error = NULL;
> +
> + struct DbusCallData *ptr_dbus_call_data = user_data;
> + AVFormatContext *ctx = ptr_dbus_call_data->ctx;
> +
> + result = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
> + if (error) {
> + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Error selecting screencast source: %s\n",
> + error->message);
> +
> + return;
> + }
> +}
> +
> +static void select_source(AVFormatContext *ctx)
> +{
> + g_autofree char *request_token = NULL;
> + g_autofree char *request_path = NULL;
> + GVariantBuilder builder;
> + struct DbusCallData *ptr_dbus_call_data;
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return;
> + }
> +
> + new_request_path(ctx, &request_path, &request_token);
> +
> + ptr_dbus_call_data =
> + subscribe_to_signal(ctx /* user_data */, request_path,
> + on_select_source_response_received_callback);
> + if (ptr_dbus_call_data == NULL) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] subscribe_to_signal failed!\n");
> + return;
> + }
> +
> + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
> + g_variant_builder_add(&builder, "{sv}", "types",
> + g_variant_new_uint32(pw_ctx->capture_type));
> + g_variant_builder_add(&builder, "{sv}", "multiple",
> + g_variant_new_boolean(FALSE));
> + g_variant_builder_add(&builder, "{sv}", "handle_token",
> + g_variant_new_string(request_token));
> +
> + if (pw_ctx->available_cursor_modes & PORTAL_CURSOR_MODE_METADATA)
> + g_variant_builder_add(&builder, "{sv}", "cursor_mode",
> + g_variant_new_uint32(PORTAL_CURSOR_MODE_METADATA));
> + else if ((pw_ctx->available_cursor_modes & PORTAL_CURSOR_MODE_EMBEDDED)
> + && pw_ctx->cursor.visible)
> + g_variant_builder_add(&builder, "{sv}", "cursor_mode",
> + g_variant_new_uint32(PORTAL_CURSOR_MODE_EMBEDDED));
> + else
> + g_variant_builder_add(&builder, "{sv}", "cursor_mode",
> + g_variant_new_uint32(PORTAL_CURSOR_MODE_HIDDEN));
> +
> + g_dbus_proxy_call(pw_ctx->proxy, "SelectSources",
> + g_variant_new("(oa{sv})", pw_ctx->session_handle,
> + &builder),
> + G_DBUS_CALL_FLAGS_NONE, -1, pw_ctx->cancellable,
> + (GAsyncReadyCallback)on_source_selected_callback,
> + ptr_dbus_call_data /* user_data */);
> +}
> +
> +static void on_create_session_response_received_callback(
> + GDBusConnection *connection, const char *sender_name,
> + const char *object_path, const char *interface_name,
> + const char *signal_name, GVariant *parameters, gpointer user_data)
> +{
This looks like an earlier function. Can code be deduplicated?
> + uint32_t response;
> + g_autoptr(GVariant) result = NULL;
> + struct DbusCallData *ptr_dbus_call_data = user_data;
> + AVFormatContext *ctx = ptr_dbus_call_data->ctx;
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> +
> + (void)connection;
> + (void)sender_name;
> + (void)object_path;
> + (void)interface_name;
> + (void)signal_name;
> +
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return;
> + }
> +
> + g_clear_pointer(&ptr_dbus_call_data, dbus_call_data_free);
> +
> + g_variant_get(parameters, "(u at a{sv})", &response, &result);
> +
> + if (response != 0) {
> + av_log(
> + ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Pipewire failed to create session, denied or cancelled by user!\n");
> + return;
> + }
> +
> + av_log(ctx, AV_LOG_DEBUG, "[pipewiregrab] Screencast session created\n");
> +
> + g_variant_lookup(result, "session_handle", "s", &pw_ctx->session_handle);
> +
> + /* enable/disable mouse cursor */
> + pw_ctx->cursor.visible = pw_ctx->draw_mouse;
> + select_source(ctx);
> +}
> +
> +static void on_session_created_callback(GObject *source, GAsyncResult *res,
> + gpointer user_data)
> +{
This looks like an earlier function. Can code be deduplicated?
> + g_autoptr(GVariant) result = NULL;
> + g_autoptr(GError) error = NULL;
> + struct DbusCallData *ptr_dbus_call_data = user_data;
> + AVFormatContext *ctx = ptr_dbus_call_data->ctx;
> +
> + result = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
> + if (error) {
> + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Error creating screencast session: %s\n",
> + error->message);
> +
> + return;
> + }
> +}
> +
> +/**
> + * function to start the negotation
> + *
> + * @param ctx ctx AVFormatContext avformat context
> + */
> +static void create_session(AVFormatContext *ctx)
> +{
> + GVariantBuilder builder;
> + g_autofree char *request_token = NULL;
> + g_autofree char *request_path = NULL;
> + g_autofree char *session_token = NULL;
> + struct DbusCallData *ptr_dbus_call_data;
> +
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return;
> + }
> +
> + new_request_path(ctx, &request_path, &request_token);
> + new_session_path(ctx, NULL, &session_token);
> +
> + ptr_dbus_call_data =
> + subscribe_to_signal(ctx /* user_data */, request_path,
> + on_create_session_response_received_callback);
> + if (ptr_dbus_call_data == NULL) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] subscribe_to_signal failed!\n");
> + return;
> + }
> +
> + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
> + g_variant_builder_add(&builder, "{sv}", "handle_token",
> + g_variant_new_string(request_token));
> + g_variant_builder_add(&builder, "{sv}", "session_handle_token",
> + g_variant_new_string(session_token));
> +
> + g_dbus_proxy_call(pw_ctx->proxy, "CreateSession",
> + g_variant_new("(a{sv})", &builder),
> + G_DBUS_CALL_FLAGS_NONE, -1, pw_ctx->cancellable,
> + on_session_created_callback,
> + ptr_dbus_call_data /* user_data */);
> +}
> +
> +/**
> + * helper function: get available cursor mode and update
> + *
> + * @param ctx ctx AVFormatContext avformat context
> + */
> +static void update_available_cursor_modes(AVFormatContext *ctx)
> +{
> + g_autoptr(GVariant) cached_cursor_modes = NULL;
> + uint32_t available_cursor_modes;
> +
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return;
> + }
> +
> + cached_cursor_modes =
> + g_dbus_proxy_get_cached_property(pw_ctx->proxy, "AvailableCursorModes");
> + available_cursor_modes =
> + cached_cursor_modes ? g_variant_get_uint32(cached_cursor_modes) : 0;
> +
> + /* Use embedded or hidden mode for now*/
> + available_cursor_modes = available_cursor_modes &
> + (PORTAL_CURSOR_MODE_EMBEDDED |
> + PORTAL_CURSOR_MODE_HIDDEN);
> + pw_ctx->available_cursor_modes = available_cursor_modes;
> +}
> +
> +/**
> + * callback function of created proxy
> + *
> + * @param source GObject
> + * @param res GAsyncResult
> + * @param user_data ctx AVFormatContext avformat context
> + */
> +static void on_pipewire_proxy_created_callback(GObject *source,
> + GAsyncResult *res,
> + gpointer user_data)
> +{
This looks like an earlier function. Can code be deduplicated?
> + g_autoptr(GError) error = NULL;
> + AVFormatContext *ctx = user_data;
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> +
> + (void)source;
> +
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return;
> + }
> +
> + pw_ctx->proxy = g_dbus_proxy_new_finish(res, &error);
> +
> + if (error) {
> + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Error creating proxy: %s\n", error->message);
> +
> + return;
> + }
> +
> + update_available_cursor_modes(ctx);
> + create_session(ctx);
> +}
> +
> +/**
> + * Get the proxy from desktop-portal
> + *
> + * @param ctx AVFormatContext avformat context
> + */
> +static void create_pipewire_proxy(AVFormatContext *ctx)
> +{
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return;
> + }
> +
> + g_dbus_proxy_new(pw_ctx->connection, G_DBUS_PROXY_FLAGS_NONE, NULL,
> + "org.freedesktop.portal.Desktop",
> + "/org/freedesktop/portal/desktop",
> + "org.freedesktop.portal.ScreenCast", pw_ctx->cancellable,
> + (GAsyncReadyCallback)on_pipewire_proxy_created_callback,
> + ctx /* user_data */);
> +}
> +
> +/**
> + * Initiate the pipewiregrab
> + *
> + * @param ctx AVFormatContext avformat context
> + */
> +static int init_pipewiregrab(AVFormatContext *ctx)
> +{
> + char *aux;
> + g_autoptr(GError) error = NULL;
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return AVERROR(EINVAL);
> + }
> +
> + pw_ctx->capture_type = DESKTOP_CAPTURE;
> + pw_ctx->cancellable = g_cancellable_new();
> +
> + pw_ctx->connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
> + if (error) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Error getting session bus: %s\n",
> + error->message);
> + return error->code;
> + }
> +
> + pw_ctx->sender_name =
> + g_strdup(g_dbus_connection_get_unique_name(pw_ctx->connection) + 1);
> +
> + /* Replace dots by underscores */
av_strireplace
> + while ((aux = g_strstr_len(pw_ctx->sender_name, -1, ".")) != NULL)
> + *aux = '_';
> +
> + av_log(ctx, AV_LOG_DEBUG, "[pipewiregrab] Initialized (sender name: %s)\n",
> + pw_ctx->sender_name);
> +
> + create_pipewire_proxy(ctx);
> +
> + return 0;
> +}
> +
> +static const AVClass pipewiregrab = {
> + .class_name = "pipewiregrab indev",
> + .item_name = av_default_item_name,
> + .option = options,
> + .version = LIBAVUTIL_VERSION_INT,
> + .category = AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT,
> +};
Put this at the end of the file, not in the middle.
> +
> +/**
> + * helper function: calculate the wait time based
> + * on the frame duration
> + *
> + * @param s AVFormatContext avformat context
> + * @return current time
> + */
> +static int64_t wait_frame(AVFormatContext *s)
> +{
> + PipewireGrabContext *c = s->priv_data;
> + int64_t curtime, delay;
> +
> + c->time_frame += c->frame_duration;
> +
> + for (;;) {
while (1)
> + curtime = av_gettime_relative();
> + delay = c->time_frame - curtime;
> + if (delay <= 0)
> + break;
> + av_usleep(delay);
I'm not sure you need to do this, check other demuxers.
> + }
> +
> + return curtime;
> +}
> +
> +/**
> + * helper function: Grabs a frame from pipewire
> + *
> + * @param ctx AVFormatContext avformat context
> + * @param pkt Packet holding the grabbed frame
> + */
> +static void grab_frame(AVFormatContext *ctx, AVPacket *pkt)
> +{
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> +
> + pthread_mutex_lock(&pw_ctx->current_pkt_mutex);
> + av_packet_ref(pkt, pw_ctx->current_pkt);
This can fail, so you need to return an error message.
> + pthread_mutex_unlock(&pw_ctx->current_pkt_mutex);
> +}
> +
> +/**
> + * read the frame from pipewire
> + *
> + * @param ctx Context from avformat core
> + * @param pkt Packet holding the grabbed frame
> + * @return frame size in bytes
> + */
> +static int pipewiregrab_read_packet(AVFormatContext *ctx, AVPacket *pkt)
> +{
> + int64_t pts;
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return AVERROR(EINVAL);
> + }
> + do {
> + wait_frame(ctx);
> + pts = av_gettime();
> + grab_frame(ctx, pkt);
Can fail.
> + pkt->dts = pkt->pts = pts;
> + pkt->duration = pw_ctx->frame_duration;
> + } while(!pkt->data);
> +
> + return pkt->size;
> +}
> +
> +/**
> + * Closes pipewire frame grabber.
> + *
> + * @param ctx Context from avformat core
> + * @return 0 success, !0 failure
> + */
> +static int pipewiregrab_read_close(AVFormatContext *ctx)
> +{
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return AVERROR(EINVAL);
> + }
> +
> + if (pw_ctx->thread_loop != NULL)
if (pw_ctx->thread_loop)
Also nullify it when you free it.
> + pw_thread_loop_signal(pw_ctx->thread_loop, false);
> +
> + if (pw_ctx->glib_main_loop != NULL &&
> + g_main_loop_is_running(pw_ctx->glib_main_loop)) {
> + g_main_loop_quit(pw_ctx->glib_main_loop);
> + }
> +
> + pthread_join(pw_ctx->pipewire_pthread, NULL);
> +
> + if (pw_ctx->current_pkt) {
> + av_packet_free(&pw_ctx->current_pkt);
> + }av_packet_free(&foo); works even if foo is NULL, so this check is
superfluous.
> +
> + return 0;
> +}
> +
> +/**
> + * Thread to initialize the Pipewire library and
> + * initiate the connetion flow
> + *
> + * @param argo thread argument ctx
> + */
> +static void *pipewire_gdbus_init_pthread(void *argo)
Why is the return type of this function void *? You only return zero or
AVERROR values. Make it int.
> +{
> + intptr_t ret = 0;
> + AVFormatContext *ctx = (AVFormatContext *)argo;
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return (void *)AVERROR(EINVAL);
> + }
> +
> + /* initialize the PipeWire library */
> + pw_init(NULL, NULL);
> +
> + pw_ctx->glib_main_loop = g_main_loop_new(NULL, FALSE);
> + if (pw_ctx->glib_main_loop == NULL) {
> + av_log(ctx, AV_LOG_ERROR, "[pipewiregrab] g_main_loop_new failed!\n");
> + ret = AVERROR(ENOMEM);
> + goto exit_pw_deinit;
> + }
> +
> + if (init_pipewiregrab(ctx) != 0) {
> + av_log(ctx, AV_LOG_ERROR, "[pipewiregrab] init_pipewiregrab failed!\n");
> + ret = AVERROR(EPERM);
> + goto exit_glib_loop;
> + }
> +
> + av_log(ctx, AV_LOG_INFO, "[pipewiregrab] starting glib main loop!\n");
> + g_main_loop_run(pw_ctx->glib_main_loop);
> +
> + /* g_main_loop quited, destroy pipewire*/
> + pipewire_destroy(ctx);
> +
> +exit_glib_loop:
> + g_main_loop_unref(pw_ctx->glib_main_loop);
> + pw_ctx->glib_main_loop = NULL;
> +exit_pw_deinit:
> + pw_deinit();
> + > + return (void *)ret;
> +}
> +
> +/**
> + * Initializes the pipewire grab device demuxer
> + *
> + * @param ctx Context from avformat core
> + * @return AVERROR_IO error, 0 success
> + */
> +static int pipewiregrab_read_header(AVFormatContext *ctx)
> +{
> + int ret = 0;
> + PipewireGrabContext *pw_ctx = ctx->priv_data;
> +
> + if (!pw_ctx) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Invalid private context data!\n");
> + return AVERROR(EINVAL);
> + }
> +
> + atomic_init(&pw_ctx->avstream_codec_flag, 0);
> +
> + pthread_cond_init(&pw_ctx->avstream_codec_cond_var, NULL);
> + pthread_mutex_init(&pw_ctx->avstream_codec_mutex, NULL);
> + pthread_mutex_init(&pw_ctx->current_pkt_mutex, NULL);
> +
> + pw_ctx->current_pkt = av_packet_alloc();
> + if (pw_ctx->current_pkt == NULL) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] Failed to allocate new av packet\n");
> + return -1;
return AVERROR(ENOMEM);
> + }
> +
> + ret = av_parse_video_rate(&pw_ctx->user_frame_rate, pw_ctx->framerate);
> + if (ret < 0) {
> + av_log(ctx, AV_LOG_ERROR,
> + "[pipewiregrab] av_parse_video_rate failed!\n");
> + return ret; > + }
> +
> + ret = pthread_create(&pw_ctx->pipewire_pthread, NULL,
> + &pipewire_gdbus_init_pthread, ctx);
Check the return value, this can fail.
> + pthread_mutex_lock(&pw_ctx->avstream_codec_mutex);
> + while (atomic_load(&pw_ctx->avstream_codec_flag) == 0) {
while (!atomic_load(foo))
> + pthread_cond_wait(&pw_ctx->avstream_codec_cond_var,
> + &pw_ctx->avstream_codec_mutex);
> + }
> + /* wait until signaled */
> + pthread_mutex_unlock(&pw_ctx->avstream_codec_mutex);
> +
> + return ret;
> +}
> +
> +/** pipewire grabber device demuxer declaration */
> +const AVInputFormat ff_pipewiregrab_demuxer = {
> + .name = "pipewiregrab",
> + .long_name = NULL_IF_CONFIG_SMALL("screen capture, using pipewire"),
> + .priv_data_size = sizeof(struct PipewireGrabContext),
> + .read_header = pipewiregrab_read_header,
> + .read_packet = pipewiregrab_read_packet,
> + .read_close = pipewiregrab_read_close,
> + .flags = AVFMT_NOFILE,
> + .priv_class = &pipewiregrab,
> +};
More information about the ffmpeg-devel
mailing list