/* * 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 #include #include #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 (¤t_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; }