서론
FFmpeg은 오디오 및 비디오를 다루는 라이브러리로, 다양한 멀티미디어 형식의 인코딩, 디코딩, 변환, 스트리밍 등의 작업을 수행할 수 있도록 도와준다.
그리고 FFmpeg WASM은 FFmpeg(FFmpeg)을 웹 어셈블리(WebAssembly) 형식으로 컴파일한 라이브러리이다.
즉, 오디오 및 비디오와 관련된 라이브러리를 웹 클라이언트에서도 나름 자유롭게 쓸 수 있게 만든 것이다.
나는 최근 프로젝트가 이것과 관련되어 있어서 이리저리 만지던 참이었다.
그러다가 FFmpeg WASM의 디코딩/인코딩 속도를 과연 높일 수 있을지에 대한 의문이 문뜩 들었다.
FFmpeg WASM의 디코딩/인코딩 속도가 워낙 CPU를 학대하는데다가, 워낙 느리기 때문이었다.
그렇기에 트랜스코드(특정 형식의 파일을 다른 형식으로 변환하는 것) 속도를 높이는 방법에 대한 실험을 진행해보았다.
이번 실험에 사용된 핵심 준비물은 다음과 같다.
<라이브러리>
"@ffmpeg/core": "^0.11.0"
"@ffmpeg/ffmpeg": "^0.11.5",
<실험에 사용할 비디오 변환 사이트>
React와 React-Video로 제작했다.
비디오 길이 수정 기능과 GIF 및 본래의 확장자(MP4->MP4)로 변환하는 기능을 갖고 있다.
<하드웨어>
CPU: Intel i5-12600k
Ram: 32GB
GPU: NVIDIA GTX 980
Drive: TOSHIBA P300(7200rpm, 64MB, PMR) HDD
이러한 준비물을 갖고 비디오 인코딩 속도를 높여볼 생각이다.
정확하게는 30초 혹은 1분 남짓의 비디오가 GIF로 얼마나 빨리 변환하는지를 확인해볼 생각이다.
(물론 실력 없는 개발자 지망생이므로, 내용에 오류가 다소 있을 수도 있다.)
잘못된 실험들...?
미적용 상태
//미적용 상태
//MP4->GIF
await ffmpeg.run("-i", inputFileName, "-ss", `${minTime}`, "-to", `${maxTime}`, "-f", "gif", outputFileName);
//MP4->MP4
await ffmpeg.run("-i", inputFileName, `-ss`, `${minTime}`, "-t", `${maxTime}`, "-c", "copy", outputFileName);
GPU 하드웨어 미가속 |
FHD 화질의 28초 정도 MP4 영상을 넣고, GIF로 인코딩한 결과이다.
34~37초 즈음이 소요되고 있다는 걸 알 수 있다.
이후의 실험도 같은 조건에서 해볼 예정이다.
스레드 조정
// -threads 스레드개수
-threads 8
FFmpeg에는 가용 스레드 개수를 조정할 수 있는 옵션이 존재한다.
그러나 그런 걸 조절하지 않아도 FFmpeg 자체적으로 스레드를 효율적으로 사용하는 편이다.
그리고 FFmpeg의 웹브라우저 버전인 FFmpeg WASM은 안타깝게도 기본이 싱글 스레드이다.
멀티 스레드 버전(FFmpeg WASM core-mt)이 있기는 하나, 표준이 아닐 뿐더러 불안정하다고 한다.
그런 관계로 쓸 수는 없었다.
GPU 하드웨어 가속
https://trac.ffmpeg.org/wiki/HWAccelIntro
FFmpeg HWAccel 소개 항목 참고
하드웨어 디코더는 소프트웨어 디코더와 동일한 출력을 생성한다. 하지만 소프트웨어 디코더에 비하면 전력과 CPU 사용량이 더 적은 편이다.
이는 하드웨어 인코더 또한 마찬가지로, 빠르고 CPU 사용량이 적은 편이다. 다만, 하드웨어 인코더는 일반적으로 x264와 같은 좋은 소프트웨어 인코더보다 훨씬 낮은 품질의 출력물을 생성한다.
즉, 실질적으로 비슷한 품질로 출력하려면 더 높은 비트레이트가 필요하다. 애당초 동일한 비트레이트에서 더 낮은 품질의 출력물을 생성하니 말이다.
CUDA
//하드웨어 가속
//-hwaccel cuda 사용
await ffmpeg.run("-hwaccel", "cuda", "-i", inputFileName, "-ss", `${minTime}`, "-to", `${maxTime}`, "-f", "gif", outputFileName);
GPU 하드웨어 가속 사용 ("-hwaccel", "cuda") | |
|
엔비디아 GPU의 쿠다(CUDA)를 기반으로 하는 하드웨어 가속 디코딩을 사용했다.
놀랍게도 하드웨어 가속이 하드웨어가 가속되지 않은 환경보다 200배 넘게 더 빠르다.
다만, 해당 하드웨어 가속의 경우 쿠다를 사용하는 것이기에, 엔비디아 GPU 환경에서만 사용이 가능하다.
DXVA2
//하드웨어 가속
//-hwaccel dxva2 사용
await ffmpeg.run("-hwaccel", "dxva2", "-i", inputFileName, "-ss", `${minTime}`, "-to", `${maxTime}`, "-f", "gif", outputFileName);
GPU 하드웨어 가속 사용 ("-hwaccel", "dxva2") | |
마이크로소프트에서 개발한 다이렉트X 기반의 하드웨어 디코딩 가속기를 사용했다.
마찬가지로 빠르다고 할 수 있었다.
그런데...
하드웨어 가속을 통해 변환된 GIF 파일들이 알고보니 파일 크기가 전부 0byte였다.
그러니까, 변환이 된 줄 알았는데 전혀 아닌 셈이었다.
이는 GIF가 아니라 WEBM 같은 타 확장자도 매한가지였다.
(특히 WEBM은 속도가 매우 끔찍했다.)
즉, FFmpeg와 다르게 FFmpeg WASM은 하드웨어 가속으로 뭔가를 할 수 없는 것이었다.
이는 인터넷 검색을 해봐도 나오는 사실이었다.
정확하게는 구글링을 해보면 2020년 전후 즈음까지는 하드웨어 가속이 지원되지 않는다는 게 분명했다.
그리고 하드웨어 가속이 개발 중이라는 익명에 가까운 정보를 접할 수 있었으나, 그게 전부였다.
FFmpeg WASM 0.11.5 버전이 22년 출시라는 걸 생각하면, 2020년 이후에도 개발 완료가 안 되었다고 할 수 있을 것 같다.
사실 온갖 보안 문제가 있는데.
웹브라우저 쪽에서 GPU 자원을 마음대로 끌어와 쓰는 것 자체가 그리 쉬울리가 없었다.
결국 위의 측정은 사실 전부 시간 낭비에 불과했을 뿐이었다.
그나마 트랜스코드를 빠르게 할 수 있는 방법
코덱 복사
//-c:v libx264 -c:a aac
//비디오, 오디오 코덱을 별도로 적어줌 (H.264 코덱과 AAC 코덱 사용)
await ffmpeg.run("-i", inputFileName, `-ss`, `${minTime}`, "-t", `${maxTime}`, "-c:v", "libx264", "-c:a", "aac", outputFileName);
//-c copy
//비디오, 오디오 코덱을 전부 복사함
await ffmpeg.run("-i", inputFileName, `-ss`, `${minTime}`, "-t", `${maxTime}`, "-c", "copy", outputFileName);
기본(코덱을 별도로 적어줌) | 코덱을 전부 복사함 |
입력 파일과 출력 파일의 확장자(규격)이 같을 때만 할 수 있는 방법이다.
-c copy 속성을 이용하여 코덱을 복사시키는데, 이런 경우에는 꽤 큰 트랜스코드 속도 증가를 이뤄낼 수 있다.
비트레이트 감소
//비트레이트 유지
await ffmpeg.run("-i", inputFileName, `-ss`, `${minTime}`, "-t", `${maxTime}`, "-c:v", "libx264", "-c:a", "aac", outputFileName);
//-b:v 500k
//비트레이트 감소
await ffmpeg.run("-i", inputFileName, `-ss`, `${minTime}`, "-t", `${maxTime}`, "-c:v", "libx264", "-b:v", "500k", "-c:a", "aac", outputFileName);
기본(비트레이트 유지) | 비트레이트 감소 (500k) (GIF, MP4) |
비트레이트를 과감하게 낮추는 방법 또한 트랜스코드의 시간을 줄이는 법 중 하나이다.
다만, 이런 경우에는 영상의 품질이 깎여나가는 문제점이 있다.
그렇기에 -c copy 속성과 함께 쓰면 되지 않나 싶지만, 안타깝게도 그렇게 해봤자 속도가 유의미하게 올라가지는 않는다.
그렇게 쓸 바에 -c copy 속성 하나만 쓰는 게 더 낫다고 할 수 있다.
프레임 삭제
//프레임 유지
await ffmpeg.run(
"-i", inputFileName,
"-ss", `${minTime}`,
"-to", `${maxTime}`,
"-f", "gif",
outputFileName
);
//프레임 1/3으로 만들기
//프레임 삭제
await ffmpeg.run(
"-i", inputFileName,
"-vf", "select='not(mod(n\,3))',setpts=N/FRAME_RATE/TB",
"-ss", `${minTime}`,
"-to", `${maxTime}`,
"-f", "gif",
outputFileName
);
기본(프레임 유지) | 프레임 삭제 |
프레임을 삭제함으로써 트랜스코드 속도를 올릴 수도 있다.
다만, 해당 방법을 사용시 영상의 속도가 빨라지는 불상사가 따를 수도 있다.
결론
본인의 식견이 짧아서 이런 결과가 나온 것일 수도 있지만, 어쨌거나 코덱 복사/비트레이트 감소/프레임 삭제 외의 소프트웨어적인 속도 증가 방법은 찾지 못했다.
FFmpeg WASM 0.11.5를 이용해, 본래의 품질로 빠르면서도 저용량인 비디오/오디오 변환을 추구하는 건 사실상 불가능하다고 봐도 무방할 것 같다.
물론 FFmpeg WASM의 트랜스코드 속도에 큰 영향을 끼치는 고성능 컴퓨터(특히 고성능 CPU) 사용자들은 다소 예외다.
어쨌거나, FFmpeg 공부용과 개인 프로젝트용으로는 좋아도 그 이상의 용도로는 썩 적합해보이지 않는다...
그래도 개인적으로는 이틀에서 사흘 정도 붙잡고 있어서 그런지.
FFmpeg 사용법을 어느 정도 익혀서 마냥 시간 낭비에 가까웠던 경험은 아니었던 것 같다.