웹에서 HEVC with Alpha 영상을 사용할 때 고려해야 할 것들

웹 페이지에 투명 배경을 가진 3D 애니메이션을 넣어야 하는 상황이 있었다.

영상은 HEVC with Alpha로 만들어졌고, 약 20초 길이에 4MB, 8MB, 10MB 세 가지 용량으로 제공됐다. 모바일용 영상의 해상도는 가로 1280px 정도였다.

처음에는 단순히 이런 고민이었다.

4MB, 8MB, 10MB 중 웹에서 사용하기 적절한 용량은 어느 정도일까?

그런데 조금 파고들어 보니 영상 파일의 용량만 비교해서 결정할 문제는 아니었다.

네트워크, 비트레이트, 브라우저의 Range Request, 디코딩, 메모리, GPU 사용량까지 서로 다른 문제를 구분해서 볼 필요가 있었다.

먼저 투명 영상 지원 여부를 확인해야 한다

일반 HEVC를 재생할 수 있다는 것과 HEVC with Alpha의 투명도를 표시할 수 있다는 것은 다르다. Apple은 iOS 13과 macOS Catalina의 Safari부터 HEVC with Alpha를 지원한다고 안내했다. Alpha를 지원하지 않는 HEVC 플레이어는 기본 영상만 재생하고 투명도 정보를 무시할 수 있다.

따라서 브라우저의 일반 HEVC 지원 여부나 canPlayType() 결과만으로 투명 재생까지 보장해서는 안 된다. 실제 대상 OS와 브라우저에서 배경이 비치는지 확인하고, 지원하지 않는 환경에는 투명 WebM 등 검증된 대체 영상이나 정적 이미지를 제공해야 한다. 아래 용량과 성능 비교는 우선 해당 영상의 투명 재생이 가능한 환경을 전제로 한다.

파일 용량보다 먼저 봐야 하는 비트레이트

20초짜리 영상의 파일 크기가 각각 다음과 같다고 해보자.

  • 4MB
  • 8MB
  • 10MB

대략적인 평균 비트레이트는 다음 공식으로 구할 수 있다.

파일 크기(MB) × 8 ÷ 영상 길이(초)

여기서는 1MB를 1,000,000바이트로 계산한다. 파일 전체 크기를 사용하므로 오디오와 컨테이너 정보까지 포함한 평균이며, 영상 스트림만의 비트레이트와는 조금 다를 수 있다.

따라서 20초 영상이라면:

파일 크기 평균 비트레이트
4MB 약 1.6 Mbps
8MB 약 3.2 Mbps
10MB 약 4 Mbps

여기서 Mbps는 Megabits per second, 즉 초당 몇 메가비트의 데이터를 소비하는지를 의미한다.

예를 들어 10MB 영상의 평균 비트레이트가 4Mbps라면, 영상을 실시간으로 끊김 없이 공급하려면 평균적으로 초당 약 4Mb 이상의 데이터를 받아올 수 있어야 한다.

물론 실제 스트리밍은 버퍼가 있기 때문에 정확히 4Mbps를 항상 유지해야 한다는 뜻은 아니다.

예를 들어 인터넷 다운로드 속도가 안정적으로 9Mbps라면 10MB 영상 전체의 데이터 크기는 약 80Mb이므로:

80Mb ÷ 9Mbps ≈ 8.9초

정도면 전체 데이터를 받을 수 있다.

영상은 20초이기 때문에 네트워크가 재생 속도보다 상당히 빠른 셈이다.

이 가정에서는 10MB 영상을 재생 중에 받아올 수 있다. 다만 이 계산만으로 웹 페이지에 적절한 용량이라고 결론 내릴 수는 없다. 초기 재생 지연, 다른 리소스와의 대역폭 경쟁, 사용자의 데이터 소비도 함께 봐야 한다.

하지만 여기서 중요한 것은 평균 인터넷 속도만 보고 판단하면 안 된다는 것이다.

모바일 환경에서는 이동 중이거나 지하철, 건물 내부, 혼잡한 기지국 환경 등에서 순간적인 네트워크 속도가 크게 떨어질 수 있다.

같은 해상도와 프레임률에서 더 작은 파일은 대역폭 요구와 데이터 소비를 낮출 수 있다. 다만 화질이 요구를 충족하는지 먼저 비교해야 한다. 국가별 평균 속도나 파일 용량만으로 모바일 안정성을 확정할 수는 없다.

CDN이 모든 네트워크 문제를 해결해주지는 않는다

영상 파일을 AWS S3에 올리고 CloudFront 같은 CDN을 연결하면 사용자 요청이 지연 시간을 고려해 선택된 엣지 로케이션으로 전달된다. 요청한 영상이 캐시에 있다면 원본 서버까지 가지 않고 받을 수 있다.

이는 상당히 중요한 최적화다.

멀리 있는 원본 서버에 직접 요청하는 것보다 지연과 TTFB를 줄일 수 있다. 다만 캐시 미스가 나면 원본 조회가 필요하므로 항상 같은 개선 폭을 보장하지는 않는다.

하지만 CDN이 사용자의 마지막 네트워크 구간까지 빠르게 만들어주는 것은 아니다.

사용자가 실제로 3Mbps LTE 환경에 있다면 CDN을 사용한다고 해서 그 연결이 갑자기 30Mbps가 되지는 않는다.

즉 CDN은:

  • 물리적 서버 거리
  • 국제망 구간
  • 서버 응답 지연
  • 캐시 적중률

같은 문제를 개선하지만,

사용자의 실제 모바일 네트워크 품질 자체까지 해결하는 것은 아니다.

웹 브라우저는 8MB 파일을 처음부터 전부 받을까?

웹의 <video> 요소는 서버가 HTTP Range Request를 지원한다면 영상 파일의 특정 구간만 요청할 수 있다.

브라우저 요청에는 다음과 같은 헤더가 들어갈 수 있다.

1
Range: bytes=0-1048575

서버나 CDN이 이를 지원하면:

1
2
206 Partial Content
Content-Range: bytes 0-1048575/8388608

같은 응답을 받을 수 있다. 이 헤더 예시의 전체 크기 8,388,608 bytes는 8MiB이며, 앞의 비트레이트 표에서 사용한 8MB와는 다른 단위다.

S3의 GetObjectCloudFront는 Range 요청을 지원한다. 유효한 Range 요청과 원본 응답 조건이 맞으면 별도 스트리밍 서버 없이 부분 응답을 전달할 수 있다. 다만 Range는 다운로드 단위를 제어하는 기능이며, 네트워크 속도에 따라 낮은 화질로 바꾸는 적응형 비트레이트 스트리밍과는 다르다.

다만 206 Partial Content라는 응답 코드만 보고 브라우저가 항상 작은 조각만 다운로드한다고 단정해서는 안 된다.

예를 들어 다음 요청도 가능하다.

1
Range: bytes=0-

이는 0바이트부터 파일 끝까지 요청한다는 의미이므로 결과적으로 전체 파일을 전달할 수도 있다.

따라서 실제 동작을 확인하려면 Chrome DevTools의 Network 탭에서 다음을 확인하는 것이 좋다.

  • Request의 Range
  • Response의 Content-Range
  • Transferred 크기
  • 추가 Range 요청 발생 여부

다운로드된 영상은 네트워크가 끊겨도 재생될까?

가능하다.

비트레이트는 네트워크가 반드시 계속 연결되어 있어야 한다는 의미가 아니다.

브라우저가 이미 영상 데이터를 충분히 받아두었다면 네트워크가 끊겨도 해당 버퍼 범위까지는 계속 재생할 수 있다.

개념적으로 보면 구조는 다음과 같다.

Network → Media Buffer → Decoder → Frame → 화면

네트워크는 버퍼를 채우고, 디코더는 그 버퍼의 데이터를 계속 소비한다.

따라서 재생에 필요한 데이터 전체가 버퍼나 캐시에 남아 있다면 인터넷 연결이 끊겨도 20초 영상을 끝까지 재생할 수 있다. 한 번 다운로드했다는 사실만으로 데이터가 계속 보존되는 것은 아니다. 브라우저가 버퍼를 정리할 수 있으므로 실제 재생 가능한 구간은 video.buffered로 확인해야 한다.

반대로 4MB 정도만 받아둔 상태에서 인터넷이 끊기면 해당 데이터로 재생 가능한 구간까지만 재생되고 이후에는 멈출 수 있다.

8MB 영상은 메모리에서도 8MB일까?

여기서 가장 많이 헷갈릴 수 있는 부분이 있다.

영상 파일 크기와 재생 중 메모리 사용량은 전혀 다른 값이다.

HEVC 영상의 8MB는 강하게 압축된 데이터 크기다.

브라우저가 이를 화면에 보여주려면 압축된 데이터를 실제 픽셀 정보로 복원해야 한다.

이 과정을 디코딩(decoding) 이라고 한다.

예를 들어 1280×720 RGBA 한 프레임을 단순 계산하면:

1280 × 720 × 4 bytes = 3,686,400 bytes ≈ 3.69MB ≈ 3.52MiB

정도다. 이는 픽셀당 4바이트인 RGBA 버퍼 하나를 가정한 계산이다. 실제 디코더는 YUV 평면과 별도 Alpha 평면 등을 사용할 수 있고, 비트 깊이·메모리 정렬·버퍼 수에 따라 사용량도 달라진다.

즉 8MB짜리 영상 전체보다 디코딩된 몇 장의 프레임이 더 많은 메모리를 사용할 수도 있다.

물론 브라우저가 20초 영상 전체를 한 번에 프레임으로 변환해서 메모리에 올려두는 것은 아니다.

대략 다음과 같이 동작한다.

압축 데이터 읽기 → 필요한 프레임 디코딩 → 몇 개 프레임 버퍼 유지 → 화면 표시 → 오래된 프레임 정리

HEVC는 프레임 간 압축을 사용하므로 다른 참조 프레임을 함께 보관해야 할 수 있다. 화면에 표시되는 순서상 뒤에 있는 프레임을 참조할 수도 있지만, 그 참조 프레임은 디코딩 순서상 먼저 처리된다.

따라서 실제 메모리에는:

  • 압축 데이터 버퍼
  • 디코딩된 프레임
  • Reference Frame
  • Decoder 내부 버퍼
  • GPU Texture

등이 동시에 존재할 수 있다.

그렇다면 8MB를 4MB로 줄이면 메모리도 절반이 될까?

대부분 그렇지 않다.

예를 들어 다음 두 영상이 있다고 하자.

  • 1280×720 / 30fps / 4MB
  • 1280×720 / 30fps / 8MB

파일 크기는 두 배 차이가 난다.

하지만 디코딩한 뒤에는 둘 다 결국 1280×720 크기의 프레임이 만들어진다.

따라서 재생 중 디코딩 프레임이나 GPU Texture가 사용하는 메모리는 큰 차이가 나지 않을 수 있다.

4MB 영상의 주요 장점은 오히려:

  • 다운로드 시간 감소
  • 네트워크 트래픽 감소
  • 버퍼를 더 빠르게 확보
  • 데이터 사용량 감소

쪽에 있다.

메모리를 줄이는 것이 목적이라면 동시에 유지하는 디코더와 프레임 버퍼 수, 해상도, 픽셀 형식을 먼저 확인해야 한다. FPS는 초당 디코딩·합성 작업량에 직접 영향을 주지만, FPS를 절반으로 낮춘다고 상주 메모리도 절반이 되는 것은 아니다. 이 요소들의 영향에 고정된 순위를 매기기보다는 실제 기기에서 측정해야 한다.

HEVC with Alpha는 조금 더 까다롭다

이번 영상은 일반적인 불투명 영상이 아니라 배경이 투명한 HEVC with Alpha 영상이었다.

일반 영상도 색 변환이나 확대·축소, 화면 합성 과정을 거칠 수 있다. Alpha 영상은 여기에 투명도 정보를 디코딩하고 각 픽셀의 투명도를 배경과 합성하는 처리가 더해진다.

즉:

HEVC Decode → Alpha 처리 → 배경과 Compositing → GPU Rendering

이라는 추가 과정이 들어간다.

특히 3D 애니메이션처럼:

  • 반투명 영역
  • 그림자
  • 글로우
  • 복잡한 움직임

등이 많은 영상에서는 디코딩과 합성 부담을 함께 고려해야 한다. 다만 미리 렌더링된 영상의 그림자나 글로우를 브라우저가 3D 효과로 다시 계산하는 것은 아니다. 표시 면적, 프레임률, 겹치는 레이어 수와 디코딩 경로가 실제 부하에 영향을 준다.

따라서 모바일에서는 일반적인 영상보다 조금 더 보수적으로 운영하는 것이 좋다.

캐러셀에서 영상이 여러 개라면?

실제 페이지에는 이런 영상이 약 10개 있었고 캐러셀 형태로 제공됐다.

다행히 동시에 DOM에 존재하는 <video>는 2개뿐이었다.

이 구조는 영상 10개를 모두 mount해두는 것보다 훨씬 좋다.

예를 들어:

  • 현재 슬라이드 영상
  • 다음 슬라이드 영상

만 유지하고 나머지 영상은 DOM에서 제거한다면 브라우저가 불필요한 Decoder와 GPU Resource를 계속 들고 있을 가능성을 크게 줄일 수 있다.

다만 video element가 2개 존재하는 것과 video 2개가 동시에 재생되는 것은 다른 문제다.

모바일에서는 가능하면:

현재 영상 → Play

다음 영상 → Preload만

구조가 더 안정적이다.

예를 들어:

1
2
3
4
5
6
7
8
9
10
11
12
13
<video
  src="current.mov"
  autoplay
  muted
  playsinline
></video>

<video
  src="next.mov"
  preload="metadata"
  muted
  playsinline
></video>

처럼 운용할 수 있다.

다음 영상의 초기 재생 지연이 실제로 문제가 된다면 preload="auto"도 테스트해볼 수 있다.

preload는 브라우저에 주는 힌트다. metadata는 다음 영상의 재생 프레임까지 준비됐다는 뜻이 아니며, 브라우저는 네트워크·전력 정책에 따라 힌트를 다르게 처리할 수 있다. autoplay, muted, playsinline을 지정해도 자동 재생이 항상 성공하는 것은 아니므로 재생 실패 시 표시할 이미지나 사용자 재생 동작도 준비해야 한다.

DOM에서 제거하면 메모리는 바로 해제될까?

반드시 즉시 해제되는 것은 아니다.

React에서 <video>를 unmount하면 화면의 요소는 제거된다. 다만 JavaScript 참조나 진행 중인 재생·다운로드 상태가 남아 있다면 관련 리소스가 계속 유지될 수 있다.

하지만:

  • 브라우저 캐시
  • Decoder Resource
  • GPU Resource
  • Garbage Collection

등의 이유로 DevTools에서 보는 메모리가 즉시 내려오지 않을 수 있다.

따라서 메모리가 순간적으로 증가한다고 해서 곧바로 메모리 누수라고 판단하면 안 된다. JavaScript 힙 수치만으로 디코더나 GPU 메모리 전체를 파악할 수도 없다.

중요한 것은 반복했을 때 메모리가 안정화되는가다.

다음 숫자는 측정값이나 메모리 한계치가 아닌, 추세를 설명하기 위한 가상 예시다. 자원 사용이 안정화되는 경우에는:

180MB → 230MB → 270MB → 240MB → 270MB

처럼 어느 정도 범위에서 올라갔다 내려갔다 할 수 있다.

반대로:

180MB → 250MB → 320MB → 410MB → 500MB → 600MB

처럼 같은 순회를 반복할 때마다 계속 증가한다면 리소스가 제대로 해제되지 않는지 확인해야 한다. 초기 캐시 축적이나 다른 페이지 작업도 원인일 수 있으므로, 이 모양만으로 누수를 확정하지는 않는다.

필요하다면 unmount 전에 좀 더 적극적으로:

1
2
3
video.pause();
video.removeAttribute('src');
video.load();

를 호출하는 방법도 있다.

이 예시는 src 속성으로 영상을 연결한 경우다. <source> 자식이나 srcObject를 사용한다면 그 연결도 정리해야 한다. 그렇지 않으면 load()가 남아 있는 소스를 다시 선택할 수 있다.

모바일에서는 어떤 문제가 생길 수 있을까?

영상 처리 때문에 메모리와 GPU 사용량이 지나치게 올라가면 모바일에서는 다음과 같은 현상이 나타날 수 있다.

  • 캐러셀 애니메이션 버벅임
  • 스크롤 프레임 드랍
  • 영상 재생 지연
  • 검은 화면
  • 발열 증가
  • 배터리 소비 증가
  • 브라우저의 적극적인 리소스 정리
  • 심한 경우 Safari 탭 리로드

특히 모바일 Safari에서는 페이지가 사용할 수 있는 리소스가 무한하지 않기 때문에 단순히 “최신 iPhone에서는 잘 되니까 괜찮다”고 판단하기보다 실제 기기에서 반복 테스트하는 것이 중요하다.

결국 무엇을 최적화해야 할까?

처음에는 4MB와 8MB 중 어떤 파일을 선택할지가 가장 중요한 문제라고 생각했다.

하지만 정리해보니 최적화 대상은 서로 분리해서 봐야 했다.

네트워크를 줄이고 싶다면

  • 파일 용량
  • 비트레이트
  • CDN
  • HTTP Range
  • preload 전략

을 봐야 한다.

메모리와 렌더링 부하를 줄이고 싶다면

  • 동시에 재생되는 video 개수
  • 해상도
  • FPS
  • Alpha 합성
  • video mount/unmount 전략

이 더 중요하다.

예를 들어 같은 1280px 해상도라면 파일만 8MB에서 4MB로 줄이는 비교와, 실제 동시 재생을 2개에서 1개로 줄이는 비교를 분리하겠다. 후자는 활성 디코더와 합성 작업을 줄일 수 있다는 가설이다. 어느 쪽의 효과가 큰지는 실제 병목과 기기에서 확인해야 한다.

선택 전에 비교할 최소 실험

아래는 이 사례의 측정 결과가 아니라, 4MB·8MB·10MB 중 선택하기 위한 검증 계획이다. 한 번에 여러 조건을 바꾸면 개선 원인을 구분하기 어렵다.

비교 고정할 조건 확인할 것
4MB / 8MB / 10MB 해상도·FPS·화면 크기·동시 재생 수 Alpha 가장자리·그림자 화질, 전송량, 재생 시작과 중단
높은 해상도 / 낮은 해상도 FPS·화면 크기·재생 수, 가능한 한 비슷한 시각적 품질 실제 표시 선명도와 스크롤·캐러셀 부하
2개 재생 / 1개 재생 같은 파일·같은 화면 전환 끊김, 발열, 반복 전환 후 자원 사용 추세
첫 방문 / 재방문 같은 기기·같은 네트워크 브라우저·CDN 캐시가 초기 지연을 가리고 있는가
지원 환경 / 대체 환경 같은 콘텐츠와 배경 실제 투명 표시, 자동 재생 실패 시 이미지·재생 버튼

해상도를 낮추면 보통 압축량도 달라지므로 완벽하게 한 변수만 고정한 실험은 어렵다. 네트워크 효과와 디코딩 효과를 구분하려면 충분히 버퍼된 상태의 재생도 별도로 비교하면 도움이 된다. 다만 이미 다운로드했더라도 버퍼가 계속 보존된다고 가정해서는 안 된다.

모바일에서 “1280px가 필요한가”는 CSS 표시 크기와 기기의 픽셀 비율을 함께 봐야 한다. 가령 가로 320 CSS px, DPR 2라면 가로 640px 소스도 비교 후보가 된다. 이를 무조건 최적값으로 삼기보다 확대·변형과 Alpha 가장자리를 실제 표시 크기에서 확인한다.

반복 순회 횟수와 기기·OS·브라우저 버전을 기록하고 첫 재생, 여러 차례 전환, 잠시 다른 탭으로 갔다가 복귀한 상태를 비교한다. 데스크톱의 네트워크 제한은 느린 연결을 흉내 낼 수 있지만 모바일 디코더·메모리·전력 조건까지 재현하지는 않는다.

preload와 자동 재생은 브라우저 정책의 영향을 받는다. HTML 미디어 규격에 맞게 play()의 실패도 처리하고, 재생할 수 없을 때 다음 동작이 보여야 한다. 장식용 영상이라면 움직임을 줄이려는 사용자에게 정적 이미지로도 같은 내용을 전달하는 방향을 선택할 수 있다.

이 비교에서 화질 차이가 받아들일 만하면 작은 파일을 택한다. 충분히 버퍼된 뒤에도 화면이 끊긴다면 파일 용량만 더 줄이기보다 동시 재생 수·해상도·FPS를 살펴본다. 네트워크에서 기다리는 것과 이미 받은 영상을 그리지 못하는 것을 구분하는 것이 첫 판단이다.

이번 케이스에서 선택한다면

현재 조건은 다음과 같다.

  • HEVC with Alpha
  • 약 20초
  • 모바일 1280px
  • 4MB / 8MB / 10MB 버전
  • 약 10개의 영상
  • 캐러셀
  • 동시에 최대 2개의 <video> 존재
  • S3 + CDN 사용

이 조건이라면 다음 전략을 먼저 적용할 것 같다.

  1. 현재 화면의 영상 하나만 실제로 재생한다.
  2. 다음 영상은 preload만 한다.
  3. 그 외 영상은 DOM에서 제거한다.
  4. 모바일의 실제 표시 크기와 기기 성능을 기준으로 1280px보다 낮은 해상도도 비교한다.
  5. 60fps라면 30fps로 낮췄을 때 체감 차이가 있는지 확인한다.
  6. 4MB와 8MB의 화질을 실제 표시 크기에서 비교한다.
  7. 화질 차이가 거의 없다면 4MB를 사용한다.
  8. 차이가 분명하다면 8MB 버전의 초기 지연과 데이터 소비를 대상 네트워크에서 확인한다.
  9. DevTools와 실제 모바일 기기에서 캐러셀을 반복 실행하며 메모리가 안정화되는지 확인한다.

결국 4MB냐 8MB냐에는 하나의 정답이 없다.

파일 크기는 네트워크 문제이고, 영상 재생 중 발생하는 메모리와 성능 문제는 압축 데이터 버퍼뿐 아니라 디코딩과 렌더링 경로까지 살펴야 한다.

이 둘을 분리해서 생각하면 웹 영상 최적화에서 무엇을 먼저 손봐야 하는지가 훨씬 명확해진다.

압축된 영상의 크기보다 중요한 것은, 브라우저가 그 영상을 재생하기 위해 실제로 어떤 일을 하고 있는지를 이해하는 것이다.

참고