From 519b09c74c9387fe2edede9ab777b5402eb64868 Mon Sep 17 00:00:00 2001 From: dimtpap Date: Tue, 8 Oct 2024 20:38:47 +0300 Subject: [PATCH] linux-pipewire: Support MJPEG and H264 devices --- plugins/linux-pipewire/CMakeLists.txt | 3 + plugins/linux-pipewire/camera-portal.c | 15 +- plugins/linux-pipewire/pipewire.c | 226 +++++++++++++++++++------ plugins/linux-pipewire/video-decoder.c | 159 +++++++++++++++++ plugins/linux-pipewire/video-decoder.h | 58 +++++++ 5 files changed, 405 insertions(+), 56 deletions(-) create mode 100644 plugins/linux-pipewire/video-decoder.c create mode 100644 plugins/linux-pipewire/video-decoder.h diff --git a/plugins/linux-pipewire/CMakeLists.txt b/plugins/linux-pipewire/CMakeLists.txt index e81bdfe2f2c262..c5c3a119807b92 100644 --- a/plugins/linux-pipewire/CMakeLists.txt +++ b/plugins/linux-pipewire/CMakeLists.txt @@ -7,6 +7,7 @@ if(NOT ENABLE_PIPEWIRE) endif() find_package(PipeWire 0.3.33 REQUIRED) +find_package(FFmpeg REQUIRED COMPONENTS avcodec avutil avformat) find_package(Gio 2.76 REQUIRED) find_package(Libdrm REQUIRED) get_target_property(libdrm_include_directories Libdrm::Libdrm INTERFACE_INCLUDE_DIRECTORIES) @@ -26,6 +27,8 @@ target_sources( PRIVATE $<$:camera-portal.c> $<$:camera-portal.h> + $<$:video-decoder.c> + $<$:video-decoder.h> formats.c formats.h linux-pipewire.c diff --git a/plugins/linux-pipewire/camera-portal.c b/plugins/linux-pipewire/camera-portal.c index 794f849e3cc4c0..cf91fe646402e9 100644 --- a/plugins/linux-pipewire/camera-portal.c +++ b/plugins/linux-pipewire/camera-portal.c @@ -1,6 +1,7 @@ /* camera-portal.c * * Copyright 2021 Georges Basile Stavracas Neto + * Copyright 2024-2026 Dimitris Papaioannou * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -361,8 +362,18 @@ static void camera_format_list(struct camera_device *dev, obs_property_t *prop) obs_data_set_int(data, "video_format", format); format_name = obs_pw_video_format.pretty_name; - } else { - continue; + } else if (media_subtype == SPA_MEDIA_SUBTYPE_mjpg || media_subtype == SPA_MEDIA_SUBTYPE_h264) { + obs_data_set_bool(data, "encoded", true); + obs_data_set_int(data, "video_format", SPA_VIDEO_FORMAT_ENCODED); + + switch (media_subtype) { + case SPA_MEDIA_SUBTYPE_mjpg: + format_name = "MJPG"; + break; + case SPA_MEDIA_SUBTYPE_h264: + format_name = "H264"; + break; + } } if (spa_pod_parse_object(p->param, SPA_TYPE_OBJECT_Format, format ? &format : NULL, diff --git a/plugins/linux-pipewire/pipewire.c b/plugins/linux-pipewire/pipewire.c index d312a186ad5384..5d62bd77ea6d00 100644 --- a/plugins/linux-pipewire/pipewire.c +++ b/plugins/linux-pipewire/pipewire.c @@ -1,6 +1,7 @@ /* pipewire.c * * Copyright 2020 Georges Basile Stavracas Neto + * Copyright 2024-2026 Dimitris Papaioannou * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,6 +22,7 @@ #include "pipewire.h" #include "formats.h" +#include "video-decoder.h" #include @@ -105,6 +107,8 @@ struct _obs_pipewire_stream { struct spa_hook stream_listener; struct spa_source *reneg; + obs_pipewire_decoder *decoder; + struct spa_video_info format; enum spa_meta_videotransform_value transform; @@ -297,7 +301,8 @@ static void swap_texture_red_blue(gs_texture_t *texture) } static inline struct spa_pod *build_format(obs_pipewire_stream *obs_pw_stream, struct spa_pod_builder *b, - uint32_t format, uint64_t *modifiers, size_t modifier_count) + uint32_t format, uint32_t media_subtype, uint64_t *modifiers, + size_t modifier_count) { struct spa_rectangle max_resolution = SPA_RECTANGLE(8192, 4320); struct spa_rectangle min_resolution = SPA_RECTANGLE(1, 1); @@ -335,7 +340,7 @@ static inline struct spa_pod *build_format(obs_pipewire_stream *obs_pw_stream, s spa_pod_builder_push_object(b, &format_frame, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); /* add media type and media subtype properties */ spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); - spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); + spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(media_subtype), 0); /* formats */ spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); @@ -374,6 +379,7 @@ static bool build_format_params(obs_pipewire_stream *obs_pw_stream, struct spa_p { obs_pipewire *obs_pw = obs_pw_stream->obs_pw; uint32_t params_count = 0; + bool is_async = (obs_source_get_output_flags(obs_pw_stream->source) & OBS_SOURCE_ASYNC_VIDEO) != 0; const struct spa_pod **params; @@ -382,7 +388,14 @@ static bool build_format_params(obs_pipewire_stream *obs_pw_stream, struct spa_p return false; } - params = bzalloc(2 * obs_pw_stream->format_info.num * sizeof(struct spa_pod *)); + size_t formats_count = 2 * obs_pw_stream->format_info.num; + if (is_async) { + // MJPEG and H264 + formats_count += 2; + } + + assert(formats_count > 0); + params = bzalloc(formats_count * sizeof(struct spa_pod *)); if (!params) { blog(LOG_ERROR, "[pipewire] Failed to allocate memory for param pointers"); @@ -396,17 +409,26 @@ static bool build_format_params(obs_pipewire_stream *obs_pw_stream, struct spa_p if (obs_pw_stream->format_info.array[i].modifiers.num == 0) { continue; } - params[params_count++] = build_format(obs_pw_stream, pod_builder, - obs_pw_stream->format_info.array[i].spa_format, - obs_pw_stream->format_info.array[i].modifiers.array, - obs_pw_stream->format_info.array[i].modifiers.num); + params[params_count++] = + build_format(obs_pw_stream, pod_builder, obs_pw_stream->format_info.array[i].spa_format, + SPA_MEDIA_SUBTYPE_raw, obs_pw_stream->format_info.array[i].modifiers.array, + obs_pw_stream->format_info.array[i].modifiers.num); } build_shm: for (size_t i = 0; i < obs_pw_stream->format_info.num; i++) { params[params_count++] = build_format(obs_pw_stream, pod_builder, - obs_pw_stream->format_info.array[i].spa_format, NULL, 0); + obs_pw_stream->format_info.array[i].spa_format, + SPA_MEDIA_SUBTYPE_raw, NULL, 0); + } + + if (is_async) { + params[params_count++] = build_format(obs_pw_stream, pod_builder, SPA_VIDEO_FORMAT_ENCODED, + SPA_MEDIA_SUBTYPE_mjpg, NULL, 0); + params[params_count++] = build_format(obs_pw_stream, pod_builder, SPA_VIDEO_FORMAT_ENCODED, + SPA_MEDIA_SUBTYPE_h264, NULL, 0); } + *param_list = params; *n_params = params_count; return true; @@ -638,23 +660,49 @@ static enum video_range_type video_color_range_from_spa_color_range(enum spa_vid } } -static bool prepare_obs_frame(obs_pipewire_stream *obs_pw_stream, struct obs_source_frame *frame) +static bool prepare_obs_frame(obs_pipewire_stream *obs_pw_stream, struct spa_buffer *buffer, + struct obs_source_frame *frame) { struct obs_pw_video_format obs_pw_video_format; - frame->width = obs_pw_stream->format.info.raw.size.width; - frame->height = obs_pw_stream->format.info.raw.size.height; + switch (obs_pw_stream->format.media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + frame->width = obs_pw_stream->format.info.raw.size.width; + frame->height = obs_pw_stream->format.info.raw.size.height; - video_format_get_parameters(video_colorspace_from_spa_color_matrix(obs_pw_stream->format.info.raw.color_matrix), - video_color_range_from_spa_color_range(obs_pw_stream->format.info.raw.color_range), - frame->color_matrix, frame->color_range_min, frame->color_range_max); + video_format_get_parameters( + video_colorspace_from_spa_color_matrix(obs_pw_stream->format.info.raw.color_matrix), + video_color_range_from_spa_color_range(obs_pw_stream->format.info.raw.color_range), + frame->color_matrix, frame->color_range_min, frame->color_range_max); + + if (!obs_pw_video_format_from_spa_format(obs_pw_stream->format.info.raw.format, &obs_pw_video_format) || + obs_pw_video_format.video_format == VIDEO_FORMAT_NONE) + return false; + + frame->format = obs_pw_video_format.video_format; + frame->linesize[0] = SPA_ROUND_UP_N(frame->width * obs_pw_video_format.bpp, 4); + + for (uint32_t i = 0; i < buffer->n_datas && i < MAX_AV_PLANES; i++) { + frame->data[i] = buffer->datas[i].data; + if (frame->data[i] == NULL) { + blog(LOG_ERROR, "[pipewire] Failed to access data"); + return false; + } + } - if (!obs_pw_video_format_from_spa_format(obs_pw_stream->format.info.raw.format, &obs_pw_video_format) || - obs_pw_video_format.video_format == VIDEO_FORMAT_NONE) + break; + case SPA_MEDIA_SUBTYPE_mjpg: + case SPA_MEDIA_SUBTYPE_h264: + if (obs_pipewire_decoder_decode_frame(obs_pw_stream->decoder, frame, buffer->datas[0].data, + buffer->datas[0].chunk->size) < 0) { + blog(LOG_ERROR, "failed to unpack jpeg or h264"); + return false; + } + break; + default: return false; + } - frame->format = obs_pw_video_format.video_format; - frame->linesize[0] = SPA_ROUND_UP_N(frame->width * obs_pw_video_format.bpp, 4); return true; } @@ -681,19 +729,11 @@ static void process_video_async(obs_pipewire_stream *obs_pw_stream) #endif struct obs_source_frame out = {0}; - if (!prepare_obs_frame(obs_pw_stream, &out)) { + if (!prepare_obs_frame(obs_pw_stream, buffer, &out)) { blog(LOG_ERROR, "[pipewire] Couldn't prepare frame"); goto done; } - for (uint32_t i = 0; i < buffer->n_datas && i < MAX_AV_PLANES; i++) { - out.data[i] = buffer->datas[i].data; - if (out.data[i] == NULL) { - blog(LOG_ERROR, "[pipewire] Failed to access data"); - goto done; - } - } - #ifdef DEBUG_PIPEWIRE blog(LOG_DEBUG, "[pipewire] Frame info: Format: %s, Planes: %u", get_video_format_name(out.format), buffer->n_datas); @@ -964,41 +1004,79 @@ static void on_param_changed_cb(void *user_data, uint32_t id, const struct spa_p if (result < 0) return; - if (obs_pw_stream->format.media_type != SPA_MEDIA_TYPE_video || - obs_pw_stream->format.media_subtype != SPA_MEDIA_SUBTYPE_raw) + if (obs_pw_stream->format.media_type != SPA_MEDIA_TYPE_video) return; - spa_format_video_raw_parse(param, &obs_pw_stream->format.info.raw); + switch (obs_pw_stream->format.media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: { + spa_format_video_raw_parse(param, &obs_pw_stream->format.info.raw); - output_flags = obs_source_get_output_flags(obs_pw_stream->source); + output_flags = obs_source_get_output_flags(obs_pw_stream->source); - buffer_types = 1 << SPA_DATA_MemPtr; - bool has_modifier = spa_pod_find_prop(param, NULL, SPA_FORMAT_VIDEO_modifier) != NULL; - if ((has_modifier || check_pw_version(&obs_pw->server_version, 0, 3, 24)) && - (output_flags & OBS_SOURCE_ASYNC_VIDEO) != OBS_SOURCE_ASYNC_VIDEO) { - buffer_types |= 1 << SPA_DATA_DmaBuf; + buffer_types = 1 << SPA_DATA_MemPtr; + bool has_modifier = spa_pod_find_prop(param, NULL, SPA_FORMAT_VIDEO_modifier) != NULL; + if ((has_modifier || check_pw_version(&obs_pw->server_version, 0, 3, 24)) && + (output_flags & OBS_SOURCE_ASYNC_VIDEO) != OBS_SOURCE_ASYNC_VIDEO) { + buffer_types |= 1 << SPA_DATA_DmaBuf; #if PW_CHECK_VERSION(1, 2, 0) - obs_enter_graphics(); - supports_explicit_sync = gs_query_sync_capabilities(); - obs_leave_graphics(); + obs_enter_graphics(); + supports_explicit_sync = gs_query_sync_capabilities(); + obs_leave_graphics(); #endif - } + } - blog(LOG_INFO, "[pipewire] Negotiated format:"); + blog(LOG_INFO, "[pipewire] Negotiated format:"); - format_name = spa_debug_type_find_name(spa_type_video_format, obs_pw_stream->format.info.raw.format); - blog(LOG_INFO, "[pipewire] Format: %d (%s)", obs_pw_stream->format.info.raw.format, - format_name ? format_name : "unknown format"); + format_name = spa_debug_type_find_name(spa_type_video_format, obs_pw_stream->format.info.raw.format); + blog(LOG_INFO, "[pipewire] Format: %d (%s)", obs_pw_stream->format.info.raw.format, + format_name ? format_name : "unknown format"); - if (has_modifier) { - blog(LOG_INFO, "[pipewire] Modifier: 0x%" PRIx64, obs_pw_stream->format.info.raw.modifier); + if (has_modifier) { + blog(LOG_INFO, "[pipewire] Modifier: 0x%" PRIx64, obs_pw_stream->format.info.raw.modifier); + } + + blog(LOG_INFO, "[pipewire] Size: %dx%d", obs_pw_stream->format.info.raw.size.width, + obs_pw_stream->format.info.raw.size.height); + + blog(LOG_INFO, "[pipewire] Framerate: %d/%d", obs_pw_stream->format.info.raw.framerate.num, + obs_pw_stream->format.info.raw.framerate.denom); + + break; + } + case SPA_MEDIA_SUBTYPE_mjpg: + if (spa_format_video_mjpg_parse(param, &obs_pw_stream->format.info.mjpg) < 0) + return; + blog(LOG_INFO, "[pipewire] Negotiated format:"); + blog(LOG_INFO, "[pipewire] Format: (MJPG)"); + blog(LOG_INFO, "[pipewire] Size: %dx%d", obs_pw_stream->format.info.mjpg.size.width, + obs_pw_stream->format.info.mjpg.size.height); + blog(LOG_INFO, "[pipewire] Framerate: %d/%d", obs_pw_stream->format.info.mjpg.framerate.num, + obs_pw_stream->format.info.mjpg.framerate.denom); + break; + case SPA_MEDIA_SUBTYPE_h264: + if (spa_format_video_h264_parse(param, &obs_pw_stream->format.info.h264) < 0) + return; + blog(LOG_INFO, "[pipewire] Negotiated format:"); + blog(LOG_INFO, "[pipewire] Format: (H264)"); + blog(LOG_INFO, "[pipewire] Size: %dx%d", obs_pw_stream->format.info.h264.size.width, + obs_pw_stream->format.info.h264.size.height); + blog(LOG_INFO, "[pipewire] Framerate: %d/%d", obs_pw_stream->format.info.h264.framerate.num, + obs_pw_stream->format.info.h264.framerate.denom); + break; + default: + return; } - blog(LOG_INFO, "[pipewire] Size: %dx%d", obs_pw_stream->format.info.raw.size.width, - obs_pw_stream->format.info.raw.size.height); + g_clear_pointer(&obs_pw_stream->decoder, obs_pipewire_decoder_destroy); - blog(LOG_INFO, "[pipewire] Framerate: %d/%d", obs_pw_stream->format.info.raw.framerate.num, - obs_pw_stream->format.info.raw.framerate.denom); + if (obs_pw_stream->format.media_subtype != SPA_MEDIA_SUBTYPE_raw) { + obs_pw_stream->decoder = obs_pipewire_decoder_new(obs_pw_stream->format.media_subtype); + + if (obs_pw_stream->decoder == NULL) { + blog(LOG_ERROR, "Failed to initialize decoder"); + return; + } + } /* Video crop */ pod_builder = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); @@ -1273,6 +1351,8 @@ void obs_pipewire_stream_hide(obs_pipewire_stream *obs_pw_stream) uint32_t obs_pipewire_stream_get_width(obs_pipewire_stream *obs_pw_stream) { + uint32_t negotiated_height; + uint32_t negotiated_width; bool has_crop; if (!obs_pw_stream->negotiated) @@ -1280,17 +1360,34 @@ uint32_t obs_pipewire_stream_get_width(obs_pipewire_stream *obs_pw_stream) has_crop = has_effective_crop(obs_pw_stream); + switch (obs_pw_stream->format.media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + negotiated_height = obs_pw_stream->format.info.raw.size.height; + negotiated_width = obs_pw_stream->format.info.raw.size.width; + break; + case SPA_MEDIA_SUBTYPE_mjpg: + negotiated_height = obs_pw_stream->format.info.mjpg.size.height; + negotiated_width = obs_pw_stream->format.info.mjpg.size.width; + break; + case SPA_MEDIA_SUBTYPE_h264: + negotiated_height = obs_pw_stream->format.info.h264.size.height; + negotiated_width = obs_pw_stream->format.info.h264.size.width; + break; + default: + return 0; + } + switch (obs_pw_stream->transform) { case SPA_META_TRANSFORMATION_Flipped: case SPA_META_TRANSFORMATION_None: case SPA_META_TRANSFORMATION_Flipped180: case SPA_META_TRANSFORMATION_180: - return has_crop ? obs_pw_stream->crop.width : obs_pw_stream->format.info.raw.size.width; + return has_crop ? obs_pw_stream->crop.width : negotiated_width; case SPA_META_TRANSFORMATION_Flipped90: case SPA_META_TRANSFORMATION_90: case SPA_META_TRANSFORMATION_Flipped270: case SPA_META_TRANSFORMATION_270: - return has_crop ? obs_pw_stream->crop.height : obs_pw_stream->format.info.raw.size.height; + return has_crop ? obs_pw_stream->crop.height : negotiated_height; default: return 0; } @@ -1298,6 +1395,8 @@ uint32_t obs_pipewire_stream_get_width(obs_pipewire_stream *obs_pw_stream) uint32_t obs_pipewire_stream_get_height(obs_pipewire_stream *obs_pw_stream) { + uint32_t negotiated_height; + uint32_t negotiated_width; bool has_crop; if (!obs_pw_stream->negotiated) @@ -1305,17 +1404,34 @@ uint32_t obs_pipewire_stream_get_height(obs_pipewire_stream *obs_pw_stream) has_crop = has_effective_crop(obs_pw_stream); + switch (obs_pw_stream->format.media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + negotiated_height = obs_pw_stream->format.info.raw.size.height; + negotiated_width = obs_pw_stream->format.info.raw.size.width; + break; + case SPA_MEDIA_SUBTYPE_mjpg: + negotiated_height = obs_pw_stream->format.info.mjpg.size.height; + negotiated_width = obs_pw_stream->format.info.mjpg.size.width; + break; + case SPA_MEDIA_SUBTYPE_h264: + negotiated_height = obs_pw_stream->format.info.h264.size.height; + negotiated_width = obs_pw_stream->format.info.h264.size.width; + break; + default: + return 0; + } + switch (obs_pw_stream->transform) { case SPA_META_TRANSFORMATION_Flipped: case SPA_META_TRANSFORMATION_None: case SPA_META_TRANSFORMATION_Flipped180: case SPA_META_TRANSFORMATION_180: - return has_crop ? obs_pw_stream->crop.height : obs_pw_stream->format.info.raw.size.height; + return has_crop ? obs_pw_stream->crop.height : negotiated_height; case SPA_META_TRANSFORMATION_Flipped90: case SPA_META_TRANSFORMATION_90: case SPA_META_TRANSFORMATION_Flipped270: case SPA_META_TRANSFORMATION_270: - return has_crop ? obs_pw_stream->crop.width : obs_pw_stream->format.info.raw.size.width; + return has_crop ? obs_pw_stream->crop.width : negotiated_width; default: return 0; } @@ -1424,6 +1540,8 @@ void obs_pipewire_stream_destroy(obs_pipewire_stream *obs_pw_stream) g_clear_fd(&obs_pw_stream->sync.acquire_syncobj_fd, NULL); g_clear_fd(&obs_pw_stream->sync.release_syncobj_fd, NULL); + g_clear_pointer(&obs_pw_stream->decoder, obs_pipewire_decoder_destroy); + clear_format_info(obs_pw_stream); bfree(obs_pw_stream); } diff --git a/plugins/linux-pipewire/video-decoder.c b/plugins/linux-pipewire/video-decoder.c new file mode 100644 index 00000000000000..56826baa7fba1f --- /dev/null +++ b/plugins/linux-pipewire/video-decoder.c @@ -0,0 +1,159 @@ +/* video-decoder.c + * + * Copyright 2020-2026 Morten Bøgeskov + * Copyright 2021-2026 Georges Basile Stavracas Neto + * Copyright 2024-2026 Dimitris Papaioannou + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "video-decoder.h" + +#include +#include + +#define blog(level, msg, ...) blog(level, "[pipewire] decoder: " msg, ##__VA_ARGS__) + +struct _obs_pipewire_decoder { + const AVCodec *codec; + AVCodecContext *context; + AVPacket *packet; + AVFrame *frame; +}; + +obs_pipewire_decoder *obs_pipewire_decoder_new(uint32_t subtype) +{ + obs_pipewire_decoder *decoder; + + decoder = bzalloc(sizeof(obs_pipewire_decoder)); + + switch (subtype) { + case SPA_MEDIA_SUBTYPE_mjpg: + decoder->codec = avcodec_find_decoder(AV_CODEC_ID_MJPEG); + if (decoder->codec == NULL) { + blog(LOG_ERROR, "failed to find MJPEG decoder"); + goto error; + } + break; + case SPA_MEDIA_SUBTYPE_h264: + decoder->codec = avcodec_find_decoder(AV_CODEC_ID_H264); + if (decoder->codec == NULL) { + blog(LOG_ERROR, "failed to find H264 decoder"); + goto error; + } + break; + default: + goto error; + } + + decoder->context = avcodec_alloc_context3(decoder->codec); + if (!decoder->context) + goto error; + + decoder->packet = av_packet_alloc(); + if (!decoder->packet) + goto error; + + decoder->frame = av_frame_alloc(); + if (!decoder->frame) + goto error; + + decoder->context->flags2 |= AV_CODEC_FLAG2_FAST; + + if (avcodec_open2(decoder->context, decoder->codec, NULL) < 0) { + blog(LOG_ERROR, "failed to open codec"); + goto error; + } + + blog(LOG_DEBUG, "initialized avcodec"); + + return decoder; + +error: + bfree(decoder); + return NULL; +} + +void obs_pipewire_decoder_destroy(obs_pipewire_decoder *decoder) +{ + blog(LOG_DEBUG, "destroying avcodec"); + if (decoder->frame) { + av_frame_free(&decoder->frame); + } + + if (decoder->packet) { + av_packet_free(&decoder->packet); + } + + if (decoder->context) { +#if LIBAVCODEC_VERSION_MAJOR < 61 + avcodec_close(decoder->context); +#endif + avcodec_free_context(&decoder->context); + } + + bfree(decoder); +} + +int obs_pipewire_decoder_decode_frame(obs_pipewire_decoder *decoder, struct obs_source_frame *out, uint8_t *data, + size_t length) +{ + decoder->packet->data = data; + decoder->packet->size = length; + if (avcodec_send_packet(decoder->context, decoder->packet) < 0) { + blog(LOG_ERROR, "failed to send frame to codec"); + return -1; + } + + int r = avcodec_receive_frame(decoder->context, decoder->frame); + if (r == AVERROR(EAGAIN)) { + blog(LOG_DEBUG, "failed to receive frame in this state, try to send new frame to codec"); + return 0; + } else if (r < 0) { + blog(LOG_ERROR, "failed to receive frame from codec"); + return -1; + } + + out->width = decoder->frame->width; + out->height = decoder->frame->height; + + video_format_get_parameters(VIDEO_CS_DEFAULT, VIDEO_RANGE_FULL, out->color_matrix, out->color_range_min, + out->color_range_max); + + for (uint_fast32_t i = 0; i < MAX_AV_PLANES; ++i) { + out->data[i] = decoder->frame->data[i]; + out->linesize[i] = decoder->frame->linesize[i]; + } + + switch (decoder->context->pix_fmt) { + case AV_PIX_FMT_YUVJ422P: + case AV_PIX_FMT_YUV422P: + out->format = VIDEO_FORMAT_I422; + break; + case AV_PIX_FMT_YUVJ420P: + case AV_PIX_FMT_YUV420P: + out->format = VIDEO_FORMAT_I420; + break; + case AV_PIX_FMT_YUVJ444P: + case AV_PIX_FMT_YUV444P: + out->format = VIDEO_FORMAT_I444; + break; + default: + break; + } + + return 0; +} diff --git a/plugins/linux-pipewire/video-decoder.h b/plugins/linux-pipewire/video-decoder.h new file mode 100644 index 00000000000000..36c3ed3a2629dd --- /dev/null +++ b/plugins/linux-pipewire/video-decoder.h @@ -0,0 +1,58 @@ +/* video-decoder.h + * + * Copyright 2020-2026 Morten Bøgeskov + * Copyright 2021-2026 Georges Basile Stavracas Neto + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#pragma once + +#include +#include +#include +#include + +typedef struct _obs_pipewire_decoder obs_pipewire_decoder; + +/** + * Initialize the decoder. + * The decoder must be destroyed on failure. + * + * @param decoder the decoder structure + * @param subtype which codec is used + * @return non-zero on failure + */ +obs_pipewire_decoder *obs_pipewire_decoder_new(uint32_t subtype); + +/** + * Free any data associated with the decoder. + * + * @param decoder the decoder structure + */ +void obs_pipewire_decoder_destroy(obs_pipewire_decoder *decoder); + +/** + * Decode a jpeg or h264 frame into an obs frame + * + * @param out the obs frame to decode into + * @param data the codec data + * @param length length of the data + * @param decoder the decoder as initialized by pipewire_init_decoder + * @return non-zero on failure + */ +int obs_pipewire_decoder_decode_frame(obs_pipewire_decoder *decoder, struct obs_source_frame *out, uint8_t *data, + size_t length);