diff options
Diffstat (limited to 'decode.c')
-rw-r--r-- | decode.c | 419 |
1 files changed, 419 insertions, 0 deletions
diff --git a/decode.c b/decode.c new file mode 100644 index 0000000..f51fd83 --- /dev/null +++ b/decode.c @@ -0,0 +1,419 @@ +/* + * Copyright (C) 2023 Mikhail Burakov. This file is part of receiver. + * + * receiver 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 3 of the License, or + * (at your option) any later version. + * + * receiver 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 receiver. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "decode.h" + +#include <errno.h> +#include <fcntl.h> +#include <mfxvideo.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <va/va.h> +#include <va/va_drm.h> +#include <va/va_drmcommon.h> + +#include "frame.h" +#include "util.h" + +struct Surface { + VASurfaceID surface_id; + mfxFrameInfo frame_info; + struct Frame* frame; + bool locked; +}; + +struct DecodeContext { + int drm_fd; + VADisplay display; + mfxSession session; + mfxFrameAllocator allocator; + bool initialized; + + uint32_t packet_size; + uint8_t* packet_data; + uint32_t packet_alloc; + uint32_t packet_offset; + struct Surface** sufaces; + struct Frame* decoded; +}; + +static void SurfaceDestroy(struct Surface*** psurfaces) { + if (!psurfaces || !*psurfaces) return; + for (struct Surface** surfaces = *psurfaces; *surfaces; surfaces++) { + if ((*surfaces)->frame) FrameDestroy(&(*surfaces)->frame); + free(*surfaces); + } + free(*psurfaces); + *psurfaces = NULL; +} + +static struct Frame* ExportFrame(VADisplay display, VASurfaceID surface_id) { + VADRMPRIMESurfaceDescriptor prime; + VAStatus status = vaExportSurfaceHandle( + display, surface_id, VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2, + VA_EXPORT_SURFACE_WRITE_ONLY | VA_EXPORT_SURFACE_COMPOSED_LAYERS, &prime); + if (status != VA_STATUS_SUCCESS) { + LOG("Failed to export vaapi surface (%d)", status); + return NULL; + } + + struct FramePlane planes[prime.layers[0].num_planes]; + for (size_t i = 0; i < LENGTH(planes); i++) { + planes[i] = (struct FramePlane){ + .dmabuf_fd = prime.objects[prime.layers[0].object_index[i]].fd, + .pitch = prime.layers[0].pitch[i], + .offset = prime.layers[0].offset[i], + .modifier = + prime.objects[prime.layers[0].object_index[i]].drm_format_modifier, + }; + } + + struct Frame* frame = FrameCreate(prime.width, prime.height, prime.fourcc, + prime.layers[0].num_planes, planes); + if (!frame) LOG("Failed to create frame"); + for (size_t i = prime.num_objects; i; i--) close(prime.objects[i - 1].fd); + return frame; +} + +static mfxStatus OnAllocatorAlloc(mfxHDL pthis, mfxFrameAllocRequest* request, + mfxFrameAllocResponse* response) { + LOG("%s(AllocId=%u, NumFrameSuggested=%u)", __func__, request->AllocId, + request->NumFrameSuggested); + if (request->Info.FourCC != MFX_FOURCC_NV12) { + LOG("Allocation of %.4s surfaces is not supported", + (const char*)&request->Info.FourCC); + return MFX_ERR_UNSUPPORTED; + } + if (request->Info.ChromaFormat != MFX_CHROMAFORMAT_YUV420) { + LOG("Chroma format %u is not supported", request->Info.ChromaFormat); + return MFX_ERR_UNSUPPORTED; + } + + struct AUTO(Surface)** surfaces = + calloc(request->NumFrameSuggested + 1, sizeof(struct Surface*)); + if (!surfaces) { + LOG("Failed to allocate surfaces storage (%s)", strerror(errno)); + return MFX_ERR_MEMORY_ALLOC; + } + for (size_t i = 0; i < request->NumFrameSuggested; i++) { + surfaces[i] = calloc(1, sizeof(struct Surface)); + if (!surfaces[i]) { + LOG("Failed to allocate surface (%s)", strerror(errno)); + return MFX_ERR_MEMORY_ALLOC; + } + } + + VASurfaceID surface_ids[request->NumFrameSuggested]; + struct DecodeContext* decode_context = pthis; + VASurfaceAttrib attrib_list[] = { + {.type = VASurfaceAttribPixelFormat, + .value.type = VAGenericValueTypeInteger, + .value.value.i = VA_FOURCC_NV12}, + {.type = VASurfaceAttribUsageHint, + .value.type = VAGenericValueTypeInteger, + .value.value.i = VA_SURFACE_ATTRIB_USAGE_HINT_DECODER | + VA_SURFACE_ATTRIB_USAGE_HINT_EXPORT}, + }; + VAStatus status = vaCreateSurfaces( + decode_context->display, VA_RT_FORMAT_YUV420, request->Info.Width, + request->Info.Height, surface_ids, request->NumFrameSuggested, + attrib_list, LENGTH(attrib_list)); + if (status != VA_STATUS_SUCCESS) { + LOG("Failed to allocate surfaces (%d)", status); + return MFX_ERR_MEMORY_ALLOC; + } + + for (size_t i = 0; i < request->NumFrameSuggested; i++) { + surfaces[i]->surface_id = surface_ids[i]; + surfaces[i]->frame_info = request->Info; + } + + // mburakov: Separate loop for frames to ensure proper cleanup in destructor. + for (size_t i = 0; i < request->NumFrameSuggested; i++) { + surfaces[i]->frame = + ExportFrame(decode_context->display, surfaces[i]->surface_id); + if (!surfaces[i]->frame) { + LOG("Failed to export frame"); + return MFX_ERR_MEMORY_ALLOC; + } + } + + decode_context->sufaces = RELEASE(surfaces); + *response = (mfxFrameAllocResponse){ + .AllocId = request->AllocId, + .mids = (void**)decode_context->sufaces, + .NumFrameActual = request->NumFrameSuggested, + }; + return MFX_ERR_NONE; +} + +static mfxStatus OnAllocatorGetHDL(mfxHDL pthis, mfxMemId mid, mfxHDL* handle) { + (void)pthis; + struct Surface* surface = mid; + *handle = &surface->surface_id; + return MFX_ERR_NONE; +} + +static mfxStatus OnAllocatorFree(mfxHDL pthis, + mfxFrameAllocResponse* response) { + LOG("%s(AllocId=%u)", __func__, response->AllocId); + VASurfaceID surface_ids[response->NumFrameActual]; + struct Surface** surfaces = (struct Surface**)response->mids; + for (size_t i = 0; i < response->NumFrameActual; i++) + surface_ids[i] = surfaces[i]->surface_id; + struct DecodeContext* decode_context = pthis; + vaDestroySurfaces(decode_context->display, surface_ids, + response->NumFrameActual); + SurfaceDestroy(&decode_context->sufaces); + return MFX_ERR_NONE; +} + +struct DecodeContext* DecodeContextCreate(void) { + struct AUTO(DecodeContext)* decode_context = + malloc(sizeof(struct DecodeContext)); + if (!decode_context) { + LOG("Failed to allocate decode context (%s)", strerror(errno)); + return NULL; + } + *decode_context = (struct DecodeContext){ + .drm_fd = -1, + .allocator.pthis = decode_context, + .allocator.Alloc = OnAllocatorAlloc, + .allocator.GetHDL = OnAllocatorGetHDL, + .allocator.Free = OnAllocatorFree, + }; + + decode_context->drm_fd = open("/dev/dri/renderD128", O_RDWR); + if (decode_context->drm_fd == -1) { + LOG("Failed to open render node (%s)", strerror(errno)); + return NULL; + } + + decode_context->display = vaGetDisplayDRM(decode_context->drm_fd); + if (!decode_context->display) { + LOG("Failed to get vaapi display (%s)", strerror(errno)); + return NULL; + } + int major, minor; + VAStatus st = vaInitialize(decode_context->display, &major, &minor); + if (st != VA_STATUS_SUCCESS) { + LOG("Failed to init vaapi (%d)", st); + return NULL; + } + + LOG("Initialized vaapi %d.%d", major, minor); + mfxStatus status = MFXInit(MFX_IMPL_HARDWARE, NULL, &decode_context->session); + if (status != MFX_ERR_NONE) { + LOG("Failed to init mfx session (%d)", status); + return NULL; + } + status = MFXVideoCORE_SetHandle( + decode_context->session, MFX_HANDLE_VA_DISPLAY, decode_context->display); + if (status != MFX_ERR_NONE) { + LOG("Failed to set mfx session display (%d)", status); + return NULL; + } + status = MFXVideoCORE_SetFrameAllocator(decode_context->session, + &decode_context->allocator); + if (status != MFX_ERR_NONE) { + LOG("Failed to set frame allocator (%d)", status); + return NULL; + } + return RELEASE(decode_context); +} + +static bool InitializeDecoder(struct DecodeContext* decode_context, + mfxBitstream* bitstream) { + mfxVideoParam video_param = { + .mfx.CodecId = MFX_CODEC_AVC, + }; + mfxStatus status = MFXVideoDECODE_DecodeHeader(decode_context->session, + bitstream, &video_param); + switch (status) { + case MFX_ERR_NONE: + break; + case MFX_ERR_MORE_DATA: + return true; + default: + LOG("Failed to parse decode header (%d)", status); + return false; + } + + video_param.AsyncDepth = 1; + video_param.mfx.DecodedOrder = 1; + video_param.IOPattern = MFX_IOPATTERN_OUT_VIDEO_MEMORY; + status = + MFXVideoDECODE_Query(decode_context->session, &video_param, &video_param); + if (status != MFX_ERR_NONE) { + LOG("Failed to query decode (%d)", status); + return false; + } + + status = MFXVideoDECODE_Init(decode_context->session, &video_param); + if (status != MFX_ERR_NONE) { + LOG("Failed to init decode (%d)", status); + return false; + } + decode_context->initialized = true; + return true; +} + +static bool ReadSomePacketData(struct DecodeContext* decode_context, int fd) { +again:; + void* target; + size_t size; + if (!decode_context->packet_size) { + target = &decode_context->packet_size; + size = sizeof(decode_context->packet_size); + } else { + target = decode_context->packet_data + decode_context->packet_offset; + size = decode_context->packet_size - decode_context->packet_offset; + } + ssize_t result = read(fd, target, size); + switch (result) { + case -1: + if (errno == EINTR) goto again; + LOG("Failed to read packet data (%s)", strerror(errno)); + return false; + case 0: + LOG("File descriptor was closed"); + return false; + default: + break; + } + if (target != &decode_context->packet_size) { + decode_context->packet_offset += result; + return true; + } + if (result != (ssize_t)size) { + LOG("Failed to read complete packet size"); + return false; + } + if (decode_context->packet_size > decode_context->packet_alloc) { + uint32_t packet_alloc = decode_context->packet_size; + uint8_t* packet_data = malloc(packet_alloc); + if (!packet_data) { + LOG("Failed to reallocate packet data (%s)", strerror(errno)); + return false; + } + free(decode_context->packet_data); + decode_context->packet_data = packet_data; + decode_context->packet_alloc = packet_alloc; + } + return true; +} + +static struct Surface* GetFreeSurface(struct DecodeContext* decode_context) { + struct Surface** psurface = decode_context->sufaces; + for (; *psurface && (*psurface)->locked; psurface++) + ; + (*psurface)->locked = true; + return *psurface; +} + +static void UnlockAllSurfaces(struct DecodeContext* decode_context, + const struct Surface* keep_locked) { + struct Surface** psurface = decode_context->sufaces; + for (; *psurface; psurface++) { + if (*psurface != keep_locked) (*psurface)->locked = false; + } +} + +bool DecodeContextDecode(struct DecodeContext* decode_context, int fd) { + if (!ReadSomePacketData(decode_context, fd)) { + LOG("Failed to read some packet data"); + return false; + } + + if (decode_context->packet_size != decode_context->packet_offset) { + // mburakov: Full frame has to be available for decoding. + return true; + } + mfxBitstream bitstream = { + .DecodeTimeStamp = MFX_TIMESTAMP_UNKNOWN, + .TimeStamp = (mfxU64)MFX_TIMESTAMP_UNKNOWN, + .Data = decode_context->packet_data, + .DataLength = decode_context->packet_size, + .MaxLength = decode_context->packet_size, + .DataFlag = MFX_BITSTREAM_COMPLETE_FRAME, + }; + decode_context->packet_size = 0; + decode_context->packet_offset = 0; + + if (!decode_context->initialized) { + if (!InitializeDecoder(decode_context, &bitstream)) { + LOG("Failed to initialize decoder"); + return false; + } + // mburakov: Initialization might be postponed. + if (!decode_context->initialized) return true; + } + + for (;;) { + struct Surface* surface = GetFreeSurface(decode_context); + mfxFrameSurface1 surface_work = { + .Info = surface->frame_info, + .Data.MemId = surface, + }; + mfxFrameSurface1* surface_out = NULL; + mfxSyncPoint sync = NULL; + mfxStatus status = + MFXVideoDECODE_DecodeFrameAsync(decode_context->session, &bitstream, + &surface_work, &surface_out, &sync); + switch (status) { + case MFX_ERR_MORE_SURFACE: + continue; + case MFX_ERR_NONE: + break; + case MFX_WRN_VIDEO_PARAM_CHANGED: + continue; + default: + LOG("Failed to decode frame (%d)", status); + return false; + } + + status = + MFXVideoCORE_SyncOperation(decode_context->session, sync, MFX_INFINITE); + if (status != MFX_ERR_NONE) { + LOG("Failed to sync operation (%d)", status); + return false; + } + + surface = surface_out->Data.MemId; + decode_context->decoded = surface->frame; + UnlockAllSurfaces(decode_context, surface); + return true; + } +} + +const struct Frame* DecodeContextGetFrame( + struct DecodeContext* decode_context) { + return decode_context->decoded; +} + +void DecodeContextDestroy(struct DecodeContext** decode_context) { + if (!decode_context || !*decode_context) return; + if ((*decode_context)->packet_data) free((*decode_context)->packet_data); + if ((*decode_context)->session) MFXClose((*decode_context)->session); + if ((*decode_context)->display) vaTerminate((*decode_context)->display); + if ((*decode_context)->drm_fd) close((*decode_context)->drm_fd); + free(*decode_context); + *decode_context = NULL; +} |