Files
grd/grd-encode-session-vaapi.c
2026-02-13 13:06:50 +09:00

1791 lines
62 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Copyright (C) 2024 Pascal Nowack
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#include "config.h"
#include "grd-encode-session-vaapi.h"
#include <gio/gio.h>
#include <glib/gstdio.h>
#include <va/va_drmcommon.h>
#include "grd-avc-frame-info.h"
#include "grd-bitstream.h"
#include "grd-debug.h"
#include "grd-image-view-nv12.h"
#include "grd-nal-writer.h"
#include "grd-utils.h"
#include "grd-vk-image.h"
#include "grd-vk-utils.h"
/*
* See also Table 7-6 Name association to slice_type
* (Rec. ITU-T H.264 (08/2021))
*/
#define H264_SLICE_TYPE_P 0
#define H264_SLICE_TYPE_I 2
/* See also E.2.1 VUI parameters semantics (Rec. ITU-T H.264 (08/2021)) */
#define H264_Extended_SAR 255
#define LOG2_MAX_FRAME_NUM 8
/*
* One surface is needed, when encoding a frame,
* one is needed, when preparing the view,
* one is needed for the submitted pending frame in the renderer,
* and one is needed to prepare a new pending frame before it is submitted to
* the renderer, where it then replaces the old pending frame
*
* In total, this makes four needed source surfaces per view
*/
#define N_SRC_SURFACES_PER_VIEW 4
#define N_SRC_SURFACES (N_SRC_SURFACES_PER_VIEW * 2)
typedef struct
{
GrdEncodeSessionVaapi *encode_session_vaapi;
VASurfaceID va_surface;
int32_t frame_num;
gboolean is_idr;
} VAAPIPicture;
typedef struct
{
GrdEncodeSessionVaapi *encode_session_vaapi;
VAAPIPicture *reconstructed_picture;
VABufferID sequence_buffer;
VABufferID picture_buffer;
VABufferID slice_buffer;
GList *packed_header_buffers;
GList *misc_buffers;
uint32_t aud_header_length;
uint32_t sequence_header_length;
uint32_t picture_header_length;
uint32_t slice_header_length;
VASurfaceID src_surface;
VABufferID bitstream_buffer;
GrdAVCFrameInfo *avc_frame_info;
int64_t timestamps[3];
} H264Frame;
typedef struct
{
GrdEncodeSessionVaapi *encode_session_vaapi;
VASurfaceID va_surface;
GrdImageView *image_view;
int fds[2];
} NV12VkVASurface;
struct _GrdEncodeSessionVaapi
{
GrdEncodeSession parent;
GrdVkDevice *vk_device;
VADisplay va_display;
gboolean supports_quality_level;
gboolean debug_va_times;
VAConfigID va_config;
VAContextID va_context;
uint32_t surface_width;
uint32_t surface_height;
uint32_t refresh_rate;
uint8_t level_idc;
gboolean pending_idr_frame;
GrdNalWriter *nal_writer;
GHashTable *surfaces;
VAAPIPicture *reference_picture;
uint16_t frame_num;
VAEncPictureParameterBufferH264 picture_param;
VAEncSequenceParameterBufferH264 sequence_param;
GMutex pending_frames_mutex;
GHashTable *pending_frames;
GMutex bitstreams_mutex;
GHashTable *bitstreams;
};
G_DEFINE_TYPE (GrdEncodeSessionVaapi, grd_encode_session_vaapi,
GRD_TYPE_ENCODE_SESSION)
static void
vaapi_picture_free (VAAPIPicture *picture);
static void
h264_frame_free (H264Frame *h264_frame);
static void
nv12_vkva_surface_free (NV12VkVASurface *vkva_surface);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (VAAPIPicture, vaapi_picture_free)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (H264Frame, h264_frame_free)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (NV12VkVASurface, nv12_vkva_surface_free)
static VAAPIPicture *
vaapi_picture_new (GrdEncodeSessionVaapi *encode_session_vaapi,
uint32_t surface_width,
uint32_t surface_height,
int32_t frame_num,
gboolean is_idr,
GError **error)
{
g_autoptr (VAAPIPicture) picture = NULL;
VAStatus va_status;
picture = g_new0 (VAAPIPicture, 1);
picture->encode_session_vaapi = encode_session_vaapi;
picture->va_surface = VA_INVALID_SURFACE;
picture->frame_num = frame_num;
picture->is_idr = is_idr;
va_status = vaCreateSurfaces (encode_session_vaapi->va_display,
VA_RT_FORMAT_YUV420,
surface_width, surface_height,
&picture->va_surface, 1,
NULL, 0);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create surface for reconstructed picture: %s",
vaErrorStr (va_status));
return NULL;
}
g_assert (picture->va_surface != VA_INVALID_SURFACE);
return g_steal_pointer (&picture);
}
static void
vaapi_picture_free (VAAPIPicture *picture)
{
GrdEncodeSessionVaapi *encode_session_vaapi = picture->encode_session_vaapi;
VADisplay va_display = encode_session_vaapi->va_display;
if (picture->va_surface != VA_INVALID_SURFACE)
vaDestroySurfaces (va_display, &picture->va_surface, 1);
g_free (picture);
}
static H264Frame *
h264_frame_new (GrdEncodeSessionVaapi *encode_session_vaapi,
GError **error)
{
g_autoptr (H264Frame) h264_frame = NULL;
uint32_t surface_width = encode_session_vaapi->surface_width;
uint32_t surface_height = encode_session_vaapi->surface_height;
g_assert (surface_width % 16 == 0);
g_assert (surface_height % 16 == 0);
g_assert (surface_width >= 16);
g_assert (surface_height >= 16);
h264_frame = g_new0 (H264Frame, 1);
h264_frame->encode_session_vaapi = encode_session_vaapi;
h264_frame->sequence_buffer = VA_INVALID_ID;
h264_frame->picture_buffer = VA_INVALID_ID;
h264_frame->slice_buffer = VA_INVALID_ID;
h264_frame->bitstream_buffer = VA_INVALID_ID;
h264_frame->reconstructed_picture =
vaapi_picture_new (encode_session_vaapi,
surface_width, surface_height,
encode_session_vaapi->frame_num,
encode_session_vaapi->pending_idr_frame,
error);
if (!h264_frame->reconstructed_picture)
return NULL;
return g_steal_pointer (&h264_frame);
}
static void
h264_frame_free (H264Frame *h264_frame)
{
GrdEncodeSessionVaapi *encode_session_vaapi =
h264_frame->encode_session_vaapi;
VADisplay va_display = encode_session_vaapi->va_display;
GList *l;
for (l = h264_frame->misc_buffers; l; l = l->next)
vaDestroyBuffer (va_display, GPOINTER_TO_UINT (l->data));
g_clear_pointer (&h264_frame->misc_buffers, g_list_free);
for (l = h264_frame->packed_header_buffers; l; l = l->next)
vaDestroyBuffer (va_display, GPOINTER_TO_UINT (l->data));
g_clear_pointer (&h264_frame->packed_header_buffers, g_list_free);
if (h264_frame->bitstream_buffer != VA_INVALID_ID)
vaDestroyBuffer (va_display, h264_frame->bitstream_buffer);
if (h264_frame->slice_buffer != VA_INVALID_ID)
vaDestroyBuffer (va_display, h264_frame->slice_buffer);
if (h264_frame->picture_buffer != VA_INVALID_ID)
vaDestroyBuffer (va_display, h264_frame->picture_buffer);
if (h264_frame->sequence_buffer != VA_INVALID_ID)
vaDestroyBuffer (va_display, h264_frame->sequence_buffer);
g_clear_pointer (&h264_frame->avc_frame_info, g_free);
g_clear_pointer (&h264_frame->reconstructed_picture, vaapi_picture_free);
g_free (h264_frame);
}
static VABufferID *
h264_frame_get_buffers (H264Frame *h264_frame,
uint32_t *n_buffers)
{
VABufferID *va_buffers;
uint32_t i = 0;
GList *l;
*n_buffers = 0;
if (h264_frame->sequence_buffer != VA_INVALID_ID)
*n_buffers += 1;
g_assert (h264_frame->picture_buffer != VA_INVALID_ID);
g_assert (h264_frame->slice_buffer != VA_INVALID_ID);
*n_buffers += 2;
*n_buffers += g_list_length (h264_frame->packed_header_buffers);
*n_buffers += g_list_length (h264_frame->misc_buffers);
g_assert (*n_buffers > 0);
va_buffers = g_new0 (VABufferID, *n_buffers);
if (h264_frame->sequence_buffer != VA_INVALID_ID)
va_buffers[i++] = h264_frame->sequence_buffer;
va_buffers[i++] = h264_frame->picture_buffer;
for (l = h264_frame->packed_header_buffers; l; l = l->next)
{
g_assert (i < *n_buffers);
va_buffers[i++] = GPOINTER_TO_UINT (l->data);
}
for (l = h264_frame->misc_buffers; l; l = l->next)
{
g_assert (i < *n_buffers);
va_buffers[i++] = GPOINTER_TO_UINT (l->data);
}
va_buffers[i++] = h264_frame->slice_buffer;
g_assert (i == *n_buffers);
return va_buffers;
}
static NV12VkVASurface *
nv12_vkva_surface_new (GrdEncodeSessionVaapi *encode_session_vaapi,
uint32_t surface_width,
uint32_t surface_height,
GError **error)
{
g_autoptr (NV12VkVASurface) vkva_surface = NULL;
VASurfaceAttrib surface_attributes[2] = {};
VADRMPRIMESurfaceDescriptor prime_descriptor = {};
g_autoptr (GrdVkImage) vk_y_layer = NULL;
g_autoptr (GrdVkImage) vk_uv_layer = NULL;
GrdImageViewNV12 *image_view_nv12;
uint32_t object_idx;
int fd;
uint64_t drm_format_modifier;
VAStatus va_status;
g_assert (surface_width % 16 == 0);
g_assert (surface_height % 16 == 0);
g_assert (surface_width >= 16);
g_assert (surface_height >= 16);
vkva_surface = g_new0 (NV12VkVASurface, 1);
vkva_surface->encode_session_vaapi = encode_session_vaapi;
vkva_surface->va_surface = VA_INVALID_SURFACE;
vkva_surface->fds[0] = -1;
vkva_surface->fds[1] = -1;
surface_attributes[0].type = VASurfaceAttribPixelFormat;
surface_attributes[0].flags = VA_SURFACE_ATTRIB_SETTABLE;
surface_attributes[0].value.type = VAGenericValueTypeInteger;
surface_attributes[0].value.value.i = VA_FOURCC_NV12;
surface_attributes[1].type = VASurfaceAttribUsageHint;
surface_attributes[1].flags = VA_SURFACE_ATTRIB_SETTABLE;
surface_attributes[1].value.type = VAGenericValueTypeInteger;
surface_attributes[1].value.value.i = VA_SURFACE_ATTRIB_USAGE_HINT_EXPORT;
va_status = vaCreateSurfaces (encode_session_vaapi->va_display,
VA_RT_FORMAT_YUV420,
surface_width, surface_height,
&vkva_surface->va_surface, 1,
surface_attributes,
G_N_ELEMENTS (surface_attributes));
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create NV12 surface: %s", vaErrorStr (va_status));
return NULL;
}
g_assert (vkva_surface->va_surface != VA_INVALID_SURFACE);
va_status = vaExportSurfaceHandle (encode_session_vaapi->va_display,
vkva_surface->va_surface,
VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
VA_EXPORT_SURFACE_WRITE_ONLY |
VA_EXPORT_SURFACE_SEPARATE_LAYERS,
&prime_descriptor);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to export surface handle: %s",
vaErrorStr (va_status));
return NULL;
}
g_assert (prime_descriptor.num_layers == 2);
g_assert (prime_descriptor.layers[0].num_planes == 1);
g_assert (prime_descriptor.layers[1].num_planes == 1);
g_assert (prime_descriptor.num_objects > 0);
g_assert (prime_descriptor.num_objects <= 2);
g_assert (prime_descriptor.objects[0].fd != -1);
if (prime_descriptor.num_objects > 1)
g_assert (prime_descriptor.objects[1].fd != -1);
vkva_surface->fds[0] = prime_descriptor.objects[0].fd;
if (prime_descriptor.num_objects > 1)
vkva_surface->fds[1] = prime_descriptor.objects[1].fd;
object_idx = prime_descriptor.layers[0].object_index[0];
fd = prime_descriptor.objects[object_idx].fd;
drm_format_modifier =
prime_descriptor.objects[object_idx].drm_format_modifier;
vk_y_layer =
grd_vk_dma_buf_image_new (encode_session_vaapi->vk_device,
VK_FORMAT_R8_UNORM,
surface_width, surface_height,
VK_IMAGE_USAGE_STORAGE_BIT, fd,
prime_descriptor.layers[0].offset[0],
prime_descriptor.layers[0].pitch[0],
drm_format_modifier, error);
if (!vk_y_layer)
return NULL;
object_idx = prime_descriptor.layers[1].object_index[0];
fd = prime_descriptor.objects[object_idx].fd;
drm_format_modifier =
prime_descriptor.objects[object_idx].drm_format_modifier;
vk_uv_layer =
grd_vk_dma_buf_image_new (encode_session_vaapi->vk_device,
VK_FORMAT_R8G8_UNORM,
surface_width / 2, surface_height / 2,
VK_IMAGE_USAGE_STORAGE_BIT, fd,
prime_descriptor.layers[1].offset[0],
prime_descriptor.layers[1].pitch[0],
drm_format_modifier, error);
if (!vk_uv_layer)
return NULL;
image_view_nv12 =
grd_image_view_nv12_new (g_steal_pointer (&vk_y_layer),
g_steal_pointer (&vk_uv_layer));
vkva_surface->image_view = GRD_IMAGE_VIEW (image_view_nv12);
g_clear_fd (&vkva_surface->fds[1], NULL);
g_clear_fd (&vkva_surface->fds[0], NULL);
return g_steal_pointer (&vkva_surface);
}
static void
nv12_vkva_surface_free (NV12VkVASurface *vkva_surface)
{
GrdEncodeSessionVaapi *encode_session_vaapi =
vkva_surface->encode_session_vaapi;
VADisplay va_display = encode_session_vaapi->va_display;
g_clear_object (&vkva_surface->image_view);
g_clear_fd (&vkva_surface->fds[1], NULL);
g_clear_fd (&vkva_surface->fds[0], NULL);
if (vkva_surface->va_surface != VA_INVALID_SURFACE)
vaDestroySurfaces (va_display, &vkva_surface->va_surface, 1);
g_free (vkva_surface);
}
static void
grd_encode_session_vaapi_get_surface_size (GrdEncodeSession *encode_session,
uint32_t *surface_width,
uint32_t *surface_height)
{
GrdEncodeSessionVaapi *encode_session_vaapi =
GRD_ENCODE_SESSION_VAAPI (encode_session);
g_assert (encode_session_vaapi->surface_width % 16 == 0);
g_assert (encode_session_vaapi->surface_height % 16 == 0);
g_assert (encode_session_vaapi->surface_width >= 16);
g_assert (encode_session_vaapi->surface_height >= 16);
*surface_width = encode_session_vaapi->surface_width;
*surface_height = encode_session_vaapi->surface_height;
}
static GList *
grd_encode_session_vaapi_get_image_views (GrdEncodeSession *encode_session)
{
GrdEncodeSessionVaapi *encode_session_vaapi =
GRD_ENCODE_SESSION_VAAPI (encode_session);
return g_hash_table_get_keys (encode_session_vaapi->surfaces);
}
static gboolean
grd_encode_session_vaapi_has_pending_frames (GrdEncodeSession *encode_session)
{
GrdEncodeSessionVaapi *encode_session_vaapi =
GRD_ENCODE_SESSION_VAAPI (encode_session);
g_autoptr (GMutexLocker) locker = NULL;
locker = g_mutex_locker_new (&encode_session_vaapi->pending_frames_mutex);
return g_hash_table_size (encode_session_vaapi->pending_frames) > 0;
}
static gboolean
create_packed_header_buffers (GrdEncodeSessionVaapi *encode_session_vaapi,
H264Frame *h264_frame,
VAEncPackedHeaderType header_type,
uint8_t *header_data,
uint32_t header_length,
GError **error)
{
VAEncPackedHeaderParameterBuffer packed_header_param = {};
VABufferID packed_header_buffer = VA_INVALID_ID;
VABufferID packed_header_data_buffer = VA_INVALID_ID;
VAStatus va_status;
packed_header_param.type = header_type;
packed_header_param.bit_length = header_length;
packed_header_param.has_emulation_bytes = 0;
va_status = vaCreateBuffer (encode_session_vaapi->va_display,
encode_session_vaapi->va_context,
VAEncPackedHeaderParameterBufferType,
sizeof (VAEncPackedHeaderParameterBuffer), 1,
&packed_header_param,
&packed_header_buffer);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create packed header parameter buffer: %s",
vaErrorStr (va_status));
return FALSE;
}
g_assert (packed_header_buffer != VA_INVALID_ID);
h264_frame->packed_header_buffers =
g_list_append (h264_frame->packed_header_buffers,
GUINT_TO_POINTER (packed_header_buffer));
va_status = vaCreateBuffer (encode_session_vaapi->va_display,
encode_session_vaapi->va_context,
VAEncPackedHeaderDataBufferType,
(header_length + 7) / 8, 1,
header_data,
&packed_header_data_buffer);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create packed header data buffer: %s",
vaErrorStr (va_status));
return FALSE;
}
g_assert (packed_header_data_buffer != VA_INVALID_ID);
h264_frame->packed_header_buffers =
g_list_append (h264_frame->packed_header_buffers,
GUINT_TO_POINTER (packed_header_data_buffer));
return TRUE;
}
static gboolean
ensure_access_unit_delimiter (GrdEncodeSessionVaapi *encode_session_vaapi,
H264Frame *h264_frame,
GError **error)
{
GrdNalWriter *nal_writer = encode_session_vaapi->nal_writer;
g_autofree uint8_t *header_data = NULL;
uint32_t header_length = 0;
header_data = grd_nal_writer_get_aud_bitstream (nal_writer, &header_length);
if (!create_packed_header_buffers (encode_session_vaapi, h264_frame,
VAEncPackedHeaderRawData,
header_data, header_length, error))
return FALSE;
h264_frame->aud_header_length = header_length;
return TRUE;
}
static void
fill_avc_sequence (GrdEncodeSessionVaapi *encode_session_vaapi,
VAEncSequenceParameterBufferH264 *sequence_param)
{
uint32_t surface_width = encode_session_vaapi->surface_width;
uint32_t surface_height = encode_session_vaapi->surface_height;
g_assert (surface_width % 16 == 0);
g_assert (surface_height % 16 == 0);
g_assert (surface_width >= 16);
g_assert (surface_height >= 16);
sequence_param->seq_parameter_set_id = 0;
sequence_param->level_idc = encode_session_vaapi->level_idc;
sequence_param->intra_period = 0;
sequence_param->intra_idr_period = sequence_param->intra_period;
sequence_param->ip_period = 1; /* 1 + B-Frames */
sequence_param->bits_per_second = 0;
sequence_param->max_num_ref_frames = 1;
sequence_param->picture_width_in_mbs = surface_width / 16;
sequence_param->picture_height_in_mbs = surface_height / 16;
sequence_param->seq_fields.value = 0;
sequence_param->seq_fields.bits.chroma_format_idc = 1;
sequence_param->seq_fields.bits.frame_mbs_only_flag = 1;
sequence_param->seq_fields.bits.direct_8x8_inference_flag = 1;
sequence_param->seq_fields.bits.log2_max_frame_num_minus4 =
LOG2_MAX_FRAME_NUM - 4;
sequence_param->seq_fields.bits.pic_order_cnt_type = 2;
sequence_param->vui_parameters_present_flag = 1;
sequence_param->vui_fields.value = 0;
sequence_param->vui_fields.bits.aspect_ratio_info_present_flag = 1;
sequence_param->vui_fields.bits.timing_info_present_flag = 1;
sequence_param->vui_fields.bits.bitstream_restriction_flag = 1;
sequence_param->vui_fields.bits.log2_max_mv_length_horizontal = 15;
sequence_param->vui_fields.bits.log2_max_mv_length_vertical = 15;
sequence_param->aspect_ratio_idc = H264_Extended_SAR;
sequence_param->sar_width = 1;
sequence_param->sar_height = 1;
sequence_param->num_units_in_tick = 1 * 1000;
sequence_param->time_scale = 2 * encode_session_vaapi->refresh_rate * 1000;
}
static gboolean
create_sequence_buffer (GrdEncodeSessionVaapi *encode_session_vaapi,
H264Frame *h264_frame,
GError **error)
{
VAEncSequenceParameterBufferH264 *sequence_param =
&encode_session_vaapi->sequence_param;
VAStatus va_status;
va_status = vaCreateBuffer (encode_session_vaapi->va_display,
encode_session_vaapi->va_context,
VAEncSequenceParameterBufferType,
sizeof (VAEncSequenceParameterBufferH264), 1,
sequence_param,
&h264_frame->sequence_buffer);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create sequence parameter buffer: %s",
vaErrorStr (va_status));
return FALSE;
}
g_assert (h264_frame->sequence_buffer != VA_INVALID_ID);
return TRUE;
}
static gboolean
maybe_ensure_sequence (GrdEncodeSessionVaapi *encode_session_vaapi,
H264Frame *h264_frame,
GError **error)
{
GrdNalWriter *nal_writer = encode_session_vaapi->nal_writer;
VAAPIPicture *picture = h264_frame->reconstructed_picture;
VAEncSequenceParameterBufferH264 *sequence_param =
&encode_session_vaapi->sequence_param;
g_autofree uint8_t *header_data = NULL;
uint32_t header_length = 0;
if (!picture->is_idr)
return TRUE;
*sequence_param = (VAEncSequenceParameterBufferH264) {};
fill_avc_sequence (encode_session_vaapi, sequence_param);
if (!create_sequence_buffer (encode_session_vaapi, h264_frame, error))
return FALSE;
header_data = grd_nal_writer_get_sps_bitstream (nal_writer, sequence_param,
&header_length);
if (!create_packed_header_buffers (encode_session_vaapi, h264_frame,
VAEncPackedHeaderSequence,
header_data, header_length, error))
return FALSE;
h264_frame->sequence_header_length = header_length;
return TRUE;
}
static gboolean
ensure_misc_param_quality_level (GrdEncodeSessionVaapi *encode_session_vaapi,
H264Frame *h264_frame,
GError **error)
{
VAEncMiscParameterBuffer *misc_param = NULL;
VAEncMiscParameterBufferQualityLevel *misc_param_quality_level;
VABufferID misc_param_buffer = VA_INVALID_ID;
uint32_t total_size;
VAStatus va_status;
total_size = sizeof (VAEncMiscParameterBuffer) +
sizeof (VAEncMiscParameterBufferQualityLevel);
va_status = vaCreateBuffer (encode_session_vaapi->va_display,
encode_session_vaapi->va_context,
VAEncMiscParameterBufferType,
total_size, 1,
NULL,
&misc_param_buffer);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create misc parameter buffer: %s",
vaErrorStr (va_status));
return FALSE;
}
g_assert (misc_param_buffer != VA_INVALID_ID);
va_status = vaMapBuffer (encode_session_vaapi->va_display,
misc_param_buffer,
(void **) &misc_param);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to map misc parameter buffer: %s",
vaErrorStr (va_status));
return FALSE;
}
g_assert (misc_param);
misc_param->type = VAEncMiscParameterTypeQualityLevel;
misc_param_quality_level =
(VAEncMiscParameterBufferQualityLevel *) misc_param->data;
*misc_param_quality_level = (VAEncMiscParameterBufferQualityLevel) {};
misc_param_quality_level->quality_level = 0;
va_status = vaUnmapBuffer (encode_session_vaapi->va_display,
misc_param_buffer);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to unmap misc parameter buffer: %s",
vaErrorStr (va_status));
return FALSE;
}
h264_frame->misc_buffers =
g_list_append (h264_frame->misc_buffers,
GUINT_TO_POINTER (misc_param_buffer));
return TRUE;
}
static gboolean
ensure_misc_param_sub_mb_part_pel_h264 (GrdEncodeSessionVaapi *encode_session_vaapi,
H264Frame *h264_frame,
GError **error)
{
VAEncMiscParameterBuffer *misc_param = NULL;
VAEncMiscParameterSubMbPartPelH264 *misc_param_sub_mb_part_pel_h264;
VABufferID misc_param_buffer = VA_INVALID_ID;
uint32_t total_size;
VAStatus va_status;
total_size = sizeof (VAEncMiscParameterBuffer) +
sizeof (VAEncMiscParameterSubMbPartPelH264);
va_status = vaCreateBuffer (encode_session_vaapi->va_display,
encode_session_vaapi->va_context,
VAEncMiscParameterBufferType,
total_size, 1,
NULL,
&misc_param_buffer);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create misc parameter buffer: %s",
vaErrorStr (va_status));
return FALSE;
}
g_assert (misc_param_buffer != VA_INVALID_ID);
va_status = vaMapBuffer (encode_session_vaapi->va_display,
misc_param_buffer,
(void **) &misc_param);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to map misc parameter buffer: %s",
vaErrorStr (va_status));
return FALSE;
}
g_assert (misc_param);
misc_param->type = VAEncMiscParameterTypeSubMbPartPel;
misc_param_sub_mb_part_pel_h264 =
(VAEncMiscParameterSubMbPartPelH264 *) misc_param->data;
*misc_param_sub_mb_part_pel_h264 = (VAEncMiscParameterSubMbPartPelH264) {};
misc_param_sub_mb_part_pel_h264->enable_sub_pel_mode = 1;
misc_param_sub_mb_part_pel_h264->sub_pel_mode = 3;
va_status = vaUnmapBuffer (encode_session_vaapi->va_display,
misc_param_buffer);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to unmap misc parameter buffer: %s",
vaErrorStr (va_status));
return FALSE;
}
h264_frame->misc_buffers =
g_list_append (h264_frame->misc_buffers,
GUINT_TO_POINTER (misc_param_buffer));
return TRUE;
}
static gboolean
ensure_misc_params (GrdEncodeSessionVaapi *encode_session_vaapi,
H264Frame *h264_frame,
GError **error)
{
VAAPIPicture *picture = h264_frame->reconstructed_picture;
if (picture->is_idr &&
encode_session_vaapi->supports_quality_level &&
!ensure_misc_param_quality_level (encode_session_vaapi, h264_frame, error))
return FALSE;
if (!ensure_misc_param_sub_mb_part_pel_h264 (encode_session_vaapi, h264_frame,
error))
return FALSE;
return TRUE;
}
static void
fill_avc_picture (GrdEncodeSessionVaapi *encode_session_vaapi,
VAEncPictureParameterBufferH264 *picture_param,
H264Frame *h264_frame)
{
VAAPIPicture *picture = h264_frame->reconstructed_picture;
uint8_t i = 0;
picture_param->CurrPic.picture_id = picture->va_surface;
picture_param->CurrPic.frame_idx = picture->frame_num;
picture_param->CurrPic.TopFieldOrderCnt = picture->frame_num * 2;
picture_param->CurrPic.BottomFieldOrderCnt = picture->frame_num * 2;
if (!picture->is_idr)
{
VAPictureH264 *h264_picture = &picture_param->ReferenceFrames[0];
VAAPIPicture *reference = encode_session_vaapi->reference_picture;
g_assert (reference);
h264_picture->picture_id = reference->va_surface;
h264_picture->frame_idx = reference->frame_num;
h264_picture->flags = VA_PICTURE_H264_SHORT_TERM_REFERENCE;
h264_picture->TopFieldOrderCnt = reference->frame_num * 2;
h264_picture->BottomFieldOrderCnt = reference->frame_num * 2;
++i;
}
for (; i < G_N_ELEMENTS (picture_param->ReferenceFrames); ++i)
{
picture_param->ReferenceFrames[i].picture_id = VA_INVALID_ID;
picture_param->ReferenceFrames[i].flags = VA_PICTURE_H264_INVALID;
}
picture_param->pic_parameter_set_id = 0;
picture_param->seq_parameter_set_id = 0;
picture_param->last_picture = 0;
picture_param->frame_num = picture->frame_num;
picture_param->pic_init_qp = 22;
picture_param->num_ref_idx_l0_active_minus1 = 0;
picture_param->num_ref_idx_l1_active_minus1 = 0;
picture_param->chroma_qp_index_offset = 0;
picture_param->second_chroma_qp_index_offset = 0;
picture_param->pic_fields.value = 0;
if (picture->is_idr)
picture_param->pic_fields.bits.idr_pic_flag = 1;
picture_param->pic_fields.bits.reference_pic_flag = 1; /* No B-Frames are present */
picture_param->pic_fields.bits.entropy_coding_mode_flag = 1; /* CABAC */
picture_param->pic_fields.bits.transform_8x8_mode_flag = 1;
}
static gboolean
prepare_picture (GrdEncodeSessionVaapi *encode_session_vaapi,
H264Frame *h264_frame,
GError **error)
{
GrdNalWriter *nal_writer = encode_session_vaapi->nal_writer;
VAAPIPicture *picture = h264_frame->reconstructed_picture;
VAEncPictureParameterBufferH264 *picture_param =
&encode_session_vaapi->picture_param;
g_autofree uint8_t *header_data = NULL;
uint32_t header_length = 0;
*picture_param = (VAEncPictureParameterBufferH264) {};
fill_avc_picture (encode_session_vaapi, picture_param, h264_frame);
if (!picture->is_idr)
return TRUE;
header_data = grd_nal_writer_get_pps_bitstream (nal_writer, picture_param,
&header_length);
if (!create_packed_header_buffers (encode_session_vaapi, h264_frame,
VAEncPackedHeaderPicture,
header_data, header_length, error))
return FALSE;
h264_frame->picture_header_length = header_length;
return TRUE;
}
static void
fill_avc_slice (GrdEncodeSessionVaapi *encode_session_vaapi,
VAEncSliceParameterBufferH264 *slice_param,
H264Frame *h264_frame)
{
VAAPIPicture *picture = h264_frame->reconstructed_picture;
uint32_t surface_width = encode_session_vaapi->surface_width;
uint32_t surface_height = encode_session_vaapi->surface_height;
uint32_t width_in_mbs;
uint32_t height_in_mbs;
uint32_t n_macroblocks;
uint8_t i = 0;
g_assert (G_N_ELEMENTS (slice_param->RefPicList0) < 255);
g_assert (G_N_ELEMENTS (slice_param->RefPicList1) < 255);
g_assert (surface_width % 16 == 0);
g_assert (surface_height % 16 == 0);
g_assert (surface_width >= 16);
g_assert (surface_height >= 16);
width_in_mbs = surface_width / 16;
height_in_mbs = surface_height / 16;
n_macroblocks = width_in_mbs * height_in_mbs;
slice_param->macroblock_address = 0;
slice_param->num_macroblocks = n_macroblocks;
slice_param->macroblock_info = VA_INVALID_ID;
slice_param->slice_type = picture->is_idr ? H264_SLICE_TYPE_I
: H264_SLICE_TYPE_P;
slice_param->pic_parameter_set_id = 0;
slice_param->idr_pic_id = 0;
slice_param->pic_order_cnt_lsb = picture->frame_num * 2;
if (!picture->is_idr)
{
VAAPIPicture *reference = encode_session_vaapi->reference_picture;
g_assert (reference);
slice_param->RefPicList0[0].picture_id = reference->va_surface;
slice_param->RefPicList0[0].frame_idx = reference->frame_num;
slice_param->RefPicList0[0].TopFieldOrderCnt = reference->frame_num * 2;
slice_param->RefPicList0[0].BottomFieldOrderCnt = reference->frame_num * 2;
slice_param->RefPicList0[0].flags = VA_PICTURE_H264_SHORT_TERM_REFERENCE;
++i;
}
for (; i < G_N_ELEMENTS (slice_param->RefPicList0); ++i)
{
slice_param->RefPicList0[i].picture_id = VA_INVALID_SURFACE;
slice_param->RefPicList0[i].flags = VA_PICTURE_H264_INVALID;
}
/* B slices are not used here */
for (i = 0; i < G_N_ELEMENTS (slice_param->RefPicList1); ++i)
{
slice_param->RefPicList1[i].picture_id = VA_INVALID_SURFACE;
slice_param->RefPicList1[i].flags = VA_PICTURE_H264_INVALID;
}
slice_param->slice_qp_delta = 0;
}
static gboolean
create_slice_buffer (GrdEncodeSessionVaapi *encode_session_vaapi,
H264Frame *h264_frame,
VAEncSliceParameterBufferH264 *slice_param,
GError **error)
{
VAStatus va_status;
va_status = vaCreateBuffer (encode_session_vaapi->va_display,
encode_session_vaapi->va_context,
VAEncSliceParameterBufferType,
sizeof (VAEncSliceParameterBufferH264), 1,
slice_param,
&h264_frame->slice_buffer);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create slice parameter buffer: %s",
vaErrorStr (va_status));
return FALSE;
}
g_assert (h264_frame->slice_buffer != VA_INVALID_ID);
return TRUE;
}
static gboolean
ensure_slice (GrdEncodeSessionVaapi *encode_session_vaapi,
H264Frame *h264_frame,
GError **error)
{
GrdNalWriter *nal_writer = encode_session_vaapi->nal_writer;
VAEncSliceParameterBufferH264 slice_param = {};
g_autofree uint8_t *header_data = NULL;
uint32_t header_length = 0;
fill_avc_slice (encode_session_vaapi, &slice_param, h264_frame);
if (!create_slice_buffer (encode_session_vaapi, h264_frame, &slice_param,
error))
return FALSE;
header_data =
grd_nal_writer_get_slice_header_bitstream (nal_writer, &slice_param,
&encode_session_vaapi->sequence_param,
&encode_session_vaapi->picture_param,
&header_length);
if (!create_packed_header_buffers (encode_session_vaapi, h264_frame,
VAEncPackedHeaderSlice,
header_data, header_length, error))
return FALSE;
h264_frame->slice_header_length = header_length;
return TRUE;
}
static gboolean
ensure_bitstream_buffer (GrdEncodeSessionVaapi *encode_session_vaapi,
H264Frame *h264_frame,
GError **error)
{
VAEncPictureParameterBufferH264 *picture_param =
&encode_session_vaapi->picture_param;
uint32_t surface_width = encode_session_vaapi->surface_width;
uint32_t surface_height = encode_session_vaapi->surface_height;
uint32_t bitstream_buffer_size;
uint32_t header_lengths = 0;
uint32_t width_in_mbs;
uint32_t height_in_mbs;
VAStatus va_status;
g_assert (surface_width % 16 == 0);
g_assert (surface_height % 16 == 0);
g_assert (surface_width >= 16);
g_assert (surface_height >= 16);
width_in_mbs = surface_width / 16;
height_in_mbs = surface_height / 16;
bitstream_buffer_size = width_in_mbs * height_in_mbs * 400;
header_lengths += h264_frame->aud_header_length;
header_lengths += h264_frame->sequence_header_length;
header_lengths += h264_frame->picture_header_length;
header_lengths += h264_frame->slice_header_length;
bitstream_buffer_size += (header_lengths + 7) / 8;
va_status = vaCreateBuffer (encode_session_vaapi->va_display,
encode_session_vaapi->va_context,
VAEncCodedBufferType,
bitstream_buffer_size, 1,
NULL,
&h264_frame->bitstream_buffer);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create bitstream buffer: %s",
vaErrorStr (va_status));
return FALSE;
}
g_assert (h264_frame->bitstream_buffer != VA_INVALID_ID);
picture_param->coded_buf = h264_frame->bitstream_buffer;
return TRUE;
}
static gboolean
create_picture_buffer (GrdEncodeSessionVaapi *encode_session_vaapi,
H264Frame *h264_frame,
GError **error)
{
VAEncPictureParameterBufferH264 *picture_param =
&encode_session_vaapi->picture_param;
VAStatus va_status;
va_status = vaCreateBuffer (encode_session_vaapi->va_display,
encode_session_vaapi->va_context,
VAEncPictureParameterBufferType,
sizeof (VAEncPictureParameterBufferH264), 1,
picture_param,
&h264_frame->picture_buffer);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create picture parameter buffer: %s",
vaErrorStr (va_status));
return FALSE;
}
g_assert (h264_frame->picture_buffer != VA_INVALID_ID);
return TRUE;
}
static gboolean
render_picture (GrdEncodeSessionVaapi *encode_session_vaapi,
VAContextID va_context,
VASurfaceID va_render_target,
VABufferID *va_buffers,
uint32_t n_buffers,
GError **error)
{
VAStatus va_status;
va_status = vaBeginPicture (encode_session_vaapi->va_display, va_context,
va_render_target);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to begin picture: %s", vaErrorStr (va_status));
return FALSE;
}
va_status = vaRenderPicture (encode_session_vaapi->va_display, va_context,
va_buffers, n_buffers);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to submit buffers: %s", vaErrorStr (va_status));
return FALSE;
}
va_status = vaEndPicture (encode_session_vaapi->va_display, va_context);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to end picture: %s", vaErrorStr (va_status));
return FALSE;
}
return TRUE;
}
static void
prepare_avc_frame_info (GrdEncodeSessionVaapi *encode_session_vaapi,
H264Frame *h264_frame)
{
VAAPIPicture *picture = h264_frame->reconstructed_picture;
VAEncPictureParameterBufferH264 *picture_param =
&encode_session_vaapi->picture_param;
h264_frame->avc_frame_info =
grd_avc_frame_info_new (picture->is_idr ? GRD_AVC_FRAME_TYPE_I
: GRD_AVC_FRAME_TYPE_P,
picture_param->pic_init_qp,
100);
}
static H264Frame *
encode_frame (GrdEncodeSessionVaapi *encode_session_vaapi,
VASurfaceID src_surface,
GError **error)
{
g_autoptr (H264Frame) h264_frame = NULL;
g_autofree VABufferID *va_buffers = NULL;
uint32_t n_buffers = 0;
h264_frame = h264_frame_new (encode_session_vaapi, error);
if (!h264_frame)
return NULL;
if (!ensure_access_unit_delimiter (encode_session_vaapi, h264_frame, error))
return NULL;
if (!maybe_ensure_sequence (encode_session_vaapi, h264_frame, error))
return NULL;
if (!ensure_misc_params (encode_session_vaapi, h264_frame, error))
return NULL;
if (!prepare_picture (encode_session_vaapi, h264_frame, error))
return NULL;
if (!ensure_slice (encode_session_vaapi, h264_frame, error))
return NULL;
if (!ensure_bitstream_buffer (encode_session_vaapi, h264_frame, error))
return NULL;
if (!create_picture_buffer (encode_session_vaapi, h264_frame, error))
return NULL;
if (encode_session_vaapi->debug_va_times)
h264_frame->timestamps[0] = g_get_monotonic_time ();
va_buffers = h264_frame_get_buffers (h264_frame, &n_buffers);
if (!render_picture (encode_session_vaapi, encode_session_vaapi->va_context,
src_surface, va_buffers, n_buffers, error))
return NULL;
h264_frame->src_surface = src_surface;
prepare_avc_frame_info (encode_session_vaapi, h264_frame);
if (encode_session_vaapi->debug_va_times)
h264_frame->timestamps[1] = g_get_monotonic_time ();
return g_steal_pointer (&h264_frame);
}
static void
prepare_next_frame (GrdEncodeSessionVaapi *encode_session_vaapi,
H264Frame *current_h264_frame)
{
uint16_t max_frame_num;
g_clear_pointer (&encode_session_vaapi->reference_picture, vaapi_picture_free);
encode_session_vaapi->reference_picture =
g_steal_pointer (&current_h264_frame->reconstructed_picture);
max_frame_num = 1 << LOG2_MAX_FRAME_NUM;
++encode_session_vaapi->frame_num;
encode_session_vaapi->frame_num %= max_frame_num + 1;
encode_session_vaapi->pending_idr_frame = FALSE;
}
static gboolean
grd_encode_session_vaapi_encode_frame (GrdEncodeSession *encode_session,
GrdEncodeContext *encode_context,
GrdImageView *image_view,
GError **error)
{
GrdEncodeSessionVaapi *encode_session_vaapi =
GRD_ENCODE_SESSION_VAAPI (encode_session);
NV12VkVASurface *vkva_surface = NULL;
H264Frame *h264_frame = NULL;
g_mutex_lock (&encode_session_vaapi->pending_frames_mutex);
g_assert (!g_hash_table_contains (encode_session_vaapi->pending_frames,
image_view));
g_mutex_unlock (&encode_session_vaapi->pending_frames_mutex);
if (!g_hash_table_lookup_extended (encode_session_vaapi->surfaces,
image_view,
NULL, (gpointer *) &vkva_surface))
g_assert_not_reached ();
g_assert (vkva_surface);
h264_frame = encode_frame (encode_session_vaapi, vkva_surface->va_surface,
error);
if (!h264_frame)
return FALSE;
g_mutex_lock (&encode_session_vaapi->pending_frames_mutex);
g_hash_table_insert (encode_session_vaapi->pending_frames,
image_view, h264_frame);
g_mutex_unlock (&encode_session_vaapi->pending_frames_mutex);
prepare_next_frame (encode_session_vaapi, h264_frame);
return TRUE;
}
static gboolean
get_bitstream (GrdEncodeSessionVaapi *encode_session_vaapi,
VABufferID output_buf,
GrdBitstream **bitstream,
GError **error)
{
VACodedBufferSegment *va_segment = NULL;
VAStatus va_status;
va_status = vaMapBuffer (encode_session_vaapi->va_display,
output_buf,
(void **) &va_segment);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to map output buffer: %s", vaErrorStr (va_status));
return FALSE;
}
g_assert (va_segment);
*bitstream = grd_bitstream_new (va_segment->buf, va_segment->size);
return TRUE;
}
static gboolean
finish_frame_encoding (GrdEncodeSessionVaapi *encode_session_vaapi,
H264Frame *h264_frame,
GrdBitstream **bitstream,
GError **error)
{
int64_t *timestamps = h264_frame->timestamps;
VAStatus va_status;
va_status = vaSyncSurface (encode_session_vaapi->va_display,
h264_frame->src_surface);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to sync surface: %s", vaErrorStr (va_status));
return FALSE;
}
if (encode_session_vaapi->debug_va_times)
timestamps[2] = g_get_monotonic_time ();
if (!get_bitstream (encode_session_vaapi, h264_frame->bitstream_buffer,
bitstream, error))
return FALSE;
grd_bitstream_set_avc_frame_info (*bitstream,
g_steal_pointer (&h264_frame->avc_frame_info));
if (encode_session_vaapi->debug_va_times)
{
g_debug ("[HWAccel.VAAPI] EncodeFrame[Times]: submission: %liµs, "
"waitForBitstream: %liµs, total: %liµs",
timestamps[1] - timestamps[0],
timestamps[2] - timestamps[1],
timestamps[2] - timestamps[0]);
}
return TRUE;
}
static GrdBitstream *
grd_encode_session_vaapi_lock_bitstream (GrdEncodeSession *encode_session,
GrdImageView *image_view,
GError **error)
{
GrdEncodeSessionVaapi *encode_session_vaapi =
GRD_ENCODE_SESSION_VAAPI (encode_session);
g_autoptr (H264Frame) h264_frame = NULL;
g_autoptr (GMutexLocker) locker = NULL;
GrdBitstream *bitstream = NULL;
g_mutex_lock (&encode_session_vaapi->pending_frames_mutex);
if (!g_hash_table_steal_extended (encode_session_vaapi->pending_frames,
image_view,
NULL, (gpointer *) &h264_frame))
g_assert_not_reached ();
g_mutex_unlock (&encode_session_vaapi->pending_frames_mutex);
g_assert (h264_frame);
if (!finish_frame_encoding (encode_session_vaapi, h264_frame, &bitstream,
error))
return NULL;
locker = g_mutex_locker_new (&encode_session_vaapi->bitstreams_mutex);
g_hash_table_insert (encode_session_vaapi->bitstreams,
bitstream, g_steal_pointer (&h264_frame));
return bitstream;
}
static gboolean
grd_encode_session_vaapi_unlock_bitstream (GrdEncodeSession *encode_session,
GrdBitstream *bitstream,
GError **error)
{
GrdEncodeSessionVaapi *encode_session_vaapi =
GRD_ENCODE_SESSION_VAAPI (encode_session);
g_autoptr (H264Frame) h264_frame = NULL;
g_autoptr (GMutexLocker) locker = NULL;
VAStatus va_status;
locker = g_mutex_locker_new (&encode_session_vaapi->bitstreams_mutex);
if (!g_hash_table_steal_extended (encode_session_vaapi->bitstreams,
bitstream,
NULL, (gpointer *) &h264_frame))
g_assert_not_reached ();
g_clear_pointer (&locker, g_mutex_locker_free);
g_assert (h264_frame);
va_status = vaUnmapBuffer (encode_session_vaapi->va_display,
h264_frame->bitstream_buffer);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to unmap output buffer: %s", vaErrorStr (va_status));
return FALSE;
}
grd_bitstream_free (bitstream);
return TRUE;
}
static gboolean
get_avc_level_idc_for_mbps (uint32_t mbps,
uint8_t *level_idc,
GError **error)
{
*level_idc = 0;
/* See also Table A-1 - Level limits (Rec. ITU-T H.264 (08/2021)) */
if (mbps <= 1485)
*level_idc = 10;
else if (mbps <= 3000)
*level_idc = 11;
else if (mbps <= 6000)
*level_idc = 12;
else if (mbps <= 11880)
*level_idc = 13;
else if (mbps <= 19800)
*level_idc = 21;
else if (mbps <= 20250)
*level_idc = 22;
else if (mbps <= 40500)
*level_idc = 30;
else if (mbps <= 108000)
*level_idc = 31;
else if (mbps <= 216000)
*level_idc = 32;
else if (mbps <= 245760)
*level_idc = 40;
else if (mbps <= 522240)
*level_idc = 42;
else if (mbps <= 589824)
*level_idc = 50;
else if (mbps <= 983040)
*level_idc = 51;
else if (mbps <= 2073600)
*level_idc = 52;
else if (mbps <= 4177920)
*level_idc = 60;
else if (mbps <= 8355840)
*level_idc = 61;
else if (mbps <= 16711680)
*level_idc = 62;
if (*level_idc == 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Unable to determine level_idc");
return FALSE;
}
return TRUE;
}
static gboolean
determine_avc_level_idc (GrdEncodeSessionVaapi *encode_session_vaapi,
GError **error)
{
uint32_t surface_width = encode_session_vaapi->surface_width;
uint32_t surface_height = encode_session_vaapi->surface_height;
uint32_t width_in_mbs;
uint32_t height_in_mbs;
uint32_t mbs_per_sec;
g_assert (surface_width % 16 == 0);
g_assert (surface_height % 16 == 0);
g_assert (surface_width >= 16);
g_assert (surface_height >= 16);
width_in_mbs = surface_width / 16;
height_in_mbs = surface_height / 16;
mbs_per_sec = width_in_mbs * height_in_mbs *
encode_session_vaapi->refresh_rate;
if (!get_avc_level_idc_for_mbps (mbs_per_sec,
&encode_session_vaapi->level_idc,
error))
return FALSE;
return TRUE;
}
static gboolean
get_surface_constraints (GrdEncodeSessionVaapi *encode_session_vaapi,
gboolean *supports_nv12,
int32_t *min_width,
int32_t *max_width,
int32_t *min_height,
int32_t *max_height,
GError **error)
{
g_autofree VASurfaceAttrib *surface_attributes = NULL;
unsigned int n_attributes = 0;
gboolean found_min_width = FALSE;
gboolean found_max_width = FALSE;
gboolean found_min_height = FALSE;
gboolean found_max_height = FALSE;
VAStatus va_status;
unsigned int i;
*supports_nv12 = FALSE;
va_status = vaQuerySurfaceAttributes (encode_session_vaapi->va_display,
encode_session_vaapi->va_config,
NULL, &n_attributes);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to query num surface attributes: %s",
vaErrorStr (va_status));
return FALSE;
}
if (n_attributes == 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Invalid max num surface attributes");
return FALSE;
}
surface_attributes = g_new0 (VASurfaceAttrib, n_attributes);
va_status = vaQuerySurfaceAttributes (encode_session_vaapi->va_display,
encode_session_vaapi->va_config,
surface_attributes, &n_attributes);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to query surface attributes: %s",
vaErrorStr (va_status));
return FALSE;
}
for (i = 0; i < n_attributes; ++i)
{
if (!(surface_attributes[i].flags & VA_SURFACE_ATTRIB_GETTABLE) ||
surface_attributes[i].value.type != VAGenericValueTypeInteger)
continue;
if (surface_attributes[i].type == VASurfaceAttribPixelFormat &&
surface_attributes[i].value.value.i == VA_FOURCC_NV12)
*supports_nv12 = TRUE;
if (surface_attributes[i].type == VASurfaceAttribMinWidth)
{
*min_width = surface_attributes[i].value.value.i;
found_min_width = TRUE;
}
if (surface_attributes[i].type == VASurfaceAttribMaxWidth)
{
*max_width = surface_attributes[i].value.value.i;
found_max_width = TRUE;
}
if (surface_attributes[i].type == VASurfaceAttribMinHeight)
{
*min_height = surface_attributes[i].value.value.i;
found_min_height = TRUE;
}
if (surface_attributes[i].type == VASurfaceAttribMaxHeight)
{
*max_height = surface_attributes[i].value.value.i;
found_max_height = TRUE;
}
}
if (found_min_width == FALSE || found_max_width == FALSE ||
found_min_height == FALSE || found_max_height == FALSE)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Unable to query surface size constraints");
return FALSE;
}
return TRUE;
}
static gboolean
create_avc420_encode_session (GrdEncodeSessionVaapi *encode_session_vaapi,
GError **error)
{
uint32_t surface_width = encode_session_vaapi->surface_width;
uint32_t surface_height = encode_session_vaapi->surface_height;
VAConfigAttrib config_attributes[3] = {};
gboolean supports_nv12 = FALSE;
int32_t min_surface_width = 0;
int32_t max_surface_width = 0;
int32_t min_surface_height = 0;
int32_t max_surface_height = 0;
VAStatus va_status;
uint32_t i;
g_assert (surface_width % 16 == 0);
g_assert (surface_height % 16 == 0);
g_assert (surface_width >= 16);
g_assert (surface_height >= 16);
if (!determine_avc_level_idc (encode_session_vaapi, error))
return FALSE;
config_attributes[0].type = VAConfigAttribRTFormat;
config_attributes[0].value = VA_RT_FORMAT_YUV420;
config_attributes[1].type = VAConfigAttribRateControl;
config_attributes[1].value = VA_RC_CQP;
config_attributes[2].type = VAConfigAttribEncPackedHeaders;
config_attributes[2].value = VA_ENC_PACKED_HEADER_SEQUENCE |
VA_ENC_PACKED_HEADER_PICTURE |
VA_ENC_PACKED_HEADER_SLICE |
VA_ENC_PACKED_HEADER_RAW_DATA;
va_status = vaCreateConfig (encode_session_vaapi->va_display,
VAProfileH264High, VAEntrypointEncSlice,
config_attributes,
G_N_ELEMENTS (config_attributes),
&encode_session_vaapi->va_config);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create AVC420 encode session config: %s",
vaErrorStr (va_status));
return FALSE;
}
g_assert (encode_session_vaapi->va_config != VA_INVALID_ID);
if (!get_surface_constraints (encode_session_vaapi, &supports_nv12,
&min_surface_width, &max_surface_width,
&min_surface_height, &max_surface_height,
error))
return FALSE;
if (!supports_nv12)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Surface does not support NV12 as pixel format");
return FALSE;
}
if (surface_width < min_surface_width || surface_width > max_surface_width ||
surface_height < min_surface_height || surface_height > max_surface_height)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"Surface size does not meet encoder constraints. Requested "
"size: [%i, %i], Size constraints: [[%i, %i], [%i, %i]]",
surface_width, surface_height,
min_surface_width, max_surface_width,
min_surface_height, max_surface_height);
return FALSE;
}
g_debug ("[HWAccel.VAAPI] Requested surface size: [%i, %i], Size "
"constraints: [[%i, %i], [%i, %i]]",
surface_width, surface_height,
min_surface_width, max_surface_width,
min_surface_height, max_surface_height);
va_status = vaCreateContext (encode_session_vaapi->va_display,
encode_session_vaapi->va_config,
surface_width, surface_height, VA_PROGRESSIVE,
NULL, 0,
&encode_session_vaapi->va_context);
if (va_status != VA_STATUS_SUCCESS)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create AVC420 encode session context: %s",
vaErrorStr (va_status));
return FALSE;
}
g_assert (encode_session_vaapi->va_context != VA_INVALID_ID);
for (i = 0; i < N_SRC_SURFACES; ++i)
{
NV12VkVASurface *surface;
surface = nv12_vkva_surface_new (encode_session_vaapi,
surface_width, surface_height,
error);
if (!surface)
return FALSE;
g_hash_table_insert (encode_session_vaapi->surfaces,
surface->image_view, surface);
}
encode_session_vaapi->nal_writer = grd_nal_writer_new ();
return TRUE;
}
GrdEncodeSessionVaapi *
grd_encode_session_vaapi_new (GrdVkDevice *vk_device,
VADisplay va_display,
gboolean supports_quality_level,
uint32_t source_width,
uint32_t source_height,
uint32_t refresh_rate,
GError **error)
{
g_autoptr (GrdEncodeSessionVaapi) encode_session_vaapi = NULL;
uint8_t level_idc;
encode_session_vaapi = g_object_new (GRD_TYPE_ENCODE_SESSION_VAAPI, NULL);
encode_session_vaapi->vk_device = vk_device;
encode_session_vaapi->va_display = va_display;
encode_session_vaapi->supports_quality_level = supports_quality_level;
encode_session_vaapi->surface_width = grd_get_aligned_size (source_width, 32);
encode_session_vaapi->surface_height = grd_get_aligned_size (source_height, 16);
encode_session_vaapi->refresh_rate = refresh_rate;
if (!create_avc420_encode_session (encode_session_vaapi, error))
return NULL;
level_idc = encode_session_vaapi->level_idc;
g_debug ("[HWAccel.VAAPI] Using level_idc %u.%u for AVC encode session",
level_idc / 10, level_idc - (level_idc / 10) * 10);
return g_steal_pointer (&encode_session_vaapi);
}
static void
grd_encode_session_vaapi_dispose (GObject *object)
{
GrdEncodeSessionVaapi *encode_session_vaapi =
GRD_ENCODE_SESSION_VAAPI (object);
VADisplay va_display = encode_session_vaapi->va_display;
g_assert (g_hash_table_size (encode_session_vaapi->bitstreams) == 0);
g_assert (g_hash_table_size (encode_session_vaapi->pending_frames) == 0);
g_clear_pointer (&encode_session_vaapi->reference_picture, vaapi_picture_free);
g_clear_pointer (&encode_session_vaapi->surfaces, g_hash_table_unref);
g_clear_object (&encode_session_vaapi->nal_writer);
if (encode_session_vaapi->va_context != VA_INVALID_ID)
vaDestroyContext (va_display, encode_session_vaapi->va_context);
if (encode_session_vaapi->va_config != VA_INVALID_ID)
vaDestroyConfig (va_display, encode_session_vaapi->va_config);
G_OBJECT_CLASS (grd_encode_session_vaapi_parent_class)->dispose (object);
}
static void
grd_encode_session_vaapi_finalize (GObject *object)
{
GrdEncodeSessionVaapi *encode_session_vaapi =
GRD_ENCODE_SESSION_VAAPI (object);
g_mutex_clear (&encode_session_vaapi->bitstreams_mutex);
g_mutex_clear (&encode_session_vaapi->pending_frames_mutex);
g_clear_pointer (&encode_session_vaapi->bitstreams, g_hash_table_unref);
g_clear_pointer (&encode_session_vaapi->pending_frames, g_hash_table_unref);
G_OBJECT_CLASS (grd_encode_session_vaapi_parent_class)->finalize (object);
}
static void
grd_encode_session_vaapi_init (GrdEncodeSessionVaapi *encode_session_vaapi)
{
if (grd_get_debug_flags () & GRD_DEBUG_VA_TIMES)
encode_session_vaapi->debug_va_times = TRUE;
encode_session_vaapi->va_config = VA_INVALID_ID;
encode_session_vaapi->va_context = VA_INVALID_ID;
encode_session_vaapi->pending_idr_frame = TRUE;
encode_session_vaapi->surfaces =
g_hash_table_new_full (NULL, NULL,
NULL, (GDestroyNotify) nv12_vkva_surface_free);
encode_session_vaapi->pending_frames = g_hash_table_new (NULL, NULL);
encode_session_vaapi->bitstreams = g_hash_table_new (NULL, NULL);
g_mutex_init (&encode_session_vaapi->pending_frames_mutex);
g_mutex_init (&encode_session_vaapi->bitstreams_mutex);
}
static void
grd_encode_session_vaapi_class_init (GrdEncodeSessionVaapiClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GrdEncodeSessionClass *encode_session_class =
GRD_ENCODE_SESSION_CLASS (klass);
object_class->dispose = grd_encode_session_vaapi_dispose;
object_class->finalize = grd_encode_session_vaapi_finalize;
encode_session_class->get_surface_size =
grd_encode_session_vaapi_get_surface_size;
encode_session_class->get_image_views =
grd_encode_session_vaapi_get_image_views;
encode_session_class->has_pending_frames =
grd_encode_session_vaapi_has_pending_frames;
encode_session_class->encode_frame =
grd_encode_session_vaapi_encode_frame;
encode_session_class->lock_bitstream =
grd_encode_session_vaapi_lock_bitstream;
encode_session_class->unlock_bitstream =
grd_encode_session_vaapi_unlock_bitstream;
}