.网站建设的基本步骤wordpress主题模板下载失败
2026/6/20 11:34:15 网站建设 项目流程
.网站建设的基本步骤,wordpress主题模板下载失败,群晖 同步 wordpress,简单搜索网页代码从零开始搞懂 pjsip#xff1a;一次打通 VoIP 通信的底层逻辑你有没有试过在自己的项目里接入一个软电话功能#xff1f;比如做个对讲系统、远程客服工具#xff0c;或者只是想研究下 SIP 协议是怎么跑起来的。如果你选择了pjsip#xff0c;那大概率会经历这么几个阶段一次打通 VoIP 通信的底层逻辑你有没有试过在自己的项目里接入一个软电话功能比如做个对讲系统、远程客服工具或者只是想研究下 SIP 协议是怎么跑起来的。如果你选择了pjsip那大概率会经历这么几个阶段下载编译成功 → 欣喜跑官方示例卡住 → 困惑日志一堆INVITE、407 Proxy Auth Required→ 抓狂终于听到“喂”一声 → 热泪盈眶别笑这几乎是每个接触 pjsip 的开发者的真实写照。pjsip 是什么它不是一个简单的库而是一整套多媒体通信引擎。它实现了 SIP、SDP、RTP/RTCP还内置了音频设备抽象层、编解码管理、NAT 穿透支持STUN/TURN/ICE甚至能跑在嵌入式设备上。听起来很牛但问题也来了——它的 API 太“原生”了文档分散错误提示晦涩新手很容易迷失在初始化流程和回调地狱中。今天我们就来干一件事用最直白的方式带你从零跑通一次完整的 SIP 通话。不讲空话不堆术语只聚焦“怎么动起来”和“为什么这么动”。入门第一关pjsua 到底是什么很多人一开始就被名字搞糊涂了pjsippjsuapjmedia它们的关系到底是什么简单说pjsip是整个项目的名字包含协议栈、核心库等。pjsua是构建在 pjsip 之上的高级框架专为用户代理User Agent设计适合做软电话这类应用。它帮你封装了信令、媒体、账户、呼叫控制这些复杂模块是绝大多数开发者的首选入口。你可以把它想象成 Android 的 Activity —— 不是最底层的 Linux syscall但足够灵活又能快速搭出可用的东西。所以我们的主线任务就是使用 pjsua 实现“注册账号 → 接听/拨打 → 双向通话”全流程。第一步让程序“活”起来 —— 初始化 pjsua任何 pjsua 应用都始于三个动作创建、配置、启动。#include pjsua-lib/pjsua.h // 自定义日志输出可选 static void log_writer(pj_pool_t *pool, int level, const char *data, int len) { PJ_UNUSED(pool); PJ_UNUSED(level); fwrite(data, len, 1, stdout); } int main() { // 1. 创建 pjsua 上下文 pjsua_create(); // 2. 配置日志 pjsua_logging_config log_cfg; pjsua_logging_config_default(log_cfg); log_cfg.console_level 4; // 显示详细信息 log_cfg.writer log_writer; // 3. 配置媒体参数 pjsua_media_config media_cfg; pjsua_media_config_default(media_cfg); // 4. 核心配置 设置回调 pjsua_config cfg; pjsua_config_default(cfg); cfg.cb.on_incoming_call on_incoming_call; // 来电处理 cfg.cb.on_call_state on_call_state; // 通话状态变化 cfg.cb.on_call_media_state on_call_media_state; // 媒体是否就绪 // 5. 初始化并启动 pj_status_t status pjsua_init(cfg, log_cfg, media_cfg); if (status ! PJ_SUCCESS) { pjsua_perror(初始化失败, status); return -1; } // 6. 启动事件循环 status pjsua_start(); if (status ! PJ_SUCCESS) { pjsua_perror(启动失败, status); return -1; } printf(✅ pjsua 已启动等待操作...\n); // 主循环保持运行 char input[10]; while (scanf(%s, input)) { if (input[0] q) break; } // 清理资源 pjsua_destroy(); return 0; }关键点解析pjsua_create()必须最先调用它分配内部结构体pjsua_init()才是真正的初始化入口传入三大配置块回调函数必须提前设置好否则你根本不知道发生了什么pjsua_start()启动后台线程处理网络 I/O 和定时器最后别忘了pjsua_destroy()不然会有内存泄漏。⚠️ 常见坑忘记调用pjsua_init()或漏掉某个配置默认值会导致后续所有操作返回PJ_EINVALIDOP。第二步身份认证 —— 添加并注册 SIP 账户没有账户你就只是个“黑户”无法发起或接收任何呼叫。SIP 账户长这样sip:alice192.168.1.100其中-alice是用户名-192.168.1.100是你的 SIP 服务器地址可以是 Asterisk、FreeSWITCH 等下面是添加账户的标准姿势void register_account(void) { pjsua_acc_config acc_cfg; pjsua_acc_config_default(acc_cfg); // 设置本机标识ID URI pj_cstr(acc_cfg.id_uri, sip:alice192.168.1.100); // 注册服务器地址 pj_cstr(acc_cfg.reg_uri, sip:192.168.1.100); // 认证信息 acc_cfg.cred_count 1; pj_cstr(acc_cfg.cred_info[0].username, alice); pj_cstr(acc_cfg.cred_info[0].password, secret123); pj_cstr(acc_cfg.cred_info[0].realm, *); // 匹配任意域 acc_cfg.cred_info[0].scheme pj_str((char*)digest); // 开机自动注册 acc_cfg.register_on_acc_add PJ_TRUE; // 添加账户 pjsua_acc_id acc_id; pj_status_t status pjsua_acc_add(acc_cfg, PJ_TRUE, acc_id); if (status ! PJ_SUCCESS) { pjsua_perror(❌ 添加账户失败, status); return; } printf(✅ 账户已添加正在注册...\n); }重点提醒id_uri必须唯一且格式正确reg_uri通常是你的 PBX 地址realm*虽然方便调试但在生产环境建议指定具体域名register_on_acc_add PJ_TRUE表示立即触发注册请求成功添加后不会立刻注册成功你需要通过回调监听注册状态。如何知道注册是否成功注册结果由on_reg_state回调通知需在pjsua_config.cb中设置void on_reg_state(pjsua_acc_id acc_id) { pjsua_acc_info ai; pjsua_acc_get_info(acc_id, ai); if (ai.status PJSIP_SC_OK) { printf( 账户注册成功有效期%d 秒\n, ai.expires); } else { printf(❌ 注册失败: %.*s\n, (int)ai.status_text.slen, ai.status_text.ptr); } }第三步打电话实现主叫与被叫现在你已经“上线”了接下来就是核心功能拨号和接听。拨打出站电话pjsua_call_id call_id; void make_outgoing_call(const char *dst_uri_str) { pj_str_t dst_uri pj_str((char*)dst_uri_str); pjsua_call_setting call_opt; pjsua_call_setting_default(call_opt); call_opt.aud_cnt 1; // 启用音频流 call_opt.vid_cnt 0; // 不启用视频 pj_status_t status pjsua_call_make_call( pjsua_acc_get_default(), // 使用默认账户 dst_uri, call_opt, NULL, // 用户数据 NULL, // 呼叫参数 call_id ); if (status PJ_SUCCESS) { printf( 正在拨打 %s ...\n, dst_uri_str); } else { pjsua_perror(拨号失败, status); } }调用方式举例make_outgoing_call(sip:bob192.168.1.100);接听来电当别人打给你时on_incoming_call回调会被触发void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id cid, pjsua_call_op_param *param) { PJ_UNUSED(acc_id); PJ_UNUSED(param); char answer; printf( 收到来电是否接听(y/n): ); scanf( %c, answer); if (answer y) { pjsua_call_answer(cid, 200, NULL, NULL); // 200接听 call_id cid; printf(✅ 已接听\n); } else { pjsua_call_hangup(cid, 603, NULL, NULL); // 603拒绝 printf(⛔ 已拒接\n); } }监听通话状态每次通话都会经历多个状态CALLING → INCOMING → CONNECTING → CONFIRMED → DISCONNECTED我们通过on_call_state回调来跟踪void on_call_state(pjsua_call_id cid, pjsip_event *e) { PJ_UNUSED(e); pjsua_call_info ci; pjsua_call_get_info(cid, ci); printf( 通话 [%d] 状态: %.*s (%.*s)\n, cid, (int)ci.state_text.slen, ci.state_text.ptr, (int)ci.last_status_text.slen, ci.last_status_text.ptr); if (ci.state PJSIP_INV_STATE_DISCONNECTED) { printf( 通话结束原因: %.*s\n, (int)ci.disconnect_reason.slen, ci.disconnect_reason.ptr); } }第四步最关键一步 —— 打通音频通道很多人做到这里发现一个问题能打通电话但听不到声音这不是网络问题而是因为你还没把媒体流连到声卡上去。pjsua 内部有个“混音器”conference bridge每个通话有一个conf_slot编号我们需要手动将它连接到默认录音和播放设备。这个操作必须放在on_call_media_state回调中执行void on_call_media_state(pjsua_call_id cid) { pjsua_call_info ci; pjsua_call_get_info(cid, ci); if (ci.media_status PJSUA_CALL_MEDIA_ACTIVE) { // 将当前通话的 conf_slot 连接到音频设备 pjsua_conf_connect(ci.conf_slot, 0); // 通话 → 扬声器 pjsua_conf_connect(0, ci.conf_slot); // 麦克风 → 通话 printf( 音频通道已激活你现在可以说话了\n); } }✅ 注意只有当media_status PJSUA_CALL_MEDIA_ACTIVE时才能连接否则会失败。第五步音频设备管理 —— 选对麦克风和扬声器不同平台设备索引不一样最好先列出来看看有哪些可用设备void print_audio_devices(void) { unsigned count pjsua_enum_aud_devs(NULL, 0); pjsua_dev_info dev_info[PJMEDIA_AUD_MAX_DEVS]; pjsua_enum_aud_devs(dev_info, count); printf( 发现 %u 个音频设备:\n, count); for (unsigned i 0; i count; i) { printf( [%d] %.*s (输入:%d, 输出:%d)\n, i, (int)dev_info[i].name.slen, dev_info[i].name.ptr, dev_info[i].input_count, dev_info[i].output_count); } }然后切换设备也很简单// 切换到指定录音/播放设备 pjsua_set_snd_dev(0, 1); // 输入0, 输出1常见默认组合- Windows: capture0, playback1- Linux ALSA: 通常也是 0 和 1- Android/iOS由系统动态分配需权限申请常见问题与避坑指南问题原因解决方案注册失败407/401认证失败或凭据错误检查用户名、密码、realm 是否匹配能拨号但对方听不到我没有调用pjsua_conf_connect()确保在on_call_media_state中连接混音器回声严重缺少回声消除在pjsua_media_config中启用 AECmedia_cfg.ec_tail_len 200;延迟高、卡顿jitter buffer 太大或编解码器选择不当减小jb_max优先使用 Opus 编解码器NAT 穿透失败私网地址暴露给公网配置 STUNcfg.stun_host pj_str(stun.l.google.com:19302);实际应用场景联想你以为 pjsip 只能用来做软电话太局限了。它可以轻松扩展成-IP 对讲系统固定账号 自动接听 广播呼叫-智能门禁联动刷卡触发 SIP 呼叫物业值班室-车载语音调度嵌入式设备 3G/4G 网络 多线路管理-医疗应急通讯一键呼叫 位置上报 录音存档而且由于它是 C 语言写的性能极高常驻后台也没压力。总结掌握 pjsip 的关键思维学 pjsip 不是背 API而是理解它的事件驱动模型 状态机机制。记住这几条黄金法则一切从初始化开始create → init → start缺一不可回调是灵魂你不主动问状态系统也不会告诉你发生了什么媒体≠信令注册成功 ≠ 能通话通话建立 ≠ 有声音conf_slot 是桥梁必须手动连接混音器否则音频不通日志是最好的老师打开 level5 日志看清每一条 SIP 消息流转。当你第一次听到对方说“喂”的时候那种成就感值得你熬过的每一个 debug 夜晚。如果你正在尝试将 pjsip 集成到你的项目中欢迎留言交流遇到的具体问题。无论是交叉编译、Android JNI 封装还是 WebRTC 与 SIP 的桥接我们都可以一起探讨。毕竟搞通信的人最怕的就是“静音”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询