无线音频控制系统技术文档
一、概述
本系统为无线音频传输与控制系统,主要包含 TX(发送端) 和 RX(接收端)。
- TX端:采集音频(MIC/I2S/USB),处理音频数据并通过无线协议发送,同时发送控制状态字(1字节)。
- RX端:接收音频包和控制状态字,解析控制命令,触发对应功能(LED、音效、噪声控制等)。
控制状态通过 1 字节命令字传输,其中:
- 高 4 位:同步码
INFO_BIT_SYNC(0xA0),保证 RX 能正确解析 - 低 4 位:功能位(如
INFO_BIT_BLUENS),表示各功能开关状态
二、关键宏与变量
1. 功能位宏
#define INFO_BIT_BLUENS 1 // 蓝牙噪声消除
//#define INFO_BIT_BATLOW 2 // 其他功能位,可扩展
#define INFO_BIT_MASK 0x0F // 低4位为功能位
#define INFO_BIT_SYNC 0xA0 // 高4位同步码
2. 状态字变量
uint8_t InfoBits = INFO_BIT_SYNC; // 1字节状态字:高4位同步码,低4位功能位
3. 操作接口
uint8_t InfoGet(void) { return InfoBits; } // 获取当前状态字
- InfoBitsSet(bit):设置对应功能位
- InfoBitsClear(bit):清除对应功能位
三、TX端控制状态流程
- 事件触发
系统事件或按键触发:
case MSG_EFFECT_DENOISE:
InfoBitsSet(INFO_BIT_BLUENS); // 设置功能位
LedStateSet(); // LED 状态更新
break;
- 状态获取
uint8_t state = InfoGet(); // 返回 InfoBits
- 音频包构建与发送
在 audio_process() 中,每个音频包发送时,状态字随音频数据一起打包:
#ifdef KEY_REMOTE
audio_PackBuild(sbc_buf_out, sbcCnt, InfoGet());
#else
audio_PackBuild(sbc_buf_out, sbcCnt, 0xFF); // 没有遥控状态时填充默认值
#endif
- 说明:每个音频包都携带最新的状态字,保证 RX 端能实时更新功能状态。
四、RX端控制状态解析
接收端通过 cmd_process() 解析状态字:
if((RemoteMsg & (~INFO_BIT_MASK)) == INFO_BIT_SYNC) // 校验同步码
{
if(RemoteMsg & INFO_BIT_BLUENS)
InfoBitsSet(INFO_BIT_BLUENS); // 启用对应功能
else
InfoBitsClear(INFO_BIT_BLUENS); // 关闭功能
}
- 高 4 位同步码校验通过后,低 4 位表示功能状态
- RX 内部状态变量
InfoBits被更新 - 功能触发(如 LED 指示、音效处理)根据状态执行
五、音频处理流程 (audio_process())
1. 核心功能
- MIC/I2S/USB音频采集
- 音频效果处理(可选)
- 单声道 → 双声道转换
- 音频打包、SBC 编码
- 与控制状态字一起通过无线发送
2. 主要步骤
void audio_process(void)
{
// 计算抖动
#ifdef AUDIO_WIRELESS_SYNC_EN
if(AudioInSyncFrames <= AUDIOIN_SYNC_COUNT && AudioInSyncOffset > AUDIOIN_SYNC_JITTER)
Jitter = AudioInSyncOffset * 2 / 3;
#endif
// 判断是否有足够音频帧
#ifdef CODEC_DMAHALF_MODE
if(AudioADC1_DataLenGet_DMA_HALF() >= frame_size + Jitter && source_mic && sink_wireless_out)
#else
if(AudioADC1_DataLenGet() >= frame_size + Jitter && source_mic && sink_wireless_out)
#endif
{
// 清理音频缓冲区
PT_BUF_CLEAR(source_mic, PT_NET_FRAME(source_mic) * 4);
PT_BUF_CLEAR(source_usb_in, PT_NET_FRAME(source_usb_in) * 4);
PT_BUF_CLEAR(source_i2s_in, PT_NET_FRAME(source_i2s_in) * 4);
// MIC采集数据
#ifdef CODEC_DMAHALF_MODE
AudioADC1_DataGet_DMA_HALF(source_mic, frame_size - ADC_DeleteSample);
#else
AudioADC1_DataGet(source_mic, frame_size - ADC_DeleteSample);
#endif
// 单声道转双声道
upmix_1to2_apply(source_mic, source_mic, frame_size - ADC_DeleteSample);
// USB / I2S音频处理(可选)
UsbAudioSpeakerDataGet(source_usb_in, frame_size);
// 提示音混音处理(可选)
AudioRemindMixData(source_remind, frame_size);
// 音效处理或直接无线透传
AudioWirelessBypassProcess(...);
// SBC 编码并发送,每个音频包附带 InfoBits 状态字
#ifdef KEY_REMOTE
audio_PackBuild(sbc_buf_out, sbcCnt, InfoGet());
#else
audio_PackBuild(sbc_buf_out, sbcCnt, 0xFF);
#endif
}
}
六、状态字周期发送机制
- TX端:每个音频包都包含 InfoBits
- RX端:每次解析音频包,更新功能状态
- 保证状态实时性:无需额外指令,只需修改 InfoBits
七、功能位设计原则
- 可扩展性:低 4 位用于功能状态,未来可增加更多功能
- 同步性:高 4 位固定同步码
INFO_BIT_SYNC,防止误解析 - 单字节传输:符合无线传输带宽限制
- 软件控制:通过
InfoBitsSet()/InfoBitsClear()修改状态
八、TX → RX 控制状态流程图
┌─────────────┐
│ TX端事件/按键 │
│ SystemEventProcess() │
└───────┬─────┘
│
▼
┌─────────────┐
│ InfoBitsSet/ │
│ InfoBitsClear│
└───────┬─────┘
▼
┌─────────────┐
│ InfoGet() │
└───────┬─────┘
▼
┌─────────────┐
│ audio_PackBuild() │
│ SBC数据 + InfoBits │
└───────┬─────┘
▼
┌─────────────┐
│ 无线发送音频包 │
└───────┬─────┘
▼
┌─────────────┐
│ RX端接收音频包 │
└───────┬─────┘
▼
┌─────────────┐
│ 校验同步码 InfoBits & 0xF0 │
│ 解析低4位功能状态 │
└───────┬─────┘
▼
┌─────────────┐
│ RX端功能触发 │
│ LED / 音效 / 蓝牙噪声控制 │
└─────────────┘
九、总结
- InfoBits 是 TX → RX 功能状态的载体
- InfoGet() 提供接口获取状态字
- 每个音频包都携带状态字,保证 RX 实时更新
- 宏定义功能位便于扩展,保证代码可维护性
- 系统设计简单、高效、低带宽,占用无线传输资源少
这份文档可以作为开发和维护无线音频控制系统的技术说明书,涵盖了:
- 代码结构
- 控制状态字设计
- 音频处理流程
- TX → RX 状态传输机制
- 可扩展设计原则
/******************** TX 端音频处理主流程 ****************************/
void audio_process(void)
{
uint32_t CycCnt_Start; // 记录本帧处理起始 CPU cycle,用于 MCPS 统计
uint16_t Jitter = 0; // 音频同步抖动补偿采样数
// static unsigned char send_audio_cnt = 1; // 音频子包计数(通常为静态)
/* ===================== 音频同步抖动计算 ===================== */
#ifdef AUDIO_WIRELESS_SYNC_EN
// 当无线音频同步启用,并且同步帧数在允许范围内
// 同时检测到输入采样偏移,且偏移超过抖动阈值
if ((AudioInSyncFrames <= AUDIOIN_SYNC_COUNT) &&
(AudioInSyncOffset != 0) &&
(AudioInSyncOffset > AUDIOIN_SYNC_JITTER))
{
// 使用 2/3 偏移量作为丢样值,避免一次性修正过猛
Jitter = AudioInSyncOffset * 2 / 3;
}
#endif
/* ===================== 判断是否具备一帧处理条件 ===================== */
#ifdef CODEC_DMAHALF_MODE
// DMA half 模式下,检查 ADC half buffer 中的数据量
if ((AudioADC1_DataLenGet_DMA_HALF() >= frame_size + Jitter)
#else
// 普通模式下,检查 ADC buffer 中的数据量
if ((AudioADC1_DataLenGet() >= frame_size + Jitter)
#endif
&& source_mic // 麦克风源存在
&& sink_wireless_out) // 无线输出通道存在
{
uint32_t sbc_len = 0; // SBC 编码输出长度
// 记录当前 CPU cycle,用于统计本帧耗时
CycCnt_Start = __nds32__mfsr(NDS32_SR_PFMC0);
#ifdef CFG_FUNC_I2S_IN_DELAY_LOCK_EN
// 记录 ADC / I2S 采样数,用于后续时钟漂移校正
ADCSamples = AudioADC1_DataLenGet_DMA_HALF();
I2SDMASamples = I2SDMADataLenGet();
// I2S 与 ADC 采样率不同,进行比例换算
I2SDMASamples = I2SDMASamples * 441 / 480;
I2SSamples = I2SDateLenGet();
#endif
/* ===================== 清空各音频源 Buffer(防残留干扰) ===================== */
#ifdef CFG_FUNC_AUDIO_EFFECT_EN
// 开启音效时,清空音效源缓冲
AudioEffectFlowSourceClear();
#else
// 未开启音效时,清空各音频输入通道
PT_BUF_CLEAR(source_remind, PT_NET_FRAME(source_remind) * 4);
PT_BUF_CLEAR(source_mic, PT_NET_FRAME(source_mic) * 4);
PT_BUF_CLEAR(source_usb_in, PT_NET_FRAME(source_usb_in) * 4);
PT_BUF_CLEAR(source_i2s_in, PT_NET_FRAME(source_i2s_in) * 4);
#endif
/* ===================== 同步抖动处理(丢弃多余采样) ===================== */
#ifdef AUDIO_WIRELESS_SYNC_EN
if (Jitter)
{
#ifdef CODEC_DMAHALF_MODE
// 丢弃 Jitter 个 ADC 采样,用于时钟同步
AudioADC1_DataGet_DMA_HALF(MICInPcmBuf, Jitter);
#else
AudioADC1_DataGet(MICInPcmBuf, Jitter);
#endif
}
// 同步偏移已处理,清零
AudioInSyncOffset = 0;
#endif
/* ===================== 获取麦克风音频数据 ===================== */
#ifdef CODEC_DMAHALF_MODE
// 从 ADC 取一帧 PCM 数据(单声道)
AudioADC1_DataGet_DMA_HALF(source_mic, frame_size - ADC_DeleteSample);
#else
AudioADC1_DataGet(source_mic, frame_size - ADC_DeleteSample);
#endif
// 记录本帧输入采样数
get_inram_audiolen = frame_size - ADC_DeleteSample;
/* ===================== 正弦波测试信号(调试用) ===================== */
#ifdef AUDIO_SINE_TEST_EN
for (int i = 0; i < frame_size; i++)
{
source_mic[i] = SineMono[FrameOffset++ % TEST_PERIOD] / 2;
}
#endif
/* ===================== 单声道 → 双声道 ===================== */
// 麦克风输入为单声道,后续处理统一使用双声道
upmix_1to2_apply(source_mic, source_mic, frame_size - ADC_DeleteSample);
/* ===================== I2S 音频输入 ===================== */
#ifdef AUDIO_SOURCE_I2SIN
if (source_i2s_in && IS_PT_WIRELESSOUT_NET(source_i2s_in))
{
I2SInDateGet(source_i2s_in, frame_size);
}
#endif
/* ===================== ADC / I2S 同步删除采样 ===================== */
if (ADC_DeleteSample)
{
ADC_DeleteSample = 0;
#ifdef CFG_FUNC_I2S_IN_DELAY_LOCK_EN
if (I2S_DeleteSample)
{
// 删除 I2S 对应采样,保持多路同步
I2SDMADataProcess(I2S_DeleteSample);
memset(source_i2s_in, 0, frame_size * 4);
I2S_DeleteSample = 0;
}
#endif
#if defined(WIRELESS_TURNKEY5_3)
// 某些方案下直接静音一帧
memset(source_mic, 0, frame_size * 4);
#else
return; // 不支持时直接退出本帧
#endif
}
/* ===================== I2S / ADC 延迟锁定算法 ===================== */
#ifdef CFG_FUNC_I2S_IN_DELAY_LOCK_EN
{
static int32_t SampleSTotal = 0;
static int32_t Cnt = 0;
Cnt++;
// 计算 I2S 与 ADC 采样差
SampleSTotal += I2SSamples + I2SDMASamples - ADCSamples;
if (Cnt == 4)
{
SampleSTotal /= Cnt;
Cnt = 0;
// 根据偏差决定输入微调方向
if (SampleSTotal >= 81)
in_adjust = -1;
else if (SampleSTotal <= 79)
in_adjust = 1;
SampleSTotal = 0;
}
}
#endif
/* ===================== USB Audio 输入处理 ===================== */
#ifdef AUDIO_SOURCE_USB_AUDIO_IN_EN
// USB Speaker → 无线发送
if (source_usb_in && IS_PT_WIRELESSOUT_NET(source_usb_in))
{
UsbAudioSpeakProcess();
if ((UsbAudioSpeakerDataLenGet() > frame_size) &&
UsbSpeakerStateGet())
{
// 取一帧 USB PCM 数据
UsbAudioSpeakerDataGet(source_usb_in, frame_size);
// 音量渐变处理
...
}
UsbAudioSpeakProcess();
}
#endif
/* ===================== 提示音混音 ===================== */
#ifdef REMIND_EN
if (source_remind)
{
AudioRemindMixData(source_remind, frame_size);
// 单声道 → 双声道
for (int i = frame_size; i > 0; i--)
{
source_remind[2*(i-1)] = source_remind[i-1];
source_remind[2*i-1] = source_remind[i-1];
}
// 提示音音量控制
Audio_gain_control(...);
}
#endif
/* ===================== 音效 / 混音 ===================== */
#ifdef CFG_FUNC_AUDIO_EFFECT_EN
// 双声道音效处理
roboeffect_apply(context_memory);
#else
// 多路音频直通混音,输出到无线 / DAC / USB / I2S
AudioWirelessBypassProcess(
source_mic, source_usb_in, source_i2s_in, source_remind,
sink_dac, sink_usb_out, sink_wireless_out, sink_i2s_out,
frame_size);
#endif
/* ===================== 编码前声道处理 ===================== */
if (ENCODE_CH == 1)
{
// 编码为单声道时,双声道下混
downmix_2to1_apply(sink_wireless_out, sink_wireless_out, frame_size);
}
/* ===================== SBC 编码 ===================== */
if (device1.ConStatus != CONNECT_NONE)
{
wireless_sbc_encoder_aplly(
&sbc_enc,
sink_wireless_out,
&sbc_buf_out[SBC_ENC_LEN_PER_FREME * (send_audio_cnt - 1)],
&sbc_len);
/* ===================== 音频包构建 + 无线发送 ===================== */
if (((send_audio_cnt % RFPACK_NAUDIO) == 0) &&
(Wireless_TransSpaceLen() > RFAUDIO_TRANS_LEN))
{
send_audio_cnt = 1;
#ifdef KEY_REMOTE
// 音频包中附带 1 字节控制信息(TX → RX)
audio_PackBuild(sbc_buf_out, sbcCnt, InfoGet());
#else
audio_PackBuild(sbc_buf_out, sbcCnt, 0xFF);
#endif
// 写入无线发送 FIFO
Wireless_TransBufWrite(&sbc_buf_out[CRC_PACKSUB],
RFAUDIO_TRANS_LEN);
get_inram_audiolen = 0;
}
else
{
send_audio_cnt++;
}
sbcCnt++;
}
/* ===================== 本地音频输出 ===================== */
#ifndef PACKET_AUDIO_CH_BACKWARD
#ifdef DAC0_EN
if (sink_dac && IS_PT_WIRELESSOUT_NET(sink_dac))
{
downmix_2to1_apply(sink_dac, sink_dac, frame_size);
AudioDAC0_DataSet(sink_dac, frame_size);
}
#endif
#endif
/* ===================== 性能统计 ===================== */
LastEncodeCyc = __nds32__mfsr(NDS32_SR_PFMC0) - CycCnt_Start;
CycCnt = LastEncodeCyc;
}
/* ===================== MCPS 调试统计 ===================== */
#ifdef DEBUG_LOG_AUDIO_MCPS_EN
uint16_t McpsVal = CycCnt * (SAMPLE_RATE / frame_size) / 1000000 + 30;
if (McpsVal > McpsMax)
{
McpsMax = McpsVal;
DBG("AudioMax:%d M\n", McpsVal);
}
#endif
}