目录
1 dtaudio介绍
2 audio decoder接口说明
3 audio decoder添加步骤
本篇文章主要介绍如何在dtaudio的模块中添加一个decoder。 之前我们添加了file stream 以及 aac demuxer。
本次添加一个aac 的decoder,利用第三方库faad2 来解码aac
这样用户就可以体验一下去除ffmpeg的dtplayer。之前没有decoder dtplayer不得不借助ffmpeg的libavcode来解码
完成添加aac decoder之后,就可以变身成为一个简单的 aac 音乐播放器。
1 dtaudio介绍
先介绍下dtaudio模块,之前在结构设计的文章中有对dtaudio的定位。dtaudio模块是dtplayer框架中的音频处理模块,
涵盖了音频解码、音频后处理(未添加)、音频输出,本篇主要介绍音频解码器的添加。
与dtaudio相关的代码主要在dtaudio目录下,主要有
dtaudio_api.c dtaudio模块的对外接口
dtaudio.c dtaudio中间模块,向上支持对外接口,向下管理decoder render
dtaudio_decoder.c decoder管理模块
dtaudio_filter.c filter管理模块(暂未实现)
dtaudio_output.c render模块
audio_decoder/ 实际decoder的代码实现
audio_out/ 实际render的代码实现
【dtaudio_api.h】
int dtaudio_init (void **audio_priv, dtaudio_para_t * para, void *parent);
int dtaudio_start (void *audio_priv);
int dtaudio_pause (void *audio_priv);
int dtaudio_resume (void *audio_priv);
int dtaudio_stop (void *audio_priv);
int64_t dtaudio_get_pts (void *audio_priv);
int dtaudio_drop (void *audio_priv, int64_t target_pts);
int64_t dtaudio_get_first_pts (void *audio_priv);
int dtaudio_get_state (void *audio_priv, dec_state_t * dec_state);
int dtaudio_get_out_closed (void *audio_priv);
dtaudio对外提供的接口,具体含义如下
dtaudio_init 初始化audio模块,包括后面的decoder和render
dtaudio_start 启动播放, render模块开始输出
dtaudio_pause dtaudio_resume 暂停 恢复
dtaudio_stop 结束播放
dtaudio_get_pts 获取当前audio pts
dtaudio_drop 播放开头丢掉部分数据
dtaudio_get_first_pts 判断第一帧pts是否已经拿到
dtaudio_get_state 获取audio状态,主要是decoder以及render的状态
dtaudio_get_out_closed 判断数据是否播放完毕,在播放结束的时候使用
【dtaudio.h】
void audio_register_all();
int audio_read_frame (void *priv, dt_av_frame_t * frame);
int audio_output_read (void *priv, uint8_t * buf, int size);
int64_t audio_get_current_pts (dtaudio_context_t * actx);
int64_t audio_get_first_pts (dtaudio_context_t * actx);
int audio_drop (dtaudio_context_t * actx, int64_t target_pts);
void audio_update_pts (void *priv);
int audio_get_dec_state (dtaudio_context_t * actx, dec_state_t * dec_state);
int audio_get_out_closed (dtaudio_context_t * actx);
int audio_start (dtaudio_context_t * actx);
int audio_pause (dtaudio_context_t * actx);
int audio_resume (dtaudio_context_t * actx);
int audio_stop (dtaudio_context_t * actx);
int audio_init (dtaudio_context_t * actx);
audio_register_all负责注册所有的解码器和render
audio_read_frame 从dthost模块读取一包数据解码
audio_output_read render模块从audio 缓冲区读取pcm 输出
audio_get_current_pts 计算当前audio pts
audio_get_first_pts 计算第一包pts是否已经拿到
audio_drop 播放开头丢掉部分数据
audio_update_pts 刷新dthost中的audio pts信息
audio_get_dec_state 获取解码器状态
audio_get_out_closed 判断数据是否render完毕
audio_start audio_pause audio_resume audio_stop audio_init 状态机控制
这里从api可以看出dtaudio_api 主要是对dtaudio的封装,实际的实现由dtaudio完成。同理dtaudio分别又是对dtaudio_decoder dtaudio_filter dtaudio_output的封装
再进一步,dtaudio_decoder是对audio_decoder下面实际解码器的封装, dtaudio_output是对audio_out下实际render的封装。
一层一层封装下来,实现音频处理的功能。
这里就不展开讨论audio的完整处理流程了,只介绍写decoder的工作流程
1.1 register
在decoder工作之前,首先需要注册,注册的代码在dtaudio.c中
void audio_register_all(){ adec_register_all(); aout_register_all(); }
这里主要是将decoder和render全部注册
【dtaudio_decoder.c】
void adec_register_all () { /*Register all audio_decoder */ REGISTER_ADEC (FAAD, faad); REGISTER_ADEC (FFMPEG, ffmpeg); return; }
这里可以看到,主力还是通过ffmpeg来解码,这里我们也添加了faad来解码aac
1.2 decoder select
【dtaudio_decoder.c】
audio_decoder_init->select_audio_decoder
static int select_audio_decoder (dtaudio_decoder_t * decoder) { dec_audio_wrapper_t **p; dtaudio_para_t *para = &(decoder->aparam); p = &first_adec; while (*p != NULL) { if ((*p)->afmt != para->afmt && (*p)->afmt != AUDIO_FORMAT_UNKOWN) p = &(*p)->next; else //fmt found, or ffmpeg found break; } if (!*p) { dt_info (DTAUDIO_LOG_TAG, "[%s:%d]no valid audio decoder found afmt:%d\n", __FUNCTION__, __LINE__, para->afmt); return -1; } decoder->dec_wrapper = *p; dt_info (TAG, "[%s:%d] select--%s audio decoder \n", __FUNCTION__, __LINE__, (*p)->name); return 0; }
这里在audio_decoder_init的方法中会调用select_audio_decoder
选择方法比较简单,若有其他解码器并符合格式需求,就选择,否则选择ffmpeg来解码
相信大家都希望使用自己移植的解码器来工作。
1.3 decode流程
当初始化完毕后,会启动audio 解码线程,代码如下
#define MAX_ONE_FRAME_OUT_SIZE 192000
static void *audio_decode_loop (void *arg)
{
......
dt_info (TAG, "[%s:%d] AUDIO DECODE START \n", __FUNCTION__, __LINE__);
do
{
ret = audio_read_frame (decoder->parent, &frame);
DECODE_LOOP:
/*decode frame */;
used = dec_wrapper->decode_frame (dec_wrapper, pinfo);
REFILL_BUFFER:
ret = buf_put (out, pinfo->outptr + fill_size, pinfo->outlen);
}
while (1);
EXIT:
dt_info (TAG, "[file:%s][%s:%d]decoder loop thread exit ok\n", __FILE__, __FUNCTION__, __LINE__);
/* free adec_ctrl_t buf */
if(pinfo->outptr)
free(pinfo->outptr);
pinfo->outlen = pinfo->outsize = 0;
pthread_exit (NULL);
return NULL;
}
这里只将解码器循环核心的部分贴了下,
此处是audio_decode_loop 循环执行: 读包 - 解码 - 缓存 等操作来完成解码,
可以想象,render模块同样有个线程执行: 读缓存 - render的操作来完成pcm的消耗
其中,dec_wrapper代表的是实际的解码器,通过调用decode_frame来解码。
2 audio decoder接口说明
下面介绍下audio decoder部分的结构提及接口
移植audio decoder主要是实现dec_audio_wrapper_t的接口及实现
【dtaudio_decoder.h】
typedef enum
{
ADEC_STATUS_IDLE,
ADEC_STATUS_RUNNING,
ADEC_STATUS_PAUSED,
ADEC_STATUS_EXIT
} adec_status_t;
typedef struct{
uint8_t *inptr;
int inlen;
int consume;
uint8_t *outptr;
int outsize; // buffer size
int outlen; // buffer level
int info_change;
int channels;
int samplerate;
int bps;
}adec_ctrl_t;
typedef struct dec_audio_wrapper
{
int (*init) (struct dec_audio_wrapper * wrapper,void *parent);
int (*decode_frame) (struct dec_audio_wrapper * wrapper, adec_ctrl_t *pinfo);
int (*release) (struct dec_audio_wrapper * wrapper);
char *name;
audio_format_t afmt; //not used, for ffmpeg
int type;
void *adec_priv;
struct dec_audio_wrapper *next;
void *parent;
} dec_audio_wrapper_t;
简单介绍下接口
init 初始化
decode_frame 解码一包数据
release 释放资源
name 解码器的名称
afmt 解码器格式
adec_priv 解码器私有数据
parent: 这里是dtaudio_decoder_t
函数指针中的参数这里介绍下:
wrapper: 本身
parent: dtaudio_decoder_t变量
pinfo: adec_ctrl_t 结构体变量,比较重要,移植解码器需仔细研究,作用是提供解码器需要的参数: 输入buf 输出buf 输入size 等
详细介绍下adec_ctrl_t 的成员
uint8_t *inptr; 输入buf int inlen; 输入长度 int consume; 输入buf已经消耗了多少 uint8_t *outptr; 输出buf int outsize; 输出buf最大长度 int outlen; 输出buf int info_change; 音频参数是否发生了改变 int channels; 当前声道数 int samplerate; 当前采样率 int bps; 当前bps(暂时没作用)
3 添加步骤
代码位置:添加的audio decoder代码需统一存放在dtaudio/audio_decoder/下,方便管理
这里以faad为例,介绍添加一个audio decoder的具体示例
代码位置:dtaudio/audio_decoder/dec_audio_faad.c
3.1 定义结构体
【dec_audio_faad.c】
dec_audio_wrapper_t adec_faad_ops = {
.name = "faad audio decoder",
.afmt = AUDIO_FORMAT_AAC,
.type = DT_TYPE_AUDIO,
.init = faad_init,
.decode_frame = faad_decode,
.release = faad_release,
};
结构体主要是初始化name id等,最主要的是实现函数指针接口,这样在dtaudio_decoder.c的 解码线程中才能调用解码
3.2 注册解码器
主要在adec_register_all中添加faad选项
void adec_register_all ()
{
/*Register all audio_decoder */
REGISTER_ADEC (FAAD, faad);
REGISTER_ADEC (FFMPEG, ffmpeg);
return;
}
3.3 实现解码器各个函数
具体可参考faad的具体实现,比较简单
3.4 配置编译
由于此aac decoder以来第三方解码器,因此需先设置开关
【config.mk】
DT_FAAD= yes
ifeq ($(DT_FAAD),yes)
DT_CFLAGS += -DENABLE_ADEC_FAAD=1
else
DT_CFLAGS += -DENABLE_ADEC_FAAD=0
endif
LDFLAGS-$(DT_FAAD) += -lfaad
这里宏定义ENABLE_ADEC_FAAD由DT_FAAD控制,因此只需要配置DT_FAAD就可以了
然后是加上faad库依赖,这里faad的库若未安装到公共目录,请修改成绝对路径即可。
最后是加上源码支持
【Makefile】
SRCS_COMMON-$(DT_FAAD) += dtaudio/audio_decoder/dec_audio_faad.c
这里也是由DT_FAAD来控制的
至此一个audio decoder就添加成功了。
这样我们就实现了去除ffmpeg依赖的音频播放器的所有条件
stream: file
demuxer: aac
decoder : faad
render: alsa or sdl1 or sdl2
这样直接在config.mk设置DT_FFMPEG= no去除ffmpeg依赖就可以了。
源代码地址:https://github.com/peterfuture/dtplayer
联系开发者:[email protected]
blog: http://blog.csdn.net/u011350110
开源中国地址:http://www.oschina.net/p/dtplayer
由于后面随着开发的进行文章会进行细节的更新,因此为了保证读者随时读到最新的内容,文章禁止转载,多谢大家支持。