部署asterisk来进行网络通话
Easul Lv6

服务端部署

以下我所列出的Docker镜像部署方式是在 Debian 12 下进行操作的,部署过程中由于镜像下载较缓慢,我直接使用了代理进行进行拉取,没有使用Docker镜像源来进行部署。

快速部署指导

将如下配置中的 对镜像内的相关配置进行设置 命令部分的 test.212490197.xyz 改为 生产环境公网域名或IP,且开放 10000-10009UDP 公网端口,15061TCP+UDP 端口,并将相应公网端口指向该服务,即可通过配置中的默认配置使用 [1001], [1002], [1003], [10086], [10011] 五个号码进行互相打电话的操作。

BASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
#########################################
# 进行docker相关的安装
#
# 安装依赖
sudo apt update
sudo apt install -y ca-certificates curl gnupg lsb-release
# 添加Docker的GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# 添加Docker源
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/debian \
$(. /etc/os-release; echo "$VERSION_CODENAME") stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 安装Docker CE
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# 将普通用户加到docker组
sudo usermod -aG docker $USER
newgrp docker
#########################################
# 进行镜像源拉取
#
# 开启代理后重启docker服务,再拉镜像
sudo systemctl set-environment HTTP_PROXY=socks5://192.168.1.12:23333 HTTPS_PROXY=socks5://192.168.1.12:23333
sudo systemctl restart docker
# 拉完之后使用如下命令关闭代理,并重启docker服务
# sudo systemctl unset-environment HTTP_PROXY HTTPS_PROXY
# sudo systemctl restart docker
# 拉取镜像,这里使用的是 andrius/asterisk:18.26.4_debian-trixie,太新的镜像可能会不能用
docker pull andrius/asterisk:18.26.4_debian-trixie
# 运行镜像
docker run -d --name asterisk --network host --restart always andrius/asterisk:18.26.4_debian-trixie asterisk -fvvv
# 进入镜像内部进行配置
docker exec -it asterisk bash
#########################################
# 对镜像内的相关配置进行设置
# 具体配置解析可看上边的解释
#
cat > /etc/asterisk/rtp.conf <<EOF
[general]
rtpstart=10000
rtpend=10009
rtpchecksums=no
icesupport=yes
EOF
cat > /etc/asterisk/http.conf <<EOF
[general]
enabled=yes
enablestatic=yes
bindaddr=0.0.0.0
bindport=8088
tlsenable=yes
tlsbindaddr=0.0.0.0:8089
tlscertfile=/etc/asterisk/keys/asterisk.pem
tlsprivatekey=/etc/asterisk/keys/asterisk.key
EOF
cat > /etc/asterisk/pjsip.conf <<EOF
[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0:15061
external_media_address=test.212490197.xyz ; 宿主机局域网 IP
external_signaling_address=test.212490197.xyz
local_net=192.168.0.0/16

[transport-tcp]
type=transport
protocol=tcp
bind=0.0.0.0:15061 ; 监听 TCP SIP 端口
external_media_address=test.212490197.xyz
external_signaling_address=test.212490197.xyz
local_net=192.168.0.0/16

[transport-wss]
type=transport
protocol=wss
bind=0.0.0.0:8089
external_media_address=192.168.1.2
external_signaling_address=192.168.1.2

[1001]
type=endpoint
context=default
disallow=all
allow=ulaw
auth=1001
aors=1001
transport=transport-tcp
rtcp_mux=yes
rtp_symmetric=yes
force_rport=yes
direct_media=no
rewrite_contact=yes

[1001]
type=auth
auth_type=userpass
password=123456
username=1001

[1001]
type=aor
max_contacts=1
remove_existing=yes

[1002]
type=endpoint
context=default
disallow=all
allow=ulaw
auth=1002
aors=1002
transport=transport-tcp
rtcp_mux=no
use_avpf=no
rtp_symmetric=yes
force_rport=yes
direct_media=no
rewrite_contact=yes

[1002]
type=auth
auth_type=userpass
password=123456
username=1002

[1002]
type=aor
max_contacts=1
remove_existing=yes

[1003]
type=endpoint
context=default
disallow=all
allow=ulaw
auth=1003
aors=1003
transport=transport-tcp
rtcp_mux=yes
rtp_symmetric=yes
force_rport=yes
direct_media=no
rewrite_contact=yes

[1003]
type=auth
auth_type=userpass
password=123456
username=1003

[1003]
type=aor
max_contacts=1
remove_existing=yes


[10086]
type=endpoint
context=default
disallow=all
allow=ulaw
auth=10086
aors=10086
transport=transport-tcp
rtcp_mux=yes
rtp_symmetric=yes
force_rport=yes
direct_media=no
rewrite_contact=yes

[10086]
type=auth
auth_type=userpass
password=123456
username=10086

[10086]
type=aor
max_contacts=1
remove_existing=yes

[10010]
type=endpoint
context=default
disallow=all
allow=ulaw
auth=10010
aors=10010
transport=transport-wss
media_encryption=dtls
dtls_verify=no
dtls_setup=actpass
dtls_cert_file=/etc/asterisk/keys/all.pem
;dtls_ca_file=/etc/asterisk/keys/ca.crt
use_avpf=yes
rtcp_mux=yes
rtp_symmetric=yes
force_rport=yes
rewrite_contact=yes
ice_support=yes
direct_media=no

[10010]
type=auth
auth_type=userpass
password=123456
username=10010

[10010]
type=aor
max_contacts=1
remove_existing=yes

[10011]
type=endpoint
context=default
disallow=all
allow=ulaw
auth=10011
aors=10011
transport=transport-tcp
rtcp_mux=yes
rtp_symmetric=yes
force_rport=yes
direct_media=no
rewrite_contact=yes

[10011]
type=auth
auth_type=userpass
password=123456
username=10011

[10011]
type=aor
max_contacts=1
remove_existing=yes

[10000]
type=endpoint
context=default
disallow=all
allow=ulaw
auth=10000
aors=10000
transport=transport-wss
media_encryption=dtls
dtls_verify=no
dtls_setup=actpass
dtls_cert_file=/etc/asterisk/keys/all.pem
;dtls_ca_file=/etc/asterisk/keys/ca.crt
use_avpf=yes
rtcp_mux=yes
rtp_symmetric=yes
force_rport=yes
rewrite_contact=yes
ice_support=yes
direct_media=no
asymmetric_rtp_codec=no

[10000]
type=auth
auth_type=userpass
password=123456
username=10000

[10000]
type=aor
max_contacts=1
remove_existing=yes
EOF
cat > /etc/asterisk/extensions.conf <<EOF
[default]
exten => 1001,1,Dial(PJSIP/1001)
exten => 1002,1,Dial(PJSIP/1002)
exten => 1003,1,Dial(PJSIP/1003)
exten => 10086,1,Dial(PJSIP/10086)
exten => 10010,1,Dial(PJSIP/10010)
exten => 10011,1,Dial(PJSIP/10011)
exten => 10000,1,Dial(PJSIP/10000)
EOF
# 如果只是修改了 pjsip.conf ,可以使用该命令重启配置
asterisk -rx "pjsip reload"
# 如果只是修改了 extensions.conf,可以使用该命令重启配置
asterisk -rx "dialplan reload"
# 如果修改了 http.conf 和 rtp.conf,需要退出容器重启服务
docker restart asterisk

配置中的关键点解释

  • rtp.conf 中的 rtpstartrtpend 指定了进行通话时可使用的UDP端口范围(端口需在公网下进行UDP通信开放,否则无法进行通话操作),此处指定了10个,而目前的配置下,观测到一次双向通话会占用4个UDP端口,这意味着在这个配置下,最多保持2个并发的双向通话。
  • http.conf 中,配置为了供前端WebRTC进行通话的端口。由于目前浏览器使用WebRTC无法听到声音(截至2026年1月27日),故该配置目前暂时无用。
  • pjsip.conf 中
    • [transport-udp][transport-tcp][transport-wss] 分别为 UDP、TCP、浏览器与服务端通信时所使用的的信令协议。
    • [transport-udp][transport-tcp]中的
      • bind 为UDP和TCP信令需要暴露到外界的端口,同样需要进行外网端口的开放,TCP和UDP的都开放。
      • external_media_addressexternal_signaling_address 为公网IP或域名,我这里使用了我自己的测试域名,实际情况下需替换为公网IP或域名。
      • local_net 为所在服务器的局域网的 CIDR 的IP格式
    • [transport-wss]中的
      • bind 为WebRTC连接的端口,由于暂不使用,且为内网访问,故这里不需要暴露端口,且改为任意其他未占用端口即可。
      • external_media_addressexternal_signaling_address 为公网IP或域名,这里由于暂不使用,所以我只填写了内网IP,后边改为实际的内网IP即可。
    • 进行一个有效的用户添加
      • 这里 [1001] 相当于一个用户,一个用户分了三个块,按照固定格式填写即可。每个块的[xxxx]需要保持一致
      • 第一个块需要修改的部分为
        • authaors 均和用户号码一致
        • transport是信令传输协议,这里使用了tcp
      • 第二个块需要修改的部分为
        • password 是用户的认证密码
        • username与用户号码保持一致
      • 添加之后,这个用户就相当于是可以使用该服务进行通信了
    • 这里的 [1001], [1002], [1003], [10086], [10011] 为普通客户端的通信配置案例,[10010][10000] 为WebRTC通信配置示例,可根据实际情况进行修改或删减
      BASH
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      [1001]
      type=endpoint
      context=default
      disallow=all
      allow=ulaw
      auth=1001
      aors=1001
      transport=transport-tcp
      rtcp_mux=yes
      rtp_symmetric=yes
      force_rport=yes
      direct_media=no
      rewrite_contact=yes

      [1001]
      type=auth
      auth_type=userpass
      password=123456
      username=1001

      [1001]
      type=aor
      max_contacts=1
      remove_existing=yes

相关客户端下载及使用

这里的客户端下载后,先配置上边部署的服务,然后就可以打电话了。

常用客户端

包括 安卓linux桌面版MacOSWindows 等版本。
官方release链接 下载即可。
下载后,配置 服务端地址用户名密码 即可使用

linux命令行

个人尝试,使用 baresip 效果更好。

linphonec

BASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 进行linphone的安装
# 由于包的依赖比较多,最好是使用包管理器安装,否则编译安装的话,可能会遇到大量无法解决的问题
# 如果硬件架构较老,也会有诸多限制
apt update
apt install -y linphone
# 指定使用的声卡设备
export ALSA_CARD=1
export ALSA_PCM=plughw:1,0
export ALSA_PCM_CAPTURE=plughw:1,0
export ALSA_PCM_PLAYBACK=plughw:1,0
# 初始化linphone的后台进程
linphonecsh init
# 取消linphone的上一次注册信息。
# 在前边尝试的过程中发现,如果不取消注册的话,很可能就无法再次连接到服务端了
linphonecsh generic "unregister"
# 使用盒子分配的账户信息注册到盒子端
linphonecsh generic "register sip:1002@test.212490197.xyz:15061;transport=tcp sip:test.212490197.xyz:15061;transport=tcp 123456"
# 查看盒子的注册状态,没注册上会提示-1
linphonecsh generic "status register"
# 在命令行中选择使用的硬件设备,这里的 2 是选择的USB声卡
# 如果在其他场景下进行测试的话,需要使用 soundcard list 查看具体设备号,输出内容如下所示
# 0: ALSA: default device
# 1: ALSA: audiocodec
# 2: ALSA: KT USB Audio
linphonecsh generic "soundcard playback 2"
linphonecsh generic "soundcard capture 2"
# 呼叫另一个用户,并进行通话操作
linphonecsh generic "call sip:10000@test.212490197.xyz:15061"
# 挂断已接通的对话
linphonecsh generic "terminate"
# 退出linphone的后台进程模式
linphonecsh exit
################################
# 以上是使用纯命令方式
# 交互方式可以直接输入如下命令,然后进行交互操作
linphonec

baresip

BASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Debian/Ubuntu系
sudo apt update
sudo apt install -y baresip
# CentOS系
sudo yum install epel-release -y
sudo yum install baresip -y

# 安装后进行连接
# 设置声卡的号码
export ALSA_CARD=1
export ALSA_PCM=plughw:1,0
export ALSA_PCM_CAPTURE=plughw:1,0
export ALSA_PCM_PLAYBACK=plughw:1,0
# 先生成下配置文件
baresip
# 设置账户
echo "<sip:1002@test.212490197.xyz:15061;transport=tcp>;auth_pass=123456;mediaenc=dtls_srtp" >> ~/.baresip/accounts
# 再次拨打
# 由于baresip主要是用交互式方式来打电话(我这里尝试的版本比较老),所以可以使用tmux进行会话管理
baresip

网页

可使用如下测试代码来处理

HTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<title>Asterisk WebRTC Test</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jssip/3.1.2/jssip.min.js"></script>
<style>
body { font-family: sans-serif; padding: 20px; }
#status { color: gray; font-weight: bold; }
.connected { color: green !important; }
.disconnected { color: red !important; }
</style>
</head>
<body>
<h2>Asterisk WebRTC 控制台</h2>
<div>状态: <span id="status">初始化...</span></div>
<br>
<button onclick="makeCall(8888)">拨打 8888 回声测试</button>
<button onclick="makeCall(1001)">拨打 1001的电脑</button>

<audio id="remoteAudio" autoplay></audio>
<div>远端音量: <span id="audioLevel">0</span></div>

<script>
JsSIP.debug.enable('JsSIP:*');

const socket = new JsSIP.WebSocketInterface('wss://192.168.1.2:8089/ws');
const configuration = {
sockets: [socket],
uri: 'sip:10000@192.168.1.2',
password: '123456',
pcConfig: { rtcpMuxPolicy: 'require', iceServers: [] }
};

const ua = new JsSIP.UA(configuration);
ua.start();

ua.on('registered', () => {
const status = document.getElementById('status');
status.innerText = '注册成功 (10000)';
status.className = 'connected';
});

ua.on('newRTCSession', (data) => {
const session = data.session;
console.log("新会话:", session.direction);

if (session.direction === 'incoming') {
session.answer({ mediaConstraints: { audio: true, video: false } });
}

session.on('peerconnection', (e) => {
const pc = e.peerconnection;

pc.ontrack = (event) => {
console.log("检测到远端 track:", event.streams[0].getAudioTracks());
const remoteAudio = document.getElementById('remoteAudio');
if (!remoteAudio.srcObject || remoteAudio.srcObject !== event.streams[0]) {
remoteAudio.srcObject = event.streams[0];
remoteAudio.volume = 1.0;
remoteAudio.muted = false;
remoteAudio.play().catch(err => console.warn('播放音频错误:', err));

// --- Web Audio API 监听音量 ---
try {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const source = audioCtx.createMediaStreamSource(event.streams[0]);
const analyser = audioCtx.createAnalyser();
analyser.fftSize = 256;
source.connect(analyser);

const dataArray = new Uint8Array(analyser.frequencyBinCount);
function updateAudioLevel() {
analyser.getByteFrequencyData(dataArray);
let sum = 0;
for (let i = 0; i < dataArray.length; i++) sum += dataArray[i];
const avg = sum / dataArray.length / 128; // 归一化到 0~1
document.getElementById('audioLevel').innerText = avg.toFixed(3);
requestAnimationFrame(updateAudioLevel);
}
updateAudioLevel();
} catch (err) {
console.warn('无法创建音量监听:', err);
}
}
};
});

session.on('ended', () => {
console.log("呼叫结束");
const remoteAudio = document.getElementById('remoteAudio');
remoteAudio.srcObject = null;
document.getElementById('audioLevel').innerText = '0';
});

session.on('failed', (e) => console.log("呼叫失败:", e.cause));
});

function makeCall(number) {
ua.call(`sip:${number}@192.168.1.2`, {
mediaConstraints: { audio: true, video: false },
pcConfig: { rtcpMuxPolicy: 'require', iceServers: [] }
});
}
</script>

</body>
</html>

常识

  • 打电话的过程中,传输速度为 64kb/s或32kb/s 就可以保证不卡。

问题记录

websocket的配置

公网没声音

 评论