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

}

相关推荐
java1234_小锋2 小时前
[免费]基于Python的天气预报(天气预测分析)(Django+sklearn机器学习+selenium爬虫)可视化系统【论文+源码+SQL脚本】
爬虫·python·selenium·天气预报·天气预测
TG:@yunlaoda360 云老大2 小时前
华为云国际站代理商WeLink的资源与工具支持具体有哪些?
服务器·数据库·华为云
Qhumaing2 小时前
解决因为jupyter notebook修改路径下没有c.NotebookApp.notebook_dir而无法修改目录问题
ide·python·jupyter
3824278272 小时前
python3网络爬虫开发实战 第2版:使用aiohttp
开发语言·爬虫·python
TG:@yunlaoda360 云老大2 小时前
华为云国际站代理商申请跨账号代维权限的流程复杂吗?
网络·数据库·华为云
云老大TG:@yunlaoda3602 小时前
华为云国际站代理商MSGSMS的服务质量如何?
大数据·数据库·人工智能·华为云
m0_672656542 小时前
JavaScript性能优化实战技术文章大纲
开发语言·javascript·性能优化
Yang-Never2 小时前
Android 内存泄漏 -> LiveData如何解决ViewMode和Activity/Fragment之间的内存泄漏
android·java·开发语言·kotlin·android studio