active-call 0.3.76

A SIP/WebRTC voice agent
Documentation
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
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
# Active Call 配置指南

本文档详细说明 `active-call` 的配置文件(`active-call.toml`)各项参数的含义以及不同呼叫场景的配置方案。

## 目录

- [基础配置]#基础配置
- [网络配置]#网络配置
- [TLS 与 SRTP 配置]#tls-与-srtp-配置
- [媒体配置]#媒体配置
- [呼入处理配置]#呼入处理配置
- [录音与CDR配置]#录音与cdr配置
- [呼叫场景配置]#呼叫场景配置
  - [场景1: WebRTC 呼叫(浏览器通话)]#场景1-webrtc-呼叫浏览器通话
  - [场景2: SIP 呼叫(对接PBX/FS网关)]#场景2-sip-呼叫对接pbxfs网关
  - [场景3: WebSocket 呼叫(自定义应用)]#场景3-websocket-呼叫自定义应用
  - [场景4: SIP 呼入(注册到PBX)]#场景4-sip-呼入注册到pbx

---

## 基础配置

```toml
# SIP 服务监听地址
addr = "0.0.0.0"

# HTTP 服务端口地址
http_addr = "0.0.0.0:8080"

# UDP 端口(用于 SIP 和 RTP,默认 25060)
udp_port = 25060

# 日志级别:trace, debug, info, warn, error
log_level = "debug"

# 日志文件路径(可选)
# log_file = "/tmp/active-call.log"

# 跳过访问日志的 HTTP 路径(可选)
# http_access_skip_paths = ["/health", "/metrics*"]

# 媒体缓存路径
media_cache_path = "./config/mediacache"
```

### 配置项说明

- **addr**: SIP 服务绑定的 IP 地址
- **http_addr**: HTTP 服务完整地址(IP + 端口)
- **udp_port**: UDP 端口,用于 SIP 信令和 RTP 媒体流
- **log_level**: 日志级别,建议生产环境使用 `info``warn`
- **media_cache_path**: 媒体文件(如 TTS 音频)的缓存目录

---

## 网络配置

### NAT 和外部 IP 配置

如果服务器在 NAT 后面,需要配置公网 IP:

```toml
# 外部 IP 地址(用于 SIP 信令和媒体协商)
# 如果服务器在 NAT 后面,请设置公网 IP(不包含端口)
external_ip = "1.2.3.4"
```

### RTP 端口范围

```toml
# RTP 端口范围
rtp_start_port = 12000
rtp_end_port = 42000
```

### STUN/TURN 服务器配置(WebRTC)

用于 WebRTC 客户端的 NAT 穿透:

```toml
# STUN 服务器
[[ice_servers]]
urls = ["stun:stun.l.google.com:19302"]

# TURN 服务器(需要认证)
[[ice_servers]]
urls = ["turn:turn.example.com:3478"]
username = "turnuser"
credential = "turnpass"
```

---

## TLS 与 SRTP 配置

适用于需要加密信令和媒体传输的 PSTN 运营商对接场景(如 Twilio、Telnyx 等)。

### SIP over TLS(SIPS)

```toml
# TLS 端口,用于加密的 SIP 信令(SIPS)
# Twilio 要求使用 TLS;生产环境运营商对接建议开启
tls_port      = 5061
tls_cert_file = "./certs/cert.pem"   # TLS 证书路径(PEM 格式)
tls_key_file  = "./certs/key.pem"    # TLS 私钥路径(PEM 格式)
```

设置 `tls_port` 后,active-call 将在该端口额外启动一个 TLS SIP 侦听器。原有的 `udp_port` UDP 侦听器同时保持运行。

**生成自签名证书**(测试用):

```bash
mkdir -p certs
openssl req -x509 -newkey rsa:2048 -keyout certs/key.pem \
  -out certs/cert.pem -days 365 -nodes \
  -subj "/CN=你的公网IP或域名"
```

生产环境建议使用 CA 签名证书(如 Let's Encrypt)。

### SRTP(加密媒体)

```toml
# 全局开启 SRTP 加密媒体传输
# 开启 tls_port 时推荐同时开启 SRTP
enable_srtp = true
```

也可以在 **HTTP API 调用时按通话覆盖**:

```json
{
  "callee": "sip:+18005551234@yourdomain.pstn.twilio.com",
  "sip": {
    "enableSrtp": true
  }
}
```

**SRTP 优先级链**(由高到低):
1. API 调用 body 中的 `sip.enableSrtp` — 单次通话覆盖
2. `active-call.toml` 中的 `enable_srtp` — 全局默认
3. `false` — 明文 RTP(兜底)

### 配置示例:对接 Twilio 的完整配置

```toml
addr          = "0.0.0.0"
udp_port      = 5060
tls_port      = 5061
tls_cert_file = "./certs/cert.pem"
tls_key_file  = "./certs/key.pem"
enable_srtp   = true
external_ip   = "你的公网IP"
rtp_start_port = 12000
rtp_end_port   = 42000
```

完整的运营商对接流程,请参阅 [Twilio 集成指南](./twilio_integration.md)。

---

## 媒体配置

### ASR/TTS 引擎默认值

但在使用 Playbook 或通过 API 发起呼叫时,如果没有指定提供商,Active Call 将使用以下默认值:

- **中文 (zh)**: TTS 默认使用 **aliyun**, ASR 默认使用 **sensevoice**(离线) 或 **aliyun**(在线)。
- **英文 (en)**: TTS 默认使用 **supertonic**, ASR 默认使用 **sensevoice**(离线) 或 **openai**(在线)。

### SIP 配置

```toml
# SIP 服务监听地址 (对应基础配置中的 addr)
addr = "0.0.0.0"

# SIP 服务端口 (对应基础配置中的 udp_port)
udp_port = 25060
```

**注意**: 建议不使用 5060 端口,因为很多网络环境会对该端口进行特殊处理。

---

## 呼入处理配置

Active Call 支持两种 SIP 呼入处理方式:

### 方式1: Webhook 处理器

将呼入请求转发到 HTTP 接口,由外部服务决定如何处理:

```toml
[handler]
type = "webhook"
url = "http://localhost:8090/webhook"
method = "POST"
```

**Webhook 请求格式**:
```json
{
  "caller": "sip:alice@example.com",
  "callee": "sip:bob@pbx.example.com",
  "call_id": "abc123",
  "from_tag": "tag1",
  "to_tag": "tag2"
}
```

### 方式2: Playbook 处理器

根据来电号码和被叫号码自动路由到对应的 Playbook:

```toml
[handler]
type = "playbook"
default = "default.md"  # 可选:没有匹配规则时使用的默认 Playbook

# 规则1: 美国号码呼叫支持热线
[[handler.rules]]
caller = "^\\+1\\d{10}$"        # 匹配美国号码格式
callee = "^sip:support@.*"      # 匹配支持热线
playbook = "support.md"         # 使用支持 Playbook

# 规则2: 中国号码自动使用中文 Playbook
[[handler.rules]]
caller = "^\\+86\\d+"           # 匹配中国号码
playbook = "chinese.md"         # 只匹配来电号码

# 规则3: 销售热线
[[handler.rules]]
callee = "^sip:sales@.*"        # 只匹配被叫号码
playbook = "sales.md"
```

**规则匹配逻辑**:
- 按从上到下的顺序评估规则
- 每条规则可以指定 `caller` 和/或 `callee` 的正则表达式
- 两个模式都必须匹配(省略的模式匹配任何值)
- 第一条匹配的规则决定使用哪个 Playbook
- 如果没有规则匹配且没有设置 default,则拒绝呼叫

### CLI 快速配置

也可以通过命令行参数快速配置处理器:

```bash
# Webhook 处理器
./active-call --handler https://example.com/webhook

# Playbook 处理器(默认 Playbook)
./active-call --handler default.md

# 发起 SIP 呼出并执行 Playbook
./active-call --call sip:1001@127.0.0.1 --handler greeting.md

# 设置外部 IP 和支持的编码
./active-call --external-ip 1.2.3.4 --codecs pcmu,pcma,opus
```

---

## 录音与 CDR 配置

### 录音配置

```toml
[recording]
enabled = true      # 启用录音
auto_start = true   # 自动开始录音
```

### CDR(呼叫详单)配置

```toml
[callrecord]
type = "local"
root = "./config/cdr"
```

CDR 文件将保存在指定的目录中,包含每次呼叫的详细信息。

---

## 呼叫场景配置

Active Call 支持多种呼叫场景,通过不同的 WebSocket 接口发起呼叫。以下是常见场景的配置和使用方法。

### 场景1: WebRTC 呼叫(浏览器通话)

**使用场景**: 网页应用、浏览器客户端直接与 AI Agent 通话。

> **注意**: WebRTC 需要安全上下文(Secure Context)。请确保通过 **HTTPS****127.0.0.1** 访问您的网页客户端,否则浏览器将无法启用 WebRTC 功能。

#### 配置要点

```toml
# 基础配置
http_addr = "0.0.0.0:8080"

# 配置 STUN/TURN 服务器(用于 NAT 穿透)
[[ice_servers]]
urls = ["stun:stun.l.google.com:19302"]

[[ice_servers]]
urls = ["turn:turn.example.com:3478"]
username = "turnuser"
credential = "turnpass"

# 如果在 NAT 后面,配置外部 IP
external_ip = "1.2.3.4"
```

#### 发起呼叫

通过 WebSocket 连接到 `/call/webrtc` 端点:

```javascript
// 建立 WebSocket 连接
const ws = new WebSocket('ws://localhost:8080/call/webrtc?id=session123');

// 创建 WebRTC PeerConnection
const pc = new RTCPeerConnection({
  iceServers: [
    { urls: 'stun:stun.l.google.com:19302' }
  ]
});

// 添加本地音频流
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
stream.getTracks().forEach(track => pc.addTrack(track, stream));

// 创建并发送 Offer
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);

ws.send(JSON.stringify({
  command: "invite",
  option: {
    sdp: offer.sdp,
    codec: "opus",
    playbook: "greeting.md"
  }
}));

// 接收 Answer
ws.onmessage = async (event) => {
  const msg = JSON.parse(event.data);
  if (msg.event === "answer") {
    await pc.setRemoteDescription({
      type: "answer",
      sdp: msg.sdp
    });
  }
};
```

#### 优势
- 浏览器原生支持,无需插件
- 低延迟,适合实时对话
- 自动 NAT 穿透
- 支持 Opus 高质量音频编码

---

### 场景2: SIP 呼叫(对接PBX/FS网关)

**使用场景**: 对接企业 PBX 系统、[RustPBX](https://github.com/restsend/rustpbx)、FreeSWITCH、Asterisk 等传统电话网络。

#### 配置要点

```toml
# SIP 服务配置
addr = "0.0.0.0"
udp_port = 25060

# 如果在 NAT 后面,配置外部 IP
external_ip = "1.2.3.4"

# RTP 端口范围
rtp_start_port = 12000
rtp_end_port = 42000
```

#### 发起 SIP 呼叫

通过 WebSocket 连接到 `/call/sip` 端点:

```javascript
const ws = new WebSocket('ws://localhost:8080/call/sip?id=session456');

ws.onopen = () => {
  // 发起 SIP 呼叫
  ws.send(JSON.stringify({
    command: "invite",
    option: {
      caller: "sip:agent@active-call.com",
      callee: "sip:1001@pbx.example.com:5060",
      codec: "pcmu",  // 或 "pcma", "g722"
      playbook: "customer_service.md",
      asr: {
        provider: "openai",
        model: "whisper-1",
        language: "zh-CN"
      },
      tts: {
        provider: "openai",
        model: "tts-1",
        voice: "alloy"
      }
    }
  }));
};

// 处理事件
ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  console.log('Event:', msg.event);
  
  if (msg.event === "answer") {
    console.log('Call answered');
  } else if (msg.event === "transcript") {
    console.log('User said:', msg.text);
  } else if (msg.event === "end") {
    console.log('Call ended');
  }
};
```

#### 与 PBX 系统集成示例

**RustPBX 拨号计划配置**:

```yaml
# RustPBX dialplan.yml
rules:
  - match: "^9999$"
    actions:
      - bridge: "sip:agent@active-call-server:13050"
```

**FreeSWITCH 拨号计划配置**:

```xml
<extension name="call_ai_agent">
  <condition field="destination_number" expression="^9999$">
    <action application="bridge" data="sofia/external/agent@active-call-server:13050"/>
  </action>
</extension>
```

#### 优势
- 对接传统电话网络
- 支持标准 SIP 协议
- 兼容各种 PBX 系统
- 支持 G.711、G.722 等电话音频编码

---

### 场景3: WebSocket 呼叫(自定义应用)

**使用场景**: 需要完全控制音频流的自定义应用,或者不希望使用 WebRTC/SIP 的场景。

#### 配置要点

```toml
# 基础 HTTP 配置即可
http_addr = "0.0.0.0:8080"
```

#### 发起 WebSocket 呼叫

通过 WebSocket 连接到 `/call` 端点:

```javascript
const ws = new WebSocket('ws://localhost:8080/call?id=session789');

ws.onopen = () => {
  // 开始呼叫(不需要 SDP 协商)
  ws.send(JSON.stringify({
    command: "start",
    option: {
      codec: "pcm",  // 或 "pcma", "pcmu", "g722"
      sampleRate: 16000,
      playbook: "assistant.md",
      vad: {
        type: "silero",
        samplerate: 16000,
        speechPadding: 300,
        silencePadding: 500
      }
    }
  }));
};

// 发送音频数据
function sendAudio(audioData) {
  // audioData 是 PCM16 格式的音频数据(ArrayBuffer)
  ws.send(audioData);
}

// 接收音频和事件
ws.onmessage = (event) => {
  if (event.data instanceof Blob || event.data instanceof ArrayBuffer) {
    // 音频数据
    playAudio(event.data);
  } else {
    // JSON 事件
    const msg = JSON.parse(event.data);
    handleEvent(msg);
  }
};
```

#### 音频格式

WebSocket 呼叫支持以下音频格式:
- **PCM**: 16-bit signed, little-endian
- **PCMA**: G.711 A-law
- **PCMU**: G.711 μ-law  
- **G722**: G.722 宽带编码

#### 优势
- 最简单的集成方式
- 完全控制音频流
- 低开销,无 RTP/RTCP 协议
- 适合内部系统集成

---

### 场景4: SIP 呼入(注册到PBX)

**使用场景**: Active Call 作为 SIP 终端注册到 [RustPBX](https://github.com/restsend/rustpbx)、FreeSWITCH、Asterisk 等 PBX,接听来电。

#### 配置 SIP 注册

```toml
# SIP 服务配置 (使用基础配置中的 addr 和 udp_port)
addr = "0.0.0.0"
udp_port = 25060

# SIP 注册账号(可配置多个)
[[register_users]]
server = "pbx.example.com:5060"
username = "1001"
disabled = false
[register_users.credential]
username = "1001"
password = "secret123"
realm = "pbx.example.com"

[[register_users]]
server = "pbx.example.com:5060"
username = "1002"
disabled = false
[register_users.credential]
username = "1002"
password = "secret456"
realm = "pbx.example.com"
```

#### 配置呼入处理

使用 Playbook 处理器自动路由不同的来电:

```toml
[handler]
type = "playbook"
default = "default.md"

# 根据被叫号码路由
[[handler.rules]]
callee = "^sip:1001@.*"
playbook = "tech_support.md"

[[handler.rules]]
callee = "^sip:1002@.*"
playbook = "sales.md"

# 根据来电号码路由
[[handler.rules]]
caller = "^sip:vip.*"
playbook = "vip_service.md"
```

#### PBX 系统配置示例

**RustPBX 配置**:

```yaml
# RustPBX users.yml
users:
  - username: "1001"
    password: "secret123"
    domain: "pbx.example.com"
    enabled: true
```

**FreeSWITCH 配置**:

在 FreeSWITCH 中创建用户:

```xml
<!-- conf/directory/default/1001.xml -->
<include>
  <user id="1001">
    <params>
      <param name="password" value="secret123"/>
    </params>
  </user>
</include>
```

#### 优势
- 作为标准 SIP 终端接入
- 可以接收来自 PBX 的转接
- 支持多账号同时注册
- 灵活的呼入路由规则

---

## 完整配置示例

### 示例1: 纯 WebRTC 场景

```toml
addr = "0.0.0.0"
http_addr = "0.0.0.0:8080"
log_level = "info"
media_cache_path = "./config/mediacache"

[[ice_servers]]
urls = ["stun:stun.l.google.com:19302"]

[[ice_servers]]
urls = ["turn:turn.example.com:3478"]
username = "user"
credential = "pass"

[recording]
enabled = true
auto_start = true

[callrecord]
type = "local"
root = "./config/cdr"
```

### 示例2: SIP + WebRTC 混合场景

```toml
addr = "0.0.0.0"
http_addr = "0.0.0.0:8080"
udp_port = 25060
log_level = "info"
external_ip = "203.0.113.1"
media_cache_path = "./config/mediacache"

rtp_start_port = 12000
rtp_end_port = 42000

[[ice_servers]]
urls = ["stun:stun.l.google.com:19302"]

[handler]
type = "playbook"
default = "default.md"

[[handler.rules]]
callee = "^sip:support@.*"
playbook = "support.md"

[recording]
enabled = true
auto_start = true

[callrecord]
type = "local"
root = "./config/cdr"
```

### 示例3: 企业 PBX 集成场景

```toml
addr = "0.0.0.0"
http_addr = "0.0.0.0:8080"
udp_port = 25060
log_level = "info"
external_ip = "203.0.113.1"
media_cache_path = "./config/mediacache"

# SIP 账号注册
[[register_users]]
server = "pbx.company.com:5060"
username = "ai-agent-1"
disabled = false
[register_users.credential]
username = "ai-agent-1"
password = "ComplexPassword123"
realm = "pbx.company.com"

# Webhook 处理器(由外部系统决定路由)
[handler]
type = "webhook"
url = "http://crm.company.com:8090/call-routing"
method = "POST"

[recording]
enabled = true
auto_start = true

[callrecord]
type = "local"
root = "/var/recordings"
```

---

## CLI 参数

除了配置文件,也可以通过命令行参数快速启动:

```bash
# 基础使用
./active-call --conf active-call.toml

# 覆盖 HTTP 地址
./active-call --conf active-call.toml --http 0.0.0.0:9090

# 覆盖 SIP 地址
./active-call --conf active-call.toml --sip 0.0.0.0:13050

# 快速设置 Webhook 处理器
./active-call --handler https://api.example.com/webhook

# 快速设置 Playbook 处理器
./active-call --handler default.md

# 发起 SIP 呼出
./active-call --call sip:1001@127.0.0.1 --handler greeting.md

# 设置外部 IP 和支持的编码
./active-call --external-ip 1.2.3.4 --codecs pcmu,pcma,opus

# 组合使用
./active-call --conf active-call.toml \
  --http 0.0.0.0:8080 \
  --sip 0.0.0.0:13050 \
  --handler https://api.example.com/webhook \
  --external-ip 1.2.3.4 \
  --codecs pcmu,pcma,opus
```

---

## 参考文档

- [API 文档]./api.md - WebSocket API 详细说明
- [Playbook 教程]./playbook_tutorial.zh.md - Playbook 编写指南
- [架构设计]./a%20decoupled%20architecture%20for%20AI%20Voice%20Agent%20zh.md - 系统架构说明
- [WebRTC 与 SIP 互通]./how%20webrtc%20work%20with%20sip(zh.md) - 技术实现细节

---

## 常见问题

### Q: 如何选择使用哪种呼叫方式?

- **浏览器应用** → 使用 WebRTC (`/call/webrtc`)
- **对接电话网络** → 使用 SIP (`/call/sip`)
- **自定义应用/微信小程序** → 使用 WebSocket (`/call`)
- **接听电话来电** → 配置 SIP 注册和呼入处理器

### Q: 在 NAT 后面如何配置?

设置 `external_ip` 为公网 IP 地址,并在防火墙上开放相应端口:
- HTTP/WebSocket: 8080 (或自定义)
- SIP: 13050 (或自定义)
- RTP: 20000-30000 (或自定义范围)

### Q: 如何实现呼叫负载均衡?

在 Active Call 前面部署负载均衡器(如 Nginx、HAProxy),注意:
- WebSocket 连接需要支持 Upgrade
- SIP 和 RTP 是 UDP 协议,需要特殊处理
- 建议使用源 IP Hash 保持会话粘性

### Q: 支持哪些音频编码?

- **WebRTC**: PCM, Opus, PCMA, PCMU, G722
- **SIP**: PCM, PCMA, PCMU, G722
- **WebSocket**: PCM, PCMA, PCMU, G722

### Q: 如何调试配置问题?

1. 设置 `log_level = "debug"` 获取详细日志
2. 使用 `log_file` 将日志输出到文件
3. 检查防火墙和网络配置
4. 使用 SIP 调试工具(如 sngrep)查看 SIP 信令