소리꽃 KeyBloom TIL 21
투명 배경 영상 내보내기를 4K까지 밀어 보다 포기한 이야기, 유료 경계를 다시 그은 이야기, 그리고 모래 파티클을 세 번 실패하고 얻은 것.
해상도라는 벽
4배 무거운 게 아니라 아예 안 되는 지점이 있다
투명 배경 영상은 투명도를 살려야 해서 화면을 압축 없이 다뤄야 한다. 4K는 한 장이 33MB쯤 되고, 초당 수십 장이 흘러간다.
1080p에서 잘 돌던 방식이 4K에서 세 군데서 연달아 막혔다. 전체 메모리는 남아 있는데도 33MB짜리 연속 공간을 못 잡아 터지고(주소 공간 파편화), 작업을 워커(백그라운드 스레드)에 나눠 맡기니 스레드 사이로 화면을 넘기는 과정이 불안정해지고, 한 번에 몇 장까지 물릴지 정해 둔 값이 4K에서는 네 배로 불어나 초과했다. 하나씩 고쳐도 다음 벽이 나왔다.
여기서 배운 건 기술이 아니라 판단이다. 크기를 키우는 일은 대체로 “그만큼 느려진다”가 아니라 “어느 지점부터 아예 안 된다”에 가깝다. 그리고 벽이 연달아 나올 때는 하나씩 넘는 대신 이 길이 맞는지를 다시 봐야 한다.
결국 이 앱은 1080p까지로 확정하고, 4K는 나중에 다른 방식으로 만드는 별도 제품으로 미뤘다. 아쉬운 결정이지만 세 번째 벽에서 멈춘 게 맞았다고 본다. 넘었어도 네 번째가 있었을 것이다.
한도는 개수가 아니라 실제 자원으로 잡는다
동시에 처리할 화면 수를 “몇 장”으로 정해 뒀던 게 위 문제 중 하나였다. 그 숫자는 1080p 기준으로 정한 것이라 4K에서는 같은 장 수가 네 배 무게였다.
한도를 정할 때 세는 단위가 실제로 소비하는 자원과 비례하지 않으면, 조건이 바뀌는 순간 한도가 거짓말이 된다. 장 수가 아니라 용량으로 한도를 잡으니 해상도가 바뀌어도 알아서 적게 물린다.
같은 함정이 “동시 접속 몇 명”, “한 번에 몇 건” 같은 데도 있다. 세기 쉬운 단위와 실제로 부담을 만드는 단위가 다르면 언젠가 어긋난다.
사용자에게 무엇을 요구할 것인가
설치를 요구하는 순간 사람이 떨어진다
앞선 TIL 18에서 윈도우는 용량이 작고 품질 좋은 Ut Video로 가기로 했었다. 문제는 그 파일을 편집 프로그램에서 열려면 사용자가 코덱(영상을 풀어 읽는 프로그램)을 따로 설치해야 한다는 점이었다.
우리 입장에서는 “설치 링크 안내하면 되지”인데, 사용자 입장에서는 영상 하나 쓰려고 처음 보는 프로그램을 깔라는 요구다. 게다가 편집을 맡기거나 다른 사람에게 파일을 넘기면 그 사람도 깔아야 한다.
그래서 편집 프로그램들이 설치 없이 바로 읽는 ProRes 4444로 바꿨다. 파일은 커졌지만 “받아서 그냥 얹으면 된다”가 됐다.
산출물 포맷을 고를 때 기준은 우리 쪽 효율이 아니라 받는 사람의 손이 몇 번 가느냐다. 특히 남에게 넘기는 파일이면 그 손이 두 배로 든다.
함께 정한 것 — ProRes는 원래 애플 코덱이라, ffmpeg가 만든 우리 쪽 구현은 정식 인코더가 아니다. 실무 호환에는 문제가 없지만 제품 화면과 홍보 문구에서는 그 이름을 앞세우지 않고 “호환” 정도로만 적기로 했다. 이름을 빌려 쓰는 자리에서는 한 발 물러서 적는 게 싸게 먹힌다.
유료 경계를 하나로 묶으면 나중에 못 쪼갠다
앞선 TIL 17에서 잠금 판단을 한 곳에 모으면서, 워터마크 제거·4K·투명 배경을 「유료인가」 하나로 묶어 잠갔다. 그런데 위에서 4K를 이 앱에서 포기하기로 하면서 정책이 바뀌었다. 투명 배경은 유료로 열고 4K는 아무에게도 안 여는 것이다.
묶어 둔 판정으로는 4K만 따로 뺄 수가 없었다. 기능마다 따로 판정하게 나누고 나서야 정책을 자유롭게 바꿀 수 있었다.
유료 경계는 처음 정한 대로 끝까지 가는 일이 거의 없다. 가격을 바꾸거나, 등급을 늘리거나, 기능 하나를 무료로 풀어 유입을 늘려 보거나 한다. 그럴 때 코드가 발목을 잡지 않으려면 기능별로 잠금을 따로 두는 게 맞다. 잠금 하나가 여러 기능을 대표하고 있으면 그 조합을 바꾸는 순간 다시 만들어야 한다.
무리를 자연스럽게 만들기
뭉침은 공유하고 움직임은 개별로
노트를 치면 모래알 같은 알갱이들이 한 무리로 떠오르되 각자 자유롭게 흩날리는 모션을 만들려 했다. 세 번 실패했다.
처음엔 화면 전체에 흐름장(위치마다 방향과 세기가 정해진, 보이지 않는 흐름의 지도)을 깔고 알갱이를 태웠다. 한 무리는 좁은 자리에 모여 있어서 다들 같은 흐름을 타고, 결국 다 같이 도는 도넛이 됐다. 다음엔 무리를 뭉치려고 중심으로 당기는 힘(스프링)을 줬다. 점으로 당기면 그 점 둘레에 껍질이 생겨서 또 고리가 됐고, 화음을 치면 여러 무리가 같은 박자로 부풀었다 오므라들었다. 세 번째로 좌우로 못 벗어나게 벽을 세웠더니, 벽에 닿은 알갱이들이 거기 멈춰 서서 양쪽에 줄이 생겼다.
세 번 다 원인이 같았고, 앞선 TIL 13에서 나선을 가둘 때 본 것과도 같았다. 좁은 무리에 공유 규칙을 걸면 다 같이 움직여서 대형이 보인다. 자연스러움은 각자 다르게 움직이는 데서 온다.
답은 공유를 버리는 것이었다. 알갱이마다 자기만의 리듬을 주니 고리도 집단 박자도 한 번에 사라졌다. 좌우로 가두는 것도 벽 대신 “항상 그 폭 안에서 살랑거리는 움직임”으로 바꿨다. 벗어날 수가 없으니 벽에 닿을 일도 없고, 그래서 줄도 안 생긴다.
여기서 세 가지를 가져간다. 점으로 당기면 껍질이 생기고, 벽을 세우면 거기 쌓이고, 같은 규칙을 공유하면 대형이 보인다. 무리로 보이게 하고 싶은 것과 개별로 보이게 하고 싶은 것을 먼저 나누고, 무리 쪽만 공유해야 한다. 여기서 공유한 건 “어느 건반에서 태어나 위로 간다”까지였고 나머지는 전부 개별이었다.
요약
- 크기를 키우는 일은 “그만큼 느려진다”가 아니라 “어느 지점부터 안 된다”에 가깝다
- 벽이 연달아 나오면 하나씩 넘지 말고 이 길이 맞는지 다시 본다
- 한도는 세기 쉬운 단위가 아니라 실제로 부담을 만드는 단위로 잡는다
- 산출물 포맷은 우리 효율이 아니라 받는 사람의 손이 몇 번 가느냐로 고른다
- 유료 잠금은 기능별로 나눠 둬야 나중에 정책을 바꿀 수 있다
- 무리는 뭉침만 공유하고 움직임은 개별로 — 공유 규칙은 대형을 만들고 벽은 줄을 만든다