月度归档:2022年11月

debian bookworm安装配置Kamailio

bookworm上的kamailio很新,5.6.2版本

1. 先安装MariaDB

apt install default-mysql-server

2. 切换到root用户, 执行

#  mysql -u root -p

然后在 mysql 的shell里修改 mysql数据库的root密码

ALTER USER 'root'@'localhost' IDENTIFIED BY 'new_password';

因为配置Kamailio时,要用到mysql 的root用户密码

3. 安装 Kamailio

apt install kamailio kamailio-mysql-modules

4. 配置
配置文件 在 /etc/kamailio/ 目录下,有两个
一个是 /etc/kamailio/kamctlrc, 里面的配置信息,主要供 kamctl 和 kamdbctl 这两个工具命令使用

SIP_DOMAIN=
DBENGINE=MYSQL
DBRWUSER=
DBRWPW=
CHARSET="latin1"
---上面5个必须修改----
DBROUSER=
DBROPW=
DBHOST=
DBPORT=

SIP_DOMAIN可以是域名 或者 IP地址

另外一个是 /etc/kamailio/kamailio.cfg, 是供 kamailio 主程序使用的, 里面有很多 脚本

#!define WITH_MYSQL
#!define WITH_AUTH
#!define WITH_USRLOCDB

#!define DBURL "mysql://kamailio:_NEW_DBRWPW_@localhost/kamailio"

这四个选项必须修改

如果设置了SIP_DOMAIN为域名,但这个域名没有在DNS中解析, 还必须在 kamailio.cfg 设置一个选项

alias=""

5. 初始化数据库和用户

kamdbctl create

这个时候会要求输入mysql的root密码

6. 开启停止服务

systemctl start kamailio
systemctl stop kamailio
systemctl restart kamailio

7. 添加用户

kamctl add userid password
比如
kamctl add  softsimuser  softsimpassword

8. 监听 IPV6地址

listen=udp:10.0.0.10:5060

listen=[proto]:[localip]:[lport] advertise [publicip]:[pport]

如果不设置,只监听本地 ipv4地址
或者使用

auto_bind_ipv6=1

选项

9. 常用管理命令

查看用户
/usr/sbin/kamctl db show subscriber

添加用户
/usr/sbin/kamctl add  softsimuser  softsimpassword

修改用户密码
/usr/sbin/kamctl   passwd  username  passwd

显示在线用户
/usr/sbin/kamctl    online

删除用户
/usr/sbin/kamctl  rm   username


启动Kamalio
/usr/sbin/kamctl  start  

重启Kamalio
/usr/sbin/kamctl  restart 


停止Kamalio
/usr/sbin/kamctl         stop 

STREAM_VOICE_CALL

对 AudioManager.STREAM_VOICE_CALL 音频流类型进行分析

hardware/libhardware_legacy/audio/AudioPolicyManagerBase.cpp
frameworks/base/media/java/android/media/AudioManager.java
frameworks/base/media/java/android/media/AudioService.java
frameworks/base/media/java/android/media/AudioSystem.java

通过音频流的类型获取对应的音频策略

AudioPolicyManagerBase.cpp:

AudioPolicyManagerBase::routing_strategy AudioPolicyManagerBase::getStrategy(AudioSystem::stream_type stream) {
    // stream to strategy mapping
    switch (stream) {
    case AudioSystem::VOICE_CALL:
    case AudioSystem::BLUETOOTH_SCO:
        return STRATEGY_PHONE;
    ...
    }
}

可以, voice call的音频策略是 phone

通过音频策略获取到对应的输出设备

AudioPolicyManagerBase.cpp:

audio_devices_t AudioPolicyManagerBase::getDeviceForStrategy(routing_strategy strategy, bool fromCache) {
    uint32_t device = AUDIO_DEVICE_NONE;
    ... 
    switch (strategy) {
    ... 
    case STRATEGY_PHONE:
        // for phone strategy, we first consider the forced use and then the available devices by order
        // of priority
        switch (mForceUse[AudioSystem::FOR_COMMUNICATION]) { // 判断是否有设置在通讯过程中, 强制使用某种输出设备
        case AudioSystem::FORCE_BT_SCO: // 强制使用蓝牙, 作为输出设备
            if (!isInCall() || strategy != STRATEGY_DTMF) {
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
                if (device) break;
            }
            device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
            if (device) break;
            device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_SCO;
            if (device) break;
            // if SCO device is requested but no SCO device is available, fall back to default case
            // FALL THROUGH

        default:    // FORCE_NONE 没有设置通讯过程中, 强制使用某种输出设备
            // when not in a phone call, phone strategy should route STREAM_VOICE_CALL to A2DP
            if (mHasA2dp && !isInCall() &&
                    (mForceUse[AudioSystem::FOR_MEDIA] != AudioSystem::FORCE_NO_BT_A2DP) &&
                    (getA2dpOutput() != 0) && !mA2dpSuspended) {
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
                if (device) break;
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;
                if (device) break;
            }
            device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_WIRED_HEADPHONE;
            if (device) break;
            device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_WIRED_HEADSET;
            if (device) break;
            if (mPhoneState != AudioSystem::MODE_IN_CALL) {
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_USB_ACCESSORY;
                if (device) break;
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_USB_DEVICE;
                if (device) break;
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET;
                if (device) break;
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_AUX_DIGITAL;
                if (device) break;
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET;
                if (device) break;
            }
			// 没有设置通讯过程中, 音频的输出设备, 则默认使用听筒作为输出设备
            device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_EARPIECE;
            if (device) break;
            device = mDefaultOutputDevice;
            if (device == AUDIO_DEVICE_NONE) {
                ALOGE("getDeviceForStrategy() no device found for STRATEGY_PHONE");
            }
            break;

        case AudioSystem::FORCE_SPEAKER: // 强制使用扬声器, 作为输出设备
            // when not in a phone call, phone strategy should route STREAM_VOICE_CALL to
            // A2DP speaker when forcing to speaker output
            if (mHasA2dp && !isInCall() &&
                    (mForceUse[AudioSystem::FOR_MEDIA] != AudioSystem::FORCE_NO_BT_A2DP) &&
                    (getA2dpOutput() != 0) && !mA2dpSuspended) {
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER;
                if (device) break;
            }
            if (mPhoneState != AudioSystem::MODE_IN_CALL) {
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_USB_ACCESSORY;
                if (device) break;
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_USB_DEVICE;
                if (device) break;
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET;
                if (device) break;
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_AUX_DIGITAL;
                if (device) break;
                device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET;
                if (device) break;
            }
            device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_SPEAKER;
            if (device) break;
            device = mDefaultOutputDevice;
            if (device == AUDIO_DEVICE_NONE) {
                ALOGE("getDeviceForStrategy() no device found for STRATEGY_PHONE, FORCE_SPEAKER");
            }
            break;
        }
    break;
	// 关于上面default位置的问题, 如果每个case选择(包括default)最终都有break语句, 则default的位置不影响执行顺序
	// 当每个case选择(包括default)不一定有break语句时, 如果输入不满足其他选择, 最终执行default. 程序会从default处从上向下执行, 直到遇到break语句停止
	// 此处的default位置并不影响执行顺序
	... // 省略
    }
    return device;
}

speech codec

G.711 (也称PCM), 用脉冲编码调制对音频采样,采样率为8k每秒。它利用一个 64Kbps 未压缩通道传输语音讯号。 起压缩率为1:2, 即把16位数据压缩成8位。
G.711 标准下主要有两种压缩算法。一种是µ-law, 北美和日本。另一种是A-law, 欧洲等其他区域

1972 年,为了将 模拟通信转向数字通信执行制定的话音编码标准

这个在通信原理的本科课程中都有介绍过。G.711采用一种称为分组丢失隐藏(PLC)的技术来减少丢包带来的实际影响。有效的信号带宽在静默期间通过语音活动检测(VAD)这一过程被减小。

延迟 0.125 ms
mos 4.1


G.729
也是ITU-T定义的音频编码算法,frame 只有10ms,G.729点到点的时延为25ms,G.729基于CELP模式,用CS-ACELP(Conjugate-Structure Algebraic Code Excited Linear Prediction)方法以8Kbps的波特率对语音进行编码。

G.729采用的是共轭结构的代数码激励线性预测算法(Conjugate Structure Algebraic Code Excited Linear Prediction,CS-ACELP),这是一种基于CELP编码模型的算法。由于G.729编码器能够实现很高的语音质量(MOS分3.9, 这个语音质量算很不错了)和很低的算法延时,被广泛地应用于数据通信的各个领域。

G.729是对8KHz采样16bit量化的线性PCM语音信号进行编码,压缩后数据速率为8Kbps,具备16:1的高压缩率。

延迟 15ms
MOS 3.9

G.729 CS-ACELP 8 3.92


G.723
G.723是ITU-T在1996年制订成型的一种多媒体语音编解码标准。G.723编码器采用LPC合成-分析法和感觉加权误差最小化原理编码。G.723标准可在6.3kbps和5.3kbps两种码率下工作。对激励信号进行量化时,高速率(6.3kbps)编码器的激励信号采用多脉冲最大似然量化(MP­-MLQ),低速率(5.3kbps)编码器的激励信号采用代数码本激励线性预测(ACELP)。其中,高码率算法(6.3kbps)具有较高的重建语音质量,而低码率算法(5.3kbps)的计算复杂度则较低。与一般的低码率语音编码算法一样,这里的G.723标准采用的线性预测的合成分析法也就是我们通常所说的Analysis-by-Synthesis。

G.723.1是一个双速率的语音编码器.
编码器首先对语音信号进行传统电话带宽的滤波(基于G.712),再对语音信号用传统8000-Hz速率进行抽样(基于G.711),并变换成16 bit线性PCM码作为该编码器的输入;
在解码器中对输出进行逆操作来重构语音信号;高速率编码器使用多脉冲最大似然量化(MP-MLQ),低速率编码器使用代数码激励线性预测(ACELP)方法,编码器和解码器都必须支持此两种速率,并能够在帧间对两种速率进行转换;
此系统同样能够对音乐和其他音频信号进行压缩和解压缩,但它对语音信号来说是最优的;采用了执行不连续传输的静音压缩,这就意味着在静音期间的比特流中加入了人为的噪声。除了预留带宽之外,这种技术使发信机的调制解调器保持连续工作,并且避免了载波信号的时通时断。

G.723.1 MP-MLQ 6.3kbps 3.9 MOS
G.723.1 ACELP 5.3kbps 3.65 MOS


G.726

Adaptive Differential Pulse Code Modulation (ADPCM),最为常用的方式是 32 kbit/s,但由于其只是 G.711速率的一半,所以可将网络的可利用空间增加了一倍。G.726具体规定了一个 64 kbpsA-law 或 µ-law PCM 信号是如何被转化为40, 32, 24或16 kbps 的 ADPCM 通道的。在这些通道中,24和16 kbps 的通道被用于数字电路倍增设备(DCME)中的语音传输,而40 kbps 通道则被用于 DCME 中的数据解调信号(尤其是4800 kbps 或更高的调制解调器)。
G.726 encoder 输入一般都是G.711 encoder的输出:64kbps A-law or u-law.其算法实质就是一个ADPCM, 自适应量化算法。


Speex 2.15 /44
Minimizes bandwidth usage by using variable bit rate. It is one of the most preferred codecs used in many VoIP apps.


SILK/ Opus


AMR


GSM


Codec2是一个开源的 语音编解码器, 最低450 bit/s
https://github.com/drowe67/codec2

Codec2的主要开发者是David Rowe ,他同时也是另一个开源语音项目Speex 的开发者。Codec2项目允许在只占用极少带宽的情况下提供通话服务,其算法是基于1980年代的论文,并避开了未过期的专利。


VoLTE使用两种语音编码标准:AMR(或AMR-NB)、AMR-WB。


VoNR使用 EVS


twinkle支持的 codec

G.711 A-law (64 kbps payload, 8 kHz sampling rate)
G.711 μ-law (64 kbps payload, 8 kHz sampling rate)
GSM (13 kbps payload, 8 kHz sampling rate)
Speex narrow band (15.2 kbps payload, 8 kHz sampling rate)
Speex wide band (28 kbps payload, 16 kHz sampling rate)
Speex ultra wide band (36 kbps payload, 32 kHz sampling rate)
iLBC (13.3 or 15.2 kbps payload, 8 kHz sampling rate)
G.726 (16, 24, 32 or 40 kbps payload, 8 kHz sampling rate)


Linphone 自己实现了一个 G.729 编解码器

Bcg729 is an open source implementation of both an encoder and decoder for the ITU G729 Annex A/B speech codec. This library was developed by Belledonne Communications, the company supporting the Linphone project. It was written from scratch and is NOT a derivative work of the ITU reference source code of any kind.

The library is written in C 99, is fully portable, and can be executed on many platforms, including ARM and x86 processors. libbcg729 supports concurrent channel encoding/decoding for multi-call applications such as conferencing.

opus
speex 32k/16k/8k
pcmu
pcma
gsm
g722
g726 40/32/24/16
aal2-g726 32/24/16


参考资料:
https://www.gl.com/voice-codecs.html

https://en.wikipedia.org/wiki/Category:Speech_codecs

Android电话API

接听电话

mTelecomManager =  getSystemService("telecom");
mTelecomManager.acceptRingingCall();  

静音

mAudioManager =  getSystemService("audio");
mAudioManager.setParameters("incall_music_mute=true");

联发科

耳机插上时
Set_SpeechCall_UL_Mute=1
未插耳机时
Set_SpeechCall_UL_Mute=1;rcv_mux=off

对应地,取消静音则是

Set_SpeechCall_UL_Mute=0
或
Set_SpeechCall_UL_Mute=0;rcv_mux=on

请求音频焦点

.requestAudioFocus()

录音

int buf_len = AudioRecord.getMinBufferSize(16000, 16, 2);
audioRecord = new AudioRecord(3, 16000, 16, 2, buf_len);  //  inputSource=3 (AUDIO_SOURCE_VOICE_DOWNLINK)
audioRecord.startRecording();
short[] buf = new short[buf_len];
audioRecord.read(buf, 0, buf_len);

音频源定义

    AUDIO_SOURCE_VOICE_UPLINK = 2,
    AUDIO_SOURCE_VOICE_DOWNLINK = 3,
    AUDIO_SOURCE_VOICE_CALL = 4,

流媒体类型定义

    AUDIO_STREAM_VOICE_CALL       = 0,
    AUDIO_STREAM_SYSTEM           = 1,
    AUDIO_STREAM_RING             = 2,
    AUDIO_STREAM_MUSIC            = 3,