播放器
简介
该播放器基于YoC平台轻量级的多媒体AV框架开发,具体实现包含在AV组件中。其主要有以下特点:
- 轻量、极简音频播放器
- MP3 解码: ROM < 35K, RAM < 20K
- 四层架构,按需裁剪&扩展
- 低时延,首播延时最低<20ms
- 编解码DSP指令加速,充分利用硬件资源
- 支持本地/核间(跨核)解码
- 支持wav、mp3、m4a、amrnb、amrwb、flac、adts、opus、speex、adpcm_ms、alaw、mulaw等多种音频格式
- 支持sd卡、http(s)、fifo、mem等多种取流方式
- 支持语音合成(TTS)播放
- 支持软件音量、音量曲线配置
- 支持音频信息及当前播放时间获取
- 支持重采样输出
- 支持音效(audio effector)、量化器(EQ)扩展及配置
注意事项:
播放器目前仅支持音频格式播放,若支持视频播放可基于AV框架扩展。
播放器工作机制
播放器的播放流程
用户创建播放器并调用player_play接口开始播放。该接口在初始化相关资源并创建播放线程抛出 _ptask任务。播放器将播放相关阻塞操作等放在 _ptask函数中处理。这样保证了player_play可以立即返回。
为降低系统开销和简化播放器,系统将解复用、解码、音频输出等操作置于同一线程中处理。 _ptask函数初始时,调用 _player_prepare函数进行音频复用、编码格式的探测等功能,并判断播放器是否支持播放。
若支持播放,则在loop循环中不断调用解复用、解码、音频输出等功能。否则向上层上报播放失败事件退出本次播放。
主要播放流程如下图所示:
播放器的状态切换
播放器提供了一系列的调用接口,如player_pause、player_resume等接口来切换状态。几种状态间的转换关系如下图所示:
播放器通过调用player_play播放,初始时从停止态(PLAYER_STATUS_STOPED)进入到准备态(PLAYER_STATUS_PREPARING)。播放器内部通过调用_player_prepare函数来判断码流是否支持播放。若准备ok则直接进入播放态(PLAYER_STATUS_RUNNING),否则上报播放错误事件进入到停止态。由于prepare阶段花费因网络等问题可能花费较多时间,在进入播放态之前,准备态和暂停态可正常进行切换。正常播放后通过调用player_pause进入暂停态(PLAYER_STATUS_PAUSED)。在播放暂停时,通过调用player_resume恢复到播放态。通过调用player_stop可以从播放态和暂停态直接进入到停止态。
播放器接口
播放器模块初始化
int player_init();
在调用播放器其他接口之前,要初始化播放器模块。
其主要功能为注册取流类型stream、解复用demux、解码器codec、音频输出ao等。
返回值:
调用成功时返回0,否则返回-1。
注意事项:
该接口调用一次即可,可多次调用。
创建播放器
播放器配置参数初始化
int player_conf_init(ply_conf_t *ply_cnf);
初始化配置参数结构体,用于播放器的创建。
用户通过该接口获取播放器默认的配置参数后,可根据需要修改相关参数。配置参数由结构体ply_conf_t表示,其包括重采样频率、软件音量、取流超时时间等,详细定义如下:
typedef struct player_conf {
char *ao_name; ///< 音频输出名,用于选择哪一个输出(如果存在多个),默认为alsa
uint8_t vol_en; ///< 软件音量使能配置
uint8_t vol_index; ///< 软件音量大小(不同于硬件音量调节),范围为0~255
uint8_t eq_segments; ///< 量化器的段数(默认不支持,用户根据需要扩展)
uint8_t atempo_play_en;///< 播放启用
float speed; ///<播放速度,建议:0.5~2.0
uint8_t *aef_conf; ///< 音效配置参数(默认为索那音效,需商务合作或扩展)
size_t aef_conf_size; ///< 音效配置大小
uint32_t resample_rate; ///< 非0表示重采样输出到该rate,否则原样输出
uint32_t rcv_timeout; ///< 取流超时时间(ms)。AOS_WAIT_FOREVER表示一直等,0表示使用播放器默认配置
uint32_t cache_size; ///< stream层取流缓存大小(byte)
uint32_t cache_start_threshold; ///< 当码流缓存到cache_size的指定百分比时才开始播放,防止网络状态不好时播放时断时续,优化播放效果。取值范围是0~100
uint32_t period_ms; ///< 音频输出每消耗多少ms的数据量来一次中断
uint32_t period_num; ///< period_ms的个数. 通过period这两个参数可以计算出ao的缓存大小为(period_num * period_ms * (rate / 1000) * 2 * (16/8))
get_decrypt_cb_t get_dec_cb; ///< 解密回调接口,用于从上层获取秘钥(若流是加密的)
player_event_t event_cb; ///< 播放器事件回调函数
void *user_data; ///< 播放器事件的用户数据
int32_t db_min; ///< 最小分贝
int32_t db_max; ///< 最大分贝
} ply_conf_t;
对于vol_index,当配置过大时,对于某些码流可能播放存在爆音。用户需根据需要确定音频曲线范围。
对于event_cb,用户需实现下面类型的定义。其中data和len当前作为预留使用
typedef void (*player_event_t)(void *user_data, uint8_t event, const void *data, uint32_t len);
播放器当前主要提供了三种事件状态,见下表说明:
状态 | 描述 |
---|---|
PLAYER_EVENT_UNKNOWN | 未定义 |
PLAYER_EVENT_ERROR | 播放出错 |
PLAYER_EVENT_START | 开始播放 |
PLAYER_EVENT_FINISH | 播放结束 |
对于get_dec_cb,请参考AV中的stream_conf_init接口说明。
返回值:
调用成功时返回0,否则返回-1。
创建播放器实例
player_t* player_new(const ply_conf_t *ply_cnf);
根据配置参数ply_cnf创建播放器实例。该接口与player_free对应。该接口的参数和返回值见下表:创建后的实例仅能调用player_play播放一个码流。如果需播放第二个码流,则需要调用player_stop停止播放后才能继续调用player_play。player_t结构体中的主要成员如下:
typedef struct player_cb player_t;
struct player_cb {
char *url;
stream_cls_t *s; ///< 媒体层流指针
demux_cls_t *demuxer; ///< 解复用器
ad_cls_t *ad; ///< 音频解码器
ao_cls_t *ao; ///< 音频输出
char *ao_name; ///< ao name
uint64_t start_time; ///< 起始播放时间
uint32_t cache_size; ///< web缓存大小,同player_conf中定义
uint32_t cache_start_threshold; ///< 缓存阈值,同player_conf中定义
uint32_t period_ms; ///< 同player_conf中定义
uint32_t period_num; ///< 同player_conf中定义
uint32_t resample_rate; ///< 重采样频率,同player_conf中定义
uint8_t vol_en; ///< 软件音量使能,同player_conf中定义
uint8_t atempo_play_en;///< 播放启用
float speed; ///< 播放速度,建议:0.5~2.0
uint8_t vol_index; ///< 软件音量索引,同player_conf中定义
uint8_t *aef_conf; ///< 音效配置,同player_conf中定义
size_t aef_conf_size; ///< 音效配置大小,同player_conf中定义
uint8_t eq_en; ///< eq使能,同player_conf中定义
uint8_t eq_segments; ///< eq段数,同player_conf中定义
eqfp_t *eq_params; ///< eq配置参数数组,数组个数等于配置的eq段数
get_decrypt_cb_t get_dec_cb; ///< 解密回调,同player_conf中定义
int32_t db_min; ///< 最小分贝
int32_t db_max; ///< 最大分贝
int64_t cur_pts; ///< 当前播放的present timestamp,用于获取当前播放时间
uint8_t status; ///< 播放器运行状态
uint8_t before_status;
aos_task_t ptask;
aos_event_t evt;
uint8_t evt_status; ///< 播放器事件
player_event_t event_cb; ///< 播放器回调,同player_conf中定义
void *user_data; ///< 播放事件的用户数据
uint8_t interrupt;
uint8_t need_quit; ///< 是否需要退出播放,用于通知stream层接口,防止中途退出播放时长时间阻塞
aos_mutex_t lock;
uint32_t rcv_timeout; ///< 同player_conf中定义
};
返回值:
调用失败时返回NULL。
播放器播放和控制
码流播放
int player_play(player_t *player, const char *url, uint64_t start_time);
从指定时间start_time(ms)播放指定url的码流,创建本次播放所需的资源,与player_stop相对应。
url格式的定义形式如下表,具体规则定义请参见AV中的stream_open接口说明:
流类型 | URL前缀 | URL格式 |
---|---|---|
网络流 | http(s):// | http(s)://ip:port/xx.mp3 |
文件流(SD卡) | file:// | file:///fatfs0/xx.mp3?avformat=%s&avcodec=%s&channel=%u&rate=%u |
内存流 | mem:// | mem://addr=%u&size=%u&avformat=%s&avcodec=%s&channel=%u&rate=%u |
fifo流 | fifo:// | fifo://tts/1?avformat=%s&avcodec=%s&channel=%u&rate=%u |
加密流 | crypto:// | crypto://http://ip:port/xx.mp3?key=%s&iv=%s |
hls流 | http(s):// | http(s)://ip:port/xx.m3u8 |
返回值:
调用成功时返回0,否则返回-1。
注意事项:
- 对于fifo流、直播流或某些索引信息不存在的码流,即使指定start_time,默认也会从起始处开始播放。
暂停播放
int player_pause(player_t *player);
该接口可暂停播放器的播放,当恢复播放时调用player_resume接口。
返回值:
调用成功时返回0,否则返回-1。
注意事项:
- 对于fifo流,暂停后,因播放器不在从fifo中读取数据,当写端将fifo写满后会被阻塞住
- 通常情况下,对于fifo流,不建议用户调用暂停/恢复播放接口
- 若fifo写端是从网络拉取的流,恢复播放后需自行处理写端可能存在的网络重连问题
恢复播放
int player_resume(player_t *player);
恢复播放器的播放,与player_pause相对应。
对于http码流来说,若长时间暂停播放,设备不会从web服务器拉去流,web服务器通常会主动断开连接。当恢复播放时,取流发现断开连接后,会再次从当前读取位置处重新建立连接拉流。
返回值:
调用成功时返回0,否则返回-1。
停止播放
int player_stop(player_t *player);
停止播放器的播放,销毁本次播放所创建的资源,其与player_play相对应。
当停止播放后可继续调用player_play播放下一个码流。
返回值:
调用成功时返回0,否则返回-1。
跳转播放
int player_seek(player_t *player, uint64_t timestamp);
当播放器处于播放状态或暂停状态时,可跳转到指定时间timestamp(ms)开始播放。跳转成功后,播放器状态不发生改变。
返回值:
调用成功时返回0,否则返回-1。
注意事项:
- 对于fifo流、直播流或某些索引信息不存在的码流,不支持跳转播放。
播放器控制
int player_ioctl(player_t *player, int cmd, ...);
通过控制命令字cmd控制播放器的行为。
具体的播放器控制命令字如下表所示:
状态 | 对应参数类型 | 描述 |
---|---|---|
PLAYER_CMD_UNKNOWN | —— | 未定义 |
PLAYER_CMD_EQ_SET_PARAM | peq_setpa_t* | EQ参数配置 |
PLAYER_CMD_EQ_ENABLE | peq_seten_t* | EQ使能配置 |
PLAYER_CMD_SET_CACHE_SIZE | uint32_t | 取流缓存大小配置 |
PLAYER_CMD_SET_RESAMPLE_RATE | uint32_t | 重采样输出配置 |
PLAYER_CMD_SET_RCVTO | uint32_t | 取流超时时间配置 |
其中对于PLAYER_CMD_EQ_SET_PARAM和PLAYER_CMD_EQ_ENABLE作为预留,需要自行扩展实现相应EQ接口。
对于PLAYER_CMD_SET_CACHE_SIZE、PLAYER_CMD_SET_RESAMPLE_RATE及PLAYER_CMD_SET_RCVTO配置,若当前播放器未处于停止状态,则配置后下次调用player_play时才会生效。
返回值:
调用成功时返回0,否则返回-1。
销毁播放器
int player_free(player_t *player);
释放播放器相关资源,与player_new相对应。
若播放器尚未停止播放,则在free之前会先停止播放。
返回值:
调用成功时返回0,否则返回-1。
媒体信息、播放时长及软件音量
获取当前播放时间
int player_get_cur_ptime(player_t *player, play_time_t *ptime);
该接口可用于获取码流的当前播放时间和总时长,均为ms单位。当播放器处于播放或暂停状态时,才可正确调用。
对于直播流或某些信息不完整的码流,总时长可能是动态的或无法准确获取到。
返回值:
调用成功时返回0,否则返回-1。
获取媒体信息
int player_get_media_info(player_t *player, media_info_t *minfo);
用于获取媒体的时长、大小、码率、编码格式、采样率等信息。当播放器处于播放或暂停状态时,才可正确调用。
media_info_t结构中的tracks指针,上层应用无需释放。当停止播放时,该指针会被播放器释放掉。
返回值:
调用成功时返回0,否则返回-1。
配置/获取软件音量
int player_get_vol(player_t *player, uint8_t *vol);
int player_set_vol(player_t *player, uint8_t vol);
配置/获取当前播放器软件音量。该软件音量仅属于当前播放器,播放器销毁后则不存在。
该接口的前提是在创建播放器时使能软件音量配置。软件音量不同于系统音量(硬件音量)。音量的取值范围是0~255。当配置过大时,对于某些码流可能播放存在爆音。用户需根据需要确定音频曲线范围。
返回值:
调用成功时返回0,否则返回-1。
配置/获取播放速度
int player_get_speed(player_t *player, float *speed);
int player_set_speed(player_t *player, float speed);
配置或获取当前播放器的播放速度。
返回值:
调用成功时返回0,否则返回-1。
获取播放源URL
const char* player_get_url(player_t *player);
获取播放源URL。
返回值:
调用失败时返回NULL。
播放器使用示例
播放网络/SD卡mp3歌曲
//核心代码片段
static player_t* g_player;
static void _player_event(player_t *player, uint8_t type, const void *data, uint32_t len)
{
UNUSED(len);
UNUSED(data);
switch (type) {
case PLAYER_EVENT_ERROR:
player_stop(player);
break;
case PLAYER_EVENT_START:
break;
case PLAYER_EVENT_FINISH:
player_stop(player);
break;
default:
break;
}
}
int app_main()
{
ply_conf_t ply_cnf;
player_conf_init(&ply_cnf);
/* 配置音频重采样到48k */
ply_cnf.resample_rate = 48000;
/* 配置播放器状态回调函数 */
ply_cnf.event_cb = _player_event;
/* 根据配置参数创建播放器示例 */
g_player = player_new(&ply_cnf);
/* 网络mp3歌曲 */
char *url = "http://www.baidu.com/xx.mp3";
/* 文件/sd卡mp3歌曲 */
//char *url = "file:///fatfs0/test.MP3";
/* 播放指定url的音频文件 */
player_play(g_player, url, 0);
}
播放内存中的pcm裸数据
//核心代码片段
//按照上面示例中的代码,将url修改为mem://前缀并加入相关参数即可播放内存中的pcm裸数据
char url[128];
char *pcm_data = 0x123456;//pcm数据存放地址
size_t pcm_len = 1024;//pcm数据大小
snprintf(url, sizeof(url), "mem://addr=%u&size=%u&avformat=rawaudio&avcodec=pcm_s16le&channel=1&rate=16000", pcm_data, pcm_len);//pcm裸数据播放需要在url中指定avformat、avcodec、channel、rate参数
player_play(g_player, url, 0);
语音合成(TTS)流播放
用户自行下载网络流,通过fifo传递给播放器播放。fifo可用于TTS的播放。
//核心代码片段
static player_t* g_player;
static nsfifo_t* g_tts_fifo;
static void _ptask(void *arg)
{
int fd;
int cnt = 0, rc, wlen;
char *val, *pos;
uint8_t reof = 0;
web_session_t *session;
/* 创建一个http会话 */
session = web_session_create();
/* 向指定地址发起http请求 */
rc = web_session_get(session, "http://www.srcbin.net/ai/result.mp3", 3);
if (rc) {
LOGE(TAG, "web_session_get fail. rc = %d, code = %d, phrase = %s", rc, session->code, session->phrase);
goto err;
}
/* 获取内容的长度 */
val = (char*)dict_get_val(&session->hdrs, "Content-Length");
CHECK_RET_TAG_WITH_GOTO(val != NULL, err);
fd = session->fd;
LOGD(TAG, "content len = %d", atoi(val));
for (;;) {
/* 获取fifo的可写指针及长度 */
wlen = nsfifo_get_wpos(g_tts_fifo, &pos, 8*1000);
/* 获取播放器fifo读端是否退出(可能播放出错) */
nsfifo_get_eof(g_tts_fifo, &reof, NULL);
if (wlen <= 0 || reof) {
LOGE(TAG, "get wpos err. wlen = %d, reof = %d", wlen, reof);
break;
}
/* 从网络套接字中读取数据 */
rc = sock_readn(fd, pos, wlen, 6*1000);
if (rc <= 0) {
LOGE(TAG, "readn err. rc = %d", rc);
break;
}
/* 设置写指针 */
nsfifo_set_wpos(g_tts_fifo, rc);
cnt += rc;
}
LOGD(TAG, "rc = %8d, cnt = %8d", rc, cnt);
err:
/* 销毁web会话资源 */
web_session_destroy(session);
return;
}
/* 创建fifo,指定fifo地址和大小 */
g_tts_fifo = nsfifo_open("fifo://tts/1", O_CREAT, 64*1024);
if (g_tts_fifo) {
/* 创建 _ptask任务,用于通过网络获取码流数据 */
aos_task_new("xx_task", _ptask, NULL, 6*1024);
/* 设置播放器取流超时时间 */
player_ioctl(g_player, PLAYER_CMD_RCVTIMEO, AOS_WAIT_FOREVER);
/* 播放之前创建的fifo码流 */
player_play(g_player, "fifo://tts/1", 0);
}