本文借助CI230X芯片SDK 的代码来学习如何实现 CI1303 播放外部音频功能。
CI1303+Wi-Fi Combo=CI230X

cias_network_msg_protocol.c
network_recv_data_task 函数代码解析
这个函数是一个FreeRTOS任务,用于接收和处理网络数据。让我逐行分析它的功能:
void network_recv_data_task(void *parameter)
{
static int32_t recv_package_length = 0; // 存储接收包的长度
static int32_t read_start_flag = 0; // 标记是否开始读取数据
static int32_t curr_net_player_cache = 0; // 当前网络播放器缓存大小
int32_t package_length = 0; // 当前包长度
int8_t msg_state = NET_MSG_IDE; // 消息状态,初始为空闲状态
BaseType_t err; // FreeRTOS错误代码
cias_data_standard_head_t *data_header; // 指向数据包头部的指针
uint32_t cache_empty_num = 0; // 缓存空计数器
cur_play_stream = tts_player; // 设置当前播放流为TTS播放器
函数开始初始化了多个变量,包括接收包长度、读取标志、缓存大小等。然后进入一个无限循环进行消息处理:
while (1)
{
/****play deal****/
#if NETWORK_AUDIO_PLAYER_ENABLE
在循环中,首先处理音频播放相关逻辑(如果启用了网络音频播放器):
curr_net_player_cache = audio_play_stream_buffer_get_spaces_size(cur_play_stream);
获取当前播放流的可用缓冲区大小。
if ((curr_net_player_cache == IOT_AUDIO_PLAY_BUF_SIZE) && (get_net_player_status(get_net_play_type()) == NET_PLAYER_START))
{
cache_empty_num++;
int stop_empt_num = 400;
if (get_net_play_type() == PLAY_M4A)
{
stop_empt_num = 5;
}
如果缓存完全空且播放器状态为已启动,增加空缓存计数器。根据播放类型设置不同的阈值。
if ((cache_empty_num >= stop_empt_num) && (!no_audio_data_flag))
{
cache_empty_num = 0;
if (!recv_music_end_sem)
{
no_audio_data_flag = true;
当空缓存计数超过阈值且未标记无音频数据时,重置计数器并处理不同播放类型的情况:
if ((get_net_play_type() == PLAY_M4A))
{
if (read_start_flag == 0)
{
pause_play(NULL, NULL);
}
else
{
no_audio_data_flag = false;
}
}
else if (get_net_play_type() != PLAY_TTS)
{
pause_play(NULL, NULL);
}
else
{
stop_play(NULL, NULL);
}
根据播放类型执行不同操作:M4A类型根据读取标志决定暂停或继续;非TTS类型暂停播放;TTS类型停止播放。
else if (get_net_player_status(get_net_play_type()) == NET_PLAYER_READY)
{
read_start_flag = 1;
}
else
{
cache_empty_num = 0;
}
如果播放器状态为就绪,设置读取标志;否则重置空缓存计数。
if(start_recv_flag && (curr_net_player_cache >= 6*1024))
{
cias_send_cmd(PLAY_DATA_RECV, DEF_FILL);
start_recv_flag = false;
}
#endif //NETWORK_AUDIO_PLAYER_ENABLE
当需要接收数据且缓存空间大于6KB时,发送数据接收命令并重置接收标志。
接下来是网络消息接收和处理部分:
#if NEWORK_RECV_QUEUE_ENABLE
package_length = 0;
while(pdPASS == xQueueReceive(network_msg_recv_queue, &package_data[package_length], pdMS_TO_TICKS(50)))
{
package_length++;
从消息队列接收数据,每次接收一个字节,持续接收直到超时(50ms)。
if(msg_state == NET_MSG_IDE)
{
msg_state = NET_MSG_HEAD;
}
如果当前为空闲状态,转为头部接收状态。
else if (msg_state == NET_MSG_HEAD && package_length > (sizeof(cias_data_standard_head_t)-2))
{
data_header = (cias_data_standard_head_t *)package_data;
if(data_header->magic == CIAS_STANDARD_MAGIC)
{
msg_state = NET_MSG_DATE;
}
else
msg_state = NET_MSG_ERR;
}
当接收足够数据形成头部后,验证魔数是否正确。若正确,状态转为数据接收;否则,标记为错误状态。
else if(msg_state == NET_MSG_DATE && package_length > (data_header->len + sizeof(cias_data_standard_head_t) - 1))
{
data_header = (cias_data_standard_head_t *)package_data;
recv_package_length = data_header->len + sizeof(cias_data_standard_head_t);
wifi_msg_deal((uint8_t *)package_data, recv_package_length);
package_length = 0;
memset(package_data,0,1050);
msg_state = NET_MSG_IDE;
}
当数据接收完成后,计算包总长度,调用wifi_msg_deal函数处理消息,然后重置缓冲区和状态。
if(msg_state == NET_MSG_ERR)
{
ci_logdebug(LOG_USER, "ci_standard_head_t error\r\n");
package_length = 0;
memset(package_data,0,1050);
msg_state = NET_MSG_IDE;
}
}
如果状态为错误,输出日志,重置缓冲区和状态。
if(msg_state != NET_MSG_IDE)
{
ci_logdebug(LOG_USER, "recv data timeout error(%d)\r\n",package_length);
package_length = 0;
memset(package_data, 0, 1050);
msg_state = NET_MSG_IDE;
}
#endif
}
}
如果循环结束时状态不是空闲状态,说明接收超时,记录日志并重置所有状态。
network_recv_data_task 函数功能总结
该函数实现了一个网络数据接收和处理任务,主要功能包括:
- 音频播放管理:
- 监控音频缓冲区状态,根据缓存情况决定是否请求更多数据
- 处理不同类型音频(M4A、TTS等)的播放控制逻辑
- 网络消息接收:
- 通过FreeRTOS消息队列接收数据
- 实现了一个状态机处理消息格式(空闲→头部→数据)
- 验证消息头部魔数确保数据有效性
- 异常处理:
- 处理数据格式错误情况
- 处理接收超时情况
- 重置相关缓冲区和状态
接下来我们通过阅读 wifi_msg_deal 函数代码看看当数据接收完成后是如何执行的。
wifi_msg_deal 函数代码解析
我们可以看到 wifi_msg_deal 函数接受两个参数:msg_buf(消息缓冲区)和 msg_len(消息长度),然后根据消息类型调用相应的回调函数(如tuya_ir_wifi_msg_callback、tencent_iot_light_network_callback、aux_aiot_aircondition_callback等)处理不同类型的消息。
/**
* @brief WIFI 消息处理
*
* @param msg_buf 数据信息
* @param msg_len 数据长度
* @return int32_t RETURN_OK 处理成功,RETURN_ERR 数据异常
*/
int32_t wifi_msg_deal(uint8_t *msg_buf, int32_t msg_len)
{
int8_t net_config_fail_state = 0;
if ((msg_buf == NULL) || (msg_len <= 0))
{
return RETURN_ERR;
}
#if TUYA_IR_CTRL_DEMO_ENABLE | CI_IR_CTRL_DEMO_ENABLE //涂鸦红外遥控器demo
tuya_ir_wifi_msg_callback(msg_buf);
#endif
#if TENCENT_IOT_LIGHT_DEMO_ENABLE
tencent_iot_light_network_callback(msg_buf);
#endif
#if CIAS_DOUBLE_MIC_DEMO_ENABLE | CIAS_SIGNAL_MIC_DEMO_ENABLE
aux_aiot_aircondition_callback(msg_buf);
#endif
#if CIAS_FLASH_IMAGE_UPGRADE_ENABLE
cias_data_standard_head_t *pheader = (cias_data_standard_head_t *)(msg_buf);
if(pheader->type == CIAS_OTA_START)
dpmu_software_reset_system_config();
#endif
int32_t ret = RETURN_OK;
return ret;
}
接下来我们通过阅读 aux_aiot_aircondition_callback 函数代码看看后续是如何执行的。
aux_aiot_aircondition_callback.c
aux_aiot_aircondition_callback.c
aux_aiot_aircondition_callback 函数代码解析
这个函数处理从网络接收到的消息,并根据消息类型执行相应的操作。下面是详细的逐行分析:
int32_t aux_aiot_aircondition_callback(uint8_t *msg_buf)
{
函数定义,接收一个指向消息缓冲区的指针,返回int32_t类型的结果。
int32_t ret = RETURN_OK;
初始化返回值为RETURN_OK,表示默认处理成功。
static connext_cloud = false;
定义一个静态变量connext_cloud,初始值为false,用于跟踪设备是否连接到云端。静态变量会在函数调用之间保持其值。
/*这里将数据发送到需要的BUF中,例如下面*/
注释行,提供了代码意图的说明。
cias_data_standard_head_t *pheader = (cias_data_standard_head_t *)(msg_buf);
将输入的msg_buf转换为cias_data_standard_head_t类型的指针,以访问消息的头部信息。
wifi_communicate_cmd_t wifi_cmd = (wifi_communicate_cmd_t)pheader->type;
从消息头部提取命令类型,保存到wifi_cmd变量中。
if (wifi_cmd != PLAY_DATA_GET)
ci_logdebug(LOG_MEDIA, "[qcloud aux:]recv type: %x\n", wifi_cmd);
如果命令不是PLAY_DATA_GET(可能这类命令太频繁),则记录日志输出接收到的命令类型。这有助于调试但避免了过多的日志信息。
switch (wifi_cmd)
{
开始一个switch语句,根据命令类型执行不同的处理逻辑。
#if AUDIO_PLAYER_ENABLE
case PLAY_DATA_GET:
case NET_PLAY_START:
case NET_PLAY_STOP:
case NET_PLAY_PAUSE:
case NET_PLAY_RESUME :
case PLAY_DATA_END:
{
network_audio_processing(msg_buf);
break;
}
#endif //AUDIO_PLAYER_ENABLE
如果启用了音频播放器,这段代码处理与音频播放相关的所有命令。当收到这些命令时,调用network_audio_processing函数处理消息。
case NET_VOLUME:
{
if(!connext_cloud)
break;
处理网络音量命令。如果设备未连接到云端(connext_cloud为false),则跳过此处理。这可能是为了防止在未建立云连接时接收到不可信的音量控制信息。
int vol = *((unsigned int *)(msg_buf + 16))/14;
从消息中提取音量值(位于消息缓冲区偏移16字节的位置),并将其除以14进行归一化处理。
if(vol < VOLUME_MIN)
{
vol = VOLUME_MIN;
}
确保音量不低于最小值。如果计算出的音量小于最小值,则将其设为最小值。
vol_set(vol);
break;
}
设置系统音量为计算出的值,并退出当前case。
case GET_PROFILE:
{
ci_logdebug(LOG_MEDIA, "RECV GET PROFILE CMD\n");
cias_send_profile();
处理获取配置文件的命令。记录日志并调用cias_send_profile函数发送设备配置文件。
while(CI_SS_PLAY_STATE_PLAYING == ciss_get(CI_SS_PLAY_STATE))
{
vTaskDelay(1000);
}
等待当前播放完成。检查播放状态,如果处于播放中状态,则延迟1000ms后再次检查,直到播放完成。
prompt_play_by_cmd_id(1004, -1, play_done_callback, true);
break;
}
播放提示音(ID为1004),使用play_done_callback作为播放完成回调,最后退出当前case。
case NEED_PROFILE:
{
while(CI_SS_PLAY_STATE_PLAYING == ciss_get(CI_SS_PLAY_STATE))
{
vTaskDelay(1000);
}
处理需要配置文件的命令。同样等待当前播放完成。
ci_logdebug(LOG_MEDIA, "RECV NEED PROFILE CMD\n");
prompt_play_by_cmd_id(1005, -1, play_done_callback, true);
break;
}
记录日志并播放另一个提示音(ID为1005),然后退出当前case。
case QCLOUD_IOT_CMD:
{
unsigned short qcloud_cmd;
qcloud_cmd = *((unsigned short *)(msg_buf + 16));
处理腾讯云IoT命令。从消息缓冲区偏移16字节的位置提取2字节的命令ID。
ci_logdebug(LOG_MEDIA, "[ qcloud ]recv cmd: %d\n", qcloud_cmd);
// tanmi_uart_send_msg_to_elc(qcloud_cmd&0xff,0x02);
记录收到的命令ID,并注释掉了一行可能将命令发送到电器的代码。
pause_voice_in();
prompt_play_by_cmd_id(qcloud_cmd, -1, play_done_callback, true);
break;
}
暂停语音输入,播放与命令ID对应的提示音,并退出当前case。
case VAD_END: //云端VAD结束
{
// #if RECEIVE_VAD_FROM_THE_CLOUD_ENABLE
#if 1
voice_upload_disable();
voice_upload_vad_enable = false;
xQueueReset(pcm_speex_deal_queue);
处理云端VAD(语音活动检测)结束命令。禁用语音上传,清除语音上传VAD使能标志,并重置PCM/Speex处理队列。
ci_loginfo(LOG_VOICE_UPLOAD, "rev cloud vad end flag********************\n");
break;
#endif
}
记录日志并退出当前case。
// case RECV_NET_CONNECTED_CMD:
case CLOUD_CONNECTED: //云端已连接
{
start_connect_wifi = false;
connext_cloud = true;
处理云端连接成功的命令。将WiFi连接标志设为false(因为已经连接完成),将云连接标志设为true。
if(NETWORK_CONNECTED_STATE != wifi_current_state_get())
{
ci_loginfo(LOG_IR, "RECV_NET_CONNECTED_CMD cmd is 0x%x\n", wifi_cmd);
wifi_current_state_set(NETWORK_CONNECTED_STATE);
如果当前网络状态不是NETWORK_CONNECTED_STATE,记录日志并更新网络状态。
//pause_voice_in();
vTaskDelay(pdMS_TO_TICKS(1000));
prompt_play_by_cmd_id(1002, -1, play_done_callback, true);
}
break;
}
延迟1000毫秒,然后播放连接成功提示音(ID为1002),最后退出当前case。
case WIFI_DISCONNECTED://配网失败,网络已断开
{
start_connect_wifi = false;
connext_cloud = false;
处理WiFi断开连接命令。将WiFi连接标志和云连接标志都设为false。
if(WIFI_CONFIGING_STATE == wifi_current_state_get())
{
wifi_current_state_set(WIFI_DISCONNECT_STATE);
pause_voice_in();
prompt_play_by_cmd_id(1003, -1, play_done_callback, true);
}
如果当前正处于配网状态,则将WiFi状态设为断开状态,暂停语音输入,并播放断开连接提示音(ID为1003)。
ci_loginfo(LOG_IR, "WIFI_DISCONNECTED cmd is 0x%x\n", wifi_cmd);
break;
}
记录日志并退出当前case。
case ENTER_NET_CONFIG://WIFI进入配网模式
{
mprintf("---进入配网模式\n");
prompt_play_by_cmd_id(1001, -1, play_done_callback, true);
break;
}
处理进入配网模式命令。打印日志并播放进入配网模式提示音(ID为1001)。
case NET_CONFIG_SUCCESS://配网成功
{
mprintf("---配网成功\n");
prompt_play_by_cmd_id(1007, -1, play_done_callback, true);
break;
}
处理配网成功命令。打印日志并播放配网成功提示音(ID为1007)。
case NET_CONFIG_FAIL://配网失败
{
mprintf("---配网失败\n");
start_connect_wifi = false;
connext_cloud = false;
处理配网失败命令。打印日志并重置WiFi连接标志和云连接标志。
if(WIFI_CONFIGING_STATE == wifi_current_state_get())
{
wifi_current_state_set(WIFI_DISCONNECT_STATE);
pause_voice_in();
prompt_play_by_cmd_id(1006, -1, play_done_callback, true);
}
break;
}
如果当前正处于配网状态,则将WiFi状态设为断开状态,暂停语音输入,并播放配网失败提示音(ID为1006)。
default:
{
ci_loginfo(LOG_IR, "other cmd is 0x%x\n", wifi_cmd);
处理所有未明确处理的命令。记录未知命令的类型。
if(wifi_cmd == PLAY_EMPTY)
{
//ci_logdebug(LOG_MEDIA, "MP3 play_end, change to bf *********************\n");
#if USE_BEAMFORMING_MODULE
ciss_set(CI_SS_PLAY_TYPE,CI_SS_PLAY_TYPE_CMD_PROMPT);
#endif
media_play.recv_media_stream_end = true;
}
如果命令是PLAY_EMPTY(播放结束),在启用波束形成模块的情况下设置播放类型为命令提示,并标记媒体流接收结束。
/* if((wifi_cmd == 0xeeee)&&(media_play.start_play == START_PLAY_TTS)&&(media_play.mp3_decode_fail))
{
media_play.mp3_decode_fail = false;
ci_loginfo(LOG_USER, "\r\n TTS fail, NET_PLAY_RESTART=============\r\n");
cias_send_cmd(NET_PLAY_RESTART, DEF_FILL);
} */
注释掉的代码,可能是处理TTS播放失败的逻辑。
ret = RETURN_ERR;
break;
}
}
对于未明确处理的命令,返回值设为RETURN_ERR,并退出switch语句。
return ret;
}
返回处理结果(RETURN_OK或RETURN_ERR)。
aux_aiot_aircondition_callback 函数功能总结
aux_aiot_aircondition_callback 函数是一个网络消息处理函数,主要用于处理智能空调与云端的通信。它处理多种类型的命令,包括:
- 音频播放控制:包括播放、暂停、停止等操作
- 音量控制:通过网络命令调整设备音量
- 配置文件管理:处理获取和需要配置文件的请求
- 云端命令处理:执行从腾讯云IoT平台发送的命令
- 语音控制:管理语音上传和VAD(语音活动检测)
- 网络状态管理:处理云连接、WiFi连接/断开和配网过程的各种状态
接下来我们阅读 network_audio_processing 函数代码看看后续是如何执行的。
cias_voice_plyer_handle.c
network_audio_processing 函数详细解读
这个函数用于处理网络音频相关的消息,负责管理不同类型的音频播放(MP3、TTS、M4A和WAV)的状态和控制流程。下面是详细的逐行解读:
int32_t network_audio_processing(uint8_t *msg_buf)
{
函数定义,接收一个指向消息缓冲区的指针,返回一个int32_t类型的值。
int32_t ret = RETURN_OK;
初始化返回值为RETURN_OK,表示默认处理成功。
static int32_t recv_music_data_len = 0;
静态变量,用于跟踪已接收的音乐数据总长度。
static int32_t recv_net_mp3_num = 0;
静态变量,用于跟踪已接收的MP3数据量。
static int16_t recv_resume_cmd_num = 0;
静态变量,用于跟踪接收到的恢复播放命令数量。
cias_data_standard_head_t *pheader = (cias_data_standard_head_t *)(msg_buf);
从消息缓冲区提取标准消息头信息。
wifi_communicate_cmd_t wifi_cmd = (wifi_communicate_cmd_t)pheader->type;
从消息头中获取命令类型。
switch (wifi_cmd)
{
根据命令类型执行不同的处理逻辑。
1. 处理音频数据接收 (PLAY_DATA_GET)
case PLAY_DATA_GET:
{
处理获取播放数据的命令。
if (pheader->len == 0)
{
ci_logdebug(LOG_MEDIA, "pheader->len = %d\n", pheader->len);
ret = RETURN_ERR;
break;
}
如果数据长度为0,记录日志并返回错误。
int ret_tts = outside_write_stream(cur_play_stream, (uint32_t)(msg_buf + 16), pheader->len, false);
将数据写入当前的播放流(从消息缓冲区偏移16字节的位置开始)。
if (RETURN_OK == ret_tts)
{
如果写入成功:
if (recv_net_mp3_num == 0)
{
如果这是第一次接收数据:
if ((get_net_player_status(get_net_play_type()) == NET_PLAYER_RESUME) && (recv_music_data_len == 0))
{
如果当前播放状态是恢复播放且尚未接收任何数据:
if (0 != memcmp(get_next_resume_play_data(), (uint8_t *)(msg_buf + 16), 8))
{
比较接收到的数据与预期的恢复播放数据,如果不匹配:
ci_loginfo(LOG_MEDIA, "error first packet\n");
for (int32_t w = 0; w < 8; w++)
{
ci_loginfo(LOG_MEDIA, "0x%x ", *(uint8_t *)(msg_buf + 16 + w));
mprintf("0x%x ", *(uint8_t *)(msg_buf + 16 + w));
}
ci_loginfo(LOG_MEDIA, "\n");
mprintf("\n");
记录错误和接收到的数据内容。
set_net_player_status(get_net_play_type(), NET_PLAYER_IDLE);
cias_send_cmd(NET_PLAY_NEXT, DEF_FILL);
将播放器状态设置为空闲,并发送播放下一首的命令。
}
else
{
ci_loginfo(LOG_MEDIA, "resume ok\n");
}
}
}
如果数据匹配,记录"恢复成功"的日志。
recv_music_data_len += pheader->len;
recv_net_mp3_num += pheader->len;
recv_resume_cmd_num = 0;
更新已接收的数据量,并重置恢复命令计数。
}
else
{
ci_logdebug(LOG_MEDIA, "write data fail!\n");
}
如果写入失败,记录日志。
if(recv_net_mp3_num >= PLAY_BUF_SIZE_MAX) start_recv_flag = true;
如果接收的数据量超过缓冲区最大大小,设置接收标志为真,表示可以继续接收更多数据。
接下来的代码处理不同情况下的播放启动:
if ((recv_music_data_len >= PLAY_BUF_SIZE_MAX ) && (get_net_player_status(get_net_play_type()) == NET_PLAYER_READY))
{
如果接收数据量足够且播放器状态为"准备就绪":
ci_logdebug(LOG_MEDIA, "write data get_net_play_type =%d\n", get_net_play_type());
if (get_net_play_type() == PLAY_MP3)
{
play_with_outside(0, "mp3", mp3_player_end_callbk);
set_net_player_status(PLAY_MP3, NET_PLAYER_START);
}
else if (get_net_play_type() == PLAY_TTS)
{
play_with_outside(0, "mp3", tts_player_end_callbk);
set_net_player_status(PLAY_TTS, NET_PLAYER_START);
get_response_timer_cnt();
}
else if (get_net_play_type() == PLAY_M4A)
{
play_with_outside(0, "m4a", m4a_player_end_callbk);
set_net_player_status(PLAY_M4A, NET_PLAYER_START);
}
else if (get_net_play_type() == PLAY_WAV)
{
play_with_outside(0, "ms_wav", wav_player_end_callbk);
set_net_player_status(PLAY_WAV, NET_PLAYER_START);
}
根据不同的播放类型使用相应的回调开始播放,并更新播放器状态。
no_audio_data_flag = false;
}
清除无音频数据标志。
接下来处理恢复播放的两种情况:
else if (((recv_music_data_len >= 20 * 1024) && (get_net_player_status(get_net_play_type()) == NET_PLAYER_RESUME) && (no_audio_data_flag)))
{
如果接收数据足够且状态为恢复播放,且之前标记为无音频数据:
if (get_net_play_type() == PLAY_MP3)
{
play_with_outside(get_music_data_offset(), "history", mp3_player_end_callbk);
set_net_player_status(PLAY_MP3, NET_PLAYER_START);
}
// 其他音频类型类似处理...
使用历史偏移量恢复播放,并更新播放器状态。
ci_logdebug(LOG_MEDIA, "--start playing-- play_type2 = %d\n", get_net_play_type());
no_audio_data_flag = false;
}
清除无音频数据标志。
第三种情况与第二种类似,但区别是no_audio_data_flag为false的情况:
else if (((recv_music_data_len >= 20 * 1024) && (get_net_player_status(get_net_play_type()) == NET_PLAYER_RESUME) && (!no_audio_data_flag)))
{
// 类似的播放处理逻辑...
}
break;
}
2. 处理开始播放命令 (NET_PLAY_START)
case NET_PLAY_START:
{
处理开始播放的命令。
if (check_current_playing())
{
cias_send_cmd(NET_PLAY_LOCAL_TTS, NO_WAKEUP_FILL_DATA);
ci_logdebug(LOG_MEDIA, "NET_PLAY_START error under playing\n");
}
如果当前已有播放内容,则发送本地TTS播放命令,并记录日志。
recv_resume_cmd_num = 0;
recv_music_end_sem = false;
recv_music_data_len = 0;
recv_net_mp3_num = 0;
start_recv_flag = false;
重置相关变量。
根据填充数据类型进行不同处理:
if (pheader->fill_data == INVAILD_SPEAK)
{
如果是无效语音:
ci_logdebug(LOG_MEDIA, "INVAILD_SPEAK\n");
if (get_voice_invalid())
{
cias_send_cmd(SKIP_INVAILD_SPEAK, DEF_FILL);
break;
}
如果已设置语音无效标志,则跳过无效语音处理。
cur_play_stream = tts_player;
set_net_play_type(PLAY_TTS);
outside_clear_stream(tts_player, tts_player_end);
set_curr_outside_handle(tts_player, tts_player_end);
ci_logdebug(LOG_MEDIA, " receive TTS start 1 !\n");
cias_send_cmd(PLAY_DATA_GET, DEF_FILL);
set_net_player_status(PLAY_TTS, NET_PLAYER_READY);
media_play.start_play = START_PLAY_TTS;
}
准备TTS播放。
else if (pheader->fill_data == RECV_TTS_PLAY)
{
// 类似TTS播放准备...
}
else if (pheader->fill_data == RECV_MP3_PLAY)
{
cur_play_stream = mp3_player;
set_net_play_type(PLAY_MP3);
outside_clear_stream(mp3_player, mp3_player_end);
set_curr_outside_handle(mp3_player, mp3_player_end);
ci_logdebug(LOG_MEDIA, "receive MP3 start!\n");
cias_send_cmd(PLAY_DATA_GET, DEF_FILL);
set_net_player_status(PLAY_MP3, NET_PLAYER_READY);
set_music_data_offset(0);
#if USE_BEAMFORMING_MODULE
ciss_set(CI_SS_PLAY_TYPE,CI_SS_PLAY_TYPE_MUSIC);
#endif
}
准备MP3播放,并设置波束形成模块为音乐播放类型(如果启用)。
else if (pheader->fill_data == RECV_M4A_PLAY)
{
// 类似M4A播放准备...
}
else if (pheader->fill_data == RECV_WAV_PLAY)
{
// 类似WAV播放准备...
}
break;
}
3. 处理停止播放命令 (NET_PLAY_STOP)
case NET_PLAY_STOP:
{
ci_logdebug(LOG_MEDIA, "receive media stop!\n");
if (SYS_STATE_WAKEUP != get_wakeup_state())
{
pause_play(NULL, NULL);
}
break;
}
记录日志并暂停播放(如果不在唤醒状态)。
4. 处理暂停播放命令 (NET_PLAY_PAUSE)
case NET_PLAY_PAUSE:
{
ci_logdebug(LOG_MEDIA, "receive mp3 pause!\n");
if (get_net_play_type() == PLAY_M4A)
{
pause_play(NULL, NULL);
}
break;
}
记录日志并暂停M4A播放(仅对M4A类型有效)。
5. 处理恢复播放命令 (NET_PLAY_RESUME)
case NET_PLAY_RESUME:
{
ci_logdebug(LOG_MEDIA, "receive resume! fill_data = 0x%x\n", pheader->fill_data);
记录收到恢复命令及填充数据。
if (recv_resume_cmd_num > 0)
{
recv_resume_cmd_num = 0;
cias_send_cmd(NET_PLAY_NEXT, DEF_FILL);
ci_logdebug(LOG_MEDIA, "resume recv 2 times\n");
}
如果已经连续收到多次恢复命令,则发送播放下一首的命令。
if (check_current_playing())
{
ci_logdebug(LOG_MEDIA, "resume error under playing\n");
ret = RETURN_ERR;
break;
}
如果当前已有内容在播放,则记录错误并返回。
recv_music_data_len = 0;
recv_music_end_sem = false;
recv_net_mp3_num = 0;
start_recv_flag = false;
重置相关变量。
根据不同的播放器状态和填充数据类型进行处理:
if ((get_net_player_status(PLAY_MP3) == NET_PLAYER_PAUSE) && ((pheader->fill_data == RECV_MP3_PLAY)))
{
set_net_play_type(PLAY_MP3);
cur_play_stream = mp3_player;
set_curr_outside_handle(mp3_player, mp3_player_end);
outside_clear_stream(mp3_player, mp3_player_end);
set_net_player_status(PLAY_MP3, NET_PLAYER_RESUME);
cias_send_cmd(PLAY_DATA_GET, get_music_data_offset());
ci_logdebug(LOG_MEDIA, "recv next data 2\n");
}
如果MP3播放器处于暂停状态且填充数据为MP3播放,则准备恢复MP3播放。
类似地处理M4A和WAV播放恢复。
else
{
if ((get_net_player_status(PLAY_M4A) == NET_PLAYER_IDLE) && (get_net_player_status(PLAY_MP3) == NET_PLAYER_IDLE) && (get_net_player_status(PLAY_WAV) == NET_PLAYER_IDLE))
{
cias_send_cmd(NET_PLAY_NEXT, DEF_FILL);
ci_logdebug(LOG_MEDIA, "resume error NET_PLAYER_IDLE\n");
}
else
{
ci_logdebug(LOG_MEDIA, "resume not pasue error\n");
++recv_resume_cmd_num;
}
}
break;
}
如果所有播放器都处于空闲状态,则发送播放下一首的命令;否则增加恢复命令计数。
6. 处理播放数据结束命令 (PLAY_DATA_END)
case PLAY_DATA_END:
{
rev_voice_end_flag = true;
recv_music_end_sem = true;
recv_net_mp3_num = 0;
设置相关标志,表示数据接收完毕。
根据不同的播放器状态处理:
if (get_net_player_status(get_net_play_type()) == NET_PLAYER_START)
{
if (get_net_play_type() == PLAY_TTS)
{
outside_send_end_sem(tts_player_end);
}
else if (get_net_play_type() == PLAY_MP3)
{
outside_send_end_sem(mp3_player_end);
}
// 类似处理其他音频类型...
ci_logdebug(LOG_MEDIA, "send_end_sem2 play_type = %d\n", get_net_play_type());
}
如果播放器已经开始播放,则发送结束信号量。
else if (get_net_player_status(get_net_play_type()) == NET_PLAYER_RESUME)
{
if (get_net_play_type() == PLAY_MP3)
{
play_with_outside(get_music_data_offset(), "history", mp3_player_end_callbk);
set_net_player_status(PLAY_MP3, NET_PLAYER_START);
outside_send_end_sem(mp3_player_end);
}
// 类似处理其他音频类型...
}
如果播放器处于恢复状态,则开始播放并发送结束信号量。
else if (get_net_player_status(get_net_play_type()) == NET_PLAYER_READY)
{
if (get_net_play_type() == PLAY_MP3)
{
set_net_player_status(PLAY_MP3, NET_PLAYER_START);
play_with_outside(0, "mp3", mp3_player_end_callbk);
outside_send_end_sem(mp3_player_end);
}
// 类似处理其他音频类型...
ci_logdebug(LOG_MEDIA, "Start net player\n");
}
break;
}
}
return 0;
}
如果播放器处于就绪状态,则开始播放并发送结束信号量。
network_audio_processing 函数功能总结
network_audio_processing 函数实现了一个完整的网络音频处理流程,包括:
- 数据接收与缓冲:接收网络音频数据,写入相应的播放流缓冲区
- 播放控制:支持不同格式音频(MP3、TTS、M4A、WAV)的开始、暂停、恢复和停止播放
- 状态管理:管理不同播放器的状态转换(空闲、准备、开始、暂停、恢复等)
- 数据验证:在恢复播放时验证数据的正确性
- 异常处理:处理各种异常情况,如数据不足、格式错误等
函数使用状态机模式处理不同类型的命令和不同状态下的播放控制,确保音频播放的连续性和稳定性。它是实现网络音频流媒体播放的核心组件,负责处理从网络接收到的音频数据并控制播放器的行为。