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 byte[4096]; // MP3帧通常小于4KB
int framesSent = 0;

while (true)
{
if (dic_Sockets[inn1.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 byte[1+3+ frame.RawData.Length+2];

buffer2[0] = 111;//mp3 play

buffer2[1] = (byte)((frame.RawData.Length)&0xff);

buffer2[2] = (byte)((frame.RawData.Length>>8) & 0xff);

buffer2[3] = (byte)((frame.RawData.Length >> 16) & 0xff);

buffer2[4 + frame.RawData.Length] = 111;//end maker

buffer2[4 + 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_Sockets[inn1.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_Sockets[inn1.clientUrl1].wait_ack.Reset();

try
{
await dic_Sockets[inn1.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_Sockets[inn1.clientUrl1].wait_ack.WaitOne(800);

if (isok_ret1 == true && dic_Sockets[inn1.clientUrl1].ACK_NUM == 111)
{

break;

}
else
{

log.fvdou_append_error_to_log_txtH("conn Send bytes err", $"dic_Sockets[inn1.clientUrl1].ACK_NUM = {dic_Sockets[inn1.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 buffer[64 * 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_ptr[0]==111&&ws_reassembler->length==0)||is_fenbao_ing==1)) || (ws_reassembler->length>0) ){

if(data->data_ptr[0]==111&&ws_reassembler->length==0){

len_frame_= data->data_ptr[1]+data->data_ptr[2]*256+data->data_ptr[3]*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 sendata[1];

if(len<6 || data[len-1]!=111||data[len-2]!=111){

/* sendata[0]=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 ||data[len-1]!=111||data[len-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");

}

}

/* sendata[0]=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; 影响本次的播放,

}

相关推荐
黎雁·泠崖9 小时前
【魔法森林冒险】1/14 项目总览:用Java打造你的第一个回合制冒险游戏
java·开发语言
独好紫罗兰9 小时前
对python的再认识-基于数据结构进行-a006-元组-拓展
开发语言·数据结构·python
Dfreedom.9 小时前
图像直方图完全解析:从原理到实战应用
图像处理·python·opencv·直方图·直方图均衡化
C++ 老炮儿的技术栈9 小时前
Qt 编写 TcpClient 程序 详细步骤
c语言·开发语言·数据库·c++·qt·算法
怣509 小时前
MySQL子查询零基础入门教程:从小白到上手(零基础入门版)
数据库·mysql
yuuki2332339 小时前
【C++】继承
开发语言·c++·windows
222you9 小时前
Redis的主从复制和哨兵机制
java·开发语言
铉铉这波能秀9 小时前
LeetCode Hot100数据结构背景知识之集合(Set)Python2026新版
数据结构·python·算法·leetcode·哈希算法
码界调试侠9 小时前
MongoDB 常用查询语法
数据库·mongodb
静听山水9 小时前
StarRocks导入数据【Stream Load】
数据库