1. 기준

「기존 소프트웨어보다 더 좋지는 못할망정, 더 나쁘지는 않아야 한다.」 이 판의 기준이다. 반디캠(화면 녹화), OBS(화면 녹화), Windows 녹음기(음성 녹음)의 기본 기능을 표로 늘어놓고, 빠진 것을 하나씩 채웠다. 넣지 않은 것은 이유를 적었다.

기능반디캠OBS녹음기v1.0.0.1v1.0.0.2
특정 창 녹화(따라가기)○○-×○ EnumWindows + 매 프레임 창 위치
자기 창이 영상에 안 찍힘○○-×(트레이로 숨김)○ SetWindowDisplayAffinity
녹화 중 작은 제어 막대○△(독)-×○ 미니 막대, 끌어서 이동
시작 전 초 읽기○×××○ 0·3·5초
정해진 시간 뒤 자동 정지○×××○ 10~120분
마우스 클릭 효과○×-×○ 노란 고리
스크린샷○○-×○ Ctrl+Alt+S, 같은 이름 규칙
하드웨어 인코더○○-×○ NVENC·QSV·AMF 자동 탐지
디스크 여유 확인○○-×○ 500MB 미만이면 시작 거부
녹화 중 파일 크기 표시○○-×○
최근 결과 목록 유지○-○×(끄면 사라짐)○ 설정에 20개
소리 비트레이트○○○×(160k 고정)○ 96~256k
영역 크기 프리셋○×-×○ HD·FHD·480
표식(marker) 파일로 저장××△(앱 안)○○
녹화+녹음 한 프로그램×××○○
웹캠 덧붙이기○○-×× 카메라가 없어 시험할 수 없음
방송·장면 전환×○-×× 목적 밖
녹화 중 그리기(펜)○×-×× 다음 판

2. 창 녹화

「모니터」와 「영역」 사이에 「창」이 생겼다. Windows 의 EnumWindows 로 제목이 있고 보이는 최상위 창을 모으고, DwmGetWindowAttribute(EXTENDED_FRAME_BOUNDS) 로 그림자를 뺀 실제 사각형을 얻는다. 시작할 때 창 크기를 출력 크기로 고정하고, 프레임마다 창의 지금 위치를 다시 물어 그 자리를 찍는다. 인코더는 크기가 바뀌면 안 되므로 창이 작아지면 주변이 함께 찍히고 커지면 왼쪽 위부터 고정 크기만 찍힌다. 창이 화면 밖으로 나가면 사각형을 안으로 밀고, 최소화되거나 닫히면 예외를 내서 VideoWriter 가 직전 프레임을 다시 쓴다.

class FollowSource:
    def _place(self):
        r = self.provider()                 # 지금 창 사각형. 없으면 None
        if r is None: raise RuntimeError("창이 보이지 않는다")
        left = max(b.left, min(r.left, b.left + b.width - self.rect.width))   # 화면 안으로
        ...
창 따라가기
realtest 가 사각형을 1초마다 (0,0)→(300,200)→(700,400) 으로 옮기며 찍은 세 프레임. 자리마다 다른 화면이 잡혔다.

3. 자기 창을 영상에서 빼기와 미니 막대

1판은 녹화가 시작되면 창을 트레이로 숨겼다. 그러면 정지하려고 트레이를 찾아야 한다. 이번에는 SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE) 를 건다. Windows 10 2004 부터 있는 기능으로, 창은 모니터에는 보이지만 어떤 화면 캡처에도 찍히지 않는다. 주 창과 미니 막대 둘 다 이것을 건다. 미니 막대는 시간·파일 크기·일시정지·표식·정지·창 보기가 든 작은 막대다. 끌어서 옮길 수 있고 늘 위에 있다.

미니 막대
exe 가 wine 에서 찍은 프레임의 오른쪽 아래. wine 은 캡처 제외를 지원하지 않아 막대가 찍혔다. 실제 Windows 10 2004 이상에서는 안 찍힌다.

4. 나머지

v1.0.0.2 녹화 모드
v1.0.0.2 녹화 모드. 원천에 「창」, 화질에 클릭 강조·인코더, 저장에 미니 막대·시작 전·자동 정지·스크린샷·여유 공간.

5. 느린 PC 에서 일시정지 직후 프레임이 더 들어가던 결함

wine 에서 realtest 를 돌리니 「일시정지 중 프레임 안 늘어남」이 7장 차이로 실패했다. wine 의 화면 캡처는 한 장에 100ms 넘게 걸린다. 캡처 스레드는 늦은 만큼 같은 프레임을 다시 보내 박자를 맞추는데(1판 4-2), 그 채우기 고리가 일시정지 신호를 안 봤다. 일시정지를 누른 뒤에도 밀린 박자를 최대 fps 장까지 채우고 나서야 멈췄다. 빠른 PC 에서는 밀린 박자가 0~1 이라 드러나지 않는다.

for _ in range(fill):
    if self._pause_ev.is_set() or self._stop_ev.is_set():
        break                              # 채우다가도 일시정지면 그만
    pipe.write(frame); tick += 1

같은 자리에서 하나를 더 정했다. 밀린 박자가 3초(fps × 3)를 넘으면 그 PC 는 이 설정을 못 따라가는 것이다. 무한히 채우면 파일만 커지고 실시간과도 어긋난다. 넘친 만큼은 버리고 시계를 다시 맞추되, ticks_lost 에 세어 두었다가 끝날 때 상태줄에 「PC 가 못 따라가 N초를 놓쳤다. 초당 프레임이나 출력 크기를 낮추면 좋다」고 알린다.

6. 첫 시작이 5초 멈추던 것

하드웨어 인코더 확인은 ffmpeg 를 세 번 띄운다. 처음엔 시작 단추 안에서 했더니 wine 에서 단추가 5초 멈췄고, 초 읽기가 그 뒤에야 시작됐다. 실제 PC 에서도 1~3초는 멈춘다. 창이 뜨자마자 뒤 스레드에서 확인하게 옮기고, 시작 때 아직 안 끝났으면 1초만 기다리고 CPU 인코더로 간다. 다음 시작부터는 결과가 있다.

7. 시험

enginetest  [1]~[6] 이전 판 그대로 + [7] 클릭 고리 · 인코더 인수 · 하드웨어 탐지 · 창 따라가기(옮김·화면 밖·사라짐) · 파일 크기 · 96k
            클라우드 Linux 전부 통과 / Windows Python(wine) 전부 통과
realtest    스크린샷 → 3초 초 읽기 → 시작 → 미니 막대 → 표식 → 미니 막대로 일시정지·재개 → 자동 정지(1.5초로 앞당김)
            → 결과 검증 → 창 따라가기 3위치       클라우드 전부 통과 / wine 전부 통과
exe         wine 에서 스크린샷 단추 → 초 읽기 → 녹화 → 정지. PNG 와 3.03초 MP4
초 읽기
exe 의 초 읽기. 화면 가운데 「1」.

8. 남은 일