编辑语:
芯片开放社区(OCC)面向开发者推出RISC-V系列内容,通过多角度、全方位解读RISC-V,系统性梳理总结相关理论知识,构建RISC-V知识图谱,促进开发者对RISC-V生态全貌的了解。
关于YoC组件介绍系列内容,我们已经为大家讲解了CSI组件和AT组件。本期内容将继续为大家介绍YoC组件,本文将通过阐述AV组件的框架、功能、使用示例等,带大家详细了解YoC的AV(多媒体)组件。
01 多媒体组件
libav是一个轻量级的多媒体组件,主要包含AV基础框架、播放器和音频服务三部分。其中AV框架采用典型的4层多媒体模型设计及面向对象的思想开发,使得用户在此基础上易于复用与扩展。
av组件中内置了一个播放器的实现,可以播放wav、mp3、m4a、amrnb、amrwb、flac、adts、opus、speex、adpcm_ms、alaw、mulaw等多种常见音频格式。同时基于播放器和YoC微服务架构实现了音频服务,支持提示音、语音合成(TTS)、音乐播放的状态切换与维护等功能,方便上层应用开发。用户可以分别基于音频服务、播放器和AV基础框架三个不同层次开发和扩展多媒体相关功能。
这三个层次之间的关系如下图所示:
1.1 主要代码层次结构
. ├── aef ├── avcodec ├── avfilter ├── avformat ├── avutil ├── icore ├── include ├── mca ├── media ├── output ├── player ├── stream ├── swresample └── tfft
各文件夹的具体说明如下表所示:
状态 |
描述 |
aef |
音效相关,当前包含sona音效算法(需商务合作)和核间音效(算法与播放器不在同一核上) |
avcodec |
AV框架中编解码相关,主要用于mp3、aac、amr、flac等格式的解码 |
avfilter |
AV框架中定义一套过滤器机制。各filter串联,用于重采样、单双通道等处理 |
avformat |
AV框架中封装格式相关,主要用于mp3、m4a、wma等封装格式的demux处理 |
avutil |
AV相关的通用函数实现等,如fifo、url解析、http client等 |
icore |
相关核间算法的对接实现,包含核间解码、音效、重采样、fft实现等(针对多核运算场景) |
include |
av组件对外提供的头文件 |
mca |
mca加速器实现,包含IIR滤波器等功能(硬件相关) |
media |
基于YoC微服务和播放器的音频服务的实现,支持语音合成、提示音等播放和状态切换 |
output |
音视频输出实现,当前包含类似alsa接口的音频输出实现。用户可安装ao定义进行扩展 |
player |
播放器的实现,其依赖于AV框架中的各个层次的模块 |
stream |
AV框架中媒体接入层实现,当前包含http、file、mem、fifo取流实现等 |
swresample |
包含单双通道转换、多种音频重采样(含本地/核间重采样)等功能实现 |
tfft |
傅里叶变换接口封装及实现等。用户可基于此切换使用本地/核间fft功能 |
02 播放器
该播放器基于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、m3u8等多种取流方式
- 支持语音合成(TTS)播放
- 支持软件音量、音量曲线配置
- 支持音频信息及当前播放时间获取
- 支持重采样输出
- 支持音效(audio effector)、量化器(EQ)扩展及配置
注意事项:
播放器目前仅支持音频格式播放,若支持视频播放可基于AV框架扩展。
2.1 播放器的播放流程
用户创建播放器并调用player_play接口开始播放。该接口在初始化相关资源并创建播放线程抛出 _ptask任务。播放器将播放相关阻塞操作等放在 _ptask函数中处理。这样保证了player_play可以立即返回。
为降低系统开销和简化播放器,系统将解复用、解码、音频输出等操作置于同一线程中处理。 _ptask函数初始时,调用 _player_prepare函数进行音频复用、编码格式的探测等功能,并判断播放器是否支持播放。
若支持播放,则在loop循环中不断调用解复用、解码、音频输出等功能。否则向上层上报播放失败事件退出本次播放。主要播放流程如下图所示:
2.2 播放器的状态切换
播放器提供了一系列的调用接口,如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可以从播放态和暂停态直接进入到停止态。
03 AV框架
AV框架是一个轻量级的多媒体开发框架,其采用典型的4层多媒体模型设计及面向对象的思想开发,使得用户在此基础上易于复用与扩展。
当前AV组件中提供了wav、mp3、m4a、amrnb、amrwb、flac、adts、opus、speex、adpcm_ms、alaw、mulaw等音频格式的支持。框架本身的设计向后提供视频支持。若有需要,可基于此AV框架进行扩展。
3.1 AV框架分层设计
AV框架主要抽象为四个层次:
- 媒体接入层:access层,负责媒体数据的来源,可能是file、http、fifo、mem等
- 解复用层:demux层,负责把容器里的音视频数据剥离出来,然后分别送给audio/video decoder。
- 解码层:decoder层,将解码完成后的数据(yuv、pcm)送给audio/video output输出。
- 输出层:output层,负责将decoder过来的数据呈现/播放出来。
如果把数据想象成流水的话,每层的功能虽然不同,但是他们大致抽象的功能都是接收上个模块过来的数据,然后加工并把加工后的数据送到下一个模块。把上述这些层通过某种方式连接起来,就形成了一个音频播放器。
3.1.1 媒体接入层(stream)
UML设计如下图所示:
- stream_ops_http、stream_ops_mem、stream_ops_file、stream_ops_fifo分别对应于网络流、内存流、本地文件流、fifo流取流播放。
- 其中语音合成(TTS)流的播放可基于stream_ops_fifo实现。
- 后续对于新增的媒体接入类型,可根据struct stream_ops结构中定义的类型,实现对应的接口即可扩展。
- 主要类型定义详见stream_cls.h
3.1.2 解复用层(avformat)
UML设计如下图所示:
- 其中demux_ops_wav、demux_ops_mp3、demux_ops_m4a分别对应于wav、mp3、m4a音频复用格式解复用。解复用出来的一帧音频数据会被送到对应的解码器进行解码。
- 当前支持wav、mp3、mp4、adts、flac、asf、amr等格式的解复用
- 后续对于新增的解复用格式,可根据struct demux_ops结构中定义的类型,实现对应的接口即可扩展
- 主要类型定义详见demux_cls.h
3.1.3 解码层(avcodec)
- 该层将demux解复用后出来的一帧帧编码数据解码成音视频裸数据(pcm/yuv)。
- 在某些情况下,某些编码类型解码时可能占用很高的主频(如在ck803ef上,HE-AAC解码主频需求在240M左右),此时可能需要通过核间通信(mailbox/IPC)将解码工作放到另一核上执行。
- 核间解码接口使用请参考下方介绍。开发者可略过AV框架,直接使用核间解码功能。
- 解码层为考虑扩展性,支持本地(核内)与跨核(核间)解码。其中核间解码适配层请参考av/avcodec/ad_ipc.c使用。
核间解码结构如下图所示:
核间解码时序图如下所示:
核内/核间解码框架设计如下图所示:
解码器UML设计如下图所示:
- 其中ad_ops_pvmp3、ad_ops_rawaudio、ad_ops_opus、ad_ops_fdk分别对应于mp3、裸pcm(解码透传)、opus、aac音频编码格式解码
- 当前支持mp3、aac、adpcm_ms、flac、amrnb、amrwb等格式的解码
- 后续对于新增的解码类型,可根据struct ad_ops结构中定义的类型,实现对应的接口即可扩展
- 主要类型定义详见ad_cls.h
3.1.4 输出层(output)
UML设计如下图所示:
- 其中ao_ops_alsa对应于采用alsa标准音频输出接口实现。通过alsa层来屏蔽各产品不同codec的实现
- 后续对于新增的输出类型,可根据struct ao_ops结构中定义的类型或在alsa/sound驱动层,实现对应的接口即可扩展
- 主要类型定义详见ao_cls.h
音频输出链路如下图所示:
04 协处理器侧AV(av_cp)
在嵌入式IoT领域,编解码等功能主频需求较大,单核嵌入式硬件平台已经难以满足复杂的计算需求。而异构多核处理器在音视频编解码运算上具有强大的优势。所以需要将这部分功能置于其他核或带有DSP功能的核上运行。
av_cp是一个轻量级的多核异构核间多媒体AV库,用于协处理器侧(此处称为DSP侧)的固件开发。其与AV组件中的核间处理接口(运行在应用程序处理器侧,此处称为主控侧)配合使用,如核间解码。该屏蔽了底层核间通信(IPC)的细节,开发人员可直接基于该库使用芯片的多核异构解码、重采样、fft等运算能力。这样可以有效提高音视频播放等功能的实时性、降低产品成本、充分利用硬件资源。
该组件的使用是硬件相关的,当前仅支持pangu芯片。对于其他多核芯片,用户可基于此扩展实现。
4.1 核间处理机制
系统上电后,可通过bootloader或在主控侧应用程序初始时先加载DSP固件到指定位置运行。DSP固件启动后,即通过核间通信机制开启监听服务,如核间解码服务。当主控侧调用核间封装的解码器接口时,底层会通过核间通信机制向DSP侧发起核间解码请求。DSP侧核间解码服务接收到该核间请求后,如果资源满足则正确响应该请求,否则返回失败。
4.2 核间解码器内部通信机制设计
4.2.1 服务ID
主控侧向DSP侧发起核间解码请求时,需要携带内部定义的核间解码服务ID才能找到DSP侧对应的服务。该服务ID定义如下:
#define ADICORE_IPC_SERIVCE_ID 0x10
注意事项:
DSP侧的IPC服务ID不能冲突,否则核间通信创建失败
4.2.2 命令字及内部核间通信消息
解码器通常的操作有打开、解码、重置、关闭等。主控侧在发起核间解码请求时,需要在核间通信消息中携带相应的命令字。命令字定义如下:
enum { ICORE_CMD_AD_INVALID, ICORE_CMD_AD_OPEN, ICORE_CMD_AD_DECODE, ICORE_CMD_AD_RESET, ICORE_CMD_AD_CLOSE, };
打开解码器
int _icore_ad_open(icore_msg_t *msg);
此函数为ICORE_CMD_AD_OPEN对应的内部处理函数。打开解码器内部核间通信消息具体定义如下:
typedef struct { avcodec_id_t id; ///< 解码器类型 adi_conf_t adi_cnf; ///< 解码器配置信息,某些解码器可能需要 void *ad; ///< DSP侧内部打开的解码器句柄 sf_t sf; ///< 打开解码器后的音频采样格式(可能与解复用出来的格式不一致) } adicore_open_t;
返回值:
调用成功时返回0,否则返回-1。
解码器解码
int _icore_ad_decode(icore_msg_t *msg)
此函数为ICORE_CMD_AD_DECODE对应的内部处理函数。内部核间通信消息具体定义如下:
typedef struct { void *ad; ///< DSP侧内部打开的解码器句柄 uint8_t *es_data; ///< 音频编码帧 int32_t es_len; ///< 音频编码帧大小 avframe_t frame; ///< 音频解码后的pcm int got_frame;///< 本次是否解码一帧标志 } adicore_decode_t;
返回值:
调用失败时,返回-1,大于0表示解码一帧消耗的原始编码帧字节数
重置解码器
int _icore_ad_reset(icore_msg_t *msg)
此函数为ICORE_CMD_AD_RESET对应的内部处理函数。内部核间通信消息具体定义如下:
typedef struct { void *ad; ///< DSP侧内部打开的解码器句柄 } adicore_reset_t;
返回值:
调用成功时返回0,否则返回-1。
关闭解码器
int _icore_ad_close(icore_msg_t *msg)int _icore_ad_close(icore_msg_t *msg)
此函数为ICORE_CMD_AD_CLOSE对应的内部处理函数。内部核间通信消息具体定义如下:
typedef struct { void *ad; ///< DSP侧内部打开的解码器句柄 } adicore_close_t;
返回值:
调用成功时返回0,否则返回-1。
4.3 DSP侧核间解码器接口及使用
4.3.1 核间解码器初始化
int adicore_cp_init();
初始化DSP侧核间解码器模块(一次即可)。该接口成功调用后,DSP侧便成功通过核间通信机制开启监听。主控侧的核间解码功能需要搭配此接口使用。
返回值:
调用成功时返回0,否则返回-1
4.3.2 核间解码器使用
开发者可基于solution/pangu_804demo开发DSP固件。默认demo中提供pangu芯片的mp3核间解码。
用户可修改解决方案下package.yaml中的CONFIG_DECODER_XXX等配置项。如solutions/pangu_804demo/package.yaml中,以下配置项默认可将mp3解码器(pvmp3)注册进去
CONFIG_DECODER_PCM: 0 CONFIG_DECODER_PVMP3: 1 CONFIG_DECODER_FLAC: 0 CONFIG_DECODER_FDK: 0 CONFIG_DECODER_ADPCM_MS: 0 CONFIG_DECODER_AMRNB: 0 CONFIG_DECODER_AMRWB: 0 CONFIG_DECODER_IPC: 0
注意事项:
- 解决方案下package.yaml中的CONFIG_DECODER_IPC配置项不能开启。此选项仅用于主控侧
- 若核间解码支持了mp3,则没有必要在主控侧也注册mp3解码支持。否则会根据对应解码注册的先后顺序使用
- 因对于pangu芯片,DSP侧(cpu1)中链接文件gcc_eflash.ld.S中配置的内存仅1M。若需要加入多个核间解码支持时,需要分配好每个核的内存使用。以防止内存不足导致核间解码异常。
- 编译好的DSP固件存放在generated/data/prim。对于盘古芯片,可将此固件重命名为cpu1。并和主控侧程序一起打包生成对应镜像文件。
05 功能配置与裁剪
鉴于某些产品硬件资源如ram、flash等比较受限,而av组件又提供了很多的功能。所以不太可能将av组件提供的所有功能都能够包含进去。此时就需要开发者根据具体产品需要开启或配置相关功能或资源大小使用。
av组件中提供了如流、解复用器、解码器、音频输出器、播放任务栈/网络缓冲任务栈默认大小、倍速播放、重采样器、单双声道输出等配置等,方便开发者根据需要来裁剪。
av组件package.yaml中的def_config字段下提供了默认组件默认打开的功能和配置。开发者在solution或板级组件中重新配置即可。
若开发者想修改默认配置的话,可在具体solution/板级组件中重新配置这些宏。在编译时,其会覆盖av组件中的默认配置。
Linux开发环境,可通过打开/编辑对应的package.yaml中的def_config选项来查看/修改相关宏配置。Windows下CDK集成开发环境,可右键查看/编辑对应组件的define。
5.1 宏配置说明
av组件默认提供了多种配置。其中CONFIG_XXX_IPC用于核间处理,仅适用于多核处理器,其表示具体的操作实际在协处理器上工作。本地仅是该功能的核间适配器,需要搭配相应的协处理器固件使用。下述所介绍的配置是以yaml格式介绍的。
av组件中的配置项大概分为如下几类:
5.1.1 媒体接入层配置
CONFIG_STREAMER_CRYPTO: 0 #加密流 CONFIG_STREAMER_FIFO: 0 #队列流 CONFIG_STREAMER_FILE: 0 #文件流 CONFIG_STREAMER_HLS: 0 #http live stream CONFIG_STREAMER_HTTP: 1 #http网络流 CONFIG_STREAMER_MEM: 1 #内存流 CONFIG_WEB_CACHE_TASK_STACK_SIZE: 2048 #网络流缓冲任务栈大小 CONFIG_AV_STREAM_CACHE_SIZE_DEFAULT: 8192#网络流缓冲大小 CONFIG_AV_STREAM_INNER_BUF_SIZE: 256 #stream内部buf大小,用于性能优化
CONFIG_STREAMER_XXX表示需要支持的媒体接入类型,在媒体接入层接口小节已介绍过。
CONFIG_WEB_CACHE_TASK_STACK_SIZE表示在获取http、hls等网络音频流时默认的任务栈大小。对于不同的取流类型,该值可根据需要来调整。
CONFIG_AV_STREAM_CACHE_SIZE_DEFAULT表示获取网络音频流时的默认环形buffer缓冲大小,默认80KB。该值可控制网络播放时的抖动,优化网络播放的效果。开发者可使用ffprobe等工具探测具体音频流的码率,结合产品的硬件配置,应用场景等计算出合适的网络缓冲大小。同时网络缓冲大小也可以在创建播放器时动态配置。
CONFIG_AV_STREAM_INNER_BUF_SIZE用于所有流内部小buf的大小配置,不管流是来自网络或是本地文件。合适的大小配置可提升性能,默认2KB。
5.1.2 解复用器配置
CONFIG_DEMUXER_ADTS: 0 #adts解复用 CONFIG_DEMUXER_AMR: 0 #amr解复用 CONFIG_DEMUXER_ASF: 0 #asf解复用 CONFIG_DEMUXER_FLAC: 0 #flac解复用 CONFIG_DEMUXER_MP3: 1 #mp3解复用 CONFIG_DEMUXER_MP4: 0 #mp4解复用 CONFIG_DEMUXER_OGG: 0 #ogg解复用 CONFIG_DEMUXER_RAWAUDIO: 0 #rawaudio解复用 CONFIG_DEMUXER_TS: 0 #ts解复用 CONFIG_DEMUXER_WAV: 0 #wav解复用 CONFIG_AV_PROBE_SIZE_MAX: 1024 #音频格式探测最大长度 CONFIG_AV_SAMPLE_NUM_PER_FRAME_MAX: 80 #控制wav音频帧的最大采样数 CONFIG_AV_MP4_IDX_OPT: 1 #m4a解复用内存优化
CONFIG_DEMUXER_xxx表示需要支持的解复用器类型,在解复用层接口小节已介绍过。
CONFIG_AV_PROBE_SIZE_MAX用于在解复用阶段探测音频格式具体是哪种类型。该配置项可提高音频格式探测的准确性,默认2KB。在网络播放时,建议该值小于或等于CONFIG_AV_STREAM_INNER_BUF_SIZE,可加快首播时间。否则可能会引起seek,导致首播时间慢。
CONFIG_AV_SAMPLE_NUM_PER_FRAME_MAX表示控制rawaudio或某些wav音频帧的最大采样数。可用于降低小内存芯片的最大内存占用,如ch2601、w800等。
CONFIG_AV_MP4_IDX_OPT表示m4a解复用内存优化是否开启。由于m4a文件播放时,需要先获取所有索引信息才能播放,内存开销较大。该优化开启后,可以在播放时分段建立索引,降低最大内存开销。但开启后,对于网络播放,可能会引起卡顿。
5.1.3 解码器配置
CONFIG_DECODER_ADPCM_MS: 0 #adpcm_ms解码 CONFIG_DECODER_ALAW: 0 #alaw解码 CONFIG_DECODER_AMRNB: 0 #amrnb解码 CONFIG_DECODER_AMRWB: 0 #amrwb解码 CONFIG_DECODER_FLAC: 0 #flac解码 CONFIG_DECODER_IPC: 0 #核间解码 CONFIG_DECODER_MULAW: 0 #ulaw解码 CONFIG_DECODER_OPUS: 0 #opus解码 CONFIG_DECODER_PCM: 1 #pcm裸流解码 CONFIG_DECODER_PVMP3: 1 #mp3解码 CONFIG_DECODER_SPEEX: 0 #speex解码
CONFIG_DECODER_xxx表示需要支持的解码器类型,在解码层接口小节已介绍过。
5.1.4 音频输出配置
CONFIG_AV_AO_ALSA: 1 #音频输出采用alsa CONFIG_AV_AO_CHANNEL_NUM: 1 #音频输出通道,仅单声道或双声道输出 CONFIG_AO_MIXER_SUPPORT: 0 #混音播放
CONFIG_AV_AO_xxx表示需要支持的音频输出类型,在音频输出接口小节已介绍过。
CONFIG_AV_AO_CHANNEL_NUM表示音频输出通道数,其决定音频流最终送到codec输出采用单声道还是双声道。对于某些低成本芯片,可仅采用单声道输出,降低最大内存开销。
CONFIG_AO_MIXER_SUPPORT表示是否支持混音播放输出,及允许创建两个播放器同时输出。若关闭,播放器需要将之前的播放stop后才能正常播放,否则可能会引起异常。若开启,其不会有关闭情况下的功能限制,但会有较多的内存开销。
5.1.5 解析器配置
CONFIG_AVPARSER_MP3: 1 #mp3解析器 CONFIG_AVPARSER_ADTS: 1 #adts解析器
某些格式,如ts等码流格式解复用后,可能会出现多个音频编码帧粘连的情况。CONFIG_AVPARSER_MP3和CONFIG_AVPARSER_ADTS可用于将mp3和adts粘连帧解析成一个个单独的音频帧。同时CONFIG_AVPARSER_ADTS可将m4a等容器格式解复用出来的带有Audio Special codec参数的aac转换为adts帧(当前av中的aac解码器仅支持adts帧解码)。
建议在开启mp3或aac解码时,同时配置对应的解析器。
5.1.6 播放控制配置等
CONFIG_AEFXER_IPC: 0 #核间音效处理 CONFIG_AEFXER_SONA: 0 #索那音效处理 CONFIG_ATEMPOER_IPC: 0 #核间变速播放 CONFIG_ATEMPOER_SONIC: 1 #变速播放 CONFIG_EQXER_IPC: 0 #量化器 CONFIG_EQXER_SILAN: 0 #量化器 CONFIG_FFTXER_IPC: 0 #fft变换 CONFIG_FFTXER_SPEEX: 0 #fft变换 CONFIG_RESAMPLER_IPC: 0 #核间音频重采样 CONFIG_RESAMPLER_SPEEX: 0 #speex重采样 CONFIG_PLAYER_TASK_STACK_SIZE: 2048 #播放器任务栈大小
CONFIG_AEFXER_xxx用于音效功能的处理,在某些芯片上功能可能不存在,开发者可根据现有接口定义扩展实现。
CONFIG_ATEMPOER_xxx用于倍速播放,采用sonic开源倍速处理算法。
CONFIG_PLAYER_TASK_STACK_SIZE用于控制播放任务栈大小。当前播放任务涵盖解复用、解码、解码后处理、音频输出等功能。这些功能随着解码器等配置的不同所要求的栈空间大小不一样。
5.2 配置依赖
要完成一个特定功能时,可能要求某些配置项要同时开启。如本地内存中一个mp4容器格式文件,其内部的音频编码格式可能是mp3的,最终通过alsa接口输出。则必要的配置项如下:
CONFIG_STREAMER_MEM: 1 CONFIG_DEMUXER_MP4: 1 CONFIG_DECODER_PVMP3: 1 CONFIG_OUTPUTER_ALSA: 1
5.2.1 外部影响配置
CONFIG_SAL CONFIG_TCPIP CONFIG_USING_TLS
av组件中的功能同时会受到外部其他配置项的影响。
CONFIG_SAL表示网络协议栈使用了at网卡模组等,其网络协议栈跑在模组上。CONFIG_TCPIP表示本地使用lwip网络协议栈。若av组件支持http等网络流的播放,CONFIG_SAL和CONFIG_TCPIP需要配置一个,否则不支持网络播放。当前CONFIG_SAL和CONFIG_TCPIP无法共存。
CONFIG_USING_TLS用于https网络流、加密流等播放,其依赖mbedtls组件。
5.2.2 解复用器和解码器
关于容器格式和编码格式的详细关系请自行查阅,这里不再赘述。此处仅介绍av组件中解复用器支持哪几种音频编码格式,如下表所示:
解复用器 |
解码器 |
CONFIG_DEMUXER_AMR |
CONFIG_DECODER_AMRNB、CONFIG_DECODER_AMRWB |
CONFIG_DEMUXER_FLAC |
CONFIG_DECODER_FLAC |
CONFIG_DEMUXER_MP3 |
CONFIG_DECODER_PVMP3 |
CONFIG_DEMUXER_MP4 |
CONFIG_DECODER_PVMP3 |
CONFIG_DEMUXER_OGG |
CONFIG_DECODER_OPUS、CONFIG_DECODER_SPEEX |
CONFIG_DEMUXER_RAWAUDIO |
CONFIG_DECODER_PCM |
CONFIG_DEMUXER_TS |
CONFIG_DECODER_PVMP3 |
CONFIG_DEMUXER_WAV |
CONFIG_DECODER_ADPCM_MS、CONFIG_DECODER_PCM、CONFIG_DECODER_ALAW、CONFIG_DECODER_MULAW |
5.3 典型应用场景配置
5.3.1 本地mp3文件音频播放
必须打开的宏配置如下:
CONFIG_STREAMER_FILE: 1 CONFIG_DEMUXER_MP3: 1 CONFIG_DECODER_PVMP3: 1 CONFIG_OUTPUTER_ALSA: 1
播放控制等配置项根据需要开启。如需要支持重采样功能,则需要配置CONFIG_RESAMPLER_SPEEX或CONFIG_RESAMPLER_IPC,下述类同。
5.3.2 内存中某wav音频文件adpcm_ms编码格式播放
必须打开的宏配置如下:
CONFIG_STREAMER_MEM: 1 CONFIG_DEMUXER_WAV: 1 CONFIG_DECODER_ADPCM_MS: 1 CONFIG_OUTPUTER_ALSA: 1
5.3.3 网络(https)mp3音频播放
必须打开的宏配置如下:
CONFIG_STREAMER_HTTP: 1 CONFIG_DEMUXER_MP3: 1 CONFIG_DECODER_PVMP3: 1 CONFIG_OUTPUTER_ALSA: 1
同时CONFIG_USING_TLS需要配置。
CONFIG_SAL和CONFIG_TCPIP根据产品网络类型配置一个。
5.3.4 hls直播流播放,采用核间mp3解码
必须打开的宏配置如下:
CONFIG_STREAMER_HTTP: 1 CONFIG_STREAMER_HLS: 1 #hls内部会使用CONFIG_STREAMER_HTTP CONFIG_DEMUXER_TS: 1 CONFIG_DECODER_IPC: 1 CONFIG_OUTPUTER_ALSA: 1 CONFIG_AVPARSER_MP3: 1
hls(http live stream)实际音视频数据是采用ts容器格式的,所以需要配置CONFIG_DEMUXER_TS。同时ts容器格式中解复用出来的mp3帧可能是黏连帧,所以需要配置CONFIG_AVPARSER_MP3。
核间解码固件编译时,请配置CONFIG_DECODER_PVMP3。
若hls采用ssl加密传输,CONFIG_USING_TLS也需要配置。
CONFIG_SAL和CONFIG_TCPIP根据产品网络类型配置一个。
06 核间解码器(主控侧)
6.1 核间解码器初始化
int adicore_init();
初始化核间解码器模块(一次即可)
返回值:
调用成功时返回0,否则返回-1
注意事项:
核间解码器是硬件相关的。需要搭配DSP核解码固件使用
6.2 打开核间解码器
6.2.1 核间解码器参数初始化
int adicore_conf_init(adi_conf_t *adi_cnf);
初始化解码器配置参数,用于打开解码器。
用户通过该接口获取默认的配置参数后,可根据需要修改相关参数。配置参数由结构体adi_conf_t表示,其包括采样格式、额外解码配置参数等,详细定义如下:
typedef ad_conf_t adi_conf_t; typedef struct ad_conf { sf_t sf; ///< 音频采样格式,如果有需要 uint8_t *extradata; ///< 额外编码参数,如果存在。如mp4 int32_t extradata_size; uint32_t block_align; ///< 块大小,如果需要 uint32_t bps; ///< 比特率, if needed } ad_conf_t;
返回值:
调用成功时返回0,否则返回-1。
注意事项:
- 对于裸流解码(透传),需要将sf传递给pcm解码器。
- 对于extradata和extradata_size,如果从封装格式中拆出核心解码信息是单独存储的,则需要传入。如mp4
- 对于block_align,则在adpcm_ms解码时需要从wav封装格式中拆出传入
- 对于运算量较小的音频解码算法,不建议使用核间解码
6.2.2 打开核间解码器
adicore_t* adicore_open(avcodec_id_t id, const adi_conf_t *adi_cnf);
根据解码配置参数adi_cnf打开指定codec id的解码器。
返回值:
调用失败时,返回NULL。
6.2.3 获取音频采样格式
int adicore_get_sf(adicore_t *hdl, sf_t *sf);
获取音频采样格式(某些格式,如aac,在open时通过传入的extradata(Audio Specific Config.)可解析出sf)
返回值:
调用成功时返回0,否则返回-1。
注意事项:
解码器打开后,获得的音频采样格式(sf)可能与解复用出来的有差异(与相应解码库的配置或实现有关系)。后续打开音频输出时,以解码器的sf为准
6.3 核间解码及控制
6.3.1 核间解码
int adicore_decode(adicore_t *hdl, avframe_t *frame, int *got_frame, const avpacket_t *pkt);
将一帧音频包pkt解码,解码数据存储在frame结构中。got_frame表明本次是否可以解出数据(可能需要向解码器传入多个音频包才能解出来一帧)。
返回值:
调用失败时,返回-1,大于0表示解码一帧消耗的原始编码帧字节数
6.3.2 重置解码器
int adicore_reset(adicore_t *hdl);
重置解码器。当音频跳转到新的时间点播放时,需要重置解码器。防止可能出现爆音现象。
返回值:
调用成功时返回0,否则返回-1。
6.4 关闭核间解码器
int adicore_close(adicore_t *hdl);
关闭解码器,销毁相关资源
返回值:
调用成功时返回0,否则返回-1。
6.5 如何使用
开发者可基于两个层次使用。一是基于AV框架中的解码层,其通过解码器注册的方式兼容了本地解码和核间解码。我们推荐使用此种方式。二是直接调用核间解码相关接口使用。
6.5.1 基于AV框架使用与集成
- adicore在YoC平台上的具体使用请参见components/av/avcodec/ad_ipc.c中的实现。其主要实现struct ad_ops定义的几个接口,如下所示:
struct ad_ops { const char *name; avcodec_id_t id; int (*open) (ad_cls_t *o); int (*decode) (ad_cls_t *o, avframe_t *frame, int *got_frame, const avpacket_t *pkt); int (*control) (ad_cls_t *o, int cmd, void *arg, size_t *arg_size); int (*reset) (ad_cls_t *o); int (*close) (ad_cls_t *o); }; const struct ad_ops ad_ops_ipc = { .name = "ipc", .id = AVCODEC_ID_AAC | AVCODEC_ID_MP3, ///< 默认支持mp3和aac核间解码 .open = _ad_ipc_open, .decode = _ad_ipc_decode, .control = _ad_ipc_control, .reset = _ad_ipc_reset, .close = _ad_ipc_close, };
- 在具体使用时,将ad_ops_ipc注册进av框架中。如下所示:
int ad_register_ipc() { extern struct ad_ops ad_ops_ipc; return ad_ops_register(&ad_ops_ipc); }
- adicore在player组件中的使用如下:
int player_init() { static int inited = 0; if (!inited) { stream_register_mem(); stream_register_file(); stream_register_http(); stream_register_fifo(); demux_register_wav(); demux_register_mp3(); demux_register_mp4(); demux_register_rawaudio(); ad_register_ipc(); ///< 注册adicore核间解码组件 ao_register_alsa(); inited = 1; } return 0; }
注意事项:
- 也可通过解决方案下package.yaml加入CONFIG_DECODER_IPC: 1配置项使能核间解码
- 若核间解码支持了mp3,则没有必要本地解码也注册mp3解码支持。否则会根据对应解码注册的先后顺序使用
- 再次提醒,核间解码是硬件相关的。需要搭配DSP核间解码固件使用
07 接口设计
7.1 播放器接口
7.1.1 播放器模块初始化
int player_init();
在调用播放器其他接口之前,要初始化播放器模块。
其主要功能为注册取流类型stream、解复用demux、解码器codec、音频输出ao等。
返回值:
调用成功时返回0,否则返回-1。
注意事项:
该接口调用一次即可,可多次调用
7.1.2 创建播放器
播放器配置参数初始化
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 *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; ///< 播放器事件回调函数 } ply_conf_t;
对于vol_index,当配置过大时,对于某些码流可能播放存在爆音。用户需根据需要确定音频曲线范围。
对于event_cb,用户需实现下面类型的定义。其中data和len当前作为预留使用
typedef void (*player_event_t)(player_t *player, uint8_t event, const void *data, uint32_t len);
播放器当前主要提供了三种事件状态,见下表说明:
状态 |
描述 |
PLAYER_EVENT_UNKNOWN |
未定义 |
PLAYER_EVENT_ERROR |
播放出错 |
PLAYER_EVENT_START |
开始播放 |
PLAYER_EVENT_FINISH |
播放结束 |
对于get_dec_cb,请参考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 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中定义 int64_t cur_pts; ///< 当前播放的present timestamp,用于获取当前播放时间 uint8_t status; ///< 播放器运行状态 aos_event_t evt; uint8_t evt_status; ///< 播放器事件 player_event_t event_cb; ///< 播放器回调,同player_conf中定义 uint8_t need_quit; ///< 是否需要退出播放,用于通知stream层接口,防止中途退出播放时长时间阻塞 aos_mutex_t lock; uint32_t rcv_timeout; ///< 同player_conf中定义 };
返回值:
调用失败时返回NULL。
7.1.3 播放器播放和控制
码流播放
int player_play(player_t *player, const char *url, uint64_t start_time);
从指定时间start_time(ms)播放指定url的码流,创建本次播放所需的资源,与player_stop相对应。
url格式的定义形式如下表,具体规则定义请参见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。
7.1.4 销毁播放器
int player_free(player_t *player);
释放播放器相关资源,与player_new相对应。
若播放器尚未停止播放,则在free之前会先停止播放。
返回值:
调用成功时返回0,否则返回-1。
7.1.5 媒体信息、播放时长及软件音量
获取当前播放时间
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。
7.2 媒体接入层接口
7.2.1 流类型注册
注册AV框架中stream类型
static inline int stream_register_all();
流的注册提供了一个总接口,但具体注册了哪些流类型可依赖于解决方案下package.yaml中的CONFIG_STREAMER_XXX等配置项。如solutions/pangu_demo/package.yaml中,以下配置项默认可将内存流、文件流、http网络流、队列流、加密流、hls(http live stream)注册进去。
CONFIG_STREAMER_MEM: 1 CONFIG_STREAMER_FILE: 1 CONFIG_STREAMER_HTTP: 1 CONFIG_STREAMER_FIFO: 1 CONFIG_STREAMER_CRYPTO: 1 CONFIG_STREAMER_HLS: 1
AV框架中当前提供了如下几种流类型的注册接口:
int stream_register_mem(); ///< 内存流 int stream_register_file(); ///< 文件流 int stream_register_http(); ///< http/https网络流 int stream_register_fifo(); ///< 队列流 int stream_register_crypto();///< 加密流 int stream_register_hls(); ///< http live stream(直播/点播均支持)
av组件当前默认将这几种流都注册进去。用户可根据需要分别注册所需流类型。
返回值:
调用成功时返回0,否则返回-1。
注册stream子类型
int stream_ops_register(const struct stream_ops *ops);
通过该接口,用户可以将自己扩展定义的ops子类型注册到系统中。
返回值:
调用成功时返回0,否则返回-1。
注意事项:
当前stream子类型最大支持注册16个,定义在stream.h中的STREAM_OPS_MAX。用户可根据需要修改
7.2.2 打开流
流配置参数初始化
int stream_conf_init(stm_conf_t *stm_cnf);
初始化流配置参数,用于stream的打开。
用户通过该接口获取stream默认的配置参数后,可根据需要修改相关参数。配置参数由结构体stm_conf_t表示,其包括缓存配置、解密回调、取流超时时间等,详细定义如下:
typedef struct stream_conf { enum stream_mode mode; ///< 打开流的模式 irq_av_t irq; ///< 取流中断回调。使用者通知stream,防止长时间阻塞 uint32_t rcv_timeout; ///< 取流超时时间(ms)。AOS_WAIT_FOREVER表示一直等,0表示使用默认配置 uint32_t cache_size; ///< 取流缓存大小(byte) uint32_t cache_start_threshold; ///< 当码流缓存到cache_size的指定百分比时才开始播放,防止网络状态不好时播放时断时续,优化播放效果。取值范围是0~100 get_decrypt_cb_t get_dec_cb; ///< 解密回调接口,用于从上层获取秘钥(若流是加密的) } stm_conf_t;
对于get_dec_cb,用户需实现下面类型的定义。该接口在从码流中解析出特殊key时,可回调到上层获取秘钥。具体可由用户扩展实现自行定义。
et_decrypt_cb_t)(const void *in, size_t ilen, void *out, size_t *olen);
返回值:
调用成功时返回0,否则返回-1。
注意事项:
对于流的模式,当前仅支持STREAM_READ读模式
打开流
stream_cls_t* stream_open(const char *url, const stm_conf_t *stm_cnf);
根据配置参数stm_cnf,打开指定url的流。该接口调用前,必须保证对应url的stream已经成功注册。其会根据url的前缀找到应的stream_ops打开流。stream_cls_t结构体中的主要成员如下:
typedef struct stream_cls stream_cls_t; struct stream_cls { int32_t buf_pos; ///< 当前读指针在buf数组中的偏移量 int32_t buf_len; ///< buf数组有效数据长度 int32_t pos; ///< 当前读进buf数组中的位置 int32_t size; ///< 流的大小 char *url; uint8_t eof; ///< 流是否结束 uint8_t quit; uint8_t seekable; ///< 流是否支持seek操作 irq_av_t irq; get_decrypt_cb_t get_dec_cb; ///< 解密接口,同stm_conf_t中定义 uint32_t rcv_timeout; ///< 读超时时间,同stm_conf_t中定义 aos_event_t cache_quit; ///< 用户缓存线程事件 uint32_t cache_size; ///< 同stm_conf_t中定义 uint32_t cache_start_threshold; ///< 同stm_conf_t中定义 uint8_t cache_start_upto; ///< 用于缓存是否达到阈值标记 uint8_t cache_status; ///< 缓存的状态,停止态、运行态 int32_t cache_pos; ///< cache position. used when cache enable void *priv; ///< 指向stream子类结构 const struct stream_ops *ops; ///< 子类ops aos_mutex_t lock; sfifo_t *fifo; ///< 用于缓冲线程的队列 uint8_t buf[STREAM_BUF_SIZE_MAX];///< 内部buf块(比如说流来自sd卡,为效率考虑按块读取) };
该接口对于网络流(实际依赖于stream_ops结构中的enable_cache字段),会根据stm_cnf中的cache_size创建缓冲fifo和缓冲线程,以用于处理网络抖动带来的播放卡顿问题。
url格式定义规则如下表所示:
流类型 |
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 |
- 对于能够探测到媒体信息的码流,url格式中的avformat、avcodec、channel、rate字段不是必须的。
- 这些配置项一般用于raw pcm的播放(通过这些参数传入裸流的具体格式)。
- avformat字段可选有rawaudio/wav/mp3/m4a等。
- avcodec字段可选有pcm_s16be/pcm_s32be/pcm_s16le/pcm_s32le/pcm_s8/pcm_u16be/pcm_u32be/pcm_u16le/pcm_u32le/pcm_u8。
url格式具体示例如下表所示:
流类型 |
示例 |
网络流 |
|
文件流(SD卡) |
file:///fatfs0/test.MP3 |
内存流 |
mem://addr=765432&size=1024&avformat=rawaudio&avcodec=pcm_s16le &channel=1&rate=16000 |
fifo流 |
fifo://tts/1 |
加密流 |
crypto://http://www.baidu.com/xx.mp3?key=965582bcbff7da4c3590bf1640c94f06&iv=0123456789abcdef0123456789abcdef |
hls流 |
返回值:
调用失败时,返回NULL。
注意事项:
- 对于加密流,其是通过ffmpeg crypto协议加密的,详情请自行查阅。
- 加密命令示例:ffmpeg -i input.mp3 -c copy -key "855582bceff7de4c3590bf1640c94f05" -iv "0123456789ABCDEF0123456789ABCDEF" crypto:out_enc.mp3
7.2.3 读写、控制流
输入流
int stream_read(stream_cls_t *o, uint8_t *buf, size_t count);
从指定stream中读取count个字节的数据存储到buf中。
返回值:
大于0表示实际读取流字节数,等于0表示读空,失败返回-1
单变量读取
int stream_r8(stream_cls_t *o); uint16_t stream_r16be(stream_cls_t *o); uint32_t stream_r24be(stream_cls_t *o); uint32_t stream_r32be(stream_cls_t *o); uint64_t stream_r64be(stream_cls_t *o); uint16_t stream_r16le(stream_cls_t *o); uint32_t stream_r24le(stream_cls_t *o); uint32_t stream_r32le(stream_cls_t *o); uint64_t stream_r64le(stream_cls_t *o);
按照1/2/3/4/8字节(大小端)读取流
输出流
int stream_write(stream_cls_t *o, const uint8_t *buf, size_t count);
将指定字节大小count的buf写入到stream中。该接口尚未实现。
返回值:
大于0表示实际写入流字节数,等于0表示写满,失败返回-1
跳转流
int stream_seek(stream_cls_t *o, int32_t offset, int whence);
跳转到流的指定位置处。whence表示偏移基准,有SEEK_SET/SEEK_CUR/SEEK_END这三个取值。通过offset和whence可以计算出跳转的绝对位置。
当要跳转的绝对问题不在当前缓存范围内,对于网络流来说底层会退出缓存线程并重新根据新的位置打开流、创建相关缓存资源。
返回值:
调用成功时返回0,否则返回-1。
跳过流
int stream_skip(stream_cls_t *o, int32_t offset);
从stream当前位置跳过指定偏移量。
当要跳转的绝对问题不在当前缓存范围内,对于网络流来说底层会退出缓存线程并重新根据新的位置打开流、创建相关缓存资源。
返回值:
调用成功时返回0,否则返回-1。
控制流
int stream_control(stream_cls_t *o, int cmd, void *arg, size_t *arg_size);
通过控制命令字cmd控制流。该接口尚未实现,预留。
返回值:
调用成功时返回0,否则返回-1。
7.2.4 获取流状态、信息
获取流位置
int stream_tell(stream_cls_t *o);
获取流当前读写位置
返回值:
调用成功时大于等于0,否则返回-1。
流是否支持跳转
int stream_is_seekable(stream_cls_t *o);
获取流是否支持跳转
返回值:
支持流跳转返回1,否则返回0。
流是否结束
int stream_is_eof(stream_cls_t *o);
获取流是否读结束
返回值:
已结束返回1,否则返回0。
流是否打断
int stream_is_interrupt(stream_cls_t *o);
获取流的获取等是否被上层打断,可用于上层快速退出返回。
返回值:
已打断返回1,否则返回0。
获取流大小
int stream_get_size(stream_cls_t *o);
获取流的大小。
返回值:
调用失败返回-1。
注意事项:
对于直播流或fifo流等,流的大小可能不是固定的或获取不到。
获取流路径
onst char* stream_get_url(stream_cls_t *o);
获取当前流的url地址
返回值:
调用失败返回NULL。
7.2.5 关闭流
int stream_close(stream_cls_t *o);
关闭流,销毁相关资源。
返回值:
调用成功时返回0,否则返回-1。
7.3 解复用层接口
7.3.1 解复用类型注册
注册AV框架中的解复用类型
static inline int demux_register_all();
解复用器的注册提供了一个总接口,但具体注册了哪些解复用器类型依赖于解决方案下package.yaml中的CONFIG_DEMUXER_XXX等配置项。如solutions/pangu_demo/package.yaml中,以下配置项默认可将wav、mp3、mp4、adts、裸流、flac、asf、amr、ts、ogg注册进去。
CONFIG_DEMUXER_WAV: 1 CONFIG_DEMUXER_MP3: 1 CONFIG_DEMUXER_MP4: 1 CONFIG_DEMUXER_ADTS: 1 CONFIG_DEMUXER_RAWAUDIO: 1 CONFIG_DEMUXER_FLAC: 1 CONFIG_DEMUXER_ASF: 1 CONFIG_DEMUXER_AMR: 1 CONFIG_DEMUXER_TS: 1 CONFIG_DEMUXER_OGG: 1
AV框架中当前提供了如下几种解复用器的注册接口:
int demux_register_wav(); ///< wav格式解复用 int demux_register_mp3(); ///< mp3格式解复用 int demux_register_mp4(); ///< mp4格式解复用 int demux_register_adts(); ///< adts格式解复用 int demux_register_rawaudio(); ///< pcm裸流格式解复用 int demux_register_flac(); ///< flac格式解复用 int demux_register_asf(); ///< asf格式解复用 int demux_register_amr(); ///< amr格式解复用 int demux_register_ts(); ///< ts格式解复用 int demux_register_ogg(); ///< ogg格式解复用
播放器当前默认将这几种解复用类型都注册进去。用户可根据需要分别注册所需类型。
返回值:
调用成功时返回0,否则返回-1。
注册解复用子类型
int demux_ops_register(const struct demux_ops *ops);
通过该接口,用户可以将自己扩展定义的ops子类型注册到系统中。
返回值:
调用成功时返回0,否则返回-1。
注意事项:
当前demux子类型最大支持注册16个,定义在demux.h中的DEMUX_OPS_MAX。用户可根据需要修改
7.3.2 打开解复用
demux_cls_t* demux_open(stream_cls_t *s);
根据流打开解复用器。其会根据url中的avformat参数或者对流的内容探测,找到最匹配的demux_ops。
函数返回后,将会获取到码流的编解码、采样率等信息。用户可根据这些信息创建相应的解码器解码。demux_cls_t结构体中的主要成员如下:
typedef struct demux_cls demux_cls_t; struct demux_cls { stream_cls_t *s; sh_audio_t ash; ///< 解复用出的音频采样率、位率等 aos_mutex_t lock; avpacket_t fpkt; ///< 媒体第一个编码帧,用于编解码信息获取,同时减少不必要的seek size_t id3v2size; ///< id3信息的大小 uint64_t bps; ///< 码率 size_t time_scale; ///< 时基大小 uint64_t duration; ///< 音频总时长,ms track_info_t *tracks; ///< 媒体轨道信息 void *priv; ///< 指向解复用器子类结构 const struct demux_ops *ops; ///< 解复用器子类接口 };
返回值:
调用失败时,返回NULL。
7.3.3 解出音频包及控制
解出一帧编码数据
int demux_read_packet(demux_cls_t *o, avpacket_t *pkt);
调用找到的demux_ops解复用器,解出一帧编码包。包的数据内容和大小存储在pkt结构中。avpacket_t结构体中的主要成员如下:
typedef struct avpacket { uint8_t *data; int32_t size; ///< data的总大小 int32_t len; ///< 编码帧data的有效长度 int64_t pts; ///< 当前帧的present timestamp } avpacket_t;
返回值:
大于0表示实际读取编码帧字节数,等于0表示流已经结束,失败返回-1
解复用器跳转
int demux_seek(demux_cls_t *o, uint64_t timestamp);
解复用器跳转到指定时间点(ms),后续可从该时间点开始读取一帧编码帧数据解码播放。
返回值:
调用成功时返回0,否则返回-1。
解复用器控制
int demux_control(demux_cls_t *o, int cmd, void *arg, size_t *arg_size);
通过控制命令字cmd控制流。该接口尚未实现,预留。
返回值:
调用成功时返回0,否则返回-1。
7.3.4 关闭解复用
int demux_close(demux_cls_t *o);
关闭解复用器,销毁相关资源。
返回值:
调用成功时返回0,否则返回-1。
7.4 解码层接口
7.4.1 解码器类型注册
注册AV框架中的解码器类型
static inline int ad_register_all();
解码器的注册提供了一个总接口,但具体注册了哪些解码器类型可依赖于解决方案下package.yaml中的CONFIG_DECODER_XXX等配置项。如solutions/pangu_demo/package.yaml中,以下配置项默认可将pcm裸流、mp3解码器(pvmp3)注册进去。
CONFIG_DECODER_PCM: 1 CONFIG_DECODER_PVMP3: 1 CONFIG_DECODER_FLAC: 0 CONFIG_DECODER_ADPCM_MS: 0 CONFIG_DECODER_AMRNB: 0 CONFIG_DECODER_AMRWB: 0 CONFIG_DECODER_OPUS: 0 CONFIG_DECODER_SPEEX: 0 CONFIG_DECODER_ALAW: 0 CONFIG_DECODER_MULAW: 0 CONFIG_DECODER_IPC: 0
AV框架中当前提供了如下几种解码器的注册接口:
int ad_register_pcm(); ///< 用于裸流解码(透传) int ad_register_pvmp3(); ///< 用于mp3解码,使用pvmp3解码库 int ad_register_adpcm_ms(); ///< 用于adpcm_ms解码 int ad_register_flac(); ///< 用于flac解码 int ad_register_amrnb(); ///< 用于amrnb解码 int ad_register_amrwb(); ///< 用于amrwb解码 int ad_register_opus(); ///< 用于opus解码 int ad_register_speex(); ///< 用于speex解码 int ad_register_alaw(); ///< 用于a律解码 int ad_register_mulaw(); ///< 用于u律解码 int ad_register_ipc(); ///< 用于核间解码(具体的解码运算工作跑在其他核上)
用户若不想通过宏定义形式使用ad_register_all接口,可以分别调用这些接口。
对于核间解码ad_register_ipc,若具体mp3解码算法运行在其他核(此处称为DSP核)上,则本地无需调用ad_register_pvmp3接口。同时在DSP核上,将ad_register_pvmp3注册进去,编译成固件。
返回值:
调用成功时返回0,否则返回-1。
注册解码器子类型
int ad_ops_register(const struct ad_ops *ops);
通过该接口,用户可以将自己扩展定义的ops子类型注册到系统中。
返回值:
调用成功时返回0,否则返回-1。
注意事项:
当前音频解码器子类型最大支持注册16个,定义在ad.h中的AD_OPS_MAX。用户可根据需要修改
7.4.2 打开解码器
解码器参数初始化
int ad_conf_init(ad_conf_t *ad_cnf);
初始化解码器配置参数,用于打开解码器。
用户通过该接口获取默认的配置参数后,可根据需要修改相关参数。配置参数由结构体ad_conf_t表示,其包括采样格式、额外解码配置参数等,详细定义如下:
typedef struct ad_conf { sf_t sf; ///< 音频采样格式,如果有需要 uint8_t *extradata; ///< Audio Specific Config.额外编码参数,如果存在。如mp4 int32_t extradata_size; uint32_t block_align; ///< 块大小,如果需要 uint32_t bps; ///< 比特率, if needed } ad_conf_t;
返回值:
调用成功时返回0,否则返回-1。
注意事项:
- 对于裸流解码(透传),需要将sf传递给pcm解码器
- 对于extradata和extradata_size,如果从封装格式中拆出核心解码信息是单独存储的,则需要传入。如mp4
- 对于block_align,则在adpcm_ms解码时需要从wav封装格式中拆出传入
打开解码器
ad_cls_t* ad_open(avcodec_id_t id, const ad_conf_t *ad_cnf);
根据解码配置参数ad_cnf打开指定codec id的解码器。ad_cls_t结构体中的主要成员如下:
struct ad_cls { sh_audio_t ash; ///< 音频采样等信息 aos_mutex_t lock; void *priv; ///< 解码器子类结构 const struct ad_ops *ops; ///< 解码器子类接口 };
返回值:
调用失败时,返回NULL。
注意事项:
解码器打开后,获得的音频采样格式(sf)可能与解复用出来的有差异(与相应解码库的配置或实现有关系)。后续打开音频输出时,以解码器的sf为准(从sh_audio_t结构中获取)
7.4.3 音频解码及控制
解码音频帧
int ad_decode(ad_cls_t *o, avframe_t *frame, int *got_frame, const avpacket_t *pkt);
将一帧音频包pkt解码,解码数据存储在frame结构中。got_frame表明本次是否可以解出数据(可能需要向解码器传入多个音频包才能解出来一帧)。avframe_t结构体中的主要成员如下:
typedef struct avframe { #define AV_DATA_POINTERS_MAX (8) avmedia_type_t type; ///< 媒体类型 uint8_t *data[AV_DATA_POINTERS_MAX]; ///< 存储帧数据指针。帧的申请来源于内存池或malloc申请 int linesize[AV_DATA_POINTERS_MAX]; ///< 帧的有效数据大小 int capsize[AV_DATA_POINTERS_MAX]; ///< data指针指向的数据总大小,linesize 需<= capsize uint8_t *mpool; ///< 表明data指针指向的内存来源于此内存池 size_t msize; ///< 内存池的大小 size_t moffset; ///< 内存池当前已使用的偏移量 /* 音频信息 */ sf_t sf; ///< 音频采样格式 int nb_samples; ///< 每个声道的采样个数 } avframe_t;
返回值:
调用失败时,返回-1,大于0表示解码一帧消耗的原始编码帧字节数
重置解码器
int ad_reset(ad_cls_t *o);
重置解码器。当音频跳转到新的时间点播放时,需要重置解码器。防止可能出现爆音现象。
返回值:
调用成功时返回0,否则返回-1。
控制解码器
int ad_control(ad_cls_t *o, int cmd, void *arg, size_t *arg_size);
通过控制命令字cmd控制流。该接口尚未实现,预留。
返回值:
调用成功时返回0,否则返回-1。
7.4.4 关闭解码器
int ad_close(ad_cls_t *o);
关闭解码器,销毁相关资源
返回值:
调用成功时返回0,否则返回-1。
7.5 音频输出接口
7.5.1 音频输出类型注册
注册AV框架中的音频输出类型
static inline int ao_register_all()
音频输出的注册提供了一个总接口,但具体注册了哪些音频输出类型依赖于package.yaml中的CONFIG_AV_AO_XXX等配置项。以下配置项可将alsa音频输出注册进去。
CONFIG_AV_AO_ALSA: 1
AV框架中当前默认仅提供了类似alsa的音频输出注册接口:
int ao_register_alsa();
返回值:
调用成功时返回0,否则返回-1。
注册音频输出子类型
int ao_ops_register(const struct ao_ops *ops);
通过该接口,用户可以将自己扩展定义的ops子类型注册到系统中。
返回值:
调用成功时返回0,否则返回-1。
注意事项:
当前音频解码器子类型最大支持注册6个,定义在ao.h中的AO_OPS_MAX。用户可根据需要修改
7.5.2 打开音频输出
音频输出参数初始化
int ao_conf_init(ao_conf_t *ao_cnf);
初始化音频输出配置参数,用于打开音频输出。
用户通过该接口获取默认的配置参数后,可根据需要修改相关参数。配置参数由结构体ao_conf_t表示,其包括音频输出名称、重采样输出配置、软件音量等,详细定义如下:
typedef struct ao_conf { char *name; ///< 音频输出名 uint32_t period_ms; ///< 音频输出每消耗多少ms的数据量来一次中断 uint32_t period_num; ///< period_ms的个数. 通过period这两个参数可以计算出ao的缓存大小为(period_num * period_ms * (rate / 1000) * 2 * (16/8)) uint8_t eq_segments; ///< 量化器的段数(默认不支持,用户根据需要扩展) uint8_t *aef_conf; ///< 音效配置参数(默认为索那音效,需商务合作或扩展) size_t aef_conf_size; ///< 音效配置大小 uint32_t resample_rate; ///< 非0表示重采样输出到该rate,否则原样输出 uint8_t vol_en; ///< 软件音量使能配置 uint8_t vol_index; ///< 软件音量大小(不同于硬件音量调节),范围为0~255 } ao_conf_t;
对于vol_index,当配置过大时,对于某些码流可能播放存在爆音。用户需根据需要确定音频曲线范围。
返回值:
调用成功时返回0,否则返回-1。
打开音频输出
ao_cls_t* ao_open(sf_t sf, const ao_conf_t *ao_cnf);
根据配置参数ao_cnf和音频采样格式sf打开音频输出,创建相关资源。此时会根据配置参数,创建相应的过滤器,并将多个过滤器链接起来。ao_cls_t结构体中的主要成员如下:
struct ao_cls { sf_t sf; ///< 最终配置到实际音频输出的sample format void *priv; ///< 指向音频输出子类 uint8_t start; ///< 音频输出开始标志,用于内部状态控制 uint8_t interrupt; sf_t ori_sf; ///< 音频输出打开时,传入的sample format uint32_t period_ms; ///< 同ao_conf_t中定义 uint32_t period_num; ///< 同ao_conf_t中定义 uint32_t resample_rate; ///< 重采样,同ao_conf_t中定义 avfilter_t *avfc; ///< 过滤器链头(多个过滤器会串联起来) avfilter_t *avf_vol; ///< 软件音量过滤器 uint8_t vol_en; ///< 同ao_conf_t中定义 uint8_t vol_index; ///< 同ao_conf_t中定义 uint8_t *aef_conf; ///< 同ao_conf_t中定义 size_t aef_conf_size; ///< 同ao_conf_t中定义 uint8_t eq_en; ///< eq使能开关 uint8_t eq_segments; ///< 量化器段数,同ao_conf_t中定义 eqfp_t *eq_params; ///< 量化器的配置参数数组 avfilter_t *avf_eq; ///< equalizer过滤器 avframe_t *oframe; ///< 用于过滤器使用 const struct ao_ops *ops; ///< 指向音频输出子类接口 aos_mutex_t lock; };
返回值:
调用失败时,返回NULL。
注意事项:
- 音频最终输出格式为16bits,stereo
- 音频输出可以打开多个。单需要重采样输出到同一采样率。由于当前尚不支持混音功能,多个ao不能同时写。
7.5.3 音频输出操作
开始音频输出
int ao_start(ao_cls_t *o);
启动音频输出。
返回值:
调用成功时返回0,否则返回-1。
停止音频输出
int ao_stop(ao_cls_t *o);
停止音频输出。
返回值:
调用成功时返回0,否则返回-1。
写入数据
int ao_write(ao_cls_t *o, const uint8_t *buf, size_t count);
向音频输出中写入指定count字节的pcm数据。该接口是一个阻塞操作。当出错或将buf中的数据完全写到音频输出后才返回。调用该接口时,音频输出内部会根据相关配置参数使用创建的过滤器将音频buf处理后,才会写到音频输出。
返回值:
大于等于0表示实际写入pcm字节数,否则返回-1。
注意事项:
当调用ao_start后才能调用此接口,否则数据写不成功
播尽ao内部缓冲
int ao_drain(ao_cls_t *o);
关闭音频输出前,将已写入到音频输出的全部pcm完全播放完。当通过ao_write接口将pcm数据写完后,若该接口没有调用就直接关闭音频输出,可能导致音频尾部数据得不到播放。
返回值:
调用成功时返回0,否则返回-1。
控制音频输出
int ao_control(ao_cls_t *o, int cmd, void *arg, size_t *arg_size);
通过控制命令字cmd控制音频输出。
具体的音频输出控制命令字如下表所示:
状态 |
对应参数类型 |
描述 |
AO_CMD_UNKNOWN |
—— |
未定义 |
AO_CMD_EQ_ENABLE |
oeq_seten_t* |
EQ使能配置 |
AO_CMD_EQ_SET_PARAM |
oeq_setpa_t* |
EQ参数配置 |
AO_CMD_VOL_SET |
ovol_set_t* |
软件音量配置 |
其中对于AO_CMD_EQ_ENABLE和AO_CMD_EQ_SET_PARAM作为预留,需要自行扩展实现相应EQ接口。
对于AO_CMD_VOL_SET,只有当音频输出创建时vol_en使能才会生效。
返回值:
调用成功时返回0,否则返回-1。
7.5.4 关闭音频输出
int ao_close(ao_cls_t *o);
关闭音频输出,销毁相关资源。
返回值:
调用成功时返回0,否则返回-1。
08 播放器使用示例
8.1 播放网络/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); }
8.2 播放内存中的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);
8.3 语音合成(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); }
09 下期预告
以上即为YoC AV(多媒体)组件的全部介绍,下期我们将继续为大家推荐YoC组件介绍系列系列内容,介绍YoC的PARTITION组件。欢迎大家持续关注RISC-V系列内容。