Files
DS5Dongle-OLED-Edition-stea…/src/audio.cpp
T

192 lines
6.5 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// Created by awalol on 2026/3/5.
//
#include "audio.h"
#include "bt.h"
#include "resample.h"
#include "tusb.h"
#include "usb.h"
#include <algorithm>
#include <cstdio>
#include "opus.h"
#include "utils.h"
#include "pico/multicore.h"
#include "pico/util/queue.h"
#define INPUT_CHANNELS 4
#define OUTPUT_CHANNELS 2
#define SAMPLE_SIZE 64
#define REPORT_SIZE 398
#define REPORT_ID 0x36
// #define VOLUME_GAIN 2
#define BUFFER_LENGTH 48
using std::clamp;
using std::max;
static WDL_Resampler resampler;
static uint8_t reportSeqCounter = 0;
static uint8_t packetCounter = 0;
static bool plug_headset = false;
alignas(8) static uint32_t audio_core1_stack[8192];
queue_t audio_fifo;
queue_t opus_fifo;
struct audio_raw_element {
float data[512 * 2];
};
struct opus_element {
uint8_t data[200];
};
void set_headset(bool state) {
plug_headset = state;
}
// 有一个重构的想法,就是也把haptics也放进队列里面,使用定时器来发送数据
// 定时器伪代码:
// static uint8_t haptics_buf[64];
// static uint8_t speaker_buf[200];
// static auto last = time_us32();
// auto now = time_us32();
// if(now - last < 10666) return;
// func: send haptics and speaker;
// try_queue_remove - haptics and speaker
// 缺点是可能会导致haptics有延迟,不够实时?
void audio_loop() {
// 1. 读取 USB 音频数据
if (!tud_audio_available()) return;
int16_t raw[192];
uint32_t bytes_read = tud_audio_read(raw, sizeof(raw)); // 每次读入 384 bytes
int frames = bytes_read / (INPUT_CHANNELS * sizeof(int16_t));
if (frames == 0) {
return;
}
static float audio_buf[512 * 2];
static uint audio_buf_pos = 0;
// 2. 从4ch中提取ch3/ch4,转换为float输入重采样器
WDL_ResampleSample *in_buf;
int nframes = resampler.ResamplePrepare(frames, OUTPUT_CHANNELS, &in_buf);
for (int i = 0; i < nframes; i++) {
audio_buf[audio_buf_pos++] = raw[i * INPUT_CHANNELS] / 32768.0f * (volume[0] - 1.0f);
audio_buf[audio_buf_pos++] = raw[i * INPUT_CHANNELS + 1] / 32768.0f * (volume[0] - 1.0f);
if (audio_buf_pos == 512 * 2) {
static audio_raw_element element{};
memcpy(element.data,audio_buf,512 * 2 * 4);
if (queue_is_full(&audio_fifo)){
queue_try_remove(&audio_fifo,NULL);
}
if (!queue_try_add(&audio_fifo,&element)) {
printf("[Audio] Warning: audio_fifo add failed\n");
}
audio_buf_pos = 0;
}
in_buf[i * 2] = (WDL_ResampleSample) raw[i * INPUT_CHANNELS + 2] / 32768.0f;
in_buf[i * 2 + 1] = (WDL_ResampleSample) raw[i * INPUT_CHANNELS + 3] / 32768.0f;
}
// 3. 48kHz -> 3kHz 重采样
static WDL_ResampleSample out_buf[SAMPLE_SIZE]; // 64 floats = 32帧 × 2ch
int out_frames = resampler.ResampleOut(out_buf, nframes, SAMPLE_SIZE / OUTPUT_CHANNELS, OUTPUT_CHANNELS);
static int8_t haptic_buf[SAMPLE_SIZE];
static int haptic_buf_pos = 0;
// 4. 转换为int8并缓冲,满64字节即组包发送
for (int i = 0; i < out_frames; i++) {
int val_l = (int) (out_buf[i * 2] * 127.0f * max(volume[1],1.0f));
int val_r = (int) (out_buf[i * 2 + 1] * 127.0f * max(volume[1],1.0f));
haptic_buf[haptic_buf_pos++] = (int8_t) clamp(val_l, -128, 127); // 似乎clamp有点多余?还是以防万一吧
haptic_buf[haptic_buf_pos++] = (int8_t) clamp(val_r, -128, 127);
if (haptic_buf_pos != SAMPLE_SIZE) {
continue;
}
uint8_t pkt[REPORT_SIZE]{};
pkt[0] = REPORT_ID;
pkt[1] = reportSeqCounter << 4;
reportSeqCounter = (reportSeqCounter + 1) & 0x0F;
pkt[2] = 0x11 | (1 << 7);
pkt[3] = 7;
pkt[4] = 0b11111110;
pkt[5] = BUFFER_LENGTH;
pkt[6] = BUFFER_LENGTH;
pkt[7] = BUFFER_LENGTH;
pkt[8] = BUFFER_LENGTH;
pkt[9] = BUFFER_LENGTH; // buffer length
pkt[10] = packetCounter++;
pkt[11] = 0x12 | (1 << 7);
pkt[12] = SAMPLE_SIZE;
memcpy(pkt + 13, haptic_buf, SAMPLE_SIZE);
static opus_element opus_packet{};
if (queue_try_remove(&opus_fifo,&opus_packet)) {
pkt[77] = (plug_headset ? 0x16 : 0x13) | 0 << 6 | 1 << 7; // Speaker: 0x13
// L Headset Mono: 0x14
// L Headset R Speaker: 0x15
// Headset: 0x16
pkt[78] = 200;
memcpy(pkt + 79,opus_packet.data,200);
}else {
printf("[Audio] Warning: opus_fifo try remove failed\n");
}
bt_write(pkt, sizeof(pkt));
haptic_buf_pos = 0;
}
}
void audio_init() {
resampler.SetMode(true, 0, false);
resampler.SetRates(48000, 3000);
resampler.SetFeedMode(true);
// resampler.Prealloc(2, 480, 32);
queue_init(&audio_fifo,sizeof(audio_raw_element),2);
queue_init(&opus_fifo,sizeof(opus_element),2);
multicore_launch_core1_with_stack(core1_entry,audio_core1_stack,sizeof(audio_core1_stack));
}
static OpusEncoder *encoder;
static WDL_Resampler resampler_audio;
void core1_entry() {
int error = 0;
encoder = opus_encoder_create(48000,2,OPUS_APPLICATION_AUDIO,&error);
if (error != 0) {
printf("[Audio] OpusEncoder create failed\n");
return;
}
opus_encoder_ctl(encoder,OPUS_SET_EXPERT_FRAME_DURATION(OPUS_FRAMESIZE_10_MS));
opus_encoder_ctl(encoder,OPUS_SET_BITRATE(200 * 8 * 100));
opus_encoder_ctl(encoder,OPUS_SET_VBR(false));
opus_encoder_ctl(encoder,OPUS_SET_COMPLEXITY(0)); // max 4
resampler_audio.SetMode(true,0,false);
resampler_audio.SetRates(51200,48000);
resampler_audio.SetFeedMode(true);
while (true) {
static audio_raw_element audio_element{};
queue_remove_blocking(&audio_fifo,&audio_element);
// 将 512 frames 重采样成 480 frames 以解决噪音问题。感谢 @Junhoo
WDL_ResampleSample *in_buf;
int nframes = resampler_audio.ResamplePrepare(512, 2, &in_buf);
for (int i = 0; i < nframes * 2;i++) {
in_buf[i] = audio_element.data[i];
}
static WDL_ResampleSample out_buf[480 * 2];
resampler_audio.ResampleOut(out_buf,nframes,480,2);
static opus_element opus_packet{};
(void)opus_encode_float(encoder,out_buf,480,opus_packet.data,200);
if (queue_is_full(&opus_fifo)) {
queue_try_remove(&opus_fifo,NULL);
}
if (!queue_try_add(&opus_fifo,&opus_packet)) {
printf("[Audio] Warning: opus_fifo add failed\n");
}
}
}