Minecraft 모드가 나를 Java 미디어 라이브러리를 처음부터 만들게 한 방법
Source: Dev.to
Background
저는 Minecraft 모드에서 MP3 파일을 OpenAL을 통해 직접 재생하도록 만들고 있었습니다. 그렇게 오디오를 스트리밍하려면 저수준 MP3 디코더가 필요했죠. 저는 구식이거나 잘 알려지지 않은 도구를 파고드는 것을 좋아해서 Zoom JLayer라는 2000년대 초에 만들어진 순수 Java MP3 디코더를 발견했습니다. 딱 맞아 보였지만, 일부 MP3 파일에서만 작동했습니다. 같은 확장자, 같은 플레이어인데 전혀 다른 동작을 보였죠.
Discovering JLayer
JLayer의 소스(누군가 GitHub에 올려놓은 것)를 파헤쳐 보니, 제가 이전에 한 번도 생각해보지 못했던 사실을 알게 되었습니다: MP3 파일은 모두 동일하지 않다는 것. 비트레이트 모드(CBR, VBR), 프레임 헤더, 사이드 정보, 그리고 바이트 안에 숨겨진 다양한 구조가 존재합니다. 이때부터 저는 이 값들은 어디서 오는 걸까? 라는 질문에 빠져들었습니다. 답은 바로 바이트 자체였습니다.
원시 바이트를 직접 읽기 시작하니 멈출 수 없었습니다. 미디어 파일 포맷이 바이트 수준에서 어떻게 동작하는지 공부하게 되었고, 그때는 모드가 동작하도록 JLayer의 커스텀 포크를 배포하고 넘어갔습니다.
Building CodecMedia
나중에 WAV 지원과 포맷 변환이 필요해졌습니다. FFmpeg을 감싸는 Java 래퍼가 존재하지만, 대부분 네이티브 바이너리나 거대한 외부 툴체인에 의존해 무겁게 느껴졌습니다. 저는 의존성 없이, 순수 Java이며 가능한 가벼운 무언가를 원했습니다.
그래서 CodecMedia를 만들었습니다. 처음엔 Minecraft 모드를 위한 작은 개인 유틸리티였지만, 점차 실제 바이트 구조를 직접 파싱해 파일 포맷을 탐지하고 검증하는 미디어 라이브러리로 성장했습니다—네이티브 도구는 전혀 사용하지 않았습니다.
Usage
Add the dependency
me.tamkungz.codecmedia
codecmedia
1.1.1Probe an audio file
CodecMediaEngine engine = CodecMedia.createDefault();
ProbeResult probe = engine.probe(Path.of("song.mp3"));
System.out.println(probe.mimeType()); // audio/mpeg
System.out.println(probe.durationMillis()); // 213000
System.out.println(probe.streams().get(0).codec()); // mp3
System.out.println(probe.streams().get(0).sampleRate()); // 44100Validate before using
ValidationResult validation = engine.validate(
Path.of("song.mp3"),
new ValidationOptions(true, 64L * 1024L * 1024L)
);
System.out.println(validation.valid()); // true
System.out.println(validation.errors()); // []Supported Formats
CodecMedia는 현재 다음 포맷에 대한 탐지를 지원합니다:
- MP3
- OGG/Vorbis/Opus
- WAV
- FLAC
- AIFF/AIFC
- …그 외 다수
아직 진행 중인 작업이며, 진행하면서 미디어 포맷에 대해 계속 배우고 있습니다. Java 환경에서 네이티브 의존성 없이 미디어 탐지가 필요하다면 도움이 될 수 있습니다.
Conclusion
더 나은 도구가 존재할 수도 있고, 다양한 대안도 많습니다. 저는 단순히 작동하는 도구를 만들기 위해 CodecMedia를 만든 것이 아니라, 그 작동 방식을 이해하기 위해 만들었습니다—포맷을 공부하고, 바이트를 읽으며, 그 지식을 실제 구현에 적용함으로써 배운 것이죠. CodecMedia는 바로 그런 실험입니다.