프로젝트

네트워크 없이 소리로 통신하는 음향 모뎀

2024.04

Python
MFSK
PyAudio
FFT
NumPy
실제 송수신 시연 — 스피커가 낸 소리를 마이크가 받아 텍스트로 복원한다

문제

"네트워크 연결 없이, 소리만으로 텍스트를 주고받을 수 있을까?"라는 질문에서 시작했다. 와이파이도 블루투스도 페어링도 없이, 스피커가 낸 소리를 마이크가 듣는 것만으로 임의의 유니코드 텍스트를 정확히 복원해내는 게 목표였다. 조용한 방이 아니라 주변에 소음이나 음악이 깔린 환경에서도, 정해진 주파수의 소리만 걸러내 통신이 끊기지 않게 하는 것까지가 목표였다.

인코딩 — 문자 하나에 주파수 하나

먼저 전송할 텍스트를 UTF-8로 인코딩한 뒤 16진수 문자열로 바꾼다. 유니코드 문자는 이 단계에서 이미 0-9, A-F 16개 기호로 환원되므로 이후 과정은 문자 종류와 무관하게 동일하게 동작한다.

각 16진수 자리에 고유한 주파수를 하나씩 배정한다. 여기에 시작(START)·종료(END) 신호용 주파수 두 개를 더해 총 18개 톤을 쓴다.

자리주파수(Hz)
START512
0–9768 ~ 1920 (128Hz 간격)
A–F2048 ~ 2688 (128Hz 간격)
END2944

문자열을 START 톤 → 각 16진수 자리에 대응하는 톤 → END 톤 순서로 이어 붙이면, 텍스트 하나가 순수한 사인파 시퀀스로 바뀐다. 각 톤은 0.1초씩 재생하고 48kHz 샘플레이트로 .wav를 만들어 스피커로 내보낸다.

디코딩 — 마이크 입력을 FFT로 읽기

수신 쪽은 마이크로 오디오를 스트리밍 입력받아 청크 단위로 자른다. 진폭이 임계값을 넘는 구간부터 신호로 간주해 버퍼에 쌓고 버퍼가 한 톤 길이(4800 샘플)에 도달하면 FFT(고속 푸리에 변환) 로 주파수 성분을 분석한다.

가장 강한 주파수 성분을 위 표와 대조해 어떤 톤인지 판정한다 — START 신호가 두 번 인식되면 수신 시작, END 신호가 두 번 인식되면 종료로 보고 그동안 쌓인 16진수 문자열을 다시 UTF-8 텍스트로 디코딩한다. 잡음 속에서도 오탐을 줄이기 위해 시작·종료 신호는 연속 2회 인식돼야 유효하다.

결과

노트북 스피커로 재생한 소리를 다른 기기의 마이크가 실시간으로 받아 원문 텍스트를 그대로 복원하는 것을 확인했다. 인터넷도, 블루투스도, 페어링도 없이 공기 중의 소리만으로 데이터가 왕복한다.

GitHub