0
0
mirror of https://github.com/obsproject/obs-studio.git synced 2024-09-19 20:32:15 +02:00

obs-ffmpeg: Implement texture encoding for VAAPI

This commit is contained in:
David Rosca 2023-04-30 15:26:50 +02:00 committed by Lain
parent 65295eaf93
commit 06e364b670
5 changed files with 353 additions and 46 deletions

View File

@ -66,6 +66,7 @@ target_link_libraries(
$<$<PLATFORM_ID:Linux,FreeBSD,OpenBSD>:Libva::va>
$<$<PLATFORM_ID:Linux,FreeBSD,OpenBSD>:Libva::drm>
$<$<PLATFORM_ID:Linux,FreeBSD,OpenBSD>:Libpci::pci>
$<$<PLATFORM_ID:Linux,FreeBSD,OpenBSD>:Libdrm::Libdrm>
$<$<BOOL:${ENABLE_NEW_MPEGTS_OUTPUT}>:Librist::Librist>
$<$<BOOL:${ENABLE_NEW_MPEGTS_OUTPUT}>:Libsrt::Libsrt>)

View File

@ -33,6 +33,7 @@ elseif(
OR OS_OPENBSD)
find_package(Libva REQUIRED)
find_package(Libpci REQUIRED)
find_package(Libdrm REQUIRED)
endif()
if(OS_WINDOWS OR (OS_LINUX AND ENABLE_NATIVE_NVENC))

View File

@ -111,8 +111,9 @@ if(OS_WINDOWS)
elseif(OS_POSIX AND NOT OS_MACOS)
find_package(Libva REQUIRED)
find_package(Libpci REQUIRED)
find_package(Libdrm REQUIRED)
target_sources(obs-ffmpeg PRIVATE obs-ffmpeg-vaapi.c vaapi-utils.c vaapi-utils.h)
target_link_libraries(obs-ffmpeg PRIVATE Libva::va Libva::drm LIBPCI::LIBPCI)
target_link_libraries(obs-ffmpeg PRIVATE Libva::va Libva::drm LIBPCI::LIBPCI Libdrm::Libdrm)
if(ENABLE_NATIVE_NVENC)
find_package(FFnvcodec 12.0.0.0...<12.2.0.0 REQUIRED)

View File

@ -35,6 +35,7 @@
#include <libavutil/opt.h>
#include <libavutil/pixdesc.h>
#include <libavutil/hwcontext.h>
#include <libavutil/hwcontext_vaapi.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>
@ -44,6 +45,9 @@
#include "vaapi-utils.h"
#include "obs-ffmpeg-formats.h"
#include <va/va_drmcommon.h>
#include <libdrm/drm_fourcc.h>
#define do_log(level, format, ...) \
blog(level, "[FFmpeg VAAPI encoder: '%s'] " format, \
obs_encoder_get_name(enc->encoder), ##__VA_ARGS__)
@ -58,12 +62,19 @@ enum codec_type {
CODEC_AV1,
};
struct vaapi_surface {
AVFrame *frame;
gs_texture_t *textures[4];
uint32_t num_textures;
};
struct vaapi_encoder {
obs_encoder_t *encoder;
enum codec_type codec;
AVBufferRef *vadevice_ref;
AVBufferRef *vaframes_ref;
VADisplay va_dpy;
const AVCodec *vaapi;
AVCodecContext *context;
@ -146,6 +157,11 @@ static bool vaapi_init_codec(struct vaapi_encoder *enc, const char *path)
return false;
}
AVHWDeviceContext *vahwctx =
(AVHWDeviceContext *)enc->vadevice_ref->data;
AVVAAPIDeviceContext *vadevctx = vahwctx->hwctx;
enc->va_dpy = vadevctx->display;
enc->vaframes_ref = av_hwframe_ctx_alloc(enc->vadevice_ref);
if (!enc->vaframes_ref) {
warn("Failed to alloc HW frames context");
@ -158,7 +174,6 @@ static bool vaapi_init_codec(struct vaapi_encoder *enc, const char *path)
frames_ctx->sw_format = enc->context->pix_fmt;
frames_ctx->width = enc->context->width;
frames_ctx->height = enc->context->height;
frames_ctx->initial_pool_size = 20;
ret = av_hwframe_ctx_init(enc->vaframes_ref);
if (ret < 0) {
@ -376,6 +391,117 @@ static bool vaapi_update(void *data, obs_data_t *settings)
return vaapi_init_codec(enc, device);
}
static inline enum gs_color_format drm_to_gs_color_format(int format)
{
switch (format) {
case DRM_FORMAT_R8:
return GS_R8;
case DRM_FORMAT_R16:
return GS_R16;
case DRM_FORMAT_GR88:
return GS_R8G8;
case DRM_FORMAT_GR1616:
return GS_RG16;
default:
blog(LOG_ERROR, "Unsupported DRM format %d", format);
return GS_UNKNOWN;
}
}
static void vaapi_destroy_surface(struct vaapi_surface *out)
{
obs_enter_graphics();
for (uint32_t i = 0; i < out->num_textures; ++i) {
if (out->textures[i]) {
gs_texture_destroy(out->textures[i]);
out->textures[i] = NULL;
}
}
obs_leave_graphics();
av_frame_free(&out->frame);
}
static bool vaapi_create_surface(struct vaapi_encoder *enc,
struct vaapi_surface *out)
{
int ret;
VAStatus vas;
VADRMPRIMESurfaceDescriptor desc;
const AVPixFmtDescriptor *fmt_desc;
bool ok = true;
memset(out, 0, sizeof(*out));
fmt_desc = av_pix_fmt_desc_get(enc->context->pix_fmt);
if (!fmt_desc) {
warn("Failed to get pix fmt descriptor");
return false;
}
out->frame = av_frame_alloc();
if (!out->frame) {
warn("Failed to allocate hw frame");
return false;
}
ret = av_hwframe_get_buffer(enc->vaframes_ref, out->frame, 0);
if (ret < 0) {
warn("Failed to get hw frame buffer: %s", av_err2str(ret));
goto fail;
}
vas = vaExportSurfaceHandle(enc->va_dpy, (uintptr_t)out->frame->data[3],
VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
VA_EXPORT_SURFACE_WRITE_ONLY |
VA_EXPORT_SURFACE_SEPARATE_LAYERS,
&desc);
if (vas != VA_STATUS_SUCCESS) {
warn("Failed to export VA surface handle: %s", vaErrorStr(vas));
goto fail;
}
obs_enter_graphics();
for (uint32_t i = 0; i < desc.num_layers; ++i) {
unsigned int width = desc.width;
unsigned int height = desc.height;
if (i) {
width /= 1 << fmt_desc->log2_chroma_w;
height /= 1 << fmt_desc->log2_chroma_h;
}
out->textures[i] = gs_texture_create_from_dmabuf(
width, height, desc.layers[i].drm_format,
drm_to_gs_color_format(desc.layers[i].drm_format), 1,
&desc.objects[desc.layers[i].object_index[0]].fd,
&desc.layers[i].pitch[0], &desc.layers[i].offset[0],
&desc.objects[desc.layers[i].object_index[0]]
.drm_format_modifier);
if (!out->textures[i]) {
warn("Failed to import VA surface texture");
ok = false;
}
out->num_textures++;
}
obs_leave_graphics();
for (uint32_t i = 0; i < desc.num_objects; ++i)
close(desc.objects[i].fd);
if (ok)
return true;
fail:
vaapi_destroy_surface(out);
return false;
}
static inline void flush_remaining_packets(struct vaapi_encoder *enc)
{
int r_pkt = 1;
@ -457,21 +583,72 @@ fail:
return NULL;
}
static inline bool vaapi_test_texencode(struct vaapi_encoder *enc)
{
struct vaapi_surface surface;
if (obs_encoder_scaling_enabled(enc->encoder) &&
!obs_encoder_gpu_scaling_enabled(enc->encoder))
return false;
if (!vaapi_create_surface(enc, &surface))
return false;
vaapi_destroy_surface(&surface);
return true;
}
static void *vaapi_create_tex_internal(obs_data_t *settings,
obs_encoder_t *encoder,
enum codec_type codec,
const char *fallback)
{
void *enc = vaapi_create_internal(settings, encoder, codec);
if (!enc) {
return NULL;
}
if (!vaapi_test_texencode(enc)) {
vaapi_destroy(enc);
blog(LOG_WARNING, "VAAPI: Falling back to %s encoder",
fallback);
return obs_encoder_create_rerouted(encoder, fallback);
}
return enc;
}
static void *h264_vaapi_create(obs_data_t *settings, obs_encoder_t *encoder)
{
return vaapi_create_internal(settings, encoder, CODEC_H264);
}
static void *h264_vaapi_create_tex(obs_data_t *settings, obs_encoder_t *encoder)
{
return vaapi_create_tex_internal(settings, encoder, CODEC_H264,
"ffmpeg_vaapi");
}
static void *av1_vaapi_create(obs_data_t *settings, obs_encoder_t *encoder)
{
return vaapi_create_internal(settings, encoder, CODEC_AV1);
}
static void *av1_vaapi_create_tex(obs_data_t *settings, obs_encoder_t *encoder)
{
return vaapi_create_tex_internal(settings, encoder, CODEC_AV1,
"av1_ffmpeg_vaapi");
}
#ifdef ENABLE_HEVC
static void *hevc_vaapi_create(obs_data_t *settings, obs_encoder_t *encoder)
{
return vaapi_create_internal(settings, encoder, CODEC_HEVC);
}
static void *hevc_vaapi_create_tex(obs_data_t *settings, obs_encoder_t *encoder)
{
return vaapi_create_tex_internal(settings, encoder, CODEC_HEVC,
"hevc_ffmpeg_vaapi");
}
#endif
static inline void copy_data(AVFrame *pic, const struct encoder_frame *frame,
@ -500,49 +677,14 @@ static inline void copy_data(AVFrame *pic, const struct encoder_frame *frame,
}
}
static bool vaapi_encode(void *data, struct encoder_frame *frame,
struct encoder_packet *packet, bool *received_packet)
static bool vaapi_encode_internal(struct vaapi_encoder *enc, AVFrame *frame,
struct encoder_packet *packet,
bool *received_packet)
{
struct vaapi_encoder *enc = data;
AVFrame *hwframe = NULL;
int got_packet;
int ret;
hwframe = av_frame_alloc();
if (!hwframe) {
warn("vaapi_encode: failed to allocate hw frame");
return false;
}
ret = av_hwframe_get_buffer(enc->vaframes_ref, hwframe, 0);
if (ret < 0) {
warn("vaapi_encode: failed to get buffer for hw frame: %s",
av_err2str(ret));
goto fail;
}
copy_data(enc->vframe, frame, enc->height, enc->context->pix_fmt);
enc->vframe->pts = frame->pts;
hwframe->pts = frame->pts;
hwframe->width = enc->vframe->width;
hwframe->height = enc->vframe->height;
ret = av_hwframe_transfer_data(hwframe, enc->vframe, 0);
if (ret < 0) {
warn("vaapi_encode: failed to upload hw frame: %s",
av_err2str(ret));
goto fail;
}
ret = av_frame_copy_props(hwframe, enc->vframe);
if (ret < 0) {
warn("vaapi_encode: failed to copy props to hw frame: %s",
av_err2str(ret));
goto fail;
}
ret = avcodec_send_frame(enc->context, hwframe);
ret = avcodec_send_frame(enc->context, frame);
if (ret == 0 || ret == AVERROR(EAGAIN))
ret = avcodec_receive_packet(enc->context, enc->packet);
@ -619,11 +761,63 @@ static bool vaapi_encode(void *data, struct encoder_frame *frame,
obs_av1_keyframe(packet->data, packet->size);
}
*received_packet = true;
} else {
*received_packet = false;
}
av_packet_unref(enc->packet);
return true;
fail:
av_packet_unref(enc->packet);
return false;
}
static bool vaapi_encode_copy(void *data, struct encoder_frame *frame,
struct encoder_packet *packet,
bool *received_packet)
{
struct vaapi_encoder *enc = data;
AVFrame *hwframe = NULL;
int ret;
*received_packet = false;
hwframe = av_frame_alloc();
if (!hwframe) {
warn("vaapi_encode_copy: failed to allocate hw frame");
return false;
}
ret = av_hwframe_get_buffer(enc->vaframes_ref, hwframe, 0);
if (ret < 0) {
warn("vaapi_encode_copy: failed to get buffer for hw frame: %s",
av_err2str(ret));
goto fail;
}
copy_data(enc->vframe, frame, enc->height, enc->context->pix_fmt);
enc->vframe->pts = frame->pts;
hwframe->pts = frame->pts;
hwframe->width = enc->vframe->width;
hwframe->height = enc->vframe->height;
ret = av_hwframe_transfer_data(hwframe, enc->vframe, 0);
if (ret < 0) {
warn("vaapi_encode_copy: failed to upload hw frame: %s",
av_err2str(ret));
goto fail;
}
ret = av_frame_copy_props(hwframe, enc->vframe);
if (ret < 0) {
warn("vaapi_encode_copy: failed to copy props to hw frame: %s",
av_err2str(ret));
goto fail;
}
if (!vaapi_encode_internal(enc, hwframe, packet, received_packet))
goto fail;
av_frame_free(&hwframe);
return true;
@ -632,6 +826,59 @@ fail:
return false;
}
static bool vaapi_encode_tex(void *data, struct encoder_texture *texture,
int64_t pts, uint64_t lock_key, uint64_t *next_key,
struct encoder_packet *packet,
bool *received_packet)
{
UNUSED_PARAMETER(lock_key);
UNUSED_PARAMETER(next_key);
struct vaapi_encoder *enc = data;
struct vaapi_surface surface;
int ret;
*received_packet = false;
if (!vaapi_create_surface(enc, &surface)) {
warn("vaapi_encode_tex: failed to create texture hw frame");
return false;
}
obs_enter_graphics();
for (uint32_t i = 0; i < surface.num_textures; ++i) {
if (!texture->tex[i]) {
warn("vaapi_encode_tex: unexpected number of textures");
goto fail;
}
gs_copy_texture(surface.textures[i], texture->tex[i]);
}
gs_flush();
obs_leave_graphics();
enc->vframe->pts = pts;
ret = av_frame_copy_props(surface.frame, enc->vframe);
if (ret < 0) {
warn("vaapi_encode_tex: failed to copy props to hw frame: %s",
av_err2str(ret));
goto fail;
}
if (!vaapi_encode_internal(enc, surface.frame, packet, received_packet))
goto fail;
vaapi_destroy_surface(&surface);
return true;
fail:
vaapi_destroy_surface(&surface);
return false;
}
static void set_visible(obs_properties_t *ppts, const char *name, bool visible)
{
obs_property_t *p = obs_properties_get(ppts, name);
@ -1047,12 +1294,29 @@ struct obs_encoder_info h264_vaapi_encoder_info = {
.get_name = h264_vaapi_getname,
.create = h264_vaapi_create,
.destroy = vaapi_destroy,
.encode = vaapi_encode,
.encode = vaapi_encode_copy,
.get_defaults = h264_vaapi_defaults,
.get_properties = h264_vaapi_properties,
.get_extra_data = vaapi_extra_data,
.get_sei_data = vaapi_sei_data,
.get_video_info = vaapi_video_info,
.caps = OBS_ENCODER_CAP_INTERNAL,
};
struct obs_encoder_info h264_vaapi_encoder_tex_info = {
.id = "ffmpeg_vaapi_tex",
.type = OBS_ENCODER_VIDEO,
.codec = "h264",
.get_name = h264_vaapi_getname,
.create = h264_vaapi_create_tex,
.destroy = vaapi_destroy,
.encode_texture2 = vaapi_encode_tex,
.get_defaults = h264_vaapi_defaults,
.get_properties = h264_vaapi_properties,
.get_extra_data = vaapi_extra_data,
.get_sei_data = vaapi_sei_data,
.get_video_info = vaapi_video_info,
.caps = OBS_ENCODER_CAP_PASS_TEXTURE,
};
struct obs_encoder_info av1_vaapi_encoder_info = {
@ -1062,12 +1326,29 @@ struct obs_encoder_info av1_vaapi_encoder_info = {
.get_name = av1_vaapi_getname,
.create = av1_vaapi_create,
.destroy = vaapi_destroy,
.encode = vaapi_encode,
.encode = vaapi_encode_copy,
.get_defaults = av1_vaapi_defaults,
.get_properties = av1_vaapi_properties,
.get_extra_data = vaapi_extra_data,
.get_sei_data = vaapi_sei_data,
.get_video_info = vaapi_video_info,
.caps = OBS_ENCODER_CAP_INTERNAL,
};
struct obs_encoder_info av1_vaapi_encoder_tex_info = {
.id = "av1_ffmpeg_vaapi_tex",
.type = OBS_ENCODER_VIDEO,
.codec = "av1",
.get_name = av1_vaapi_getname,
.create = av1_vaapi_create_tex,
.destroy = vaapi_destroy,
.encode_texture2 = vaapi_encode_tex,
.get_defaults = av1_vaapi_defaults,
.get_properties = av1_vaapi_properties,
.get_extra_data = vaapi_extra_data,
.get_sei_data = vaapi_sei_data,
.get_video_info = vaapi_video_info,
.caps = OBS_ENCODER_CAP_PASS_TEXTURE,
};
#ifdef ENABLE_HEVC
@ -1078,11 +1359,28 @@ struct obs_encoder_info hevc_vaapi_encoder_info = {
.get_name = hevc_vaapi_getname,
.create = hevc_vaapi_create,
.destroy = vaapi_destroy,
.encode = vaapi_encode,
.encode = vaapi_encode_copy,
.get_defaults = hevc_vaapi_defaults,
.get_properties = hevc_vaapi_properties,
.get_extra_data = vaapi_extra_data,
.get_sei_data = vaapi_sei_data,
.get_video_info = vaapi_video_info,
.caps = OBS_ENCODER_CAP_INTERNAL,
};
struct obs_encoder_info hevc_vaapi_encoder_tex_info = {
.id = "hevc_ffmpeg_vaapi_tex",
.type = OBS_ENCODER_VIDEO,
.codec = "hevc",
.get_name = hevc_vaapi_getname,
.create = hevc_vaapi_create_tex,
.destroy = vaapi_destroy,
.encode_texture2 = vaapi_encode_tex,
.get_defaults = hevc_vaapi_defaults,
.get_properties = hevc_vaapi_properties,
.get_extra_data = vaapi_extra_data,
.get_sei_data = vaapi_sei_data,
.get_video_info = vaapi_video_info,
.caps = OBS_ENCODER_CAP_PASS_TEXTURE,
};
#endif

View File

@ -50,9 +50,12 @@ extern struct obs_encoder_info aom_av1_encoder_info;
#ifdef LIBAVUTIL_VAAPI_AVAILABLE
extern struct obs_encoder_info h264_vaapi_encoder_info;
extern struct obs_encoder_info h264_vaapi_encoder_tex_info;
extern struct obs_encoder_info av1_vaapi_encoder_info;
extern struct obs_encoder_info av1_vaapi_encoder_tex_info;
#ifdef ENABLE_HEVC
extern struct obs_encoder_info hevc_vaapi_encoder_info;
extern struct obs_encoder_info hevc_vaapi_encoder_tex_info;
#endif
#endif
@ -404,6 +407,7 @@ bool obs_module_load(void)
if (h264_vaapi_supported()) {
blog(LOG_INFO, "FFmpeg VAAPI H264 encoding supported");
obs_register_encoder(&h264_vaapi_encoder_info);
obs_register_encoder(&h264_vaapi_encoder_tex_info);
} else {
blog(LOG_INFO, "FFmpeg VAAPI H264 encoding not supported");
}
@ -411,6 +415,7 @@ bool obs_module_load(void)
if (av1_vaapi_supported()) {
blog(LOG_INFO, "FFmpeg VAAPI AV1 encoding supported");
obs_register_encoder(&av1_vaapi_encoder_info);
obs_register_encoder(&av1_vaapi_encoder_tex_info);
} else {
blog(LOG_INFO, "FFmpeg VAAPI AV1 encoding not supported");
}
@ -419,6 +424,7 @@ bool obs_module_load(void)
if (hevc_vaapi_supported()) {
blog(LOG_INFO, "FFmpeg VAAPI HEVC encoding supported");
obs_register_encoder(&hevc_vaapi_encoder_info);
obs_register_encoder(&hevc_vaapi_encoder_tex_info);
} else {
blog(LOG_INFO, "FFmpeg VAAPI HEVC encoding not supported");
}