[실전 미디어 12] 아이폰 영상이 커지는 이유

지난 편에서 동영상 용량을 해상도와 비트레이트 상한으로 잡는 큰 틀을 잡았다. 그런데 그 논의를 뒤흔드는 특수한 사례가 있었다. 아이폰에서 올라오는 동영상이었다. 사진 앱에서 분명 몇 메가로 표시되던 짧은 영상이, 업로드하려는 순간 몇 배로 불어나 용량 제한에 걸려 즉시 거부됐다. 원본보다 커져서 들어오는 이 기이한 현상의 원인을 파고드니, 아이폰의 코덱 정책과 브라우저의 실시간 재인코딩이 얽혀 있었다. 이번 편은 그 추적기다.


1. 8메가 영상이 갑자기 거부됐다

증상은 구체적이었다. 어떤 사용자가 아이폰으로 찍은 동영상을 올리려는데, 사진 앱에서는 용량이 몇 메가로 표시되는 짧은 영상이 업로드 단계에서 용량 초과로 즉시 거부됐다. 다른 영상은 멀쩡히 올라가는데 유독 그 영상만 그랬다. 사진 앱이 보여주는 크기와 실제로 거부되는 크기가 전혀 맞지 않았다.


처음엔 용량 체크 코드가 잘못됐나 의심했다. 하지만 코드는 파일 크기가 한도를 넘으면 막도록 정상 동작하고 있었다. 즉 브라우저가 서버로 보내려는 실제 파일이 진짜로 한도를 넘고 있었던 것이다. 사진 앱에 표시된 작은 크기와, 업로드 직전 브라우저가 쥐고 있는 파일의 크기가 서로 다른 파일처럼 달랐다.


거부가 즉시 떴다는 것도 단서였다. 서버에 보내보기도 전에 클라이언트 단계에서 바로 막혔다는 뜻이다. 파일을 고르자마자 그 크기가 이미 한도를 넘어 있었다는 얘기다. 그렇다면 파일이 그 큰 크기가 된 시점은 업로드 전송 중이 아니라, 사용자가 파일을 고르는 바로 그 순간이었다. 무언가가 선택 직후에 파일을 부풀리고 있었다.


이 단서들을 모으니 방향이 잡혔다. 사진 앱의 작은 원본과, 웹 업로드용으로 브라우저가 내놓는 파일이 다른 물건이며, 그 변환이 파일 선택 순간에 일어난다는 것이다. 즉 아이폰이 웹에 올릴 때 무언가 처리를 하면서 파일을 키우고 있었다. 그 처리가 무엇인지가 이 문제의 핵심이었다.


2. 아이폰은 효율 코덱으로 녹화한다

원인의 첫 조각은 아이폰의 기본 녹화 코덱이었다. 아이폰은 저장 공간을 아끼려고 효율이 아주 좋은 최신 코덱으로 영상을 녹화한다. 같은 화질을 훨씬 작은 용량에 담는 코덱이라, 사진 앱에 표시되는 그 작은 크기는 이 효율 코덱으로 압축된 결과였다. 기기 안에서는 이 작은 파일이 원본이었다.


문제는 이 효율 코덱이 웹에서 보편적으로 재생되지 않는다는 데 있었다. 특정 브라우저에서는 잘 재생되지만, 웹 전반에서 안전하게 재생되는 표준 코덱은 아니었다. 그래서 이 코덱 그대로 웹에 올리면 다른 사용자 기기에서 재생이 안 될 위험이 있었다. 기기 안에서 효율적인 것과 웹에서 호환되는 것은 별개 문제였다.


그래서 아이폰은 웹 업로드 시 배려를 한다. 웹 호환성을 위해, 효율 코덱으로 저장된 원본을 널리 재생되는 표준 코덱으로 바꿔서 내보내는 것이다. 사용자가 파일을 고르는 순간, 브라우저가 원본을 표준 코덱으로 실시간 재인코딩해 업로드용 파일을 만든다. 좋은 의도의 자동 변환이었지만, 바로 이게 용량 폭증의 방아쇠였다.


여기서 지난 편의 함정이 그대로 재현됐다. 효율 좋은 코덱에서 호환성 좋은 표준 코덱으로 바꾸면, 같은 화질을 유지할 경우 용량이 오히려 늘어난다는 그 원리 말이다. 아이폰은 화질을 지키려 하니, 코덱만 비효율적인 것으로 바뀌면서 용량이 불어났다. 코덱 교체가 용량을 늘릴 수 있다는 원리의 생생한 사례였다.


3. 실시간 재인코딩이 파일을 부풀린다

재인코딩의 세부를 보면 용량 증가가 더 이해됐다. 효율 코덱과 표준 코덱은 압축 방식이 근본적으로 달라서, 둘 사이 변환은 단순 포맷 변경이 아니라 완전히 디코딩한 뒤 다시 압축하는 진짜 재인코딩이었다. 그리고 이 순간의 재인코딩은 화질을 지키려고 넉넉한 비트레이트로 이뤄졌다. 화질 손실을 피하려다 데이터 양이 늘어난 것이다.


게다가 실시간으로 즉석에서 인코딩하다 보니, 시간을 들여 최적으로 압축하는 것과는 거리가 멀었다. 빠르게 변환하느라 비트레이트가 여유롭게 잡혔고, 그 결과 원본의 몇 배에 달하는 파일이 나오기도 했다. 사진 앱의 작은 원본이 업로드 단계에서 훌쩍 커지는 건 이 과정 때문이었다. 작은 원본과 큰 업로드본 사이의 격차가 여기서 생겼다.


정리하면 인과는 이랬다. 아이폰이 효율 코덱으로 작게 저장한 원본을, 웹 호환을 위해 표준 코덱으로 실시간 재인코딩하면서, 화질 유지를 위해 넉넉한 비트레이트를 써서, 결과적으로 원본보다 큰 파일이 업로드된다. 사진 앱의 크기는 원본 기준이고, 거부된 크기는 재인코딩본 기준이라 둘이 안 맞았던 것이다. 서로 다른 두 파일의 크기를 비교하고 있었으니 당연했다.


이 원인 규명으로 사용자에게도 설명이 가능해졌다. 사용자가 사진 앱의 작은 숫자를 근거로 왜 안 올라가냐고 물으면, 웹에 올릴 때 호환 포맷으로 바뀌며 용량이 커진다고 안내할 수 있었다. 증상만 보면 버그 같지만 사실은 아이폰의 정상적인 호환성 배려의 부작용이었다. 원인을 알면 사용자 응대도 달라졌다. 무턱대고 용량을 줄이라고만 하면 사용자는 답답해하지만, 왜 커지는지와 어떻게 피하는지를 함께 알려주면 납득하고 스스로 대처했다. 증상을 이해한 설명은 그 자체로 좋은 사용자 안내였다.


4. 업로드 경로의 한도라는 벽

왜 하필 그 크기에서 막혔는지는 내 업로드 구조와 관련 있었다. 내 스택에서는 브라우저가 보낸 파일이 엣지 함수의 본문을 통과해 스토리지로 들어갔다. 그래서 함수가 받을 수 있는 요청 본문의 한도가 곧 업로드 크기의 상한이 됐다. 이 한도를 넘는 파일은 함수에 닿기도 전에 거부됐다. 서버를 얇게 유지하는 구조의 대가였다.


클라이언트 단의 용량 체크는 이 한도를 미리 반영한 것이었다. 어차피 함수 한도를 넘으면 전송해도 실패하니, 브라우저 단계에서 미리 막아 헛된 전송을 피한 것이다. 그래서 아이폰 재인코딩본이 이 한도를 넘으면 파일 선택 즉시 거부됐다. 코드는 정상 동작이었고, 문제는 파일이 그만큼 커졌다는 데 있었다.


이 한도 자체를 무한정 올릴 수는 없었다. 함수의 본문 한도에는 상한이 있었고, 전송 오버헤드까지 감안하면 안전하게 쓸 수 있는 크기는 그 상한보다 조금 낮았다. 그래서 한도를 올리는 것만으로 모든 큰 파일을 받을 수는 없었다. 한도 상향은 완화책이지 근본 해결은 아니었다. 근본은 이 통과 구조 자체를 바꾸는 데 있었는데, 그건 다음 편의 주제였다.


당장은 이 한도를 현실적인 선에서 올리는 게 급했다. 기존 한도는 지나치게 낮게 잡혀 있어서, 아이폰 재인코딩본 상당수가 걸렸다. 이걸 함수 본문 한도 바로 아래까지 올리니, 아이폰에서 온 표준 코덱 변환본 대부분이 통과했다. 통과한 뒤에는 앞 편의 큐로 넘겨 재인코딩해 용량을 회수하면 됐다. 입구를 넓히고 뒤에서 줄이는 조합이었다.


5. 현실적인 대응 조합

정리한 대응은 몇 가지를 겹친 것이었다. 첫째, 업로드 한도를 함수 본문 한도 바로 아래까지 올려, 아이폰 재인코딩본이 일단 들어오게 했다. 둘째, 들어온 큰 동영상은 앞 편의 큐로 넘겨, 표준 해상도와 적정 비트레이트로 재인코딩해 용량을 회수했다. 입구는 넓히되 저장은 줄이는, 두 단계의 역할 분담이었다.


셋째는 사용자 안내였다. 그래도 한도를 넘는 큰 영상은 있을 수밖에 없으니, 그럴 때는 원인과 대안을 알려줬다. 아이폰 설정에서 호환성 우선으로 촬영하면 처음부터 표준 코덱으로 찍혀 업로드 폭증이 없다는 것, 또는 영상을 짧게 나누거나 낮은 해상도로 찍으라는 안내였다. 사용자가 스스로 회피할 수 있게 돕는 것이다.


이 조합의 핵심은 어느 하나로 다 해결하려 하지 않았다는 데 있었다. 한도 상향만으로도, 안내만으로도, 큐 회수만으로도 부족했다. 셋을 겹치니 대다수 경우가 커버됐고, 극단적으로 큰 영상만 예외로 남았다. 여러 부분 해법을 겹쳐 실용적 커버리지를 확보하는 것이 완벽한 단일 해법을 찾는 것보다 현실적이었다.


남은 근본 과제는 업로드가 함수 본문을 통과한다는 구조 자체였다. 이 구조를 그대로 두는 한 함수 본문 한도라는 천장은 사라지지 않았다. 정말 큰 파일까지 받으려면 브라우저가 스토리지에 직접 올리도록 경로를 바꿔야 했다. 다음 편에서는 이 업로드 파이프라인을 어떻게 설계했는지, 그리고 직접 업로드로 천장을 걷어내는 선택지까지 폭넓게 다룬다.