目前最关心声音的采集与回放,所以第一篇从PJSIP的PJMedia声卡驱动流层开始研究:
API的使用参考: http://www.pjsip.org/docs/latest/pjmedia/docs/html/group__audio__device__api.htm
数据流: https://trac.pjsip.org/repos/wiki/media-flow
有问题,看这里: https://trac.pjsip.org/repos/wiki/FAQ
注意pjsip源代码里面由两个audiodev.c文件, 但路径不同,如下:
pjmedia/src/pjmedia/audiodev.c -----提供对声音设备本身的操作封装,重点在对具体设备的操作或管理
pjmedia/src/pjmedia-audiodev/audiodev.c -----提供与声音设备子系统初始化和设备工厂的注册管理操作
一开始没注意, 看代码看糊涂了, 设备子系统工厂管理操作包括:
pjmedia_aud_register_factory
pjmedia_aud_unregister_factory
pjmedia_aud_subsys_init
pjmedia_aud_subsys_shutdown
pisip在pjmedia_aud_subsys_init()中采取了硬编码的方式进行了一些声音设备的静态注册,pjmedia_aud_register_factory并未在例子代码中被使用
例如alsa设备工厂注册代码:aud_subsys->drv[aud_subsys->drv_cnt].create = &pjmedia_alsa_factory;
然后:pjmedia_aud_driver_init(aud_subsys->drv_cnt) 中使用该create,进一步填充设备信息(设备名,该设备的工厂操作接口初始化)
1) 应用层通过以下函数遍历注册的声音设备,根据设备名,获得设备在系统中的唯一ID
pjmedia_aud_dev_lookup( const char *drv_name, const char *dev_name, pjmedia_aud_dev_index *id)
2) 应用层通过调用工厂接口创建/销毁一个设备实例, 这些函数是对pjmedia_aud_dev_factory_op接口的封装
pjmedia_aud_stream_create
pjmedia_aud_stream_destory
3) 应用层通过以下函数调用设备操作接口,进行设备控制(启动、停止、配置读写), 这些函数是对pjmedia_aud_stream_op接口调用的封装
pjmedia_aud_stream_start
pjmedia_aud_stream_stop
pjmedia_aud_stream_get_cap
pjmedia_aud_stream_set_cap
pjmedia_aud_stream_get_cap
***pjsip库提供了一个auddemo.c程序, 演示了使用pjmedia_aud_subsys_init注册所有声音设备后,使用控制台命令对声卡进行操作的过程
***下面讲述以下alsa声卡的alsa_stream设备实现
1) pjmedia_aud_stream_create()函数调用工厂接口函数create_stream创建一个alsa_stream设备实例
接口函数原型: pj_status_t (*create_stream)(pjmedia_aud_dev_factory *f,
const pjmedia_aud_param *param,
pjmedia_aud_rec_cb rec_cb,
pjmedia_aud_play_cb play_cb,
void *user_data,
pjmedia_aud_stream **p_aud_strm);
2) alsa声卡设备会为PCM回放和抓取功能各自建立一个线程,线程函数入口参数就是驱动对应的stream(例如alsa_stream)
3) 应用层在调用pjmedia_aud_stream_create()建立alsa_stream时指定回调函数, user_data作为回调参数,通常会指向一个pjmedia_port实例,表明PCM数据的来源或去向
4) 抓取线程中通过ca_cb回调(工厂接口函数create_stream提供),把抓取的pcm帧数据提供个app层 ==》对于MT7628来说,每次读取固定帧长的数据, 大小是个常数
5) 回访线程中通过pb_cb回调(工厂接口函数create_stream提供),从app层获得满足声卡设置的pcm帧数据 ==》对于MT7628来说,每次写入固定帧长的数据, 大小是个常数
****所以,对于MT7628板子来说,刚好会遇到下面提到的问题:
Potential Problem:
Ideally, rec_cb() and play_cb() should be called one after another, interchangeably, by the sound device. But unfortunately this is not always the case; in many low-end sound cards, it is quite common to have several consecutive rec_cb() callbacks called and then followed by several consecutive play_cb() calls. To accomodate this behavior, the internal sound device queue buffer in the conference bridge is made large enough to store eight audio frames, and this is controlled by RX_BUF_COUNT macro in conference.c. It is possible that a very very bad sound device may issue more than eight consecutive rec_cb()/play_cb() calls, which in this case it would be necessary to enlarge the RX_BUF_COUNT number.
*** 以下是pjsip中对alsa声卡的封装, 根据pjsip声卡抽象层的要求,
struct alsa_stream
{
pjmedia_aud_stream base;=========》struct pjmedia_aud_stream{
struct {
/** Driver index */
unsigned drv_idx; //该设备在所有声音设备数组列表中的数组索引
} sys;
pjmedia_aud_stream_op *op;========》aud_stream操作接口函数族
};
/* Common */
pj_pool_t *pool;
struct alsa_factory *af;======>aud_stream工厂对象( struct alsa_factory)
{
pjmedia_aud_dev_factory base; =======》struct pjmedia_aud_dev_factory
{
/** Internal data to be initialized by audio subsystem. */
struct {
/** Driver index */
unsigned drv_idx;
} sys;
/** Operations */
pjmedia_aud_dev_factory_op *op; ==========>aud_stream工厂操作函数族
};
pj_pool_factory *pf;
pj_pool_t *pool;
pj_pool_t *base_pool;
unsigned dev_cnt;
pjmedia_aud_dev_info devs[MAX_DEVICES];
char pb_mixer_name[MAX_MIX_NAME_LEN];
};
void *user_data; //给回调函数pb_cb和ca_cb使用的私有数据,指向一个pjmedia_port实例
pjmedia_aud_param param;
int rec_id;
int quit;
/* Playback 线程*/
snd_pcm_t *pb_pcm;
snd_pcm_uframes_t pb_frames;
pjmedia_aud_play_cb pb_cb;
unsigned pb_buf_size;
char *pb_buf;
pj_thread_t *pb_thread;
/* Capture 线程*/
snd_pcm_t *ca_pcm;
snd_pcm_uframes_t ca_frames;
pjmedia_aud_rec_cb ca_cb;
unsigned ca_buf_size;
char *ca_buf;
pj_thread_t *ca_thread;
};