자유롭게 질의 및 응답을 할 수 있는 게시판입니다. 개발자 여러분의 답변이 큰 도움이 됩니다.
- 제품설치/등록 오류 문의: 설치/등록 Q&A 이용 (제품 구매 고객 한정)
Delphi FFMpeg 디코딩된 AVFrame을 최대한 빨리 TImage에 그리는 방법 질문.
2020.02.22 02:09
본 게시판은 개발자들이 자유롭게 질문과 답변을 공유하는 게시판입니다.
* 따라서 최대한 정중하게 질문을 올려 주세요.
* 질문을 상세히 작성해 주실 수록 좋은 답변이 올라 옵니다.
* 다른 분들도 참고할 수 있도록 결과 댓글 필수(또는 감사 댓글)
(결과 댓글을 달지 않는 경우 다음 질문에 대한 답변이 달리지 않는 불이익이 있을 수 있습니다.)
-----------------------------------------------------------------------------------------------
안녕하세요~~
날씨는 따듯해 지는데 코로나의 기세는 꺾일 줄 모르네요.
건강 조심하세요~~
질문 올립니다.
아래에서 위 두개의 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); 이렇게 해봤는데 영상이 하나의 프레임이 아닌 여러개가 나오더라구요.
부분만 해결이 되어도 CPU 부담은 많이 적어질 거 같은데 도통 모르겠습니다 ㅠ.ㅠ
AVFrame 또는 pIplImage 또는 buffer를 TImage에 그리는 가장 빠른 방법이 어떤게 있을까요?
감사합니다.
댓글 4
-
험프리
2020.02.27 11:41
-
아크나톤
2020.02.28 02:54
감사합니다.
FLASHAVCONVERT는 예전에 문의 했었는데 하나의 폼에 비디오 채널 동시 8개 이상은 현재 지원 안된다며 향후 채널 수가 풀리는 버전이 구현되면 웹사이트에 게시하겠다는 전달을 받은 상태입니다.
예전에 Parallel-For에 대한 질문을 올린 적이 있는데 그땐 비디오의 프레임을 병렬처리하여 받아오는 테스트를 했었는데 이미지를 그리는데 이 방법을 한번 써봐야겠네요. 감사합니다.
-
happy
2020.02.28 12:12
위의 코드는 한 눈에 봐도... 픽셀 단위로 카피하고 있으니
처리속도가 느리고 cpu 만 잡아 먹을 수 밖에 없는 코드에요.
픽셀 단위가 아닌 라인 단위로 카피하든가 아니면
소스가 RGB24 픽셀형식이면 메모리 상에 선형적으로 배치되므로
RGB24 픽셀포맷의 타겟 비트맵의 Data 에 한번에 memcopy로 다이렉트로 카피한 후
비트맵을 TImage의 비트맵에 assign 해주면 간단한 겁니다.
-
happy
2020.02.28 12:16
속도가 가장 빠른 방법이 필요하다면 FMX 프레임웍 쓰지 말고
DirectX 를 직접 이용해서 Overlay Surface를 생성해서
영상보드가 DirectX Surface의 버퍼에 Bus Mastering DMA로
하드웨어적으로 다이렉트로 영상데이타를 전송하도록 하는 거고
WM_SIZE 등의 윈도우 메세지를 오버라이드 해서 DirectX Surface의
창 크기와 위치만 변경해 주도록 하면 되죠.
BUS Mastering DMA로 하드웨어적인 전송을 사용하므로
CPU 이용하지 않고 다이렉트로 전송 합니다.
UHD 4K TV 수신카드 등이 사용하는 방식 임.
Delphi FFMpeg 디코딩된 AVFrame을 최대한 빨리 TImage에 그리는 방법 질문.
2020.02.22 02:09
본 게시판은 개발자들이 자유롭게 질문과 답변을 공유하는 게시판입니다.
* 따라서 최대한 정중하게 질문을 올려 주세요.
* 질문을 상세히 작성해 주실 수록 좋은 답변이 올라 옵니다.
* 다른 분들도 참고할 수 있도록 결과 댓글 필수(또는 감사 댓글)
(결과 댓글을 달지 않는 경우 다음 질문에 대한 답변이 달리지 않는 불이익이 있을 수 있습니다.)
-----------------------------------------------------------------------------------------------
안녕하세요~~
날씨는 따듯해 지는데 코로나의 기세는 꺾일 줄 모르네요.
건강 조심하세요~~
질문 올립니다.
아래에서 위 두개의 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); 이렇게 해봤는데 영상이 하나의 프레임이 아닌 여러개가 나오더라구요.
부분만 해결이 되어도 CPU 부담은 많이 적어질 거 같은데 도통 모르겠습니다 ㅠ.ㅠ
AVFrame 또는 pIplImage 또는 buffer를 TImage에 그리는 가장 빠른 방법이 어떤게 있을까요?
감사합니다.
댓글 4
-
험프리
2020.02.27 11:41
-
아크나톤
2020.02.28 02:54
감사합니다.
FLASHAVCONVERT는 예전에 문의 했었는데 하나의 폼에 비디오 채널 동시 8개 이상은 현재 지원 안된다며 향후 채널 수가 풀리는 버전이 구현되면 웹사이트에 게시하겠다는 전달을 받은 상태입니다.
예전에 Parallel-For에 대한 질문을 올린 적이 있는데 그땐 비디오의 프레임을 병렬처리하여 받아오는 테스트를 했었는데 이미지를 그리는데 이 방법을 한번 써봐야겠네요. 감사합니다.
-
happy
2020.02.28 12:12
위의 코드는 한 눈에 봐도... 픽셀 단위로 카피하고 있으니
처리속도가 느리고 cpu 만 잡아 먹을 수 밖에 없는 코드에요.
픽셀 단위가 아닌 라인 단위로 카피하든가 아니면
소스가 RGB24 픽셀형식이면 메모리 상에 선형적으로 배치되므로
RGB24 픽셀포맷의 타겟 비트맵의 Data 에 한번에 memcopy로 다이렉트로 카피한 후
비트맵을 TImage의 비트맵에 assign 해주면 간단한 겁니다.
-
happy
2020.02.28 12:16
속도가 가장 빠른 방법이 필요하다면 FMX 프레임웍 쓰지 말고
DirectX 를 직접 이용해서 Overlay Surface를 생성해서
영상보드가 DirectX Surface의 버퍼에 Bus Mastering DMA로
하드웨어적으로 다이렉트로 영상데이타를 전송하도록 하는 거고
WM_SIZE 등의 윈도우 메세지를 오버라이드 해서 DirectX Surface의
창 크기와 위치만 변경해 주도록 하면 되죠.
BUS Mastering DMA로 하드웨어적인 전송을 사용하므로
CPU 이용하지 않고 다이렉트로 전송 합니다.
UHD 4K TV 수신카드 등이 사용하는 방식 임.
재미있는 작업을 진행하시네요^^
말씀하신데로 코로나19가 극성입니다. 몸조리 잘하시길 바라겠습니다.
질문주신 내용의 요점은 특정버퍼의 데이터를 파이어몽키 TImage에 빠르게 표현하고 싶은 것으로 이해됩니다.
저도 FFMpeg을 직접 다뤄본게 언제인지 모르겠습니다.(사실 거의 래핑된 라이브러리 위주로만 사용했던 것 같습니다.)
아무래도 직접적인 해결방안보다는 2가지 정도의 의견을 드릴수 있을 것 같습니다.
1) 더블버퍼 형태로 Bitmap에 기록 후 Bitmap을 Image에 할당하는 것은 어떨까요?
저도 파이어몽키의 TImage와 TBitmap을 자세히 뜯어보지는 못했습니다. TImage의 Bitmap에 직접 데이터를 기로갛는 경우 이벤트등이 연결되어 영향을 줄수도 있지 않을까 생각됩니다.
그리고 UI요소와 의존성을 끊으면 쓰래드 또는 패러럴 라이브러리 등을 통해 병렬처리도 가능할 것으로 보입니다.
http://docwiki.embarcadero.com/RADStudio/Rio/en/Using_the_Parallel_Programming_Library
2) 두번째 안은 파이어몽키 캔버스의 성능 개선을 시도해보는 것은 어떨까요?
그렇다면 다음 링크의 내용이 도움이 되지 않을까 싶어 안내드립니다.
파이어몽키 캔버스 클래스 및 앱 속도를 높이기 위한 버그 수정 - https://parnassus.co/firemonkey-canvas-classes-and-a-bugfix-to-speed-up-your-apps/
추가로, 써드파티 기술을 검토해 보시는 것은 어떨까요?
http://www.flashavconverter.com/content/ffmpeg-converter-firemonkey
아무래도 관련 기술에 대한 노하우가 있을 수도 있을것 같습니다.^^
직접적인 도움을 드리지 못해 안타깝습니다. 부디 좋은 결과 있길 기원합니다.
감사합니다.