[Java] 동영상 용량을 줄여보자
어디에 쓰이나?
예상한대로 역시 비디오 파일도 다루게 되었고, 이건 비디오 파일의 용량을 줄여 트래픽을 줄이기 위해 시도한 방법들을 적어보려고 한다.
비디오 용량에 영향을 끼치는 요인
1. 해상도
: 비디오는 초당 많은 수의 이미지를 출력해 보여주는 방식으로 이미지의 해상도(픽셀 수)에 따라 용량에 영향을 끼치며, 이미지 파일을 다룰때와 마찬가지다.
2. 프레임 수
: 1초당 출력되는 이미지의 수를 뜻하고 1초에 몇장이 출력되느냐에 따라 24프레임(초당 24장) 등으로 표기되며 프레임 수가 늘어날수록 영상이 매끄럽지만 용량이 늘어나게 된다.
3. 비트레이트
: 비디오 데이터를 1초당 전송되는 비트의 양을 뜻하며 1초에 1000kb를 전송한다면 1000kbps 와 같은 방식으로 표기되면 초당 사용된 비트레이트를 프레임별로 나눠서 사용하게 된다.
그럼 어떻게 할까?
Java에서 비디오 파일을 처리할때 주로 사용하는 라이브러리는 FFmpeg 이라는 라이브러리로 해상도와 프레임 수를 지정하면 FFmpeg이 적절한 비트레이트로 설정해 준다고 한다.
따라서 해상도와 프레임 수를 통해 동영상 파일의 용량을 줄여보도록 하겠다.
개발
Grabber는 파일을 읽어들여 프레임 단위로 이미지를 다루고
Recorder는 Grabber로 읽어들인 프레임들로 비디오 파일을 생성한다.
public class VideoUtil {
//Post 요청으로 전달받은 Multipart 파일을 리사이징 처리해 반환
public MultipartFile resizeVideo(MultipartFile file) throws IOException {
// 원본 파일과 동일한 확장자로 임시파일 생성
String extension = Objects.requireNonNull(file.getOriginalFilename()).split("\\.")[1];
File tempFile = File.createTempFile(UUID.randomUUID()+LocalDate.now().toString(), "."+extension);
// 파일 읽어들이기
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(file.getInputStream());
grabber.start();
FFmpegFrameRecorder recorder = getRecorder(grabber, tempFile, extension);
// 프레임 단위로 영상 리사이즈 후 기록
Frame frame;
while ((frame = grabber.grabFrame()) != null) {
recorder.record(frame);
}
// 리소스 정리
recorder.stop();
recorder.release();
grabber.stop();
grabber.release();
FileInputStream input = new FileInputStream(tempFile);
// 리사이징된 영상을 MultipartFile로 변환
MultipartFile resizedFile = new MockMultipartFile(
file.getName(), // 원본 파일 이름 유지
file.getOriginalFilename(),
file.getContentType(), // 원본 파일 타입 유지 (예: "video/mp4")
input);
input.close();
tempFile.delete();
return resizedFile;
}
private FFmpegFrameRecorder getRecorder(FFmpegFrameGrabber grabber, File tempFile, String extension) throws FFmpegFrameRecorder.Exception {
// 영상의 종횡비를 확인
BigInteger width = BigInteger.valueOf(grabber.getImageWidth());
BigInteger height = BigInteger.valueOf(grabber.getImageHeight());
int gcd = width.gcd(height).intValue();
int widthRatio = grabber.getImageWidth() / gcd;
int heightRatio = grabber.getImageHeight() / gcd;
int pixels = 360; //(360 ~ 1440)
// 영상의 종횡비에 따라 해상도를 제한
if(widthRatio > 400) pixels = 1; // (400 ~)
else if (widthRatio > 100) pixels = 5; //(404 ~ 1600)
else if (widthRatio > 50) pixels = 12; //(612 ~ 1200)
else if (widthRatio > 20) pixels = 30; //(630 ~ 1500)
else if (widthRatio > 5) pixels = 80; //(480 ~ 1600)
int targetWidth = widthRatio * pixels;
int targetHeight = heightRatio * pixels;
// FFmpegFrameRecorder로 영상을 리사이즈하여 저장
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(tempFile, targetWidth, targetHeight);
recorder.setFormat(extension); // 출력 포맷
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); // H.264 코덱 사용
recorder.setFrameRate(24); // 24 프레임으로 고정
// 오디오 채널 설정 (필요 없으면 0으로 설정)
if (grabber.getAudioChannels() > 0) {
recorder.setAudioChannels(grabber.getAudioChannels());
} else {
recorder.setAudioChannels(0); // 오디오 없는 경우
}
recorder.start();
return recorder;
}
}
돌아보며
깊게 생각해보지 않았던 비디오 파일에 대해 조금이나마 알아가게 된 시간이였다.
개발 이후 알게 된 사실로는
FFmpeg이 비트레이트를 자동으로 설정해주지만, 직접 지정하는 것이 더 효율적이라는 사실
새로운 코덱과 확장자를 이용해 더 효율적으로 용량을 줄이는 방법이 있다는 사실
들을 알게 되었다. 다음에 해당 내용들을 포함해 코드를 개선해보도록 하겠다.