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; 影响本次的播放,

}

相关推荐
航Hang*21 小时前
VMware vSphere 云平台运维与管理基础——第5章:VMware vSphere 5.5 高级特性
运维·服务器·开发语言·windows·学习·虚拟化
kaico201821 小时前
python操作数据库
开发语言·数据库·python
被摘下的星星21 小时前
MySQL 别名使用规则详解
数据库·mysql
zhangzeyuaaa21 小时前
Python变量的四种作用域
开发语言·python
范纹杉想快点毕业21 小时前
C语言全能实战教程
c语言·开发语言
墨着染霜华21 小时前
MySQL 重复数据删除语句
数据库·mysql
ego.iblacat21 小时前
PostgreSQL 数据库
数据库·postgresql
Hommy8821 小时前
【开源剪映小助手-客户端】桌面客户端
python·开源·node.js·github·剪映小助手
大空大地202621 小时前
程序调试与异常处理
开发语言
二等饼干~za89866821 小时前
源码可控:云罗 GEO 源头工厂,开源搭建 + 二次开发全链路解决方案
服务器·开发语言·开源·php·音视频·ai-native