Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 10개 있습니다.)
VC++: 121. DXGI를 이용한 윈도우 화면 캡처 소스 코드(Visual C++)
; https://www.sysnet.pe.kr/2/0/11385

.NET Framework: 705. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드
; https://www.sysnet.pe.kr/2/0/11400

.NET Framework: 706. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력
; https://www.sysnet.pe.kr/2/0/11401

.NET Framework: 712. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력 + OpenCV
; https://www.sysnet.pe.kr/2/0/11407

.NET Framework: 713. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력 + OpenCV (2)
; https://www.sysnet.pe.kr/2/0/11408

.NET Framework: 913. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 라이브러리
; https://www.sysnet.pe.kr/2/0/12238

.NET Framework: 1123. C# - (SharpDX + DXGI) 화면 캡처한 이미지를 빠르게 JPG로 변환하는 방법
; https://www.sysnet.pe.kr/2/0/12889

.NET Framework: 1126. C# - snagit처럼 화면 캡처를 연속으로 수행해 동영상 제작
; https://www.sysnet.pe.kr/2/0/12895

.NET Framework: 1128. C# - 화면 캡처한 이미지를 ffmpeg(FFmpeg.AutoGen)로 동영상 처리
; https://www.sysnet.pe.kr/2/0/12897

.NET Framework: 1152. C# - 화면 캡처한 이미지를 ffmpeg(FFmpeg.AutoGen)로 동영상 처리 (저해상도 현상 해결)
; https://www.sysnet.pe.kr/2/0/12963




C# - snagit처럼 화면 캡처를 연속으로 수행해 동영상 제작

화면 캡처 프로그램인 snagit을 써보시면,

snagit
; https://www.techsmith.com/store/snagit

원하는 범위에서 캡처를 동영상으로 만드는 기능도 제공합니다. 마침(?) 오래전의 질문도 있으니,

c# 으로 화면캡쳐해서 동영상으로 만드는앱을 만들고싶습니다
; https://www.sysnet.pe.kr/3/0/5037

이참에 지난번에 만들어 둔 코드에,

C# - (SharpDX + DXGI) 화면 캡처한 이미지를 빠르게 JPG로 변환하는 방법
; https://www.sysnet.pe.kr/2/0/12889

화면 녹화 기능을 넣어보겠습니다. ^^ 사실, 이에 대한 구현은 아래의 2개 내용을 합치기만 하면 됩니다.

C# - (SharpDX + DXGI) 화면 캡처한 이미지를 빠르게 JPG로 변환하는 방법
; https://www.sysnet.pe.kr/2/0/12889

C# - OpenCvSharp.VideoWriter에 BMP 파일을 1초씩 출력하는 예제
; https://www.sysnet.pe.kr/2/0/12485

"C# - (SharpDX + DXGI) 화면 캡처한 이미지를 빠르게 JPG로 변환하는 방법" 글에서 이미 화면 캡처 이미지를 Mat으로 반환받았으니, "C# - OpenCvSharp.VideoWriter에 BMP 파일을 1초씩 출력하는 예제"에서 소개한 VideoWriter로는 단순히 원하는 fps에 맞게 쓰기 작업을 해주면 되는 것입니다.

즉, 대충 다음과 같은 식으로 하면 구색은 갖춥니다. ^^

private void CaptureLoopMat(DXGIManager manager)
{
    int fps = 30;

    using (Mat src = new Mat(manager.Height, manager.Width, MatType.CV_8UC4))
    using (VideoWriter writer = new VideoWriter("C:\\temp\\test.avi", FourCC.XVID, fps, src.Size(), true))
    {
        while (_exitCapture == false)
        {
            long started = sw.ElapsedMilliseconds;

            if (manager.Capture(src.Data, 1000) == true)
            {
                using (Mat dst = src.CvtColor(ColorConversionCodes.BGRA2BGR))
                {
                    writer.Write(dst);
                }
            }
        }
    }
}

그런데 실제로 위의 코드를 실행해 보면, 당연히 avi 파일에 지정한 30fps, 즉 초당 30개의 이미지를 써야 동영상 재생 프로그램이 현실 시간에 따른 속도로 화면을 보여줄 텐데, 위의 코드에서는 manager.Capture가 반환하는 속도에 의존해 초당 쓰게 될 이미지의 수가 결정되므로 정상적인 속도로 화면 레코딩이 되지 않습니다.




이 문제를 해결하기 위해 우선 Capture의 동작을 이해해야 합니다. 지난 글에도 설명했지만, (Capture 내에서 사용하는) AcquireNextFrame은 캡처 대상이 되는 모니터의 화면에 변화가 있어야 그 이미지를 반환합니다.

따라서, 단순히 CaptureLoopMat에서와 같은 코드를 작성하면 화면 변경이 거의 없는 경우에는 30fps가 요구하는 이미지 수를 못 채울 것이고, Youtube와 같은 사이트를 방문해 동영상을 재생하면 30fps에 가까운 이미지가 VideoWriter에 추가될 수 있습니다. 혹은, 30fps보다 더 빠르게 Capture가 된다면 재생 시에 슬로우 모션이 나옵니다.

결국, Capture 루프에서 몇 장의 이미지가 나오든지에 상관없이 우리는 fps에 맞는 프레임을 VideoWriter에 써야 합니다.

그런데, fps가 참 재미있습니다. 가령 30fps라고 하면 1초에 30장의 이미지를 써야 하는데요, 그렇다면 1000/30 = 33.3333... 과 같이 정수로 딱 떨어지지 않는 시간이 나옵니다. 그래서 만약 33ms마다 1장씩 VideoWriter에 쓰게 된다면 0.3.... 의 시간이 절삭되고, 그것이 누적되면서 나중에는 초 단위까지 영향을 줄 수 있습니다.

제가 영상 관련은 해본 적이 없어서 이런 소수점 이하의 처리를 현업에서는 어떻게 하고 있는지 모르겠습니다. ^^; 그래서 그냥 제 맘대로 만들었습니다. 일단, Capture한 영상을 VideoWriter에 곧바로 쓰지 않고 FPS에 맞는 분량으로, 예를 들어 30fps라면 무조건 1초마다 30장의 이미지를 쓰도록 중간 처리를 하는 FpsWriter를 두었습니다.

private void CaptureLoopMat(DXGIManager manager)
{
    int fps = 30;

    Stopwatch sw = new Stopwatch();
    sw.Start();

    OpenCvSharp.Size frameSize = new OpenCvSharp.Size(manager.Width, manager.Height);
    using (Mat frame = new Mat(frameSize, MatType.CV_8UC4))
    using (VideoWriter writer = new VideoWriter("C:\\temp\\test.avi", FourCC.XVID, fps, frameSize, true))
    using (FpsWriter matBuffer = new FpsWriter(writer, MatType.CV_8UC3))
    {
        while (_exitCapture == false)
        {
            long started = sw.ElapsedMilliseconds;

            if (manager.Capture(frame.Data, 1000) == true)
            {
                Mat current = matBuffer.GetMat();
                Cv2.CvtColor(frame, current, ColorConversionCodes.BGRA2BGR);
                matBuffer.Write(current);
            }

            int elapsed = (int)(sw.ElapsedMilliseconds - started);
            matBuffer.TrySleep(elapsed);
        }
    }
}

FpsWriter.Write에서는 캡처 루프에 영향을 주지 않도록 BufferBlock<T>를 이용해 이미지 처리 스레드를 별도로 두어 전달하는 식입니다.

public void Write(Mat current)
{
    _lastCaptured = new FrameItem { TimeStamp = timeGetTime(), Mat = current };
    _queue.SendAsync(_lastCaptured); // BufferBlock에 담기만 하고 메서드를 벗어남.
}

실제적인 처리는 비동기 처리를 위한 루프에서 담당하고,

private async Task ProcessFrame()
{
    // ...[생략]...

    while (true)
    {
        FrameItem frame = await _queue.ReceiveAsync();
        if (frame.TimeStamp == 0)
        {
            break;
        }

        // ...[생략]...

        AddFrame(...[생략]...);
    }
}

AddFrame 내에서는 1초마다, 그동안 쌓인 이미지 데이터를 VideoWriter에 쓰는 작업을 합니다.

internal void AddFrame(Queue<FrameItem> frameQueue, ref FrameItem lastFrameOfPreviousTime,
    FrameItem frame, ref uint started)
{
    frameQueue.Enqueue(frame);

    // ...[생략]...

    uint diff = frame.TimeStamp - started;
    if (diff >= 1000) // 쌓인 프레임의 처음과 마지막의 프레임 시간 차이가 1초 넘으면, Queue에 쌓여 있던 프레임을 파일에 fps에 맞게 기록 
    {
        uint seconds = (uint)(diff / 1000);
        MakeFramesPerTime(frameQueue, ref lastFrameOfPreviousTime, started, seconds, 1000);
        started += (seconds * 1000);
    }
}

그리고 MakeFramesPerTime은 몇 장의 이미지가 들어왔든지에 상관없이 (fps가 30인 경우) 1초에 30장의 이미지를 VideoWriter에 씁니다. 가령, 1초에 10장의 이미지가 캡처가 되었다면, 33ms마다 그 시점과 가장 가까운 이미지를 선택해 VideoWriter에 씁니다. 예를 들어, 다음과 같은 식으로 캡처 이미지는 불규칙하게 떴을 것이고,

유형 1)
선택 이미지:                *
캡처 이미지:     |   | |||| |   |    |   |||||
fps 시점:                    |

유형 2)
선택 이미지:                        *
캡처 이미지:     |                  |
fps 시점:                      |

유형 3)
선택 이미지:     *
캡처 이미지:     |               
fps 시점:                      |                   |                   |

33ms 구간이 되는 시점이 되면 유형 1의 경우 뒷 이미지가, 유형 2는 앞 이미지가 더 가깝게 캡처가 되었기 때문에 그것을 쓰게 했습니다. 유형 3은 구간 내에 캡처 이미지가 1개뿐인데, 대략 99ms 동안 연속으로 캡처를 못했기 때문에 처음 1개의 이미지를 계속해서 VideoWriter에 출력을 합니다. 그러니까, 이런 상황이 되면 동영상 재생 시 화면이 끊기는 듯한 움직임을 보이게 됩니다.

그나저나, 다른 프로그램들도 저렇게 동영상 처리를 하는지는 모르겠습니다. (혹시, 정식 fps 처리를 알고 계신 분은 덧글 부탁드립니다. ^^)




그런데, 저런 식으로 만들다 보니 의외의 문제가 하나 있는데 바로 메모리 사용량입니다. 1920 * 1080 * RGB 캡처 이미지 한 장이 6MB 정도 됩니다. 30fps로 캡처가 된다고 가정하면 1초 분량의 이미지를 쓰기 위해 Queue에 보관한 Frame의 용량이 180MB 정도 됩니다. 즉, 1초마다 180MB의 메모리가 끊임없이 할당/해제를 반복하는 것입니다.

그래서 그 부분에 대한 처리를 DisposableObjectPool을 사용해,

C# - DefaultObjectPool의 IDisposable 개체에 대한 풀링 문제
; https://www.sysnet.pe.kr/2/0/12894

부하를 조금이라도 낮춰봤습니다. 딱히 그 외에 더 설명할 것이 없군요, ^^ (소스 코드가 포함된) 첨부 파일을 다운로드/빌드/실행할 수 있습니다. (윈도우에 포커스가 있는 상태에서) Ctrl + C를 누르면 캡처를 시작하고 Ctrl + X가 눌릴 때까지 동영상으로 c:\temp\test.avi 파일에 저장합니다.




참고로, fps가 소숫점을 가질 수 있으므로 이런 경우에는 구현이 더 복잡해집니다. 이에 관해서는 다음의 글을 참고하세요.

23.976프레임과 24프레임
; https://blog.naver.com/soundpf/220510307023




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 12/31/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2023-01-05 11시45분
[나그네] 선생님의 호기심에 박수를 보냅니다. ^^
제가 가지고 있던 여러가지 욕망(?)들이 가끔이 실현이 되는것들을 보고는 그 열정에 존경심이 우러나옵니다.
[guest]

... 16  17  18  19  20  21  22  23  24  25  26  27  [28]  29  30  ...
NoWriterDateCnt.TitleFile(s)
12920정성태1/14/20226394오류 유형: 786. Camtasia - An error occurred with the camera: Failed to Add Video Sampler.
12919정성태1/13/20226220Windows: 199. Host Network Service (HNS)에 의해서 점유되는 포트
12918정성태1/13/20226412Linux: 47. WSL - shell script에서 설정한 환경 변수가 스크립트 실행 후 반영되지 않는 문제
12917정성태1/12/20225658오류 유형: 785. C# - The type or namespace name '...' could not be found (are you missing a using directive or an assembly reference?)
12916정성태1/12/20225387오류 유형: 784. TFS - One or more source control bindings for this solution are not valid and are listed below.
12915정성태1/11/20225669오류 유형: 783. Visual Studio - We didn't find any interpreters
12914정성태1/11/20227568VS.NET IDE: 172. 비주얼 스튜디오 2022의 파이선 개발 환경 지원
12913정성태1/11/20228090.NET Framework: 1133. C# - byte * (바이트 포인터)를 FileStream으로 쓰는 방법 [1]
12912정성태1/11/20228729개발 환경 구성: 623. ffmpeg.exe를 사용해 비디오 파일의 이미지를 PGM(Portable Gray Map) 파일 포맷으로 출력하는 방법 [1]
12911정성태1/11/20226053VS.NET IDE: 171. 비주얼 스튜디오 - 더 이상 만들 수 없는 "ASP.NET Core 3.1 Web Application (.NET Framework)" 프로젝트
12910정성태1/10/20226537제니퍼 .NET: 30. 제니퍼 닷넷 적용 사례 (8) - CPU high와 DB 쿼리 성능에 문제가 함께 있는 사이트
12909정성태1/10/20227933오류 유형: 782. Visual Studio 2022 설치 시 "Couldn't install Microsoft.VisualCpp.Redist.14.Latest"
12908정성태1/10/20225772.NET Framework: 1132. C# - ref/out 매개변수의 IL 코드 처리
12907정성태1/9/20226233오류 유형: 781. (youtube-dl.exe) 실행 시 "This app can't run on your PC" / "Access is denied." 오류 발생
12906정성태1/9/20226843.NET Framework: 1131. C# - 네임스페이스까지 동일한 타입을 2개의 DLL에서 제공하는 경우 충돌을 우회하는 방법 [1]파일 다운로드1
12905정성태1/8/20226503오류 유형: 780. Could not load file or assembly 'Microsoft.VisualStudio.TextTemplating.VSHost.15.0, Version=16.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies.
12904정성태1/8/20228538개발 환경 구성: 623. Visual Studio 2022 빌드 환경을 위한 github Actions 설정 [1]
12903정성태1/7/20227145.NET Framework: 1130. C# - ELEMENT_TYPE_INTERNAL 유형의 사용 예
12902정성태1/7/20227173오류 유형: 779. SQL 서버 로그인 에러 - provider: Shared Memory Provider, error: 0 - No process is on the other end of the pipe.
12901정성태1/5/20227271오류 유형: 778. C# - .NET 5+에서 warning CA1416: This call site is reachable on all platforms. '...' is only supported on: 'windows' 경고 발생
12900정성태1/5/20228917개발 환경 구성: 622. vcpkg로 ffmpeg를 빌드하는 경우 생성될 구성 요소 제어하는 방법
12899정성태1/3/20228400개발 환경 구성: 621. windbg에서 python 스크립트 실행하는 방법 - pykd (2)
12898정성태1/2/20228953.NET Framework: 1129. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 인코딩 예제(encode_video.c) [1]파일 다운로드1
12897정성태1/2/20227826.NET Framework: 1128. C# - 화면 캡처한 이미지를 ffmpeg(FFmpeg.AutoGen)로 동영상 처리 [4]파일 다운로드1
12896정성태1/1/202210704.NET Framework: 1127. C# - FFmpeg.AutoGen 라이브러리를 이용한 기본 프로젝트 구성파일 다운로드1
12895정성태12/31/20219210.NET Framework: 1126. C# - snagit처럼 화면 캡처를 연속으로 수행해 동영상 제작 [1]파일 다운로드1
... 16  17  18  19  20  21  22  23  24  25  26  27  [28]  29  30  ...