---
# System prepended metadata

title: SIP & PJSUA2 的使用介紹
tags: [Tutorial]

---

# SIP 是什麼？
SIP（Session Initiation Protocol）是一種基於 TCP/IP 的應用層網路協議，專門用於建立、修改與終止 IP 網路上的多媒體通訊會話。這些通訊會話可以涵蓋文字、語音、影像和視訊等多種內容。

由於 SIP 是架構在應用層之上，開發 SIP 應用程式時，可以不受底層傳輸協定或網路架構的限制。這帶來以下幾個優點：

- 容易開發：SIP 訊息格式簡潔，基於類似 HTTP 的文字格式。
- 高度擴充性：能支援多種傳輸協定，例如 TCP、UDP 或 TLS 等。
- 靈活整合：SIP 不僅可在傳統網路（如 TCP/UDP）運作，還能架構在各種不同類型的傳輸網路上，方便系統整合與跨平台部署。

# PJSUA2 介紹 & 使用說明
PJSUA2 是一個基於 PJSIP 的高階 C++ API，提供更簡單、易用的介面來實現 SIP 功能，並支援多種平台（包括 Python 綁定）。

透過 PJSUA2，開發者可以在 Python 中快速實現 SIP 功能，例如：

- 發起與接收電話
- 管理多媒體會話
- 處理事件回調

PJSUA2 封裝了底層的實作細節，使開發過程更直觀，降低學習門檻，同時保留了 PJSIP 強大的功能與靈活性。


## 使用方法
### 0. 匯入pjsua2 package
```
import pjsua2 as pj
```
### 1. 建立Endpoint
Endpoint是SIP當中的核心，它負責管理整個 SIP 會話和傳輸層的資源，並提供 API 讓你可以進行操作，例如建立傳輸、註冊帳號、發起通話等。
備註：需要做一個迴圈讓Endpoint保持開啟，否則初始化&啟動後就會直接結束。
```
# 創建 Endpoint 物件
lib = pj.Endpoint()
lib.libCreate()

# 初始化 Endpoint
ep_cfg = pj.EpConfig() 
ep_cfg.logConfig.level = 4 # 4: 顯示所有日誌 (0 ~ 5)
lib.libInit(ep_cfg) # 初始化 Endpoint

# 創建傳輸層
transport_cfg = pj.TransportConfig()
transport_cfg.port = 5060  # SIP 端口
lib.transportCreate(pj.PJSIP_TRANSPORT_UDP, transport_cfg)

# 啟動
lib.libStart()

# 進入事件循環，保持程序運行
try:
    print("SIP transport created, waiting for events...")
    while True:
        # 不斷運行，保持程序活躍
        pass
except KeyboardInterrupt:
    # 用戶按下 Ctrl+C 時結束循環
    print("Exiting...")
    lib.libDestroy()

```
- Endpoint 就像一個 SIP 應用的總管理者，所有與 SIP 協議相關的動作（如建立通訊通道、註冊帳號、通話管理）都需要通過 Endpoint 進行初始化和管理。
- 作為 SIP 通信的核心元件，管理網絡層的連接、傳輸協議以及會話狀態，確保客戶端之間的訊息和呼叫能夠正確地發送和接收。
- 同時處理不同網域和使用者之間的註冊、身份驗證和消息傳遞等過程。

**Endpoint 是用來處理 SIP 通信的橋樑，負責與SIP伺服器和其他 SIP 客戶端進行通信。**


### 2. 註冊帳號
為不同的使用者，在不同的網域下建立帳號。這些帳號會被註冊到 SIP 伺服器，讓使用者能夠利用事先建立好的 Endpoint 來進行訊息交換和通信。
通過這些帳號，使用者可以在同一個系統中發送和接收呼叫、訊息以及其他 SIP 操作。
```
# account 設定
user_name = "1001"
domain_name = "10.0.0.100"
password = "1234"
acc_config = pj.AccountConfig() # 設置帳號配置
acc_config.idUri = f"sip:{user_name}@{domain_name}"  # 使用者帳號
acc_config.regConfig.registrarUri = f"sip:{domain_name}"  # 註冊服務器 URI
acc_config.sipConfig.authCreds.append(pj.AuthCredInfo("digest", "*", user_name, 0, password))

account = pj.Account()
account.create(acc_config)
print("帳號設定完成！")
```
domain 的主要作用是用來指定你的 SIP 伺服器的位置，並讓你的帳號可以註冊到該伺服器。
換句話說，你的 SIP Endpoint（即程式）會使用一個端口來進行通訊，而帳號則是在特定的 domain 上註冊。Domain 只是用來幫助區分不同的帳號和指向註冊伺服器，它並不直接影響 Endpoint 的端口設置。

### 3. 建立SIP通話
```
# 通話類別
class MyCall(pj.Call):
    def onCallState(self, prm):
        ci = self.getInfo()
        print(f"呼叫狀態: {ci.stateText}")

# 發起 SIP 呼叫
call = MyCall(account)
call.makeCall("sip:destination@sipserver.com")
print("正在呼叫對方...")
```

### 4. 接收來電
```
class MyAccount(pj.Account):
    def onIncomingCall(self, call_prm):
        print("接收到來電！")
        call = MyCall(self, call_prm.callId)
        call.answer(200)  # 回應 200 OK，接聽來電
        print("來電已接聽")
```

### 5. 完整範例
直接跑server.py & client.py，即可傳送訊息透過client -> server
<details><summary>server.py</summary>
    import pjsua2 as pj

    # 1. 創建 Endpoint 並初始化
    lib = pj.Endpoint()
    lib.libCreate()

    # 設置日誌等級
    ep_cfg = pj.EpConfig()
    ep_cfg.logConfig.level = 4
    lib.libInit(ep_cfg)

    # 2. 創建 UDP Transport 監聽端口 5060
    transport_cfg = pj.TransportConfig()
    transport_cfg.port = 5060
    lib.transportCreate(pj.PJSIP_TRANSPORT_UDP, transport_cfg)

    # 3. 啟動 Endpoint
    lib.libStart()
    print("SIP 伺服器已啟動，正在監聽 5060 端口...")

    # 4. 自定義帳號以處理來電和訊息
    class MyAccount(pj.Account):
        def onIncomingCall(self, call):
            print("收到來電！")
            call_prm = pj.CallOpParam(True)  # 自動接聽
            call.answer(call_prm)

        def onIncomingSubscribe(self, prm):
            print("收到訂閱訊息！")

        def onInstantMessage(self, prm):
            print(f"收到訊息: {prm.msgBody}")

    # 5. 設定帳號
    acc_cfg = pj.AccountConfig()
    acc_cfg.idUri = "sip:server@10.0.0.122"  # server SIP 帳號
    account_1 = MyAccount()
    account_1.create(acc_cfg)

    print("SIP 伺服器已準備好，等待訊息或呼叫...")

    # 6. 保持程式運行
    try:
        while True:
            pass
    except KeyboardInterrupt:
        print("伺服器關閉...")
    finally:
        lib.libDestroy()
</details>



<details><summary> client.py </summary>

    import pjsua2 as pj
    import time

    # 創建 Endpoint 並初始化
    lib = pj.Endpoint()
    lib.libCreate()

    # 設置日誌等級
    ep_cfg = pj.EpConfig()
    ep_cfg.logConfig.level = 4
    lib.libInit(ep_cfg)

    # 創建 UDP Transport 監聽端口 5061
    transport_cfg = pj.TransportConfig()
    transport_cfg.port = 5061
    lib.transportCreate(pj.PJSIP_TRANSPORT_UDP, transport_cfg)

    # 啟動 Endpoint
    lib.libStart()
    print("SIP 客戶端已啟動，正在註冊...")

    # 定義帳號並註冊到 Server端 (這邊為客戶端)
    class MyAccount(pj.Account):
        def onIncomingCall(self, call):
            print("收到來電！")
            call_prm = pj.CallOpParam(True)  # 自動接聽
            call.answer(call_prm)

        def onInstantMessage(self, prm):
            print(f"收到訊息: {prm.msgBody}")

    # 設定帳號，這裡的idUri和registrarUri要和server的domain匹配  
    # 也就是說我們透過domain建立一個連結帳戶，當作之後溝通使用
    
    acc_cfg = pj.AccountConfig()
    acc_cfg.idUri = "sip:cilent_2@10.0.0.122"  # Server 端 SIP 帳號
    acc_cfg.regConfig.registrarUri = "sip:10.0.0.122"  # 伺服器地址
    account = MyAccount()
    account.create(acc_cfg)

    # 註冊後等待幾秒鐘，讓註冊完成
    time.sleep(2)

    # 傳送訊息到 server1
    buddy_config = pj.BuddyConfig()
    buddy_config.uri = "sip:cilent_1@10.0.0.122"  # 對方 SIP 帳號
    buddy = pj.Buddy()
    buddy.create(account, buddy_config)

    # 發送訊息
    im_param = pj.SendInstantMessageParam()
    im_param.content = "你好，這是來自 client2 的訊息！"
    if buddy.isValid():
        buddy.sendInstantMessage(im_param)
        print("SIP 訊息已發送！")
    else:
        print("Buddy 創建失敗，無法發送訊息。")

    # 保持程序運行
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print("客戶端關閉...")
    finally:
        lib.libDestroy()

</details>
    
# 總結

SIP（Session Initiation Protocol）是一個網路通訊協議，用於建立、管理和終止網路設備之間的對話。我們可以把 SIP 想像成一套 通訊架構，而 Endpoint 就是這個架構中的「溝通橋樑」，負責處理 SIP 訊息的傳遞與管理。

- Endpoint 的角色

    - 每個設備（或程式）都必須先建立一個 Endpoint，它是 SIP 通訊的核心起點，負責和其他設備或伺服器進行通訊。
Endpoint 就像是一個中轉站，通過這個中轉站，你可以註冊帳號、接收來電、發送請求等。
- 註冊帳號

    - 當建立了 Endpoint 後，Client 需要透過 向伺服器（SIP 伺服器）註冊（建立一個帳號），讓伺服器知道 Client 的存在與位置。
    - 帳號註冊後，Client 就能夠透過伺服器和其他已經向伺服器註冊的 Client 進行通訊。
- 通訊方式

    - SIP 支援多種傳輸協定，如 UDP、TCP 和 TLS 等，這些方式決定了訊息的傳輸路徑。
    - 每個設備或進程都需要獨立的 Endpoint，確保各自能正確建立橋樑，進而完成通訊任務。

## 白話文懶人包
- **SIP 協議** 就像是一套郵政系統的標準。
- **Endpoint** 就像是一個郵局，負責接收和發送信件（SIP 訊息）。
- **帳號註冊** 則是將自己的地址（Client 的位置資訊）通知給伺服器，這樣其他人才能找到你並和你通訊。
- 當所有郵局（Endpoints）和地址（帳號）都設置完成後，設備之間就能透過網路傳送訊息或建立語音、視訊等會話。