Search code examples
delphiheader-filesdelphi-xe4yuv

Convert c-header function to Delphi function to pass YUV image


I need to translate a C header of an image processing SDK containing this function:

#define SDK_API __declspec(dllimport)
SDK_API   BOOL   WINAPI SetSourceYUVJ420(HANDLE Display, BYTE **YUV420P, int *LineSize, int srcWidth, int srcHeight);

This function is used to pass a YUVJ420 frame to the SDK.

In my code, the frame is stored in a PAVPicture record (defined in the FFVCL component library), with:

data: array[0..7] of pbyte;
linesize: array[0..7] of integer;

After decoding a video frame, FFVCL triggers an event in which the frame is available as APicture of type PAVPicture.

I translated like so:

function  SetSourceYUVJ420(Display: UIntPtr; YUV420P: Pointer; LineSize: Pointer; srcWidth, srcHeight: integer): boolean stdcall; external 'SDK.DLL' name '_SetSourceYUVJ420@20';

And used it like so:

SetSourceYUVJ420(Display, @APicture.data[0], @APicture.linesize[0], W, H);

.. but this way I'm missing some pointers/addresses.

The SDK documentation is Chinglish and out of date. I do have a piece of C example, which is using FFMpeg for decoding, and when a frame is finished parts of pFrame (type AVFrame) are passed to the SDK:

unsigned WINAPI MYTest() {
   AVFormatContext *pFormatCtx;
   unsigned int    i, videoStream;
   AVCodecContext  *pCodecCtx;
   AVCodec         *pCodec;
   AVFrame         *pFrame = NULL;
   AVPacket        packet;
   int             frameFinished;

   av_register_all();

   pFormatCtx =  avformat_alloc_context();
   avformat_open_input(&pFormatCtx, "..\\..\\images\\Test.mp4",NULL,NULL);
   av_find_stream_info(pFormatCtx);

   videoStream=-1;
   for (i=0; i < pFormatCtx->nb_streams; i++) {
      if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
         videoStream = i;
         break;
      }
   }

   // Get a pointer to the codec context for the video stream
   pCodecCtx = pFormatCtx->streams[videoStream]->codec;
   pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
   // Open codec
   avcodec_open2(pCodecCtx, pCodec,0) < 0;

   // Allocate video frame
   pFrame = av_frame_alloc();

   // Read frames and save first five frames to disk
   i = 0;

   BOOL fSetBuffer = FALSE;
   BOOL f = FALSE;
   while (av_read_frame(pFormatCtx, &packet) >= 0 && !fTerminate) {
      // Is this a packet from the video stream?
      if(packet.stream_index==videoStream) {
         // Decode video frame
          avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

          if ( frameFinished ) {
            // This function told SDK, new video imcoming, with video format is YUV420
             SetSourceYUVJ420(gDisplay, pFrame->data, pFrame->linesize, pCodecCtx->width, pCodecCtx->height);
         }
    }

     // Free the packet that was allocated by av_read_frame
     av_free_packet(&packet);
  }
  // Free the YUV frame
  av_free(pFrame);
  // Close the codec
  avcodec_close(pCodecCtx);
  // Close the video file
  avformat_close_input(&pFormatCtx);

  return TRUE;
}

This is the declaration of AVFrame (taken from frame.h from FFMPeg):

typedef struct AVFrame {
#define AV_NUM_DATA_POINTERS 8
    /**
     * pointer to the picture/channel planes.
     * This might be different from the first allocated byte
     *
     * Some decoders access areas outside 0,0 - width,height, please
     * see avcodec_align_dimensions2(). Some filters and swscale can read
     * up to 16 bytes beyond the planes, if these filters are to be used,
     * then 16 extra bytes must be allocated.
     */
    uint8_t *data[AV_NUM_DATA_POINTERS];

    /**
     * For video, size in bytes of each picture line.
     * For audio, size in bytes of each plane.
     *
     * For audio, only linesize[0] may be set. For planar audio, each channel
     * plane must be the same size.
     *
     * For video the linesizes should be multiplies of the CPUs alignment
     * preference, this is 16 or 32 for modern desktop CPUs.
     * Some code requires such alignment other code can be slower without
     * correct alignment, for yet other it makes no difference.
     *
     * @note The linesize may be larger than the size of usable data -- there
     * may be extra padding present for performance reasons.
     */
    int linesize[AV_NUM_DATA_POINTERS];
<SNIP>
} AVFrame;

Questions:

  • how to correctly translate this C function definition
  • how to call this function using the provided FFVCL type PAVPicture

Solution

  • There's a lot of detail missing here. I'll try to steer you in the right direction, but please do not take this answer as being definitive. In order to write binary interop code you really do need a full and clear specification of the binary interface. You may need to do some more digging to get to the bottom of this.

    First of all, let's look at the HANDLE type. That could be the Win32 type HANDLE, in which case it would translate as THandle. More likely it is a type defined by the library in question. Perhaps it is best translated as Pointer or NativeInt. I will assume the latter.

    The BYTE** parameter is an array of BYTE*. This appears to be allocated by the caller. You will probably translate this into Delphi as PPByte. That is pointer to pointer to Byte.

    The next parameter is LineSize, types as int*. This is an array of int. So again the literal translation would be PInteger, pointer to Integer.

    The final two parameters are simple integers.

    So the function will be declared as:

    function SetSourceYUVJ420(
      Display: NativeInt; 
      YUV420P: PPByte; 
      LineSize: PInteger; 
      srcWidth: Integer;
      srcHeight: integer
    ): LongBool; stdcall; external 'SDK.DLL' name '_SetSourceYUVJ420@20';
    

    You will also need to translate the struct. It goes like this:

    type
      TAVFrame = record
        data: array [0..7] of PByte;
        linesize: array [0..7] of Integer;
      end;
      PAVFrame = ^TAVFrame;
    

    Clearly your code will need to get hold of the display handle. I cannot tell how you are to go about doing that. Presumably you already know how to do that. Likewise you'll need to create a frame with a call to av_frame_alloc. Again, I can only assume that you already know how to do that.

    So, assuming you have the following variables correctly initialised:

    var
      Display: NativeInt;
      Frame: PAVFrame;
    

    Then the call would go like this:

    if not SetSourceYUVJ420(Display, @Frame.data[0], @Frame.linesize[0], W, H) then
      .... handle error
    

    Judging from the C code you show, there are a lot of things that need to be done in order to call this function. You will need everything to be correct in order for the function to work. That is, if your code does not work, the problem could perfectly be in the code that we cannot see, the code that is not included in your question.