질문과 답변

FFMpeg HW Decoding-> 이미지 출력 문의 날짜:2020-2-19 2:46:49 조회수:86
작성자 : 아크나톤
포인트 : 7
가입일 : 2020-02-17 18:19:31
방문횟수 : 9
글 1개, 댓글 1개
소개 : 여행 좋아하는 개발자입니다.
작성글 보기
쪽지 보내기
아래는 FFMpeg의 Frame을 hw decoding하여 파일로 저장해 주는 예제입니다.

program hw_decode;

  ... 선언문 생략 ....

var
  hw_device_ctx: PAVBufferRef = nil;
  hw_pix_fmt: TAVPixelFormat;
  output_file: THandle = INVALID_HANDLE_VALUE;

function hw_decoder_init(ctx: PAVCodecContext; const type_: TAVHWDeviceType): Integer;
var
  err: Integer;
begin
  err := av_hwdevice_ctx_create(@hw_device_ctx, type_, nil, nil, 0);
  if err < 0 then
  begin
    Writeln(ErrOutput, 'Failed to create specified HW device.');
    Result := err;
    Exit;
  end;
  ctx.hw_device_ctx := av_buffer_ref(hw_device_ctx);

  Result := err;
end;

function get_hw_format(ctx: PAVCodecContext; const pix_fmts: PAVPixelFormat): TAVPixelFormat; cdecl;
var
  p: PAVPixelFormat;
begin
  p := pix_fmts;
  while p^ <> AV_PIX_FMT_NONE do
  begin
    if p^ = hw_pix_fmt then
    begin
      Result := p^;
      EXit;
    end;
    Inc(p);
  end;

  Writeln(ErrOutput, 'Failed to get HW surface format.');
  Result := AV_PIX_FMT_NONE;
end;

function decode_write(avctx: PAVCodecContext; packet: PAVPacket): Integer;
var
  frame, sw_frame: PAVFrame;
  tmp_frame: PAVFrame;
  buffer: PByte;
  size: Integer;
  ret: Integer;
label
  fail;
begin
  frame := nil;
  sw_frame := nil;
  buffer := nil;

  ret := avcodec_send_packet(avctx, packet);
  if ret < 0 then
  begin
    Writeln(ErrOutput, 'Error during decoding');
    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
      begin
        Writeln(ErrOutput, 'Error while decoding');
        goto fail;
      end;

    if TAVPixelFormat(frame.format) = hw_pix_fmt then
      begin
        (* retrieve data from GPU to CPU *)
        ret := av_hwframe_transfer_data(sw_frame, frame, 0);  // 여기서 부터 파일저장이 아닌 iplframe으로 변환이 목적
        if ret < 0 then
          begin
            Writeln(ErrOutput, 'Error transferring the data to system memory');
            goto fail;
          end;
        tmp_frame := sw_frame;
      end
    else
      tmp_frame := frame;

    size := av_image_get_buffer_size(TAVPixelFormat(tmp_frame.format), tmp_frame.width, tmp_frame.height, 1);
    buffer := av_malloc(size);
    if not Assigned(buffer) then
      begin
        Writeln(ErrOutput, 'Can not alloc buffer');
        ret := AVERROR_ENOMEM;
        goto fail;
      end;
    ret := av_image_copy_to_buffer(buffer, size, @tmp_frame.data[0], @tmp_frame.linesize[0], TAVPixelFormat(tmp_frame.format), tmp_frame.width, tmp_frame.height, 1);
    if ret < 0 then
      begin
        Writeln(ErrOutput, 'Can not copy image to buffer');
        goto fail;
      end;

    ret := FileWrite(output_file, buffer^, size);
    if ret < 0 then
      begin
        Writeln(ErrOutput, 'Failed to dump raw data.');
        goto fail;
      end;

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;

function main(): Integer;
var
  pFormatCtx: PAVFormatContext;
  vidx, ret: Integer;
  vStream: PAVStream;
  pVCodecCtx : PAVCodecContext;
  pVCodec: PAVCodec;
  packet: TAVPacket;
  type_: TAVHWDeviceType;
  i: Integer;
  config: PAVCodecHWConfig;
begin
  pFormatCtx := nil;
  pVCodecCtx := nil;
  pVCodec := nil;

  if ParamCount < 3 then
  begin
    Writeln(ErrOutput, Format('Usage: %s ', [ExtractFileName(ParamStr(0))]));
    Result := 1;
    Exit;
  end;

  type_ := av_hwdevice_find_type_by_name(PAnsiChar(AnsiString(ParamStr(1))));
  if type_ = AV_HWDEVICE_TYPE_NONE then
  begin
    Writeln(ErrOutput, Format('Device type %s is not supported.', [ParamStr(1)]));
    Writeln(ErrOutput, 'Available device types:');
    type_ := av_hwdevice_iterate_types(type_);
    while type_ <> AV_HWDEVICE_TYPE_NONE do
    begin
      Write(ErrOutput, Format(' %s', [av_hwdevice_get_type_name(type_)]));
      type_ := av_hwdevice_iterate_types(type_);
    end;
    Writeln(ErrOutput, '');
    Result := 1;
    Exit;
  end;

  (* open the input file *)
  if avformat_open_input(@pFormatCtx, PAnsiChar(AnsiString(ParamStr(2))), nil, nil) <> 0 then
  begin
    Writeln(ErrOutput, Format('Cannot open input file "%s"', [ParamStr(2)]));
    Result := 1;
    Exit;
  end;

  if avformat_find_stream_info(pFormatCtx, nil) < 0 then
  begin
    Writeln(ErrOutput, 'Cannot find input stream information.');
    Result := 1;
    Exit;
  end;

  (* find the video stream information *)
  ret := av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, @pVCodec, 0);
  if ret < 0 then
  begin
    Writeln(ErrOutput, 'Cannot find a video stream in the input file');
    Result := 1;
    Exit;
  end;
  vidx := ret;

  i := 0;
  while True do
  begin
    config := avcodec_get_hw_config(pVCodec, i);
    if not Assigned(config) then
    begin
      Writeln(ErrOutput, Format('Decoder %s does not support device type %s.', [pVCodec.name, av_hwdevice_get_type_name(type_)]));
      Result := 1;
      Exit;
    end;
    if ((config.methods and AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) <> 0) and
      (config.device_type = type_) then
    begin
      hw_pix_fmt := config.pix_fmt;
      Break;
    end;
    Inc(i);
  end;

  pVCodecCtx := avcodec_alloc_context3(pVCodec);
  if not Assigned(pVCodecCtx) then
  begin
    Result := AVERROR_ENOMEM;
    Exit;
  end;

  vStream := PPtrIdx(pFormatCtx.streams, vidx);
  if avcodec_parameters_to_context(pVCodecCtx, vStream.codecpar) < 0 then
  begin
    Result := 1;
    Exit;
  end;

  pVCodecCtx.get_format := get_hw_format;
  av_opt_set_int(pVCodecCtx, 'refcounted_frames', 1, 0);

  if hw_decoder_init(pVCodecCtx, type_) < 0 then
  begin
    Result := 1;
    Exit;
  end;

  ret := avcodec_open2(pVCodecCtx, pVCodec, nil);
  if ret < 0 then
  begin
    Writeln(ErrOutput, Format('Failed to open codec for stream #%u', [vidx]));
    Result := 1;
    Exit;
  end;

  (* 파일 저장하지 않으니까 이 부분 불필요 *)
  output_file := FileCreate(ParamStr(3));
  if output_file = INVALID_HANDLE_VALUE then
  begin
    Writeln(ErrOutput, Format('Could not open %s', [ParamStr(3)]));
    Result := 1;
    Exit;
  end;

  (* actual decoding and dump the raw data *)
  while ret >= 0 do
  begin
    ret := av_read_frame(pFormatCtx, @packet);
    if ret < 0 then
      Break;

    if vidx = packet.stream_index then
      ret := decode_write(pVCodecCtx, @packet);  // 읽은 프레임을 cuda desocing하는 부분

    av_packet_unref(@packet);
  end;

  (* flush the decoder *)
  packet.data := nil;
  packet.size := 0;
  {ret := }decode_write(pVCodecCtx, @packet);
  av_packet_unref(@packet);

  if output_file <> INVALID_HANDLE_VALUE then
    FileClose(output_file);
  avcodec_free_context(@pVCodecCtx);
  avformat_close_input(@pFormatCtx);
  av_buffer_unref(@hw_device_ctx);

  Result := 0;
end;

begin
  try
    ExitCode := main();
  except
    on E: Exception do
      Writeln(ErrOutput, E.ClassName, ': ', E.Message);
  end;
end.

----  예제 소스의 끝  -----

저는 아래와 같이 HW Decoding 없이 AVFrame => pIplImage => Crop또는 필터등 필요한 처리 => Birmap으로 출력
에 대한 정상적인 작동은 확인을 했습니다.
대략 아래의 코드입니다.

  av_dict_set(@optionsDict, 'rtsp_transport', 'tcp', 0);
  av_dict_set(@optionsDict, 'rtsp_flags', 'prefer_tcp', 0);
  av_dict_set(@optionsDict, 'allowed_media_types', 'video', 0);
  av_dict_set(@optionsDict, 'reorder_queue_size', '10', 0);
  av_dict_set(@optionsDict, 'max_delay', '500000', 0);
  av_dict_set(@optionsDict, 'stimeout', '1000000', 0);

  ret := avformat_open_input(@pFormatCtx, PAnsiChar(TH_Url), nil, @optionsDict);
  if ret >= 0 then
    begin
      ret := avformat_find_stream_info(pFormatCtx, @optionsDict);  // @optionsDict
      if ret >= 0 then
        vidx := av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, @pCCodec, 0);
      pVCodecCtx := avcodec_alloc_context3(pVCodec);
      vStream := pFormatCtx.streams[vidx];
      vParam := vStream.codecpar;
      pVCodec := avcodec_find_decoder(vParam.codec_id);
      pVCodecCtx := avcodec_alloc_context3(pVCodec);
      avcodec_parameters_to_context(pVCodecCtx, vParam);
      avcodec_open2(pVCodecCtx, pVCodec, @optionsDict);  // @optionsDict
    end;
  iplframe := cvCreateImage(CvSize(vParam.width, vParam.height), IPL_DEPTH_8U, 3);
  FillChar(linesize, SizeOf(linesize), 0);
  linesize[0] := iplframe^.widthStep;

   while (not Terminated) and (ret >= 0) do
     begin
       frame := av_frame_alloc();
       img_convert_context := sws_getCachedContext(nil, pVCodecCtx^.Width, pVCodecCtx^.Height, Integer(pVCodecCtx^.pix_fmt), pVCodecCtx^.width, pVCodecCtx^.height, Integer(AV_PIX_FMT_BGR24), SWS_BILINEAR, nil, nil, nil);
       ret := av_read_frame(pFormatCtx, @packet);
       if ret >= 0 then
         begin
           if (packet.stream_index = vidx) then
             begin
               avcodec_decode_video2(pVCodecCtx, frame, @frame_finished, @packet);
               if (frame_finished <> 0) then
                 begin
                   sws_scale(img_convert_context, @frame^.data, @frame^.linesize, 0, pVCodecCtx^.Height, @iplframe^.imageData, @linesize);
                   Synchronize(Proc_Disp);  // 스레드이므로 iplframe을 BitMap으로 출력하러 가는 부분
                 end;
             end;
         end;
      end;

위와 같이 HW Decoding 없이 Frame을 iplframe으로 변환하는 것은 정상적으로 작동합니다.
이제 cuda HW Decoding하여 Bitmap으로 출력하고자 하여 맨 위의 소스코드를 참고해 보았습니다.
맨 위의 HW 디코딩 소스를 참고하여 이 소스를 수정하여 get_hw_format 함수에서 디코더의 포맷 hw_pix_fmt이 AV_PIX_FMT_CUDA 인 것까지 확인을 했습니다.

마지막으로 decode_write에서 avcodec_send_packet, avcodec_receive_frame, av_hwframe_transfer_data 함수까지 정상적으로 작동하는 것 까지 확인을 했는데 이제 이 sw_frame을 iplframe으로 변환하는데서 막히네요;;
sws_getContext 와 sws_scale을 사용하니 sws_scale에서 오류가 나는 것 같습니다.
sws_ctx := sws_getContext(sw_frame.width, sw_frame.height, TAVPixelFormat(sw_frame.format), frame.width, frame.height, hw_pix_fmt, SWS_BICUBIC, nil, nil, nil);
sws_scale(sws_ctx, @sw_frame^.data, @sw_frame^.linesize, 0, sw_frame^.Height, @iplframe^.imageData, @linesize);

cuda의 hw_pix_fmt에 대한 이해가 부족해서인지 잘 안되네요;;
조언 부탁드리겠습니다.
감사합니다.
 

목록보기 삭제 수정 신고 스크랩

아크나톤 2월19일 6:59:51  

아마 제가 헛다리 짚고 있는 것일 수도 있겠습니다.
좀전에 소스를 좀 수정해서 실행은 되는데 Video Decoding중이라고 작업관리자에 표시되긴 하지만 실제 CPU 점유율은 큰 변화가 없어 보입니다.
또한 sws_scale은 하드웨어 가속을 지원하지 않는다고 하네요;;

          ret := av_read_frame(pFormatCtx, @packet);
          if ret >= 0 then
            begin
              if (packet.stream_index = vidx) then
                begin
                  ret := decode_packet(pVCodecCtx, @packet);
                  av_packet_unref(@packet);
                end;
            end;
이렇게 읽은 프레임을 decode_packet 이라는 함수로 보냈습니다.
그리고 decode_packet 함수를 이렇게 작성하였는데 잘 돌아는 갑니다.
그리고 H.264 비디오를 로딩해보면 작업관리자에 Video Decoding이라고 표시는 되는데 CPU 점유율은 그다지 큰 변화는 없네요;;
VLC Player 수준은 아니더라도 좀 낮아지길 바랬는데 ㅠ.ㅠ

function TCHThread.decode_packet(avctx: PAVCodecContext; rpacket: PAVPacket): Integer;
var
  frame, sw_frame: PAVFrame;
  tmp_frame: PAVFrame;
  buffer: PByte;
  size: Integer;
  ret: Integer;
  sws_ctx: PSwsContext;
label
  fail;
begin
  frame := nil;
  sw_frame := nil;
  buffer := nil;

  ret := avcodec_send_packet(avctx, rpacket);
  if ret < 0 then
  begin
    Proc_Msg('Error during decoding');
    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
        begin
          Proc_Msg('Error while decoding');
          goto fail;
        end;

      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
              Proc_Msg('Error transferring the data to system memory');
              goto fail;
            end;
          // 아래 TAVPixelFormat(sw_frame.format) 을 확인해보면 AV_PIX_FMT_NV12 이다
          sws_ctx := sws_getContext(sw_Frame.width, sw_frame.height, TAVPixelFormat(sw_frame.format), sw_frame.width, sw_frame.height, AV_PIX_FMT_BGR24, SWS_BILINEAR, nil, nil, nil);
          size := av_image_get_buffer_size(AV_PIX_FMT_BGR24, sw_frame.width, sw_frame.height, 1);
          buffer := av_malloc(size);
          ret := av_image_copy_to_buffer(buffer, size, @sw_frame.data[0], @sw_frame.linesize[0], TAVPixelFormat(sw_frame.format), sw_frame.width, sw_frame.height, 1);
          sws_scale(sws_ctx, @sw_frame^.data, @sw_frame^.linesize, 0, sw_frame^.Height, @iplframe^.imageData, @linesize);
          Synchronize(Proc_Disp);
        end;
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;

OpenCV에서의 하드웨어 가속을 좀 들여다 보러 갑니다요 ㅠ.ㅠ


로그인하셔야 댓글을 달 수 있습니다.