播放器

简介

该播放器基于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);
}

results matching ""

    No results matching ""