




종이 한 장에 노래 한 곡 전체를 담아 다닐 수 있다면 어떨까요? 스포티파이도, 클라우드 저장 공간도, 와이파이도, 이동통신망도 필요 없습니다. 그저 QR 코드로 가득 찬 종이 한 장만 있으면 됩니다.
이 프로젝트를 시작하게 된 계기는 바로 그 엉뚱한 아이디어였습니다. 그 결과물이 바로 제가 ‘ QR 음악 카세트’라고 부르는 것입니다. 인터넷 연결 없이도 휴대폰에서 노래를 재생하는 데 필요한 모든 정보가 담긴, 종이에 인쇄된 8개의 QR 코드입니다.
저는 같은 개념을 바탕으로 ESP32와 LoRa 모듈을 사용하여 소규모 LoRa 기반 음악 네트워크를 설계했습니다. 압축된 음악 파일은 Wi-Fi, 이동통신 인프라, 인터넷 연결 없이도 기기 간에 무선으로 전송될 수 있습니다.
따라서 이 프로젝트에서는 휴대폰에 음악을 넣는 두 가지 방법이 있습니다:
이 실험은 우리가 평소 당연하게 여기는 인프라를 제거하고, 디지털 미디어를 저장하고 공유하는 완전히 독립적인 방식을 구축하려고 할 때 어떤 일이 일어나는지 알아보는 것입니다. 또는 제가 부르는 이름으로는, 바로 그 ‘종말의 스포티파이’입니다.
스캔한 음악을 직접 들어보세요! (아래에 첨부됨)
만들어 봅시다.
아마존: 구매 링크
기타 유통사: 구매 링크
참고: RYLR998 자체는 AT 명령어를 통해 UART로 제어되는 868/915 MHz 모듈입니다. 해당 지역에 적합한 버전을 구매·사용하고, 현지 무선 통신 규정에 따라 설정하시기 바랍니다.



무언가를 만들기 전에, 이 작은 QR 코드에 담으려고 하는 터무니없이 많은 양의 데이터를 먼저 살펴보도록 합시다.
저는 2분 7초짜리 노래로 시작했습니다. MP3 파일로 저장하면 약 2.9MB의 저장 공간을 차지합니다. 이제 이를 최대 크기의 버전 40 QR 코드와 비교해 봅시다. 인코딩 및 오류 정정 설정에 따라, 유효 페이로드 용량은 약 3KB 정도입니다.
따라서 3MB짜리 노래를 3KB짜리 QR 코드에 담는 것이 과제입니다.
만약 MP3 파일을 QR 코드에 직접 담으려고 한다면, 이 노래 한 곡만으로도 약 1,000개의 버전 40 QR 코드가 필요할 것입니다. 이는 분명 실용적인 음악 카세트가 될 수 없습니다. 따라서 먼저 음악 파일을 압축하여 용량을 줄여야 합니다.
압축에는 크게 두 가지 접근 방식이 있습니다:
처음에는 MP3 파일을 간단히 ZIP으로 압축한 뒤, 압축된 파일을 QR 코드에 넣으면 될 것처럼 보일 수도 있습니다. 하지만 안타깝게도 그런 방식으로는 목표에 전혀 가까워질 수 없습니다. MP3는 이미 손실 압축 형식이기 때문에, 무손실 압축을 적용하더라도 활용할 수 있는 중복 정보가 거의 남아 있지 않기 때문입니다.
MP3 파일을 더 이상 압축하려고 애쓰기보다는, 오디오를 완전히 다른 방식으로 표현할 수 있을지 알아보고 싶었습니다. 바로 오디오를 극히 적은 양의 데이터로 압축하도록 특별히 고안된 방식 말입니다.



저는 노래 전체를 아주 작은 공간에 표현하기 위해 특정 유형의 신경망을 사용하기로 결정했습니다. 이를 오토인코더라고 합니다.
이 시스템은 인코더와 디코더로 구성됩니다. 인코더는 원본 오디오 파형을 입력받아 훨씬 더 작은 표현으로 압축합니다. 이 표현은 디코더가 오디오를 재구성하는 데 필요한 정보를 담은 이산 코드로 이루어져 있습니다.
그래서 오디오 파일 전체를 QR 코드에 넣는 대신, 이 압축된 코드들을 QR 코드에 담았습니다. 이를 ‘잠재 데이터’라고 합니다. 그러면 노래를 재생하고 싶을 때 앱이 이 코드들을 가져와 디코더를 통해 처리함으로써 오디오를 재구성할 수 있습니다. 우리는 대역폭 을 조절하여 파일 크기와 음질을 바꿔 볼 수 있습니다.
첫 번째 테스트에서는 3 kbps의 대역폭을 사용했습니다. 결과는 꽤 놀라웠습니다. 약 2.9 MB 크기의 MP3 파일이 약 21 KB의 잠재 데이터로 축소되었습니다. 이는 이미 원본 파일보다 약 1,000배나 작은 크기입니다.
하지만 21 KB는 여전히 하나의 QR 코드에 담을 수 있는 약 3 KB보다 훨씬 큰 용량입니다. 그래서 한계를 더 밀어붙여 보기로 했습니다. 대역폭을 1.5 kbps로 낮추었더니 표현이 약 13 KB로 줄었습니다. 하지만 작다고 해서 반드시 더 좋은 것은 아닙니다.
두 버전을 모두 디코딩해서 들어보았습니다. 1.5 kbps 버전은 음질이 크게 저하되어 소리가 꽤 나빴습니다. 반면 3 kbps 버전은 압축이 상당히 심했음에도 불구하고 의외로 음질이 좋았습니다.
아래에 두 버전을 모두 첨부해 두었으니 직접 들어보세요!
그래서 프로젝트의 나머지 부분을 진행하는 데 3 kbps를 시작점으로 사용하기로 했습니다. 그 결과 약 21 KB 크기의 압축 음악 파일이 남았습니다. QR 코드 하나에 담기에는 여전히 너무 크지만, 이제 어느 정도 진전이 있는 것 같습니다.
이 프로젝트에는 인코더와 디코더 스크립트를 포함했습니다. 인코더는 오디오 파일을 입력받아 압축된 표현을 생성하고, 디코더는 그 표현을 입력받아 오디오를 재구성합니다.
실험해 볼 때 중요한 매개변수는 대역폭입니다:
예를 들어:
는 더 작은 표현을 생성하는 반면,
더 많은 데이터를 사용하며 일반적으로 더 높은 음질을 제공합니다.
한 가지 중요한 점은 EnCodec이 단순히 ZIP과 유사한 압축기가 아니라는 것입니다. EnCodec은 압축된 표현으로부터 오디오를 재구성하도록 학습된 신경망 기반 오디오 코덱입니다.





우리 노래의 용량이 2.9MB에서 약 21KB로 줄어들었는데, 이는 엄청난 감소폭이지만, 최대 용량의 QR 코드는 약 3KB 정도의 이진 데이터만 담을 수 있습니다.
이 시점에서, 하나의 QR 코드에서 더 많은 데이터를 추출할 수 있는지 확인해 보았습니다.
QR 코드에는 중복 정보가 포함되어 있어 코드의 일부가 손상되어도 판독이 가능합니다. 오류 정정 수준은 L, M, Q, H의 네 가지가 있으며, 수준이 높을수록 데이터 용량은 다소 줄어들지만 오류 허용 범위는 더 넓어집니다.
그래서 이런 생각이 들었어요. 그 중복 부분을 가능한 한 많이 제거하면 어떨까?
오류 정정 수준을 가장 낮은 단계인 L로 변경했습니다. 이렇게 하면 표준 QR 코드에서 사용할 수 있는 최대 용량인 약 3.3KB를 확보할 수 있습니다.
설령 오류 정정 정보를 모두 제거할 수 있다고 해도, 내가 필요로 하는 7배의 용량 증대에는 여전히 한참 못 미칠 것이다. 더 중요한 것은, 그렇게 하면 인쇄된 코드가 손상이나 인쇄 불량에 대한 내성이 훨씬 약해진다는 점이다.
그래서 저는 기존의 흑백 QR 코드를 뛰어넘는 무언가를 찾아보기 시작했습니다.
그때야말로 제가 JAB 코드를 알게 되었습니다.
JAB 코드는 단순한 흑백 대신 여러 가지 색상을 사용하는 새로운 유형의 2차원 바코드입니다. 원칙적으로 여러 가지 색상을 사용하면 각 셀이 더 많은 정보를 표현할 수 있으므로, 동일한 물리적 공간에 훨씬 더 많은 데이터를 담을 수 있습니다. 이는 매우 유망해 보였습니다.
그래서 압축된 음악 데이터가 담긴 JAB 코드를 생성하는 파이썬 프로그램을 작성해 봤습니다. 그 결과는 정말 끔찍하더군요! 말 그대로 텔레비전 노이즈처럼 보였습니다.
문제는 단지 보기만 이상해 보였다는 것만이 아니었다. 개별 색상 셀들이 너무 작아지고 빽빽하게 밀집되어 있어 일반 스마트폰 카메라로는 이를 선명하게 포착하기 어려웠다.
그래서 가장 간단한 해결책으로 돌아갔습니다.
하나의 QR 코드에 더 많은 데이터를 담으려고 하기보다는, QR 코드를 여러 개 사용하는 건 어떨까요?
21KB 크기의 파일은 각 QR 코드의 용량을 표준 용량 범위 내로 유지하면서 대략 8개의 QR 코드로 나눌 수 있습니다.
그러자 또 다른 생각이 떠올랐습니다.
결과물이 단순히 바코드로 뒤덮인 종이 한 장이 아니라, 실제 음악 매체와 같은 느낌을 주었으면 했습니다.
그래서 저는 스트리밍이 등장하기 훨씬 전부터 존재했던 것, 즉 양면 물리적 매체에서 영감을 얻었습니다.
일부 비닐 레코드는 듣다가 중간에 뒤집어야 했죠. 그래서 저도 제 QR 카세트에 똑같이 해보기로 했습니다.
이렇게 적을게요:
A면
B면
또한 각 QR 코드는 해당 번호가 포함된 작은 식별자로 시작합니다. 이렇게 하면 앱이 코드가 특정 순서대로 스캔되었다고 가정할 필요가 없습니다. 앱은 단순히 ID를 읽어들이기만 해도 코드를 재구성할 수 있습니다.



이제 음악 파일을 약 21 KB로 압축했으니, 이 데이터를 실제 형태로 만들 차례입니다.
먼저 압축된 데이터를 8개의 청크로 나눕니다.
각 청크의 시작 부분에 작은 식별자를 추가합니다:
ID가 중요한 이유는 디코더 측에서 각 조각이 어디에 속하는지 알아야 하기 때문입니다. QR 코드를 앱에서 어떤 순서로 스캔하더라도 상관없습니다. 앱이 ID를 읽고 모든 데이터를 올바른 순서로 다시 조합할 수 있기 때문입니다.
그런 다음 8개의 QR 코드를 각 면에 4개씩 배치합니다:
이렇게 하면 다음과 같은 멋진 물리적 비유가 생깁니다: A면과 B면, 전통적인 카세트나 비닐 레코드처럼요.
압축된 music.raw 파일을 받아 8개의 청크로 나누고, ID를 추가하고, QR 코드를 생성한 다음, 인쇄할 수 있는 두 개의 PNG 이미지로 배치하는 간단한 Python 스크립트를 작성했습니다.
이 스크립트는 데이터 양에 따라 QR 코드의 크기를 자동으로 조정합니다. 곡마다 최종 압축 파일의 크기가 반드시 정확히 같지는 않기 때문에 유용한 기능입니다.
또한 각 QR 코드 주변에 필요한 여백을 남겨 둡니다. 이를 콰이어트 존이라고 하며, 표준 QR 지침에 따르면 코드 주변에 최소 4모듈의 여백이 있어야 합니다.
그런 다음 두 면을 일반 용지에 실물 크기로 인쇄했습니다.
이렇게 해서 노래 한 곡 전체를 종이 한 장에 물리적으로 인쇄하여 성공적으로 저장했습니다!




QR 카세트는 문제의 절반, 즉 인터넷에 의존하지 않고 음악을 물리적으로 저장하는 방법을 해결합니다.
하지만 저는 Wi-Fi나 이동통신망 없이도 한 곳에서 다른 곳으로 음악을 전송할 방법도 원했습니다.
그래서 LoRa를 선택했습니다. LoRa는 비교적 적은 양의 데이터를 장거리로 전송하도록 설계된 장거리 저전력 무선 기술입니다.
기본 시스템은 다음과 같습니다:
송신기에는 압축된 음악 파일 모음이 저장되어 있습니다. 제가 노래를 요청하면 ESP32가 압축된 데이터를 읽어 Lora 모듈(RYLR998)을 통해 패킷 단위로 전송합니다.
수신기는 해당 패킷을 수신해 올바른 순서로 재조립한 다음, 완성된 압축 파일을 휴대폰으로 전달합니다. 그러면 수신 측에서는 QR 카세트에 사용했던 것과 동일한 디코더로 파일을 디코딩할 수 있습니다.
Reyax는 취미로 사용하는 사람들도 쉽게 프로그래밍하고 사용할 수 있는 훌륭한 통신 모듈을 제작합니다. 이번에는 Reyax RYLR998 모듈 두 개를 사용합니다. 이 모듈은 다음에서 구매할 수 있습니다. 링크.
이 프로젝트에서 LoRa의 흥미로운 점은 속도가 아니라 두 기기 사이에 기존 네트워크가 필요 없다는 사실입니다.
두 기기 사이에는 라우터도, 클라우드 서버도 없으며 데이터 전송에 이동통신 기지국도 관여하지 않습니다. 두 LoRa 모듈이 직접 통신합니다.
따라서 이 기술은 이와 같은 오프그리드 실험에 유용합니다.
물론 상당한 절충점이 있습니다. LoRa는 느립니다. 일반적인 2.9 MB MP3 파일을 이 방식으로 전송하려면 몇 시간이 걸립니다. 하지만 압축하면 이 노래는 약 21 KB에 불과합니다.





이번 개념 증명(PoC)을 위해 하드웨어를 가능한 한 단순하게 구성하고자 했습니다.
무선 링크의 양 끝단에는 각각 ESP32와 REYAX RYLR998 LoRa 모듈이 장착되어 있습니다.
ESP32 한 대는 송신기 역할을 하고, 다른 한 대는 수신기 역할을 합니다. 송신기는 압축된 음악 파일에 접근할 수 있으며, 요청이 들어오면 LoRa를 통해 해당 파일을 전송합니다. 수신기 역할을 하는 ESP32는 데이터를 수신한 후 USB를 통해 PC로 전달하며, PC에서는 간단한 파이썬 프로그램이 음악을 복원하고 디코딩합니다.
ESP32는 RYLR998과의 통신을 담당하고, RYLR998은 실제 LoRa 무선 통신을 담당합니다.
이번 개념 증명(PoC)에서는 수신 측에서 PC가 대부분의 주요 작업을 담당합니다. 이를 통해 기반 시스템이 제대로 작동하는지 확인하기 전에 완전한 스마트폰 애플리케이션을 개발해야 하는 번거로움을 피할 수 있습니다.
RYLR998은 UART를 통해 ESP32와 통신합니다.
제 환경에서는 연결 구성은 다음과 같습니다:
RYLR998 -> ESP32
VDD -> 3.3V
GND -> GND
TXD → ESP32 RX
RXD → ESP32 TX
한 장치의 송신기가 다른 장치의 수신기에 연결되어 있기 때문에 TX 및 RX 라인이 교차되어 있습니다.
모든 배선이 완료되면, ESP32는 AT 명령어 인터페이스를 통해 RYLR998과 통신할 수 있습니다. 즉, ESP32가 LoRa 무선 프로토콜을 직접 구현할 필요가 없다는 뜻입니다. ESP32는 모듈에 무엇을 전송할지 지시하기만 하면 되며, 무선 통신은 모듈이 처리합니다.
중요: LoRa 주파수, 송신 전력, 대역폭, 듀티 사이클 및 기타 무선 매개변수는 국가마다 규제가 다릅니다. 해당 지역에서 허용되는 설정을 사용하십시오. 제가 제작한 시스템의 경우, 인도 관련 규정에 따라 모듈이 작동하도록 구성했습니다.

이제 하드웨어 연결이 완료되었으니, 첫 번째 ESP32가 음악 송신기 역할을 하도록 프로그래밍할 수 있습니다.
송신기는 압축된 음악 파일인 ‘music.raw’를 LittleFS 파일 시스템에 저장합니다. 이 파일은 앞서 인코더가 생성한 것과 동일한 잠재 데이터 파일입니다.
송신기는 파일의 내용을 이해할 필요가 없습니다. ESP32의 관점에서 이 파일은 안정적으로 전송해야 하는 단순한 바이너리 파일일 뿐입니다.
수신 측 ESP32가 간단한 GET
요청을 보내면 통신이 시작됩니다. 그러면 송신기는 다음과 같이
송신기는 패킷을 보낼 때마다 확인 응답을 기다리므로, 무선 링크에서 패킷이 손실되더라도 전체 전송을 다시 시작할 필요 없이 해당 패킷을 재전송할 수 있습니다.
중요한 점은 원본 MP3가 아니라 압축된 EnCodec 표현을 전송한다는 것입니다. 수 메가바이트에 달하는 음악 파일은 이러한 유형의 저대역폭 링크를 통해 보내기 어렵지만, 압축 파일은 수십 킬로바이트에 불과합니다.
송신기 코드 전체를 첨부했습니다: transmitter.ino
스케치를 업로드하기 전에 music.raw 파일이 ESP32의 LittleFS 파일 시스템에 업로드되었는지 확인하십시오.
업로드가 완료되면 송신기는 수신 측 ESP32의 GET 요청을 기다립니다.

두 번째 ESP32는 수신기 역할을 하며 LoRa 무선 통신 장치와 PC를 연결하는 브리지 역할도 합니다.
시작되면 수신기는 송신기에 GET 요청을 보냅니다. 송신기가 파일 메타데이터로 응답하면 수신기는 received.raw 라는 이름의 새 파일을 자체 LittleFS 파일 시스템에 생성합니다.
그런 다음 수신되는 DATA 패킷을 기다립니다.
수신기는 각 패킷에 대해 다음을 수행합니다:
송신기가 이전 수신 확인 응답을 받지 못해 실수로 패킷을 다시 전송하는 경우, 수신기는 이를 중복 패킷으로 인식하고 데이터를 두 번 기록하지 않은 채 수신 확인 응답만 다시 전송합니다.
이를 통해 LoRa 연결 위에 간단하고 신뢰할 수 있는 파일 전송 시스템을 구축할 수 있습니다.
수신자가 END 메시지를 받으면 파일을 닫고, 수신한 바이트 수가 전송 시작 시 알린 크기와 일치하는지 확인합니다.
모든 것이 일치하면 수신기는 DONE 메시지를 송신기로 다시 전송합니다.
이 시점에서 수신기 ESP32에는 압축된 전체 음악 파일이 received.raw
파일을 PC로 전송하기 위해 수신기에는 한 가지 일이 더 남습니다.
수신기는 ESP32의 USB 직렬 연결을 통해 재구성된 바이너리 파일을 PC로 전송합니다.
파이썬 프로그램이 이를 쉽게 식별할 수 있도록 수신기는 바이너리 데이터를 매우 간단한 프레임으로 감쌉니다:
따라서 파이썬 프로그램은 ESP32의 일반 디버그 메시지와 혼동하지 않고 음악 파일에 해당하는 정확한 바이트 수를 읽을 수 있습니다.
완전한 수신기 코드인 receiver.ino를 첨부했습니다.


이번 개념 증명(PoC)에서는 아직 별도의 Android 애플리케이션을 만들지 않기로 했습니다. 대신 PC에서 Python 프로그램을 수신 애플리케이션으로 사용합니다.
프로그램에는 두 가지 모드가 있습니다.
이 모드에서는 PC가 USB를 통해 수신기 ESP32와 통신합니다.
ESP32는 LoRa를 통해 압축된 음악 데이터를 수신하고, 완전한 received.raw 파일을 재구성한 다음 PC로 전송합니다. 그러면 Python 프로그램이 EnCodec을 사용해 파일을 디코딩하고 재구성된 곡을 저장합니다.
동일한 프로그램으로 인쇄된 카세트도 읽을 수 있습니다.
이 모드에서는 프로그램에 이미지 두 장을 제공합니다:
프로그램은 모든 QR 페이로드의 시작 부분에 있는 ID를 읽으므로 코드를 정확한 순서로 제공할 필요가 없습니다. ID를 기준으로 정렬하고, ID 바이트를 제거한 뒤, 8개의 청크를 결합하여 원본 music.raw 파일을 재구성합니다. 그런 다음 결과 파일은 LoRa 버전과 정확히 동일한 EnCodec 디코딩 파이프라인을 거칩니다.
전체 PC 프로그램은 다음 파일로 첨부되어 있습니다:
python_receiver.py
필요한 패키지를 설치합니다:
그런 다음 다음 명령을 실행합니다:
간단한 메뉴가 표시됩니다:
QR 옵션을 선택하면 프로그램에서 카세트 이미지 두 장을 요청합니다:
그런 다음 QR 코드를 감지하고 ID를 확인한 뒤 파일을 재구성하고 오디오를 디코딩합니다.


이것이 완전한 시스템입니다.
단순한 질문 하나로 시작한 일이, 한 곡 전체를 QR 코드 안에 넣을 수 있을까? 평소 우리가 의존하는 인프라 없이 음악을 저장하고 공유하는 훨씬 더 큰 실험으로 발전했습니다.
흥미로운 점은 우리가 평소 당연하게 여기는 인프라를 제거했을 때 시스템을 얼마나 한계까지 밀어붙일 수 있는지 확인하는 것입니다.
종이 한 장에 노래를 저장할 수 있습니다.
작은 라디오 몇 대만으로도 그 노래를 공중으로 전송할 수 있습니다.
그리고 수신기는 인터넷 없이도 그 노래를 복원해 재생할 수 있습니다.
앞으로 더 나은 오디오 압축과 더 빠른 전송부터 수신 측에서 사용할 실제 Android 앱을 만드는 것까지, 이 프로젝트를 개선하고 싶은 방법이 많습니다.
직접 버전을 만들어 보시거나 흥미로운 개선 방법을 생각해 내셨다면, 어떤 결과가 나왔는지 꼭 보고 싶습니다.
끝까지 함께해 주셔서 감사합니다. 즐거운 제작 되세요!
원문: https://www.instructables.com/Paper-Tunes-Store-Music-on-Paper-and-Stream-Them-O/