pjsip 修改输入输出通道

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(&param);
        param.ec_options = pjsua_var.media_cfg.ec_options;
        status = create_aud_param(&param.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(&param);
        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, &param->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(&param_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(&param_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(&param, 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, &param, 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,
                     &param->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,这里就不做详细解释了。

  • 24
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值