자유롭게 질의 및 응답을 할 수 있는 게시판입니다. 개발자 여러분의 답변이 큰 도움이 됩니다. 
  • 제품설치/등록 오류 문의: 설치/등록 Q&A 이용 (제품 구매 고객 한정)

본 게시판은 개발자들이 자유롭게 질문과 답변을 공유하는 게시판입니다.
* 따라서 최대한 정중하게 질문을 올려 주세요.
* 질문을 상세히 작성해 주실 수록 좋은 답변이 올라 옵니다.
* 다른 분들도 참고할 수 있도록 결과 댓글 필수(또는 감사 댓글)
(결과 댓글을 달지 않는 경우 다음 질문에 대한 답변이 달리지 않는 불이익이 있을 수 있습니다.)
-----------------------------------------------------------------------------------------------
 

안녕하세요~~

날씨는 따듯해 지는데 코로나의 기세는 꺾일 줄 모르네요.

건강 조심하세요~~

 

질문 올립니다.

아래에서 위 두개의 procedure는 맨 아래 함수 decode_packet에서 참조하는 것입니다.

핵심은 decode_packet 함수입니다.

 

FFMpeg의 AVFrame을 TImage에 그리는 것이 목표입니다.

안되는건 아니구요.

비디오를 디코딩하여 마지막으로 프레임을 TImage로 그려주는데 CPU 점유율을 최대한 줄여보고자 합니다.

 

 

procedure cvBuffToFMXBitmap(const SrcData: pByte; const FMXBitmap: TBitmap; i_Width, i_Height : Integer); inline;  // 버퍼를 TImage에 그리는 프로시저

Var

  BitmapData: TBitmapData;

  i : Integer;

  DestData: pByte;

  pf: Integer;

begin

  Assert(Assigned(SrcData) and Assigned(FMXBitmap));

  try

    FMXBitmap.SetSize(i_Width, i_Height);

 

    if FMXBitmap.Map(TMapAccess.Write, BitmapData) then

      begin

        try

          FMXBitmap.Canvas.BeginScene;

          DestData := pByte(BitmapData.Data);

          pf := PixelFormatBytes[FMXBitmap.PixelFormat];

          for i := 0 to BitmapData.Width * BitmapData.Height - 1 do

            begin

              DestData[i * pf + 0] := SrcData[i * 3 + 0];

              DestData[i * pf + 1] := SrcData[i * 3 + 1];

              DestData[i * pf + 2] := SrcData[i * 3 + 2];

              DestData[i * pf + 3] := $FF;

            end;

        finally

          FMXBitmap.Unmap(BitmapData);

        FMXBitmap.Canvas.EndScene;

        end;

      end;

    finally

    end;

end;

 

procedure cvToFMXBitmap(const IpImage: pIplImage; const FMXBitmap: TBitmap); inline;  // iplframe을 TImage에 그리는 프로시저

Var

  BitmapData: TBitmapData;

  i, j : Integer;

  SrcData, DestData: pByte;

  nC: Integer;

  pf: Integer;

begin

  SrcData := nil;

  Assert(Assigned(IpImage) and Assigned(FMXBitmap));

  if (IpImage^.Width > 0) and (IpImage^.Height > 0) and Assigned(IpImage^.imageData) then

    try

      nC := IpImage^.nChannels;

      With IpImage^ do

      begin

        SrcData := AllocMem(Width * Height * nC);

        Move(imageData^, SrcData^, Width * Height * nC);

      end;

 

      FMXBitmap.SetSize(IpImage^.Width, IpImage^.Height);

 

      if FMXBitmap.Map(TMapAccess.Write, BitmapData) then

        try

        FMXBitmap.Canvas.BeginScene;

          DestData := pByte(BitmapData.Data);

          pf := PixelFormatBytes[FMXBitmap.PixelFormat];

          for i := 0 to BitmapData.Width * BitmapData.Height - 1 do

            begin

              DestData[i * pf + 0] := SrcData[i * nC + 0];

              DestData[i * pf + 1] := SrcData[i * nC + 1];

              DestData[i * pf + 2] := SrcData[i * nC + 2];

              DestData[i * pf + 3] := $FF;

            end;

        finally

          FMXBitmap.Unmap(BitmapData);

        FMXBitmap.Canvas.EndScene;

        end;

    finally

      if Assigned(SrcData) then

        FreeMem(SrcData);

    end;

end;

 

// 비디오의 AVFrame을 디코딩하여 TImage에 그리는 함수

function decode_packet(avctx: PAVCodecContext; rpacket: PAVPacket): Integer;

var

  frame, sw_frame: PAVFrame;

  tmp_frame: PAVFrame;

  buffer: PByte;

  size: Integer;

  ret: Integer;

  swsCtx: PSwsContext;

label

  fail;

begin

  frame := nil;

  sw_frame := nil;

  buffer := nil;

 

  ret := avcodec_send_packet(avCtx, rpacket);  // avCtx는 AVCodecContext, rpacket는 프레임을 읽은 패킷정보

  if ret < 0 then

    begin

      Result := ret;

      Exit;

    end;

 

  while True do

    begin

      frame := av_frame_alloc();

      sw_frame := av_frame_alloc();

      ret := avcodec_receive_frame(avCtx, frame);

      if (ret = AVERROR_EAGAIN) or (ret = AVERROR_EOF) then

        begin

          av_frame_free(@frame);

          av_frame_free(@sw_frame);

          Result := 0;

          Exit;

        end

      else if ret < 0 then

        goto fail;

 

      if TAVPixelFormat(frame.format) = hw_pix_fmt then

        begin

          (* retrieve data from GPU to CPU *)

          ret := av_hwframe_transfer_data(sw_frame, frame, 0);

          if ret < 0 then

            begin

              goto fail;

            end;

          tmp_frame := sw_frame;

        end

      else

      tmp_frame := frame;

 

//1) --------------------------------------------------------------------------------------------------

      swsCtx := sws_getCachedContext(nil, tmp_frame^.Width, tmp_frame^.Height, integer(tmp_frame^.format), tmp_frame^.Width, tmp_frame^.Height, Integer(AV_PIX_FMT_BGR24), SWS_BILINEAR, nil, nil, nil);

      sws_scale(swsCtx, @tmp_frame^.data, @tmp_frame^.linesize, 0, tmp_frame^.Height, @iplframe^.imageData, @linesize);

      cvToFMXBitmap(iplframe, Image1.Bitmap);  // pIplImage 인 iplframe을 TImage에 그려주는 프로시저 CPU점유율 안습

// -----------------------------------------------------------------------------------------------------

 

// 2) -------------------------------------------------------------------------------------------------

      size := av_image_get_buffer_size(AV_PIX_FMT_BGR24, tmp_frame^.width, tmp_frame^.height, 1);

      buffer := av_malloc(size);

      av_image_copy_to_buffer(buffer, size, @tmp_frame.data[0], @tmp_frame.linesize[0], AV_PIX_FMT_BGR24, tmp_frame.width, tmp_frame.height, 1);

      av_image_fill_arrays(@iplframe^.imageData, @linesize, buffer, AV_PIX_FMT_BGR24, avCtx.width, avCtx.height, 1);  // buffer를 바로 TImage에 그려주면 안되는가? 굳이 iplframe에 넣고 다시 TImage에 그려야 하는가?

      cvToFMXBitmap(iplframe, Image1.Bitmap);

// -----------------------------------------------------------------------------------------------------

 

fail:

      av_frame_free(@frame);

      av_frame_free(@sw_frame);

      av_freep(@buffer);

      if ret < 0 then

        begin

          Result := ret;

          Exit;

        end;

    end;

    Result := 0;

end;

 

※ 핵심 목표는 AVFrame의 프레임을 TImage에 나타내고 싶습니다.

그래서 위의 1) 과 2) 의 두가지 방법이 있는데 

1)의 경우는 cvToFMXBitmap(iplframe, Image1.Bitmap); 하기전까지는 CPU점유율이 아주 낮습니다.

2)의 경우는 그냥 pbyte형인 buffer를 TImage에 바로 그려주면 더 좋겠는데 일단 iplframe(OpenCV의 pIplImage)에 밀어넣고 이것을 다시 cvToFMXBitmap(iplframe, Image1.Bitmap); 하여

  Timage에 그립니다.

두가지의 경우 모두 cvToFMXBitmap(iplframe, Image1.Bitmap); 에서 CPU점유율이 높다는 점입니다.

당연할 수도 있습니다. 동영상의 사이즈가 1280정도로 크기 때문이라 볼 수 있는데 저는 AVFrame -> TImage에 가장 빠르고 CPU에 부담을 주지 않는 방법을 고민중입니다.

 

2)와 같이 av_image_copy_to_buffer까지만 한 상태에서 buffer를 TImage에 그려보았는데 아래와 같이 영상이 여러개가 나오더군요.

맨위의 cvBuffToFMXBitmap 프로시저를 사용해 cvBuffToFMXBitmap(buffer, Image1.Bitmap, sw_frame.width, sw_frame.height); 이렇게 해봤는데 영상이 하나의 프레임이 아닌 여러개가 나오더라구요.

버퍼를 TImage에 그리면.png

 

 부분만 해결이 되어도 CPU 부담은 많이 적어질 거 같은데 도통 모르겠습니다 ㅠ.ㅠ

 

AVFrame 또는 pIplImage 또는 buffer를 TImage에 그리는 가장 빠른 방법이 어떤게 있을까요?

감사합니다.