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; +} | 
