websocket下发mp3帧数据时一个包被分包为几个子包而导致mp3解码失败而播放卡顿有杂音或断播的解决方法

websocket下发mp3帧数据时一个包被分包为几个子包而导致mp3解码失败而播放卡顿有杂音或断播的解决方法:

websocket发的mp3帧数据格式自定义为:

111+3字节mp3帧长度+mp3帧数据+111+111

如果想可靠传输mp3数据来播放:

第一 :服务器下发音频数据时需要一帧一帧的发,不能合在一起发,

第二:tcp收到的包可能被拆分为2-3个包了,然后就不完整,mp3解码就失败,识别到分包后需要合并后再送去mp3解码

服务器下发音频数据时需要一帧一帧的发:

private async Task StreamMp3WithNaudioSimpleAsync(Stream mp3Stream, mp3_play_t_do_info inn1,
CancellationToken cancellationToken = default)
{

// 直接使用NAudio的Mp3FileReader,它内部会处理帧解析
using (var mp3Reader = new NAudio.Wave.Mp3FileReader(mp3Stream))
{
//byte\[\] frameBuffer = new byte4096; // MP3帧通常小于4KB
int framesSent = 0;

while (true)
{
if (dic_Socketsinn1.clientUrl1.is_need_end_the_tts_Sending == 1)
{
await SendCompletionAsync(inn1, cancellationToken);
return;
}

// 读取一个MP3帧
var frame = mp3Reader.ReadNextFrame();
if (frame == null)
break; // 没有更多帧

byte\[\] buffer2 = new byte1+3+ frame.RawData.Length+2;

buffer20 = 111;//mp3 play

buffer21 = (byte)((frame.RawData.Length)&0xff);

buffer22 = (byte)((frame.RawData.Length>>8) & 0xff);

buffer23 = (byte)((frame.RawData.Length >> 16) & 0xff);

buffer24 + frame.RawData.Length = 111;//end maker

buffer24 + frame.RawData.Length+1 = 111;//end maker

int need_ack_mode = 0;

Array.Copy(frame.RawData, 0, buffer2, 4, frame.RawData.Length);

if (need_ack_mode == 0)
{

try
{
await dic_Socketsinn1.clientUrl1.conn.Send(buffer2);
}
catch (Exception err1)
{
log.fvdou_append_error_to_log_txtH("conn Send bytes err", $"{err1.Message}{err1.StackTrace}");
return;
}

// await Task.Delay(4);

}

if (need_ack_mode == 1)
{

int max_trynum = 0;

while (max_trynum <= 4)
{
max_trynum++;

dic_Socketsinn1.clientUrl1.wait_ack.Reset();

try
{
await dic_Socketsinn1.clientUrl1.conn.Send(buffer2);
}
catch (Exception err1)
{
log.fvdou_append_error_to_log_txtH("conn Send bytes err", $"{err1.Message}{err1.StackTrace}");
return;
}

bool isok_ret1 = dic_Socketsinn1.clientUrl1.wait_ack.WaitOne(800);

if (isok_ret1 == true && dic_Socketsinn1.clientUrl1.ACK_NUM == 111)
{

break;

}
else
{

log.fvdou_append_error_to_log_txtH("conn Send bytes err", $"dic_Socketsinn1.clientUrl1.ACK_NUM = {dic_Socketsinn1.clientUrl1.ACK_NUM}");

}

}
}

framesSent++;

// 每10帧让出一次,避免阻塞
if (framesSent % 20 == 0)
await Task.Delay(3);
}

log.fvdou_append_error_to_log_txtH("StreamMp3WithNaudioSimpleAsync",
$"{inn1.mac_id}发送完成,共 {framesSent} 帧");
await SendCompletionAsync(inn1, cancellationToken);
}
}

=======================================================

单片机端:

// 定义 WebSocket 数据重组器

typedef struct {

uint8_t buffer64 \* 1024; // 64KB 缓冲区

size_t length;

bool expecting_more;

uint8_t opcode;

} websocket_reassembler_t;

static websocket_reassembler_t * ws_reassembler;

。。。。。。

int len_frame_=0;

static void websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)

{

。。。。。。。

case WEBSOCKET_EVENT_DATA:

if (data->op_code == 0x2) { // Opcode 0x2 indicates binary data

if((((data->data_ptr0==111&&ws_reassembler->length==0)||is_fenbao_ing==1)) || (ws_reassembler->length>0) ){

if(data->data_ptr0==111&&ws_reassembler->length==0){

len_frame_= data->data_ptr1+data->data_ptr2*256+data->data_ptr3*256*256;

len_frame_=len_frame_+1+3+2;//1帧头+3字节长度+2字节帧尾。

if(len_frame_!=data->data_len){//如果分包发生了!!!

is_fenbao_ing=1;

}else{

is_fenbao_ing=0;

len_frame_=0;

}

}

if(is_fenbao_ing==1){

ESP_LOGI(TAG, "is_fenbao_ing=1,ws_reassembler->length=%d ,len_frame_=%d ",ws_reassembler->length,len_frame_);

}

if(!data->fin || (len_frame_!=0&&len_frame_>(ws_reassembler->length+data->data_len ) )) {

// 这是分片帧的一部分

if(!ws_reassembler->expecting_more) {

// 开始新的分片序列

ws_reassembler->opcode = data->op_code ;

ws_reassembler->length = 0;

ws_reassembler->expecting_more = true;

}

// 存储数据

if(ws_reassembler->length + data->data_len <= sizeof(ws_reassembler->buffer)) {

memcpy(ws_reassembler->buffer + ws_reassembler->length, data->data_ptr, data->data_len);

ws_reassembler->length += data->data_len;

}

ESP_LOGI(TAG, "not data->fin data come!");

} else {

// FIN 为 true,这是最后一片或独立消息

if(ws_reassembler->expecting_more) {

// 这是分片的最后一部分

if(ws_reassembler->length + data->data_len <= sizeof(ws_reassembler->buffer)) {

memcpy(ws_reassembler->buffer + ws_reassembler->length, data->data_ptr, data->data_len);

ws_reassembler->length += data->data_len;

// 现在有了完整消息,处理它

process_complete_mp3_data(ws_reassembler->buffer, ws_reassembler->length);

// 重置重组器

ws_reassembler->length = 0;

ws_reassembler->expecting_more = false;

}

} else {

// 独立消息,直接处理

process_complete_mp3_data((uint8_t * )data->data_ptr, data->data_len);

}

is_fenbao_ing=0;

}

。。。。。。。。

uint8_t is_fenbao_ing=0;

void play_A_mp3_stream(int len,const char * datas);

void process_complete_mp3_data(uint8_t *data, size_t len) {

// 验证是否是完整的 MP3 数据

//printf("收到完整 WebSocket 消息,长度: %d\n", len);

char sendata1;

if(len<6 || datalen-1!=111||datalen-2!=111){

/* sendata0=222;

esp_websocket_client_send_bin_partial(client, sendata, sizeof(sendata), portMAX_DELAY);

esp_websocket_client_send_fin(client, portMAX_DELAY);

*/

is_fenbao_ing=1;

ESP_LOGE(TAG, "len<6 ||datalen-1!=111||datalen-2!=111 len=%d\r\n",len);

ESP_LOG_BUFFER_HEX("Received wrong binary data", data, len);

return;

}

is_fenbao_ing=0;

// 发送到 ringbuffer

if(buf_handle_for_mp3_play != nullptr) {

UBaseType_t res2 = xRingbufferSend(

buf_handle_for_mp3_play,

data,

len-2, //减去末尾2字节

pdMS_TO_TICKS(4000)

);

if(res2 != pdTRUE) {

printf("Failed to send item to ringbuffer\n");

}

}

/* sendata0=111;

esp_websocket_client_send_bin_partial(client, sendata, sizeof(sendata), portMAX_DELAY);

esp_websocket_client_send_fin(client, portMAX_DELAY);*/

G_is_can_INTO_VAD=0;

G_is_VAD_ing=0;

G_left_times_to_INTO_VAD=0;//防止上次的mp3结束时的 G_left_times_to_INTO_VAD=40; 影响本次的播放,

}

相关推荐
曹牧21 分钟前
oracle:“not all variables bound”
数据库·oracle
数据库百宝箱30 分钟前
Oracle RMAN Image Copy 本地恢复
数据库·oracle
肖永威1 小时前
Python多业务并行计算框架插件化演进:从硬编码到动态注册
python·插件化·并行计算·动态注册
yz_aiks1 小时前
Linux Jar包配置Systemd自启动实战:从排查到配置全流程
linux·python·jar·自启动·systemd
threelab1 小时前
Three.js 物理模拟着色器 | 三维可视化 / AI 提示词
开发语言·前端·javascript·人工智能·3d·着色器
武器大师721 小时前
lv_binding_js 代码解读
开发语言·javascript·ecmascript
不知名的老吴1 小时前
线程的生命周期之线程“插队“
java·开发语言·python
zuYM4g7Dp2 小时前
NoSql数据库设计心得
数据库·nosql
kaikaile19952 小时前
数字全息图处理系统(C# 实现)
开发语言·c#
xsc6996752 小时前
从零搭建大模型与智能体平台 - 完整技术详解
python