Files
grd/grd-encode-session-vaapi.c

1791 lines
62 KiB
C
Raw Permalink Normal View History

2026-02-13 13:06:50 +09:00
/*
* 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;
}