pjsip音频流传递过程和混音算法

原文来自:https://blog.csdn.net/lincaig/article/details/79633771

对于实现voip,pjsip是一个非常优秀的开源项目。其实现了复杂的sip信令交互和音频的传输建立。

1、媒体流的传递过程

我们来结合代码分析下媒体流的传递。


conference.c模块是用来做音频设备和媒体数据流之间的桥接作用,它与媒体流和音频设备之间的数据传递都是通过pjmedia_port接口来实现的。pjmedia_port定义如下(省略了其他字段):

  1. typedef struct pjmedia_port  
  2. {  
  3.     pj_status_t (*put_frame)(struct pjmedia_port *this_port,   
  4.                  pjmedia_frame *frame);  
  5.   
  6.     pj_status_t (*get_frame)(struct pjmedia_port *this_port,   
  7.                  pjmedia_frame *frame);  
  8. } pjmedia_port;  

媒体stream对象要实现pjmedia_port的方法,作为接口交给conference管理,被动的被conference调用。conference通过get_frame得到stream中解码后的pcm数据,通过put_frame将pcm传递给stream来编码、传输。

conferece内部需要实现一个index为0的port,其对应的pjmedia_port叫master_port。master_port作为与音频设备之间的接口,被动的被sound device调用。音频设备采集的pcm通过put_frame传递给conference,conference接下来传递给所有监听他的音频流。音频设备播放是会通过get_frame从conference获取pcm数据,这些pcm数据是所有被conference监听流mix后的pcm数据。
conference还要充当混音合流的角色。它会将多个输入的stream流的PCM数据混音后,再交给音频设备播放。也能将音频采集的pcm和某路流A混音后,传递个streamB编码发送。


2、音频混音分析

上述提到的master_port需要实现put_frame和get_frame接口。

  1. /* 
  2.  * Recorder (or passive port) callback. 
  3.  */  
  4. static pj_status_t put_frame(pjmedia_port *this_port,   
  5.                  pjmedia_frame *frame)  
  6. {  
  7.     pj_status_t status;  
  8.   
  9.     status = pjmedia_delay_buf_put(port->delay_buf, (pj_int16_t*)frame->buf);  
  10.   
  11.     return status;  
  12. }  

删除我们分析不比较的代码。

我们看到put_frame方法是将数据保存进了一个delay_buf。由1我们知道这个接口是被sound device调用的,但是这里仅仅做了数据的保存,没有将数据发送给监听的stream。这是为何呢?

其实媒体数据的发送过程是在了get_frame里实现的,为何这么做我们在后面分析。


  1. /* 
  2.  * Player callback. 
  3.  */  
  4. static pj_status_t get_frame(pjmedia_port *this_port,   
  5.                  pjmedia_frame *frame)  
  6. {  
  7.     pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata;  
  8.     pjmedia_frame_type speaker_frame_type = PJMEDIA_FRAME_TYPE_NONE;  
  9.     unsigned ci, cj, i, j;  
  10.     pj_int16_t *p_in;  
  11.       
  12.     TRACE_((THIS_FILE, "- clock -"));  
  13.   
  14.     /* Check that correct size is specified. */  
  15.     pj_assert(frame->size == conf->samples_per_frame *  
  16.                  conf->bits_per_sample / 8);  
  17.   
  18.     /* Must lock mutex */  
  19.     pj_mutex_lock(conf->mutex);  
  20.   
  21.     /* Reset port source count. We will only reset port's mix 
  22.      * buffer when we have someone transmitting to it. 
  23.      */  
  24.     for (i=0, ci=0; i<conf->max_ports && ci < conf->port_cnt; ++i) {  
  25.     struct conf_port *conf_port = conf->ports[i];  
  26.   
  27.     /* Skip empty port. */  
  28.     if (!conf_port)  
  29.         continue;  
  30.   
  31.     /* Var "ci" is to count how many ports have been visited so far. */  
  32.     ++ci;  
  33.   
  34.     /* Reset buffer (only necessary if the port has transmitter) and 
  35.      * reset auto adjustment level for mixed signal. 
  36.      */  
  37.     conf_port->mix_adj = NORMAL_LEVEL;  
  38.     if (conf_port->transmitter_cnt) {  
  39.         pj_bzero(conf_port->mix_buf,  
  40.              conf->samples_per_frame*sizeof(conf_port->mix_buf[0]));  
  41.     }  
  42.     }  

上述代码初始化了每个port的合流调整值mix_adj为NORMAL_LEVEL,NORMAL_LEVEL的值为128。当mix_adj值为NORMAL_LEVEL时,合流后的音频数据不做调整。若mix_adj为200,需要对mix_buf的每个采样做处理:

mix_buf[i] = mix_buf[i] * 200 / 128

这里要注意的是,mix_buf保存的不是这个port本身的数据,而是其监听流的数据。

假如有三个流对象streamA、streamB和streamC,若streamA监听了streamB和streamC,那么streamA的transmitter_cnt值为2,streamB和streamC的listener_cnt为1。streamB和streamC的数据会被conference 混合进streamA的mix_buf中,最终通过streamA发送出去。

  1.    /* Get frames from all ports, and "mix" the signal  
  2.     * to mix_buf of all listeners of the port. 
  3.     */  
  4.    for (i=0, ci=0; i < conf->max_ports && ci < conf->port_cnt; ++i) {  
  5. struct conf_port *conf_port = conf->ports[i];  
  6. pj_int32_t level = 0;  
  7.   
  8. /* Skip empty port. */  
  9. if (!conf_port)  
  10.     continue;  
  11.   
  12. /* Var "ci" is to count how many ports have been visited so far. */  
  13. ++ci;  
  14.   
  15. /* Skip if we're not allowed to receive from this port. */  
  16. if (conf_port->rx_setting == PJMEDIA_PORT_DISABLE) {  
  17.     conf_port->rx_level = 0;  
  18.     continue;  
  19. }  
  20.   
  21. /* Also skip if this port doesn't have listeners. */  
  22. if (conf_port->listener_cnt == 0) {  
  23.     conf_port->rx_level = 0;  
  24.     continue;  
  25. }  
  26.   
  27. /* Get frame from this port. 
  28.  * For passive ports, get the frame from the delay_buf. 
  29.  * For other ports, get the frame from the port.  
  30.  */  
  31. if (conf_port->delay_buf != NULL) {  
  32.     pj_status_t status;  
  33.   
  34.     status = pjmedia_delay_buf_get(conf_port->delay_buf,  
  35.               (pj_int16_t*)frame->buf);  
  36.     if (status != PJ_SUCCESS) {  
  37.     conf_port->rx_level = 0;  
  38.     continue;  
  39.     }         
  40.   
  41. else {  
  42.   
  43.     pj_status_t status;  
  44.     pjmedia_frame_type frame_type;  
  45.   
  46.     status = read_port(conf, conf_port, (pj_int16_t*)frame->buf,   
  47.                conf->samples_per_frame, &frame_type);  
  48.       
  49.     if (status != PJ_SUCCESS) {  
  50.     /* bennylp: why do we need this???? 
  51.      * Also see comments on similar issue with write_port(). 
  52.     PJ_LOG(4,(THIS_FILE, "Port %.*s get_frame() returned %d. " 
  53.                  "Port is now disabled", 
  54.                  (int)conf_port->name.slen, 
  55.                  conf_port->name.ptr, 
  56.                  status)); 
  57.     conf_port->rx_setting = PJMEDIA_PORT_DISABLE; 
  58.      */  
  59.     conf_port->rx_level = 0;  
  60.     continue;  
  61.     }  
  62.   
  63.     /* Check that the port is not removed when we call get_frame() */  
  64.     if (conf->ports[i] == NULL) {  
  65.     conf_port->rx_level = 0;  
  66.     continue;  
  67.     }  
  68.       
  69.   
  70.     /* Ignore if we didn't get any frame */  
  71.     if (frame_type != PJMEDIA_FRAME_TYPE_AUDIO) {  
  72.     conf_port->rx_level = 0;  
  73.     continue;  
  74.     }         
  75. }  

遍历所有port,查看其是否被其他port监听,若listener_cnt为0,直接continue,若有,从这个port中读取pcm数据。

这里读取pcm数据有两个方式,一直是从delay_buf,正好就是我们我们在第1节中提到的录音回调,这个port是一个特殊的media_port,叫master_port,index为0,;其他普通的port都是通过read_port调用各stream对象的get_frame得到。

  1. p_in = (pj_int16_t*) frame->buf;  
  2.   
  3. /* Adjust the RX level from this port 
  4.  * and calculate the average level at the same time. 
  5.  */  
  6. if (conf_port->rx_adj_level != NORMAL_LEVEL) {  
  7.     for (j=0; j<conf->samples_per_frame; ++j) {  
  8.     /* For the level adjustment, we need to store the sample to 
  9.      * a temporary 32bit integer value to avoid overflowing the 
  10.      * 16bit sample storage. 
  11.      */  
  12.     pj_int32_t itemp;  
  13.   
  14.     itemp = p_in[j];  
  15.     /*itemp = itemp * adj / NORMAL_LEVEL;*/  
  16.     /* bad code (signed/unsigned badness): 
  17.      *  itemp = (itemp * conf_port->rx_adj_level) >> 7; 
  18.      */  
  19.     itemp *= conf_port->rx_adj_level;  
  20.     itemp >>= 7;  
  21.   
  22.     /* Clip the signal if it's too loud */  
  23.     if (itemp > MAX_LEVEL) itemp = MAX_LEVEL;  
  24.     else if (itemp < MIN_LEVEL) itemp = MIN_LEVEL;  
  25.   
  26.     p_in[j] = (pj_int16_t) itemp;  
  27.     level += (p_in[j]>=0? p_in[j] : -p_in[j]);  
  28.     }  
  29. else {  
  30.     for (j=0; j<conf->samples_per_frame; ++j) {  
  31.     level += (p_in[j]>=0? p_in[j] : -p_in[j]);  
  32.     }  
  33. }  
  34.   
  35. level /= conf->samples_per_frame;  
  36.   
  37. /* Convert level to 8bit complement ulaw */  
  38. level = pjmedia_linear2ulaw(level) ^ 0xff;  
  39.   
  40. /* Put this level to port's last RX level. */  
  41. conf_port->rx_level = level;  

上述代码根据设置的rx_adj_level,调整每个sample的值。根据调整后的sample值的绝对值累加值,计算出平均sample的值level。将level转换成8bit的u律,保存进rx_level。

  1. // Ticket #671: Skipping very low audio signal may cause noise   
  2. // to be generated in the remote end by some hardphones.  
  3. /* Skip processing frame if level is zero */  
  4. //if (level == 0)  
  5. //    continue;  
  6.   
  7. /* Add the signal to all listeners. */  
  8. for (cj=0; cj < conf_port->listener_cnt; ++cj)   
  9. {  
  10.     struct conf_port *listener;  
  11.     pj_int32_t *mix_buf;  
  12.   
  13.     listener = conf->ports[conf_port->listener_slots[cj]];  
  14.   
  15.     /* Skip if this listener doesn't want to receive audio */  
  16.     if (listener->tx_setting != PJMEDIA_PORT_ENABLE)  
  17.     continue;  
  18.   
  19.     mix_buf = listener->mix_buf;  
  20.   
  21.     if (listener->transmitter_cnt > 1) {  
  22.     /* Mixing signals, 
  23.      * and calculate appropriate level adjustment if there is 
  24.      * any overflowed level in the mixed signal. 
  25.      */  
  26.     unsigned k, samples_per_frame = conf->samples_per_frame;  
  27.     pj_int32_t mix_buf_min = 0;  
  28.     pj_int32_t mix_buf_max = 0;  
  29.   
  30.     for (k = 0; k < samples_per_frame; ++k) {  
  31.         mix_buf[k] += p_in[k];  
  32.         if (mix_buf[k] < mix_buf_min)  
  33.         mix_buf_min = mix_buf[k];  
  34.         if (mix_buf[k] > mix_buf_max)  
  35.         mix_buf_max = mix_buf[k];  
  36.     }  
  37.   
  38.     /* Check if normalization adjustment needed. */  
  39.     if (mix_buf_min < MIN_LEVEL || mix_buf_max > MAX_LEVEL) {  
  40.         int tmp_adj;  
  41.   
  42.         if (-mix_buf_min > mix_buf_max)  
  43.         mix_buf_max = -mix_buf_min;  
  44.   
  45.         /* NORMAL_LEVEL * MAX_LEVEL / mix_buf_max; */  
  46.         tmp_adj = (MAX_LEVEL<<7) / mix_buf_max;  
  47.         if (tmp_adj < listener->mix_adj)  
  48.         listener->mix_adj = tmp_adj;  
  49.     }  
  50.     } else {  
  51.     /* Only 1 transmitter: 
  52.      * just copy the samples to the mix buffer 
  53.      * no mixing and level adjustment needed 
  54.      */  
  55.     unsigned k, samples_per_frame = conf->samples_per_frame;  
  56.   
  57.     for (k = 0; k < samples_per_frame; ++k) {  
  58.         mix_buf[k] = p_in[k];  
  59.     }  
  60.     }  
  61. /* loop the listeners of conf port */  
  62.    } /* loop of all conf ports */  

上述代码将此port的pcm数据拷贝进它listener port的mix_buf里。

1、若listener port仅监听一个port,即当前的port,只要将pcm数据简单拷贝进mix_buf里即可;

2、若listener port监听多个port,需将当前port的数据累加到mix_buf,计算累加后的最大值mix_buf_max和最小值mix_buf_min。当MAX(-mix_buf_min, mix_buf_max)大于MAX_LEVEL时,计算tmp_adj值:MAX_LEVEL * 128 / mix_buf_max。更新port->mix_adj为tmp_adj,若tmp_adj变小。

  1.    /* Time for all ports to transmit whetever they have in their 
  2.     * buffer.  
  3.     */  
  4.    for (i=0, ci=0; i<conf->max_ports && ci<conf->port_cnt; ++i) {  
  5. struct conf_port *conf_port = conf->ports[i];  
  6. pjmedia_frame_type frm_type;  
  7. pj_status_t status;  
  8.   
  9. if (!conf_port)  
  10.     continue;  
  11.   
  12. /* Var "ci" is to count how many ports have been visited. */  
  13. ++ci;  
  14.   
  15. status = write_port( conf, conf_port, &frame->timestamp,  
  16.              &frm_type);  
  17. if (status != PJ_SUCCESS) {  
  18.     /* bennylp: why do we need this???? 
  19.        One thing for sure, put_frame()/write_port() may return 
  20.        non-successfull status on Win32 if there's temporary glitch 
  21.        on network interface, so disabling the port here does not 
  22.        sound like a good idea. 
  23.  
  24.     PJ_LOG(4,(THIS_FILE, "Port %.*s put_frame() returned %d. " 
  25.              "Port is now disabled", 
  26.              (int)conf_port->name.slen, 
  27.              conf_port->name.ptr, 
  28.              status)); 
  29.     conf_port->tx_setting = PJMEDIA_PORT_DISABLE; 
  30.     */  
  31.     continue;  
  32. }  
  33.   
  34. /* Set the type of frame to be returned to sound playback 
  35.  * device. 
  36.  */  
  37. if (i == 0)  
  38.     speaker_frame_type = frm_type;  
  39.    }  

遍历所有port,通过write_port往stream里put_frame数据。后面会分析write_port()。

  1.     /* Return sound playback frame. */  
  2.     if (conf->ports[0]->tx_level) {  
  3.     TRACE_((THIS_FILE, "write to audio, count=%d",   
  4.                conf->samples_per_frame));  
  5.     pjmedia_copy_samples( (pj_int16_t*)frame->buf,   
  6.                   (const pj_int16_t*)conf->ports[0]->mix_buf,   
  7.                   conf->samples_per_frame);  
  8.     } else {  
  9.     /* Force frame type NONE */  
  10.     speaker_frame_type = PJMEDIA_FRAME_TYPE_NONE;  
  11.     }  
  12.   
  13.     /* MUST set frame type */  
  14.     frame->type = speaker_frame_type;  
  15.   
  16.     pj_mutex_unlock(conf->mutex);  
  17.   
  18. #ifdef REC_FILE  
  19.     if (fhnd_rec == NULL)  
  20.     fhnd_rec = fopen(REC_FILE, "wb");  
  21.     if (fhnd_rec)  
  22.     fwrite(frame->buf, frame->size, 1, fhnd_rec);  
  23. #endif  
  24.   
  25.     return PJ_SUCCESS;  
  26. }   

数据返回。前面我们知道get_frame方法是被音频设备调用的,conference的index为0的port用来给音频设备提供数据。直接从此port的mix_buf拷贝数据。 


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值