# H.264 스트리밍을 JPEG 스크린샷으로 대체했더니 더 잘 작동했다

> Clean Markdown view of GeekNews topic #25301. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=25301](https://news.hada.io/topic?id=25301)
- GeekNews Markdown: [https://news.hada.io/topic/25301.md](https://news.hada.io/topic/25301.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2025-12-24T11:33:02+09:00
- Updated: 2025-12-24T11:33:02+09:00
- Original source: [blog.helix.ml](https://blog.helix.ml/p/we-mass-deployed-15-year-old-screen)
- Points: 1
- Comments: 1

## Topic Body

- **Helix**는 클라우드 상에서 자율 코딩 에이전트가 작동하는 화면을 사용자에게 보여주는 **AI 플랫폼**으로, 안정적인 원격 화면 전송이 핵심임  
- 기업 네트워크의 **UDP 차단과 방화벽 제약**으로 WebRTC 기반 스트리밍이 실패하자, 팀은 **WebSocket 기반 H.264 파이프라인**을 구축했으나 불안정한 Wi-Fi 환경에서 지연이 심각하게 발생함  
- 복잡한 인코딩·디코딩 구조 대신, 단순히 **JPEG 스크린샷을 HTTP로 주기적으로 전송**하는 방식이 훨씬 안정적이고 효율적임을 발견  
- 이 방식은 **대역폭 사용량이 적고**, 손상된 프레임 복구가 필요 없으며, 네트워크 품질에 따라 **자동으로 화질과 프레임 속도를 조정**함  
- 결과적으로 Helix는 **좋은 연결에서는 H.264**, 나쁜 연결에서는 **JPEG 폴링으로 전환하는 하이브리드 구조**를 채택해, 단순하지만 실용적인 원격 스트리밍 시스템을 완성함  

---

### Helix의 스트리밍 문제와 제약
- Helix는 클라우드 샌드박스에서 작동하는 **AI 코딩 에이전트의 화면을 실시간으로 공유**해야 하는 플랫폼  
  - 사용자는 마치 원격 데스크톱처럼 AI가 코드를 작성하는 과정을 시청함  
- 초기에는 WebRTC를 사용했으나, **기업 네트워크의 UDP 차단**으로 연결이 실패함  
  - TURN 서버, STUN/ICE, 커스텀 포트 등은 모두 방화벽 정책에 의해 차단됨  
- 이에 따라 **HTTPS(443 포트)만 사용하는 WebSocket 기반 H.264 스트리밍 파이프라인**을 직접 구현  
  - GStreamer + VA-API로 하드웨어 인코딩, WebCodecs로 브라우저 디코딩  
  - 60fps, 40Mbps, 100ms 미만의 지연을 달성  

### 네트워크 지연과 성능 저하
- 커피숍 등 불안정한 네트워크 환경에서 **영상이 멈추거나 수십 초 지연**되는 문제가 발생  
  - TCP 기반 WebSocket은 패킷 손실 시 프레임이 순차적으로 지연되어 **실시간성이 붕괴**  
- 비트레이트를 낮춰도 지연은 해결되지 않고, 화질만 저하됨  
- 키프레임만 전송하는 방식도 시도했으나, **Moonlight 프로토콜**이 P-프레임을 요구해 실패  

### JPEG 스크린샷 방식의 발견
- 디버깅 중 `/screenshot?format=jpeg&quality=70` 엔드포인트를 호출하자 **즉시 선명한 이미지가 로드**됨  
  - 150KB 크기의 JPEG 한 장이 지연 없이 표시됨  
- 단순히 HTTP 요청을 반복해 스크린샷을 갱신하자 **5fps 수준의 부드러운 화면 갱신**이 가능  
- 결국 복잡한 비디오 파이프라인 대신, **주기적 JPEG 요청(fetch loop)** 방식으로 전환  

### JPEG 방식의 장점
- H.264 대비 주요 비교 항목  
  - **대역폭**: H.264는 40Mbps 고정, JPEG는 100~500Kbps로 변동  
  - **상태 관리**: H.264는 상태 의존적, JPEG는 **완전한 독립 프레임**  
  - **복구성**: H.264는 키프레임 대기 필요, JPEG는 다음 프레임으로 즉시 복구  
  - **복잡도**: H.264는 수개월 개발, JPEG는 `fetch()` 루프 몇 줄로 구현  
- 네트워크 품질이 나쁠수록 단순한 JPEG 방식이 **더 안정적이고 효율적**  

### 하이브리드 전환 구조
- Helix는 두 방식을 **RTT(왕복 지연 시간)** 기준으로 자동 전환  
  1. RTT < 150ms → H.264 스트리밍  
  2. RTT > 150ms → JPEG 폴링  
  3. 연결 복구 시 사용자가 클릭해 재전환  
- 입력 이벤트(키보드·마우스)는 WebSocket으로 계속 전송되어 **상호작용성 유지**  
- 서버는 `{"set_video_enabled": false}` 메시지로 비디오 전송을 중단하고 스크린샷 모드로 전환  

### 전환 불안정(oscillation) 문제와 해결
- 전송 중단 후 WebSocket 트래픽이 줄어들면 지연이 낮아져 **자동으로 다시 비디오 모드로 전환되는 무한 루프** 발생  
- 해결책: 스크린샷 모드 진입 후에는 **사용자 클릭 전까지 고정 유지**  
  - UI에 “대역폭 절약을 위해 비디오 일시 중지됨” 메시지 표시  

### JPEG 지원 문제와 빌드 과정
- Wayland용 스크린샷 도구 **grim**이 Ubuntu 기본 패키지에서 JPEG 지원이 비활성화되어 있음  
  - `grim -t jpeg` 실행 시 “jpeg support disabled” 오류 발생  
- 이를 해결하기 위해 Dockerfile에서 **libjpeg-turbo8-dev를 포함해 grim을 소스에서 직접 빌드**  

### 최종 아키텍처
- **좋은 연결**: 60fps H.264, 하드웨어 가속  
- **나쁜 연결**: 2~10fps JPEG 폴링, 완전한 신뢰성  
- 스크린샷 품질은 전송 시간에 따라 자동 조정  
  - 500ms 초과 시 품질 -10%, 300ms 미만 시 +5%, 최소 2fps 유지  

### 주요 교훈
1. **단순한 해법이 복잡한 시스템보다 낫다** — 3개월의 H.264 개발보다 2시간의 JPEG 해킹이 실용적  
2. **우아한 성능 저하(graceful degradation)** 가 사용자 경험의 핵심  
3. **WebSocket은 입력 전송에 최적**, 영상 전송에는 필수 아님  
4. **Ubuntu 패키지는 기능 누락 가능성** — 필요 시 직접 빌드  
5. **최적화 전 측정 필수** — 복잡한 스트리밍이 유일한 해법은 아님  

### 오픈소스 공개
- Helix는 오픈소스로 제공되며, 핵심 구현은 다음과 같음  
  - `api/cmd/screenshot-server/main.go` — 스크린샷 서버  
  - `MoonlightStreamViewer.tsx` — 적응형 클라이언트 로직  
  - `websocket-stream.ts` — 비디오 전환 제어  
- Helix는 **실제 환경에서도 작동하는 AI 인프라**를 목표로 개발 중임

## Comments



### Comment 48209

- Author: neo
- Created: 2025-12-24T11:33:02+09:00
- Points: 1

###### [Hacker News 의견들](https://news.ycombinator.com/item?id=46367475) 
- 네트워크가 나쁠 때 JPEG이 줄어드는 건 **UDP 때문이 아니라 TCP 구현 방식** 때문임  
  JPEG은 버퍼링이나 혼잡 제어 문제를 해결하지 않음. 아마도 프레임 전송을 최소화하는 구조로 구현했을 가능성이 큼  
  h.264는 JPEG보다 **부호화 효율**이 높음. 동일한 크기라면 h.264 IDR 프레임이 더 좋은 품질을 낼 수 있음  
  근본적인 문제는 **대역폭 추정 부재**임. TCP 환경에서도 초기 대역폭 프로브와 전송 지연 감지를 통해 비트레이트를 조정할 수 있음  
  가능하다면 WebRTC를 쓰는 게 낫고, 방화벽 회피용으로는 WebSocket을 쓰는 게 좋음
  - 기사에 나온 폴링 코드에서는 이전 JPEG 다운로드가 끝나야 다음 요청을 보내는 구조였음. UDP가 없어도 이런 루프는 가능함
  - 아마도 프레임을 완전히 직렬화해서 한 번에 하나씩만 요청하는 구조거나, 매번 새로운 GET 요청으로 새 연결을 여는 방식일 가능성이 큼
  - 흥미로운 점은, **시각적으로 동일한 JPEG**이라도 용량이 10~15% 수준으로 줄어들 수 있다는 것임. 2000년대 후반 웹 성능 최적화 작업을 하며 이런 효율 개선이 매우 보람찼음

- 글의 형식 문제나 LLM 스타일을 제쳐두더라도, 내용 전반이 잘못된 부분이 많음  
  10Mbps면 정적인 화면에는 충분해야 함. 문제는 **인코딩 설정**이 잘못됐거나 인코더 품질이 낮은 것임  
  “키프레임만 보내자”는 접근은 비효율적이며, 대신 **짧은 keyframe 간격**을 설정하면 됨  
  결국 문제는 **TCP 단일 연결로 전체 스트림을 밀어 넣는 구조**임. 이런 상황에 맞춘 DASH 같은 솔루션이 이미 존재함
  - AI가 쓴 글이 왜 상단에 오르는지 이해가 안 됨. 정성 없이 쓴 글을 읽는 건 시간 낭비라는 생각임
  - Apple에서는 DASH가 지원되지 않음. **HLS**가 대안이 될 수 있지만, ffmpeg가 없으면 구현이 매우 힘듦
  - 이 글은 오히려 LLM 스타일이 거의 느껴지지 않음. 근거 없는 비판은 설득력이 없음
  - 기존 도구를 익히는 데 드는 **시간과 비용**이 크기 때문에, “잘못된 재발명”이라도 상황에 따라 더 효율적일 수 있음

- VNC가 1998년부터 해온 방식을 참고하면 좋을 것 같음  
  클라이언트 풀 모델을 유지하면서 프레임버퍼를 **타일 단위로 나누고**, 변경된 부분만 전송하는 구조임  
  정적인 코딩 화면에서는 대역폭을 크게 줄일 수 있음. 스크롤 감지도 추가하면 더 효율적일 것임
  - 여러 제안 중 이게 가장 현실적인 출발점 같음. 40Mbps를 기본으로 잡은 건 문제 접근 자체가 잘못된 것 같음
  - 글에서 미숙함이 느껴졌음. 이런 접근이 **오픈소스**로도 가능한지 궁금함
  - [neko 프로젝트](https://github.com/m1k1o/neko)를 먼저 살펴보길 권함. VNC보다 연결 지연과 백프레셔 문제를 훨씬 잘 처리함
  - VNC 방식을 복제하는 게 가장 자연스러운 첫 시도일 것 같음. Moonlight처럼 게임용 저지연 솔루션을 쓰는 건 오히려 부적절함

- 예전에 비디오 인코딩을 다뤄봤는데, 40Mbps는 **블루레이급 품질**임  
  단순한 텍스트 스트리밍에는 과도함. Claude와 대화해본 결과, 30FPS, GOP 2초, 평균 1Mbps 정도면 충분하다는 결론이 나왔음  
  최악의 경우에도 1.2Mbps면 충분히 안정적인 품질을 유지할 수 있음

- 이 글의 핵심 문제는 **h.264 최소 대역폭을 너무 높게 설정**한 것임  
  H.264는 JPEG보다 훨씬 효율적임. 1Mbps부터 시작해 조정했어야 함  
  키프레임만 쓰는 건 오히려 비효율적임
  - 글에서 “10Mbps로 낮췄더니 30초 지연이 생겼다”고 했지만, 이는 인코딩 설정 문제일 가능성이 큼
  - JPEG도 버퍼링을 통해 **재생 대기열**을 만들면 끊김 문제를 완화할 수 있음. 요즘 플레이어는 네트워크 품질을 실시간으로 감시함

- 나였다면 완전히 다르게 접근했을 것임  
  10Mbps는 과도하고, YouTube의 코딩 영상은 1080p에서도 **0.6Mbps** 수준임. 충분히 선명함  
  차라리 1fps로 줄이거나 keyframe 간격을 조정하는 게 낫다고 생각함
  - 글의 문체와 논리 전개가 **LLM 냄새**가 남. 코드도 비슷한 수준일 듯함
  - 1fps로는 부족할 수 있음. 모든 프레임을 keyframe으로 만드는 설정이 필요함
  - 하지만 어떤 사람에게는 YouTube 화질도 **참을 수 없을 정도로 거슬릴** 수 있음

- 브라우저로 실시간 비디오를 스트리밍하는 건 정말 고통스러운 일임  
  JPEG 스크린샷이 잘 작동한다면 그대로 두는 게 나음  
  gstreamer나 Moonlight 같은 스택은 **백프레셔와 오류 전파**를 이해하지 못하면 디버깅이 지옥임  
  NVIDIA Video Codec SDK + WebSocket + MediaSource Extensions 조합이 현실적인 대안임  
  하지만 글이 LLM 생성물이라면, 저자는 이런 내부 구조를 이해할 의지가 없을 것 같음
  - 이런 복잡한 시스템을 **단일 목적**으로 다뤄야 할 때는 오히려 LLM이 유용하게 쓰일 수 있음

- 예전에 5초마다 스크린샷을 찍는 프로그램을 썼는데, 하드디스크가 금방 가득 찼음  
  이미지 대부분이 동일하다는 걸 깨닫고, **변경된 부분만 저장하는 알고리즘**을 고민하다가  
  결국 내가 비디오 압축을 재발명하고 있었다는 걸 깨달음  
  ffmpeg 한 줄로 해결했고, 저장 공간을 98% 절약했음

- LLM이 타이핑하는 영상을 40Mbps로 스트리밍한다는 게 **비정상적으로 과도한 대역폭**임  
  - 게다가 60fps로 “컴퓨터가 타이핑하는 장면”을 본다는 것도 이상함. 문제 도메인을 전혀 이해하지 못한 접근 같음

- HN에서 좋은 답변을 얻는 유일한 방법은 **틀린 글을 올리는 것**임  
  틀렸지만 흥미로운 글이야말로 토론을 이끌어내는 완벽한 균형을 맞춘 사례라고 생각함
