本文旨在以最小的篇幅,最少的信息,介绍最高频使用的内容,从而掌握C++编程开发的能力。
这种能力,只是语法层面,不涉及具体的函数库,基础库等内容。
能力准备:需要C语言基础。基本的if else, while,基础数据类型等等,不在本文涉及的范围之内。
环境准备
感谢微软的努力,让我们在 Windows环境下可以毫无障碍的进行Linux开发。
推荐使用Windows + wsl2 的环境开发。
打开Microsoft Store,搜索ubuntu,安装最新版本即可。
安装如有问题,请自行百度
使用 vscode + wsl插件的形式,编辑、编译代码。ctrl + ` 可以在命令行和文件编辑之间切换,非常的方便。
在ubuntu下,安装cmake, gcc, g++。安装方法自行百度。
代码的组织结构
- build : 编译目录
- docs : 文档目录,负责存放该代码相关的信息
- libs : 该项目依赖的外部的库及头文件
- libs/include 依赖库的头文件
- libs/[编译器名称] 平台相关的库文件。比如cc放x86-64位的库,arm-linux-gnu-gcc放该编译器编译出的相关的库文件
- source 项目源码目录
- test 测试目录,内含测试代码
复制代码
本文中所有涉及到的示例代码,可在此下载:
链接:https://pan.baidu.com/s/1f73k5uxYTRgtMEORbvgqvA?pwd=tnje
提取码:tnje
编译方法:
该代码展示了一个功能库的目录结构,编译方法。
如果想要做成可执行程序,参考test目录中的内容即可。
类的基本规则
通常定义一个类,我们会分为源文件.cpp,和头文件.h分开来用。
如下为头文件。其中的注释请仔细阅读。
- // rtspc.h
- /**
- * @author
- * @brief rtsp客户端
- * @version 0.1
- * @date 2023-11-30
- *
- * @copyright Copyright (c) 2023
- *
- */
- // 使用pragma once 让头文件只引用一次。与下面的 _RTSPC_H_ 作用一致
- #pragma once
- // 头文件避免重复引用。与#pragma once 二选一
- #ifndef _RTSPC_H_
- #define _RTSPC_H_
- #include <string>
- #include <functional>
- // 注意,原则上禁止在头文件中使用using namespace xxx。避免命名空间失效
- // 实际上,不管源文件和头文件,都不建议using namespace的方式。而是直接写全。
- // using namespace std;
- // 这是命名空间。可以有效隔离类,函数,变量的重名问题。在定义库时都建议添加使用。
- namespace rtsp
- {
- class Rtspc
- {
- // 公共方法,类外部可访问
- public:
- // 回调函数新写法。对应lambda表达式使用
- using OnData = std::function<void(const char *data, int len)>;
- Rtspc(bool btcp, OnData onData);
- // 注意,如果该类会被继承,则务必将它写成虚函数。否则影响析构
- virtual ~Rtspc();
- // 建议安装doxygen插件,在函数上方输入 /// 或者 /** 自动生成注释模板。
- /// @brief 公共方法,大写开头。私有方法,小写开头(代码规范,自行约定)。
- /// @param url
- /// @param bTCP
- /// @return
- int Run(const std::string &url, bool bTCP);
- /// @brief 停止
- /// @return
- void Stop(){
- _running = false;
- }
- // 保护方法。类内及类的子类可访问。
- protected:
- // 对于不改变类的内容的方法,后面加const
- // 对于不希望被改变的返回的引用,前面加const
- const std::string &getValue() const {return _url;}
- // 私有方法。通常它和私有成员的private分开写,更清晰一些。
- private:
- void workthread();
- // 私有成员。成员变量通常为私有成员
- private:
- std::string _url; // 成员变量以 '_' 开头,以便代码中与局部变量,参数做区分。
- bool _btcp;
- bool _running;
- OnData _onData;
- };
- #endif //_RTSPC_H_
- } // namespace rtsp
复制代码
源文件长这样。
- // rtspc.cpp
- #include <iostream>
- #include <chrono>
- #include <thread>
- #include "rtspc.h"
- namespace rtsp
- {
- // 构造函数
- Rtspc::Rtspc(bool btcp, OnData onData)
- // 这下面是类成员初始化的写法。据说比写在大括号里效率要高
- :_btcp(btcp)
- ,_running(true)
- ,_onData(onData)
- {
-
- }
- Rtspc::~Rtspc()
- {
- }
- int Rtspc::Run(const std::string &url, bool bTCP)
- {
- std::cout << "Running " << url << std::endl;
- const std::string data = "haha, i am data";
- while (_running)
- {
- std::this_thread::sleep_for(std::chrono::milliseconds(400));
- _onData(data.c_str(), data.size());
- }
-
- return 0;
- }
- } // namespace rtsp
复制代码
继承与虚函数
干货都在代码中
- //rtp-pack.h 这是父类
- #pragma once
- #include <memory>
- #include <functional>
- namespace rtsp
- {
- class RtpPack
- {
- public:
- // 回调打包好的数据
- using OnRtpData = std::function<void(const std::string &rtp)>;
- using Ptr = std::shared_ptr<RtpPack>;
- RtpPack(OnRtpData onRtp)
- :_onRtp(onRtp)
- {}
- // 注意,如果该类会被继承,则务必将它写成虚函数。否则影响析构
- virtual ~RtpPack(){}
- virtual int Pack(const uint8_t *data, int len) = 0;
- /// @brief 创建打包器
- /// @param encode 编码方式
- /// @return
- static Ptr CreatePacker(const char *encode, OnRtpData onData);
- protected:
- OnRtpData _onRtp;
- };
- } // namespace rtsp
复制代码
- // rtp-pack-h264.h 这是子类
- #pragma once
- #include <rtp/rtp-pack.h>
- namespace rtsp
- {
- class RtpPackH264 : public RtpPack
- {
- private:
- /* data */
- public:
- RtpPackH264(RtpPack::OnRtpData onRtp)
- :RtpPack(onRtp)
- {}
- virtual ~RtpPackH264(){}
- virtual int Pack(const uint8_t *data, int len) override
- {
- std::string rtp;
- rtp.append("begin flag");
- rtp.append((const char *) data, len);
- _onRtp(rtp);
- return 0;
- }
- // 非虚函数
- int Demo()
- {
- return 0;
- }
- };
- } // namespace rtsp
复制代码
如代码所示,RtpPackH264为子类,它继承于RtpPack
虚函数
其中,Pack 在RtpPack中,被定义为纯虚函数。这意味着你无法将RtpPack实例化。
也就是说,RtpPack pack; 是非法的。
只有在子类中实现了 Pack方法,就像RtpPackH264 一样,它才能够被实例化。
std::function 与lambda表达式
它是C++11开始支持的好东西,它有两个作用:
1,替代回调函数
2,替代回调函数的 param回传参数。
最重要的是第二点,结合lambda函数使用,让我们的代码看起来是如此的与众不同。
请参看如下示例。
testrtp.cpp
- #include <rtp/rtp-pack-h264.h>
- #include <sys/types.h> /* See NOTES */
- #include <sys/socket.h>
- int main()
- {
- int sock = socket(AF_INET, SOCK_STREAM, 0);
- connect(sock, xxx);
- // std::function 的定义与 lambda的应用
- rtsp::RtpPackH264 rtp([sock](const std::string &rtp){
- send(sock, rtp.c_str(), rtp.size());
- });
-
- while (1)
- {
- rtp.Pack("123456", 6);
- }
- return 0;
- }
复制代码
可以看到,RtpPackH264 rtp 在实例化的时候,传的参数是一个奇怪的东西:[sock](const std::string &rtp){xxx
这个奇怪的东西,叫作lambda表达式。也叫匿名函数。
[]内部,就相当于我们注册回调函数时,注册进去的param,它通过回调函数再传回给我们。
而这里则不需要这么麻烦,你可以在[ ] 中加入任意多的变量,然后就如代码中的sock一样,在lambda体中使用。
需要注意的是,[sock]这是值传递的写法。它会记录sock的值。还可以这样写:[&sock],引用传递。此时需要注意,它相当于记录了sock的指针。
这里还有另一种写法,可以将lambda表达式写成一个变量:
- auto onRtp = [sock](const std::string &rtp){
- send(sock, rtp.c_str(), rtp.size());
- };
- rtsp::RtpPackH264 rtp(onRtp);
-
复制代码
小提示:
本节中的代码,没有源文件。类的定义与实现,可以都写在头文件中,只不过这要看实际情况而写。
它的缺点是编译、链接较慢,封装性差。
但有些时候,比如模板,必须写在头文件中。
类的本质是什么?
C++中的类,命名空间,虚实函数,本质上都可以用C来表达。或者换个说法,C++编译器最终会把它变成C语言那样的东西。
就拿RtpPackH264来讲,它在编译器处理后,变成了如下的东西。至于C++的各种特性,都是语法糖。
- rtp-pack-h264.c
- #include <stdlib.h>
- #include <stdint.h>
- typedef void (*rtpCallback_t)(void *userparam, const uint8_t *data, int len);
- struct rtpH264_class{
- // 类成员变量
- rtpCallback_t _onRtp;
- void *_userparam;
-
- // 虚函数表
- struct rtpH264VirtualFunctionTable{
- int (*Pack)(struct rtpH264_class *thiz, const uint8_t *data, int len);
- }functionTable;
- };
- // 虚函数的实现,对应rtsp::RtpPackH264::Pack
- // 注意这奇怪的名字,param之后,列出了参数类型。这就是为什么C++允许重名但参数不同的函数。
- int rtsp_RtpPackH264_Pack_param_u8_i32(struct rtpH264_class *thiz, const uint8_t *data, int len)
- {
- return 0;
- }
- // 构造函数,生成对象时自动调用。无论是new,还是局部变量
- struct rtpH264_class *rtsp_RtpPackH264_RtpPackH264_param_rtpCallback_t_void(rtpCallback_t onRtp, void *userparam){
- // 分配内存
- struct rtpH264_class *rtp = malloc(sizeof(struct rtpH264_class));
- // 构造虚函数表
- rtp->functionTable.Pack = rtsp_RtpPackH264_Pack_param_u8_i32;
- rtp->_onRtp = onRtp;
- rtp->_userparam = userparam;
- return rtp;
- }
- // 析构函数。在对象生命周期结束时,自动调用
- void rtsp_RtpPackH264_del_RtpPackH264(struct rtpH264_class *rtp)
- {
- free(rtp);
- }
- // 非虚函数的实现
- int rtsp_RtpPackH264_Demo(struct rtpH264_class *thiz)
- {
- return 0;
- }
复制代码
以上代码中可以看到,类的函数的名字,实际上是由“命名空间+类名+方法名+参数类型”以一定规则,形成的。
而构造函数,实际上是编译器在生成对象是,帮我们调用的。
虚函数表,是在构造函数中指向了各个实际的函数。(不准确,但按此理解无不利影响)
敲黑板
所以,非虚函数,是在编译时就确定了调用关系的。比如调用RtpPackH264:
emo,是在编译时就确定了要调这个函数。
虚函数,是在执行时,查表,确定虚函数表中,指向的是哪个函数,从而完成调用。
请仔细研读,对照上述c实现的类代码,与类本身的关系。
请思考如下代码,最终输出的是什么?
- class A{
- public:
- void running(){printf("A running\n");}
- virtual void VirtualFunc(){printf("A virtual func\n");}
- };
- class B: public A{
- public:
- void running(){printf("B running\n");}
- virtual void VirtualFunc(){printf("B virtual func\n");}
- };
- void main()
- {
- B b;
- A *a = &b;
- b.running();
- a->running();
- b.VirtualFunc();
- a->VirtualFunc();
- }
复制代码
搜索一下隐藏和覆盖,看看网上五花八门的解释,对照我们把C++的类改成C的写法,你能明白隐藏和覆盖是咋回事了吗?
还有lambda,std::function。
我们将lambda以基础的C++类的方式来实现,它是这样的:
rtp-pack-lambda.hpp (由 rtp-pack 的无lambda写法)
- /**
- * @author LiuFengxiang (20451250@qq.com)
- * @brief 以C++的类模拟 lambda,捕获等行为
- * @version 0.1
- * @date 2023-12-01
- *
- * @copyright Copyright (c) 2023
- *
- */
- #pragma once
- #include <string>
- namespace lambdaTest
- {
- // 代替 std::function<void(const std::string &rtp)>
- // std::function<xxx> 实际上是模板实例化成了一个类,这个类会记录函数指针和lambda捕获的变量
- class RtpPackFunc
- {
- public:
- typedef void (*OnRtpData)(RtpPackFunc *thiz, const std::string &rtp);
- RtpPackFunc(OnRtpData data):_onRtp(data){}
- virtual ~RtpPackFunc(){}
- void Call(const std::string &rtp){
- _onRtp(this, rtp);
- }
- public:
- OnRtpData _onRtp;
- };
- class RtpPack
- {
- public:
- RtpPack(RtpPackFunc *callback)
- :_callback(callback)
- {}
- ~RtpPack(){}
- void Pack(){
- _callback->Call("haha");
- }
- private:
- RtpPackFunc *_callback;
- };
- } // namespace lambdaTest
复制代码
它的测试代码:testrtp-lambda.cpp, (由testrtp.cpp转化而来)
- /**
- * @author LiuFengxiang (20451250@qq.com)
- * @brief 对应testrtp.cpp,我们不使用lambda,而是改用基础的类来实现
- * @version 0.1
- * @date 2023-12-01
- *
- * @copyright Copyright (c) 2023
- *
- */
- #include <sys/types.h> /* See NOTES */
- #include <sys/socket.h>
- #include <unistd.h>
- #include <rtp/rtp-pack-lambda.hpp>
- using namespace lambdaTest;
- // 实际的实现并不相同,但是这样写起来优雅一点儿,也并不妨碍理解。
- class MyRtpPackFunc :public RtpPackFunc
- {
- public:
- MyRtpPackFunc(RtpPackFunc::OnRtpData data, int sock)
- :RtpPackFunc(data)
- ,_sock(sock)
- {}
- virtual ~MyRtpPackFunc(){}
- public:
- int _sock;
- };
- // 编译器将lambda表达式生成了回调函数
- static void MyOnRtpData(RtpPackFunc *func, const std::string &rtp)
- {
- printf("%s running, data: %s\n", __func__, rtp.c_str());
- MyRtpPackFunc *mine = dynamic_cast<MyRtpPackFunc *> (func);
- if (mine != nullptr)
- {
- // 不能真发,没准备好呢
- if (0)
- send(mine->_sock, rtp.c_str(), rtp.size(), 0);
- }
- }
- int main()
- {
- int sock = socket(AF_INET, SOCK_DGRAM, 0);
- // connect(sock, xxx);
- /*在使用lambda时,编译器干了很多事情:
- 1,将匿名函数以自有的规则命名(objdump可以看一下,巨长),这里是 MyOnRtpData
- 2,将 std::function模板实例化,相当于 MyRtpPackFunc
- 3,将实例化的类生成对象,也就是这里的 func, 并传入初始化的两个参数: MyOnRtpData, sock
- */
- MyRtpPackFunc *func = new MyRtpPackFunc(MyOnRtpData, sock);
- // RtpPack记录的,实际上就是 std::function 的对象: func
- RtpPack rtp(func);
-
- while (1)
- {
- rtp.Pack();
- usleep(1*1000*1000);
- }
- }
复制代码
为了简化写法,我们帮编译器翻译的并不精确,但这并不妨碍理解。
你就记住:lambda表达式,就是编译器帮你起名的匿名函数。而std::function 则是编译器帮你生成的类。
所谓捕获,同样没什么神奇之处,值捕获,在类中直接记录了该变量的值,引用捕获,则是在类中记录了该类的指针。
思考:值捕获和引用捕获的变量,它们的生命周期是怎样的?
本节是想告诉你,C++的很多规则,并不是人为制订出来的,而是语言本身的实现上,必须这么做。它的因果关系是:因为这门语言是这样设计的,所以,产生了这样的规则。
本节只是个引子,借此提示。
多思考!
多思考!!
多思考!!!
多思考背后的机理,那才能举一反三,抓住本质。
记住这句话:C++的所有规则,都是因为设计时,只能这样做。
最常用容器
std::string 其实也是容器。但是我们把它当成一个普通类用就好了
vector 是数组容器。用来管理数量不定的同类型的内容
map 相当于一个映射表,key,value的形式。通过key可以快速的查找到对应的值。
如下代码展示了一些常用的函数,更详细内容查文档:DevDocs
- /**
- * @author LiuFengxiang (20451250@qq.com)
- * @brief 介绍常用容器的用法
- * @version 0.1
- * @date 2023-12-01
- *
- * @copyright Copyright (c) 2023
- *
- */
- #include <vector>
- #include <string>
- #include <map>
- #include <iostream>
- #include <memory> // 智能指针相关
- class Node
- {
- public:
- // 智能指针的技巧。简化写法
- // 使用的时候 Node::Ptr p 相当于 std::shared_ptr<Node> p
- using Ptr = std::shared_ptr<Node>;
- Node(int val, const std::string &str)
- :_val(val)
- ,_str(str){}
- virtual ~Node(){}
- const std::string &Str()const {return _str;}
- int Val()const {return _val;}
- void SetVal(int val){_val = val;}
- private:
- int _val;
- std::string _str;
- };
- static void vectorDemo()
- {
- // 使用智能指针代替Node * 或者 直接Node。
- // 如果用指针,在释放时必须逐个 delete。一但遗漏就会有内存泄漏
- // 如果直接用Node,则每次加入时都会产生拷贝动作。
- std::vector<Node::Ptr> vec;
- for (int i = 0; i < 10; i++)
- {
- auto node = std::make_shared<Node>(i, "haha");
- vec.push_back(node);
- }
-
- // 遍历,并按条件删除
- for (auto it=vec.begin();it!= vec.end();)
- {
- if ((*it)->Val() == 5)
- {
- // 删除成员时,不能直接it++
- it = vec.erase(it);
- }
- else
- {
- it++;
- }
- }
- // 另一种遍历方式
- for (auto &&it : vec)
- {
- printf("node val: %d, str: %s\n", it->Val(), it->Str().c_str());
- it->SetVal(it->Val()+1);
- }
- // 注意,如果是 std::vector<Node *> vec, vec.clear() 执行时,并不能自动对每个node做delete动作。
- // 所以此时,你需要先逐个 delete ,再行 clear
- vec.clear();
- }
- // map 主要是为了快速通过key 来找到对应的内容。key可以是基础类型,string
- // 如果要把自定义的类作为key,则需要自定义比较函数
- void mapDemo()
- {
- std::map<int, Node::Ptr> _map;
- for (int i = 0; i < 10; i++)
- {
- auto node = std::make_shared<Node>(i, "haha");
- _map.emplace(i, node);
- }
- // 查找
- int key = 5;
- auto it = _map.find(key);
- if (it != _map.end())
- {
- std::cout << "we Found it, key: " << it->first << ", str: " << it->second->Str() << std::endl;
- }
- else
- {
- std::cout << "We Failed found with key: " << key << std::endl;
- }
-
- // 遍历,并按条件删除
- for (auto it=_map.begin();it!= _map.end();)
- {
- if (it->first == 5)
- {
- // 删除成员时,不能直接it++
- it = _map.erase(it);
- }
- else
- {
- it++;
- }
- }
- // 另一种遍历方式
- for (auto &&it : _map)
- {
- std::cout << "node key: " << it.first << ", val: " << it.second->Str() << std::endl;
- it.second->SetVal(it.second->Val()+1);
- }
- }
- int main()
- {
- vectorDemo();
- mapDemo();
- return 0;
- }
复制代码
智能指针的使用
智能指针,是现代C++编程非常重要的一个特性。
实际上,有了智能指针之后,我们不应该再使用裸指针了。
下面罗列几个主要的使用场景:
1,配合容器使用
比如有一个类Car,它有很多成员。
如果定义std::vector _carvec,它的问题是:
Car需要可拷贝,有可能需要实现拷贝构造函数
Car是拷贝了多份的,是独立的。它们之间互相完全无关。
而如果使用指针 std::vector _pcarvec。
那你需要注意的是:插入前要new Car, 擦除前要先 delete 成员。
最容易忘的是_pcarvec.clear(). 这个方法执行前,你需要先遍历,逐个delete car
此时,更方便的用法是:std::vector> _shCarVec;
2,回调函数中使用weak_ptr
(weak_ptr的概念可以先百度一下。)
回调函数有个比较大的问题是,当回调上来之后,数据的消费者可能已经被销毁了。这时我们的指针,是否还生效?如何判断?
如下代码中,rtspc的回调,数据上来之后,窗口是否还存在?
这里,我们通过保存它的weak_ptr句柄,使用时,通过lock的形式来处理。
只要lock成功了,weak_ptr将会升级成为强引用,说明对象还在,我们就可以正常输入数据。
- //rtspclient.cpp
- #include <memory>
- #include <map>
- #include <vector>
- #include <mutex>
- #include <thread>
- #include <rtspc/rtspc.h>
- // 解码窗口
- class MyWindow
- {
- public:
- using Ptr = std::shared_ptr<MyWindow>;
- MyWindow(int winid)
- :_winid(winid)
- {}
- ~MyWindow(){
- printf("window %d destroyed\n", _winid);
- }
- int InputMediaData(const char *data, int len){
- // printf("input data in window: %d\n", _winid);
- return 0;
- }
- private:
- int _winid;
- };
- class WindowMgr
- {
- private:
- WindowMgr(/* args */){}
- ~WindowMgr(){}
- public:
- // 单例
- static WindowMgr& Instance(){
- static WindowMgr _inst;
- return _inst;
- }
- void SetWindowCnt(int cnt){
- std::lock_guard<std::mutex> guard(_mutex);
- // 注意哦,这里窗口重建了
- if ((size_t)cnt != _windows.size())
- {
- _windows.clear();
- // 延时,扩大rtspc上回调时窗口销毁的概率
- std::this_thread::sleep_for(std::chrono::milliseconds(1000));
- for (int i = 0; i < cnt; i++)
- {
- _windows.push_back(std::make_shared<MyWindow>(i));
- }
- }
- }
- MyWindow::Ptr GetWindow(int winid){
- std::lock_guard<std::mutex> guard(_mutex);
- if ((size_t)winid >= _windows.size())
- {
- return nullptr;
- }
- return _windows[winid];
- }
- private:
- std::vector<std::shared_ptr<MyWindow> > _windows;
- std::mutex _mutex;
- };
- static void PlayInWindow(int winid, const char *url){
- std::weak_ptr<MyWindow> weak = WindowMgr::Instance().GetWindow(winid);
- rtsp::Rtspc rtspc(true, [&rtspc, weak](const char *data, int len){
- std::shared_ptr<MyWindow> strongPtr = weak.lock();
- if (strongPtr == nullptr)
- {
- printf("window destroyed, exit rtspc\n");
- rtspc.Stop();
- }
- else
- {
- strongPtr->InputMediaData(data, len);
- }
- });
- rtspc.Run(url, true);
- }
- static void SetWindowCnt(int winCnt)
- {
- printf("Now win cnt: %d\n", winCnt);
- WindowMgr::Instance().SetWindowCnt(winCnt);
- for (int i = 0; i < winCnt; i++)
- {
- std::thread([i](){
- char url[256];
- snprintf(url, sizeof(url), "rtsp://192.168.1.2:554/live/chn%d", i);
- PlayInWindow(i, url);
- }).detach();
- }
- }
- int main(int argc, const char *argv[])
- {
- int count = 4;
- while (true)
- {
- SetWindowCnt(count);
- getchar();
- count++;
- }
- return 0;
- }
复制代码
可能还有同学有疑问,如果strongPtr拿到之后,在InputMediaData执行之前,发生了窗口切换怎么办呢?
这完全无须担心,由于我们已经持有了window的强引用,此时它并不会被销毁。只有等我们InputMediaData执行完之后,rtspc的回调函数执行完,strongPtr的生命周期完结,此时智能指针的计数清零,MyWindow才会得到释放。
3,类成员指针
类成员指针,它的重建,需要先delete老的。析构时,也需要析构。
而使用了智能指针,这些工作都不需要做了
如下代码中,智能指针_packer,构造函数中的创建,ChangePacker函数中把它重新赋值,都不需要考虑销毁。因为智能指针会自动析构老的内容。
同时,~Rtsps()析构函数执行时,也不需要手工析构_packer。
- /**
- * @author LiuFengxiang (20451250@qq.com)
- * @brief rtsp 服务端
- * @version 0.1
- * @date 2023-12-9
- *
- * @copyright Copyright (c) 2023
- *
- */
- #pragma once
- #include <string>
- #include <rtp/rtp-pack.h>
- namespace rtsp
- {
- class Rtsps
- {
- public:
- Rtsps(/* args */){
- _packer = RtpPack::CreatePacker("H264", [this](const std::string &rtp){
- onRtpData(rtp);
- });
- }
- // 注意,如果该类会被继承,则务必将它写成虚函数。否则影响析构
- virtual ~Rtsps(){}
- /// @brief 更换打包器
- /// @param encode 打包器名称
- void ChangePacker(const char *encode){
- _packer = RtpPack::CreatePacker(encode, [this](const std::string &rtp){
- onRtpData(rtp);
- });
- }
- int Run(){
- return 0;
- }
- private:
- void onRtpData(const std::string &rtp){
- printf("onRtpData\n");
- }
- private:
- // 打包器的句柄。
- RtpPack::Ptr _packer;
- };
-
- } // namespace rtsp
复制代码