
기존에 테스트 했던 TCP 기반 네트웍 스피커 테스트 코드를 수정하여 여러 스피커에 오디오 파일을 출력 할 수 있는 UDP 형태로 코드를 수정해 보자
W55RP20 UDP전송률 테스트에서 17Mbps이상의 전송률을 확인 했으므로 이번에는 96khz 의 고음질 오디오도 전송 할 수 있도록 수정하였다.
void ProcessCommandUDP(audio_buffer_pool_t *pool, float volume, uint32_t current_time) {
if (getSn_RX_RSR(SOCK_UDP_ID) > 0) {
uint8_t destip[4];
uint16_t destport;
int32_t len = recvfrom(SOCK_UDP_ID, rx_buf, RX_BUF_SIZE, destip, &destport);
if (len == SOCK_BUSY) return;
if (len < 0) {
printf("⚠️ UDP 수신 에러가 발생했습니다. (code: %d)\n", len);
return;
}
last_recv_time = current_time;
is_playing = true;
int buf_idx = 0;
while (buf_idx < len) {
if ((len - buf_idx) < 4) {
buf_idx = len;
break;
}
struct audio_buffer *buffer = take_audio_buffer(pool, true);
if (!buffer) break;
int16_t *samples = (int16_t *)buffer->buffer->bytes;
uint i = 0;
while (i < buffer->max_sample_count && (buf_idx + 3) < len) {
int16_t left_sample = (rx_buf[buf_idx + 1] << 8) | rx_buf[buf_idx];
int16_t right_sample = (rx_buf[buf_idx + 3] << 8) | rx_buf[buf_idx + 2];
samples[i * 2 + 0] = (int16_t)(left_sample * volume);
samples[i * 2 + 1] = (int16_t)(right_sample * volume);
buf_idx += 4;
i++;
}
buffer->sample_count = i;
give_audio_buffer(pool, buffer);
}
}
else if (is_playing) {
if (current_time - last_recv_time > AUDIO_TIMEOUT_MS) {
printf("🎵 Audio Timeout (Stream Stopped)\n");
is_playing = false;
for (int k = 0; k < 3; k++) {
struct audio_buffer *buffer = take_audio_buffer(pool, false);
if (buffer) {
int16_t *samples = (int16_t *)buffer->buffer->bytes;
for (uint i = 0; i < buffer->max_sample_count; i++) {
samples[i * 2 + 0] = 0;
samples[i * 2 + 1] = 0;
}
buffer->sample_count = buffer->max_sample_count;
give_audio_buffer(pool, buffer);
}
}
}
}
}
void process_udps(audio_buffer_pool_t *pool, float volume) {
uint32_t current_time = to_ms_since_boot(get_absolute_time());
switch (getSn_SR(SOCK_UDP_ID)) {
case SOCK_UDP:
ProcessCommandUDP(pool, volume, current_time);
gpio_put(LED_PIN2, 0);
break;
// 소켓이 닫혀있는 경우 새로 오픈
case SOCK_CLOSED:
is_playing = false;
if (socket(SOCK_UDP_ID, Sn_MR_UDP, PORT, 0x00) == SOCK_UDP_ID) {
printf("🚀 UDP Audio Server Opened on Port %d\n", PORT);
}
last_recv_time = to_ms_since_boot(get_absolute_time());
break;
default:
break;
}
}
서버에 해당하는 PC 소프트웨어도 UDP로 변경하고 오디오 파일의 정보를 읽어서 전송할 수 있도록 수정하였다.
import socket
import time
import struct
BOARD_IP = '192.168.1.104'
PORT = 5000
# 테스트할 WAV 파일 경로 (44.1k든 96k든 아무거나 지정 가능)
FILE_PATH = 'ThaNo0b(320k)_16ibt.wav'
CHUNK_SIZE = 1024
def send_audio_to_board():
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as client_sock:
try:
with open(FILE_PATH, 'rb') as f:
header = f.read(44)
# WAV 헤더 구조체 파싱 (Little-Endian)
channels = struct.unpack('<H', header[22:24])[0] # 22~23바이트: 채널 수
sample_rate = struct.unpack('<I', header[24:28])[0] # 24~27바이트: 샘플 레이트
bits_per_sample = struct.unpack('<H', header[34:36])[0] # 34~35바이트: 비트 수
# 추출한 정보로 정확한 전송 속도 계산
bytes_per_second = sample_rate * channels * (bits_per_sample // 8)
print(f"🎵 WAV 파일 감지 정보:")
print(f" - 샘플 레이트: {sample_rate} Hz")
print(f" - 비트/샘플: {bits_per_sample} bit")
print(f" - 채널 수: {channels} ch ({'Stereo' if channels==2 else 'Mono'})")
print(f"🚀 보드로 전체 데이터를 고정밀 전송합니다...")
f.seek(0)
start_time = time.perf_counter()
bytes_sent = 0
while True:
data = f.read(CHUNK_SIZE)
if not data:
print("🎵 전송이 완료되었습니다.")
break
client_sock.sendto(data, (BOARD_IP, PORT))
bytes_sent += len(data)
# 동적으로 계산된 속도에 맞춰 고정밀 타이머 작동
expected_time = bytes_sent / bytes_per_second
while time.perf_counter() - start_time < expected_time:
pass
except FileNotFoundError:
print(f"❌ 오류: '{FILE_PATH}' 파일을 찾을 수 없습니다.")
except Exception as e:
print(f"⚠️ 전송 중 오류가 발생했습니다: {e}")
if __name__ == '__main__':
send_audio_to_board()
96khz 음원에서 TAS5825P 동작 문제 발생
코드 수정후 동작시키면 네트웍 전송은 되지만 TAS5825P에서 소리가 출력되지 않는 현상이 발생했다.
MAX98357 I2S AMP 모듈로 테스트 해보면 모노 출력이지만 잘 동작한다.
그렇다면 코드는 문제 없는데...
처음에는 TAS5825P 가 정확한 96khz 클럭(지터 없는)이 들어오지 않으면 Clock Fault 가 발생해서 출력되지 않는다고 생각했다.
그래서 96khz일 때 BCLK는 3.072 Mhz 이므로 RP2040의 클럭 설정을 최대한 맞추려고 코드를 수정 했었다.
TI 포럼을 찾아보다 보니 아래 내용이 있다.
"TAS5825P는 원래 내부 DSP 연산을 오직 48kHz로만 처리한다. 96kHz 신호가 들어오면 칩 내부 변환기(SRC)가 어차피 48kHz로 낮춰서 계산한다."
결국 샘플링 클럭 주파수는 48khz로 고정하고
96kHz 샘플데이터 2개씩 묶어 (A + B) / 2 로 처리 해 주면 된다.
void ProcessCommandUDP(audio_buffer_pool_t *pool, float volume, uint32_t current_time) {
// 수신된 UDP 데이터가 있는 경우
if (getSn_RX_RSR(SOCK_UDP_ID) > 0) {
uint8_t destip[4];
uint16_t destport;
int32_t len = recvfrom(SOCK_UDP_ID, rx_buf, RX_BUF_SIZE, destip, &destport);
if (len == SOCK_BUSY) return;
if (len < 0) {
printf("⚠️ UDP 수신 에러 (code: %d)\n", len);
return;
}
last_recv_time = current_time;
is_playing = true;
int buf_idx = 0;
while (buf_idx < len) {
// 96kHz 스트림의 연속된 2개 샘플 세트 (총 8바이트)가 버퍼에 남아있는지 확인
if ((len - buf_idx) < 8) {
buf_idx = len;
break;
}
struct audio_buffer *buffer = take_audio_buffer(pool, true);
if (!buffer) break;
int16_t *samples = (int16_t *)buffer->buffer->bytes;
uint i = 0;
while (i < buffer->max_sample_count && (buf_idx + 7) < len) {
// [1번째 96kHz 샘플 파싱]
int16_t left1 = (int16_t)((rx_buf[buf_idx + 1] << 8) | rx_buf[buf_idx]);
int16_t right1 = (int16_t)((rx_buf[buf_idx + 3] << 8) | rx_buf[buf_idx + 2]);
// [2번째 96kHz 샘플 파싱]
int16_t left2 = (int16_t)((rx_buf[buf_idx + 5] << 8) | rx_buf[buf_idx + 4]);
int16_t right2 = (int16_t)((rx_buf[buf_idx + 7] << 8) | rx_buf[buf_idx + 6]);
int16_t left_filtered = (int16_t)(((int32_t)left1 + left2) >> 1);
int16_t right_filtered = (int16_t)(((int32_t)right1 + right2) >> 1);
// 최종 48kHz I2S 버퍼에 음량 적용 후 적재
samples[i * 2 + 0] = (int16_t)(left_filtered * volume);
samples[i * 2 + 1] = (int16_t)(right_filtered * volume);
buf_idx += 8; // 96kHz 샘플 2개 분량(8바이트) 전진
i++;
}
buffer->sample_count = i;
give_audio_buffer(pool, buffer);
}
}
else if (is_playing) {
if (current_time - last_recv_time > AUDIO_TIMEOUT_MS) {
printf("🎵 Audio Timeout (Stream Stopped)\n");
is_playing = false;
for (int k = 0; k < 3; k++) {
struct audio_buffer *buffer = take_audio_buffer(pool, false);
if (buffer) {
int16_t *samples = (int16_t *)buffer->buffer->bytes;
for (uint i = 0; i < buffer->max_sample_count; i++) {
samples[i * 2 + 0] = 0;
samples[i * 2 + 1] = 0;
}
buffer->sample_count = buffer->max_sample_count;
give_audio_buffer(pool, buffer);
}
}
}
}
}
이렇게 수정후 96khz 음원이 정상적으로 출력 되는것을 확인 할 수 있다.
96khz, 16bit 고음질 음원으로 전송하면 네트웍 부하는 3.2Mbps로 증가 한다.
이정도면 Arduino 환경에서 가능하긴 하지만 96k, 32bit나 192khz 고음질로 넘어가면 네트웍 부하가 감당이 되지 않기 때문에 Pi Pico C/C++ SDK 개발환경에서 계속 해서 테스트를 진행해야 할것 같다. 앞으로 MP3와 같은 압축으로 인한 음원 손실이 있는 파이이 아니라 무손실 고음질의 PCM 데이터로 테스트 해볼 예정이다.

W55RP20에서 UDP를 이용한 96khz 고음질 음원 전송 테스트 결과
48khz보다 두배의 전송률로 전송 되는 고음질 오디오로 출력하면 확실히 음질은 좋은것 같다. 물론 카메라촬영 되면서 음질이 압축이되고 유튜브올라가면서 압축되어 정확한 원음을 확인하기 어렵지만.. 대략적 느낌을 위해