pjsip 修改输入输出通道
欢迎进入q群761341723,大家一起讨论问题。hpng该网站为我自己网站,一些想法也会发到这里
我们在使用过程中,可能涉及到输入输出通道的切换或者重置,那么 pjsip 就原生提供了相应的接口,下面我们来分析下它的具体实现流程。
前言
开始分析输入输出的时候先普及下基础知识,如果已经了解过的可以不用看此处
如上图显示,audiodev.c 是 pjsip 的设备的抽象层,向上提供统一的调用接口;向下提供了设备需要实现的统一接口,此接口文件位于 pjmedia/include/pjmedia-audiodev/audiodev_impl.h 中,其中内容如下所示:
#ifndef __AUDIODEV_IMP_H__
#define __AUDIODEV_IMP_H__
#include <pjmedia-audiodev/audiodev.h>
/**
* @defgroup s8_audio_device_implementors_api Audio Device Implementors API
* @ingroup audio_device_api
* @brief API for audio device implementors
* @{
*/
/**
* Sound device factory operations.
*/
typedef struct pjmedia_aud_dev_factory_op
{
/**
* Initialize the audio device factory.
*
* @param f The audio device factory.
*/
pj_status_t (*init)(pjmedia_aud_dev_factory *f);
/**
* Close this audio device factory and release all resources back to the
* operating system.
*
* @param f The audio device factory.
*/
pj_status_t (*destroy)(pjmedia_aud_dev_factory *f);
/**
* Get the number of audio devices installed in the system.
*
* @param f The audio device factory.
*/
unsigned (*get_dev_count)(pjmedia_aud_dev_factory *f);
/**
* Get the audio device information and capabilities.
*
* @param f The audio device factory.
* @param index Device index.
* @param info The audio device information structure which will be
* initialized by this function once it returns
* successfully.
*/
pj_status_t (*get_dev_info)(pjmedia_aud_dev_factory *f,
unsigned index,
pjmedia_aud_dev_info *info);
/**
* Initialize the specified audio device parameter with the default
* values for the specified device.
*
* @param f The audio device factory.
* @param index Device index.
* @param param The audio device parameter.
*/
pj_status_t (*default_param)(pjmedia_aud_dev_factory *f,
unsigned index,
pjmedia_aud_param *param);
/**
* Open the audio device and create audio stream. See
* #pjmedia_aud_stream_create()
*/
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);
/**
* Refresh the list of audio devices installed in the system.
*
* @param f The audio device factory.
*/
pj_status_t (*refresh)(pjmedia_aud_dev_factory *f);
} pjmedia_aud_dev_factory_op;
/**
* This structure describes an audio device factory.
*/
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;
};
/**
* Sound stream operations.
*/
typedef struct pjmedia_aud_stream_op
{
/**
* See #pjmedia_aud_stream_get_param()
*/
pj_status_t (*get_param)(pjmedia_aud_stream *strm,
pjmedia_aud_param *param);
/**
* See #pjmedia_aud_stream_get_cap()
*/
pj_status_t (*get_cap)(pjmedia_aud_stream *strm,
pjmedia_aud_dev_cap cap,
void *value);
/**
* See #pjmedia_aud_stream_set_cap()
*/
pj_status_t (*set_cap)(pjmedia_aud_stream *strm,
pjmedia_aud_dev_cap cap,
const void *value);
/**
* See #pjmedia_aud_stream_start()
*/
pj_status_t (*start)(pjmedia_aud_stream *strm);
/**
* See #pjmedia_aud_stream_stop().
*/
pj_status_t (*stop)(pjmedia_aud_stream *strm);
/**
* See #pjmedia_aud_stream_destroy().
*/
pj_status_t (*destroy)(pjmedia_aud_stream *strm);
} pjmedia_aud_stream_op;
/**
* This structure describes the audio device stream.
*/
struct pjmedia_aud_stream
{
/** Internal data to be initialized by audio subsystem */
struct {
/** Driver index */
unsigned drv_idx;
} sys;
/** Operations */
pjmedia_aud_stream_op *op;
};
#endif /* __AUDIODEV_IMP_H__ */
从上面可以知道,一个设备需要实现的接口。相应的我们都知道在 pjsip 中的整体架构中,一般要创建一个设备或者stream什么的都要有一个 factory 用来管理一些公共参数(类似:pjmedia_aud_dev_factory)。
基础知识就铺垫到这里,下面我们来看咋个设备并启动相应设备的吧!
pjsua_set_snd_dev2
pjsua_set_snd_dev2
函数定义位于 pjsip/src/pjsua-lib/pjsua_aud.c 中,其中内容如下所示:
PJ_DEF(pj_status_t) pjsua_set_snd_dev2(const pjsua_snd_dev_param *snd_param)
{
unsigned alt_cr_cnt = 1;
unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
unsigned i;
pj_status_t status = -1;
unsigned orig_snd_dev_mode = pjsua_var.snd_mode;
PJ_ASSERT_RETURN(snd_param, PJ_EINVAL);
PJ_LOG(4,(THIS_FILE, "Set sound device: capture=%d, playback=%d, mode=%d, "
"use_default_settings=%d",
snd_param->capture_dev, snd_param->playback_dev,
snd_param->mode, snd_param->use_default_settings));
pj_log_push_indent();
PJSUA_LOCK();
/* Check if there are no changes in sound device settings */
if (pjsua_var.cap_dev == snd_param->capture_dev &&
pjsua_var.play_dev == snd_param->playback_dev &&
pjsua_var.snd_mode == snd_param->mode)
{
/* If sound device is already opened, just print log and return.
* Also if PJSUA_SND_DEV_NO_IMMEDIATE_OPEN is set.
*/
if (pjsua_var.snd_is_on || (snd_param->mode &
PJSUA_SND_DEV_NO_IMMEDIATE_OPEN))
{
PJ_LOG(4,(THIS_FILE,"No changes in capture and playback devices"));
PJSUA_UNLOCK();
pj_log_pop_indent();
return PJ_SUCCESS;
}
}
/* No sound */
if (snd_param->capture_dev == PJSUA_SND_NO_DEV &&
snd_param->playback_dev == PJSUA_SND_NO_DEV)
{
PJSUA_UNLOCK();
PJ_LOG(4, (THIS_FILE, "No sound device, mode setting is ignored"));
if (!pjsua_var.no_snd)
pjsua_set_no_snd_dev();
pj_log_pop_indent();
return status;
}
/* Null-sound */
if (snd_param->capture_dev == PJSUA_SND_NULL_DEV &&
snd_param->playback_dev == PJSUA_SND_NULL_DEV)
{
PJSUA_UNLOCK();
PJ_LOG(4, (THIS_FILE, "Null sound device, mode setting is ignored"));
status = pjsua_set_null_snd_dev();
pj_log_pop_indent();
return status;
}
pjsua_var.snd_mode = snd_param->mode;
/* Just update the IDs if app does not want to open now and currently
* audio is off.
*/
if (!pjsua_var.snd_is_on &&
(snd_param->mode & PJSUA_SND_DEV_NO_IMMEDIATE_OPEN))
{
pjsua_var.cap_dev = snd_param->capture_dev;
pjsua_var.play_dev = snd_param->playback_dev;
pjsua_var.no_snd = PJ_FALSE;
PJSUA_UNLOCK();
pj_log_pop_indent();
return PJ_SUCCESS;
}
/* Set default clock rate */
alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
if (alt_cr[0] == 0)
alt_cr[0] = pjsua_var.media_cfg.clock_rate;
/* Allow retrying of different clock rate if we're using conference
* bridge (meaning audio format is always PCM), otherwise lock on
* to one clock rate.
*/
if (pjsua_var.is_mswitch) {
alt_cr_cnt = 1;
} else {
alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
}
/* Attempts to open the sound device with different clock rates */
for (i=0; i<alt_cr_cnt; ++i) {
pjmedia_snd_port_param param;
unsigned samples_per_frame;
/* Create the default audio param */
samples_per_frame = alt_cr[i] *
pjsua_var.media_cfg.audio_frame_ptime *
pjsua_var.media_cfg.channel_count / 1000;
pjmedia_snd_port_param_default(¶m);
param.ec_options = pjsua_var.media_cfg.ec_options;
status = create_aud_param(¶m.base, snd_param->capture_dev,
snd_param->playback_dev,
alt_cr[i], pjsua_var.media_cfg.channel_count,
samples_per_frame, 16,
snd_param->use_default_settings);
if (status != PJ_SUCCESS)
goto on_error;
/* Open! */
param.options = 0;
status = open_snd_dev(¶m);
if (status == PJ_SUCCESS)
break;
}
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Unable to open sound device", status);
goto on_error;
}
pjsua_var.no_snd = PJ_FALSE;
pjsua_var.snd_is_on = PJ_TRUE;
PJSUA_UNLOCK();
pj_log_pop_indent();
return PJ_SUCCESS;
on_error:
pjsua_var.snd_mode = orig_snd_dev_mode;
PJSUA_UNLOCK();
pj_log_pop_indent();
return status;
}
- 第一步:先判断设置的设备是否是当前正在使用的,如果是则直接返回
- 第二步:判断是否设置为 no 或者 null 的设备,则分别调用 pjsua_set_no_snd_dev 或 pjsua_set_null_snd_dev
- 第三步:如果不想立即打开设备,则只是更新下 id 即可
- 第四步:创建 create_aud_param ,然后调用 open_snd_dev 打开音频设备。
很显然 open_snd_dev 是关键函数,我们进去看下。
open_snd_dev
open_snd_dev
函数的定义位于 pjsip/src/pjsua-lib/pjsua_aud.c 中,其中内容如下所示:
/* Open sound device with the setting. */
static pj_status_t open_snd_dev(pjmedia_snd_port_param *param)
{
pjmedia_port *conf_port;
pj_status_t status;
pj_bool_t speaker_only = (pjsua_var.snd_mode & PJSUA_SND_DEV_SPEAKER_ONLY);
PJ_ASSERT_RETURN(param, PJ_EINVAL);
/* Check if NULL sound device is used */
if (PJSUA_SND_NULL_DEV==param->base.rec_id ||
PJSUA_SND_NULL_DEV==param->base.play_id)
{
return pjsua_set_null_snd_dev();
}
/* Close existing sound port */
close_snd_dev();
/* Save the device IDs */
pjsua_var.cap_dev = param->base.rec_id;
pjsua_var.play_dev = param->base.play_id;
/* Notify app */
if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
(*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
}
/* Create memory pool for sound device. */
pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
/* Setup preview callbacks, if configured */
if (pjsua_var.media_cfg.on_aud_prev_play_frame)
param->on_play_frame = &on_aud_prev_play_frame;
if (pjsua_var.media_cfg.on_aud_prev_rec_frame)
param->on_rec_frame = &on_aud_prev_rec_frame;
PJ_LOG(4,(THIS_FILE, "Opening sound device (%s) %s@%d/%d/%dms",
speaker_only?"speaker only":"speaker + mic",
get_fmt_name(param->base.ext_fmt.id),
param->base.clock_rate, param->base.channel_count,
param->base.samples_per_frame / param->base.channel_count *
1000 / param->base.clock_rate));
pj_log_push_indent();
if (speaker_only) {
pjmedia_snd_port_param cp_param;
int dev_id = param->base.play_id;
/* Normalize dev_id */
if (dev_id < 0)
dev_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
pjmedia_snd_port_param_default(&cp_param);
pj_memcpy(&cp_param.base, ¶m->base, sizeof(cp_param.base));
cp_param.base.dir = PJMEDIA_DIR_PLAYBACK;
cp_param.base.play_id = dev_id;
status = pjmedia_snd_port_create2(pjsua_var.snd_pool, &cp_param,
&pjsua_var.snd_port);
} else {
status = pjmedia_snd_port_create2(pjsua_var.snd_pool,
param, &pjsua_var.snd_port);
}
if (status != PJ_SUCCESS)
goto on_error;
/* Get the port0 of the conference bridge. */
conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
pj_assert(conf_port != NULL);
/* For conference bridge, resample if necessary if the bridge's
* clock rate is different than the sound device's clock rate.
*/
if (!pjsua_var.is_mswitch &&
param->base.ext_fmt.id == PJMEDIA_FORMAT_PCM &&
PJMEDIA_PIA_SRATE(&conf_port->info) != param->base.clock_rate)
{
pjmedia_port *resample_port;
unsigned resample_opt = 0;
if (pjsua_var.media_cfg.quality >= 3 &&
pjsua_var.media_cfg.quality <= 4)
{
resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
}
else if (pjsua_var.media_cfg.quality < 3) {
resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
}
status = pjmedia_resample_port_create(pjsua_var.snd_pool,
conf_port,
param->base.clock_rate,
resample_opt,
&resample_port);
if (status != PJ_SUCCESS) {
char errmsg[PJ_ERR_MSG_SIZE];
pj_strerror(status, errmsg, sizeof(errmsg));
PJ_LOG(4, (THIS_FILE,
"Error creating resample port: %s",
errmsg));
close_snd_dev();
goto on_error;
}
conf_port = resample_port;
}
/* Otherwise for audio switchboard, the switch's port0 setting is
* derived from the sound device setting, so update the setting.
*/
if (pjsua_var.is_mswitch) {
if (param->base.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) {
conf_port->info.fmt = param->base.ext_fmt;
} else {
unsigned bps, ptime_usec;
bps = param->base.clock_rate * param->base.bits_per_sample;
ptime_usec = param->base.samples_per_frame /
param->base.channel_count * 1000000 /
param->base.clock_rate;
pjmedia_format_init_audio(&conf_port->info.fmt,
PJMEDIA_FORMAT_PCM,
param->base.clock_rate,
param->base.channel_count,
param->base.bits_per_sample,
ptime_usec,
bps, bps);
}
}
/* Connect sound port to the bridge */
status = pjmedia_snd_port_connect(pjsua_var.snd_port,
conf_port );
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Unable to connect conference port to "
"sound device", status);
pjmedia_snd_port_destroy(pjsua_var.snd_port);
pjsua_var.snd_port = NULL;
goto on_error;
}
/* Update sound device name. */
if (!speaker_only) {
pjmedia_aud_dev_info rec_info;
pjmedia_aud_stream *strm;
pjmedia_aud_param si;
pj_str_t tmp;
strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
status = pjmedia_aud_stream_get_param(strm, &si);
if (status == PJ_SUCCESS)
status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
if (status==PJ_SUCCESS) {
if (param->base.clock_rate != pjsua_var.media_cfg.clock_rate) {
char tmp_buf[128];
int tmp_buf_len;
tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf),
"%s (%dKHz)",
rec_info.name,
param->base.clock_rate/1000);
if (tmp_buf_len < 1 || tmp_buf_len >= (int)sizeof(tmp_buf))
tmp_buf_len = sizeof(tmp_buf) - 1;
pj_strset(&tmp, tmp_buf, tmp_buf_len);
pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
} else {
pjmedia_conf_set_port0_name(pjsua_var.mconf,
pj_cstr(&tmp, rec_info.name));
}
}
/* Any error is not major, let it through */
status = PJ_SUCCESS;
}
/* If this is the first time the audio device is open, retrieve some
* settings from the device (such as volume settings) so that the
* pjsua_snd_get_setting() work.
*/
if (pjsua_var.aud_open_cnt == 0) {
update_initial_aud_param();
++pjsua_var.aud_open_cnt;
}
pjsua_var.snd_is_on = PJ_TRUE;
/* Subscribe to audio device events */
pjmedia_event_subscribe(NULL, &on_media_event, NULL,
pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port));
pj_log_pop_indent();
return PJ_SUCCESS;
on_error:
pj_log_pop_indent();
return status;
}
- close_snd_dev: 关闭已经存在的音频设备
- on_aud_prev_play_frame 与 on_aud_prev_rec_frame: 抓取播放前与录音后的语音流信息
- pjmedia_snd_port_create2: 创建对应的音频的 player 或 recorder
- pjmedia_conf_get_master_port: 获取 master 的会议桥
- pjmedia_resample_port_create: 如果没有启动 switchboard 则需要启动重采样的逻辑。
- pjmedia_snd_port_connect:连接音频到会议桥
由于此处我们仅仅关系怎么修改音频设备的,因而此处就详细解释音频会议桥了,后续再仔细分析。
pjmedia_snd_port_create2
pjmedia_snd_port_create2
的函数定义位于 pjmedia/src/pjmedia/sound_port.c 中,其中内容如下所示:
/*
* Create sound port.
*/
PJ_DEF(pj_status_t) pjmedia_snd_port_create2(pj_pool_t *pool,
const pjmedia_snd_port_param *prm,
pjmedia_snd_port **p_port)
{
pjmedia_snd_port *snd_port;
pj_status_t status;
unsigned ptime_usec;
PJ_ASSERT_RETURN(pool && prm && p_port, PJ_EINVAL);
snd_port = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_port);
PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM);
snd_port->dir = prm->base.dir;
snd_port->rec_id = prm->base.rec_id;
snd_port->play_id = prm->base.play_id;
snd_port->clock_rate = prm->base.clock_rate;
snd_port->channel_count = prm->base.channel_count;
snd_port->samples_per_frame = prm->base.samples_per_frame;
snd_port->bits_per_sample = prm->base.bits_per_sample;
pj_memcpy(&snd_port->aud_param, &prm->base, sizeof(snd_port->aud_param));
snd_port->options = prm->options;
snd_port->prm_ec_options = prm->ec_options;
snd_port->user_data = prm->user_data;
snd_port->on_play_frame = prm->on_play_frame;
snd_port->on_rec_frame = prm->on_rec_frame;
ptime_usec = prm->base.samples_per_frame * 1000 / prm->base.channel_count /
prm->base.clock_rate * 1000;
pjmedia_clock_src_init(&snd_port->cap_clocksrc, PJMEDIA_TYPE_AUDIO,
snd_port->clock_rate, ptime_usec);
pjmedia_clock_src_init(&snd_port->play_clocksrc, PJMEDIA_TYPE_AUDIO,
snd_port->clock_rate, ptime_usec);
/* Start sound device immediately.
* If there's no port connected, the sound callback will return
* empty signal.
*/
status = start_sound_device( pool, snd_port );
if (status != PJ_SUCCESS) {
pjmedia_snd_port_destroy(snd_port);
return status;
}
*p_port = snd_port;
return PJ_SUCCESS;
}
根据传入的参数初始化 pjmedia_snd_port,然后调用了 start_sound_device,那么我们继续往下看。
start_sound_device
start_sound_device
函数定义位于 pjmedia/src/pjmedia/sound_port.c 中,其中内容如下所示:
/*
* Start the sound stream.
* This may be called even when the sound stream has already been started.
*/
static pj_status_t start_sound_device( pj_pool_t *pool,
pjmedia_snd_port *snd_port )
{
pjmedia_aud_rec_cb snd_rec_cb;
pjmedia_aud_play_cb snd_play_cb;
pjmedia_aud_param param_copy;
pj_status_t status;
/* Check if sound has been started. */
if (snd_port->aud_stream != NULL)
return PJ_SUCCESS;
PJ_ASSERT_RETURN(snd_port->dir == PJMEDIA_DIR_CAPTURE ||
snd_port->dir == PJMEDIA_DIR_PLAYBACK ||
snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK,
PJ_EBUG);
/* Get device caps */
if ((snd_port->aud_param.dir & PJMEDIA_DIR_CAPTURE) ||
(snd_port->aud_param.dir & PJMEDIA_DIR_PLAYBACK))
{
pjmedia_aud_dev_info dev_info;
pjmedia_aud_dev_index dev_id =
(snd_port->aud_param.dir & PJMEDIA_DIR_CAPTURE) ?
snd_port->aud_param.rec_id :
snd_port->aud_param.play_id;
status = pjmedia_aud_dev_get_info(dev_id, &dev_info);
if (status != PJ_SUCCESS)
return status;
snd_port->aud_caps = dev_info.caps;
}
/* Process EC settings */
pj_memcpy(¶m_copy, &snd_port->aud_param, sizeof(param_copy));
if (param_copy.flags & PJMEDIA_AUD_DEV_CAP_EC) {
/* EC is wanted */
if ((snd_port->prm_ec_options & PJMEDIA_ECHO_USE_SW_ECHO) == 0 &&
(snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC))
{
/* Device supports EC */
/* Nothing to do */
} else {
/* Application wants to use software EC or device
* doesn't support EC, remove EC settings from
* device parameters
*/
param_copy.flags &= ~(PJMEDIA_AUD_DEV_CAP_EC |
PJMEDIA_AUD_DEV_CAP_EC_TAIL);
}
}
/* Use different callback if format is not PCM */
if (snd_port->aud_param.ext_fmt.id == PJMEDIA_FORMAT_L16) {
snd_rec_cb = &rec_cb;
snd_play_cb = &play_cb;
} else {
snd_rec_cb = &rec_cb_ext;
snd_play_cb = &play_cb_ext;
}
/* Open the device */
status = pjmedia_aud_stream_create(¶m_copy,
snd_rec_cb,
snd_play_cb,
snd_port,
&snd_port->aud_stream);
if (status != PJ_SUCCESS)
return status;
/* Inactivity limit before EC is suspended. */
snd_port->ec_suspend_limit = AEC_SUSPEND_LIMIT *
(snd_port->clock_rate /
snd_port->samples_per_frame);
/* Create software EC if parameter specifies EC and
* (app specifically requests software EC or device
* doesn't support EC). Only do this if the format is PCM!
*/
if ((snd_port->aud_param.flags & PJMEDIA_AUD_DEV_CAP_EC) &&
((snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC)==0 ||
(snd_port->prm_ec_options & PJMEDIA_ECHO_USE_SW_ECHO) != 0) &&
param_copy.ext_fmt.id == PJMEDIA_FORMAT_PCM)
{
if ((snd_port->aud_param.flags & PJMEDIA_AUD_DEV_CAP_EC_TAIL)==0) {
snd_port->aud_param.flags |= PJMEDIA_AUD_DEV_CAP_EC_TAIL;
snd_port->aud_param.ec_tail_ms = AEC_TAIL;
PJ_LOG(4,(THIS_FILE, "AEC tail is set to default %u ms",
snd_port->aud_param.ec_tail_ms));
}
status = pjmedia_snd_port_set_ec(snd_port, pool,
snd_port->aud_param.ec_tail_ms,
snd_port->prm_ec_options);
if (status != PJ_SUCCESS) {
pjmedia_aud_stream_destroy(snd_port->aud_stream);
snd_port->aud_stream = NULL;
return status;
}
}
/* Start sound stream. */
if (!(snd_port->options & PJMEDIA_SND_PORT_NO_AUTO_START)) {
status = pjmedia_aud_stream_start(snd_port->aud_stream);
}
if (status != PJ_SUCCESS) {
pjmedia_aud_stream_destroy(snd_port->aud_stream);
snd_port->aud_stream = NULL;
return status;
}
return PJ_SUCCESS;
}
- pjmedia_aud_stream_create: 创建媒体流,本质就是创建对应的语音设备
- pjmedia_aud_stream_start: 启动相应的语音设备
pjmedia_aud_stream_create
pjmedia_aud_stream_create
的函数定义位于 pjmedia/src/pjmedia/audiodev.c 中,其中内容如下所示:
PJ_DEF(pj_status_t) pjmedia_aud_stream_create(const pjmedia_aud_param *prm,
pjmedia_aud_rec_cb rec_cb,
pjmedia_aud_play_cb play_cb,
void *user_data,
pjmedia_aud_stream **p_aud_strm)
{
pjmedia_aud_dev_factory *rec_f=NULL, *play_f=NULL, *f=NULL;
pjmedia_aud_param param;
pj_status_t status;
PJ_ASSERT_RETURN(prm && prm->dir && p_aud_strm, PJ_EINVAL);
PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT);
PJ_ASSERT_RETURN(prm->dir==PJMEDIA_DIR_CAPTURE ||
prm->dir==PJMEDIA_DIR_PLAYBACK ||
prm->dir==PJMEDIA_DIR_CAPTURE_PLAYBACK,
PJ_EINVAL);
/* Must make copy of param because we're changing device ID */
pj_memcpy(¶m, prm, sizeof(param));
/* Normalize rec_id */
if (param.dir & PJMEDIA_DIR_CAPTURE) {
unsigned index;
if (param.rec_id < 0)
param.rec_id = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV;
status = lookup_dev(param.rec_id, &rec_f, &index);
if (status != PJ_SUCCESS)
return status;
param.rec_id = index;
f = rec_f;
}
/* Normalize play_id */
if (param.dir & PJMEDIA_DIR_PLAYBACK) {
unsigned index;
if (param.play_id < 0)
param.play_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
status = lookup_dev(param.play_id, &play_f, &index);
if (status != PJ_SUCCESS)
return status;
param.play_id = index;
f = play_f;
}
PJ_ASSERT_RETURN(f != NULL, PJ_EBUG);
/* For now, rec_id and play_id must belong to the same factory */
PJ_ASSERT_RETURN((param.dir != PJMEDIA_DIR_CAPTURE_PLAYBACK) ||
(rec_f == play_f),
PJMEDIA_EAUD_INVDEV);
/* Create the stream */
status = f->op->create_stream(f, ¶m, rec_cb, play_cb,
user_data, p_aud_strm);
if (status != PJ_SUCCESS)
return status;
/* Assign factory id to the stream */
(*p_aud_strm)->sys.drv_idx = f->sys.drv_idx;
return PJ_SUCCESS;
}
最终我们到了 audio device 的抽象层,下面就是具体调用相应实现的地方。那么我们怎么知道它调用的那个呢?你用的啥平台就调用啥子就行(根废话一样),这里由于我需要看 android, 我们直接看 android_jni_dev.c 中函数即可。
android_create_stream
android_create_stream
函数位于 pjmedia/src/pjmedia-audiodev/android-jni_dev.c 中,其内容如下所示:
static pj_status_t android_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)
{
struct android_aud_factory *pa = (struct android_aud_factory*)f;
pj_pool_t *pool;
struct android_aud_stream *stream;
pj_status_t status = PJ_SUCCESS;
int state = 0;
int buffSize, inputBuffSizePlay = 0, inputBuffSizeRec = 0;
int channelInCfg, channelOutCfg, sampleFormat;
jmethodID constructor_method=0, bufsize_method = 0;
jmethodID method_id = 0;
jclass jcl;
JNIEnv *jni_env = 0;
pj_bool_t attached;
PJ_ASSERT_RETURN(param->channel_count >= 1 && param->channel_count <= 2,
PJ_EINVAL);
PJ_ASSERT_RETURN(param->bits_per_sample==8 || param->bits_per_sample==16,
PJ_EINVAL);
PJ_ASSERT_RETURN(play_cb && rec_cb && p_aud_strm, PJ_EINVAL);
pool = pj_pool_create(pa->pf, "jnistrm", 1024, 1024, NULL);
if (!pool)
return PJ_ENOMEM;
PJ_LOG(4, (THIS_FILE, "Creating Android JNI stream"));
stream = PJ_POOL_ZALLOC_T(pool, struct android_aud_stream);
stream->pool = pool;
pj_strdup2_with_null(pool, &stream->name, "JNI stream");
stream->dir = param->dir;
pj_memcpy(&stream->param, param, sizeof(*param));
stream->user_data = user_data;
stream->rec_cb = rec_cb;
stream->play_cb = play_cb;
buffSize = stream->param.samples_per_frame*stream->param.bits_per_sample/8;
stream->rec_buf_size = stream->play_buf_size = buffSize;
channelInCfg = (param->channel_count == 1)? 16 /*CHANNEL_IN_MONO*/:
12 /*CHANNEL_IN_STEREO*/;
channelOutCfg = (param->channel_count == 1)? 4 /*CHANNEL_OUT_MONO*/:
12 /*CHANNEL_OUT_STEREO*/;
sampleFormat = (param->bits_per_sample == 8)? 3 /*ENCODING_PCM_8BIT*/:
2 /*ENCODING_PCM_16BIT*/;
attached = attach_jvm(&jni_env);
if (stream->dir & PJMEDIA_DIR_CAPTURE) {
/* Find audio record class and create global ref */
jcl = (*jni_env)->FindClass(jni_env, "android/media/AudioRecord");
if (jcl == NULL) {
PJ_LOG(3, (THIS_FILE, "Unable to find audio record class"));
status = PJMEDIA_EAUD_SYSERR;
goto on_error;
}
stream->record_class = (jclass)(*jni_env)->NewGlobalRef(jni_env, jcl);
(*jni_env)->DeleteLocalRef(jni_env, jcl);
if (stream->record_class == 0) {
status = PJ_ENOMEM;
goto on_error;
}
/* Get the min buffer size function */
bufsize_method = (*jni_env)->GetStaticMethodID(jni_env,
stream->record_class,
"getMinBufferSize",
"(III)I");
if (bufsize_method == 0) {
PJ_LOG(3, (THIS_FILE, "Unable to find audio record "
"getMinBufferSize() method"));
status = PJMEDIA_EAUD_SYSERR;
goto on_error;
}
inputBuffSizeRec = (*jni_env)->CallStaticIntMethod(jni_env,
stream->record_class,
bufsize_method,
param->clock_rate,
channelInCfg,
sampleFormat);
if (inputBuffSizeRec <= 0) {
PJ_LOG(3, (THIS_FILE, "Unsupported audio record params"));
status = PJMEDIA_EAUD_INIT;
goto on_error;
}
}
if (stream->dir & PJMEDIA_DIR_PLAYBACK) {
/* Find audio track class and create global ref */
jcl = (*jni_env)->FindClass(jni_env, "android/media/AudioTrack");
if (jcl == NULL) {
PJ_LOG(3, (THIS_FILE, "Unable to find audio track class"));
status = PJMEDIA_EAUD_SYSERR;
goto on_error;
}
stream->track_class = (jclass)(*jni_env)->NewGlobalRef(jni_env, jcl);
(*jni_env)->DeleteLocalRef(jni_env, jcl);
if (stream->track_class == 0) {
status = PJ_ENOMEM;
goto on_error;
}
/* Get the min buffer size function */
bufsize_method = (*jni_env)->GetStaticMethodID(jni_env,
stream->track_class,
"getMinBufferSize",
"(III)I");
if (bufsize_method == 0) {
PJ_LOG(3, (THIS_FILE, "Unable to find audio track "
"getMinBufferSize() method"));
status = PJMEDIA_EAUD_SYSERR;
goto on_error;
}
inputBuffSizePlay = (*jni_env)->CallStaticIntMethod(jni_env,
stream->track_class,
bufsize_method,
param->clock_rate,
channelOutCfg,
sampleFormat);
if (inputBuffSizePlay <= 0) {
PJ_LOG(3, (THIS_FILE, "Unsupported audio track params"));
status = PJMEDIA_EAUD_INIT;
goto on_error;
}
}
if (stream->dir & PJMEDIA_DIR_CAPTURE) {
jthrowable exc;
jobject record_obj;
int mic_source = 0; /* DEFAULT: default audio source */
if ((param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_SOURCE) &&
(param->input_route & PJMEDIA_AUD_DEV_ROUTE_CUSTOM))
{
mic_source = param->input_route & ~PJMEDIA_AUD_DEV_ROUTE_CUSTOM;
}
/* Get pointer to the constructor */
constructor_method = (*jni_env)->GetMethodID(jni_env,
stream->record_class,
"<init>", "(IIIII)V");
if (constructor_method == 0) {
PJ_LOG(3, (THIS_FILE, "Unable to find audio record's constructor"));
status = PJMEDIA_EAUD_SYSERR;
goto on_error;
}
if (mic_source == 0) {
/* Android-L (android-21) removes __system_property_get
* from the NDK.
*/
/*
char sdk_version[PROP_VALUE_MAX];
pj_str_t pj_sdk_version;
int sdk_v;
__system_property_get("ro.build.version.sdk", sdk_version);
pj_sdk_version = pj_str(sdk_version);
sdk_v = pj_strtoul(&pj_sdk_version);
if (sdk_v > 10)
*/
mic_source = 7; /* VOICE_COMMUNICATION */
}
PJ_LOG(4, (THIS_FILE, "Using audio input source : %d", mic_source));
do {
record_obj = (*jni_env)->NewObject(jni_env,
stream->record_class,
constructor_method,
mic_source,
param->clock_rate,
channelInCfg,
sampleFormat,
inputBuffSizeRec);
if (record_obj == 0) {
PJ_LOG(3, (THIS_FILE, "Unable to create audio record object"));
status = PJMEDIA_EAUD_INIT;
goto on_error;
}
exc = (*jni_env)->ExceptionOccurred(jni_env);
if (exc) {
(*jni_env)->ExceptionDescribe(jni_env);
(*jni_env)->ExceptionClear(jni_env);
PJ_LOG(3, (THIS_FILE, "Failure in audio record's constructor"));
if (mic_source == 0) {
status = PJMEDIA_EAUD_INIT;
goto on_error;
}
mic_source = 0;
PJ_LOG(4, (THIS_FILE, "Trying the default audio source."));
continue;
}
/* Check state */
method_id = (*jni_env)->GetMethodID(jni_env, stream->record_class,
"getState", "()I");
if (method_id == 0) {
PJ_LOG(3, (THIS_FILE, "Unable to find audio record getState() "
"method"));
status = PJMEDIA_EAUD_SYSERR;
goto on_error;
}
state = (*jni_env)->CallIntMethod(jni_env, record_obj, method_id);
if (state == 0) { /* STATE_UNINITIALIZED */
PJ_LOG(3, (THIS_FILE, "Failure in initializing audio record."));
if (mic_source == 0) {
status = PJMEDIA_EAUD_INIT;
goto on_error;
}
mic_source = 0;
PJ_LOG(4, (THIS_FILE, "Trying the default audio source."));
}
} while (state == 0);
stream->record = (*jni_env)->NewGlobalRef(jni_env, record_obj);
if (stream->record == 0) {
jmethodID release_method=0;
PJ_LOG(3, (THIS_FILE, "Unable to create audio record global ref."));
release_method = (*jni_env)->GetMethodID(jni_env,
stream->record_class,
"release", "()V");
(*jni_env)->CallVoidMethod(jni_env, record_obj, release_method);
status = PJMEDIA_EAUD_INIT;
goto on_error;
}
status = pj_sem_create(stream->pool, NULL, 0, 1, &stream->rec_sem);
if (status != PJ_SUCCESS)
goto on_error;
status = pj_thread_create(stream->pool, "android_recorder",
AndroidRecorderCallback, stream, 0, 0,
&stream->rec_thread);
if (status != PJ_SUCCESS)
goto on_error;
PJ_LOG(4, (THIS_FILE, "Audio record initialized successfully."));
}
if (stream->dir & PJMEDIA_DIR_PLAYBACK) {
jthrowable exc;
jobject track_obj;
/* Get pointer to the constructor */
constructor_method = (*jni_env)->GetMethodID(jni_env,
stream->track_class,
"<init>", "(IIIIII)V");
if (constructor_method == 0) {
PJ_LOG(3, (THIS_FILE, "Unable to find audio track's constructor."));
status = PJMEDIA_EAUD_SYSERR;
goto on_error;
}
track_obj = (*jni_env)->NewObject(jni_env,
stream->track_class,
constructor_method,
0, /* STREAM_VOICE_CALL */
param->clock_rate,
channelOutCfg,
sampleFormat,
inputBuffSizePlay,
1 /* MODE_STREAM */);
if (track_obj == 0) {
PJ_LOG(3, (THIS_FILE, "Unable to create audio track object."));
status = PJMEDIA_EAUD_INIT;
goto on_error;
}
exc = (*jni_env)->ExceptionOccurred(jni_env);
if (exc) {
(*jni_env)->ExceptionDescribe(jni_env);
(*jni_env)->ExceptionClear(jni_env);
PJ_LOG(3, (THIS_FILE, "Failure in audio track's constructor"));
status = PJMEDIA_EAUD_INIT;
goto on_error;
}
stream->track = (*jni_env)->NewGlobalRef(jni_env, track_obj);
if (stream->track == 0) {
jmethodID release_method=0;
release_method = (*jni_env)->GetMethodID(jni_env,
stream->track_class,
"release", "()V");
(*jni_env)->CallVoidMethod(jni_env, track_obj, release_method);
PJ_LOG(3, (THIS_FILE, "Unable to create audio track's global ref"));
status = PJMEDIA_EAUD_INIT;
goto on_error;
}
/* Check state */
method_id = (*jni_env)->GetMethodID(jni_env, stream->track_class,
"getState", "()I");
if (method_id == 0) {
PJ_LOG(3, (THIS_FILE, "Unable to find audio track getState() "
"method"));
status = PJMEDIA_EAUD_SYSERR;
goto on_error;
}
state = (*jni_env)->CallIntMethod(jni_env, stream->track,
method_id);
if (state == 0) { /* STATE_UNINITIALIZED */
PJ_LOG(3, (THIS_FILE, "Failure in initializing audio track."));
status = PJMEDIA_EAUD_INIT;
goto on_error;
}
status = pj_sem_create(stream->pool, NULL, 0, 1, &stream->play_sem);
if (status != PJ_SUCCESS)
goto on_error;
status = pj_thread_create(stream->pool, "android_track",
AndroidTrackCallback, stream, 0, 0,
&stream->play_thread);
if (status != PJ_SUCCESS)
goto on_error;
PJ_LOG(4, (THIS_FILE, "Audio track initialized successfully."));
}
if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
strm_set_cap(&stream->base, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
¶m->output_vol);
}
/* Done */
stream->base.op = &android_strm_op;
*p_aud_strm = &stream->base;
detach_jvm(attached);
return PJ_SUCCESS;
on_error:
detach_jvm(attached);
strm_destroy(&stream->base);
return status;
}
代码太多了,基本是调用 android 的逻辑用于创建 recorder 和 player,这里就不做详细解释了。