百度行发代理商/重庆seo排名优化费用
文章目录
- 前言
- 一、关键实现
- 1、主要流程
- 2、解决端口冲突
- (1)、出现原因
- (2)、解决方法
- 3、解析sdp
- (1)、定义实体
- (2)、解析视频
- (3)、解析音频
- 4、命令行推拉流
- (1)、视频推流
- (2)、音频推流
- (3)、音视频播放
- 二、sipua接口设计
- 三、使用示例
- 四、完整代码
- 五、效果预览
- 总结
前言
使用sip做视频通话时,会遇到需要使用ip摄像头作为视频源的情况,查了资料使用pjsip通常也需要改源码。pjsip包含的功能很完整,但有点过于庞大,很多功能并不需要。而且笔者有一个想法,只要有个能处理sip交互的库比如eXosip,音视频这块另外实现,比如先使用ffmpeg和ffplay命令行作为音视频测试,成功后再写代码实现。本文就是测试成功的方案,真正灵活的方式还是要写代码调ffmpeg,本文更多的是提供一种实现思路。
一、关键实现
主要的实现步骤是使用eXosip处理sip、自己解析sdp、流媒体使用ffmpeg、ffplay命令行。
1、主要流程
2、解决端口冲突
(1)、出现原因
按照上述流程会遇到端口冲突问题,推流和拉流需要使用同一个本地udp端口,由于ffmpeg和ffplay是两个进程同使用相同的端口就会冲突。具体细节如下:
(2)、解决方法
一般想到的解决办法是使用jrtplib只建立一个rtp会话兼顾发送和接收,且流媒体通过ffmpeg代码实现。本文没有使用此方法,为了坚持使用ffmpeg和ffplay命令行,最好的方法是使用udp代理监听端口对数据进行转发,就可以有效的解决端口冲突问题。
3、解析sdp
虽然eXosip提供了sdp的获取方法,但是对于具体的信息还是需要自己解析,其实也是比较简单。
(1)、定义实体
//流类型
enum StreamType {STREAMTYPE_VIDEO,STREAMTYPE_AUDIO
};
/// <summary>
/// 流信息
/// </summary>
class StreamInfo {
public://流类型StreamType type;//rtp推流地址,可以用此地址ffmpeg直接推流,也可以用下面参数自定义推流char rtpAdress[128] = { 0 };//流的远端地址char remoteIp[32] = { 0 };//流的远端端口int remotePort = 0;//本地接收/发送端口int localPort = 0;//编码格式char codec[16];//负载类型int payload = 0;union{//采样率,音频int sampleRate = 0;//时间基、视频int timebase;};//声道数int channels = 0;
};
(2)、解析视频
std::vector<StreamInfo> SipUA::_getVideoStreams(sdp_message_t* sdp_msg)
{std::vector<StreamInfo> streams;if (!sdp_msg)return streams;sdp_connection_t* connection = eXosip_get_video_connection(sdp_msg);if (!connection)return streams;std::string ip = connection->c_addr; sdp_media_t* sdp = eXosip_get_video_media(sdp_msg);if (!sdp)return streams;int port = atoi(sdp->m_port); for (int i = 0; i < sdp->a_attributes.nb_elt; i++){sdp_attribute_t* attr = (sdp_attribute_t*)osip_list_get(&sdp->a_attributes, i);if (attr){std::string audio_filed = attr->a_att_field;if (audio_filed == "rtpmap"){StreamInfo stream;stream.type = StreamType::STREAMTYPE_VIDEO;snprintf(stream.remoteIp, 32, ip.c_str());stream.remotePort = port;std::string value = attr->a_att_value;std::string::size_type pt_idx = value.find_first_of(0x20);if (pt_idx == std::string::npos)continue;stream.payload = atoi(value.substr(0, pt_idx).c_str());std::string::size_type bitrate_idx = value.find_first_of('/');if (bitrate_idx == std::string::npos)continue;stream.timebase = atoi(value.substr(bitrate_idx + 1).c_str());snprintf(stream.codec, 32, value.substr(pt_idx + 1, bitrate_idx - pt_idx - 1).c_str());streams.push_back(stream);}}}return streams;
}
(3)、解析音频
std::vector<StreamInfo> SipUA::_getAudioStreams(sdp_message_t* sdp_msg)
{std::vector<StreamInfo> streams;if (!sdp_msg)return streams;sdp_connection_t* connection = eXosip_get_audio_connection(sdp_msg);if (!connection)return streams;std::string audio_ip = connection->c_addr; //audio_ipsdp_media_t* audio_sdp = eXosip_get_audio_media(sdp_msg);if (!audio_sdp)return streams;int audio_port = atoi(audio_sdp->m_port); //audio_portfor (int i = 0; i < audio_sdp->a_attributes.nb_elt; i++){sdp_attribute_t* attr = (sdp_attribute_t*)osip_list_get(&audio_sdp->a_attributes, i);if (attr){std::string audio_filed = attr->a_att_field;if (audio_filed == "rtpmap"){StreamInfo stream;stream.type = StreamType::STREAMTYPE_AUDIO;snprintf(stream.remoteIp, 32, audio_ip.c_str());stream.remotePort = audio_port;std::string value = attr->a_att_value;auto strs = StringHelper::split(value, " ");if (strs.size() > 1){stream.payload = atoi(strs[0].c_str());auto format = StringHelper::split(strs[1], "/");if (format.size() > 1){snprintf(stream.codec, 16, format[0].c_str());stream.sampleRate = atoi(format[1].c_str());if (format.size() > 2)stream.channels = atoi(format[2].c_str());}}streams.push_back(stream);}}}return streams;
}
4、命令行推拉流
(1)、视频推流
转发rtsp的h264流为例,rtp推流同时显示预览框。
ffmpeg -i rtmp://127.0.0.1/live/a123 -an -vcodec copy -payload_type 96 -f rtp rtp://127.0.0.1:25026?localrtpport=15514 -window_size 192x108 -f sdl
(2)、音频推流
以本地文件转码为g.711u为例,每个包大小160bytes。
ffmpeg -re -stream_loop -1 -i D:\test_music.wav -vn -acodec pcm_mulaw -ar 8000 -ac 1 -af "aresample=8000[0];[0]asetnsamples=n=160:p=0" -payload_type 0 -f rtp rtp://127.0.0.1:15026?localrtpport=25514
音频设备采集编码为g.711u为例,每个包大小160bytes。
ffmpeg -f dshow -i audio="音频设备名称" -vn -acodec pcm_mulaw -ar 8000 -ac 1 -af "aresample=8000[0];[0]asetnsamples=n=160:p=0" -payload_type 0 -f rtp rtp://127.0.0.1:15026?localrtpport=25514
注:如果音频与视频为同一个输入源也可以合并为同一条命令。
(3)、音视频播放
将sdp字符串保存本地文件
本地播放的sdp
v=0
o=1002 158 1 IN IP4 127.0.0.1
s=Talk
c=IN IP4 127.0.0.1
t=0 0
m=video 25008 RTP/AVP 96
a=rtpmap:96 H264/90000
a=rtcp:25008
m=audio 25310 RTP/AVP 0
a=rtpmap:0 PCMU/8000
a=rtcp:25310
保存到test.sdp
FILE* f=NULL;
fopen_s(&f, "test.sdp", "wb");
if (f)
{fwrite(call->sdp, 1, strlen(call->sdp), f);fclose(f);
}
命令行播放
ffplay.exe -x 640 -y 360 -protocol_whitelist \"file,udp,rtp\" -i test.sdp
二、sipua接口设计
#pragma once
#include<functional>
#include <string>
#include <vector>
#include "UdpProxy.h"
#include <eXosip2\eXosip.h>
#include"MessageQueue.h"/// 这是一个sipua,内部实现是eXosip2,只提供sip交互,sdp解析、udp代理功能。
/// udp代理分离端口功能:
/// sdp的每个m媒体的推拉流需要使用一个端口,sip服务器要检查来源。
/// 如果此时采样ffmpeg.exe推流、ffplay.exe拉流,两个进程都需要绑定本地同一个端口,就会产生端口冲突。
/// 那就只能个使用jrtplib之类的库,打开一个连接同时发送和接收数据。
/// 但是有一个巧妙的解决办法那就是使用udp代理转发数据,就可以将端口拓展为多个了。/// <summary>
/// sip状态
/// </summary>
enum SipUAState {//收到对方inviteSIPUAEVENT_INVITE,//收到对方回复SIPUAEVENT_ANSWER,//处理流媒体,推流拉流端口有做分离,便于推拉流分开实现。SIPUAEVENT_STREAM,//结束通话,对方挂断SIPUAEVENT_ENDED,
};/// <summary>
/// 流类型
/// </summary>
enum StreamType {STREAMTYPE_VIDEO,STREAMTYPE_AUDIO
};/// <summary>
/// 流信息
/// </summary>
class StreamInfo {
public://流类型StreamType type;//rtp推流地址,可以用此地址ffmpeg直接推流,也可以用下面参数自定义推流char rtpAdress[128] = { 0 };//流的远端地址char remoteIp[32] = { 0 };//流的远端端口int remotePort = 0;//本地接收/发送端口int localPort = 0;//编码格式char codec[16];//负载类型int payload = 0;union{//采样率,音频int sampleRate = 0;//时间基、视频int timebase;};//声道数int channels = 0;
};/// <summary>
/// 通话对象
/// </summary>
class SipCall {
public:int callId = 0;//对方idconst char* userId = nullptr;//播发的sdpconst char* sdp = nullptr;//需要推流的视频信息StreamInfo* video = nullptr;//需要推流的音频信息StreamInfo* audio = nullptr;
};
class SipUA
{
public:/// <summary>/// 状态改变回调,目前版本除媒体流外只有对方的消息会触发状态改变/// </summary>std::function<void(SipUAState state, SipCall* call)> onState = [](auto, auto) {};SipUA(const std::string& serverIp, int serverPort, const std::string& username, const std::string& password);~SipUA();/// <summary>/// 开启客户端,此方法是阻塞的,可以在线程中开启。/// </summary>/// <param name="exitFlag">退出标记,值为true则退出</param>void exec(int* exitFlag);/// <summary>/// 呼叫/// </summary>/// <param name="remoteUserID">对方id</param>/// <param name="hasVideo">有视频否</param>/// <param name="hasAudio">有音频否</param>/// <returns>是否呼叫成功</returns>bool call(const std::string& remoteUserID, bool hasVideo = true, bool hasAudio = true);/// <summary>/// 应答/// </summary>/// <param name="hasVideo">有视频否</param>/// <param name="hasAudio">有音频否</param>void answer(bool hasVideo, bool hasAudio);/// <summary>/// 挂断/// </summary>void hangup();
};
三、使用示例
/// <summary>
/// 本示例启动后会自动拨号,
/// 接收到通话请求会自动接听
/// </summary>
void main() {SipUA ua("192.168.1.10", 5060, "1002", "1234");int exitFlag = false;ua.onState = [&](SipUAState state, SipCall* call) {switch (state){case SIPUAEVENT_INVITE:ua.answer(true, true);break;case SIPUAEVENT_ANSWER:break;case SIPUAEVENT_STREAM://视频推流if (call->video){std::string srcUrl = "test.mp4";std::string format = "-re -stream_loop -1";auto codec = StringHelper::toLower(call->video->codec);std::string params = "";char cmd[512]; if (codec == "h264"){params = "-preset ultrafast -tune zerolatency -level 4.2";}//发送桌面流,同时使用sdl本地预览sprintf_s(cmd, "ffmpeg %s -i %s -an -vcodec %s -pix_fmt yuv420p %s -s 640x360 -b:v 500k -r 30 -g 10 -payload_type %d -f rtp %s -window_size 192x108 -f sdl \"%s\" ",format.c_str(), srcUrl.c_str(), codec.c_str(), params.c_str(), call->video->payload, call->video->rtpAdress, srcUrl.c_str());//运行命令行runCmd(cmd);}//音频推流,如何是同一个输入流也可以和视频合并为一条命令if (call->audio){ std::string srcUrl = "test_music.wav";std::string format = "-re -stream_loop -1"; auto codec = StringHelper::toLower(call->audio->codec);std::string params = "";char cmd[512];if (codec == "opus"){codec = "libopus";}if (codec == "pcmu"){codec = "pcm_mulaw";params = "-ac 1 -af \"aresample=8000[0];[0]asetnsamples=n=160:p=0\"";//af滤镜确保每个包160bytes}//转发本地文件sprintf_s(cmd, "ffmpeg %s -i %s -vn -acodec %s -ar %d %s -payload_type %d -f rtp %s",format.c_str(), srcUrl.c_str(), codec.c_str(), call->audio->sampleRate, params.c_str() , call->audio->payload, call->audio->rtpAdress);printf(cmd);//运行命令行runCmd(cmd);}//播放对方音视频if (call->sdp){FILE* f=NULL;fopen_s(&f, "test.sdp", "wb");if (f){fwrite(call->sdp, 1, strlen(call->sdp), f);fclose(f);std::string cmd = "ffplay.exe -x 640 -y 360 -protocol_whitelist \"file,udp,rtp\" -i test.sdp";//运行命令行runCmd(cmd);}else{printf("fopen_s test.sdp error\n");}}break;case SIPUAEVENT_ENDED://关闭所有子进程closeJobObject();break;default:break;}};//开启测试拨号new std::thread([&]() {Sleep(2000);ua.call("1004", true);});ua.exec(&exitFlag);
}
四、完整代码
eXosip版本为5.1,ffmpeg.exe为4.3,vs2022项目。
https://download.csdn.net/download/u013113678/88180712
五、效果预览
使用freeswitch作为sip服务器
本文程序的运行效果:
推送本地mp4到sip
使用linphone作为对端运行效果:
总结
以上就是今天讲述的内容,本文使用的技术很简单,但是实现过程有点曲折。尤其是端口冲突问题,花了不少的时间确定原因,解决办法也是无意中想到的,否则可能很早就用代码去实现整个sip客户端了。本文的实现方式,很好的解耦了sip和流媒体以及rtp,sip可以单独实现、流媒体也可以自由选择、也不需要共用一个rtp会话,有时想要快速搭建一个测试项目就变得容易多了。
相关文章:

使用eXosip+ffmpeg、ffplay命令行实现sip客户端
文章目录 前言一、关键实现1、主要流程2、解决端口冲突(1)、出现原因(2)、解决方法 3、解析sdp(1)、定义实体(2)、解析视频(3)、解析音频 4、命令行推拉流&am…...

dotNet 之网络TCP
**硬件支持型号 点击 查看 硬件支持 详情** DTU701 产品详情 DTU702 产品详情 DTU801 产品详情 DTU802 产品详情 DTU902 产品详情 G5501 产品详情 ARM dotnet 编程 dotNet使用TCP,可以使用Socket和TcpClient 、TcpListener类 2种,对于高级用户&…...

python基础面试题汇总(持续更新),冲击offer
目录 1.概念理解题python内置数据结构,哪些是不可变的python新式类和经典类的区别is和有什么区别Python中变量查找顺序python函数的参数是值传递还是引用传递python垃圾回收机制什么是闭包什么是装饰器,开发中用到举例如何实现只读属性Python中类方法、实…...

Java课题笔记~ AOP编程术语(掌握)
(1) 切面(Aspect) 切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面是通知(Advice)。实际就是对主业务逻辑的一种增强。 (2) 连接点(Jo…...

暑假刷题第23天--8/6
3748. 递增子串 - AcWing题库 #include<iostream> #include<string> const int N200005; int a[N]; using namespace std; int main(){int t;cin>>t;for(int q1;q<t;q){int n;cin>>n;string s;cin>>s;int cnt1;a[1]1;for(int i2;i<n;i){i…...

ArcGIS API for JavaScript 4.x 教程(一) 显示一张地图
了解如何创建和显示带有基本地图图层的地图。 地图包含地理数据层。地图包含一个基本地图层,以及一个或多个数据层(可选)。可以使用地图视图显示地图的特定区域,并设置位置和缩放级别。 本教程将向您展示如何使用地形底图层创建和…...

Python-OpenCV中的图像处理
Python-OpenCV中的图像处理 颜色空间转换物体跟踪获取HSV的值几何变换图像缩放图像平移图像旋转仿射变换透视变换 图像阈值单阈值自适应阈值Otsus二值化 颜色空间转换 在 OpenCV 中有超过 150 中进行颜色空间转换的方法。但是你以后就会 发现我们经常用到的也就两种࿱…...

分清性能测试,负载测试,压力测试这三个的区别
做测试一年多来,虽然平时的工作都能很好的完成,但最近突然发现自己在关于测试的整体知识体系上面的了解很是欠缺,所以,在工作之余也做了一些测试方面的知识的补充。不足之处,还请大家多多交流,互相学习。 …...

前端架构师岗位的工作职责(合集)
前端架构师岗位的工作职责1 职责: 1.制定前端的标准和规范,并推广和应用,提高团队的开发效率; 2.前端架构的框架或核心模块的设计与实现; 3.在前端架构、设计与开发上对团队进行足够的指导; 4.在日常的系统设计与优化上与服务端团队紧密合…...

使用 Amazon ECS Anywhere 在边缘部署 Amazon IoT Greengrass
1.概述 亚马逊云科技提供了完备的IoT服务能力,涵盖设备服务、连接和控制服务以及云端分析服务,是快速构建安全可靠、可扩展的 IoT 平台的常见选择。Amazon IoT Greengrass 边缘运行时和云服务,可帮助您在设备上构建、部署和管理 IoT 应用。A…...

pytorch Stream 多流处理
CUD Stream https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#c-language-extensions 中指出在kenel的调用函数中最后一个可选参数表示该核函数处在哪个流之中。 - 参数Dg用于定义整个grid的维度和尺寸,即一个grid有多少个block。为dim3类型。…...

微信小程序选项卡切换(滑动切换,点击切换)
效果如下:可点击切换,滑动切换 代码如下 这个可以在项目用 index.wxml <view classtopTabSwiper><view classtab {{currentData 0 ? "tabBorer" : ""}} data-current "0" bindtapcheckCurrent>选项一&…...

安路FPGA的赋值报错——移位处理,加括号
authordaisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主 在使用移位符号用来当作除以号使用时,发现如下问题 其中 cnt_8K 为偶数和奇数时输出的数据不一样 reg [10:0] cnt_8K; reg [10:0] ram1_addra; always(posedge clk_16M) begin if(ram_out_flag )begin if(…...

GO学习之 接口(Interface)
GO系列 1、GO学习之Hello World 2、GO学习之入门语法 3、GO学习之切片操作 4、GO学习之 Map 操作 5、GO学习之 结构体 操作 6、GO学习之 通道(Channel) 7、GO学习之 多线程(goroutine) 8、GO学习之 函数(Function) 9、GO学习之 接口(Interface) 文章目录 GO系列前言一、什么是…...

ansible常见模块的运用
ansible常见模块的运用 一:Ansible简介二:ansible 环境安装部署管理端安装 ansibleansible 目录结构配置主机清单配置密钥对验证 三:ansible 命令行模块1.command 模块在远程主机执行命令,不支持管道,重定向…...

合宙Air724UG LuatOS-Air script lib API--patch
patch Table of Contents patch patch.safeJsonDecode(s) (local函数 无法被外部调用) patch 模块功能:Lua补丁 patch.safeJsonDecode(s) (local函数 无法被外部调用) 封装自定义的json.decode接口 参数 名称 传入值类型 释义 s string json格式的字符串 返回值 t…...

pytorch求导
pytorch求导的初步认识 requires_grad tensor(data, dtypeNone, deviceNone, requires_gradFalse)requires_grad是torch.tensor类的一个属性。如果设置为True,它会告诉PyTorch跟踪对该张量的操作,允许在反向传播期间计算梯度。 x.requires_grad 判…...

Java基础异常详解
Java基础异常详解 文章目录 Java基础异常详解编译时异常(Checked Exception):运行时异常(Unchecked Exception): Java中的异常是用于处理程序运行时出现的错误或异常情况的一种机制。 异常本身也是一个类。 异常分为…...

vue3+vue-i18n 监听语言的切换
最近在用 vue3 做一个后台管理系统,之前是只考虑中文,现在加了个需求是多语言。 本来也不是太难的需求,但是我用的并不熟悉,并且除了页面展示不同的语言,需求是在切换语言的时候在几个页面中需要做出一些自定义的行为&…...

【考研复习】24王道数据结构课后习题代码|2.3线性表的链式表示
文章目录 总结01 递归删除结点02 删除结点03 反向输出04 删除最小值05 逆置06 链表递增排序07 删除区间值08 找公共结点09 增序输出链表10 拆分链表--尾插11 拆分链表--头插12 删除相同元素13 合并链表14 生成含有公共元素的链表C15 求并集16 判断子序列17 判断循环链表是否对称…...

娇滴滴的一朵花(Python实现)
目录 1 娇滴滴的她 2 Python代码实现 1 娇滴滴的她 娇滴滴。双眉敛破春山色。春山色。 为君含笑,为君愁蹙。多情别後无消息。 此时更有谁知得。谁知得。夜深无寐,度江横笛。 2 Python代码实现 import turtle from turtle import * turtle.title(春天送她一朵小花)…...

Android AccessibilityService研究
AccessibilityService流程分析 AccessibilityService开启方式AccessibilityService 开启原理 AccessibilityService开启方式 . 在Framework里直接添加对应用app 服务component。 loadSetting(stmt, Settings.Secure.ACCESSIBILITY_ENABLED,1); loadSetting(stmt, Settings.Se…...

华为OD机试(含B卷)真题2023 算法分类版,58道20个算法分类,如果距离机考时间不多了,就看这个吧,稳稳的
目录 一、数据结构1、线性表2、优先队列3、滑动窗口4、二叉树5、并查集6、栈 二、算法1、基础算法2、字符串3、图4、动态规划5、数学 三、漫画算法2:小灰的算法进阶参与方式 很多小伙伴问我,华为OD机试算法题太多了,知识点繁杂,如…...

JMeter命令行执行+生成HTML报告
1、为什么用命令行模式 使用GUI方式启动jmeter,运行线程较多的测试时,会造成内存和CPU的大量消耗,导致客户机卡死; 所以一般采用的方式是在GUI模式下调整测试脚本,再用命令行模式执行; 命令行方式支持在…...

学习Boost二:从附录3来看编码习惯
附录C 关键字浅谈 在C11标准中(C11.2.12)总共定义了73个关键字(keyword)、2个“准”关键字(identifiers with special meaning)和11个操作符替代字(alternative representation)[1]。…...

STM32基础入门学习笔记:核心板 电路原理与驱动编程
文章目录: 一:LED灯操作 1.LED灯的点亮和熄灭 延迟闪烁 main.c led.c led.h BitAction枚举 2.LED呼吸灯(灯的强弱交替变化) main.c delay.c 3.按键控制LED灯 key.h key.c main.c 二:FLASH读写程序(有…...

最后一次模拟考试题解
哦我想这不用看都知道是为了水任务 T1 黑白染色 其实这题有原 什么手写体 md (指 markdown) 分析 首先这题如果你题目没看错的话 ,会发现其实他是 n m n \times m nm 让你求 n n n \times n nn 的区域内的点(不会只有我一个人题目看错了罢 然后我们会发现…...

Mac 创建和删除 Automator 工作流程,设置 Terminal 快捷键
1. 创建 Automator 流程 本文以创建一个快捷键启动 Terminal 的自动操作为示例。 点击打开 自动操作; 点击 新建文稿 点击 快速操作 选择 运行 AppleScript 填入以下内容 保存名为 “Open Terminal” 打开 设置 > 键盘,选择 键盘快捷键 以此选择 服…...

2023华为OD机试真题B卷 Java 实现【最长的元音串】
前言 本题使用Java解答,如果需要Python代码,链接 题目 给定一个只由英文字母(a-z, A-Z)组成的字符串,找出其中最长的只包含元音字母(a, e, i, o, u, A, E, I, O, U)的子串,并返回其长度。如果不存在元音子串,则返回0。 输入: 一个由英文字母组成的字符串,长度大…...

网络防御之传输安全
1.什么是数据认证,有什么作用,有哪些实现的技术手段? 数据认证是一种权威的电子文档 作用:它能保证数据的完整性、可靠性、真实性 技术手段有数字签名、加密算法、哈希函数等 2.什么是身份认证,有什么作用,有哪些…...