关于c++:ACM博弈论SG函数入门1从巴什博奕到尼姆游戏

在我小时候以前做题的时候,遇到博弈题往往都是漫无目的地打表找法则,或者找一些非凡状况然而没有很好的分析方法。 其实博弈题是有比拟套路的解题办法的,那就是利用SG函数,第一节不会讲到SG函数的具体用法,咱们先来博弈入个门,学习一下最根本的博弈类型:Nim游戏。 作者:Eriktse 简介:19岁,211计算机在读,现役ACM银牌选手力争以通俗易懂的形式解说算法!❤️欢送关注我,一起交换C++/Python算法。(优质好文继续更新中……) 浏览原文取得更好浏览体验:https://www.eriktse.com/algorithm/1110.html巴什博奕在进入Nim游戏之前,咱们先看一个简略的博弈:巴什博奕。 看这道例题:http://acm.hdu.edu.cn/showproblem.php?pid=1846 因为HDUOJ常常打不开,我这里复制一下题意: 1、 本游戏是一个二人游戏;2、 有一堆石子一共有n个;3、 两人轮流进行;4、 每走一步能够取走1…m个石子;5、 最先取光石子的一方为胜;如果游戏的单方应用的都是最优策略,请输入哪个人能赢。假如此时的n = 10, m = 3,咱们来剖析一下这个场面。 咱们设一个布尔函数f(x),示意当n = x时的输赢。 f(0) = 0,因为无奈持续操作了,显然是0,也就是说这是一个必败态。f(1) = 1,能够取走一个石子,使对手陷入必败态,所以这是一个必胜态。f(2) = 1,能够取走两个。f(3) = 1,能够取走三个。f(4) = 0,无论取走一个、两个或三个都必然使得对手进入必胜态,所以这是一个必败态。f(5) = 1f(6) = 1f(7) = 1f(8) = 0,无论取走一个、两个或三个都必然使得对手进入必胜态,所以这是一个必败态。f(9) = 1f(10) = 1至此,咱们失去了答案,f(n) = f(10) = 1所以先手必胜,不难发现下面这个函数f(x)的法则,仅当x % 4 == 0时为0,其余状况都为1。 于是咱们只须要判断n % (m + 1)是否为0就能判断先手是否获胜。 这就是巴什博奕模型,是不是很简略,咱们简略打个表就找到了法则。 Nim游戏仍然看这道例题:https://www.luogu.com.cn/problem/P2197 咱们这么剖析: 败局肯定是全为0的状况,此时所有数字的异或和为0(不晓得怎么写异或和的latex,大家凑合着看): $$\oplus_{i=1}^{n}a_i = 0$$ 那么想一下那些状态能够到这个败局呢?咱们反向的思考,往任意一个地位加上一个数字,就能够作为前一个状态,也就是一个必胜态(因为那个状态必然能够达到败局的状态)。 往任意一个地位加上一个数字之后就必然有: $$\oplus_{i=1}^{n}a_i \ne 0$$ ...

April 10, 2023 · 1 min · jiezi

关于c++:牛客小白月赛70AF题解小d和超级泡泡堂小d和孤独的区间小d的博弈小d和送外卖

较量传送门:https://ac.nowcoder.com/acm/contest/53366 难度适中。 作者:Eriktse 简介:19岁,211计算机在读,现役ACM银牌选手力争以通俗易懂的形式解说算法!❤️欢送关注我,一起交换C++/Python算法。(优质好文继续更新中……) 浏览原文取得更好浏览体验:https://www.eriktse.com/algorithm/1109.htmlA - 小d和答案批改Tag:签到 略。 Code: #include <bits/stdc++.h>#define int long longusing namespace std;const int N = 1e5 + 9;char s[N];signed main(){ cin >> s + 1; for(int i = 1; s[i]; ++ i) { if('a' <= s[i] && s[i] <= 'z')printf("%c", s[i] - 'a' + 'A'); else printf("%c", s[i] - 'A' + 'a'); } return 0;}B - 小d和图片压缩Tag:签到 略。 Code: #include <bits/stdc++.h>#define int long longusing namespace std;const int N = 1e3 + 9;int a[N][N];signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n, m;cin >> n >> m; for(int i = 1;i <= n; ++ i) for(int j = 1;j <= m; ++ j) cin >> a[i][j]; for(int i = 1;i <= n; i += 2) { for(int j = 1;j <= m;j += 2) { int sum = a[i][j] + a[i + 1][j] + a[i][j + 1] + a[i + 1][j + 1]; cout << sum / 4 << ' '; } cout << '\n'; } return 0;}C - 小d和超级泡泡堂Tag:dfs,联通块 ...

April 8, 2023 · 4 min · jiezi

关于c++:C统计文件内给定关键词

博主刚开始学习c++,前段时间老师安排了c++的一个作业: 给定两个文件(一个源文件text4search.txt,一个文件keywords.txt蕴含须要在源文件中搜寻的关键词),要求输入keywords.txt中每个关键词在源文件中呈现的行号。举个例子,如果keywords.txt中有一个关键词是c++,在text4search.txt中第1,7,9,43,543,586,2445行都呈现了c++,那么应该输入c++:{1 7 9 43 543 586 2445 }。 先上完整版代码(代码已在vscode c++11规范运行胜利) #include <algorithm>#include <fstream>#include <iostream>#include <map>#include <sstream>#include <vector>using namespace std;// 报错函数void error(const char *p, const char *p2 = "") { std::cerr << p << ' ' << p2 << std::endl; std::exit(1);}map<string, vector<int>> keyCount(ifstream &inputFile, vector<string> &keywords) { map<string, vector<int>> resultMap; string line; int lineNum = 0; while (getline(inputFile, line)) { ++lineNum; // 读入文件的每一行 std::istringstream sin(line); string word; //每行单词一一查看是不是在keywords外面呈现,而不是统计keyword在一行中呈现的次数 //这样不必独自写一个函数统计一行中某个关键词呈现次数,也可能呈现一次关键词就压入行号一次 while (sin >> word) { auto it = find(keywords.begin(), keywords.end(), word); if (it != keywords.end()) { resultMap[word].push_back(lineNum); } } } return resultMap;}int main() { // 建设三个文件流对象并关联相应文件,留神这里大家要批改为本人的文件门路(或者应用命令行输出) std::ifstream fin1; fin1.open("D:/University/Code/cpp_vscode/lab4/keywords.txt"); // 和keywords.txt建设关联 if (!fin1) { error("cannot open input file", "D:/University/Code/cpp_vscode/lab4/keywords.txt"); } std::ifstream fin2; fin2.open("D:/University/Code/cpp_vscode/lab4/text2search.txt"); // 和text2search.txt建设关联 if (!fin2) { error("cannot open input file", "D:/University/Code/cpp_vscode/lab4/text2search.txt"); } std::ofstream fout; fout.open("D:/University/Code/cpp_vscode/lab4/result.txt"); // 和result.txt建设关联 if (!fout) { error("cannot open output file", "D:/University/Code/cpp_vscode/lab4/result.txt"); } // 将keywords存入keys这个字符串向量中(用fin1) vector<string> keys; string s; while (fin1 >> s) { keys.push_back(s); } fin1.close(); // 利用fin2和keys产生后果map map<string, vector<int>> result = keyCount(fin2, keys); fin2.close(); //输入,因为map会主动依照字典序排序,而行号也是依照升序逐行检测,因而输入不必再排序 map<string, vector<int>>::iterator it1 = result.begin(); while (it1 != result.end()) { fout << it1->first << " : " << "{"; //it1->second是vector<int>类型,不能间接用fout输入 for (const auto &it2 : it1->second) { fout << it2 << ","; } fout << "}" << endl; it1++; } fout.close(); system("pause"); return 0;}上面联合代码讲一讲我的思路。这道题的难点在于如何选取正确、高效的存储办法和搜寻办法:显然咱们不能简略的挨个读取text中的单词,找到一个关键词就输入一个行号——这样输入的行号是凌乱的。 ...

April 5, 2023 · 2 min · jiezi

关于c++:ACM数论和式变换技术也许是最好的讲解之一

在做数论题时,往往须要进行和式变换,而后变换成咱们能够解决的和式,再针对和式做筛法、整除分块等操作。 本文将介绍一些常见的和式变换技术。 以下呈现的概念大部分为集体总结,未必是学术界/比赛界的对立说法,有不谨严的中央请谅解。 作者:Eriktse 简介:19岁,211计算机在读,现役ACM银牌选手力争以通俗易懂的形式解说算法!❤️欢送关注我,一起交换C++/Python算法。(优质好文继续更新中……) 原文链接(浏览原文取得更好浏览体验):https://www.eriktse.com/algorithm/1101.html 和式的根本模式和式个别有两种:区间枚举型和整除枚举型。 区间枚举型咱们的前缀和就是一个典型的区间枚举型和式。 假如咱们有一个定义域为$x\in[1, n],x\in Z^+$的函数$f(x)$,那么咱们能够设一个前缀和函数$F(x)$,定义为: $$F(x) = \sum_{i=1}^{x}f(i) = f(1) + f(2) + ... + fx()$$ 求和符号中,如果没有非凡阐明,个别枚举的都是整数,且步长为1。整除枚举型约数个数是一个典型的整除枚举型和式,咱们能够容易的写出它的表达式: $$f(n) = \sum_{d|n}1$$ 其中 $d|n$ 示意 $i$ 能够整除 $n$ ,即 $i$ 是 $n$ 的因子。约数之和也是一个整除枚举型和式,表达式如下: $$g(n) = \sum_{d|n}d$$ 和式的根本性质可拆分性质第一种拆分如下: $$\sum_{i=1}^{n}a_i = \sum_{i=1}^{m}a_i + \sum_{i=m+1}^{n}a_i$$ 这是显然的,然而基本上用不着。 第二种拆分如下: $$\sum_{i=1}^{n}(a_i + b_i) = \sum_{i=1}^{n}a_i + \sum_{i=1}^{n}b_i$$ 这也是显然的。 常数可提取当咱们的和式外面乘上了一个常数$k$,那么这个常数是能够提出来的,因为咱们探讨的数域是整数域,这个$k$个别为整数。(其实对于实数也是满足条件的)。 $$\sum_{i=1}^{n}ka_i = k\sum_{i=1}^{n}a_i$$ 整除枚举型变换为区间枚举型(重要)就比方下面那个约数之和的函数: $$g(i) = \sum_{d|n}d = \sum_{i=1}^{n}[d|n]$$ 咱们晓得$d$的取值肯定在$[1, n]$,所以咱们能够转换枚举类型,此时枚举指标的范畴就要扭转,同时加上一个布尔函数来限定。 ...

April 2, 2023 · 2 min · jiezi

关于c++:C面试必备常见C面试题汇总及详细解析

C++作为一门重要的编程语言,其在面试中经常是热门的考查对象。本文将会介绍一些常见的C++面试题,帮忙C++面试者防止很多不必要的困惑和蛊惑。每个问题都有绝对应的答案,以便各位同学疾速查阅。 C++和C的区别是什么?C++是C的超集,也就是说,C++包含了C的所有根底个性,并且还减少了一些新的个性。上面列举一些C和C++之间的次要区别: 面向对象编程C++ 是一种面向对象的编程语言,而 C 不是。因而,C++ 反对类、继承、封装、多态等一系列面向对象的概念和个性,这些能力使 C++ 更加灵便和弱小。 规范库C++ 规范库比 C 规范库更加欠缺和弱小。C++ 规范库包含了很多容器类,如 vector、map、set 等,以及输入输出流、字符串解决等罕用性能。这些库函数能够在许多状况下进步开发效率。 命名空间C++ 引入了命名空间的概念,能够防止函数命名雷同的抵触。应用命名空间能够将代码依照逻辑分组,并更好地组织代码。 异样解决C++ 反对异样解决机制,这个机制能够加强程序的容错性和可靠性。当程序产生异样时,能够抛出异样并在可控范畴内进行解决,防止程序解体。而 C 不反对异样解决机制。 运算符重载C++ 容许对运算符进行重载,能够使得运算符在解决特定类型的数据时更具备描述性。而 C 不反对运算符重载。什么是指针? 指针是C++中的一种数据类型,指针变量存储了一个内存地址,该地址指向某个变量或者对象。指针能够用来拜访和批改内存中的数据,同时也能够通过指针来传递参数和返回值。对于C++程序员来说,精通指针的应用是十分重要的。 重写和重载的区别重写指的是在派生类中从新定义基类的虚函数的行为。当基类中的某个虚函数在派生类中被从新定义时,如果派生类对象调用该函数,则会笼罩掉基类中的实现,执行派生类中的实现代码。在进行对象的多态性转换时,重写十分重要。重载则指的是在同一个作用域内申明几个同名然而参数列表不同的函数。通过函数名雷同但参数类型、个数或程序的不同,能够让多个函数具备不同的行为。例如,C++ 中能够重载函数来解决不同类型的数据,如整数、浮点数等。在应用函数时,依据传递给函数的参数类型和个数来主动抉择对应的函数进行调用。因而,重写和重载的次要区别在于,重写是通过派生类从新定义基类虚函数的行为,以实现运行时多态性;而重载是在同一作用域内申明几个雷同名称的函数,以实现编译时多态性。 讲讲面向对象面向对象编程有3大个性: 封装:封装是指将对象的属性和办法绑定在一起,造成一个独立的、关闭的单元。内部只能通过对象提供的公共接口来拜访或操作对象的外部状态,而无奈间接拜访或批改对象的数据。这样能够保障对象的外部状态不受内部烦扰,从而进步了程序的安全性和可靠性,简化了代码的调用形式。继承:通过继承机制,一个类能够从另一个类中继承某些属性和办法,并在此基础上增加新的属性和办法,从而防止了反复编写代码的冗余,进步了代码的可重用性和可维护性。多态:多态是指同一个音讯能够被不同的对象解释执行,即不同的对象对同一音讯作出不同的响应。具体来说,多态能够通过虚函数和模板等机制实现。通过多态,能够使代码更加灵便、可扩大,同时也可能使程序更易读懂和保护。这三个个性是面向对象编程的外围,它们相互配合,独特组成了一个残缺的面向对象编程体系,可能无效地进步程序的可靠性、可重用性、可扩展性等方面。 什么是援用?援用也是C++中的一种数据类型,它提供了一种简洁而高效的形式来操作变量和对象,而不须要拷贝它们自身。援用被视为原变量的一个别名,其操作相似于指针,然而援用不能被赋值为NULL,也不能进行指针运算。 C/C++援用和指针的区别?指针是一个实体,须要分配内存空间;援用只是变量的别名,不须要分配内存空间。援用在定义的时候必须进行初始化,并且不可能扭转;指针在定义的时候不肯定要初始化,并且指向的空间可变。有多级指针,然而没有多级援用,只能有一级援用。指针和援用的自增运算后果不一样。 内联函数和一般函数有什么区别?内联函数和一般函数的区别在于是否进行了“内联优化”。内联函数是一种非凡的函数,编译器会在编译时将其整个函数体插入到调用该函数的中央,从而节俭了函数调用的开销。一般函数则须要在调用时进行函数栈的压栈和出栈操作,这样会带来额定的工夫和空间开销。因而,对于简略的函数或者频繁被调用的函数,咱们能够思考应用内联函数来进步程序的性能。 常量指针和指针常量?常量指针是指针指向的地址不能扭转,然而能够通过指针批改地址对应的值。而指针常量则是指针指向的地址能够扭转,然而不能通过指针批改地址对应的值。 在C语言中,常量指针的定义形式为const int* ptr,示意指向int类型的常量指针,指针所指向的地址不能扭转,然而能够通过指针批改地址对应的值。 而指针常量的定义形式为int* const ptr,示意指向int类型的指针常量,指针所指向的地址能够扭转,然而不能通过指针批改地址对应的值。 如果想要定义既不能批改地址,也不能批改地址对应的值的指针,能够应用const int* const ptr。 常量指针和指针常量的区别在于指针所指向的内容可不可变,须要依据具体情况而定。 P.S. 感兴趣的同学能够看我之前帖子,有具体介绍 如何防止野指针?野指针是指指向曾经被开释或者有效的内存空间的指针,这是 C++ 中常见的一个程序谬误。当咱们拜访野指针时,程序会呈现不可预期的行为,甚至解体。 为了防止野指针,咱们能够采取以下措施: 在指针应用前初始化在定义一个指针变量的时候,咱们应该立刻将其初始化为一个无效的地址。如果不能确定指针的初始值,能够将其初始化为 nullptr 或 0,防止野指针的产生。 int* p = nullptr; // 初始化为空指针在指针应用后及时置空当指针变量不再应用时,咱们应该将其置为空指针,避免误用。这样能够无效地防止产生野指针。 int* p = new int;*p = 10;delete p;p = nullptr; // 置空指针,防止野指针产生不要反复开释曾经开释的内存在开释指针所指向的内存空间之后,咱们应该将该指针赋值为 NULL 或 nullptr,以避免该指针被误用。 ...

March 31, 2023 · 1 min · jiezi

关于c++:ACM算法竞赛日常训练DAY5题解与分析储物点的距离糖糖别胡说我真的不是签到题目-前缀和-思维

DAY5共2题: 储物点的间隔(前缀和)糖糖别胡说,我真的不是签到题目(multiset,思维) 作者:Eriktse 简介:19岁,211计算机在读,现役ACM银牌选手力争以通俗易懂的形式解说算法!❤️欢送关注我,一起交换C++/Python算法。(优质好文继续更新中……) 原文链接(浏览原文取得更好浏览体验):https://www.eriktse.com/algorithm/1096.html 储物点的间隔题目链接:https://ac.nowcoder.com/acm/problem/14683 预处理出各点搬运到点1和点n的代价前缀和,以及区间分量和。 如果咱们要将区间[5, 7]的物品全副搬运到3,代价应该是区间[5, 7]的物品全副搬运到的1,而后减去多搬的代价:[5, 7]的分量和 * dist(1, 3)。 下面是x < l的状况,x > r的状况相似。 当l <= x <= r只需将区间[l, r]分为两局部求和即可。 记得取模,间隔要取模,前缀和也要取模。 #include <bits/stdc++.h>#define int long longusing namespace std;const int maxn = 2e5 + 9, p = 1e9 + 7;//sum_l[i]示意区间[1, i]的物品都运到点1的代价之和//prefix_a[i]示意区间[1, i]的物品分量之和//pos[i]是第i个点的地位,通过a[]作前缀和失去int a[maxn], pos[maxn], sum_l[maxn], sum_r[maxn], prefix_a[maxn];int n, m;//取模函数int mo(int x){return (x % p + p) % p;}int f(int x, int l, int r){ if(l > r)return 0; int res = 0; if(x <= l) { res = mo(sum_l[r] - sum_l[l - 1]); res = mo(res - mo(pos[x] - pos[1]) * mo(prefix_a[r] - prefix_a[l - 1]) % p); } else if(x >= r) { res = mo(sum_r[r] - sum_r[l - 1]); res = mo(res - mo(pos[n] - pos[x]) * mo(prefix_a[r] - prefix_a[l - 1]) % p); } return res;}signed main(){ scanf("%lld %lld",&n, &m); pos[1] = 1; for(int i = 2, d;i <= n; ++ i)scanf("%lld", &d), pos[i] = pos[i - 1] + d; for(int i = 1;i <= n; ++ i)scanf("%lld", a + i); for(int i = 1;i <= n; ++ i) { sum_l[i] = mo(sum_l[i - 1] + a[i] * mo(pos[i] - pos[1]) % p); sum_r[i] = mo(sum_r[i - 1] + a[i] * mo(pos[n] - pos[i]) % p); } for(int i = 1;i <= n; ++ i)prefix_a[i] = mo(prefix_a[i - 1] + a[i]); while(m --) { int x, l, r;scanf("%lld %lld %lld", &x, &l, &r); int ans = 0; if(l <= x and x <= r)ans = mo(f(x, l, x - 1) + f(x, x + 1, r)); else ans = f(x, l, r); printf("%lld\n", ans); } return 0;}糖糖别胡说,我真的不是签到题目题目链接:https://ac.nowcoder.com/acm/problem/14583 ...

March 28, 2023 · 3 min · jiezi

关于c++:JAVA让-ChatGPT-来浅说-AQS

前言又迎来了一年一度的金三银四,尽管说往年的大环境不好,然而招聘还是在炽热进行中。面试过 Java 工程师的小伙伴都晓得,Java 中的 AQS 是面试高频题,面试官上来就间接了当地问,AQS 晓得是什么吧,来讲讲它是怎么实现的,以及哪些地方用到了它。那么接下来,让咱们应用 ChatGPT 并联合本人的了解来讲述一下 AQS 的相干内容。什么是 AQS当博主发问 ChatGPT 什么是 AQS 时,ChatGPT 给出了如下答复: 大抵意思就是说,AQS 全称 AbstractQueuedSynchronizer,是 Java 中并发包中用于实现锁和其余同步器的根底框架。它应用了 CAS 操作和 unsafe 类来实现同步器的状态更新以及线程的挂起和唤醒等操作,提供了一种通用的、高效且可扩大的同步机制,能够用来构建各种同步组件。AQS 外部保护了一个 FIFO 队列,用于存储期待获取同步状态的线程。同时定义了一些模板办法,这些办法被子类实现,用于实现不同类型的同步器,例如 ReentrantLock、CountDownLatch、Semaphore 等。AQS 应用了一种独特的模板办法设计模式,应用外部状态(一个 volatile 润饰的 state 变量)来管制同步器的行为,子类通过实现模板办法来管制同步器的状态变动。AQS 的外部状态能够被子类用于实现独占式、共享式的同步器。综上,想要了解 AQS,以下几个方面是必要的: 同步器:AQS 是同步器的一个形象基类,通过继承 AQS 能够构建各种同步组件,如锁、信号量等。状态:AQS 外部保护了一个状态变量,示意同步器的状态。同步器的具体含意由子类来定义。队列:AQS 外部应用 FIFO 队列来存储期待获取同步状态的线程。当多个线程同时申请同步状态时,AQS 会将其中一个线程设置为独占模式,即该线程成为获取到同步状态的惟一持有者,其余线程则会被退出到期待队列中。模板办法:AQS 采纳了模板办法设计模式,在 AQS 中定义了一系列形象办法和钩子办法,子类须要实现这些办法来定义本人的同步逻辑。CAS 和 volatile:AQS 外部应用了 CAS 和 volatile 等原语来保障同步器的正确性和并发性能。 总之,AQS 是 Java 中并发包中实现锁和其余同步器的根底框架,应用模板办法设计模式和 CAS 操作实现了高效、可扩展性高的同步器。了解 AQS 对于了解 Java 中并发编程的原理和实现十分重要。AQS 如何实现那接下来问一下 ChatGPT AQS 是如何实现地: ...

March 28, 2023 · 2 min · jiezi

关于c++:C开发方向书籍推荐

如果你正在学习C++,那么一本好的教材或参考书能够事倍功半。以下是几本我集体举荐的C++书籍或视频: C++根底看书C++ PrimerC++程序设计语言Effective C++More Effective C++Effective STLSTL源码剖析深度摸索C++对象模型看视频黑马程序员(B站)C++内存治理(候捷)STL源码剖析(候捷)C++ STL与泛型编程高级(候捷)C++11 新个性(候捷)C++进阶书籍C++语言的设计与演变C++深思录C++ TemplatesC++ 模版元编程视频CppCon数据结构与算法书籍大话数据结构算法图解数据结构与算法剖析算法第4版算法导论视频浙大数据结构网课刷题LeetCode操作系统深刻了解计算机系统操作系统精华与设计原理古代操作系统(选读)程序员的自我涵养LinuxLinux/UNIX零碎编程手册Linux内核设计与实现深刻了解Linux内核计算机网络计算机网络自顶向下TCP/IP详解:卷1网络是怎么连贯的图解HTTP网络编程Unix网络编程Unix环境高级编程Linux多线程服务器端编程数据库数据库系统概念mysql必知必会高性能MySQLMqSQL技术底细设计模式大话设计模式Head First设计模式其余Redis设计与实现完结以上是我集体举荐的几本C++书籍。心愿这些举荐对你有所帮忙!

March 28, 2023 · 1 min · jiezi

关于c++:ACM算法竞赛日常训练DAY4题解与分析树子序列-组合数学-动态规划

DAY4共2题: 树(组合数学)子序列(dp,数学) 作者:Eriktse 简介:19岁,211计算机在读,现役ACM银牌选手力争以通俗易懂的形式解说算法!❤️欢送关注我,一起交换C++/Python算法。(优质好文继续更新中……) 原文链接(浏览原文取得更好浏览体验):https://www.eriktse.com/algorithm/1095.html树题目传送门:https://ac.nowcoder.com/acm/problem/13611 通过观察条件“一个染色计划是非法的,当且仅当对于所有雷同色彩的点对(x,y),x到y的门路上的所有点的色彩都要与x和y雷同。”咱们能够发现,当且仅当染色的点能够全副连通时能够满足条件。 所以当初问题是如何将n个点划分为k块。 咱们能够发现在树上,任意删除一条边都会使得联通块个数 + 1。 其实块数只有<= k即可,因为咱们能够有一些色彩不应用。所以要划分为i块,只须要从n - 1条边中任选i - 1条进行删除即可,计划数是C(n - 1, i - 1)。 假如当初咱们失去了i (i <= k)个联通块,须要将i种颜色染上去,首先须要C(k, i)种办法取出色彩,而后A(i, i)一个全排列将色彩染上去。 所以答案公式如下: $$ans=\sum_{i=1}^{k}C(n - 1, i - 1)C(k, i)i!$$ 可能波及一些疾速幂、乘法逆元的常识,须要自行学习。 #include <bits/stdc++.h>#define int long longusing namespace std;const int maxn = 350, p = 1e9 + 7;int fac[maxn];int qmi(int a, int b){ int res = 1; while(b) { if(b & 1)res = res * a % p; a = a * a % p, b >>= 1; } return res;}int inv(int x){return qmi(x, p - 2);}int C(int n, int m){ if(n < m || n < 0 || m < 0)return 0; return fac[n] * inv(fac[n - m] * fac[m] % p) % p;}signed main(){ int n, k;scanf("%lld %lld", &n, &k); fac[0] = 1; for(int i = 1;i <= n; ++ i)fac[i] = fac[i - 1] * i % p; int ans = 0; for(int i = 1;i <= n; ++ i)//分为i块 { int tmp = C(n - 1, i - 1) * C(k, i) % p * fac[i] % p; ans = (ans + tmp) % p; } printf("%lld\n", ans); return 0;}子序列题目传送门:https://ac.nowcoder.com/acm/problem/17065 ...

March 27, 2023 · 2 min · jiezi

关于c++:C学习路线

C++是一种高级编程语言,宽泛用于开发操作系统、应用程序、游戏和各种工具。如果你想学习这门语言,以下是一个适宜初学者的学习路线: 第一步:学习C++基础知识在学习C++之前,你须要把握一些基础知识,如计算机科学和编程方面的根底概念。你能够通过浏览相干书籍、观看视频教程或加入在线课程来学习这些常识。这些资源应该涵盖以下主题: 数据类型、变量和表达式管制语句(如if语句和循环)函数和参数数组和字符串指针和援用除此之外,初学者还能够学习一些其余的编程语言,比方Python。学习Python能够帮忙你更好地了解编程的基本概念和原理。 第二步:学习C++规范库一旦你把握了基础知识,你就能够开始学习C++规范库了。C++规范库是C++编程的外围,蕴含各种有用的函数和类型,可用于开发各种类型的应用程序。你须要学习以下内容: 输出和输入(如cin和cout)字符串和字符处理函数容器(如vector和map)算法(如排序和搜寻)此外,你还能够学习一些其余的库和框架,如Boost和STL等。这些库和框架能够帮忙你更好地了解C++编程和进步编程效率。 第三步:学习C++高级主题一旦你把握了基础知识和规范库,你就能够开始学习C++的高级主题了。这些主题可能包含以下内容: 面向对象编程模板和泛型编程异样解决多线程编程除此之外,你还能够学习一些其余的高级主题,如网络编程、图形界面编程等。这些主题能够帮忙你更好地利用C++编程。 第四步:练习和实际学习C++须要大量的实际和练习。你能够实现一些课程作业或参加开源我的项目,或者本人设计和实现一些小型应用程序。这样能够帮忙你坚固所学常识并进步编程技能。 除此之外,你还能够加入一些编程较量或者挑战赛,这样能够帮忙你更好地利用所学常识。 总结以上是一个适宜初学者的C++学习路线,当然还有很多其余的资源和办法能够帮忙你学习C++。最重要的是,继续一直地学习和实际,能力成为一名优良的C++编程人员。一直地浏览相干的书籍和文章,与其余编程人员交换,也能够帮忙你更好地学习和成长。

March 27, 2023 · 1 min · jiezi

关于c++:常用Linux系统命令vim编辑命令汇总看一遍用时候来查

Linux操作基本操作pwd命令作用:显示当前工作目录 用法:pwd cd命令作用:扭转目录地位 用法:cd [option] [dir] cd 目录门路 -进入指定目录 cd .. -返回父目录 cd / -进入根目录 cd或cd ~ -进入用户主目录 ls命令用法:ls [option] [file] 罕用参数: 不带任何参数:列出当前目录下的所有文件和子目录-F:分类显示,不便浏览-a:显示隐含文件 (.结尾的文件是暗藏文件)-l:查看文件的各种属性dir和vidr命令用法: dir [option] [file] dir [option] [file] 阐明:dir和ls差不多,比ls性能少;vdir相当于ls -l mkdir命令用法:mkdir [option] [file] mkdir 一次能够建设一个或者几个目录 罕用参数: 不带任何参数:创立相应目录,如果目录父级目录不存在,则创立失败; -p:创立相应目录,如果父级目录不存在,则一起创立 mv命令用法:mv [option] [源文件] [指标] 罕用参数: 不带任何参数:将源文件挪动到指标文件(能够挪动的同时改名字),留神:如果指标文件存在,则替换! -i:将源文件复制到指标文件,如果指标文件存在则提醒是否替换; -b:将源文件复制到指标文件,如果指标文件存在则不进行笼罩,而是在指标文件后加~ cp命令用法:cp [option] [源文件] [指标]罕用参数: 不带任何参数:将源文件复制到指标文件,留神:如果指标文件存在则替换; -i:将源文件复制到指标文件,留神:如果指标文件存在则提醒是否替换; -b:将源文件复制到指标文件,如果指标文件存在则不进行笼罩,而是在指标文件后加~ -r:将子目录及其中的文件一起复制到另一个子目录下 rm命令用法:rm [option] [file] 永恒删除文件! 罕用参数: 不带任何参数:删除文件或目录,不予任何提醒(然而我的带提醒,不知为何) -i :删除文件或者目录,删除是进行揭示 -f:强制性删除文件或者目录 -r:将子目录及其中的文件一并删除 ln命令用法:ln [option] [源文件] [指标文件] 建设文件链接 ...

March 26, 2023 · 1 min · jiezi

关于c++:windows安装最新pangolin-v08

如果想在windows上配置slam的开发环境,以前是比拟麻烦的,然而当初有了微软vcpkg的反对,在win上装置依赖也能够像pip和apt那样难受,尽管还不算那么欠缺,但微软将来还会加大反对。 pangolin是slam零碎实时可视化的一个重要工具,简直各个支流开源我的项目都会用它,但网上对于怎么在windows环境装置pangolin的博客不太多,大多步骤都十分繁琐,为了躲避编译问题还要使出各种奇技淫巧,集成到我的项目中时也要费一番功夫。 我在vcpkg反对的软件包list里能看到pangolin的最新开源,所以打算用vcpkg一键装置。 装置间接在vcpkg根目录下执行:.\vcpkg.exe install pangolin:x64-windows 就会主动装置pangolin,主动下载相干依赖,主动实现编译,超级不便。目前最新的版本是0.8。 能够执行:.\vcpkg.exe list 看看装置状况。 pangolin会依赖opengl和glew,所以它们也会被装置。 接着别忘了集成到VS中,对于vcpkg的应用能够去找博客疾速理解一下。我这里是集成到全局的,所以在任何我的项目里都能够间接应用装置好的依赖。 测试新建一个我的项目,用上面的代码测试一下pangolin的应用。 #include <pangolin/pangolin.h>int main(int /*argc*/, char** /*argv*/){ // 创立名称为“Main”的GUI窗口,尺寸为640×640 pangolin::CreateWindowAndBind("Main", 640, 480); // 启动深度测试 glEnable(GL_DEPTH_TEST); // 创立一个察看相机视图 pangolin::OpenGlRenderState s_cam( pangolin::ProjectionMatrix(640, 480, 420, 420, 320, 320, 0.2, 100), pangolin::ModelViewLookAt(2, 0, 2, 0, 0, 0, pangolin::AxisY) ); // 创立交互视图 pangolin::Handler3D handler(s_cam); //交互相机视图句柄 pangolin::View& d_cam = pangolin::CreateDisplay() .SetBounds(0.0, 1.0, 0.0, 1.0, -640.0f / 480.0f) .SetHandler(&handler); while (!pangolin::ShouldQuit()) { // 清空色彩和深度缓存 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); d_cam.Activate(s_cam); // 在原点绘制一个立方体 pangolin::glDrawColouredCube(); // 绘制坐标系 glLineWidth(3); glBegin(GL_LINES); glColor3f(0.8f, 0.f, 0.f); glVertex3f(-1, -1, -1); glVertex3f(0, -1, -1); glColor3f(0.f, 0.8f, 0.f); glVertex3f(-1, -1, -1); glVertex3f(-1, 0, -1); glColor3f(0.2f, 0.2f, 1.f); glVertex3f(-1, -1, -1); glVertex3f(-1, -1, 0); glEnd(); // 运行帧循环以推动窗口事件 pangolin::FinishFrame(); } return 0;}问题编译时会报错,一大堆GL结尾的未定义标识符,发现是pangolin的头文件中找不到位于gl.h中的宏定义,对于这个问题我搜寻了很久才找到答案。 ...

March 25, 2023 · 1 min · jiezi

关于c++:面试高频问题之C编译过程

C++编译过程C++是一种高级编程语言,然而计算机并不能间接了解它。因而,须要将C++代码翻译成计算机能够了解的机器语言。这个过程就是编译过程,是C++程序从源代码到可执行文件的转换过程,包含预处理、编译、汇编和链接四个阶段。 1. 预处理在编译器开始编译之前,会先进行预处理。预处理器会解决代码中的所有预处理指令,例如#include和#define等。它会将这些指令替换成对应的代码,生成一个新的文本文件。这个新的文本文件就是编译器的输出。在预处理的过程中,还会进行条件编译,即依据条件来抉择是否编译某些代码块。预处理实现后,会生成一个没有宏定义和条件编译的两头文件。 预处理器的次要作用是解决代码中的宏定义,它会将宏定义开展成对应的代码。此外,预处理器还会解决#include指令,将指定的头文件插入到源文件中,以便让编译器可能对头文件中的函数和变量进行编译。 2. 编译编译器将预处理后的文件作为输出,对其进行词法剖析、语法分析和语义剖析等解决。这些解决会将代码转化成中间代码,中间代码是一种相似于汇编语言的低级代码。编译器将代码分成多个模块,每个模块编译成一个对象文件。在编译的过程中,编译器会对代码进行优化,以进步程序的运行效率和节俭内存空间。 在词法剖析和语法分析的过程中,编译器会对代码进行查看,以确保代码的正确性和合法性。语义剖析的工作是在代码的语法结构上进行剖析,以确定代码的含意和作用。 3. 汇编中间代码会被汇编器转化成机器语言指令,这些指令能够被计算机间接执行。汇编的过程将每个对象文件转换成机器语言的指标文件。每个指标文件蕴含机器指令和数据,以及其余的管制信息。指标文件能够被链接器用于构建可执行文件。 汇编器的次要工作是将中间代码翻译成机器语言指令,以便让计算机可能执行代码。在这个过程中,汇编器会将每个源文件编译成一个指标文件,而后将所有的指标文件组合成一个可执行文件。 4. 链接在编译过程中,可能会用到其余的库文件和对象文件。链接器会将这些文件与生成的可执行文件进行链接,生成最终的可执行文件。链接器的次要工作是解决符号援用问题,即通过在不同的指标文件中查找符号的定义,使得所有的符号都可能正确地被解析和链接。链接器还会对代码进行优化和压缩,以减小可执行文件的体积,并进步程序的运行效率。 链接器的工作包含将每个指标文件中的符号解析成地址,并将它们组合成一个独自的地址空间。链接器还会解决代码中的重定位信息,以确保代码可能正确地加载和执行。 论断C++编译过程是一个非常复杂的过程,它须要通过屡次解决能力最终生成可执行文件。然而,理解这个过程对于了解C++代码和调试程序都十分有帮忙。在理论的开发中,程序员须要把握编译工具链的应用,以便可能更好地进行调试和优化。同时,程序员还须要理解编译器的工作原理和优化技术,以写出高效的C++代码。

March 25, 2023 · 1 min · jiezi

关于c++:面试高频问题之C11新特性

背景C++11是C++语言的一个重大更新,引入了许多新个性,包含主动类型推导、lambda表达式、右值援用、智能指针等等。这些新个性使得C++更加现代化、高效、易用。也是面试容很容易被问到一个问题,上面我将简要介绍一些C++11的新个性: 主动类型推导C++11引入了auto关键字,能够让编译器主动推导变量的类型。这样能够缩小代码中的反复代码,进步代码的可读性和可维护性。例如: auto i = 42; // 推导为int类型auto d = 3.14; // 推导为double类型auto s = "hello"; // 推导为const char*类型lambda表达式C++11引入了lambda表达式,能够不便地定义匿名函数。这样能够缩小代码中的反复代码,进步代码的可读性和可维护性。例如: auto f = [](int x, int y) { return x + y; };int z = f(1, 2); // z的值为3右值援用C++11引入了右值援用,能够不便地实现挪动语义和完满转发。这样能够进步代码的性能和效率。例如: class MyString {public: MyString() : data_(nullptr), size_(0) {} MyString(const char* str) : data_(new char[strlen(str) + 1]), size_(strlen(str)) { strcpy(data_, str); } MyString(MyString&& other) : data_(other.data_), size_(other.size_) { other.data_ = nullptr; other.size_ = 0; } ~MyString() { delete[] data_; }private: char* data_; size_t size_;};更多对于右值援用的内容,感兴趣的小伙伴能够翻看我之前的帖子,有专门的介绍。 ...

March 20, 2023 · 1 min · jiezi

关于c++:Visual-Studio-2023-激活下载安装教程内附Visual-Studio激活码密钥

visual studio 2022(vs 2022)是由微软官网出品的最新版本的开发工具包系列产品。它是一个残缺的开发工具集,囊括了整 visual studio 2022是一款由微软全新研发推出的编程开发软件,该软件可能为程序开发人员提供一个绝佳的IDE开发环境,可帮忙用户高效率地杜绝错误代码,当用户输出错误代码的时候,零碎即会主动将其标红,可完满反对C#、C++、Python、Visual Basic、Node.js、HTML、JavaScript等支流的编程语言,帮忙程序员轻松地实现调试、探查和诊断程序,进步代码的准确率和工作效率,明天小编为大家带来了这款软件的专业版,附带了专业版激活密钥,激活后即可永恒收费应用,喜爱的小伙伴千万不要错过哦。 外围亮点1、外围调试器中的性能改良 2、UI 更新,旨在缩小复杂性,这将减少与 Accessibility Insights 的集成。图标更新,并减少对 Cascadia Code 的反对,这是一种新的 fixed-width 字体,可进步可读性 3、对 .NET 6 的反对,Windows 和 Mac 开发人员都能够应用它来构建 Web、客户端和挪动应用程序,并为开发 Azure 应用程序提供了更好的反对反对热更新预览,无需重启我的项目即可查看更改 4、更弱小的代码预测能力 5、对 C++ 工作负载的弱小反对,包含新的 productivity features、C++20 工具和 IntelliSense。还集成了对 CMake、Linux 和 WSL 的反对,使用户更容易创立、编辑、构建和调试跨6、平台应用程序 7、将文本聊天集成到 Live Share 合作性能中 8、反对 Git 和 GitHub 9、改良代码搜寻性能。 10、Visual Studio for Mac 将迁徙至原生 macOS UI,领有更好的性能和可靠性。 Visual Studio 2022密钥PS: 密钥没有间接贴出来,是为了回绝一部分白嫖党,因为有的人即使是你帮了他他还骂你,所以我把密钥放在了公众号里,这样我既能够帮忙到那些信赖我的人,同时也能够给公众号涨涨粉,两败俱伤,不丢人。 须要的小伙伴,扫描下方公众号二维码,或者关注公众号: 徐公,回复关键字:vs2 即可收费无套路获取,继续更新中~ visual studio 2022 最新破解装置教程,附激活码(2023 年 2 月 10 号,亲测无效 ...

March 19, 2023 · 1 min · jiezi

关于c++:CSTL教程7priorityqueue优先队列入门学习零基础都能听懂的教程

人不知;鬼不觉C++STL教程系列曾经第7期了。之前咱们介绍过:vector, queue, stack, set, map等等数据结构。 明天咱们来学习一个新的stl容器:priority_queue优先队列。 作者:Eriktse 简介:19岁,211计算机在读,现役ACM银牌选手力争以通俗易懂的形式解说算法!❤️欢送关注我,一起交换C++/Python算法。(优质好文继续更新中……) 集体博客:www.eriktse.compriority_queue介绍priority_queue是STL中的一个重要容器,它相似于一般的队列,反对弹出堆顶元素和插入元素的操作,然而有一个很大的不同:队列中元素依照优先级参数排序,最小(或者最大)元素总是在队列的前端,并且会在每次出队的时候被删除。 priority_queue领有两种不同的形式:一种是依照大顶堆(max heap)形式排序,另一种是依照小顶堆(min heap)形式排序。STL提供了基于容器改编的priority_queue,因而能够在传递给priority_queue的容器中,反对不同类型的元素。 初始化在应用priority_queue之前须要引入头文件#include <priority_queue>。 用以下代码初始化一个空的priority_queue。 priority_queue<int> pq;//默认为大根堆priority_queue<int, vector<int>, greater<int> > pq2;//批改为小根堆能够用适当类型的对象初始化一个优先级队列: string wrds[] { "one", "two", "three", "four"};priority_queue<string> words {start(wrds), end(wrds)}; // "two" "three" "one" "four" 如果是寄存构造体,且要自定义比拟函数,能够用仿函数,就是用写一个构造体,而后重载()运算符。 struct Node{ int a, b;};//申明Node构造体struct cmp{ bool operator () (const Node &u, const Node &v)const { return u.a < v.a; }};priority_queue<Node, vector<Node>, cmp> pq_Node;罕用操作函数priority_queue仅保护一个顶部。 插入元素push()办法:用于将一个新元素增加到堆中。 pq1.push(1);//将元素1插入到pq1中。获取堆顶元素top()办法:获取堆顶的元素,然而不会自动弹出,如果须要弹出请再加一个pop()。 留神当堆为空时,不容许获取堆顶元素,或弹出堆顶元素,须要判断堆非空能力对堆顶操作。获取堆内元素个数size()办法:返回一个非负整数,示意堆内元素个数。 当size() == 0时,示意堆为空。 判断堆是否为空empty()办法:返回一个bool值,示意堆是否为空。 ...

March 17, 2023 · 1 min · jiezi

关于c++:CSTL教程6bitset是什么和bool有什么区别零基础都能看懂的入门教程

之前咱们介绍过vector, queue, stack,map,set,明天咱们介绍另外一个stl容器:bitset。 作者:Eriktse 简介:19岁,211计算机在读,现役ACM银牌选手力争以通俗易懂的形式解说算法!❤️欢送关注我,一起交换C++/Python算法。(优质好文继续更新中……) 集体博客:www.eriktse.com本文仅从入门和实用角度进行解说,次要针对初学者或比赛向。如有不谨严的中央欢送斧正! bitset 简介bitset是C++规范模板库(STL)中的一种数据结构,它以十进制数的模式存储bit(二进制位)的值。它的目标是包容在肯定的空间中存储和操作十分小的数字,因为常常只须要一位来示意某个值是true还是false。 bitset能够存储大小可变的位,每一位存储一位数据,可用 true, false, 0, 1 来示意,可能灵便的操作每一位。 bitset提供一个十分无效的接口,用于在bit向量中疾速地实现检索和更新操作,是对内存和硬件资源的高效利用。 初始化应用bitset须要引入头文件#include <bitset>。 用以下代码初始化一个bitset变量。 bitset<4> bs;//初始化一个大小为4的bitsetbitset即便初始化在栈空间(非全局变量)里,也会全副初始化为0。 bitset对象的构造函数要求参数为定义一个数字,这个数字就是你要应用的位的个数。例如, bitset<4> bs;这个bitset<4> bs定义一个bitset,它具备4个二进制位,每个位都是0。 bitset<4> bs(5);//0101要定义一个bitset并为每个位设定初始值,能够把对应的0和1用字符串来写 bitset<10> bs2("1010101010");用法其实能够间接当做一个能够做位运算的bool数组用,它常常用于动静布局中。 批改bs[0] = 1;cout << bs << '\n';//0001位运算运算符个别能够分为两类:流运算符,例如<<和>>; bs <<= 1;cout << bs << '\n';//0010另一类是比拟、赋值运算符,例如&、|、^等等。 bs |= (bitset<4>(1) << 3);cout << bs << '\n';//1010bs &= (bitset<4>(1) << 4);cout << bs << '\n';//1000其余办法bitset还有几个有用的办法: count():用于计算bitset中1的个数。any():如果bitset中有一个位是1,那么返回true,否则返回false。none ():如果bitset中每个位都是0,那么返回true,否则返回false。set ():用于将bitset中的所有地位设置成1。reset ():用于将bitset中的所有地位设置成0。flip ():用于将bitset中的所有地位反转。和bool数组的区别STL中的 bitset 是由固定长度的二进制位组成的数据类型,位数的范畴由模板参数确定,最多可示意2^[模板参数]个值; bool 是c++用作布尔型的类型,只能示意真或假,两个类型之间惟一的不同就是:bitset 是变长度,bool 是定长度。这意味着对于 bool,每个值都须要占用8位,而bitset能够依据理论值须要而扭转长度,比方只有1位保留即可。 ...

March 17, 2023 · 1 min · jiezi

关于c++:CSTL教程5set是什么怎么用零基础都能看懂的入门教程

之前咱们介绍过vector, queue, stack,map。咱们晓得前三者是线性构造,而map是一种树状构造,明天咱们要介绍另外一个树状构造实现的stl容器:set。 作者:Eriktse 简介:19岁,211计算机在读,现役ACM银牌选手力争以通俗易懂的形式解说算法!❤️欢送关注我,一起交换C++/Python算法。(优质好文继续更新中……) 集体博客:www.eriktse.com本文仅从入门和实用角度进行解说,次要针对初学者或比赛向。如有不谨严的中央欢送斧正! 什么是 set?set意为汇合,在高中咱们学习过汇合的三大性质:确定性、互同性、无序性。在C++的STL中的汇合,容器内会默认依照“升序”排列,但同样遵循确定性和互同性。 C++ STL SetSet是C++规范模板库(STL)中较为重要的容器,原生反对有序,惟一。 一个大小为n的set所需的空间约为nlogn个单位。set的插入、删除、查找操作复杂度均为O(logn)。 要害个性唯一性:Set容器内的元素都是惟一的,也就是说,每个元素都是不同的有序性:Set容器内的元素总是排序的,向Set中增加元素,它将主动插入到正确的地位中,不须要手动排序查找/插入疾速:因为Set容器的元素是排序的,所以在Set中查找和插入元素都很快实用场景Set容器的有序性和唯一性个性极大地缩小了大量反复和排序等工作,在很多场景下Set容器更具劣势,下列状况是应用Set容器适合的状况: 存储元素类型不可能反复的场景,比方存储用户的惟一ID操作多个对象时,必须应用排序算法的场景须要疾速查找和插入元素的场景通过Set容器,能够疾速获取惟一和有序的后果,同时在大数据量下性能也绝对较高,因而应用场景宽泛。 初始化 set在应用set之前,须要引入头文件#include <set>,当然你也能够用#include <bits/stdc++.h>(比赛选手举荐)。 用以下代码,申明一个空的set。 set<int> st;//申明一个存储类型为 int 的 set 容器插入元素insert(x)办法:将元素x插入到set对象中,如果set中曾经有了x元素,则不会插入(保障set中每个元素最多呈现一次)。 C++中的STL中提供了一种汇合容器——Set,static set它是一个领有非凡性能(无序、不容许反复)的容器。STL中Set如何插入元素呢? 1.应用insert函数插入元素:(1)一个元素的插入:set容器提供的insert函数能够将一个元素插入到容器中,代码如下: set<int> a;a.insert(10);此外,也能够插入pair类型的数据,如: set<pair<int, int> > b;b.insert(std::make_pair(2, 3));(2)多个元素的插入:vector<int> array{1, 2, 3};set<int> a;a.insert(array.begin(), array.end());2.应用emplace函数插入元素:emplace函数向set容器中增加一个新元素,但不复制或挪动现有元素,而是间接在容器存储区中结构元素。它的作用和insert函数雷同,但emplace函数的效率会更高。 set<int> a;//a : {empty}a.emplace(10);//a : {10}删除元素在应用set删除元素之前,须要了解对于set的删除元素形式,目前c++ STL提供了三种删除set元素的形式: clear() 、erase() 、 和 pop_back() 。 要删除set中的元素,通常是应用erase()函数: // 申明一个set容器std::set<int> st;// 增加元素到容器st.insert(9);st.insert(5);st.insert(1);// 如果要删除set中的某个值int value = 5;st.erase(value);//删除st中value = 5的数据项// 如果要删除set中的某个指定的地位的值std::set<int>::iterator it = /* Set Iterator */;st.erase(it);// 此外,还提供了clear()函数来清空set中的所有元素:st.clear();// 最初,也能够应用pop_back()函数来删除set中的一个元素:st.pop_back();查找元素C++STL的set提供了十分不便的查找操作,也即咱们常说的find操作。find操作的应用非常简单,能够应用Iterator类型的游标变量,对Set成员汇合进行定位,若要查找的元素存在,则会返回该元素的地位信息,若不存在,则返回set开端迭代器(未设定取值)。 ...

March 17, 2023 · 1 min · jiezi

关于c++:CSTL教程4map超强的容器它终于来了零基础都能理解的入门教程

之前咱们介绍过vector, queue, stack,他们都有一个独特的特点,就是都能够用线性表来模仿。明天咱们来学习一个全新且高封装性的容器:map。 作者:Eriktse 简介:19岁,211计算机在读,现役ACM银牌选手力争以通俗易懂的形式解说算法!❤️欢送关注我,一起交换C++/Python算法。(优质好文继续更新中……) 集体博客:www.eriktse.com什么是 mapstd::map是C++规范库中的一个容器,数据以<key, value>的模式存储,也就是咱们常说的“键值对”模式,且其“键值对”是有序的,也就是能够程序遍历的。 这意味着一个key只能对应一个value,而一个value可能对应了多个key,其关系有点像高中学过的函数的关系。 map的底层个别实现为红黑树,这个仅作理解即可。搜寻、移除和插入操作领有log级别复杂度。 初始化 map首先引入头文件: #include <map>用以下代码申明一个空的map: map<int, string> mp;//申明一个类型为<int, string>的map留神这里应用了string,也就须要引入头文件#include <string>。 插入数据map有一个函数是insert(),反对将数据插入。工夫复杂度O(logn),n为map中已有的数据个数。 mp.insert({0, "张三"});//插入一条数据当然还有另外一种方法来插入数据,就是间接赋值,像操作数组一样操作map,然而这个map的下标可不是间断的,能够是任意符合条件的key。 mp[2] = "李四";//当初map中的数据:{0: "张三", 2: "李四"}可能会有小伙伴纳闷,这里没有1的吗?在这里map的key只有int类型即可,就算是正数都能够! mp[-1] = "王五";//mp = {-1: "王五", 0: "张三", 2: "李四"};mp[-1] = "eriktse";//mp = {-1: "eriktse", 0: "张三", 2: "李四"};值得注意的是,value是可笼罩的,且这里的key是有序的,尽管我的-1这个key是前面退出的,然而却排在了第一个,如果程序遍历这个mp的话,{-1: "eriktse"}会是第一个被遍历到的。前面会讲到如何遍历map。 删除数据 & 清空maperase(key)办法:删除key所对应的数据。工夫复杂度O(logn)。 clear()办法:清空整个map。 mp.earse(-1);////mp = {0: "张三", 2: "李四"};获取map大小(元素个数)size()办法:返回map的大小,是一个非负整数。 查看容器是否无元素,即是否 begin() == end() 。 获取map中的数据间接像用数组一样获取就行了。 mp[key]示意map中这个key所对应的value。 cout << mp[0] << '\n';//输入: 张三遍历输入map遍历map须要用到std::iterator迭代器,没有接触过的同学可能不太理解,能够先看代码,或者用第二种办法。 ...

March 16, 2023 · 1 min · jiezi

关于c++:BoostAsyncSocket-异步反弹通信案例

Boost 利用ASIO框架实现一个跨平台的反向远控程序,该远控反对保留套接字,当有套接字连入时,主动存储到map容器,当客户下线时主动从map容器中移除,当咱们须要与特定客户端通信时,只须要指定客户端ID号即可。 AsyncTcpServer服务端首先定义CEventHandler类并继承自CAsyncTcpServer::IEventHandler接口,该类内须要咱们实现三个办法,办法ClientConnected用于在客户端连贯时触发,办法ClientDisconnect则是在登录客户端来到时触发,而当客户端有数据发送过去时则ReceiveData办法则会被触发。 办法ClientConnected当被触发时主动将clientId客户端Socket套接字放入到tcp_client_id全局容器内存储起来,而当ClientDisconnect客户端退出时,则间接遍历这个迭代容器,找到序列号并通过tcp_client_id.erase将其剔除; // 客户端连贯时触发virtual void ClientConnected(int clientId){ // 将登录客户端退出到容器中 tcp_client_id.push_back(clientId);} // 客户端退出时触发virtual void ClientDisconnect(int clientId){ // 将登出的客户端从容器中移除 vector<int>::iterator item = find(tcp_client_id.begin(), tcp_client_id.end(), clientId); if (item != tcp_client_id.cend()) tcp_client_id.erase(item);}而ReceiveData一旦收到数据,则间接将其打印输出到屏幕,即可实现客户端参数接管的目标; // 客户端获取数据virtual void ReceiveData(int clientId, const BYTE* data, size_t length){ std::cout << std::endl; PrintLine(80); std::cout << data << std::endl; PrintLine(80); std::cout << "[Shell] # ";}绝对于接收数据而言,发送数据则是通过同步的形式进行,当咱们须要发送数据时,只须要将数据字符串放入到一个BYTE*字节数组中,并在调用tcpServer.Send时将所需参数,套接字ID,缓冲区Buf数据,以及长度传递即可实现将数据发送给指定的客户端; // 同步发送数据到指定的线程中void send_message(CAsyncTcpServer& tcpServer, int clientId, std::string message, int message_size){ // 获取长度 BYTE* buf = new BYTE(message_size + 1); memset(buf, 0, message_size + 1); for (int i = 0; i < message_size; i++) { buf[i] = message.at(i); } tcpServer.Send(clientId, buf, message_size);}AsyncTcpClient客户端首先咱们封装实现AsyncConnect类,该类内次要实现两个性能,其中aysnc_connect办法用于实现异步连贯到服务端,而port_is_open办法则用于验证服务器特定端口是否凋谢,在调用boost::bind绑定套接字时传入&AsyncConnect::timer_handle设置一个超时等待时间。 ...

March 16, 2023 · 5 min · jiezi

关于c++:CSTL教程3stack栈入门简明教程小白都能理解

在学习之前,先理解一下什么是stack。 std::stack 类是容器适配器,它给予程序员栈的性能——特地是 FILO (先进后出)数据结构。该类模板体现为底层容器的包装器——只提供特定函数汇合。栈从被称作栈顶的容器尾部推弹元素。FILO指的是First In Last Out,也就是说第一个进来的,是最初一个进来的。咱们能够将stack了解为一个上端闭口的铁箱子,咱们能够从顶部拿出物品或放入物品,且记录物品个数。 stack仅保护一个栈顶,意味着咱们只能从一端对数据进行操作。 本文仅从入门和实用角度介绍queue的用法,次要针对初学者或比赛向。如有不谨严的中央欢送斧正!初始化在应用stack之前,须要先引入头文件。 #include <stack>初始化的语法如下: stack<T> stk;// T 为数据类型stack<int> stk_int;//申明一个栈,寄存类型为int和其余的stl容器一样,stack只能寄存雷同类型的元素,默认初始化为空栈。 入栈stk.push(x)将元素x推入栈stk的栈顶,复杂度O(1)。 每入栈一个新元素,会使得栈的大小+1。 // 右边为栈顶// stk: emptystk.push(1);// stk : 1stk.push(5);// stk : 5 1还有一种办法是用emplace()函数进行入栈,两者差异能够临时疏忽。 stk.emplace(7);// stk : 7 5 1出栈stk.pop()将stk的栈顶元素弹出栈,复杂度O(1)。 // stk : 7 5 1stk.pop();// stk : 5 1stk.pop();// stk : 1在出栈时要留神栈是否为空,空栈不容许出栈。这和queue出队相似,感兴趣的能够看看这篇文章:https://www.eriktse.com/technology/1063.html 你问一个人“push”的反义词是什么?如果他答复“pop”那他肯定是程序员。(答案兴许是“pull”)获取栈顶元素语法:stk.top()能够获取栈顶元素,然而不会主动将栈顶元素弹出。须要自行pop()。复杂度O(1)。 // stk : 7 5 1cout << stk.top() << '\n';// 7留神获取栈顶元素的时候也须要保障栈不为空,否则将导致谬误!获取栈的大小语法:stk.size(),返回值为一个非负整数,当返回0时示意栈为空,复杂度O(1)。 // stk : 1cout << stk.size() << '\n';// 1stk.push(9);// stk : 9 1cout << stk.size() << '\n';// 2本文由eriktse原创,未经容许禁止转载!判断栈是否为空语法:stk.empty(),返回值为一个bool值,当返回true时示意栈为空,当返回值为false时示意栈非空。复杂度O(1)。 ...

March 16, 2023 · 1 min · jiezi

关于c++:CSTL教程2queue队列容器小白都能看懂的讲解

在学习数据结构的时候咱们会听到这样一个词:队列。 本文将介绍STL中的队列:queue 本文仅从入门和实用角度介绍queue的用法,次要针对初学者或比赛向。如有不谨严的中央欢送斧正!本文长度约2000字,浏览大概需5分钟。什么是队列?队列是一种FIFO,即First In First Out的数据结构,就像是小朋友排队一样,所有元素都只能从队尾(rear / back)进,队头(front)出,队列内的元素放弃着入队时的程序。 这时候有小伙伴可能会问:队列能做的,数组都能模仿,为什么还要队列呢? 咱们要晓得的是,咱们学习队列并非学习队列这个构造自身, 而是学习其中蕴含的思维,用于解决简单的问题。 知乎答复:https://www.zhihu.com/question/457210423 队列和其余C++的规范库容器一样,都只能寄存雷同的数据类型。如果要尝试寄存任意类型,能够去理解一下\<any\>这个库。队列的初始化在应用queue前,须要引入头文件。 #include <queue>用以下的代码初始化一个空队列: queue<T> q; // 其中T为数据类型接下来的操作都针对q这个实例化对象。 插入元素语法:q.push(x), x为q中寄存的类型的变量。 q.push(1);q.push(4);q.push(5);//q: 1(front) 4 5 (rear)咱们认为队头的元素为front,而队尾元素的前面一个地位是rear,合乎计算机中常见的“左闭右开”的规范。 还有一种方法,就是用q.emplace()函数进行入队,它和push用法雷同单有稍微差别然而初学者能够疏忽。 此函数用于将新元素插入队列容器,并将新元素增加到队列的开端。 q.emplace(1);q.emplace(2);q.emplace(3);//这个q接着上一个例子//q: 1 4 5 1 2 3弹出元素语法:q.pop(),将队头元素弹出。 //q: 1(front) 4 5 (rear)q.pop();//q: 4(front) 5 (rear)q.pop();//q: 5(front) (rear)q.pop();//q: (empty)q.pop();//谬误!值得注意的是,当队列为空的时候弹出元素会导致谬误,所以咱们在每一步pop()操作的时候肯定要判断队列是否为空,除非你有十足的把握。 本文为eriktse原创,未经容许禁止转载。获取队列大小语法:size(),返回值为一个非负整数。 cout << q.size() << '\n';当q.size() == 0时,队列为空。 判断队列是否为空语法:empty(),返回值为bool。 用法和vector相似,感兴趣的能够看这篇文章:[[C++STL教程]1.vector容器是什么?实用教程来啦!](https://www.eriktse.com/algorithm/1051.html) if(q.empty())cout << "队列为空" << '\n';else cout << "队列非空" << '\n';当然还能够通过判断队列的大小来判断是否为空。 //当q.size() > 0时,其bool值为true,队列非空if(q.size())cout << "队列非空" << '\n';else cout << "队列为空" << '\n';清空队列有时候咱们须要将队列清空,然而queue并没有像vector那样的clear函数,怎么做呢? ...

March 15, 2023 · 1 min · jiezi

关于c++:SkeyeIVMS-SkeyeVSS视频安防综合管理系统打造工业联网数字化智慧工厂

 随着古代工厂信息化的倒退,工厂治理也进入智能化阶段,越来越多的工厂开始利用物联网技术和设施监控技术增强信息管理和服务。智慧工厂视频安防零碎通过装置在工厂生产施工作业现场的各个监控安装,构建智能监控剖析预警和防备体系,及时、高效地反映重要地点区域的现场状况,实现对人员、机械、资料、环境的全方位实时监控,变被动“监督”为被动“监控”;真正做到事先预警,事中常态检测,预先标准治理,无效补救了传统办法和技术在治理中的缺点,将工厂平安生产做到信息化治理。 视开科技针对“智慧工厂”这一制造业新概念,提出了“SkeyeIVMS+ SkeyeVSS”视频安防综合管理系统解决方案,通过利用5G、AI、互联网+等技术手段,在保障安防管理系统稳固、牢靠的根底上,实现工厂重点区域全天候实时视频监控,满足视频文件海量存储、回放需要,并且具备报警联动、操作标准辨认、火灾辨认等平安生产保障性能。 1、实时监管:通过接入在企业厂区门口、厂房、办公楼、周界围墙、仓库等工厂重点区域部署的监控设施,SkeyeVSS视频安防综合治理平台可实现目标区域实时全天候视频监控,打造智慧工厂近程监管模式,可疾速解决、排查前端设施问题,升高运维老本。 2、海量视频文件存储:SkeyeVSS视频安防综合治理平台的对象存储(OSS)可能提供海量的文件存储,同时具备10个9的数据可靠性,并提供同城热备和异地灾备计划,使海量监控视频永恒存储,随时可得。工厂监管人员可随时对工厂各个区域、车间等现场情况及生产过程中遇到的突发安全事故进行起因剖析和视频调阅、问责、取证,实现对生产过程的治理及监督。3、反对子系统交融接入:SkeyeVSS视频安防综合治理平台零碎低耦合,采纳散布式微服务架构,可灵便配置安防零碎中的各个子系统服务,如:(1)报警联动零碎:SkeyeVSS视频安防综合治理平台配置多样化的报警触发规定,如挪动侦测报警、遮挡报警等。当零碎侦测到监控异样时,可实现报警信号联动,上传报警信息,自动弹出报警地位的视频图像,帮助工厂管理人员迅速定位并记录报警信息。(2)火灾预防零碎:SkeyeVSS视频安防综合治理平台可接入火灾预防零碎,依据人工智能技术图像识别算法全自动捕捉浓烟或火苗等违章行为,智能化取得监控视频周边的出现异常点,并产生警示,通告无关人员避免劫难,正当晋升工厂查验高效率,革除安全隐患。(3)操作标准识别系统:零碎可接入各类识别系统,如:工厂作业人员安全帽辨认、口罩辨认、工装穿戴辨认、安全带穿戴辨认等,零碎获取未满足操作标准的行为数据后,信息推送记录至管理人员,从而达到工厂操作标准监管、记录的目标。 3、安全可靠:SkeyeIVMS分布式集群视频云管控零碎采纳Spring Security架构,集流媒体服务集群、视频资源管理、软硬件服务节点保障、容灾双机热备、主备主动切换为一体,保障智慧工厂视频安防零碎的稳固运行,在呈现故障或中断时,能确保数据的准确性和完整性,并能迅速复原运行。 

March 15, 2023 · 1 min · jiezi

关于c++:CSTL教程1vector容器是什么实用教程来啦超简单易懂拿来就用

C++与传统的C语言有一个很大的区别,就是新增了规范模板库 STL(Standard Template Library),它是 C++ 规范库的一部分,不须要独自装置,只须要 #include 对应的头文件即可。 本文将介绍STL中最根底的一个容器:vector 留神:本文仅从入门和实用角度介绍vector的用法。如有不谨严的中央欢送斧正! 引入头文件在应用vector之前须要用#include <vector>来引入头文件。 如果你是比赛选手,也能够用万能头#include <bits/stdc++.h>其中蕴含了<vector>。 vector简介vector能够了解为动静数组,它的大小会随着元素的减少而主动增大。下标从0开始,大小为n的vector的可用范畴是[0, n - 1]。 vector中不仅能够寄存int, char等根底数据类型,还能够寄存构造体、类等等。 然而一个vector中只能寄存一种类型。 初始化 当初咱们能够入手来生成一个vector试试: vector<int> v(3, 5);//生成一个vector,其中有3个值为5的元素二维数组前面会讲到。 遍历数组既然是数组必定少不了遍历嘛对吧~思路是,先用v.size()获取vector的大小,而后用for循环遍历。 //v.size()返回值为unsigned intfor(int i = 0;i < v.size(); ++ i){ printf("%d", v[i]);}//后果为: 5 5 5在C++11之后,能够应用auto关键字进行遍历,这个在前面会讲到。 插入元素void push_back(const T& x)vector有一个叫push_back()的办法,能够在数组尾部插入一个元素且令数组大小+1。举个例子: printf("%d\n", (int)v.size());//v.size() = 3for(int i = 1;i <= 3; ++ i)v.push_back(i);//当初 v 是: 5 5 5 1 2 3//v.size() = 6个别只向尾部插入元素,因为向其余地位插入元素的复杂度较高。 删除元素void pop_back()删除最初一个元素。void clear()清空vector,大小变为0void erase(iter l, iter r)清空迭代器指向的区间$[l, r)$的元素。 ...

March 14, 2023 · 2 min · jiezi

关于c++:通过摄像机视频设备或者流媒体服务器SDK获取到数据转换成RTMP流实现网页手机微信播放

写这篇博客次要是为了给新入门的流媒体开发者解惑,当初看到各种开发者的需要:网页播放RTSP摄像机、微信播放RTSP摄像机、网页播放摄像机SDK输入的视频流、网页播放第三方流媒体平台输入的视频流、包含Github有一些所谓的H5RTSPPlayer,这些都有一个共同点,就是H.264+AAC进行RTMP推流,当然SkeyeWebPlayer除外,SkeyeWebPlayer开创性的反对在浏览器端采纳H5间接播放器RTSP流,下载地址:SkeyeWebPlayer: 永恒收费H5直播点播播放器,反对FLV、HLS、RTSP、WS-FLV、WS-RTSP、WEBRTC、HEVC/H265https://gitee.com/visual-opening/skeyewebplayer 将整个过程进行拆分为二: 一、通过RTSP或者SDK获取H.264视频流和AAC音频流,获取RTSP流咱们能够用ffmpeg、SkeyeRTSPClient(https://gitee.com/visual-opening/SkeyeRTSPClient)等计划,获取SDK流咱们就能够用各个摄像机厂家或者平台厂家提供的各种平台SDK(海康NetSDK、大华NetSDK、雄迈LocalSDK)等等,这些都能无一例外地有独特的流程: 初始化登录设置数据回调开始实时流解决实时返回的音视频数据开释Handle视频数据大多返回的是H.264数据流,音频就各种各样了,有返回G.711、G.726、AAC的,如果是返回的G.711或者G.726时,就须要调用AAC的编码器(如ffmpeg等)将安防的音频流转换成AAC的音频编码格局; 二、将编码好的H.264视频和AAC音频以RTMP/FLV的形式推流到通用的RTMP服务器(nginx-rtmp、srs、SkeyeDSS、CRTMPD等),进行RTMP/HLS的直播,这里就次要是RTMP推流性能的实现,咱们能够采纳ffmpeg、librtmp、SkeyeRTMPPusher等多种计划推流,如果采纳ffmpeg为工具进行RTSP拉流的话,咱们倡议推流也用ffmpeg,这样间接将音频数据在ffmpeg外部进行一次filter转码,就能够实现rtmp的output了,如果采纳librtmp的话,也是比拟好的计划,毕竟市面上90%的RTMP推流都是Based On librtmp,包含SkeyeRTMPPusher,之所以举荐用SkeyeRTMPPusher一方面是因为是SkeyOpenSKEYE团队其余搭档的产品,另一方面,的确好用,有十分齐备的多平台调用示例(Gitee - OpenSKEYE/SkeyeRTMPPusher: SkeyeRTMPPusher是一套调用简略、功能完善、运行高效稳固的RTMP性能组件,通过多年实战和线上运行打造,反对RTMP推送断线重连、环形缓冲、智能丢帧、网络事件回调,反对Windows、Linux、arm(hisiv100/hisiv200/hisiv300/hisiv400/hisiv500/hisiv600/etc..)、Android、iOS平台,反对市面上绝大部分的RTMP流媒体服务器,包含Wowza、Red5、ngnix_rtmp、crtmpserver等支流RTMP服务器,可能完满利用于各种行业的直播需要,手机直播、桌面直播、摄像机直播、课堂直播等等方面! ): SkeyeRTMP_File:读取MP4文件推RTMP直播流,实现虚构直播;SkeyeRTMP_RTSP:读取RTSP流并进行转换成RTMP推流到RTMP服务器;SkeyeRTMP_SDK:调用SDK获取音视频流,再推流RTMP;SkeyeRTMP Android:安卓推送摄像头/屏幕进行RTMP直播;SkeyeRTMP iOS:iOS苹果推送摄像头进行RTMP直播;齐备的调用示例,加上全平台的反对(Windows、Linux、arm、Android、iOS),给了开发者十分好的抉择和帮忙; 依照以上的两个流程,咱们就能够将RTSP/SDK获取到的数据进行RTMP/HLS公布,实现全终端、无插件化的播放了,这也是所谓的H5 RTSP播放器的实现计划,RTSP要H5播放,必须借助于服务器的转换;

March 14, 2023 · 1 min · jiezi

关于c++:SkeyeRTMPPusher推送RTMP扩展支持HEVCH265

不久前刚实现SkeyeRTMPPusher扩大反对h265推送,过后在网上也查找了很多材料,发现都不尽具体,而官网也没有更新对HEVC(H265,后文统称HEVC)tag的反对,反正是走了不少弯路,当然,在宽广网友以及ffmpeg代码的帮忙下我最终实现了通过SkeyeRTMPPusher推送HEVC视频帧数据到SkeyeSMS,这里我将把实现过程具体的记录下来,供宽广网友参考。 首先, RTMP头部信息封装并没有定义HEVC,咱们采纳CDN联盟的HEVC扩大规范,将HEVC的VideoTagHeader定义为12,详见下图:![在这里插入图片形容](https://img-blog.csdnimg.cn/img_convert/3319b358ebe3b50efd59cb16cb18a292.png)而后,咱们在H264封装的根底上进行改良,以反对HEVC头部的封装,而HEVC头有SPS PPS VPS,咱们参考ffmpeg的HEVCDecoderConfigurationRecord构造对metadata进行封装,该构造体代码如下: typedef struct HVCCNALUnitArray { uint8_t array_completeness; uint8_t NAL_unit_type; uint16_t numNalus; uint16_t *nalUnitLength; uint8_t **nalUnit;} HVCCNALUnitArray;typedef struct HEVCDecoderConfigurationRecord { uint8_t configurationVersion; uint8_t general_profile_space; uint8_t general_tier_flag; uint8_t general_profile_idc; uint32_t general_profile_compatibility_flags; uint64_t general_constraint_indicator_flags; uint8_t general_level_idc; uint16_t min_spatial_segmentation_idc; uint8_t parallelismType; uint8_t chromaFormat; uint8_t bitDepthLumaMinus8; uint8_t bitDepthChromaMinus8; uint16_t avgFrameRate; uint8_t constantFrameRate; uint8_t numTemporalLayers; uint8_t temporalIdNested; uint8_t lengthSizeMinusOne; uint8_t numOfArrays; HVCCNALUnitArray *array;} HEVCDecoderConfigurationRecord;参考ffmeg对该构造进行初始化如下: static void hvcc_init(HEVCDecoderConfigurationRecord *hvcc){ memset(hvcc, 0, sizeof(HEVCDecoderConfigurationRecord)); hvcc->configurationVersion = 1; hvcc->lengthSizeMinusOne = 3; // 4 bytes /* * The following fields have all their valid bits set by default, * the ProfileTierLevel parsing code will unset them when needed. */ hvcc->general_profile_compatibility_flags = 0xffffffff; hvcc->general_constraint_indicator_flags = 0xffffffffffff; /* * Initialize this field with an invalid value which can be used to detect * whether we didn't see any VUI (in which case it should be reset to zero). */ hvcc->min_spatial_segmentation_idc = MAX_SPATIAL_SEGMENTATION + 1;}须要留神的是,该构造其余参数咱们其实能够不特地关怀,咱们只须要在HVCCNALUnitArray数组中把HEVC的VPS,SPS和PPS信息填入即可。 ...

March 14, 2023 · 2 min · jiezi

关于c++:SkeyeRTMPLive流媒体直播软件应用解决方案

上一篇咱们讲到了RTMPLive多流媒体协定转无插件直播协定(RTMP)解决方案,当初咱们能够通过RTMPLive将网络摄像机IPC的流,以及网络电视流,或者点播服务器的回放流,甚至本地文件转发成RTMP流,进行互联网直播以及网页无插件观看,本篇将重点讲述RTMPLive软件的理论场景利用。 首先,从Giuhub下载RTMPLive v2.0 64位版本,而后关上软件主界面如下图所示: 如上图所示,软件默认曾经增加几个测试的数据源,当初咱们手动来增加一个网络摄像机,右键点击"数据源列表"上司的“本地数据源”,在弹出菜单中选择“增加数据源”,弹出窗口如下图所示:如上图所示,别离填入数据源名称为“海康IPC”以及数据源URL流地址“rtsp://admin:Ds123456@192.168.0.102:554/h264/ch1/main/av_stream”,点击“确定”保留。 实现后咱们就在“本地数据源”目录下看到咱们新增加的数据源“海康IPC”,点击该数据源,则切换到该数据源通道,因为咱们没有为该通道增加任何的转发任何,所以咱们看到的数据源列表为空; 同时,右键点击该数据源项,弹出菜单咱们可对该项数据源进行编辑和删除。 当初咱们为该数据源通道配置一个转发工作,右键点击工作转发列表框,在弹出菜单中选择“增加工作”或者点击按钮“增加工作”,弹出增加工作对话框如下图所示:输出转发RTMP流地址“rtmp://demo.easydss.com:10085/hls/hikIPC”如上图所示,点击“确定”保留。实现后咱们在转发流列表内就能看到一个转发工作。 抉择刚增加的转发工作项,点击“开始推流”按钮,或者右键点击该转发项,在弹出菜单中当初“开始推流”,同时,咱们也能够在弹出菜单中选择项对该项数据源进行编辑和删除;当转发工作项状态显示为"推流中"的时候,阐明流曾经在转发了,关上SkeyeVSS的后盾,查看推流列表,则能看到咱们刚推送上来的流"hikIPC",如下图所示: 点击播放按钮,即可播放从本地网络摄像机转发到SkeyeVSS的RTMP流,如下图所示:源码地址:https://gitee.com/visual-opening/RTMPLive

March 14, 2023 · 1 min · jiezi

关于c++:思维提升干货All-in6种算法解决LeetCode困难题滑动窗口最大值

为了更好的浏览体验,欢送浏览原文:[[思维晋升|干货All in]6种算法解决LeetCode艰难题:滑动窗口最大值 (eriktse.com)](https://www.eriktse.com/algorithm/1039.html) 最近在leetcode遇到一道十分经典的题目:239. 滑动窗口最大值 - 力扣(LeetCode) 以前只会看题解用枯燥队列做,最近钻研一下发现是一道很好的题,能够帮忙咱们晋升“保护区间最值”的算法思维。 先介绍一下我解决这题所用的算法及其复杂度: 枯燥队列 O(n)st表 O(nlogn)树状数组 O(n(logn)^2)多重集非法 O(nlogn)莫队O (n sqrt{n})优先队列 O(nlogn)首先确定一点,枯燥队列是解决这道题最好的方法,然而其余的办法的适用范围更广。 1、枯燥队列首先能够参考几篇优良的文章: 算法学习笔记(66): 枯燥队列 - 知乎 (zhihu.com) 枯燥队列 - OI Wiki (oi-wiki.org) 我这里提几点值得注意的中央: 1.枯燥队列中寄存的是下标,而不是元素值 2.枯燥队列是一个双端队列,尾插前先查队头后查队尾 3.枯燥队列保护的是元素值的枯燥性 有了这几点留神,代码就很好写了: class Solution {public: static const int maxn = 1e5 + 9; int a[maxn]; deque<int> dq; vector<int> maxSlidingWindow(vector<int>& nums, int k) { vector<int> res; int n = nums.size(); for(int i = 1;i <= n; ++ i)a[i] = nums[i - 1]; for(int i = 1;i <= n; ++ i) { int x = a[i]; while(!dq.empty() and dq.front() < i - k + 1)dq.pop_front(); while(!dq.empty() and x >= a[dq.back()])dq.pop_back(); dq.push_back(i); if(i >= k)res.push_back(a[dq.front()]); } return res; }};我做题习惯把输出数组存一个array,大家请勿介意。 ...

March 14, 2023 · 4 min · jiezi

关于c++:SkeyeRTMPLive多流媒体协议转无插件直播协议RTMP解决方案

随着互联网以及科技程度的倒退,对于互联网服务,PC不再是惟一抉择,智能手机、平板电脑、特定的挪动终端等都是可抉择的用户终端硬件形式,因而,咱们须要一款能将安防协定,电视广播协定以及其余各种格局的流媒体协定接入到互联网上来,通过一种对立格局的协定进行多平台多终端直播。RTMPLive流媒体直播软件(以下简称“RTMPLive”)将RTSP/RTP/RTMP/HTTP/UDP等协定的音视频数据进行拉取、转换,再转换成RTMP的形式推送到RTMP服务器/CDN等。以实现网页端无插件直播以及多平台多终端观看。 RTMPLive反对多种流格局,以及H264,H265多种编码格局推送RTMP;其中蕴含SkeyeStreamClient拉流、SkeyeRTMP推流: SkeyeStreamClient拉流模块智能解析多种流媒体协定格局,反对重连并可能超低延时拉取多种格局流地址并解析出视频帧和音频帧数据;SkeyeRTMPPusher推流模块集成了包含:RTMP协定封装、断线重连、异步推送、环形缓冲区、推送网络拥塞主动丢帧、缓冲区关键帧检索、事件回调(断线、音视频数据回调),反对推流到市面上绝大部分的RTMP流媒体服务器;RTMPLive流媒体直播框架如下:RTMPLive反对多通道拉流集中式治理,以及各通道流可配置化进行分布式转发;其弱小的性能同时反对上千路的拉流转发,各路流转发状态高深莫测,软件界面如下图所示:

March 14, 2023 · 1 min · jiezi

关于c++:SkeyeRTMPClient拉取RTMP流扩展支持HEVCH265解决方案

不久前咱们曾经在RTMP推送端扩大反对了HEVC(H.265 后文统称H265)编码格局,然而,因为RTMP官网指定的协定格局曾经不再更新,官网的播放器的Flash播放器并不反对H265格局的编码数据进行解码播放;当初,咱们须要在播放器端解析RTMP流时对H265编码格局进行扩大反对。 首先,咱们能够通过扩大ffmpeg,让其反对拉H265封装的RTMP流进行解码播放,咱们能够通过金山云对FFmepg的扩大反对H265来解决。 而后,咱们通过批改SkeyeRTMPClient的代码实现对H265 的反对,因为咱们曾经实现了推送端的扩大反对,所以,播放端反对则绝对比较简单;不过仍然有几个方面咱们须要留神;上面将对整个扩大流程进行介绍。 1. 扩大编码ID和推送端一样,须要先扩大反对H265的编码ID,咱们定义为12,如下代码所示: enum FlvVideoCodecId{ e_FlvVideoCodecId_None = 0, e_FlvVideoCodecId_Jpeg = 1, e_FlvVideoCodecId_H263 = 2, e_FlvVideoCodecId_Screen = 3, e_FlvVideoCodecId_Vp6 = 4, e_FlvVideoCodecId_Vp6Alpha = 5, e_FlvVideoCodecId_ScreenV2 = 6, e_FlvVideoCodecId_Avc = 7, // RTMP扩大反对HEVC(H.265) e_FlvVideoCodecId_Hevc = 12, };enum FlvCodeId{ FlvCodeId_Jpeg = 1, FlvCodeId_Sorenson = 2, FlvCodeId_ScreenVideo = 3, FlvCodeId_On2Vp6 = 4, FlvCodeId_On2Vp6Alpha = 5, FlvCodeId_ScreenVideoV2 = 6, FlvCodeId_AVC = 7, // RTMP扩大反对HEVC(H.265) FlvCodeId_Hevc = 12,};2. 扩大H265特有头部数据结构VPS因为H264只有SPS和PPS,所以在兼容H265的时候,咱们须要定义其特有的头VPS: char vps_buf_[MAX_VPS_LEN];//256 int vps_len_; 3. MetaData解析扩大反对H265在推送端,咱们扩大H265发送的MetaData构造定义如下: ...

March 14, 2023 · 2 min · jiezi

关于c++:SkeyeRTMPClient扩展支持HEVCH265解决方案之兼容H264和H265帧数据解析详解-1

在之前两篇对于SkeyeRTMPClient扩大反对HEVC(H.265)解决方案的文章中,咱们曾经实现了对H265的反对,本文次要论述将H26和H265反对兼容起来,实现不同视频编码格局的自适应兼容适配。 1. 依据CodecId判断数据编码类型依据视频编码ID判断视频编码类型,如果视频编码ID==FlvCodeId_Hevc(12),则判断视频编码格局为H265,反之则为H264(因为目前咱们只反对这两种编码格局的视频推送),如下代码所示: parser_VideoTag *video_tag = (parser_VideoTag*)(buf+parser_offset); FlvCodeId video_code_id = (FlvCodeId)(video_tag->code_id&0x0f); if (video_code_id == FlvCodeId_Hevc) { av_frame.u32AVFrameFlag = SKEYE_SDK_VIDEO_CODEC_H265;// HEVC; } else { av_frame.u32AVFrameFlag = SKEYE_SDK_VIDEO_CODEC_H264;// 默认h264, 其余类型是否须要判断?!; }2. 数据帧头部判断依据FLV/RTMP扩大反对H265规范,反对HEVC的VideoTagHeader定义如下图所示:即 当CodecID == 12时,AVCPacketType为HEVCPacketType: 如果HEVCPacketType为0,示意HEVCVIDEOPACKET中寄存的是HEVC sequence header;如果HEVCPacketType为1,示意HEVCVIDEOPACKET中寄存的是HEVC NALU;如果HEVCPacketType为2,示意HEVCVIDEPACKET中寄存的是HEVC end of sequence,即HEVCDecoderConfigurationRecord;而当CodecID == 7时,AVCPacketType为AVCPacketType: 如果AVCPacketType为0,示意HEVCVIDEOPACKET中寄存的是AVC sequence header;如果AVCPacketType为1,示意HEVCVIDEOPACKET中寄存的是AVC NALU;如果AVCPacketType为2,示意HEVCVIDEPACKET中寄存的是AVC end of sequence,即AVCDecoderConfigurationRecord;SkeyeRTMPClient对sequence header的解析函数如下代码段所示: int ParserVideoSequencePacket(FlvCodeId video_code_id, char *buf,int len){ int parser_offset = 0; char *parser_config = buf; if (video_code_id == FlvCodeId_Hevc) { if(len <= sizeof(Parser_HEVCDecoderConfigurationRecord)) { return -1001; } ......’ //Parser HEVCDecoderConfigurationRecord ...... rtmpclient_h265_decode_sps((unsigned char *)sps_buf_, sps_len_, width_, height_); } else { if(len <= sizeof(parser_AVCDecoderHeader)) { return -1001; } ......’ //Parser HEVCDecoderHeader ...... rtmpclient_h264_decode_sps((unsigned char *)sps_buf_, sps_len_, width_, height_); } return 0;}3. 视频数据体帧数据nalu类型判断依据FLV/RTMP扩大反对协定规范,反对H265的VideoTagBody定义如下, 扩大后的VideoTagBody如下图所示(红色字体为HEVC新增内容):: ...

March 14, 2023 · 2 min · jiezi

关于c++:SkeyeRTMPClient扩展支持HEVC解决方案之HEVCDecoderConfigurationRecord结构详解

在上一篇 SkeyeRTMPClient拉取RTMP流扩大反对HEVC(H.265)解决方案 中对于HEVCDecoderConfigurationRecord构造解析的解说存在一些表述上不分明的中央,本文为之续篇,重点对HEVC格局的MetaData构造的解析进行解说。在SkeyeRTMPPusher扩大反对H265的解决方案讲述时。咱们对Metadata构造进行过详解,大家能够回顾一下这篇文章RTMP推送扩大反对HEVC(H265)之Metadata构造填写详解,重点来了,因为失常状况下,咱们只须要从MetaData中取出对咱们解码有用的数据头(即VPS,SPS和PPS),所以咱们对HEVCDecoderConfigurationRecord填充的MetaData其余数据并不关怀,然而,在解析时,咱们须要对该构造所有数据都解析进去,以保障能准确无误的获取到咱们所须要的数据头信息。 再次回顾HEVCDecoderConfigurationRecord构造: typedef struct HEVCDecoderConfigurationRecord { uint8_t configurationVersion; uint8_t general_profile_space; uint8_t general_tier_flag; uint8_t general_profile_idc; uint32_t general_profile_compatibility_flags; uint64_t general_constraint_indicator_flags; uint8_t general_level_idc; uint16_t min_spatial_segmentation_idc; uint8_t parallelismType; uint8_t chromaFormat; uint8_t bitDepthLumaMinus8; uint8_t bitDepthChromaMinus8; uint16_t avgFrameRate; uint8_t constantFrameRate; uint8_t numTemporalLayers; uint8_t temporalIdNested; uint8_t lengthSizeMinusOne; uint8_t numOfArrays; HVCCNALUnitArray *array;} HEVCDecoderConfigurationRecord;而事实上,该构造如果间接填入到MetaData中是不正确的,咱们看ffmpeg中hevc.c文件中的实现,该构造具体申明如下: // The CodecPrivate syntax shall follow the// syntax of HEVCDecoderConfigurationRecord// defined in ISO/IEC 14496-15.//// The number zero (0) shall be written to// the configurationVersion variable until// official finalization of 14496-15, 3rd ed.//// After its finalization, this field and the// following CodecPrivate structure shall// follow the definition of the// HEVCDecoderConfigurationRecord in 14496-15.unsigned int(8) configurationVersion;unsigned int(2) general_profile_space;unsigned int(1) general_tier_flag;unsigned int(5) general_profile_idc;unsigned int(32) general_profile_compatibility_flags;unsigned int(48) general_constraint_indicator_flags;unsigned int(8) general_level_idc;bit(4) reserved = ‘1111’b;unsigned int(12) min_spatial_segmentation_idc;bit(6) reserved = ‘111111’b;unsigned int(2) parallelismType;bit(6) reserved = ‘111111’b;unsigned int(2) chromaFormat;bit(5) reserved = ‘11111’b;unsigned int(3) bitDepthLumaMinus8;bit(5) reserved = ‘11111’b;unsigned int(3) bitDepthChromaMinus8;bit(16) avgFrameRate;bit(2) constantFrameRate;bit(3) numTemporalLayers;bit(1) temporalIdNested;unsigned int(2) lengthSizeMinusOne;unsigned int(8) numOfArrays;for (j=0; j < numOfArrays; j++) { bit(1) array_completeness; unsigned int(1) reserved = 0; unsigned int(6) NAL_unit_type; unsigned int(16) numNalus; for (i=0; i< numNalus; i++) { unsigned int(16) nalUnitLength; bit(8*nalUnitLength) nalUnit; }}从上代码段咱们能够看出,以general_constraint_indicator_flags这个参数为例,构造体申明位宽64,而理论位宽是48,,所以构造体申明的参数位宽和理论位宽可能是不对等的,这就将导致解析MetaData时产生错位,从而解析产生谬误,从而,咱们从新意识HEVCDecoderConfigurationRecord,并申明其构造如下: ...

March 14, 2023 · 2 min · jiezi

关于c++:LyMemory-内核级内存读写驱动

一款完全免费的内核级内存读写工具,可冲破驱动爱护,强制读写应用层任意过程内存数据,驱动工具目前反对读写整数,字节,字节集,单精度浮点数,双精度浮点数,多级偏移读写,取模块地址,调配近程内存等性能,读写效率高,速度快,兼容性好,应用时需本人签名或在测试模式下。 C++ 调用接口目前驱动读写反对的读写函数如下表所示,须要留神的是SwitchDriver在根底版本中不存在,如需应用请购买Pro专业版,专业版与根底版惟一的区别是在读写形式上,专业版具备更强的读写模式,而根底版则只反对Cr3读写模式; 导出函数函数作用BOOL SwitchDriver(PCHAR pSwitch)切换内存条模式(Pro)BOOL SetPid(DWORD Pid)设置全局过程PIDBOOL Read(ULONG64 address, T* ret)自定义读内存BOOL Write(ULONG64 address, T data)自定义读内存BOOL ReadMemoryDWORD(ULONG64 addre, DWORD * ret)读内存DWORDBOOL ReadMemoryDWORD64(ULONG64 addre, DWORD64 * ret)读内存DWORD64BOOL ReadMemoryBytes(ULONG64 addre, BYTE **ret, DWORD sizes)读内存字节BOOL ReadMemoryFloat(ULONG64 addre, float* ret)读内存浮点数BOOL ReadMemoryDouble(ULONG64 addre, double* ret)读内存双精度浮点数BOOL WriteMemoryBytes(ULONG64 addre, BYTE * data, DWORD sizes)写内存字节BOOL WriteMemoryDWORD(ULONG64 addre, DWORD ret)写内存DWORDBOOL WriteMemoryDWORD64(ULONG64 addre, DWORD64 ret)写内存DWORD64BOOL WriteMemoryFloat(ULONG64 addre, float ret)写内存浮点数BOOL WriteMemoryDouble(ULONG64 addre, double ret)写内存双精度浮点数DWORD ReadDeviationMemory32(ProcessDeviationMemory *read_offset_struct)计算32位偏移数据基址DWORD64 ReadDeviationMemory64(ProcessDeviationMemory *read_offset_struct)计算64位偏移数据基址DWORD64 GetModuleAddress(std::string dllname)驱动读取过程模块基地址DWORD64 GetSystemRoutineAddress(std::string funcname)获取零碎函数内存地址DWORD64 CreateRemoteMemory(DWORD length)在对端分配内存空间DWORD DeleteRemoteMemory(DWORD64 address, DWORD length)销毁对端内存新版本读写API接口在读写内存之前须要提前设置过程PID号,前期的调用将不须要再传入过程PID,此类读写适宜长期读,某些FPS射击类游戏的人物数组,3D类游戏坐标因为坐标会频繁挪动,需继续不间断读取,此读写模块将很适,接下来将带大家剖析并简略应用这些API接口实现性能。 ...

March 13, 2023 · 7 min · jiezi

关于c++:驱动开发配置Visual-Studio驱动开发环境

在正式开始驱动开发之前,须要自行搭建驱动开发的必要环境,首先咱们须要装置Visual Studio 2013这款功能强大的程序开发工具,在课件内请双击ISO文件并运行外部的vs_ultimate.exe安装包,Visual Studio的装置十分的简略,您只须要依照提醒全副抉择默认参数即可,依据机器配置不同可能须要期待一段时间; 配置驱动开发环境在正式开始驱动开发之前,须要自行搭建驱动开发的必要环境,首先咱们须要装置Visual Studio 2013这款功能强大的程序开发工具,在课件内请双击ISO文件并运行外部的vs_ultimate.exe安装包,Visual Studio的装置十分的简略,您只须要依照提醒全副抉择默认参数即可,依据机器配置不同可能须要期待一段时间; 接着读者还须要持续装置Windows Driver Kit 8.1工具包,请将该工具包解压缩到桌面,并双击wdksetup.exe进行装置,过程中只须要始终下一步,并期待WDK工具包装置实现; WDK就是内核编程开发工具包,某些读者可能据说过DDK或者IFSDDK,最典型的开发工具包莫过于DDK7600,直到目前此类工具包依然能够失常应用,但并不举荐。 为了能测试驱动程序运行状态,读者需装置VMWare虚拟机,双击附件中的VMware-workstation-full-16.2.4-20089737.exe安装程序始终点击下一步即可,须要留神的是在如下选项中请在增强型键盘驱动程序上打对勾,之后期待装置结束即可; 接着关上VMware虚拟机,并在【文件】处抉择【新建虚拟机】,单机下一步并选中【稍后装置操作系统】,在操作系统抉择页面抉择【Win10 x64】版本。 在硬件配置处,读者可依据本人电脑的配置灵便的抉择,当自定义配置实现后,则虚拟机模板将被创立。 虚拟机模板创立实现后,读者可依据如下配置抉择编辑虚拟机设置,并在磁盘地位处将课件中的cn_windows_10_consumer_editions_version_1903_x64_dvd_8f05241d.iso挂载到虚拟机上; 点击开启虚拟机,并依照提醒将Windows零碎正确的装置,须要留神的是在抉择版本时,读者最好应用教育版与笔者开发环境保持一致,至此只需期待零碎装置结束,依据零碎差别安装时间可能有所差异,耐性期待即可; 当所有装置就绪后咱们须要在零碎中装置VMware Tools工具,该组件在装置后可让虚拟机具备有拖拽上传文件的性能,且鼠标键盘将能够自在切换,该性能是咱们必须要用到的; 装置VMware Tools工具很容易,只须要点击装置菜单,后会在虚拟机中呈现DVD驱动器,此时双击驱动器并依照要求装置即可,装置实现后重启零碎,此时则具备了拖拽上传性能; 当这些都做好当前,倡议用户敞开虚拟机,并点击【虚拟机】菜单,找到【快照】并拍摄一个快照,快照的作用是当虚拟机零碎呈现问题后可疾速复原到初始模式,防止重装系统,在后续课程中读者会呈现无数次的蓝屏,而虚拟机快照的疾速复原性能则是一个很好的抉择; 配置驱动开发模板1.关上Visual Studio开发工具,而后抉择【文件】菜单新建我的项目,并在已装置模板中选中【Visual C++】新建空我的项目,并将项目名称命名为【WinDDK】点击确定。 2.顺次抉择【解决方案视图-源文件-增加新建项】选项卡,或者间接按下Ctrl + Shift + A快捷关上菜单,并创立main.c文件。 3.接着须要批改配置管理器,增加自定义配置管理,抉择【生成-配置管理器-新建】选项卡,此处咱们命名为WinDDK即可。 4.批改配置属性中的【惯例】属性,点击菜单栏中的调试,抉择【WinDDK属性-配置-惯例】批改为标黄处所示内容即可。 5.配置可执行文件门路与导入库门路,这里咱们抉择【配置属性-VC++目录】顺次将如下信息填入配置项。 可执行目录C:\Program Files (x86)\Windows Kits\8.1\bin\x64C:\Program Files (x86)\Windows Kits\8.1\bin蕴含目录C:\Program Files (x86)\Windows Kits\8.1\Include\kmC:\Program Files (x86)\Windows Kits\8.1\Include\sharedC:\Program Files (x86)\Windows Kits\8.1\Include\umC:\Program Files (x86)\Windows Kits\8.1\Include\wdf\kmdf\1.13C:\Program Files (x86)\Windows Kits\8.1\Include\wdf\umdf\2.0C:\Program Files (x86)\Windows Kits\8.1\Include\winrt援用目录C:\Program Files (x86)\Windows Kits\8.1\Lib\win7\km\x64库目录C:\Program Files (x86)\Windows Kits\8.1\Lib\win7\km\x64C:\Program Files (x86)\Windows Kits\8.1\Lib\wdf\kmdf\x64\1.13C:\Program Files (x86)\Windows Kits\8.1\Lib\wdf\umdf\x64\2.0C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\um\x64C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\km\x64当如上文件配置实现后,最终成果如下图所示; ...

March 13, 2023 · 2 min · jiezi

关于c++:Visual-studio配置第三方库的步骤总结

1 设置第三方库的头文件目录(Header文件目录),有两个地位能够设置,两者成果雷同,二选一即可。VC++ Directories中的include directories 或者 C/C++==>General==>addition include directories。 2 设置第三方库的库目录(lib),有两个地位能够设置,两者成果雷同,二选一即可。VC++ Directories中的library directories 或者 Linker==>General==>addition library directories。 就第三方库目录设置的话,1、2中,上述两组地位设置成果是一样的,没有区别。那么,集体的话,习惯哪个就用那个,公司我的项目的话,跟曾经有的设置放弃对立即可。 3 设置第三方库的具体的lib依赖名称。Linker==>Input==>Additional Dependencies,录入2中lib目录下所有的lib文件名称。 4 如果生成的可执行文件,依赖第三方库的可执行文件(exe)或者动态链接库(dll)。把第三方库中的exe或者dll文件,放到咱们的我的项目生成exe所在的目录即可,或者把第三方库的可执行文件(exe)与动态链接库(dll)所在目录,设置为环境变量。 注意事项:在vs界面顶部地位上的configuration与platform选项,与我的项目==>右键==>properties配置界面中的configuration、platform选项要保持一致。不保持一致的话,vs老手可能会误以为对第三方库的配置没有失效。 参考文献:https://blog.csdn.net/Dontla/article/details/122745857 (VS里的VC++属性和C/C++属性有什么区别?_Dontla的博客-CSDN博客)https://www.cnblogs.com/JMLiu/p/7954630.html (VS2010中VC++目录和C/C++之间的区别。VC++ Directories和C/C++的区别。 - J.M.Liu - 博客园)https://blog.csdn.net/HandsomeHong/article/details/114157201 (面向小白visual studio 2019 增加第三方库教程_vs链接第三方库_微妙之二进制的博客-CSDN博客)https://blog.csdn.net/raodotcong/article/details/8998379 (Visual Studio下C++第三方库的配置办法总结_贝塔酷狗的博客-CSDN博客)https://www.jianshu.com/p/92eb3379a381 (Visual Studio配置C++第三方库开发指南 - 简书)

March 4, 2023 · 1 min · jiezi

关于c++:Skeye全景AR及IVMS技术助力地铁安防视频监控系统建设

 地铁近程视频监控零碎是保障城市轨道交通保护和运输平安的重要伎俩,它可能无效地监控各个地铁站的出入情况,为地铁控制中心的调度员、各车站值班员、列车司机等提供无关列车运行、防灾救灾、旅客疏导以及社会治安等方面的视觉信息,是进步行车指挥工作效率的辅助通信工具,为保障地铁平安作出安防奉献。 个别状况,地铁处于关闭环境,人流量大,如果产生突发事件,救济难度极大;另外地铁零碎智能化水平高,软硬件零碎宏大,整个零碎长期处于半自动和全自动运行状态。因而地铁的视频监控零碎,一方面要对人流进行24小时全方位监控,对突发事件做出预警,视频存储7*24小时以便帮助公安人员更直观高效的解决各类安全事件。另一方面随着治安局势的日益严厉,地铁视频监控零碎中,摄像设施数量多,因而对视频监控零碎的稳定性、一致性、并发性、安全性等性能都具备较强要求。 SkeyeARS 全景AR加强监视系统是视开科技携手电子科技大学长三角研究院智能交通研究所独特研制的一款基于宽场景多路视频无缝拼接、视频实时加强、监督指标加强显示、指标主动跟踪、视频存储回放、近程数据传输和多通道全景视频同步显示等性能的综合视频AR加强监视系统,反对全景拼接最大反对程度视场角360度,反对超高清8K全景视频(7680*1080)多路路编码存储与回放,让乘客在享受地铁带来的高速便捷的出行时多一分平安保障。  SkeyeIVMS分布式集群视频云管控平台是一套集流媒体服务集群、视频资源管理、软硬件服务节点保障、容灾双机热备、主备主动切换为一体的综合性流媒体集群管控云服务,零碎采纳Spring Security架构,平安有保障;反对私有云、私有化部署,保障系统的稳定性、数据强一致性、高并发应用性、高安全性等准则。 1、 稳定性:该平台保障系统的可用性,健壮性为准则。任何状况只有部署服务器的散布状况不同,则应用不变,确保地铁场景监控不间断;2、 数据强一致性:在集群部署的零碎中,某个服务在宕机正在解决的业务局部也会容许ZK选举机制进行节点数据的同步,保障用户所看到的与操作的工夫都是统一的;3、 高并发应用性:基于集群的分布式部署架构,利用负载平衡技术,零碎很轻松在高并发场景下应用,正当防止了因地铁监控设施泛滥导致的并发应用问题;4、 安全性:零碎采纳Spring Security架构,零碎接口和验证鉴权更加严格,即便在公共网络中应用,也能保障地铁零碎数据、文件等信息安全不泄露。

March 2, 2023 · 1 min · jiezi

关于c++:SkeyePlayer源码解析系列之支持H265

近期SkeyePlayer(windows)更新已全面反对H265的RTSP流的解码播放,这里就反对H265过程做简要介绍; 一、 libSkeyeRTSPClient库已反对H265视频源的RTSP流的拉取和解析 二、H265头解析 H265和H264相似,不过其NAL type格局更多样化,除了SPS,PPS之外,还减少了VPS,上面就针对H265帧nal 头做简略剖析;首先,看X265源码中的H265nal头格局定义://H265 NAL type//this enum have been defined in x265.htypedef enum tagH265NalUnitType{ NAL_UNIT_CODED_SLICE_TRAIL_N = 0, // 0 NAL_UNIT_CODED_SLICE_TRAIL_R, // 1 NAL_UNIT_CODED_SLICE_TSA_N, // 2 NAL_UNIT_CODED_SLICE_TLA, // 3 // Current name in the spec: TSA_R NAL_UNIT_CODED_SLICE_STSA_N, // 4 NAL_UNIT_CODED_SLICE_STSA_R, // 5 NAL_UNIT_CODED_SLICE_RADL_N, // 6 NAL_UNIT_CODED_SLICE_DLP, // 7 // Current name in the spec: RADL_R NAL_UNIT_CODED_SLICE_RASL_N, // 8 NAL_UNIT_CODED_SLICE_TFD, // 9 // Current name in the spec: RASL_R NAL_UNIT_RESERVED_10, NAL_UNIT_RESERVED_11, NAL_UNIT_RESERVED_12, NAL_UNIT_RESERVED_13, NAL_UNIT_RESERVED_14, NAL_UNIT_RESERVED_15, NAL_UNIT_CODED_SLICE_BLA, // 16 // Current name in the spec: BLA_W_LP NAL_UNIT_CODED_SLICE_BLANT, // 17 // Current name in the spec: BLA_W_DLP NAL_UNIT_CODED_SLICE_BLA_N_LP, // 18 NAL_UNIT_CODED_SLICE_IDR, // 19 // Current name in the spec: IDR_W_DLP NAL_UNIT_CODED_SLICE_IDR_N_LP, // 20 NAL_UNIT_CODED_SLICE_CRA, // 21 NAL_UNIT_RESERVED_22, NAL_UNIT_RESERVED_23, NAL_UNIT_RESERVED_24, NAL_UNIT_RESERVED_25, NAL_UNIT_RESERVED_26, NAL_UNIT_RESERVED_27, NAL_UNIT_RESERVED_28, NAL_UNIT_RESERVED_29, NAL_UNIT_RESERVED_30, NAL_UNIT_RESERVED_31, NAL_UNIT_VPS, // 32 NAL_UNIT_SPS, // 33 NAL_UNIT_PPS, // 34 NAL_UNIT_ACCESS_UNIT_DELIMITER, // 35 NAL_UNIT_EOS, // 36 NAL_UNIT_EOB, // 37 NAL_UNIT_FILLER_DATA, // 38 NAL_UNIT_SEI, // 39 Prefix SEI NAL_UNIT_SEI_SUFFIX, // 40 Suffix SEI NAL_UNIT_RESERVED_41, NAL_UNIT_RESERVED_42, NAL_UNIT_RESERVED_43, NAL_UNIT_RESERVED_44, NAL_UNIT_RESERVED_45, NAL_UNIT_RESERVED_46, NAL_UNIT_RESERVED_47, NAL_UNIT_UNSPECIFIED_48, NAL_UNIT_UNSPECIFIED_49, NAL_UNIT_UNSPECIFIED_50, NAL_UNIT_UNSPECIFIED_51, NAL_UNIT_UNSPECIFIED_52, NAL_UNIT_UNSPECIFIED_53, NAL_UNIT_UNSPECIFIED_54, NAL_UNIT_UNSPECIFIED_55, NAL_UNIT_UNSPECIFIED_56, NAL_UNIT_UNSPECIFIED_57, NAL_UNIT_UNSPECIFIED_58, NAL_UNIT_UNSPECIFIED_59, NAL_UNIT_UNSPECIFIED_60, NAL_UNIT_UNSPECIFIED_61, NAL_UNIT_UNSPECIFIED_62, NAL_UNIT_UNSPECIFIED_63, NAL_UNIT_INVALID,}H265NalUnitType;#endif咱们能够看到其中VPS, SPS和PPS的定义: ...

March 2, 2023 · 5 min · jiezi

关于c++:SkeyePlayer源码解析系列之录像写MP4

SkeyePlayer(Windows)中录像采纳GPAC的MP4Box库来封装MP4,上面我将简略介绍MP4的封装调用流程和须要留神的点; 一、GPAC库的编译,GPAC是跨平台的库,windows和linux都能很不便多编译,再次不做过多赘述,大家可去GPAC官网或者Github上下载; 二、创立MP4 bool SkeyeMP4Writer::CreateMP4File(char*filename,int flag){ SaveFile(); m_audiostartimestamp=-1; m_videostartimestamp=-1; if(filename==NULL) { char filename2[256]={0}; sprintf(filename2,"%d-gpac%d.mp4",time(NULL),rand()); p_file=gf_isom_open(filename2,GF_ISOM_OPEN_WRITE,NULL);//关上文件 }else p_file=gf_isom_open(filename,GF_ISOM_OPEN_WRITE,NULL);//关上文件 if (p_file==NULL) { return false; } gf_isom_set_brand_info(p_file,GF_ISOM_BRAND_MP42,0); //if(flag&ZOUTFILE_FLAG_VIDEO) //{ // m_videtrackid=gf_isom_new_track(p_file,0,GF_ISOM_MEDIA_VISUAL,1000); // gf_isom_set_track_enabled(p_file,m_videtrackid,1); //} //if(flag&ZOUTFILE_FLAG_AUDIO) //{ // m_audiotrackid=gf_isom_new_track(p_file,0,GF_ISOM_MEDIA_AUDIO,1000); // gf_isom_set_track_enabled(p_file,m_audiotrackid,1); //} m_nCreateFileFlag = flag; return true;}创立MP4很简略,调用gf_isom_open函数就能轻松搞定,gf_isom_set_brand_info函数设置以后写MP4的版本为MP4V2;值得注意的中央是: 1>. 创立文件之前须要对所有的参数进行初始化,以及如果文件正在写入则须要将其敞开,这个操作次要是32位程序写的MP4文件大于4G可能呈现不能播放的问题,为了不便写MP4文件进行分片,这个将在系列文章后续中进行解说;2>. 大家能够看到上段代码有屏蔽了局部代码flag&ZOUTFILE_FLAG_VIDEO和flag&ZOUTFILE_FLAG_AUDIO的判断,这两段代码是用来在MP4文件中创立音频轨和视频轨(默认各只创立一个),请留神:如果这里曾经创立了音频和视频轨,然而后续的写入过程中如果只写音频或者视频的话,某些播放器可能是播不进去的(比方windows自带的播放器),所以,如果只写音频的话只须要创立音频轨就能够了,视频同理。三、写入视频H264的SPS和PPS头信息 bool SkeyeMP4Writer::WriteH264SPSandPPS(unsigned char*sps,int spslen,unsigned char*pps,int ppslen,int width,int height){ if (m_nCreateFileFlag&ZOUTFILE_FLAG_VIDEO) { m_videtrackid = gf_isom_new_track(p_file, 0, GF_ISOM_MEDIA_VISUAL, 1000); gf_isom_set_track_enabled(p_file, m_videtrackid, 1); } else { return false; } p_videosample=gf_isom_sample_new(); p_videosample->data=(char*)malloc(1024*1024); p_config=gf_odf_avc_cfg_new(); gf_isom_avc_config_new(p_file,m_videtrackid,p_config,NULL,NULL,&i_videodescidx); gf_isom_set_visual_info(p_file,m_videtrackid,i_videodescidx,width,height); GF_AVCConfigSlot m_slotsps={0}; GF_AVCConfigSlot m_slotpps={0}; p_config->configurationVersion = 1; p_config->AVCProfileIndication = sps[1]; p_config->profile_compatibility = sps[2]; p_config->AVCLevelIndication = sps[3]; m_slotsps.size=spslen; m_slotsps.data=(char*)malloc(spslen); memcpy(m_slotsps.data,sps,spslen); gf_list_add(p_config->sequenceParameterSets,&m_slotsps); m_slotpps.size=ppslen; m_slotpps.data=(char*)malloc(ppslen); memcpy(m_slotpps.data,pps,ppslen); gf_list_add(p_config->pictureParameterSets,&m_slotpps); gf_isom_avc_config_update(p_file,m_videtrackid,1,p_config); free(m_slotsps.data); free(m_slotpps.data); return true;}首先,通过gf_odf_avc_cfg_new()创立一个设置AVC信息的配置构造p_config,而后对构造中指定的信息,如:长,宽,SPS和PPS等要害参数写入配置构造,调用gf_isom_avc_config_update函数写入参数信息;当然这里只是H264格局的参数设置,像其余的格局比方H265的设置也相似,这将在后续系列中进行解说; ...

March 2, 2023 · 3 min · jiezi

关于c++:SkeyePlayer渲染引擎D3DRender实现视频图像高效率渲染解决方案附源码

SkeyePlayer播放器以其低延时播放RTSP、RTMP等流而闻名,而低延时播放除了低延时的拉流库以及高编解码效率外,视频图像的高速渲染也尤为重要; 本篇以及系列文章次要解说SkeyePlayer播放器的渲染引擎D3DRender,该渲染引擎反对基于GDI和D3D两种渲染形式,GDI形式也是咱们熟知的windows图像绘制接口,广泛利用于win32以及MFC界面编程,以及图形元素的绘制;然而因为应用纯软的windows零碎api接口进行绘制,其效率通常比拟低,而D3D能够启用硬件加速,所以,通常其效率会比拟高,上面咱们将具体解说D3DRende视频图像渲染流程。 1. D3DRender初始化D3DRender库别离为GDI和D3D两种渲染模式提供不同API接口进行渲染初始化,调用如下代码段实现其初始化: //创立D3dRender if (pThread->renderFormat == GDI_FORMAT_RGB24) { if (NULL == pThread->d3dHandle) RGB_InitDraw(&pThread->d3dHandle); } else if ( (NULL == pThread->d3dHandle) && ((unsigned int)time(NULL)-deviceLostTime >= 2) ) { D3D_FONT font; memset(&font, 0x00, sizeof(D3D_FONT)); font.bold = 0x00; wcscpy(font.name, TEXT("Arial Black")); font.size = (int)(float)((width)*0.2f);// 32; if (pThread->showOSD) { font.size = pThread->osd.size;// 32; } font.width = (int)(float)((font.size)/2.5f);//13; if (NULL!=pThread->hWnd && (IsWindow(pThread->hWnd)) ) { D3D_Initial(&pThread->d3dHandle, pThread->hWnd, width, height, 0, 1, pThread->renderFormat, &font); }留神D3D_Initial初始化D3D渲染模式须要设置OSD叠加的相干参数,如OSD叠加文字的字体、大小等参数,OSD叠加具体解说将在后续的文章中进行解说,本文不做过多赘述。 ...

March 2, 2023 · 1 min · jiezi

关于c++:SkeyePlayer渲染引擎D3DRender电子放大功能实现解决方案附源码-1

SkeyePlayer依附D3DRender弱小的渲染能力咱们能够实现很多视频编辑性能,比方电子放大性能,本文将深刻D3DRender渲染引擎库代码,重点讲述其如何采纳surface离屏外表技术来实现渲染视频图像出现,以实现在surface上做电子放大缩略图显示等性能。 1. D3DRender初始化D3D创立设施首先,咱们须要创立一个D3D9设施用于操作系统软硬件资源来为咱们的视频渲染服务,这个代码很简略,依照Direct3D教程即可实现,如下代码所示: pD3D = Direct3DCreate9(D3D_SDK_VERSION); if (NULL == pD3D) { errCode = D3D_NOT_ENABLED; return false; } //获取显卡张数 int adaptnum = pD3D->GetAdapterCount(); //_TRACE("共[%d]张显卡.\n", adaptnum); //获取显卡反对的显示格局 if (FAILED(pD3D->GetAdapterDisplayMode(nAdapterNo, &d3dDisplayMode )) ) { __SAFE_RELEASE(pD3D); return false; } //_TRACE("显卡信息: %d X %d\tRefreshRate: %d\tFormat: %d\n", d3dDisplayMode.Width, d3dDisplayMode.Height, d3dDisplayMode.RefreshRate, d3dDisplayMode.Format); if (FAILED(pD3D->CheckDeviceFormat(nAdapterNo, D3DDEVTYPE_HAL, d3dDisplayMode.Format, 0, D3DRTYPE_SURFACE, d3dFormat))) { _TRACE("CheckDeviceFormat 不反对指定的格局..\n"); errCode = D3D_FORMAT_NOT_SUPPORT; __SAFE_RELEASE(pD3D); return false; } //查看输出格局到显示格局的转换是否反对 if ( FAILED(pD3D->CheckDeviceFormatConversion(nAdapterNo, D3DDEVTYPE_HAL, d3dFormat, d3dDisplayMode.Format) ) ) { errCode = D3D_FORMAT_NOT_SUPPORT; __SAFE_RELEASE(pD3D); return false; } //查看是否反对硬件顶点渲染形式 if ( FAILED(pD3D->GetDeviceCaps(nAdapterNo, D3DDEVTYPE_HAL, &d3dCaps) ) ) { errCode = D3D_VERTEX_HAL_NOT_SUPPORT; __SAFE_RELEASE(pD3D); return false; } //检测硬件是否反对变换和灯光 INT vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; if ( d3dCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ) { vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; //硬件反对 } else { vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; //软件反对 } memset(&d3dParameters, 0, sizeof(D3DPRESENT_PARAMETERS)); D3DFORMAT d3dFormatRender = D3DFMT_UNKNOWN; d3dParameters.Windowed = TRUE; d3dParameters.SwapEffect = D3DSWAPEFFECT_DISCARD;//D3DSWAPEFFECT_DISCARD;//D3DSWAPEFFECT_COPY;//D3DSWAPEFFECT_DISCARD; //如果想通过GetBackBuffer取得后备缓冲内容打印屏幕画面,则不能应用 D3DSWAPEFFECT_DISCARD d3dParameters.BackBufferFormat = d3dFormatRender;//D3DFMT_R5G6B5;//D3DFMT_R5G6B5;//D3DFMT_UNKNOWN; //应用桌面格局 d3dParameters.Flags = D3DPRESENTFLAG_VIDEO;//D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;//D3DPRESENTFLAG_VIDEO;// | D3DPRESENTFLAG_LOCKABLE_BACKBUFFER; d3dParameters.BackBufferCount = 1; d3dParameters.BackBufferWidth = width; d3dParameters.BackBufferHeight = height; if (NULL == pD3dDevice) { HRESULT hr = pD3D->CreateDevice(nAdapterNo, D3DDEVTYPE_HAL, m_hWnd, vp, &d3dParameters, &pD3dDevice); if ( FAILED(hr) ) { _TRACE("D3D Create Device fail.."); switch (hr) { case D3DERR_DEVICELOST: { _TRACE("D3DERR_DEVICELOST\n"); } break; case D3DERR_INVALIDCALL: { _TRACE("D3DERR_INVALIDCALL\n"); } break; case D3DERR_NOTAVAILABLE: { _TRACE("D3DERR_NOTAVAILABLE\n"); } break; case D3DERR_OUTOFVIDEOMEMORY: { _TRACE("D3DERR_OUTOFVIDEOMEMORY\n"); } break; default: break; } errCode = D3D_DEVICE_CREATE_FAIL; __SAFE_RELEASE(pD3D); return false; } } if ( FAILED( pD3dDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &pD3dBackBuffer ) ) ) { errCode = D3D_GETBACKBUFFER_FAIL; __SAFE_RELEASE(pD3dDevice); __SAFE_RELEASE(pD3D); return false; } if ( FAILED( pD3dBackBuffer->GetDesc( &surfaceDesc ) ) ) { errCode = D3D_GETBACKBUFFER_FAIL; __SAFE_RELEASE(pD3dDevice); __SAFE_RELEASE(pD3D); return false; }如上代码所示,咱们依据须要渲染的视频长宽创立一个反对硬件加速D3D渲染设施,为视频图像渲染做筹备。 ...

March 2, 2023 · 3 min · jiezi

关于c++:SkeyePlayer插件使用说明

鉴于大家对于SkeyePlayer插件的应用还不太熟悉,特此写一篇插件的应用文档,供大家参考;SkeyePlayer插件有两种,一种是基于IE的ActiveX控件,一种是基于FireFox(也反对多浏览器)的npAPI插件;两种插件均蕴含源码,因为FireBreath框架生成代码比拟多,npAPI插件只蕴含要害代码,后续会提供专门解说FireBreath生成SkeyePlayer npAPI插件的文档,上面咱们开始解说两种插件如何应用。 ActiveX OCX控件 ActiveX控件应用MFC ActiveX框架生成非常简单,这里就不做过多赘述,感兴趣的同学能够下载SkeyePlayer源码;留神,IE11曾经不反对AttachEvent的形式触发浏览器页面事件,所以,须要导出播放事件的同学在前端页面编写的时候须要做好IE11的兼容性解决; npAPI插件 npAPI插件由FireBreath框架生成,该框架提供了十分具体的步骤即可生成咱们想要的播放器插件框架,框架生成好当前将SkeyePlayer源码的SkeyePlayerPlugin目录下的代码替换掉生成的代码即可。插件导出接口阐明 [1] LONG Start(LPCTSTR sURL, LPCTSTR sRenderFormat, LPCTSTR sUserName, LPCTSTR sPassword, LPCTSTR sHardDecord);Start函数对SkeyePlayer_OpenStream函数进行了封装,其作用是关上一个RTSP流; 参数阐明:[sURL]:关上的流地址,以rtsp://结尾;[sRenderFormat]:播放渲染格局,枚举对应格局:D3D反对格局如下: YV12: 0YUY2: 1UYVY: 2A8R8G8B8: 3X8R8G8B8: 4RGB565: 5RGB555: 6GDI反对格局: RGB24: 7留神:OCX接口函数所有参数均为字符串类型,次要是为了不便页面js调用,上面所有接口参数皆是如此; [2] void Config(LPCTSTR sFrameCache, LPCTSTR sPlaySound, LPCTSTR sShowToScale, LPCTSTR sShowStatisticInfo);Config函数配置播放器的一些参数,诸如缓存,是否播放音频,是否按比列显示,是否显示码率信息;参数阐明:[sFrameCache]:缓存帧数,1-n ,该数值越小,延时越小,当然播放画面可能再网络带宽不现实的状况下会比拟卡,反之则延时越大,相应的播放也会比拟晦涩;[sPlaySound]:是否播放音频,1=播放,0=不播放[sShowToScale]:是否按比列显示,1=是 0=否 ,软解码时无效[sShowStatisticInfo]:是否显示码率信息,1=是 0=否 ,软解码时无效 [3] void SetOSD(LPCTSTR show, LPCTSTR alpha, LPCTSTR red, LPCTSTR green, LPCTSTR blue, LPCTSTR left, LPCTSTR top, LPCTSTR right, LPCTSTR bottom, LPCTSTR strOSD);SetOSD是新增接口,用于设置视频显示时的OSD显示;参数阐明:[show]:是否显示OSD,1=显示 0=不显示[alpha]:OSD显示叠加透明度, 0-255,0是齐全通明。255齐全不通明[red]:OSD字幕色彩RGB重量中的R重量,[green]:OSD字幕色彩RGB重量中的G重量,[blue]:OSD字幕色彩RGB重量中的B重量,[left]:OSD基于视频的显示地位坐标的左上角x轴坐标[top]:OSD基于视频的显示地位坐标的左上角y轴坐标[right]:OSD基于视频的显示地位坐标的右下角x轴坐标[bottom]:OSD基于视频的显示地位坐标的右下角y轴坐标[strOSD]:OSD字幕 ...

March 2, 2023 · 1 min · jiezi

关于c++:SkeyePlayer-rtsp播放器源码解析之64位编译方案

SkeyePlayer反对多路拉流播放,而拉流解码的过程须要占用系统资源,而咱们都晓得32位程序最多只能利用4G的内存,而当初动则8G,16G的内存空间是齐全不能被利用的;所以,为了充分利用内存资源,咱们筹备将SkeyePlayer改成64位版本。 新建64位编译平台所有依赖库编译成64位 1> gpac库编译64位 官网下载gpac源码,通过cmake生成对应的VS版本,而后编译64位平台即可;2> libSkeyeAACEncoder库编译64位 3> ffmpeg编译64位 ffmpeg可从官网下载源码,通过穿插编译或者cmake编译64位;4> IntelHardCodec编译64位 5> SaveJPGDll库编译64位 6> FFDecoder编译64位 7> D3DRender编译64位 这里郑重感激Gavin大神百忙之中抽出工夫帮忙编译64位版本库;8> SkeyeRTSPClient编译64位 程序编译64位并兼容32位调试 程序编译很简略,指定编译平台,而后附加对应平台的依赖项的头文件和库文件即可; 通过本次编译64位版本,遇到的几个问题及注意事项: (1)程序依赖的DLL库编译64位的通过要留神其所依赖的库(零碎的或者第三方的)都要编译的64位反对; (2)如果没有源码,由第三方提供的库,如果须要兼容编译,能够通过宏定义的形式解决,如:#ifdef _WIN64#include "D3DRender\include\x64\D3DRenderAPI.h"#pragma comment(lib, "SkeyeRTSPClient/lib/x64/libSkeyeRTSPClient.lib")#pragma comment(lib, "FFDecoder/lib/x64/SkeyeDecoder.lib")#pragma comment(lib, "./D3DRender/lib/x64/D3DRender.lib")#pragma comment(lib, "IntelHardCodec/lib/x64/IntelHardCodec.lib")#pragma comment(lib, "libSkeyeAACEncoder/lib/x64/libSkeyeAACEncoder.lib")// 减少MP4box和SaveJPG库的反对 [9/20/2016 dingshuai]// JPGSave#pragma comment(lib, "SaveJPGDll/lib/x64/SaveJpgDll.lib")#else#include "D3DRender\include\x86\D3DRenderAPI.h"#pragma comment(lib, "SkeyeRTSPClient/lib/x86/libSkeyeRTSPClient.lib")#pragma comment(lib, "FFDecoder/lib/x86/SkeyeDecoder.lib")#pragma comment(lib, "D3DRender/lib/x86/D3DRender.lib")#pragma comment(lib, "IntelHardCodec/lib/x86/IntelHardCodec.lib")#pragma comment(lib, "libSkeyeAACEncoder/lib/x86/libSkeyeAACEncoder.lib")// 减少MP4box和SaveJPG库的反对 [9/20/2016 dingshuai]// JPGSave#pragma comment(lib, "SaveJPGDll/lib/x86/SaveJpgDll.lib")#endif目前x64的零碎已宽泛应用,将来支流程序都应该是64位的,为了充分利用内存资源,也倡议大家都将目前的32位程序升级成64位;最新兼容64和32位的SkeyePlayer将在近期公布。

March 2, 2023 · 1 min · jiezi

关于c++:SkeyePlayer-RTSP播放器源码解析系列之效率优化方案

测试发现,通过SkeyePlayer拉取网络摄像机的流, 其音频可能是G711,G726等,而写MP4或者转推RTMP等都不反对这些音频格式,那么咱们就须要将其音频转码成AAC,能够应用libSkeyeAACEncoder库进行转码,而后写MP4或者推送;然而,在理论利用中,咱们发现转码过程其实还是比拟耗时的,它甚至会导致解码线程来不及从而使直播延时增大,所以,咱们采纳队列缓存+线程的形式来优化录像和抓图。 实现如下: 录像优化1> 开启录像 if (pThread->manuRecording == 0x01 && NULL==pThread->m_pMP4Writer && frameinfo.type==Skeye_SDK_VIDEO_FRAME_I)//开启录制 { //EnterCriticalSection(&pThread->critRecQueue); if (!pThread->m_pMP4Writer) { pThread->m_pMP4Writer = new SkeyeMP4Writer(); } unsigned int timestamp = (unsigned int)time(NULL); time_t tt = timestamp; struct tm *_time = localtime(&tt); char szTime[64] = {0,}; strftime(szTime, 32, "%Y%m%d%H%M%S", _time); int nRecordPathLen = strlen(pThread->manuRecordingPath); if (nRecordPathLen==0 || (pThread->manuRecordingPath[nRecordPathLen-1] != '/' && pThread->manuRecordingPath[nRecordPathLen-1] != '\\') ) { pThread->manuRecordingPath[nRecordPathLen] = '/'; } char sFileName[512] = {0,}; sprintf(sFileName, "%sch%d_%s.mp4", pThread->manuRecordingPath, pThread->channelId, szTime); if (!pThread->m_pMP4Writer->CreateMP4File(sFileName, ZOUTFILE_FLAG_FULL)) { delete pThread->m_pMP4Writer; pThread->m_pMP4Writer = NULL; //return -1; } else { } //LeaveCriticalSection(&pThread->critRecQueue); }2> 录像数据写缓存 ...

March 2, 2023 · 2 min · jiezi

关于c++:SkeyePlayer-RTSP播放器源码解析系列之H264一帧多NAL写MP4录像花屏问题解决方案

接上一篇[SkeyePlayer源码解析系列之录像写MP4]之续篇,咱们来解说一下对于H264编码格局中的一帧多nal(Network Abstract Layer, 即网络形象层),对于H264和NAL,这里援用一段话来科普一下: 【转】 在H.264/AVC视频编码标准中,整个零碎框架被分为了两个层面:视频编码层面(VCL)和网络形象层面(NAL)。其中,前者负责无效示意视频数据的内容,而后者则负责格式化数据并提供头信息,以保证数据适宜各种信道和存储介质上的传输。因而咱们平时的每帧数据就是一个NAL单元(SPS与PPS除外)。在理论的H264数据帧中,往往帧后面带有00 00 00 01 或 00 00 01分隔符,一般来说编码器编出的首帧数据为PPS与SPS,接着为I帧…… 个别状况下,一个H264帧间接以00 00 00 01结尾作为一个NAL作为网络传输单元,而在有些H264的编码器则编码进去的H264帧蕴含了多个NAL,这个时候每个分片的NAL(留神是分片的)则是是以00 00 01结尾作为网络传输单元,通过分片的NAL数据量更小,从而更加不便进行网络;然而,咱们在接管到带有多个NAL的H264帧的时候进行写MP4则不能简略是只通过将头部的00 00 00 01标记转换从AVC的长度标识,而须要将所有的00 00 00 01和00 00 01都须要转换成该NAL单元的长度,否则就会呈现视频解码只能播放头部一小部分,其余局部全副花屏的状况,如下图所示: 说了这么多,大家是否明确了呢,如果不明确的(文字描述比拟虚),咱们间接看SkeyePlayer代码实现: int SkeyeMP4Writer::WriteMp4File(unsigned char* pdata, int datasize, bool keyframe, long nTimestamp, int nWidth, int nHeight){ if (nTimestamp==0||(pdata==NULL)||datasize<=0) { return -1; } int inlen=datasize; unsigned char*pin=pdata; int outlen=0; unsigned char*pout=NULL; bool bend = false; int datalen=0; bool bSPSOrPPS = false; int iOutLen = datasize; unsigned char* pRealData = new unsigned char[datasize<<1]; int nRealDataSize = 0; memset(pRealData,0x00, datasize+4); do { int nal_start = 0; int nal_end = 0; outlen = find_nal_unit(pin,inlen, &nal_start, &nal_end ); if (outlen<=0) { break; } pout = pin+nal_start; if(pout!=NULL) { unsigned char naltype = ( pout[0] & 0x1F); if (naltype==0x07)//0x67 {// m_psps=pout;// m_spslen=outlen; //pout[0] = 0x67; if(m_bwritevideoinfo==false) { m_psps = new unsigned char[outlen]; memcpy(m_psps, pout, outlen); m_spslen=outlen; } bSPSOrPPS = true; } else if (naltype==0x08)//0x68 { // m_ppps=pout; // m_ppslen=outlen; //pout[0] = 0x68; if(m_bwritevideoinfo==false) { m_ppps = new unsigned char[outlen];//outlen memcpy(m_ppps, pout, outlen); m_ppslen = outlen; } bSPSOrPPS = true; }// else if (pout[0] == 0x06)//SEI// {// // }// else { memcpy(pRealData+nRealDataSize, &outlen, 4); //写入头4个字节==nal内容的长度(H264数据的长度) unsigned char byte0 = pRealData[nRealDataSize+3]; unsigned char byte1 = pRealData[nRealDataSize+2]; pRealData[nRealDataSize+3] = pRealData[nRealDataSize+0]; pRealData[nRealDataSize+2] = pRealData[nRealDataSize+1]; pRealData[nRealDataSize+1] = byte1; pRealData[nRealDataSize+0] = byte0; nRealDataSize += 4; memcpy(pRealData+nRealDataSize, pout, outlen); nRealDataSize += outlen; } inlen=inlen-outlen-(pout-pin); pin=pout+outlen; } } while (bend!=true); if (m_bwritevideoinfo==false&&m_ppps&&m_psps) { // PPS开端的0过滤,否则VLC可能播放不进去 [12/22/2015 Dingshuai] int nPPSSize = m_ppslen; int nZeroCount = 0; for (int nI = nPPSSize-1; nI>=0; nI--) { if (m_ppps[nI] == 0x00) { nZeroCount++; } else { break; } } m_ppslen = m_ppslen-nZeroCount; WriteH264SPSandPPS(m_psps,m_spslen,m_ppps,m_ppslen,nWidth,nHeight); m_bwritevideoinfo = true; } if (m_bwritevideoinfo==false||nRealDataSize<=0 ) { return 0;//获取sps pps失败 }// if(/*bSPSOrPPS*/pout[0]==0x67 || pout[0]==0x68)// {// return 0;// } WriteH264Frame(pRealData, nRealDataSize, keyframe, nTimestamp);//左移4单位,加上数据长度头? if (pRealData) { delete []pRealData; pRealData = NULL; } return true;}其中find_nal_unit()函数是从H264帧中剖析出以00 00 00 01和00 00 01结尾的NAL单元,而后间接填充成该NAL单元的长度,留神字节程序为大端程序://写入头4个字节==nal内容的长度(H264数据的长度) ...

March 2, 2023 · 2 min · jiezi

关于c++:C2-C到C的升级

C与C++的关系C++继承了所有的C个性C++在C的根底上提供了更多的语法和个性C++设计指标是运行效率与开发效率的对立 C++强调语言的实用性C++所有的变量都能够再须要应用时在定义C语言中变量都必须在作用域开始的地位定义 register 关键字申请编译器将局部变量存储于寄存器中在C++中仍然反对register关键字 C++编译器有本人的优化形式C语言中无奈获取register变量的地址C++中能够获得register变量的地址 C到C++的降级在C语言中,反复定义多个同名的全局变量是非法的C语言中,多个同名的全局变量会被链接到全局数据区的同一个地址空间上在C++中,不容许多个同名的全局变量 #include <stdio.h>int g_v;//int g_v;int main(int argc, char *argv[]) { printf("Begin..\n"); int c = 0; for (int i = 1; i <= 3; i++) { for (int j = 1; j <= 3; j++) { c += i * j; } } printf("c = %d\n",c); register int a = 0; printf("c=%p\n",&a); printf("end....\n"); return 0;}struct关键字的增强C语言中的struct定义了一组变量的汇合C语言中struct定义的标识符并不是一种新的类型C++中的struct用于定义一个全新的类型C代码: typedef struct _tag_student Student;struct _tag_student{ const char* name; int age; };C++代码: ...

March 1, 2023 · 1 min · jiezi

关于c++:SkeyePlayer-RTSP播放器库API接口说明

概述libSkeyePlayer实现对RTSP直播流进行实时采集和解码显示,稳固,高效,低延时;解码可采纳intel硬件解码和软件解码两种形式,能实时进行录像和快照抓图,OSD叠加等性能。 API接口函数定义 int SkeyePlayer_Init();函数阐明:播放器初始化,播放器应用之前调用;参数阐明:  void SkeyePlayer_Release();函数阐明:播放器资源开释,播放器不再应用当前调用;参数阐明: int SkeyePlayer_OpenStream(const char url, HWND hWnd, RENDER_FORMAT renderFormat, int rtpovertcp, const char username, const char password, MediaSourceCallBack callback, void userPtr, bool bHardDecode);函数阐明:播放器开始进行流播放;返回值为以后播放的通道ID,该ID在进行推流时须要用到;参数阐明:Url:[IN] 字符串类型,示意以后要播放的流地址,Eg: rtsp://127.0.0.1:554/stream.sdpHWnd: [IN] 窗口句柄类型,示意为以后播放器将显示的窗口的句柄;renderFormat:[IN] 播放渲染类型,详见RENDER_FORMAT构造;Rtpovertcp:[IN] 整数型,拉取流的传输模式,0=udp, 1=tcpUsername:[IN] 字符串,拜访流的用户名(如果存在)Password:[IN] 字符串,拜访流的用户名(如果存在)Callback:[IN] 播放器回调音视频数据回调函数userPtr:[IN] 用户自定义传入数据bHardDecode:[IN] 是否采纳硬件解码 1=是,0=否 void SkeyePlayer_CloseStream(int channelId);函数阐明:播放器进行流播放;参数阐明:channelId [IN] 以后播放的流通道ID,该ID是SkeyePlayer_OpenStream函数关上流的返回值;int SkeyePlayer_SetFrameCache(int channelId, int cache);函数阐明:播放器设置以后流播放缓存帧数;参数阐明:channelId [IN] 以后播放的流通道ID,该ID是SkeyePlayer_OpenStream()函数关上 流的返回值;cache [IN] 以后通道的流播放设置的缓存的视频帧数,Eg: 缓存10帧,则cache = 10; int SkeyePlayer_SetShownToScale(int channelId, int shownToScale);函数阐明:播放器按比例进行显示;参数阐明:channelId [IN] 以后播放的流通道ID,该ID是SkeyePlayer_OpenStream()函数关上 流的返回值;shownToScale [IN] 0=整个窗口区域显示,1=按比例显示; ...

March 1, 2023 · 2 min · jiezi

关于c++:SkeyePlayer-RTSP-Windows播放器抓图代码重构

SkeyePlayer RTSP Windows端(下文简称:SkeyePlayer)播放器之前抓图代码次要通过OpenCV来实现,且数据格式转换的效率过于低下;故而在过后的代码中采纳线程机制来解决抓图导致视频播放时卡顿的问题;而最新版的SkeyePlayer为了精简代码也为了进步抓图效率,咱们采纳ffmpeg进行抓图,为了保障视频播放的流畅性,线程机制咱们依然保留。 采纳ffmpeg进行抓图代码如下 // 抓图函数实现int take_snapshot(char *file, int w, int h, uint8_t *buffer, AVPixelFormat Format){ char *fileext = NULL; enum AVCodecID codecid = AV_CODEC_ID_NONE; struct SwsContext *sws_ctx = NULL; AVPixelFormat swsofmt = AV_PIX_FMT_NONE; AVFrame picture = {}; int ret = -1; AVFormatContext *fmt_ctxt = NULL; AVOutputFormat *out_fmt = NULL; AVStream *stream = NULL; AVCodecContext *codec_ctxt = NULL; AVCodec *codec = NULL; AVPacket packet = {}; int retry = 8; int got = 0; // init ffmpeg av_register_all(); fileext = file + strlen(file) - 3; if (_stricmp(fileext, "png") == 0) { codecid = AV_CODEC_ID_APNG; swsofmt = AV_PIX_FMT_RGB24; } else { codecid = AV_CODEC_ID_MJPEG; swsofmt = AV_PIX_FMT_YUVJ420P; } AVFrame video; int numBytesIn; numBytesIn = av_image_get_buffer_size(Format, w, h, 1); av_image_fill_arrays(video.data, video.linesize, buffer, Format, w, h, 1); video.width = w; video.height = h; video.format = Format; // alloc picture picture.format = swsofmt; picture.width = w > 0 ? w : video.width; picture.height = h > 0 ? h : video.height; int numBytes = av_image_get_buffer_size(swsofmt, picture.width, picture.height , 1); buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t)); av_image_fill_arrays(picture.data, picture.linesize, buffer, swsofmt, picture.width, picture.height, 1); // scale picture sws_ctx = sws_getContext(video.width, video.height, (AVPixelFormat)Format/*video->format*/, picture.width, picture.height, swsofmt, SWS_FAST_BILINEAR, NULL, NULL, NULL); if (!sws_ctx) { //av_log(NULL, AV_LOG_ERROR, "could not initialize the conversion context jpg\n"); goto done; } sws_scale(sws_ctx, video.data, video.linesize, 0, video.height, picture.data, picture.linesize); // do encoding fmt_ctxt = avformat_alloc_context(); out_fmt = av_guess_format(codecid == AV_CODEC_ID_APNG ? "apng" : "mjpeg", NULL, NULL); fmt_ctxt->oformat = out_fmt; if (!out_fmt) { //av_log(NULL, AV_LOG_ERROR, "failed to guess format !\n"); goto done; } if (avio_open(&fmt_ctxt->pb, file, AVIO_FLAG_READ_WRITE) < 0) { //av_log(NULL, AV_LOG_ERROR, "failed to open output file: %s !\n", file); goto done; } stream = avformat_new_stream(fmt_ctxt, 0); if (!stream) { //av_log(NULL, AV_LOG_ERROR, "failed to create a new stream !\n"); goto done; } codec_ctxt = stream->codec; codec_ctxt->codec_id = out_fmt->video_codec; codec_ctxt->codec_type = AVMEDIA_TYPE_VIDEO; codec_ctxt->pix_fmt = swsofmt; codec_ctxt->width = picture.width; codec_ctxt->height = picture.height; codec_ctxt->time_base.num = 1; codec_ctxt->time_base.den = 25; codec = avcodec_find_encoder(codec_ctxt->codec_id); if (!codec) { //av_log(NULL, AV_LOG_ERROR, "failed to find encoder !\n"); goto done; } if (avcodec_open2(codec_ctxt, codec, NULL) < 0) { //av_log(NULL, AV_LOG_ERROR, "failed to open encoder !\n"); goto done; } while (retry-- && !got) { if (avcodec_encode_video2(codec_ctxt, &packet, &picture, &got) < 0) { //av_log(NULL, AV_LOG_ERROR, "failed to do picture encoding !\n"); goto done; } if (got) { ret = avformat_write_header(fmt_ctxt, NULL); if (ret < 0) { //av_log(NULL, AV_LOG_ERROR, "error occurred when opening output file !\n"); goto done; } av_write_frame(fmt_ctxt, &packet); av_write_trailer(fmt_ctxt); } } // ok ret = 0;done: avcodec_close(codec_ctxt); if (fmt_ctxt) { avio_close(fmt_ctxt->pb); } avformat_free_context(fmt_ctxt); av_packet_unref(&packet); sws_freeContext(sws_ctx); av_free(buffer); return ret;}借助ffmpeg弱小的视频解决和转换性能,咱们能够将一帧图像转换成任意格局的图片,当然如代码所示咱们只选择性地反对了“jpeg”和“png”两种格局的图片格式;采纳ffmpeg抓图的步骤分两步: ...

March 1, 2023 · 3 min · jiezi

关于c++:SkeyePlayer-RTSP-Windows播放器OSD字幕叠加接口方法和使用效果全解析

SkeyePlayer RTSP Windows播放器新增OSD字幕叠加接口办法,这个接口和码率信息显示接口办法相似,都是调用FFRender库的接口实现的多OSD叠加,上面解说下该办法的调用和注意事项; OSD叠加办法申明如下: LIB_SkeyePLAYER_API int SkeyePlayer_ShowOSD(int channelId, int show, Skeye_PALYER_OSD osd);其中,channelId: 播放器通道ID,标识以后的播放器实例;show:标识是否显示OSD叠加,0=不事实 1=显示osd:显示信息填充构造,定义如下: typedef struct tagSkeye_PALYER_OSD { char stOSD[1024]; //OSD字幕信息 DWORD alpha; //通明通到0-255 0=通明 255=齐全不通明DWORD color; //RGB(0xf9,0xf9,0xf9) DWORD shadowcolor; //OSD背景色彩RGB(0x4d,0x4d,0x4d) 全为0背景通明 RECT rect; //OSD基于图像右上角显示区域 int size; //OSD字体的大小 }Skeye_PALYER_OSD; 留神:osd字幕叠加通过”\r\n“结束符进行换行,一行的长度不能超过128个字节,总的OSD叠加不能超过1024个字节。其中OSD大小设置只有D3D渲染模式能力失效; 咱们曾经对OSD叠加的接口有所理解,上面咱们来写一段调用代码来看看成果: //OSD Example Skeye_PALYER_OSD osd; osd.alpha = 255; osd.size = 35; osd.color = RGB(255,0,255); osd.rect.left = 10; osd.rect.right = 5000; osd.rect.top = 100; osd.rect.bottom = 800; osd.shadowcolor = RGB(0,0,0); char* ss = "这是SkeyePlayer-RTSP-Win播放器 \r\n的字幕叠加接口的成果!!!\r\n以\"\\r\\n\"为换行完结符号\r\n留神:每行的长度不能超过128个字节\r\n总的OSD长度不能超过1024个字节"; strcpy(osd.stOSD ,ss); SkeyePlayer_ShowOSD(m_ChannelId, 1, osd);如上代码段所示,次要对OSD_PLAYER_OSD构造的参数进行设置,就能失去咱们想要的成果,如下图所示:(1)GDI显示[外链图片转存中...(img-6MyHsd96-1652407820539)] ...

March 1, 2023 · 1 min · jiezi

关于c++:SkeyePlayer-RTSP-Windows播放器D3DGDI的几种渲染方式的选择区别-2

SkeyePlayer RTSP windows播放器反对D3D和GDI两种渲染形式,其中D3D反对格局如下: DISPLAY_FORMAT_YV12 DISPLAY_FORMAT_YUY2 DISPLAY_FORMAT_UYVY DISPLAY_FORMAT_A8R8G8B8 DISPLAY_FORMAT_X8R8G8B8 DISPLAY_FORMAT_RGB565 DISPLAY_FORMAT_RGB555 GDI反对格局如下: DISPLAY_FORMAT_RGB24_GDIGDI渲染形式则是咱们熟知的采纳GDI进行图像绘制,其劣势就是通用性强,只有是目前罕用的windows操作系统基本上都反对;其劣势就是效率比拟低下,也只反对RGB24一种色调格局显示;D3D渲染形式则刚好相同,其劣势就是效率比拟高,反对多种色调格局进行渲染;劣势就是通用性较差,windows零碎必须要反对D3D才能够应用,须要肯定的硬件撑持。 GDI渲染格局界面抉择如下: D3D渲染格局界面抉择如下: 从界面上能够看出GDI形式的OSD字幕叠加比D3D形式有显著的区别,从SkeyePlayer渲染库FFRender的接口能够看进去两种形式尽管出现形式相似,然而接口是齐全不同的,这再渲染形式和OSD叠加的时候都能够看出显著的区别,这些区别再后文OSD叠加解说的时候也会提到;二者的性能比拟如下: SkeyePlayer拉的同样的流,上图是GDI渲染形式,下图是D3D渲染形式;咱们能够看出GDI形式所耗费的CPU资源均比D3D形式要高,然而不耗GPU,正好相同, D3D形式占用的CPU资源低,然而比拟耗GPU; 综上所述,应用SkeyePlayer抉择渲染形式的时候,如果电脑的配置较低抉择D3D会占用较低的资源,如果电脑不反对D3D渲染的话也只能抉择GDI渲染形式;当然如果机器反对D3D也配置还能够的话两种形式都没有显著的区别;

March 1, 2023 · 1 min · jiezi

关于c++:SkeyeLive中DShow本地采集视频参数设置及可能出现的错误提示详解

在近期公布的SkeyeLive多窗口版本中,因为界面的局限性,选择性的将本地采集的音视频参数设置在界面上剔除掉了(临时还没想好放在哪里,后续版本会在界面调整后增加),大家能够查看SkeyeLive源码中的StartDSCapture函数的参数设置代码进行相应设置;也应近期SkeyeSMS流媒体服务器群(QQ群:102644504,欢送大家进群交换,任何技术问题,在所知的范畴内都能给予解答)中大家提到的参数设置的谬误提醒以及参数该如何设置的问题,上面我将就参数设置的具体细节和代码关联做具体介绍。 一、查看设施属性 在Windows零碎中大多数音视频采集设施都能反对通过DirecShow进行捕捉,在捕捉之前,咱们首先应该理解设施的性能参数,如:视频采集设施的长,宽,色调格局,图像格式等,以及音频设备的采样率,采样位宽,声道数等;要理解这些,咱们能够简略的通过一款微软提供的DShow设施采集小工具“amcap.exe”查看,如下图所示:在“设施”菜单栏中能够查看到枚举的以后零碎中所有的音视频采集设施(包含虚构设施),抉择一个设施即可进行捕捉,而后在“选项”一栏中能够进行“预览”或者相应的参数设置,如下图所示:其中,“视频捕捉接口”项是咱们常常用到的查看视频的长,宽,色调格局,图像格式等参数的设施信息,如下图所示:此外,还有其余一些图像参数的设置,有趣味的也能够理解下;当然,咱们齐全能够通过代码来实现这个小工具的性能,在我的另一片文章“SkeyLive中DirectShow采集音视频流程及几种采集形式介绍”中有具体的阐明,这里就不做过多赘述。 二、DShow采集参数设置 参数设置咱们定义了一个构造,其原型如下: // 设施捕捉参数信息typedef struct tagDEVICE_CONFIG_INFO{ int nVideoId;//视频 ID -1==不必,0-n == id int nAudioId;//音频id -1==不必,0-n == id int nDeviceId;//设施序号 1,2,3,4,5.... struct tagVideoInfo { int nWidth;//视频高度 int nHeight;//视频宽度 int nVideoWndId;//视频关联窗口ID int nFrameRate;//视频帧率 char strDataType[64];//标识 色调格局 数据类型,如"YUY2" "YUYV" "UYVY等" int nRenderType;//出现形式 int nPinType;//捕捉口 }VideoInfo; struct tagAudioInfo { int nChannaels;//通道数 int nBytesPerSample;//采样位数 int nSampleRate;//采样率 DWORD nAudioBufferType;//音频缓存区的大小 int nPinType;//捕捉口 }AudioInfo;}DEVICE_CONFIG_INFO;构造中参数均有具体阐明,这里就比拟重要的色调格局进行举例说明:-> 色调格局(strDataType)色调格局是指DShow捕捉输入的图像数据的色调格局,次要是RGB和YUV两种,这个参数很重要,在显示和编码时咱们都会用到,设置不当可能导致色调显示不对甚至未知的程序解体(个别是指针越界),在SkeyeStreamPusher中反对两种罕用的格局YUY2和RGB24,次要是编码时须要进行格局转换或者分支解决。 咱们在程序中中进行了简略的设施配置: //1. 咱们来简略配置一个设施信息 m_sDevConfigInfo.nDeviceId = 1; m_sDevConfigInfo.nVideoId = nCamId;//摄像机视频捕捉ID m_sDevConfigInfo.nAudioId = nAudioId;//音频捕捉ID m_sDevConfigInfo.VideoInfo.nFrameRate = nFps; m_sDevConfigInfo.VideoInfo.nWidth = nVideoWidth; m_sDevConfigInfo.VideoInfo.nHeight = nVideoHeight; strcpy_s(m_sDevConfigInfo.VideoInfo.strDataType, 64, "YUY2"); m_sDevConfigInfo.VideoInfo.nRenderType = 1; m_sDevConfigInfo.VideoInfo.nPinType = 1; m_sDevConfigInfo.VideoInfo.nVideoWndId = 0; m_sDevConfigInfo.AudioInfo.nAudioBufferType = 4096; m_sDevConfigInfo.AudioInfo.nBytesPerSample = 16; m_sDevConfigInfo.AudioInfo.nSampleRate = 16000;//44100; m_sDevConfigInfo.AudioInfo.nChannaels = 2; m_sDevConfigInfo.AudioInfo.nPinType = 2; //初始化Pusher构造信息 memset(&m_mediainfo, 0x00, sizeof(Skeye_MEDIA_INFO_T)); m_mediainfo.u32VideoCodec = Skeye_SDK_VIDEO_CODEC_H264;//0x1C; m_mediainfo.u32VideoFps = nFps; m_mediainfo.u32AudioCodec = Skeye_SDK_AUDIO_CODEC_AAC; m_mediainfo.u32AudioChannel = 2; m_mediainfo.u32AudioSamplerate = 16000;//44100;如果参数设置不胜利,将会呈现谬误(或者正告)提醒,有一种提醒是设施不反对外部显示(经测试某些虚构设施可能呈现),这在DShow采集库底层代码中曾经做过解决,进行内部关上窗口显示,不影响采集;另有一种提醒为“应用默认参数”,这种提醒就表明咱们设置的参数失败了,可能是设施不反对这种色调格局或者分辨率;当然,捕捉设施通常是胜利的,然而,要留神默认的色调格局和分辨率将有可能和咱们的设置参数不统一,这时候就要理解设施的具体参数,而后在编码时进行相应解决,否则可能导致编码失败,或者编码后推送进去的视频色调不对。参数设置代码如下: ...

February 28, 2023 · 1 min · jiezi

关于c++:SkeyeLive中DirectShow采集音视频流程及几种采集方式介绍

前段时间SkeyeLive凋谢了DirectShow采集库,这个库底层采纳DirectShow SDK的接口实现音视频的预览(播放)和采集;很多人可能还不太理解这个封装库的回调形式和之前的DShow线程采集形式有什么不同,或者说对DirectShow的采集流程还不太熟悉,上面我将就Windows平台下用应用DirectShow的过滤器(滤波器)进行流媒体开发的前端采集局部进行简要介绍,如果大家想深刻的学习和摸索,举荐大家去看看《Visual C++音频/视频解决技术及工程实际》这本书,第9章有具体的流程解说。 一、枚举采集设施 应用采集设施前,须要首先确定零碎曾经装置的采集设施:视频、音频采集设施。零碎设施枚举器为按类型枚举已注册在零碎中的滤波器提供了对立的办法。而且它可能辨别不同的硬件设施,即使是同一个滤波器反对它们。这对那些应用Windows驱动模型、KSProxy Filter的设施来说是十分有用的,零碎设施枚举器对它们按不同的设施实例进行看待。 当利用零碎设施枚举器查问设施的时候,零碎设施枚举器为特定类型的设施(如音频捕捉和视频压缩)生成了一张枚举表(Enumerator)。类型枚举器(Category Enumerator)为每个这种类型的设施返回一个Moniker,类型枚举器主动把每种即插即用的设施蕴含在内。 调用规范办法CoCreateInstance生成零碎设施枚举器(Device Enumerator),类标识(CLSID)为CLSID_SystemDeviceEnum,办法如下: CAMERA_LIST_T *CCameraDS::GetCameraList(){ if (NULL != cameraList.pCamera || cameraList.count > 0) { return &cameraList; } if (NULL == cameraList.pCamera) { cameraList.count = 0; // enumerate all video capture devices CComPtr<ICreateDevEnum> pCreateDevEnum; HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void**)&pCreateDevEnum); CComPtr<IEnumMoniker> pEm; hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEm, 0); if (hr != NOERROR) { return &cameraList; } pEm->Reset(); CAMERA_INFO_T *pCameraList = cameraList.pCamera; CAMERA_INFO_T *pCameraInfo = NULL; ULONG cFetched; IMoniker *pM = NULL; while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK) { pCameraInfo = new CAMERA_INFO_T; memset(pCameraInfo, 0x00, sizeof(CAMERA_INFO_T)); IPropertyBag *pBag=0; hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag); if(SUCCEEDED(hr)) { VARIANT var; var.vt = VT_BSTR; hr = pBag->Read(L"FriendlyName", &var, NULL); //还有其余属性,像形容信息等等... if(hr == NOERROR) { //获取设施名称 WideCharToMultiByte(CP_ACP,0,var.bstrVal,-1,pCameraInfo->friendlyName, sizeof(pCameraInfo->friendlyName) ,"",NULL); SysFreeString(var.bstrVal); } pBag->Release(); cameraList.count++; { pCameraList = cameraList.pCamera; if (NULL == cameraList.pCamera) cameraList.pCamera = pCameraInfo; else { while (NULL != pCameraList->pNext) { pCameraList = pCameraList->pNext; } pCameraList->pNext = pCameraInfo; } } } pM->Release(); } pCreateDevEnum = NULL; pEm = NULL; } return &cameraList;}这是视频设施枚举局部,当然音频也是一样的,只须要把CreateClassEnumerator函数的第一个参数换成CLSID_AudioInputDeviceCategory即可。留神:调用ICreateDevEnum::CreateClassEnumerator办法生成类型枚举器,参数为用户想要失去的类的ID(CLSID),该办法返回一个IEnumMoniker接口指针。如果指定的类型是空的或不存在,则函数ICreateDevEnum::CreateClassEnumerator将返回S_FALSE而不是错误代码,同时IEnumMoniker指针也是空的,这就要求咱们在调用CreateClassEnumerator的时候明确用S_OK进行比拟而不应用宏SUCCEEDED;(扯远了...)而在SkeyeLive中还提供了另外一种枚举音频设备的形式,那就是采纳DirectSound的枚举形式:DirectSoundCaptureEnumerate这个函数来实现的,须要留神,这个函数枚举出的设施GUID有可能是空的,设施名称可能表象为”声卡主设施驱动“,经测试:这个设施是不能用于采集,也是不存在的,枚举过程中应该抛弃。当然,其实DirectShow也是封装了底层的DirectSound的接口来实现的COM接口的对立封装。(须要重点阐明的是:枚举设施这一块不是DShow封装库中的代码,这是由咱们EasyDarwin团队的Gavin大神之前的DShow采集局部代码中提供的(前身是EasyCamera_win),我只是鸠占鹊巢的给大家解说,向大神致敬~~~哈哈哈!) ...

February 28, 2023 · 5 min · jiezi

关于c++:SkeyeLive同屏直播库库功能介绍和接口说明与调用

SkeyeLive是OpenSKEYE开源流媒体团队开发的一个集采集,编码,RTSP/RTMP推流和流媒体RTSP服务于一身的通用库,目前反对Windows,Android平台,通过SkeyeLive咱们就能够防止接触到稍显简单的音视频源采集,编码和流媒体推送以及RTSP/RTP/RTCP服务流程,只须要调用SkeyeLive的几个API接口,就能轻松、稳固地把流媒体音视频数据推送给SkeyeSMS服务器以及公布RTSP服务,RTSP服务反对组播和单播两种模式,可用于同屏直播,延时在300ms以内。 libSkeyeLive API接口阐明: · SkeyeLive_Create 接口申明: LIB_SKEYELIVE_API SKEYELIVE_HANDLE SkeyeLive_Create();接口阐明:创立一个SkeyeLive实例句柄,并返回,在性能模块接口调用时都要用到,一个句柄代表一个实例,该函数能够屡次调用,用SkeyeLive_Release接口进行销毁。 · SkeyeLive_Release 接口申明: LIB_SKEYELIVE_API void SkeyeLive_Release(SKEYELIVE_HANDLE handler);接口阐明:销毁一个由SkeyeLive_Create()创立的实例;参数阐明:handler: [IN] SKEYELIVE_HANDLE(void*)类型, 由SkeyeLive_Create()创立的实例句柄 · SkeyeLive_StartCapture 接口申明: LIB_SKEYELIVE_API int SkeyeLive_StartCapture(SKEYELIVE_HANDLE handler, SOURCE_TYPE eSourceType, int nCamId, int nAudioId, SKEYELIVE_HANDLE hCapWnd, int nEncoderType, int nVideoWidth=640, int nVideoHeight=480, int nFps=25, int nBitRate=2048, char* szDataType = "YUY2", //VIDEO PARAM int nSampleRate=44100, int nChannel=2 );接口阐明:开始一个数据源的采集,并初始化编码器;采集胜利返回1,采集失败返回负值,正在采集返回0;参数阐明:handler:[IN] SKEYELIVE_HANDLE(void*)类型, 由SkeyeLive_Create()创立的实例句柄eSourceType:[IN] 数据源类型,参考以下数据结构:typedef enum tagSOURCE_TYPE{ SOURCE_LOCAL_CAMERA = 0,//本地音视频SOURCE_SCREEN_CAPTURE =1,//屏幕捕捉SOURCE_FILE_STREAM = 2, //文件流推送(mp4,ts,flv???)SOURCE_RTSP_STREAM=3,//RTSP流SOURCE_RTMP_STREAM=4,//RTMP流// Any other Source to push}SOURCE_TYPE; ...

February 28, 2023 · 2 min · jiezi

关于c++:SkeyeLive开源流媒体同屏直播软件源码功能框架解析

SkeyeLive是OpenSKEYE开源流媒体团队开发的一款功能丰富的开源PC端流媒体推流拉流直播软件我的项目,目前反对Windows、Android版本,后续将反对ios版本,其中Windows版本的SkeyeLive反对多种源接入,包含Windows摄像头、麦克风、RTSP摄像机、屏幕桌面等,采集后通过x264编码、SkeyePusher推送到OpenSKEYE流媒体服务器进行转发,同时SkeyeLive还反对通过SkeyeRTSPClient拉取OpenSKEYE直播流,进行显示、播放,十分稳固、易用,用户能够基于SkeyeLive我的项目,开发相似于课堂直播、视频对讲等我的项目! 性能解说 一、程序框架SkeyeLive次要包含三个模块:采集、推送和直播,次要性能封装治理类Class CSourceManager中实现,接口非常简单,各模块别离提供开始和完结接口函数,不便界面调用;界面调用接口: //开始捕捉(采集) int StartCapture(SOURCE_TYPE eSourceType, int nCamId, int nAudioId, HWND hCapWnd, char* szURL, int nVideoWidth, int nVideoHeight, int nFps=, int nBitRate); //进行采集 void StopCapture(); //开始推流 int StartPush(char* ServerIp, int nPushPort, char* sPushName, int nPushBufSize = 1024); //进行推流 void StopPush(); //开始播放 int StartPlay(char* szURL, HWND hShowWnd); //进行播放 void StopPlay();1、采集模块采集分为本地音视频采集和RTSP流采集本地音视频次要通过DShow进行采集,函数如下: int CSourceManager::StartDSCapture(int nCamId, int nAudioId,HWND hShowWnd,int nVideoWidth, int nVideoHeight, int nFps, int nBitRate)该函数次要实现本地音视频采集和音视频编码器的初始化(详见SkeyeLive源码),须要留神的是这里的参数设置: (1) 本地采集的视频宽高和x264编码器的宽高需统一,数据格式倡议设为YUY2(程序中默认为“YUY2"),因为在DShow的数据采集线程中须要进行编码前的格局转换(YUY2->I420),如果格局不对立,这里将要重写转换函数; (2) 本地音频采样率默认为16000,这个设置在SkeyePusher中体现最佳,其余采样率还有待测试; (3) 其余设置请参照DEVICE_CONFIG_INFO构造和Encoder_Config_Info构造的具体阐明; ...

February 28, 2023 · 1 min · jiezi

关于c++:SkeyeExPlayerWindows开发之跨语言调用

上面咱们来解说一下对于SkeyeExPlayer接口的调用,次要分为C++和C#两种语言,C++也能够基于VC和QT进行开发,C++以VC MFC框架为例进行解说,C#以Winform框架为例进行解说。 VC开发SkeyeExPlayer 首先建一个基于MFC Dialog的工程,取名叫SkeyeExPlayer,对于界面逻辑的处理过程就不做过多赘述了,大家有趣味的能够去看SkeyeExPlayer我的项目代码;上面咱们解说一下SkeyeExPlayer的调用流程: (1) 关上一个流或文件进行播放 通过SkeyeExPlayer_Open关上一个流或者本地文件,关上当前即播放,不须要调用SkeyeExPlayer_Play办法,调用实现后,留神,比方拉网络流的时候,因为Open函数是非阻塞而立刻返回的,所以,播放过程可能尚未初始化实现,从而获取流的信息可能获取不到,正确的做法是在线程或者计时器外面轮询获取;Open实现后,咱们能够对一些参数进行设置,比方设置OSD和图片叠加,显示模式,音量大小等: // player open file m_player = SkeyeExPlayer_Open(str, m_stcVideoWnd->GetSafeHwnd()); if (m_player) { m_bPlayPause = FALSE; SetTimer(TIMER_ID_PROGRESS, 1000, NULL); m_bOpening = TRUE; //字幕和图片叠加 // // SkeyeExPlayer_SetLogo 设置台标/LOGO // player - 指向 SkeyeExPlayer_Open 返回的 player 对象 // bIsUse - 是否应用水印 1=启用 0=不启用 // ePos - 台标地位:1==leftttop 2==righttop 3==leftbottom 4==rightbottom // eStyle - 水印的格调,见WATERMARK_ENTRY_TYPE申明 // x - 水印左上角地位x坐标 // y - 水印左上角地位y坐标 // width - 宽 // height - 高 // logopath - 水印图片门路 SkeyeExPlayer_SetLogo (m_player, 1, 2, 3, 0, 0, 0, 0, "C:\\Dingshuai\\Project\\Skeyelogo.png"); // SkeyeExPlayer_SetOSD 设置叠加字幕 // player - 指向 SkeyeExPlayer_Open 返回的 player 对象 // bIsUse - 是否应用水印 1=启用 0=不启用 -1=删除 // nMoveType - 挪动类型:0--固定地位,1--从左往右,2--从右往左, // R,G,B - 字体色彩对应三个重量红绿蓝0-255 // x - 字幕显示左上角地位x坐标 // y - 字幕显示左上角地位y坐标 // weight - 字体权重,见如下申明 // /* Font Weights */ // #define FW_DONTCARE 0 // #define FW_THIN 100 // #define FW_EXTRALIGHT 200 // #define FW_LIGHT 300 // #define FW_NORMAL 400 // #define FW_MEDIUM 500 // #define FW_SEMIBOLD 600 // #define FW_BOLD 700 // #define FW_EXTRABOLD 800 // #define FW_HEAVY 900 // #define FW_ULTRALIGHT FW_EXTRALIGHT // #define FW_REGULAR FW_NORMAL // #define FW_DEMIBOLD FW_SEMIBOLD // #define FW_ULTRABOLD FW_EXTRABOLD // #define FW_BLACK FW_HEA // width - 宽 // height - 高 // fontname - 字体名称,如“宋体”“楷体”“隶书”“华文行楷”...... // tittleContent - OSD显示内容 SkeyeExPlayer_SetOSD (m_player, 1, 1, 0, 255, 0, 700, 100, 200, 0, -29, "隶书", "你说你爱了不该爱的人,你的心中满是创痕!"); }(2) 暂停和单步播放 ...

February 28, 2023 · 6 min · jiezi

关于c++:SkeyeExPlayerWindows开发之接口说明

SkeyeExPlayer(windows)接口阐明如下: SkeyeExPlayer_Open 阐明:关上一个媒体流或者媒体文件进行播放,同时返回一个 player 对象指针参数阐明: fileUrl - 文件门路(能够是网络流媒体的 URL) hWnd - Win32 的窗口句柄/其余平台渲染显示设施句柄 返回值: Easy_PlayerPro_Handle 指针类型,指向 SkeyeExPlayer 对象句柄申明如下:SkeyeExPlayer_Handle SkeyeExPlayer_Open(char *fileUrl, EASY_HANDLE hWnd);SkeyeExPlayer_Close 阐明: 敞开播放参数阐明: player - 指向 SkeyeExPlayer_Open 返回的 player 对象 申明如下: void SkeyeExPlayer_Close(SkeyeExPlayer_Handle player);SkeyeExPlayer_Play 阐明:开始播放,留神:媒体流或者文件关上后不须要调用此函数即开始播放,此函数在暂停、单步播放的时候调用,返回失常播放逻辑参数阐明: player - 指向 SkeyeExPlayer_Open 返回的 player 对象 申明如下: void SkeyeExPlayer_Play(SkeyeExPlayer_Handle player);SkeyeExPlayer_StepPlay 阐明:单步播放,一次播放一帧,调用SkeyeExPlayer_Play返回失常播放参数阐明: player - 指向 SkeyeExPlayer_Open 返回的 player 对象申明如下: void SkeyeExPlayer_StepPlay(SkeyeExPlayer_Handle player);SkeyeExPlayer_Pause 阐明:暂停播放,调用SkeyeExPlayer_Play返回失常播放参数阐明: player - 指向 SkeyeExPlayer_Open 返回的 player 对象申明如下:void SkeyeExPlayer_Pause(SkeyeExPlayer_Handle player);SkeyeExPlayer_Seek 阐明:播放进度跳转到指定地位参数阐明: ...

February 28, 2023 · 3 min · jiezi

关于c++:SkeyeExPlayerWindows开发之ffmpeg-log输出报错

SkeyeExPlayer次要基于ffmpeg进行开发,在SkeyeExPlayer开发过程中,曾遇到一个绝对比拟辣手的问题,该问题个别在播放不是很规范的流或者网络状况较差,容易呈现丢帧的状况特地容易呈现; 根本表象在,播放一段时间程序会弹出谬误或者解体,查代码逻辑根本无法查起,且不易重现(比方,我当初就想重现,也未能重现进去--!前面补上),不过在播放没有音频的rtsp流的时候比拟容易呈现,报错定位output.c源文件中,应该是字符串格式化输入的时候出错,通过排查,定位到ffmpeg的日志输入,为了便于查看谬误,我在avformat_open_input()关上流函数前加了日志输入打印函数调用: av_log_set_level(AV_LOG_WARNING); av_log_set_callback(callback); 在callback回调函数中输入的日志打印,从而能看到FFMPEG的外部报错信息,而屏蔽这两句,尽管在丢帧的状况下视频卡帧了,然而上文提到的报错的问题却不再呈现了,所以,咱们猜想在ffpeg外部应该存在一个中央的日志打印呈现谬误的中央,当然也有可能是我的调用办法不合理导致的(谁晓得呢),总之,问题是临时失去了解决,后续找到真正报错的问题在补上。

February 28, 2023 · 1 min · jiezi

关于c++:SkeyeExPlayerWindows开发系列之解决ffmpeg接口调用卡住的问题

在SkeyeExPlayer的开发过程中,经测试发现ffmpeg的读取网络流以及网络数据的接口都有较大概率呈现阻塞的问题,ffmpeg也提供了设置阻塞回调或者设置超时等形式来跳出阻塞而不会导致接口永恒卡住;而在某些时候,比方,网络断开工夫过长的时候,这个时候阻塞回调将不在有用而且阻塞的接口也不再返回数据,呈现"永久性"假死的状况,针对这些问题,本文将对其解决形式进行一一解说。 1.播放器完结时接口导致线程卡住针对该问题,咱们通常能够在ffmpeg的阻塞回调函数中设置退出标记来解决,如下代码所示: //播放器退出状态标记,解除阻塞 if(pPlayer->player_status & PS_CLOSE) { return AVERROR_EOF; }2.播放器因为接口卡住而呈现断线这个问题也就是咱们通常状况下所说的断线重连的解决,断线重来你分两步走,第一步,判断呈现断线的机会;第二布,断线进行重连的解决;第一步,通常认定读取的网络流数据失落肯定的工夫为断线,阻塞回调函数解决如下: int64_t curTime = av_gettime(); //5s超时退出 if ((curTime - pPlayer->cur_read_time) > pPlayer->reconnect_time * 1000 * 1000)//5秒一次的重连 { pPlayer->error_flag = 1; char sErrorInfo[100] = { 0, }; sprintf(sErrorInfo, "interrupt_cb() enter,流已断开,正在尝试重连......curtime=%lld\n", curTime); OutputDebugStringA(sErrorInfo); return AVERROR_STREAM_NOT_FOUND;//AVERROR_EOF; }cur_read_time在初始读取网络流时去一个以后的工夫戳,以及av_read_frame时每一帧进行工夫戳的更新,如果过肯定的工夫仍未更新该值,咱们则认定网络曾经断开,置error_flag =1进行重连,重连过程如下代码所示: while (!(player->player_status & PS_CLOSE)) { usleep(1000*1000); if (player->error_flag>0)//must be sth error { if (player->player_status & PS_CLOSE) goto error_handler; player->b_ready = 0; player_pause(player); usleep(500*1000); if (player->player_status & PS_CLOSE) goto error_handler; int64_t media_duration = -1; int64_t media_seek_pos = -1; if (player->avformat_context) media_duration = (player->avformat_context->duration * 1000 / AV_TIME_BASE); render_getparam(player->render, PARAM_MEDIA_POSITION, &media_seek_pos); if (media_seek_pos > 0) media_seek_pos += 500; if (player->acodec_context) avcodec_close(player->acodec_context); player->acodec_context = NULL; if (player->vcodec_context) avcodec_close(player->vcodec_context); player->vcodec_context = NULL; if (player->avformat_context) { avformat_close_input(&player->avformat_context); avformat_free_context(player->avformat_context); } player->avformat_context = NULL; //++ for avdevice char *url = player->file_url; AVInputFormat *fmt = NULL; void *win = player->render_hwnd; player->avformat_context = avformat_alloc_context(); player->avformat_context->interrupt_callback.callback = interrupt_cb; player->avformat_context->interrupt_callback.opaque = player; // open input file AVDictionary *options = NULL; //++ for find trsp if ((strstr(url, "rtsp") == url) || (strstr(url, "RTSP") == url)) { if (player->link_mode == STREAM_LINK_TCP) av_dict_set(&options, "rtsp_transport", "tcp", 0); else av_dict_set(&options, "rtsp_transport", "udp", 0); } //-- for find trsp player->cur_read_time = av_gettime(); if (avformat_open_input(&player->avformat_context, url, fmt, &options) != 0) { continue; //goto error_handler; } if (player->player_status & PS_CLOSE) goto error_handler; // find stream info if (avformat_find_stream_info(player->avformat_context, NULL) < 0) { continue; //goto error_handler; } if (player->player_status & PS_CLOSE) goto error_handler; // set current audio & video stream player->astream_index = -1; reinit_stream(player, AVMEDIA_TYPE_AUDIO, 0); player->vstream_index = -1; reinit_stream(player, AVMEDIA_TYPE_VIDEO, 0); // for audio if (player->astream_index != -1) { arate = player->acodec_context->sample_rate; aformat = player->acodec_context->sample_fmt; alayout = player->acodec_context->channel_layout; //++ fix audio channel layout issue if (alayout == 0) { alayout = av_get_default_channel_layout(player->acodec_context->channels); } //-- fix audio channel layout issue } // for video if (player->vstream_index != -1) { vrate = player->avformat_context->streams[player->vstream_index]->r_frame_rate; if (vrate.num / vrate.den >= 100) { vrate.num = 25; vrate.den = 1; } player->vcodec_context->pix_fmt = vformat; width = player->vcodec_context->width; height = player->vcodec_context->height; }#if 0 // open render player->render = render_open(ADEV_RENDER_TYPE_WAVEOUT, arate, (AVSampleFormat)aformat, alayout, player->render_mode/*VDEV_RENDER_TYPE_GDI*//*VDEV_RENDER_TYPE_D3D*/, win, vrate, vformat, width, height, player->speed); if (player->vstream_index == -1) { int effect = VISUAL_EFFECT_WAVEFORM; render_setparam(player->render, PARAM_VISUAL_EFFECT, &effect); }#endif if (player->player_status & PS_CLOSE) goto error_handler; player->b_ready = 1; } }3.avformat_open_input以及av_read_frame接口呈现永久性阻塞的解决 ...

February 28, 2023 · 2 min · jiezi

关于c++:SkeyeExPlayer-RTSP-RTMP-HLS-HTTP全功能流媒体播放器开发计划

目前OpenSKEYE团队筹备推出一款全平台的面向多种流和媒体的全能播放器SkeyeExPlayer,上面列出SkeyeExPlayer(for Windows)的开发计划; SkeyeExPlayer开发将满足以下需要: .反对rtsp/ rtmp/ hls/ http协定,反对大部分媒体文件的播放. 视频按比例显示,扩大显示(充斥整个窗口区域),全屏显示.视频osd叠加性能,加载台标,字幕,画中画缩放,丑化渲染,特效等. 反对视频截图保留.网络直播流反对本地视频录像保留.反对音量调整/静音.rtsp反对tcp/udp.反对网络断线重连.文件播放反对播放地位管制(seek).文件播放反对快放/慢放 (2倍/4倍/8倍/16倍/32倍/64倍, 1/2倍,1/4倍,逐帧).文件播放反对暂停/持续/进行.文件播放反对播完告诉(例如文件播放实现,弹出信息框通知用户播放实现).音频反对RAW PCM / G711 / G726 / AAC等;视频反对H.264/H.265.局域网内网络播放演示小于500ms.网络播放和文件播放放弃视音频同步初步预计开发周期两周左右,公布能够不便调用的Skeye系列接口API和残缺调用程序,敬请期待。

February 28, 2023 · 1 min · jiezi

关于c++:nodejs实现国标GB28181设备接入sip服务器解决方案SkeyeVSS国标视频云平台

GB28181接入服务器是SkeyeVSS接入GB28181设施/平台的信令交互服务器,GB28181将 SIP定位为联网零碎的次要信令根底协定,并利用 SIP协定的无关扩大,实现了对非会话业务的兼顾,例如,对报警业务、历史视音频回放、下载等的反对。目前有GB28181-2011和 GB28181-2016两个版本。 GB28181接入服务器对接入零碎的GB28181设施的治理,全副通过一个20位的设施ID号来治理;以SIP协定为载体,以REGISTER、INVITE、MESSAGE等命令实现与28181设施和GB28181流媒体服务器的交互。 随着node.js社区的一直壮大,借助其弱小的事件驱动和IO并发能力,曾经衍生出了很多很弱小的模块(包),实现各种各样的性能,使得服务端程序开发变得非常容易,习惯了 C/C++编程的程序员相对会感到非常诧异,因为竟然有一种语言开发能够如此高效且简略(PS: 我也就刚学习一个月node.js而已- --!);而本文将要解说的是一种通过node.js实现接入国标设施以及平台的sip信令服务器的计划。筹备工作首先,下载node.js并装置,windows,linux平台均反对; 最好有一个比拟弱小的JS编辑器或者IDE,我举荐一个非常弱小且轻量级的IDE兼编辑神器Visual Studio Code。而后,相熟npm管理工具的命令,通过npm装置各个须要依赖的node.js模块,其中最重要的sip模块,通过如下命令装置: npm install sipnode.js领有弱小的包管理工具,能够通过如下命令搜寻咱们可能须要的node.js模块: npm search xxx如下图所示:其余node.js相干学习大家感兴趣能够在网上找到非常丰盛的材料,比方举荐一本比拟好书《深入浅出node.js》, 当然最好的倡议是:看个毛线的书,老夫都是间接撸代码! 国标接入流程1 承受上级的注册和登记首先,咱们须要建设一个sip服务来检测和承受上级设施或者平台的注册命令的解决,如下代码所示: sip.start(option, async request => { switch (request.method) { case common.SIP_REGISTER: this.emit('register', request); break; case common.SIP_MESSAGE: this.emit('message', request); break; case common.SIP_INVITE: this.emit('invite', request); break; case common.SIP_ACK: this.emit('ack', request); break; case common.SIP_BYE: this.emit('bye', request); break; default: console.log('unsupported: ' + request.method); break; } });而后,sip服务接管设施端注册申请,回调函数中进行解决: case common.SIP_REGISTER: try { const username = sip.parseUri(request.headers.to.uri).user; const userinfo = config.userinfos[username]; const session = { realm: config.server.realm }; if (!userinfo) { sip.send(digest.challenge(session, sip.makeResponse(request, 401, common.messages[401]))); this.session_.set(username,session); return; } else { if(!this.session_.has(username)){ this.session_.set(username,session); } userinfo.session = userinfo.session || this.session_.get(username); if (!digest.authenticateRequest(userinfo.session, request, { user: username, password: userinfo.password })) { sip.send(digest.challenge(userinfo.session, sip.makeResponse(request, 401, common.messages[401]))); this.session_.set(username,userinfo.session); return; } else { this.session_.delete(username); if(request.headers.expires === '0'){ this.emit('unregister', request); } else{ this.emit('register', request); } let response = sip.makeResponse(request, 200, common.messages[200]); sip.send(response); } } } catch (e) { //输入到控制台 console.log(e); } break;如上代码所示,依据国标gb28181规范解决逻辑如下:1) SIP 代理向SIP 服务器发送REGISTER 申请,申请中未蕴含Authorization 字段;SIP 服务器向SIP 代理发送响应401,并在响应的音讯头WWW_Authenticate 字段中给出适宜SIP 代理的认证体制和参数;2) SIP 代理从新向SIP 服务器发送REGISTER 申请,在申请的Authorization 字段给出信赖书,蕴含认证信息;SIP 服务器对申请进行验证,如果查看出SIP 代理身份非法,向SIP 代理发送胜利响应200 OK ,如果身份不非法则发送拒绝服务应答。值得注意的是,有些国标设施接入并不遵循以上注册逻辑,这种多见于老旧的国标设施或者平台,其注册甚至都不会携带认证信息,而是通过双向注册实现验证。 ...

February 28, 2023 · 4 min · jiezi

关于c++:用于双目重建中的GPU编程juliacuda

作者:京东科技 李大冲 一、Julia是什么julia是2010年开始面世的语言,作为一个10后,Julia必然有前辈们没有的特点。Julia被冀望塑造成原生的有C++的运行速度、python的易交互性以及胶水性。最重要的是,julia也是nVidia-cuda官网选中的接口语言,这点让cuda编程变得略微容易一些。 二、我的项目背景双目平面视觉是比拟惯例获取深度信息的算法。这是一种模拟人的双眼的办法,依据对同一个点在不同的视角进行察看,并在不同的察看视角上的察看差别推算出深度,这在图1中更清晰一点。被动双目的毛病是须要纹理特色去判断左右相机里是否为同一个点。也就是,当没有问题、纹理不够显著或者纹理反复时,这种办法难以工作。 图 1 被动双目示意图。 观测点在一个相机和在另外一个相机上的成像是有显著可辨别性的,二者的察看差别(L1和L2)造成了视差,进而可造成深度信息 被动双目的毛病是某些场景下有效,被动双目便呈现了。如果咱们投射一些人工设定编码的图案,依据实现约定通过解码能够对视线内是所有点赋予惟一的标识符号,那被动双目的问题不就解决了吗。事实上,人们很快发现能够应用空间上编码和工夫上编码两种形式。空间上编码,就像人们察看河汉命名星座一样,去投射随机生成的星星点点的光斑,没有纹理的夜空照耀进了齐全可辨识的图案。如同星座往往是一个空间范畴很大的概念,空间编码的形式无奈做到十分精密。所以,工夫编码的形式应运而生,工夫编码是投射一组图案,让被照耀的区域,被照耀的明暗变动状况是齐全不一样的。像图2中的二进制编码(或行业内称为格雷码),再加上灰度变动更加丰盛的编码方式以及相机在竖方向上对齐之后是只须要思考横轴方向上的差别这两点,能够实现全视线的点齐全可辨别。 ![星塵星雲星河| 何漢權- 灼見名家]() 图 2 空间编码和工夫编码。空间编码如同河汉中的星星点点,通过组合星点变为星座,人们能够对高深夜空不同地位依附河汉辨别开。工夫编码是投射多组图案,让每一个点是工夫上都有惟一的变动状况。就像右图,这16个方格都是齐全可辨别的。 三、效率问题应用被动双目的构造去重建,一个很大的影响运行效率的问题是须要对左右图像求算视差(原理如图1所示)。具体求算视差的操作是要对左相机里的每一个点,去找右相机中同一行中与之间隔最近的点的横坐标,而后持续插值之后找到间隔更近的插值坐标,最初把这两个坐标的差别当作视差进行后续的计算。 应用工夫编码的被动双目,依照实现约定的编码进行解码之后,其成果如图3所示,这个环节就是在这对数据上进行解决。以此数据为例,无效区域大略是700850,齐全遍历所须要的工夫复杂度大略是O(700850*850),间接估算就很耗时。 图 3 工夫编码解码后的图像示意图。左右相机拍摄的图案编程了解码值。接下来须要在这对数据上计算视差,而确定雷同编码值须要大量的遍历,对雷同编码进一步的优化还要对每个点进行小范畴的插值解决,工夫复杂度很大。 图 4 视差图成果 1. 应用for训练的形式应用for循环,须要3层。经测试后,在TX2上,应用c++来实现大略须要7s多。 2. 应用numpy的矩阵操作来实现1) 代码如下,应用numpy的播送的原理来实现,而后通过一系列的过滤异样值后,再通过插值进一步的解决失去最终想要的后果。 2) 这段代码没有进一步的去实现插值的过程,耗时状况为1.35s。 def disp(l_, r_, colstartR, colendR, colstartL, colendL, rowstart, rowend): # 聚焦在有效值区域 l = l_[rowstart: rowend, colstartL: colendL] r = r_[rowstart: rowend, colstartR: colendR] # 算一下阈值。阈值帮忙确定判断太远的点就不能当作是目标值 thres = (np.max(l) - np.min(l)) / (colendL - colstartL) * 10 h, w = l.shape ll = l.T.reshape(w, h, 1) # print(l.shape, r.shape) # 放慢运算,放大图片,4倍下采样 quart_r = cv2.resize(r, (w // 4, h)) # 计算间隔 distance = np.abs(ll - quart_r) # 计算点位 idx_old = np.argmin(distance, axis=-1).T * 4 # 去除指向同一个点的反复值 _, d2 = np.unique(idx_old, return_index=True) idx = np.zeros_like(idx_old).reshape(-1) idx[d2] = idx_old.reshape(-1)[d2] idx = idx.reshape(idx_old.shape) # 计算最值 minV = np.min(distance, axis=-1).T ori_idx = np.tile(np.arange(w).reshape(w, 1), h).T disparity = ori_idx - idx + colstartL - colstartR # 去除异样点 disparity[minV > thres] = 0# 大于阈值的 disparity[l < 1e-3] = 0 # 左图点有效的 # 插值 # 办法:在最值左近的区域去-5:5的值,二次线性插值上采样,采完之后再计算一遍下面的步骤,算完之后取小数进行计算 # 临时没有实现这一步 return disparity, l, r3. 应用julia-cuda实现这段次要是cuda核函数的局部,次要包含以下局部: ...

February 27, 2023 · 5 min · jiezi

关于c++:在Linux终端管理你的密码

大家好,我是良许。 当初是互联网时代,咱们每天都要跟各种 APP 、网站打交道,而这些货色基本上都须要注册才能够应用。 然而账号一多,咱们本人都常常记不清对应的明码了。有些小伙伴就一把梭,所有的账号密码都是一样。但这样操作的危险不言而喻,一旦明码泄露,你所有的账号都会陷入危险之中! 在浏览器上,咱们有 1Password、Dashlane、RoboForm,等等十分优良的明码管理软件,但毛病是基本上都须要免费(不,这是我的毛病)~ 本文介绍一款在 Linux 终端上就能够应用的明码管理器 pass ,这是一个经典的 UNIX 格调的明码管理系统,应用 GnuPG (GPG) 进行加密,玲珑好用! 一、装置pass工具pass 在很多发行版软件库里都有,应用各自的装置命令即可实现装置。 在 Ubuntu、Elementary 等基于 Debian 的发行版上,应用 apt 命令装置: $ sudo apt install pass在 Fedora 及相似的发行版上,能够应用 dnf 命令装置: $ sudo dnf install pass在 macOS 上,能够应用 Homebrew 装置: $ brew install pass二、配置GnuPG前文提到 pass 工具是应用 GnuPG 进行加密,所以在应用 pass 之前,咱们须要一个无效的 PGP(Pretty Good Privacy)密钥。如果你曾经有了 PGP 密钥,则能够跳过此步骤。 要创立 GnuPG 密钥,须要运行以下命令: $ gpg --generate-key零碎会提醒你输出姓名和电子邮件地址,并为密钥创立明码。你的密钥是一个数字文件,明码只有你本人晓得。之后咱们就能够应用 pass 工具锁定和解锁加密信息,例如一个蕴含有明码的文件。 GPG 密钥十分重要,如果你失去了它,任何被它锁住的货色都将变得无奈解锁,即便晓得你的明码。所以,你肯定要备份你的 ~/.gnupg 目录,这样下次如果重装系统,也不会意外删除它。 ...

February 26, 2023 · 1 min · jiezi

关于c++:华为OD开发岗C面经

华为OD开发岗,C++面经 2022年10月11日机试: 第一题:扑克牌,三道题外面最难的题,这个也是破费工夫最久的,用的笨办法,扑克和状态及对应序列号放在同一构造体中,这个题破费工夫较长,做的也磕磕绊绊,用例过的够了就差不多快到工夫了 第二题:力扣第一题的加强版,因为比较简单,没啥印象忘了 第三题:岛屿问题,尽管是岛屿问题,但并不需要用到递归和动静布局,所以难度也就小了很多 性情测试:这个就最好展现出本人不是极其的人,且前后答案性情体现统一。而后就过了 2022年10月27日 技术一面: 面试时出了点小问题,没有声音,我和面试官时通过电话进行语言交换的,面试官人很好,所以出了小情况以外,其余还能够。开始就是次要介绍我的项目,期间会提出点技术问题,但没有细问。接下来就是问根底方面的问题,C++次要还是问的多态问题,智能指针,STL库,内存治理、网络通信方面的问题(PS:只记得这些了。),最初就是手撕代码,现场手撕一道算法题,这个很重要,其余的可能决定着你的定级,但这个是否做进去决定着面试是否能过。 2022年10月28日 技术二面: 这次面试流程和第一次差不多,但面试官压力给拉满了,对于每个问题的答复都是很质疑的状态,但又不具体质疑在哪,这应该是面试官的一种面试形式,但我没顶住,答复的很缓和,施展很差,我一个敌人也遇到过相似的面试官,但他顶住了压力,失常答复,最初定级就比拟高。但好在我现场手撕算法题过了,所以尽管定级不高,但面试还是通过了。 2022年10月29日 HR&综面: 这个同一天进行,也是比拟轻松,就次要问跳槽的起因,家庭情况,是否违心加班啊,当前的布局方面的,失常答复就好,只有不碰到雷区就没啥问题。 后续:因为华为11月HC膨胀,该部门需要锐减,我在该部门的入职黄了,好在HR栗栗小姐姐踊跃帮我从新寻找适合部门,所以最初还是胜利入职。 最初非常感谢栗栗小姐姐的全程辅导和帮忙。哈哈哈,给她点个赞,真的非常感谢她!!如果有动向华为OD的,举荐分割栗栗(手机 186 1720 5178,微心 lfyslp)!!!!

February 24, 2023 · 1 min · jiezi

关于c++:C中const和constexpr的作用

目录背景const关键字constexpr关键字总结背景很多C++的初学者看到const这个关键字的第一反馈都是一头雾水,次要是因为const能够呈现在很多的地位,以及前面退出的constexpr更是经常感到困惑,明天就为大家一一解释呈现它们的含意和以及作用 const关键字1. const润饰变量这是最根本的一种用法,顾名思义,就是将该变量润饰为常量,从而不能够批改。很多的全局变量都是通过常量来进行润饰,须要留神的是,应用const关键字润饰的变量须要立即初始化 // 润饰局部变量,全局变量,成员变量const int a = 2;a = 3; // 谬误,表达式必须是可批改的左值,意思就是a是个常量,无奈批改// 还有人习惯这种写法,作用是一样的,看集体爱好即可int const b = 22;// 润饰函数参数void test(const int num) { num = 3; // 谬误,表达式必须是可批改的左值,意思就是参数num是个常量,无奈批改}2. 润饰指针尽管指针也是一种变量,不过当const与指针呈现在一起的时候,地位的不同会产生不同的作用,所以独自拎进去讲 // 第一种状况:指针常量int a = 2;const int *p = &a; // const作用:使其无奈通过指针来批改变量*p = 3; // 谬误,表达式必须是可批改的左值a = 4; // 正确cout << *p << endl; // 4// 同样地,有人习惯这种写法,作用是一样的,看集体爱好即可int const *p2 = &a;// 第二种状况:常量指针int a = 2;int* const p = &a; // const作用:使指针p无奈指向其余变量int b = 3;p = &b; // 谬误,表达式必须是可批改的左值3. 润饰函数const用于润饰函数也是最困惑的中央,次要起因在于它能够呈现在不同的中央,并且每一个都有不同的含意。接下来为一一为大家解释 ...

February 24, 2023 · 2 min · jiezi

关于c++:Spring事务回滚的两种方法

当然,Spring事务回滚的前提是你以后应用的数据库必须反对事务,比方MySQL的Innodb是反对的,但Mysaim则是不反对事务的。办法一 应用 @Transaction 来配置主动回滚,能够配置在类上,也能够配置在办法上(作用域不同),但对final或private润饰的办法有效,且该类必须是受spring所管控的,也就是被曾经被注入的类,而不是new进去的类。 若配置在办法上,则该办法被加上了事务若配置在类上,则等于给该类的所有办法都加上了该注解。此时如果在该类下的某个办法也加了 @Transaction ,则该办法应用本人的配置,其余办法应用类上的配置。 @Service@Transactionalpublic class UserServiceImpl implements UserService { @Overridepublic void save(User user) { //some code //db operation}}复制代码 若被配置的办法或类抛出了异样,则事务会被主动回滚,除非你在该办法中手动捕捉了异样,且没有抛出新的异样。能够应用 @Transactional(rollbackFor = Exception.class) 来设定针对特定的异样进行事务回滚,如果不设置则默认会回滚 RuntimeException and Error (参考自源码内文档)。@Service@Transactional(rollbackFor = Exception.class)public class UserServiceImpl implements UserService { @Resourceprivate UserMapper userMapper;@Overridepublic void save(User user) { userMapper.insert(user); throw new RuntimeException(); // 抛出异样,事务回滚,下面的insert插入失败。}}复制代码 办法二通过注入 DataSourceTransactionManager 来手动开启事务,手动回滚事务,用于抛出异样被catch后,进行手动回滚,可控水平更高,能够更灵便的应用。 先注入 DataSourceTransactionManager 事务管理对象new 一个 DefaultTransactionDefinition def = new DefaultTransactionDefinition(); 对象应用 TransactionStatus status = transactionManager.getTransaction(def);来开启一个事务,应用 transactionManager.rollback(status); 来回滚这个事务应用 transactionManager.commit(status); 来提交这个事务 ...

February 22, 2023 · 1 min · jiezi

关于c++:你的哪些SQL慢看看MySQL慢查询日志吧

前言在我的项目外面,多多少少都暗藏着一些执行比较慢的SQL, 不同的开发测试人员在平时应用的过程中多多少少都可能遇到,然而无奈立马有工夫去排查解决。那么如果有一个文件可能将这些应用过程中比较慢的SQL记录下来,定期去剖析排查,那该多美妙啊。这种状况MySQL也替咱们想到了,它提供了SQL慢查问的日志,本文就分享下如何应用吧。什么是慢查问日志?MySQL的慢询日志,提供了记录在MySQL中响应工夫超过指定阈值语句的性能,比方设定阈值为3秒,那么任何SQL执行超过3秒都会被记录下来。咱们借助慢查问日志性能能够发现哪些那些执行工夫特地长的询,并且有针对性地进行优化,从而进步零碎的整体效率。怎么开启慢查问日志?默认状况下,MySQL数据库没有开启慢查问日志,因为多多少少会带来肯定性能的影响。咱们能够在开发测试环境、或者生产环境做调优的时候开启,那怎么查看是否开启了呢? 查看慢SQL是否开启 执行上面命令查看是否开启慢SQLshow variables like '%slow_query_log';复制代码 OFF: 未开启ON: 开启 如何开启慢查问 执行上面的命令开启慢查问日志 set global slow_query_log='ON';复制代码 批改慢查问阈值 后面介绍了SQL执行达到了制订的工夫阈值后记录到慢查问日志中,那么如何设置呢?set global long_query_time = N; set long_query_time = N复制代码 设置global的形式对以后session的long_query_time生效。对新连贯的客户端无效。所以能够一并执行下述语句 N示意设置的阈值,单位为秒 这里的show global variables like '%long_query_time%';能够查看阈值大小 如何设置永恒失效 后面是通过命令行的形式设置,如果MySQL重启,那么配置就会重置。咱们能够通过批改MySQL的配置my.cfg或者my.ini永恒失效。[mysqld]slow_query_log=ON # 开启慢查问日志开关slow_query_log_file=/var/lib/mysql/alvin-slow.log # 慢查问日志的目录和文件名信息long_query_time=3 # 设置慢查问的阈值为3秒,超出此设定值的SQL即被记录到慢查问日志log_output=FILE复制代码慢查问日志在哪里呢?后面解说了如何开启MySQL的慢查问日志,那么它把日志记录在哪里了呢? 查看慢查问日志地位 通过show variables like '%slow_query_log_file%';命令能够查看慢SQL文件地位,如下图所示: 批改慢查问日志地位 也很简略,执行上面的命令即可:set global slow_query_log_file = '/usr/local/mysql/data/alvin-slow-slow.log';复制代码怎么查看慢SQL内容?当初咱们曾经晓得慢查问日志在哪里了,那么如何查看外面的内容呢?咱们这里用一个例子演示下吧。 执行一个查问的SQL 执行花了1秒多,超过了后面设置的阈值1s 查看慢查问数目 执行上面命令查问以后零碎中有多少条慢查问记录SHOW GLOBAL STATUS LIKE '%Slow_queries%';复制代码 value=1, 表明刚刚的日志被记录了。 查看日志内容 通过cat命令查看文件内容,能够看到对应的慢SQL。 ...

February 22, 2023 · 1 min · jiezi

关于c++:SkeyeGisMap开发文档二

1、整体架构SkeyeGisMap 由以下几局部组成: cdt 只有一个文件, 来自 mapbox 的三角剖分库 earcut, 其官网链接为: https://github.com/mapbox/earcut core 地图外围, 次要定义了地图形态节点, 地图事件, 地图助手工具。 item 地图的可视化项(即地图容器)。 parser 地图矢量形态解析器(暂只实现了 Esri Shapefile格局解析)以及坐标参考。 style 地图款式加载解析相干, 个别状况下无需关怀。 其构造如图所示: 2、坐标系SkeyeGisMap 中总共有四种坐标系: 1、屏幕坐标系 { Screen Coordinate System } 该坐标系就是字面意思, 所有的坐标对应屏幕像素坐标。 通常事件的原始坐标即是屏幕坐标。 另外, SkeyeGisMap 中的地图事件不间接承受原始的{ Qt Event }, 须要进行一些转换。 2、显示坐标系 { Display Coordinate System } 该坐标系是地图所有可视节点的(顶点)坐标系, 即执行绘制时应用的坐标。 3、世界坐标系(地图坐标系) { World Coordinate System } SkeyeGisMap 应用 EPSG:32650 做为世界坐标的参考系. 次要来自于地图矢量形态解析器解析后产生的坐标, 通常不须要关注. 4、经纬度坐标系 { Lonlat Coordinate System } SkeyeGisMap 应用 EPSG:4326 做为经纬度坐标的参考系. ...

February 16, 2023 · 1 min · jiezi

关于c++:OneFlow-源码阅读-12从-Tensor-看-CPython-的对象创建过程

春节前后拜读了许啸宇的《TorchDynamo初探:Python ByteCode的动静批改》,随后简略总结了一下 TorchDynamo 的执行过程。这期间对 CPython 也多了一些理解。记得之前看到过,oneflow Tensor 是通过 CPython API 导出给 Python 环境的,过后很多细节都不理解,就着这个热乎劲,从新看了一下 Tensor 类型注册相干的内容,顺便相熟一下 CPython 创建对象的过程。本文中波及 CPython 相干的内容能够参考 Python behind the scenes。 1 Python 中的类型Python 中一切都是对象。对象的数据类型自身也是对象,比方 float、list 这些,都是对象。这些类型对象中,有一个比拟非凡,就是 type,它是元类型(metatype),即类型的类型。以下断言是成立的: assert(float.__class__ is type)assert(type.__class__ is type)这些类型对象在 CPyhton 中的定义如下,它们的 C 类型都是 PyTypeObject,也都能够被视为 PyObject。 object: PyBaseObject_Type(定义)type: PyType_Type(定义)float: PyFloat_Type(定义)list: PyList_Type(定义)PyTypeObject 的很多字段(slot)都是函数指针。这些 slot 的不同取值,决定了不同类型的行为。slotdefs 数组定义了 magic method 的 slot。 2 Tensor 类型的注册one::Tensor 类型注册的代码在 tensor.cpp 中。这里定义了三个 PyTypeObject 对象: TensorMetaclass_Type: metatypePyTensorObject_Type: oneflow.TensorPyParameterObject_Type: oneflow.Parameter,是 oneflow.Tensor 的子类型。MakeTensorMetaclass 函数负责初始化 TensorMetaclass_Type 对象。上面这行代码创建对象实体: ...

February 14, 2023 · 5 min · jiezi

关于c++:TorchDynamo-的执行过程

1 执行流程1.1 初始化设置全局变量 extra_index。这是 PEP 523 设置 PyCodeObject.co_extra 须要的。初始化 TSS Key。这是 PEP 539 须要的。Tss 的 key 是固定的,这里定义的这个 key 只用于存储 callback,tss 是每个线程公有的(Each thread has a distinct mapping of the key to a void* value)。 Dynamo callback 都是一样的?1.2 设置 eval frame 以代替默认的 eval frame 函数set_eval_frame_py 输出参数中的 args[0] 应该就是 Python 传入的 callback。 在 Python _TorchDynamoContext 中调用这个函数,Python 的办法名是 set_eval_frame。 1.2.1 函数的次要作用将传入的 callback 保留到 thread specific storage如果需要的话,设置 eval_frame 为 custom_eval_frame_shim返回之前保留在 tss 的、旧的 callback1.2.2 set_eval_frame_py 执行流程set_eval_frameincrement_working_threads,如果需要的话enable_eval_frame_shimtstate->interp->eval_frame = &custom_eval_frame_shim1.3 _TorchDynamoContext: 用于设置转换字节码的 callback 函数这是 torch._dynamo.optimize(...) 对应的 context manager。callback 是 Python 代码。 ...

February 14, 2023 · 2 min · jiezi

关于c++:C右值引用与移动语义

一文看懂C++右值援用和挪动语义目录背景什么是右值援用为什么须要右值援用挪动结构move的原理move的利用场景右值援用注意事项总结背景C++11引入了右值援用,它也是C++11最重要的新个性之一。起因在于它解决了C++的一大历史遗留问题,即打消了很多场景下的不必要的额定开销。即便你的代码中并不间接应用右值援用,也能够通过规范库,间接地从这一个性中收益。为了更好地了解该个性带来的优化,以及帮忙咱们实现更高效的程序,咱们有必要理解一下无关右值援用的意义。 什么是右值援用右值 在引入右值的概念前,咱们无妨先看看左值。一句话加以概括:左值就是等号右边的值;同理,右值也就是等号左边的值。举个例子:int a = 2; 这里的a是等号右边,能够通过取址符&来获取地址,所以是一个左值。而5在等号左边,无奈通过取址符&来获取地址,所以只一个右值。 右值援用 左值援用是对于左值的援用或者叫别名。同样地,右值援用也就是对于右值的援用。语法也很简略,就是在左值援用的语法之上在多加一个&,写成类型 &&右值援用名 = 右值;的模式即可,比方: int &&a = 5;a = 6;string s1 = "hello";string &&s2 = s1 + s1;s2 += s1;上述简略例子,展现了右值援用的根本用法。不过通常状况下,右值援用更多的是被用于解决函数参数。比方: struct Student { Student(Student &&s);};为什么要应用右值援用在C++11之前,很多C++程序里存在大量的长期对象,又称无名对象。次要呈现在如下场景: 函数的返回值用户自定义类型通过一些计算后产生的长期对象值传递的形参先说函数的返回值,最常见的类型就是某些返回用户自定义类型的时候,如果没有将其复制,就会产生长期对象,比方: Student func1(); // 返回一个Student对象...func1(); // 调用了func1创立了一个Student对象,然而没有应用,于是编译器创立了一个长期对象来进行存储而后是某些计算操作后产生的长期对象,比方: Complex result = c1 + c2 + c3; // 编译器先计算c1 + c2的后果,并产生一个长期对象temp来存储后果,而后计算temp + c3的后果,而后将后果复制给result还有值传递的形式的形参,例如: void func(Student s); // 值传递...Student stu;func(stu); // 这里相当于是做了一次复制操作 Student s(stu);而且这些长期对象随着生命周期的完结,编译器还会调用一次析构函数。随着这些操作次数的减少,或者当长期变量是个很大的类型时,这无疑会极大进步程序的开销,从而升高程序的效率。 C++11之后,随着右值援用的呈现,能够无效的解决这些问题。通过move和挪动结构,挪动赋值运算符函数来取得长期对象的所有权,从而防止拷贝带来的额定开销,进步程序效率 挪动结构咱们都晓得,因为C++11之前,如果没有手动申明,编译器会给一个用于自定义类型(包含class和struct)主动生成的4个函数,别离是构造函数,拷贝构造函数,赋值运算符重载函数和析构函数。尽管通过传援用的形式,能够防止对象的复制。然而还是没法防止上述的长期对象的复制。而挪动语义胜利的解决的这个问题。 在C++11之后,编译器主动生成的函数中又新增了2个,它们就是挪动结构和挪动赋值运算符重载函数,通过它们,咱们能够很好地实现对用户自定义类型的挪动操作。而挪动的实质就是获取长期对象的所有权,而不是通过复制的形式来取得。间接看代码: class Foo {public: Foo(Foo &&rhs) : ptr_(rhs.ptr_) { delete rhs.ptr_; } Foo &operator(Foo &&rhs) { if (*this != rhs) { ptr_ = rhs.ptr_; delete rhs.ptr_; } return *this; } private: int *ptr_;};Foo类重载了挪动构造函数和挪动赋值运算重载函数,使得Foo取得了挪动的能力,当咱们在面对产生长期的对象的时候,编译器就会依据传入的参数是左值还是右值来抉择调用拷贝还是挪动。如果是右值,就调用挪动结构或挪动赋值运算符函数。当Foo是一个很大的对象时候,就会极大的升高开销,进步程序效率。 ...

February 9, 2023 · 1 min · jiezi

关于c++:Leetcode专题233数字1的个数

力扣链接:https://leetcode.cn/problems/...解题思路: 找法则从个位数开始,依照以后位将数字分成右边high局部,左边low局部,以后位的数字分为三种类型:(1)0: res = high * digit;(2)1: res = high * digit + low + 1;(3)2/.../9: res = (high + 1) * digit;class Solution{ public: int countDigitOne(int n) { // 参数判断 if (n < 1) { return 0; } // 从个位数开始 long digit = 1; int high = n / 10, low = 0, cur = n % 10; int res = 0; while(cur != 0 || high != 0) { if (cur == 0) { res += high * digit; } else if (cur == 1) { res += high * digit + low + 1; } else { res += (high + 1) * digit; } low += cur * digit; high = high / 10; cur = high % 10; digit *= 10; } return res; }};

February 9, 2023 · 1 min · jiezi

关于c++:C中static关键字的作用

static是什么在最开始C中引入了static关键字能够用于润饰变量和函数,起初因为C++引入了class的概念,当初static能够润饰的对象分为以下5种: 成员变量,成员函数,一般函数,局部变量, 全局变量 static的作用润饰成员变量static润饰成员变量之后,该变量会属于该类,而不是某一个该类的对象。举个例子,Student类种有一个count的变量,在应用static关键字润饰之后,所有Student的对象共用这1个count。 调用形式会产生扭转,无奈通过 对象名 + . 变量名来调用,而是须要通过类名 + 作用域(::) + 变量名来调用,举个例子 Studnet s1;cout << s1.count << '\n'; // 会编译正告 Clang-Tidy: Static member accessed through instance 通过实例调用动态成员变量cout << Studnet::count << '\n'; // ok润饰成员函数和成员变量一样,应用static润饰的成员函数的生命周期和应用形式都产生了变动 通过static润饰的函数,如果拜访非static成员变量,编译器会间接报错 润饰一般函数函数的作用域会发声变动,被static润饰的一般函数只能在本文件内能够见,同一个程序的其余文件将无奈调用该函数。能够在肯定水平上解决命名抵触的问题,不过C++提供了namespace,所以个别不用于润饰一般函数。 润饰全局变量和润饰一般函数一样,被static润饰的全局变量的可见性会发生变化,其余文件将无奈调用该全局变量,其余和一般全局变量没有区别 润饰局部变量static润饰的局部变量被初始化一次之后,每次函数调用都持续应用之前的值,而不是从新进行初始化操作 如何应用static成员变量通过在成员变量后面加上关键字static即可 class Studnet {private: static int count;};// static润饰的成员变量只能在类外初始化int Student::count = 0;// C++17之后能够通过inline的形式在类内初始化,例如class Studnet2 {private: static inline int count = 0;};成员函数class Studnet {public: static int init(int number1, int number2) { age = number1; // 编译报错 Invalid use of member 'age' in static member function count = number2; // ok }private: static inline int count = 0; int age = 18;};一般函数static int add(int a, int b) { return a + b;}全局变量static int count = 2;局部变量void print() { static int a = 0; ++a; cout << a << endl;}底层原理之所以被static润饰的变量或者函数的生命周期会超过摆布其所在的作用域的实质是因为它在内存中的存储地位产生了变动 ...

February 1, 2023 · 1 min · jiezi

关于c++:C1-学习C的意义

学习C++的劣势 古代软件产品架构图 古代软件产品的个性零碎个别由多种语言写成零碎架构指标是拥抱用户需要文化零碎领有较好的移植性零碎可能不便部署与更新 小结

January 30, 2023 · 1 min · jiezi

关于c++:C语言中的4大基本数据类型

四大类型:⭐根本数据类型 整型 int 字符型 char 实型(浮点型):单精度实型 float 双精度实型 double⭐构造类型数组类型构造类型 struct联结类型 union枚举类型 enum⭐指针类型⭐空类型 void 思维导图 根本数据类型:整型 int int类型在内存中占用了4个字节,也就是 32位。int类型是有符号的,因而,32位 并不会全副用来存储数据,应用最高位来 存储符号,最高位是0,提醒数据是正 数,最高位是1,示意数据是正数,应用 其余的31位来存储数据。字符型 charchar类型只占一个字节,取值范畴为-128~ +127(-2^7~2*7-1)。 单精度实型 float float类型占 4 个字节,包含一个符号位、一个 8 位 excess-127 二进制指数和一个 23 位尾数。 尾数示意一个介于 1.0 和 2.0 之间的数。 因为尾数的高程序位始终为 1,因而它不是以数字模式存储的。 此示意模式为 float 类型提供了一个大概在 3.4E–38 和 3.4E+38 之间的范畴。双精度实型 doubledouble类型占8个字节。比特数为64,有效数字16位,示意范畴是-1.79E+ 308~-1.79E+308

January 25, 2023 · 1 min · jiezi

关于c++:说说-Spring-定时任务如何大规模企业级运用

Spring 定时工作简介Cloud Native定时工作是业务利用开发中十分普遍存在的场景(如:每分钟扫描超时领取的订单,每小时清理一次数据库历史数据,每天统计前一天的数据并生成报表等等), 解决方案很多 ,Spring 框架提供了一种通过注解来配置定时工作的解决方案,接入十分的简略,仅需如下两步: 在启动类上增加注解@EnableScheduling @SpringBootApplication@EnableScheduling // 增加定时工作启动注解public class SpringSchedulerApplication { public static void main(String[] args) { SpringApplication.run(SpringSchedulerApplication.class, args);}}复制代码 开发定时工作 Bean 并配置相应的定时注解@Scheduled @Componentpublic class SpringScheduledProcessor { /** * 通过Cron表达式指定频率或指定工夫 */@Scheduled(cron = "0/5 * * * * ?")public void doSomethingByCron() { System.out.println("do something");}/** * 固定执行间隔时间 */@Scheduled(fixedDelay = 2000)public void doSomethingByFixedDelay() { System.out.println("do something");}/** * 固定执行触发频率 */@Scheduled(fixedRate = 2000)public void doSomethingByFixedRate() { System.out.println("do something");} }复制代码Spring 定时工作原理Cloud Native运行原理Spring 定时工作外围逻辑次要在 spring-context 中的 scheduling 包中,其次要构造包含: ...

January 11, 2023 · 2 min · jiezi

关于c++:重载的奥义之函数重载

一、根本定义    重载,顾名思义从字面上了解就是反复装载,打一个不失当的比如,你能够用一个篮子装蔬菜,也能够装水果或者其它,应用的是同一个篮子,然而能够用篮子反复装载的货色不一样。 函数重载是C++多态(动态多态)的特色体现,它能够容许重复使用同一个函数名(篮子)的函数,然而函数的参数列表(篮子装的货色)是能够不一样的。这样就能够利用函数的重载功能设计一系列性能相近,然而性能细节不一样的函数接口。 二、利用举例      以同一个函数printData为例: #include <iostream>using namespace std; void printData(const char *str, int num){ //函数体;} void printData(const char *str){ //函数体;} void printData(double data, int num){ //函数体;} void printData(int data, int num){ //函数体;} void printData(long data, char num){ //函数体;} class Test{ public: void MyPrint(int num) {cout << "class int: " << num << endl;} void MyPrint(float num) {cout << "class float: " << num << endl;} void MyPrint(char num) {cout << "class char: " << num << endl;}}; int main(void){ printData("hello", 5); // (const char *str, int num) printData("hello"); // (const char *str) printData(1993.0, 97); printData(1993, 98); printData(1993L, 99); Test test1; test1.MyPrint(2); // class int: 2 test1.MyPrint(2.0f); // class float: 2.0 浮点型必须要显式类型,否则编译器不晓得该转换为int还是float。 test1.MyPrint("hello"); // class char: hello return 0;} 应用重载函数时,须要在函数调用中应用与对应的重载函数匹配的函数参数类型。 ...

January 8, 2023 · 1 min · jiezi

关于c++:一种将函数模板定义和声明分开的方法

 在 C++ 中为了操作简洁引入了函数模板。所谓的函数模板实际上是建设一个通用函数,其函数类型或形参类型不具体指定,用一个虚构的类型来表白,这个通用函数就称为函数模板。 1、通用的写法 函数模板不是一个具体的函数,编译器不能为其生成可执行代码。定义函数模板后只是一个对函数性能框架的形容,当它具体执行时,将依据传递的理论参数决定其性能。为了容易应用,个别通用的写法都是在头文件中间接定义函数模板,定义的同时也是申明该函数,供应其它文件蕴含调用。 //------fun.h或fun.hpp------//#ifndef _FUN_H_#define _FUN_H_using namespace std;template<typename T> void fun(int b, T c, T d) //定义函数模板 { ......}#endif 对编译器而言,定义函数模板的时候,编译器并不会对它进行编译,因为它没有一个实体可用,编译器只看到了申明,只有模板被实例化后(用在特定的类型上),编译器才会依据具体的类型对模板进行编译。因而当在别的文件中调用该函数模板时,依据传递的理论参数决定其性能,这样编译器就能够在编译期间看到模板函数的定义并实现模板的实例化,如果在编译的时候,找不到模板函数的定义,就先不在这一次编译中实例化该模板函数。 2、问题的引出 然而头文件中定义和应用函数模板时,碰到了一个这样的场景,即在函数模板中应用到了全局变量: //------fun.h或fun.hpp------//#ifndef _FUN_H_#define _FUN_H_using namespace std;int a; //定义全局变量template<typename T> void fun(int b, T c, T d) //定义函数模板 { ...... a = b;}#endif 因而碰到其它多个文件须要应用该函数模板时,都须要各自蕴含该函数模板的头文件,编译时就会呈现“全局变量反复定义”的谬误。 尝试依照一般函数定义和申明离开的思路将函数模板的定义和申明离开: 源文件: //------fun.cpp------// //错误做法using namespace std;int a; //定义全局变量template<typename T> void fun(int b, T c, T d) //定义函数模板{ ...... a = b;} 头文件: ...

January 2, 2023 · 1 min · jiezi

关于c++:万字避坑指南C的缺陷与思考下

  导读 | 在万字避坑指南!C++的缺点与思考(上)一文中,微信后盾开发工程师胡博豪,分享了C++的倒退历史、右值援用与挪动语义、类型说明符等内容,深受宽广开发者青睐!此篇,咱们邀请作者持续总结其在C++开发过程中对一些奇怪、简单的语法的了解和思考,分享C++开发的避坑指南。 static 我在后面章节吐槽了const这个命名,也吐槽了“右值援用”这个命名。那么static就是笔者下一个要重点吐槽的命名了。static这个词自身没有什么问题,其次要的槽点就在于“一词多用”,也就是说,这个词在不同场景下示意的是齐全不同的含意。(作者可能是出于节俭关键词的目标吧,明明是不同的含意,却没有用不同的关键词)。 第一,在局部变量前的static,限定的是变量的生命周期。 第二,在全局变量/函数前的static,限定的是变量/函数的作用域。 第三,在成员变量前的static,限定的是成员变量的生命周期。 第四在成员函数前的static,限定的是成员函数的调用方(或暗藏参数)。 下面是static关键字的4种不同含意,接下来我会逐个解释。 1)动态局部变量当用static润饰局部变量时,static示意其生命周期: void f() { static int count = 0; count++;}上述例子中,count是一个局部变量,既然曾经是“局部变量”了,那么它的作用域很显著,就是f函数外部,而这里的static示意的是其生命周期。一般的全局变量在其所在函数(或代码块)完结时会被开释,而用static润饰的则不会,咱们将其称为“动态局部变量”。动态局部变量会在首次执行到定义语句时初始化,在主函数执行完结后开释,在程序执行过程中遇到定义(和初始化)语句时会被疏忽。 void f() { static int count = 0; count++; std::cout << count << std::endl;}int main(int argc, const char *argv[]) { f(); // 第一次执行时count被定义,并且初始化为0,执行后count值为1,并且不会开释 f(); // 第二次执行时因为count曾经存在,因而初始化语句会忽视,执行后count值为2,并且不会开释 f(); // 同上,执行后count值为3,不会开释} // 主函数执行完结后会开释f中的count例如下面例程的输入后果会是: 1232)外部全局变量/函数当static润饰全局变量或函数时,用于限定其作用域为“以后文件内”。同理,因为曾经是“全局”变量了,生命周期肯定是合乎全局的,也就是“主函数执行前结构,主函数执行完结后开释”。至于全局函数就不用说了,函数都是全局生命周期的。因而,这时候的static不会再对生命周期有影响,而是限定了其作用域。与之对应的是extern。用extern润饰的全局变量/函数作用于整个程序内,换句话说,就是能够跨文件。 // a1.ccint g_val = 4; // 定义全局变量// a2.ccextern int g_val; // 申明全局变量void Demo() { std::cout << g_val << std::endl; // 应用了在另一个文件中定义的全局变量}而用static润饰的全局变量/函数则只能在以后文件中应用,不同文件间的static全局变量/函数能够同名,并且相互独立。 ...

December 20, 2022 · 17 min · jiezi

关于c++:百度工程师带你探秘C内存管理ptmalloc篇

作者 | daydreamer 前篇《探秘C++内存治理(实践篇)》次要介绍了Linux C++程序内存治理的实践根底,本文作为系列文章《探秘C++内存治理》的第二篇,将会探讨经典内存管理器ptmalloc如何治理C++程序的内存。借助分析ptmalloc解决问题的着重点和设计实现老本的衡量,更具体的出现c++内存治理面临的问题和工程落地中的巧思。 一、概述ptmalloc是开源GNU C Library(glibc)默认的内存管理器,以后大部分Linux服务端程序应用的是ptmalloc提供的malloc/free系列函数,而它在性能上远差于Meta的jemalloc和Google的tcmalloc。服务端程序调用ptmalloc提供的malloc/free函数申请和开释内存,ptmalloc提供对内存的集中管理,以尽可能达到: 用户申请和开释内存更加高效,防止多线程申请内存并发和加锁寻求与操作系统交互过程中内存占用和malloc/free性能耗费的平衡点,升高内存碎片化,不频繁调用零碎调用函数简略概括ptmalloc的内存管理策略: 事后向操作系统申请并持有一块内存供用户malloc,同时治理已应用和闲暇的内存用户执行free,会将回收的内存治理起来,并执行管理策略决定是否交还给操作系统接下来,将从ptmalloc数据结构、内存调配及优缺点介绍最经典的c++内存管理器的实现和应用(以32位机为例)。 二、内存治理2.1 数据结构为了解决多线程锁抢夺问题,将内存调配辨别为主调配区(main\_area)和非主调配区(no\_main\_area)。同时,为了便于管理内存,对预申请的内存采纳边界标记法划分成很多块(chunk);ptmalloc内存分配器中,malloc\_chunk是根本组织单元,用于治理不同类型的chunk,性能和大小相近的chunk串联成链表,被称为一个bin。 main\_arena与non\_main\_arena主调配区和非主调配区造成一个环形链表进行治理, 每一个调配区利用互斥锁实现线程对该调配区的拜访互斥。每个过程只有一个主调配区,但容许有多个非主调配区,且非主调配区的数量只减少不缩小。主调配区能够拜访过程的heap区域和mmap映射区域,即主调配区能够应用sbrk()和mmap()分配内存;非主调配区只能应用mmap()分配内存。 对于不同arena的管理策略大抵如下: 分配内存查看该线程的公有变量中是否曾经存在一个调配区并对其进行加锁操作,如果加锁胜利,则应用该调配区分配内存;如果未找到该分区或加锁失败,遍历环形链表中获取一个未加锁的调配区如果整个环形链表中没有未加锁的调配区,开拓一个新的调配区,将其退出循环链表并加锁,应用该调配区满足以后线程的内存调配开释内存先获取待开释内存块所在的调配区的锁,如果有其余线程正在应用该调配区,期待其余线程开释该调配区互斥锁后,再开释内存主调配区和非主调配区的构造如下: 其中fastbinsY和bins是对理论内存块的治理和操作构造: fastbinsY: 用以保留fast binsbins[NBINS * 2 - 2]: unsorted bin(1个,bin[1])、small bins(62 个,bin[2]~bin[63])、large bins(63 个,bin[64]~bin[126])的汇合,一共有 126 个表项(NBINS = 128),bin[0] 和 bin[127] 没有被应用malloc\_chunk与binsptmalloc对立治理heap和mmap映射区域中闲暇的chunk,当用户进行调配申请时,会先试图在闲暇的chunk中查找和宰割,从而防止频繁的零碎调用,升高内存调配的开销。为了更好的治理和查找闲暇chunk,在预调配的空间的前后增加了必要的管制信息,内存治理构造malloc\_chunk的成员及作用如下: mchunk_prev_size: 前一个闲暇chunk的大小mchunk_size: 以后chunk的大小必要的属性标记位:前一个chunk在应用中(P = 1)以后chunk是mmap映射区域调配(M = 1)或是heap区域调配(M = 0)以后chunk属于非主调配区(A = 0)或非主调配区(A = 1)fd和bk: chunk块闲暇时存在,用于将闲暇chunk块退出到闲暇chunk块链表中对立治理基于chunk的大小和应用办法,划分出以下几种bins: fast bins fast bins仅保留很小的堆,采纳单链表串联,增删chunk都产生在链表的头部,进一步提高小内存的调配效率。fast bins记录着大小以8字节递增的bin链表,个别不会和其余堆块合并。 unsorted bin small bins和large bins的缓冲区,用于放慢调配的速度,chunk大小无尺寸限度,用户开释的堆块,会先进入unsorted bin。调配堆块时,会优先查看unsorted bin链表中是否存在适合的堆块,并进行切割并返回。 small bins ...

December 19, 2022 · 1 min · jiezi

关于c++:HIFIVE-音加加提供曲库评分修音功能的-K-歌-SDKiOS-版本

性能阐明:KXKTVSDK 整合了歌词展现、演唱评分、音色音量调节等简单的 K 歌模块,实现了性能组件化,升高了 K 歌性能开发的门槛。SDK 反对:1> 逐字歌词、逐行歌词、动态歌词2> 反对演唱评分3> 反对调节伴奏音量、伴奏升降 Key、麦克风音量、美声音量(仅佩戴耳机时可用)4> 反对 EQ 调节5> 反对耳机返听开关(仅佩戴耳机时可用)利用场景: 开发环境:macOS 版本 10.10 及以上版本。XCode 9.0 或以上版本。(本文 XCode 的界面形容以 XCode 13.0 为例)iOS 9.0 或以上版本的设施。模拟器临时不反对本我的项目,所以请应用真机。无效的 accessKeyId 受权码。 集成 KXKTVSDK依照以下步骤将 KXKTVSDK 集成到我的项目中。1、 下载KXKTVSDK并解压。2、 将 SDK 包中 .framework 文件复制到您的我的项目中。3、 抉择我的项目的 Targets->Build Phases->Link Binary With Libraries,增加以下依赖库:√ AVFoundation.framework√ Accelerate.framework√ CoreMedia.framework√ AudioToolBox.framework√ libz.dylib 或者 libz.tbd√ libc++.dylib 或者 √libc++.tbd√KexuanKTVSDK.framework 增加实现(如下图):4、 抉择我的项目的 Targets->Build Phases->Copy Bundle Resources,将 KexuanKTVSDK.framework 增加到 Bundle Resources 依赖。增加实现(如下图):5、抉择我的项目的 Targets->Build Settings 搜寻“alway”,将 Always Embed Swift Standard Libraries 的配置更改为 Yes(如下图)6、抉择我的项目的 Targets->Info,新增 NSMicrophoneUsageDescription 麦克风权限申请提醒(如下图)以上 6 步实现后,KXKTVSDK 就集成在我的项目中了。接下来咱们要在我的项目中应用 KXKTVSDK 提供的外围 API 实现 K 歌性能,API 调用时序见下图: ...

December 15, 2022 · 1 min · jiezi

关于c++:DeepRoute-Lab-C-内存管理介绍

文章概述内存治理对于每种开发语言来说都是一个非常重要的话题;即便像Java这种领有“简单”垃圾收集器的语言,也会面临GC带来的各种困扰。C++程序设计中的很多bug也是因为内存治理不善导致的,而且难以发现和排除;如何有条理地治理内存,对于C++开发尤为重要。 “他山之石可以攻玉”,在钻研如何做好C++内存治理之前,咱们也能够看下其余语言是怎么做内存治理的,有什么模式或者模型能为咱们所借鉴,可能更好地帮忙咱们了解和做好C++的内存治理。 01不同语言的内存管理机制介绍C/C++、Java、Objective-C/Swift 和Golang 是几种应用宽泛的语言,内存管理机制也绝对典型。 1.1 C/C++C  也常被称作“可移植的汇编”,诞生之初次要是解决汇编语言的移植问题,在嵌入式和操作系统等绝对底层的开发畛域利用宽泛;对于简单的业务问题,因为它没有面向对象的能力(不好形象业务逻辑),显得难以应酬。 C++ 是一种很弱的面向对象的语言(模版和Interface简直是一种互相违反的思维)。为了兼容简直所有的C个性,背上了比拟重的历史包袱。在C++11之后,这种景象有了比拟大程度的改善,各种新的语言个性能够让C++开发者开发出更优雅、强壮的代码。 C语言的内存治理是典型的手动管理机制,通过malloc申请,free开释。C++语言除了手动治理之外,还领有弱的“垃圾回收机制”(智能指针的反对)。 C/C++中常见的内存治理问题有:a. 数组拜访越界(Java语言可抛出ArrayIndexOutOfBoundsException)b. 内存拜访超过生命周期‣ 栈弹出之后,仍旧进行拜访(函数返回外部栈地址)‣ 堆内存开释,仍旧进行拜访c. 内存泄露 (没有开释不再应用的内存)d. 悬空指针导致的问题‣ 指针指向内存开释之后,指针没有复位(设置为nullptr)‣ 应用没有复位(不为null)的有效内存(已开释或者未申请的内存)e. C++独有的问题‣ 非预期内的拷贝结构函数调用带来的适度复制(性能问题)‣ 不合理的复制、拷贝构造函数的实现,导致的意外数据共享(没有设置为nocopyable) 1.2 JavaJava 是一种面向对象的古代语言,有着丰盛的语言个性和开发生态。Java语言是为了实现下一代智能家电的通用零碎而设计的。在借鉴C++语言的根底上,又摒弃了C++的一些简单个性(可能升高软件开发品质)。比方:a. 不容许多继承b. 更纯正的interfacec. 所有皆对象(根底类型除外)d. non-static办法默认反对多态e. 等等 不想“有心栽花花不开,无心插柳柳成荫”。Java 在家电市场毫无起色,却因为优异的网络编程反对能力、平台无关性、垃圾回收等能力,加上恰逢互联网时代的到来,而后在企业级市场上大放异彩。 Java 因为有虚拟机的反对(先编译成字节码,由虚拟机解释成不同平台的“语言”),能够做到“一次编译,到处运行”。Java 目前在后盾开发、大数据以及App开发畛域(Kotlin也是类Java语言)有着十分宽泛的利用。 Java 的内存治理依靠于JVM的垃圾回收器(Garbage Collections)一般而言,垃圾回收的步骤包含两步:a. 找到可被回收的对象;b. 进行内存回收和整顿JVM(HotSpot)的GC也是如此。 (一)可收回对象判断 JVM GC基于可达剖析,来查找可回收对象;能够防止援用计数计划的循环援用问题。    (图1 基于根的可达对象剖析) (二)可回收策略和算法简直所有的垃圾回收器,都存在STW问题,高效回收以及升高对业务代码执行的影响是一件很难的事件。为了尽可能地优化性能,GC采纳 分代收集 和 标记-革除/标记-革除-整顿/标记-复制 进行内存回收。‣ 分代收集 (新生代和老年代)不难理解,新申请的内存比拟大的概率能够在不久后删除;如果一个内存存在比拟久了,那么接下来被回收的概率就会比拟低;新生代的回收会比拟轻量和高效,老年代的GC绝对会比拟重。 G1之前根本都是下图这种典型的分代内存模型。(图2 典型的内存分代模型) G1依然保留了新生代和老年代的概念,然而新生代和老年代的内存区域不再固定,都是一系列的动静汇合。(图3 G1的内存分代模型) ‣ 标记-革除/标记-革除-整顿/标记-复制标记-革除 办法绝对简略、高效,然而会存在内存碎片;标记-革除-整顿 能够解决内存碎片的问题,然而会减少GC的持续时间(益处大于害处);标记-复制 办法相似于ping-pang机制,须要有两片内存区域;在内存清理阶段,会将存活对象对立放到一个区域,而开释另外一个区域;和整顿办法一样,也不会产生内存碎片,并且复制和标记能够同时进行,然而须要更多的内存区域。 JVM有多种垃圾收集器可供选择,须要依据业务需要(低提早or高吞吐)进行衡量,CMS和G1应用绝对较多。 a. CMS用于老年代的垃圾回收,谋求最短进展;b. G1老年代和新生代都能够应用,并且绝对高效;c. Java11 推出的Z Garbage Collector(ZGC)有着不错的性能,目前根本能够投入生产。(https://docs.oracle.com/en/ja...) 1.3 Objective-C/SwiftObjective-C 是基于C语言倒退出的面向对象的开发语言(Objective);Objective-C的语法绝对繁琐、不够便捷,所以苹果在2014推出了Swift,领有脚本语言般的表现力。 Objective-C 的内存治理基于简略的援用计数,能够分为两类: ‣ MRR:Manual Retain-Release(图4 Objective-C MRR机制) ‣ ARC:Automatic Reference CountingARC底层还是MRR,只是由编译器在失当的地位帮咱们插入retain和release。是否开启ARC反对和编译器版本以及编译器选项无关。 1.4 GolangGolang 也是具备垃圾回收的一种语言,次要利用在后端开发畛域;回收策略也是基于可达对象剖析和标记-革除-整顿/复制算法。和Java的比对,能够参考以下链接:https://blog.mooncascade.com/... 02援用模型对对象生命周期的影响不同的援用类型对对象的生命周期影响不一样,从语义上可分为三类:‣ 强援用(Strong reference)强援用对象,不能够被回收如果是基于援用计数,援用计数会被影响‣ 软援用(Soft reference)非必要不回收,比方JVM在OOM之前会尝试对Soft reference对象进行回收- 如果基于援用计数,会进化为弱援用‣ 弱援用(Weak reference)  不影响对象生命期- 如果基于援用计数,不会影响援用计数 03C++的内存治理计划准则:尽量应用智能指针,不要放心智能指针带来性能损耗。 3.1 手动治理内存在某些场景下,C++须要手动治理内存;咱们能够应用一些技巧来更平安地应用和治理内存。a. 防止悬空指针(点击查看大图) b. 基于Allocator 策略进行内存调配通过Allocator能够扭转stl容器的内存分配机制,比方为vector在栈上分配内存;或者应用内存池进行内存治理;(点击查看大图) ...

November 29, 2022 · 1 min · jiezi

关于c++:结构体笔记结构体嵌套自引用结构体指针

构造体笔记(构造体嵌套、自援用,构造体指针)构造体(struct)1、基本概念 构造体-----将不同类型的数据成员组织到对立的名字之下,实用于对关系严密,逻辑相干、具备雷同或不同类型的数据进行解决 2、构造体定义格局定义构造 为了定义构造,您必须应用 struct 语句。struct 语句定义了一个蕴含多个成员的新的数据类型,struct 语句的格局如下: struct 标签名 { 类型 变量名; 类型 变量名; ······ } 构造变量 (构造体名字); struct tag { member_list member_list member_list ... } variable_list ; tag 是构造体标签。 member-list 是规范的变量定义,比方 int i; 或者 float f,或者其余无效的变量定义。 variable-list 构造变量,定义在构造的开端,最初一个分号之前,您能够指定一个或多个构造变量。上面是申明student构造的形式: struct student{ char name[50]; //名字 int id; //学号} student1; (申明构造体类型仅仅是申明了一个类型,零碎并不为之分配内存,就如同零碎不会为类型 int 分配内存一样。只有当应用这个类型定义了变量时,零碎才会为变量分配内存。所以在申明构造体类型的时候,不能够对外面的变量进行初始化。) 定义了一个tag为student的构造体和一个构造变量student1,如果省略变量名(student1),就变成了对构造的申明,上述构造体申明也可离开写 struct student{ char name[50]; //名字 int id; //学号} ;struct student student1; 与下面成果雷同,可了解为struct student相似于int,而咱们用的是student1相似于变量,如果省略构造名,则称之为无名构造,这种状况经常呈现在函数外部,或者说你只须要student1这一个变量, ...

November 25, 2022 · 3 min · jiezi

关于c++:Y-分钟速成-C

源代码下载: learncpp-cn.cpp C++是一种零碎编程语言。用它的发明者, Bjarne Stroustrup的话来说,C++的设计指标是: 成为“更好的C语言”反对数据的形象与封装反对面向对象编程反对泛型编程C++提供了对硬件的严密管制(正如C语言一样), 可能编译为机器语言,由处理器间接执行。 与此同时,它也提供了泛型、异样和类等高层性能。 尽管C++的语法可能比某些呈现较晚的语言更简单,它依然失去了人们的青睞—— 性能与速度的均衡使C++成为了目前利用最宽泛的零碎编程语言之一。 ////////////////// 与C语言的比拟////////////////// C++_简直_是C语言的一个超集,它与C语言的根本语法有许多相同之处,// 例如变量和函数的申明,原生数据类型等等。// 和C语言一样,在C++中,你的程序会从main()开始执行,// 该函数的返回值该当为int型,这个返回值会作为程序的退出状态值。// 不过,大多数的编译器(gcc,clang等)也承受 void main() 的函数原型。// (参见 http://en.wikipedia.org/wiki/Exit_status 来获取更多信息)int main(int argc, char** argv){ // 和C语言一样,命令行参数通过argc和argv传递。 // argc代表命令行参数的数量, // 而argv是一个蕴含“C语言格调字符串”(char *)的数组, // 其中每个字符串代表一个命令行参数的内容, // 首个命令行参数是调用该程序时所应用的名称。 // 如果你不关怀命令行参数的值,argc和argv能够被疏忽。 // 此时,你能够用int main()作为函数原型。 // 退出状态值为0时,示意程序执行胜利 return 0;}// 然而,C++和C语言也有一些区别:// 在C++中,字符字面量的大小是一个字节。sizeof('c') == 1// 在C语言中,字符字面量的大小与int雷同。sizeof('c') == sizeof(10)// C++的函数原型与函数定义是严格匹配的void func(); // 这个函数不能承受任何参数// 而在C语言中void func(); // 这个函数能承受任意数量的参数// 在C++中,用nullptr代替C语言中的NULLint* ip = nullptr;// C++也能够应用C语言的规范头文件,// 然而须要加上前缀“c”并去掉开端的“.h”。#include <cstdio>int main(){ printf("Hello, world!\n"); return 0;}///////////// 函数重载///////////// C++反对函数重载,你能够定义一组名称雷同而参数不同的函数。void print(char const* myString){ printf("String %s\n", myString);}void print(int myInt){ printf("My int is %d", myInt);}int main(){ print("Hello"); // 解析为 void print(const char*) print(15); // 解析为 void print(int)}///////////////////// 函数参数的默认值///////////////////// 你能够为函数的参数指定默认值,// 它们将会在调用者没有提供相应参数时被应用。void doSomethingWithInts(int a = 1, int b = 4){ // 对两个参数进行一些操作}int main(){ doSomethingWithInts(); // a = 1, b = 4 doSomethingWithInts(20); // a = 20, b = 4 doSomethingWithInts(20, 5); // a = 20, b = 5}// 默认参数必须放在所有的惯例参数之后。void invalidDeclaration(int a = 1, int b) // 这是谬误的!{}///////////// 命名空间///////////// 命名空间为变量、函数和其余申明提供了拆散的的作用域。// 命名空间能够嵌套应用。namespace First { namespace Nested { void foo() { printf("This is First::Nested::foo\n"); } } // 完结嵌套的命名空间Nested} // 完结命名空间Firstnamespace Second { void foo() { printf("This is Second::foo\n") }}void foo(){ printf("This is global foo\n");}int main(){ // 如果没有特地指定,就从“Second”中获得所需的内容。 using namespace Second; foo(); // 显示“This is Second::foo” First::Nested::foo(); // 显示“This is First::Nested::foo” ::foo(); // 显示“This is global foo”}////////////// 输出/输入////////////// C++应用“流”来输入输出。<<是流的插入运算符,>>是流提取运算符。// cin、cout、和cerr别离代表// stdin(规范输出)、stdout(规范输入)和stderr(规范谬误)。#include <iostream> // 引入蕴含输出/输入流的头文件using namespace std; // 输入输出流在std命名空间(也就是规范库)中。int main(){ int myInt; // 在规范输入(终端/显示器)中显示 cout << "Enter your favorite number:\n"; // 从规范输出(键盘)取得一个值 cin >> myInt; // cout也提供了格式化性能 cout << "Your favorite number is " << myInt << "\n"; // 显示“Your favorite number is <myInt>” cerr << "Used for error messages";}/////////// 字符串/////////// C++中的字符串是对象,它们有很多成员函数#include <string>using namespace std; // 字符串也在std命名空间(规范库)中。string myString = "Hello";string myOtherString = " World";// + 能够用于连贯字符串。cout << myString + myOtherString; // "Hello World"cout << myString + " You"; // "Hello You"// C++中的字符串是可变的,具备“值语义”。myString.append(" Dog");cout << myString; // "Hello Dog"/////////////// 援用/////////////// 除了反对C语言中的指针类型以外,C++还提供了_援用_。// 援用是一种非凡的指针类型,一旦被定义就不能从新赋值,并且不能被设置为空值。// 应用援用时的语法与原变量雷同:// 也就是说,对援用类型进行解援用时,不须要应用*;// 赋值时也不须要用&来取地址。using namespace std;string foo = "I am foo";string bar = "I am bar";string& fooRef = foo; // 建设了一个对foo的援用。fooRef += ". Hi!"; // 通过援用来批改foo的值cout << fooRef; // "I am foo. Hi!"// 这句话的并不会扭转fooRef的指向,其成果与“foo = bar”雷同。// 也就是说,在执行这条语句之后,foo == "I am bar"。fooRef = bar;const string& barRef = bar; // 建设指向bar的常量援用。// 和C语言中一样,(指针和援用)申明为常量时,对应的值不能被批改。barRef += ". Hi!"; // 这是谬误的,不能批改一个常量援用的值。///////////////////// 类与面向对象编程///////////////////// 无关类的第一个示例#include <iostream>// 申明一个类。// 类通常在头文件(.h或.hpp)中申明。class Dog { // 成员变量和成员函数默认状况下是公有(private)的。 std::string name; int weight;// 在这个标签之后,所有申明都是私有(public)的,// 直到从新指定“private:”(公有继承)或“protected:”(爱护继承)为止public: // 默认的结构器 Dog(); // 这里是成员函数申明的一个例子。 // 能够留神到,咱们在此处应用了std::string,而不是using namespace std // 语句using namespace绝不该当呈现在头文件当中。 void setName(const std::string& dogsName); void setWeight(int dogsWeight); // 如果一个函数不对对象的状态进行批改, // 该当在申明中加上const。 // 这样,你就能够对一个以常量形式援用的对象执行该操作。 // 同时能够留神到,当父类的成员函数须要被子类重写时, // 父类中的函数必须被显式申明为_虚函数(virtual)_。 // 思考到性能方面的因素,函数默认状况下不会被申明为虚函数。 virtual void print() const; // 函数也能够在class body外部定义。 // 这样定义的函数会主动成为内联函数。 void bark() const { std::cout << name << " barks!\n" } // 除了结构器以外,C++还提供了析构器。 // 当一个对象被删除或者脱离其定义域时,它的析构函数会被调用。 // 这使得RAII这样的弱小范式(参见下文)成为可能。 // 为了衍生出子类来,基类的析构函数必须定义为虚函数。 virtual ~Dog();}; // 在类的定义之后,要加一个分号// 类的成员函数通常在.cpp文件中实现。void Dog::Dog(){ std::cout << "A dog has been constructed\n";}// 对象(例如字符串)该当以援用的模式传递,// 对于不须要批改的对象,最好应用常量援用。void Dog::setName(const std::string& dogsName){ name = dogsName;}void Dog::setWeight(int dogsWeight){ weight = dogsWeight;}// 虚函数的virtual关键字只须要在申明时应用,不须要在定义时反复void Dog::print() const{ std::cout << "Dog is " << name << " and weighs " << weight << "kg\n";}void Dog::~Dog(){ std::cout << "Goodbye " << name << "\n";}int main() { Dog myDog; // 此时显示“A dog has been constructed” myDog.setName("Barkley"); myDog.setWeight(10); myDog.print(); // 显示“Dog is Barkley and weighs 10 kg” return 0;} // 显示“Goodbye Barkley”// 继承:// 这个类继承了Dog类中的私有(public)和爱护(protected)对象class OwnedDog : public Dog { void setOwner(const std::string& dogsOwner) // 重写OwnedDogs类的print办法。 // 如果你不相熟子类多态的话,能够参考这个页面中的概述: // http://zh.wikipedia.org/wiki/%E5%AD%90%E7%B1%BB%E5%9E%8B // override关键字是可选的,它确保你所重写的是基类中的办法。 void print() const override;private: std::string owner;};// 与此同时,在对应的.cpp文件里:void OwnedDog::setOwner(const std::string& dogsOwner){ owner = dogsOwner;}void OwnedDog::print() const{ Dog::print(); // 调用基类Dog中的print办法 // "Dog is <name> and weights <weight>" std::cout << "Dog is owned by " << owner << "\n"; // "Dog is owned by <owner>"}/////////////////////// 初始化与运算符重载/////////////////////// 在C++中,通过定义一些非凡名称的函数,// 你能够重载+、-、*、/等运算符的行为。// 当运算符被应用时,这些非凡函数会被调用,从而实现运算符重载。#include <iostream>using namespace std;class Point {public: // 能够以这样的形式为成员变量设置默认值。 double x = 0; double y = 0; // 定义一个默认的结构器。 // 除了将Point初始化为(0, 0)以外,这个函数什么都不做。 Point() { }; // 上面应用的语法称为初始化列表, // 这是初始化类中成员变量的正确形式。 Point (double a, double b) : x(a), y(b) { /* 除了初始化成员变量外,什么都不做 */ } // 重载 + 运算符 Point operator+(const Point& rhs) const; // 重载 += 运算符 Point& operator+=(const Point& rhs); // 减少 - 和 -= 运算符也是有意义的,但这里不再赘述。};Point Point::operator+(const Point& rhs) const{ // 创立一个新的点, // 其横纵坐标别离为这个点与另一点在对应方向上的坐标之和。 return Point(x + rhs.x, y + rhs.y);}Point& Point::operator+=(const Point& rhs){ x += rhs.x; y += rhs.y; return *this;}int main () { Point up (0,1); Point right (1,0); // 这里应用了Point类型的运算符“+” // 调用up(Point类型)的“+”办法,并以right作为函数的参数 Point result = up + right; // 显示“Result is upright (1,1)” cout << "Result is upright (" << result.x << ',' << result.y << ")\n"; return 0;}///////////// 异样解决///////////// 规范库中提供了一些根本的异样类型// (参见http://en.cppreference.com/w/cpp/error/exception)// 然而,其余任何类型也能够作为一个异样被拋出#include <exception>// 在_try_代码块中拋出的异样能够被随后的_catch_捕捉。try { // 不要用 _new_关键字在堆上为异样调配空间。 throw std::exception("A problem occurred");}// 如果拋出的异样是一个对象,能够用常量援用来捕捉它catch (const std::exception& ex){ std::cout << ex.what();// 捕捉尚未被_catch_解决的所有谬误} catch (...){ std::cout << "Unknown exception caught"; throw; // 从新拋出异样}///////// RAII///////// RAII指的是“资源获取就是初始化”(Resource Allocation Is Initialization),// 它被视作C++中最弱小的编程范式之一。// 简略说来,它指的是,用构造函数来获取一个对象的资源,// 相应的,借助析构函数来开释对象的资源。// 为了了解这一范式的用途,让咱们思考某个函数应用文件句柄时的状况:void doSomethingWithAFile(const char* filename){ // 首先,让咱们假如所有都会顺利进行。 FILE* fh = fopen(filename, "r"); // 以只读模式关上文件 doSomethingWithTheFile(fh); doSomethingElseWithIt(fh); fclose(fh); // 敞开文件句柄}// 可怜的是,随着错误处理机制的引入,事件会变得复杂。// 假如fopen函数有可能执行失败,// 而doSomethingWithTheFile和doSomethingElseWithIt会在失败时返回错误代码。// (尽管异样是C++中处理错误的举荐形式,// 然而某些程序员,尤其是有C语言背景的,并不认可异样捕捉机制的作用)。// 当初,咱们必须查看每个函数调用是否胜利执行,并在问题产生的时候敞开文件句柄。bool doSomethingWithAFile(const char* filename){ FILE* fh = fopen(filename, "r"); // 以只读模式关上文件 if (fh == nullptr) // 当执行失败是,返回的指针是nullptr return false; // 向调用者汇报谬误 // 假如每个函数会在执行失败时返回false if (!doSomethingWithTheFile(fh)) { fclose(fh); // 敞开文件句柄,防止造成内存透露。 return false; // 反馈谬误 } if (!doSomethingElseWithIt(fh)) { fclose(fh); // 敞开文件句柄 return false; // 反馈谬误 } fclose(fh); // 敞开文件句柄 return true; // 批示函数已胜利执行}// C语言的程序员通常会借助goto语句简化下面的代码:bool doSomethingWithAFile(const char* filename){ FILE* fh = fopen(filename, "r"); if (fh == nullptr) return false; if (!doSomethingWithTheFile(fh)) goto failure; if (!doSomethingElseWithIt(fh)) goto failure; fclose(fh); // 敞开文件 return true; // 执行胜利failure: fclose(fh); return false; // 反馈谬误}// 如果用异样捕捉机制来批示谬误的话,// 代码会变得清晰一些,然而依然有优化的余地。void doSomethingWithAFile(const char* filename){ FILE* fh = fopen(filename, "r"); // 以只读模式关上文件 if (fh == nullptr) throw std::exception("Could not open the file."); try { doSomethingWithTheFile(fh); doSomethingElseWithIt(fh); } catch (...) { fclose(fh); // 保障出错的时候文件被正确敞开 throw; // 之后,从新抛出这个异样 } fclose(fh); // 敞开文件 // 所有工作顺利完成}// 相比之下,应用C++中的文件流类(fstream)时,// fstream会利用本人的析构器来敞开文件句柄。// 只有来到了某一对象的定义域,它的析构函数就会被主动调用。void doSomethingWithAFile(const std::string& filename){ // ifstream是输出文件流(input file stream)的简称 std::ifstream fh(filename); // 关上一个文件 // 对文件进行一些操作 doSomethingWithTheFile(fh); doSomethingElseWithIt(fh);} // 文件曾经被析构器主动敞开// 与下面几种形式相比,这种形式有着_显著_的劣势:// 1. 无论产生了什么状况,资源(此例当中是文件句柄)都会被正确敞开。// 只有你正确应用了析构器,就_不会_因为遗记敞开句柄,造成资源的透露。// 2. 能够留神到,通过这种形式写进去的代码非常简洁。// 析构器会在后盾敞开文件句柄,不再须要你来操心这些琐事。// 3. 这种形式的代码具备异样安全性。// 无论在函数中的何处拋出异样,都不会妨碍对文件资源的开释。// 纯粹的C++代码该当把RAII的应用扩大到各种类型的资源上,包含:// - 用unique_ptr和shared_ptr治理的内存// - 各种数据容器,例如规范库中的链表、向量(容量主动扩大的数组)、散列表等;// 当它们脱离作用域时,析构器会主动开释其中贮存的内容。// - 用lock_guard和unique_lock实现的互斥扩大浏览:CPP Reference 提供了最新的语法参考。能够在 CPlusPlus 找到一些补充材料。能够在 [TheChernoProject - C ++](https://www.youtube.com/playl...)上找到涵盖语言根底和设置编码环境的教程。有倡议?或者发现什么谬误?在Github上开一个issue,或者发动pull request! ...

November 25, 2022 · 5 min · jiezi

关于c++:小凯15天快速讲完c语言简单学习第十二课

0. 温习0.1 虚继承当咱们应用多继承的时候,子类的多个父类有同名成员,拜访的时候,会呈现二义性class CA{ public: int m_a;};class CB{ public:int m_a;};class CTest:public CA,public CB{};int main(){CTest obj;obj.m_a = 10;return 0;}解决形式1:能够应用类的作用域拜访具体某一个父类的成员解决形式2:给CA和CB形象出一个独特的父类,而后应用虚继承 class CBase{public: int m_a;};class CA:virtual public CBase{public: //int m_a;};class CB:virtual public CBase{public: //int m_a;};class CTest :public CA, public CB{};int main(){ CTest obj; obj.m_a = 20; obj.CA::m_a = 10; obj.CB::m_a = 20; return 0;}如果类B虚继承自类A,此时类A就是虚基类 0.2 虚函数与多态当咱们应用父类指针指向子类对象,调用虚函数,优先调用子类的虚函数。子类如果没有实现这个虚函数,就调用父类的。虚函数是多态机制,属于动静联编。virtual void fun() =0 ; 这个叫做纯虚函数。只是提供了接口,没有提供具体实现,实现由子类来实现。抽象类:蕴含纯虚函数的类,也叫做抽象类。如果子类没有实现父类中的纯虚函数,子类也是抽象类。抽象类的特点:不能定义对象 class Tuxing{public: virtual int ClacArea() = 0;};class Sanjiaoxing:public Tuxing{public: int ClacArea() { return m_nBottom * m_nHeight / 2; }private: int m_nBottom; int m_nHeight;};class CZhengfangxing :public Tuxing{ int ClacArea() { //.. return m_nLenth * m_nLenth; }private: int m_nLenth;};class CCircle:public Tuxing{};int main(){ CCircle obj; CZhengfangxing obj2; return 0;} ...

November 19, 2022 · 5 min · jiezi

关于c++:万字好文从无栈协程到C异步框架

导语 | 本文咱们将尝试对整个 C++的协程做深入浅出的分析,不便大家的了解。再联合下层的封装,最终给出一个 C++异步框架理论业务应用的一种状态,不便大家更好的在理论我的项目中利用无栈协程。 浅谈协程 在开始开展协程前,咱们先来看一下一些非 C++语言中的协程实现。 (一)其余语言中的协程实现很多语言外面,协程是作为 "一类公民" 间接退出到语言个性中的, 比方: Dart1.9示例代码Future<int> getPage(t) async { var c = new http.Client(); try { var r = await c.get('http://xxx'); print(r); return r.length(); } finally { await c.close(); }}Python示例代码async def abinary(n): if n <= 0: return 1 l = await abinary(n-1) r = await abinary(n-1) return l + 1 + rC#示例代码aysnc Task<string> WaitAsync(){ await Task.Delay(10000); return "Finished";}小结泛滥语言都实现了本人的协程机制, 通过下面的例子, 咱们也能看到, 相干的机制使函数的执行特殊化了, 变成了能够屡次中断和重入的构造. 那么如果 C++要反对这种机制, 会是一个什么状况呢? 接下来咱们将先从最根本的原理逐渐开展相干的探讨。 ...

November 9, 2022 · 18 min · jiezi

关于c++:万字避坑指南C的缺陷与思考上

导语 | 本文次要总结了自己在C++开发过程中对一些奇怪、简单的语法的了解和思考,同时作为C++开发的避坑指南。 前言 C++是一门古老的语言,但依然在不间断更新中,一直援用新个性。但与此同时C++又甩不掉微小的历史包袱,并且C++的设计初衷和理念造成了C++异样简单,还呈现了很多不合理的“缺点”。本文次要有3个目标: 总结一些C++艰涩难懂的语法景象,解释其背地起因,作为防踩坑之用。和一些其余的编程语言进行比拟,列举它们的优劣。发表一些我本人作为C++程序员的认识和感触。 来自C语言的历史包袱C++有一个很大的历史包袱,就是C语言。C语言诞生工夫很早,并且它是为了编写OS而诞生的,语法更加底层。有人说,C并不是针对程序员敌对的语言,而是针对编译期敌对的语言。有些场景在C语言自身可能并没有什么不合理,但放到C++当中会“爆炸”,或者说,会迅速变成一种“缺点”,让人异样费解。 C++在演变过程中始终在排汇其余语言的劣势,一直提供新的语法、工具来进行优化。但为了兼容性(不仅仅是语法的兼容,还有一些设计理念的兼容),还是会留下很多坑。 (一)数组 数组自身其实没有什么问题,这种语法也十分罕用,次要是示意间断一组雷同的数据形成的汇合。但数组类型在待遇上却和其余类型(比如说构造体)十分不一样。 数组的复制咱们晓得,构造体类型是能够很轻松的复制的,比如说: struct St { int m1; double m2;};void demo() { St st1; St st2 = st1; // OK St st3; st1 = st3; // OK}但数组却并不能够,比方: int arr1[5];int arr2[5] = arr1; // ERR明明这里arr2和arr1同为int[5]类型,然而并不反对复制。照理说,数组该当比构造体更加适宜复制场景,因为需要是很明确的,就是元素按位复制。 数组类型传参因为数组不能够复制,导致了数组同样不反对传参,因而咱们只能采纳“首地址+长度”的形式来传递数组: void f1(int *arr, size_t size) {}void demo() { int arr[5]; f1(arr, 5);}而为了不便程序员进行这种形式的传参,C又做了额定的2件事: 提供一种隐式类型转换,反对将数组类型转换为首元素指针类型(比如说这里arr是int[5]类型,传参时主动转换为int*类型)函数参数的语法糖,如果在函数参数写数组类型,那么会主动转换成元素指针类型,比如说上面这几种写法都齐全等价:void f(int *arr);void f(int arr[]);void f(int arr[5]);void f(int arr[100]);所以这里非常容易误导人的就在这个语法糖中,无论中括号里写多少,或者不写,这个值都是会被疏忽的,要想晓得数组的边界,你就必须要通过额定的参数来传递。 但通过参数传递这是一种软束缚,你无奈保障调用者传的就是数组元素个数,这里的危害详见前面“指针偏移”的章节。 剖析和思考之所以C的数组会呈现这种奇怪景象,我猜想,作者思考的是数组的理论应用场景,是常常会进行切段截取的,也就是说,一个数组类型并不总是齐全整体应用,咱们可能更多时候用的是其中的一段。举个简略的例子,如果数组是整体复制、传递的话,做数组排序递归的时候会不会很难堪?首先,排序函数的参数难以书写,因为要指定数组个数,咱们总不能针对于1,2,3,4,5,6,...元素个数的数组都别离写一个排序函数吧?其次,如果取子数组就会复制出一个新数组的话,也就不能对原数组进行排序了。 所以综合思考,罗唆这里就不反对复制,强制程序员应用指针+长度这种形式来操作数组,反而更加合乎数组的理论应用场景。 ...

November 7, 2022 · 11 min · jiezi

关于c++:10大性能陷阱每个C工程师都要知道

导语 | 每个C++程序员好像都是人形编译器,不止要看懂代码外表的逻辑,甚至要晓得每行代码对应的汇编指令。优化代码也成了C++工程师日常必备,正所谓“一杯茶,一包烟,一段代码,优化一天”。在经验过无数个性能优化的日夜后,笔者也总结了几个中过招的性能陷阱,与你分享~ 本文介绍的性能陷阱次要分为两大类:“有老本形象”和“与编译器作对”。前者是指在应用C++的性能/库时须要留神的隐形老本,后者则是一些C++老手可能会写出不利于编译器优化的代码。另外本文的程序是由根底到进阶,读者能够依据须要间接跳到本人想看的局部。 有老本形象C++“信徒”们经常宣扬C++的“零老本形象(Zero Cost Abstraction)”。然而对于“零老本形象”这个概念存在很多误会。比方有的老手会认为:“应用C++的任何个性都没有老本”。那显然是大错特错的,比方应用模版就会导致编译工夫变慢的编译期老本,而且我花了21天工夫精通C++的工夫老本也是老本啊(狗头)。有些教训的C++程序员会解释为”应用C++的任何个性都没有运行时老本“,这也是对C++最常见的误会。C++的创始人Bjarne Stroustrup是这样解释“零老本形象”的: 你不会为任何你没有应用的个性付出任何老本。对于你应用的个性,你只会付出最小运行时老本。简略来说,就是C++不会背着你偷偷干坏事(比方垃圾回收),而你指定C++干的活,C++会尽量在编译期做,保障在运行期只会做“起码”的工作。连小学生都应该晓得,“起码”并不等于“零”,所以“零老本形象”其实是一种谎话,Google的C++负责人Chandler Carruth就已经在CppCon 2019说过:C++基本不存在”零老本形象“。 (链接:https://www.youtube.com/watch...) 显然,C++的很多个性是有性能老本的,而且,这些老本往往呈现在你“没有写”的代码里,即C++帮你增加的隐形代码。作为C++工程师,咱们就必须理解每个个性会带来的性能损耗,在做代码设计时尽量抉择损耗小的个性。 而下文介绍的很多坑点,C++语言服务器clangd能够帮你实时检测进去并主动修复。 (一)虚函数陈词滥调的性能损耗,这里只介绍一下虚函数调用带来的老本: 会多一次寻址操作,去虚函数表查问函数的地址。可能会毁坏cpu流水线,因为虚函数调用是一次间接调用,须要进行分支预测。妨碍了编译器内联,大部分状况下,虚函数是无奈被内联的(与前两条相比,无奈内联才是虚函数性能损耗的次要起源)。然而在理论生产环境中,可能很多的运行时多态是无奈防止的,毕竟这是OOP的根底个性,因而对于虚函数咱们也只能理解背地的老本而已。某些状况下咱们能够应用编译期多态来代替虚函数,比方CRTP(Curiously Recurring Template Pattern)、Tempated Visitor Pattern、Policy Based Design等等,我的下一篇文章《C++独有的设计模式》中会介绍这些技巧,敬请期待。 (二)隐形的拷贝也是一个陈词滥调的性能损耗,这里次要介绍几个容易被忽略的场景: Member Initialization构造函数class C { public: C(A a, B b): a_(a), b_(b){} private: A a_; B b_;}int main() { A a; B b; C c(a, b);}如果A、B是非平庸的类,会各被复制两次,在传入构造函数时一次,在结构时一次。C的构造函数该当改为: C(A a, B b): a_(std::move(a)), b_(std::move(b)){}For循环std::vector<std::string> vec;for(std::string s: vec){ // ...}这里每个string会被复制一次,改为for(const std::string& s: vec)即可。 Lambda捕捉A a;auto f = [a]{};lambda函数在值捕捉时会将被捕捉的对象拷贝一次,能够依据需要思考应用援用捕捉auto f= [&a]{};或者用std::move捕捉初始化auto f= [a = std::move(a)]{};(仅限C++14当前)。 ...

November 3, 2022 · 3 min · jiezi

关于c++:算法分析之快速排序算法c实现

算法剖析之疾速排序算法,c++实现疾速排序算法剖析如图所示,数组开始是无序的,从两端开始比拟,找到两头值,而后以两头值为中心点,进行分段 第一次划分而后再从每一段中反复此操作,直到只剩下分段长度为1完结,即i>=j则完结 实现// # 疾速排序,将一个数组,找到一个两头值,而后以这个两头值为核心,分段// # 而后再从每一段中反复此操作,直到只剩下分段长度为1完结,即i>=j则完结 // 1. 定义一个分段函数,参数别离是arr(待排序数组),begin(终点),end(起点)// 2. 初始化函数值 i = begin; j = end;// 3. 从右往左找,i不变,j--;如果arr[i] > arr[j],替换arr[i]和arr[j]的值,而后替换挪动地位,j不变,i--;// 4. 重复操作,直到i >= j;完结算法并返回i#include<iostream>using namespace std;// 排序算法,arr数组,begin终点下标,end起点下标 int sortArr(int arr[], int begin, int end) { int i = begin, j = end; while(i < j) { while(i < j && arr[i] <= arr[j]) j--; if (i < j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } while(i < j && arr[i] <= arr[j]) i++; if (i < j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } return i;}// 分段办法,arr数组,begin终点下标,end起点下标 void quilckSort(int arr[], int begin, int end) { if (begin < end) { int middle = sortArr(arr, begin, end); quilckSort(arr, begin, middle - 1); quilckSort(arr, middle + 1, end); }}int main() { int arr[] = {0, 9, 8, 6, 1, 5, 4, 12, 15, 3, -2}; int len = sizeof(arr) / 4; quilckSort(arr, 0, len - 1); for (int i = 0; i < len; i++) { cout<< arr[i] << " "; } return 0;}

October 31, 2022 · 1 min · jiezi

关于c++:动态规划之01背包问题c实现

动静布局之01背包问题,c++实现问题形容01背包问题问题剖析 动静布局法分析:1.划分子问题,2.得出子问题的递推公式,3.填表划分子问题 用数组Vn存储价值和分量关系,行示意物体,列示意分量第0行和第0列设置为0,没有物体的时候价值没0,分量为0的时候物体无论多少价值也为0如果第i个物体分量小于以后总重量j,则取前i-1和第i个物体组合的最优值,否则该物体不能够放进背包,取前i-1个物体的最优价值(例如装入以后物体,则残余分量用来装后面残余物体的最优装法失去以后最优价值,得出max{V(i-1, j), V(i-1, j-wi)+vi } ;不装入以后物体,则以目前分量装后面所有物体的最优法),得出V(i-1, j)。递推公式 不装入背包时:V(i-1, j) j < wi装入背包时:max{V(i-1, j), V(i-1, j-wi)+vi } j >= wiV(i-1, j-wi)+vi 示意用以后值和子问题的解联合,取最优填表 算法实现#include<iostream>using namespace std;struct Node { int value; int weight;}; // 物体,长度 void packageHander(Node arr[], int n, int c) { int V[n][c+1], i; // 第0行和第0列设置为0,没有物体的时候价值没0,分量3为0的时候物体无论多少价值也为0 for (i = 0; i < n; i++) { V[i][0] = 0; } for (i = 0; i <= c; i++) { V[0][i] = 0; } for (i = 1; i < n; i++) { // 分量从1到c for (int j = 1; j <= c; j++) { // 分量小于目前总量 // 如果第i个物体分量小于以后总重量j,则取前i-1和第i个物体组合的最优值,否则该物体不能够放进背包,取前i-1个物体的最优价值 if (arr[i-1].weight <= j) { V[i][j] = max(V[i-1][j], V[i-1][j-arr[i-1].weight]+arr[i-1].value); } else { V[i][j] = V[i-1][j]; } } } for (i = 0; i <= c; i++) { cout<<i<<"\t"; } cout<<endl; for (i = 0; i < n; i++) { for (int j = 0; j <= c; j++) { cout<<V[i][j]<<"\t"; } cout<<endl; } int j = 0; // 如果以后值比前一个值大,阐明被取了,就取以后值,而后减去分量,分量作为列 for ( i=n, j=c; i>0; i--) { if (V[i][j] > V[i-1][j]) { x[i-1] = 1; j = j - arr[i-1].weight; } else { x[i-1] = 0; } } cout<<"选取计划为"; for (i = 0; i<n-1; i++) { cout<<x[i]<<" "; } cout<<endl;}int main() { int n = 6; Node a[n]; a[0].value = 6; a[0].weight = 2; a[1].value = 3; a[1].weight = 2; a[2].value = 5; a[2].weight = 6; a[3].value = 4; a[3].weight = 5; a[4].value = 6; a[4].weight = 4; for (int i = 0; i < n-1; i++) { cout<<"val"<<a[i].value<<"weight"<<a[i].weight<<endl; } packageHander(a, n, 10); return 0;} 后果如下 ...

October 31, 2022 · 2 min · jiezi

关于c++:C-mutex-类及方法介绍

头文件介绍Mutex 系列类(四种) std::mutex,最根本的 Mutex 类。std::recursive_mutex,递归 Mutex 类。std::time_mutex,定时 Mutex 类。std::recursive_timed_mutex,定时递归 Mutex 类。Lock 类(两种) std::lock_guard,与 Mutex RAII 相干,不便线程对互斥量上锁。std::unique_lock,与 Mutex RAII 相干,不便线程对互斥量上锁,但提供了更好的上锁和解锁管制。其余类型 std::once_flag 标记位,与函数std::call_once 配合应用std::adopt_lock_tstd::defer_lock_tstd::try_to_lock_t函数 std::try_lock,尝试同时对多个互斥量上锁。std::lock,能够同时对多个互斥量上锁。std::scoped_lock RALL,能够同时对多个互斥量上锁并保障解锁。std::call_once,如果多个线程须要同时调用某个函数,call_once 能够保障多个线程对该函数只调用一次。mutexmutex 的全名为 mutual exclusion(互斥体),其 object 用来帮助采取独占且排他的形式管制“对资源(object或多个Object的组合)的并发拜访”。 构造函数,std::mutex不容许拷贝结构,也不容许 move 拷贝,最后产生的 mutex 对象是处于 unlocked 状态的。lock(),调用线程将锁住该互斥量。线程调用该函数会产生上面 3 种状况: (1). 如果该互斥量以后没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程始终领有该锁。(2). 如果以后互斥量被其余线程锁住,则以后的调用线程被阻塞住。(3). 如果以后互斥量被以后调用线程锁住,则会产生死锁(deadlock)。unlock(), 解锁,开释对互斥量的所有权。try\_lock(),尝试锁住互斥量,如果互斥量被其余线程占有,则以后线程也不会被阻塞。线程调用该函数也会呈现上面 3 种状况, (1). 如果以后互斥量没有被其余线程占有,则该线程锁住互斥量,直到该线程调用 unlock 开释互斥量。(2). 如果以后互斥量被其余线程锁住,则以后调用线程返回 false,而并不会被阻塞掉。(3). 如果以后互斥量被以后调用线程锁住,则会产生死锁(deadlock)。上面给出一个与 std::mutex 的小例子: #include <iostream> // std::cout#include <mutex> // std::mutex#include <thread> // std::threadvolatile int counter(0); // non-atomic counterstd::mutex mtx; // locks access to countervoid attempt_10k_increases() { for (int i = 0; i < 10000; ++i) { if (mtx.try_lock()) { // only increase if currently not locked: ++counter; mtx.unlock(); } }}int main(int argc, const char* argv[]) { std::thread threads[10]; for (int i = 0; i < 10; ++i) threads[i] = std::thread(attempt_10k_increases); for (auto& th : threads) th.join(); std::cout << counter << " successful increases of the counter.\n"; return 0;}std::recursive\_mutex 介绍std::recursive_mutex 与 std::mutex 一样,也是一种能够被上锁的对象,然而和 std::mutex 不同的是,std::recursive_mutex 容许同一个线程对互斥量屡次上锁(即递归上锁),来取得对互斥量对象的多层所有权,std::recursive_mutex 开释互斥量时须要调用与该锁档次深度雷同次数的 unlock(),可了解为 lock() 次数和 unlock() 次数雷同,除此之外,std::recursive_mutex 的个性和 std::mutex 大致相同。 ...

October 28, 2022 · 6 min · jiezi

关于c++:2022升级百度大牛带你结合实践重学C网潘货区

download:2022降级百度大牛带你联合实际重学C++网潘货区数据库什么时候分表? 子数据库和子表要解决的是现有海量数据拜访的性能瓶颈,以及一直减少的数据量的架构前瞻。数据库划分和表划分的要害指标是否是数据量。咱们以fire100.top网站的资源表t_resource为例。零碎运行初期,每天只上传几十个资源。此时应用单数据库单表的形式足以撑持零碎的存储,数据量小简直没有任何数据库性能瓶颈。然而有一天,一个神秘的流量进来了,零碎产生的资源数据量忽然减少到10万甚至上百万。此时资源表数据量达到数千万,查问响应变得迟缓,数据库的性能瓶颈逐步浮现。以MySQL数据库为例,单个表的数据量达到亿级。当通过增加索引和SQL调优等传统优化策略取得的性能晋升依然很小时,能够思考划分数据库和表。既然MySQL在存储海量数据时会呈现性能瓶颈,是否能够思考用其余计划代替?比方高性能非关系数据库MongoDB?能够,但这取决于存储的数据类型!目前互联网上大多数公司的外围数据简直都存储在关系数据库(MySQL、Oracle等。),因为它们领有堪比NoSQL的稳定性和可靠性,产品的成熟生态系统,以及其余存储工具不具备的外围交易个性。不过还是能够思考用MongoDB来评论和赞美这些非核心数据。如何划分数据库和表 子数据库和子表的外围是将数据分片并绝对平均地路由到不同的数据库和表中,分片后疾速定位数据并整合搜寻后果。 图书馆和桌子能够从垂直(纵向)和程度(横向)两个纬度来划分。咱们以经典的订单业务为例,看看如何拆分。 垂直决裂1.垂直存储一般来说,垂直数据库是依照业务和性能的维度划分的,不同的业务数据放在不同的数据库中,外围概念库是专用的。依据业务类型,将数据拆分到多个数据库中,订单、领取、会员、积分等表放在相应的订单数据库、领取数据库、会员数据库、积分数据库中。不同服务禁止跨数据库直连,获取的对方业务数据全副通过API接口交互,这也是微服务拆分的重要依据。 垂直的数据库划分很大水平上取决于业务的划分,然而有时候业务之间的划分并不是那么清晰,比方电商中订单数据的拆分,其余很多业务都是依赖订单数据的,有时候界线划分的并不好。垂直子数据库将一个数据库的压力扩散到多个数据库,进步了局部数据库的性能。然而没有解决单个表数据量大带来的性能问题,须要前面的子表来解决。2.垂直子表对业务中字段较多的大表进行竖表拆分。个别将宽业务表中的独立字段或不常用字段拆分成独自的数据表,这是一种将大表拆分成小表的模式。例如,一个t_order表有几十个字段,其中订单金额的相干字段是常常计算的。为了不影响订单表t_order的性能,能够将order amount的相干字段离开,保护一个独自的t_order_price_expansion表,使每个表只存储原表的一部分字段,这些字段由订单号order_no关联,而后将拆分后的表路由到不同的库。 数据库是以行为为单位将数据加载到内存中的,所以拆分后的外围表大部分是拜访频率高的字段,字段的长度也较短,这样能够将更多的数据加载到内存中,缩小磁盘IO,进步索引查问的命中率,进一步提高数据库性能。程度决裂数据库和表垂直拆散后,依然会存在单个数据库和表中数据过多的问题。当咱们的利用无奈进行细粒度的垂直划分时,单个数据库读写和存储的性能依然存在瓶颈。这时候就须要配合数据库和表的横向拆散。1、图书馆的程度横向数据库拆分是将同一个表依照肯定的规定拆分成不同的数据库,每个数据库能够位于不同的服务器上,从而实现横向扩大,是进步数据库性能的罕用办法。 例如,db_orde_1和db_order_2的数据库中有雷同的t_order表。当咱们拜访一个订单时,咱们能够通过对订单的订单号取模来指定该订单应该在哪个数据库中操作:订单号mod 2(数据库实例的数量)。这种计划往往能够解决单库存和性能瓶颈的问题,然而因为同一个表散布在不同的数据库中,数据拜访须要额定的路由工作,因而零碎的复杂度也有所提高。2.程度表在同一个数据库中,一个数据量很大的表依照肯定的规定被分成若干个构造雷同的表,每个表只存储原表的一部分数据。例如,一个t_order订单表有900万个数据,t_order_1、t_order_2和t_order_3三个表被程度拆分。每个表有300万个数据,以此类推。 程度表尽管拆分了表,然而子表都在同一个数据库实例中,只是解决了单个表数据过多的问题,并没有把拆分的表扩散到不同的机器上,依然在抢夺CPU、内存、网络IO等。同一个物理机器。为了进一步提高性能,须要将拆分后的表扩散到不同的数据库中,以达到分布式的成果。 存在哪个数据库的表。在将来,会有一个问题。一个表将呈现在多个数据库中。到底应该寄存在哪个数据库的哪个表呢?咱们在下面曾经屡次提到某些规定。实际上,这个规定是一个路由算法,它决定了一个数据应该存储在哪个数据库的哪个表中。常见的有取模算法、范畴限度算法、范畴+取模算法和预约义算法。1.模块化算法关键字段模块化(从散列后果中取余数hash(XXX) mod N),其中N是数据库实例或子表的数量)是最常见的路由办法。以t_order表为例,先将数据库从0到N-1编号,而后对t_order表中的order number字段取模hash (order_no) mod n失去余数I,I=0存储第一个库,i=1存储第二个库,i=2存储第三个库,以此类推。 雷同程序的数据将落在雷同的数据库和表中。查问时应用同样的规定,以t_order订单号作为查问条件,能够疾速定位数据。劣势实现简略,数据分布绝对平均,申请不容易命中一个数据库。劣势模块化算法对集群的扩大反对不是很敌对。集群中有n个数据库real hash (user _ id) mod n。当某台机器停机时,本应达到数据库的申请不能被解决,而后被抛弃的实例将被踢出集群。此时,机器号缩小算法扭转hash(user_id) mod N-1,雷同的用户数据落在不同的数据库中。本机复原后,以user_id为条件的用户数据查问会少一些。2.范畴限度算法范畴限度算法由一些范畴字段宰割,如工夫或ID区域。用户表t_user分为三个表:t_user_1、t_user_2和t_user_3。而后将user_id范畴从1到1000 W的用户数据放入t_user_1,1000到2000 W放入t_user_2,2000到3000 W放入t,日期范畴也是如此。劣势单表数据量可控。横向扩大很简略,减少节点即可,不须要迁徙其余碎片化数据。劣势因为间断切片可能存在数据热点,比方按工夫域切片时,如果某段时间(如双11)订单急剧减少,存储11月数据的表可能会频繁读写,而存储在其余切片表中的历史数据很少被查问,导致数据歪斜,数据库压力散布不平均。 3.范畴+模数算法为了防止热数据的问题,咱们能够优化下限算法。这次咱们先通过range算法定义每个数据库的用户表t_user只存储1000w的数据,第一个db_order_1库存的userId从1到1000 W,第二个数据库是10002000w,第三个数据库是20003000w,以此类推。 在每个数据库中,用户表t_user被拆分成t_user_1、t_user_2、t_user_3等。,userd模路由到相应的表。无效防止了数据分布不平均的问题,数据库的横向扩大也很简略。间接增加实例不须要迁徙历史数据。4.地理位置碎片化地理位置碎片化其实是一个更大的范畴,是以城市或者区域来划分的。比方华东和华北的数据放在不同的碎片化数据库和表中。5.预约义算法预约义算法是事后明确晓得子数据库和子表的数量,它能够间接将某一类数据路由到指定的数据库或表,甚至在查问的时候。子数据库和子表的问题不难发现,与拆分前的单数据库、单表相比,当初零碎的数据存储架构曾经变得非常复杂。看几个有代表性的问题,比方:分页、排序和跨节点联结查问分页、排序、联查,这些开发中频繁应用的看似一般的操作,在数据库和表划分后却是令人头疼的事件。查问扩散在不同库中的表的数据,而后将所有后果汇总合并提供给用户。例如,咱们须要查问11月和12月的订单数据。如果两个月的数据扩散在不同的数据库实例中,咱们须要查问两个数据库相干的数据。合并、排序和分页数据的过程很简单。

October 19, 2022 · 1 min · jiezi

关于c++:c中string的stoi函数

在学习c++的stoi函数时,遇到第二个参数不明确如何正确应用 int stoi (const string& str, size_t* idx = 0, int base = 10)后查找材料如下:所以第二个参数的应用分两种状况: 状况1:用0或者nullptr,示意不应用该参数(比拟常见)状况2:搁置一个size_t类型的指针,str调用stoi函数后,该指针指向str中第一个不为数字的下标对状况2举个栗子: #include<iostream>#include<string>using namespace std;int main() { string s = "1345s3544"; size_t n ; std::size_t* pos=&n; int m = stoi(s,pos,10); cout << *pos;}最终输入后果为4

October 19, 2022 · 1 min · jiezi

关于c++:linux-epoll用法记录

include <sys/epoll.h>/* 创立一个epoll的句柄,size用来通知内核须要监听的数目一共有多大。当创立好epoll句柄后,它就是会占用一个fd值,所以在应用完epoll后,必须调用close()敞开,否则可能导致fd被耗尽。*/int epoll_create(int size); /epoll的事件注册函数/int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); /期待事件的到来,如果检测到事件,就将所有就绪的事件从内核事件表中复制到它的第二个参数events指向的数组/int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); struct epoll_event ev, *events;for(;;) { nfds = epoll_wait(kdpfd, events, maxevents, -1);for(n = 0; n < nfds; ++n) { if(events[n].data.fd == listener) { client = accept(listener, (struct sockaddr *) &local, &addrlen); if(client < 0){ perror("accept"); continue; } setnonblocking(client); ev.events = EPOLLIN | EPOLLET; ev.data.fd = client; if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) { fprintf(stderr, "epoll set insertion error: fd=%d0, client); return -1; } } else do_use_fd(events[n].data.fd);}} ...

October 16, 2022 · 1 min · jiezi

关于c++:OneFlow源码阅读8eager模式下的SBP-Signature推导

oneflow 的 global tensor 有两个必要属性: placement:决定了 tensor 数据分布在哪些设施上。sbp:决定了 tensor 数据在这些设施上的散布形式。例如: split:将切分后的不同局部放到不同设施;同时指定切分的 axis。broadcast:将数据复制到各个设施。如果参加运算的 tensor 的 sbp 不一样,后果 tensor 的 sbp 是什么呢?例如上面的代码: # export MASTER_ADDR=127.0.0.1 MASTER_PORT=17789 WORLD_SIZE=2 RANK=0 LOCAL_RANK=0# export MASTER_ADDR=127.0.0.1 MASTER_PORT=17789 WORLD_SIZE=2 RANK=1 LOCAL_RANK=1import oneflow as flowP0 = flow.placement("cpu", ranks=[0, 1])t1 = flow.Tensor([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]], placement=P0, sbp=flow.sbp.split(0))# t1 = flow.Tensor([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]], placement=P0, sbp=flow.sbp.broadcast)t2 = flow.Tensor([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]], placement=P0, sbp=flow.sbp.split(1))t3 = t1 + t2# oneflow.placement(type="cpu", ranks=[0, 1])print(t3.placement)# (oneflow.sbp.split(dim=0),)print(t3.sbp)t1和t2是散布在雷同设施上的两个 tensor。t1.sbp是S(0),在行上切分;t2.sbp是S(1),在列上切分。计算结果t3的 sbp 不须要用户手动指定,零碎能够主动推导出t3.sbp是S(0)。这个过程中的一个外围步骤,就是 SBP Signature 的推导。 ...

October 14, 2022 · 4 min · jiezi

关于c++:cprimer转载函数指针练习65457

原文地址:https://blog.csdn.net/qq_5027... #include<iostream>#include<vector>#define NDEBUGusing namespace std;//练习6.54-6.57 定义函数int fun1(int a, int b) { return a + b;}int fun2(int a, int b) { return a - b;}int fun3(int a, int b) { return a * b;}int fun4(int a,int b){ return a / b;}void compute(int a, int b, int p(int,int)) {//函数指针作形参,函数类型会主动转换成指向函数的指针,也可写成void compute(int a, int b, int (*p)(int,int)) cout << p(a, b) << endl;}int main() { typedef int (*func)(int a, int b); vector<func> v = {&fun1,fun2,fun3,fun4};//函数名作为一个值时,&是选加 for (auto s : v) { compute(10, 2, s); } return 0;}

October 13, 2022 · 1 min · jiezi

关于c++:ffplay调试环境搭建

前言ffplay是基于FFmpeg的最简略的官网播放器。麻雀虽小,五脏俱全,虽说ffplay简略,然而各种播放器应有的性能一一俱全,说它简略或者仅仅是因为它只有一个点c文件而已吧。 想要开发一个优良的播放器,参考是必不可少的,毕竟control c和control v是程序员天生的本能。以前遇到播放的问题向人求教的时,常常听到的一句话就是去看看ffplay是怎么解决的就晓得怎么过解决了呀,可见ffplay在播放畛域的位置不个别。。。 而且想要学好FFmpeg,在理解了FFmpeg的基本知识和相干API之后,再联合我的项目实战是十分有必要的。然而光是跑马观花式的看看ffplay的源码可能播种甚微,要想深刻去理解ffplay外部的大抵细节,集成源码断点调试是必可少的。后续笔者将用几篇文章介绍ffplay的一些次要的性能点,然而想要理解ffplay单靠几篇文章必定是不够的,笔者更加心愿的是这几篇博客能起到抛砖引玉的作用,疏导大家更加深刻地学习理解。。。 所谓兵马未动,粮草先行,明天咱们就先搭建好ffplay的调试环境,不便大家后续的学习调试。 环境搭建笔者所应用的环境是:Mac零碎和Clion开发工具。笔者集成的FFmpeg的版本是应用Homebrew装置的ffmpeg5.0 1、编译集成FFmpeg库 ffplay的调试环境搭建是建设在笔者之前的文章 《FFmpeg连载1-开发环境搭建》 之上,首先须要集成FFmpeg的相干库,这一步大家参考笔者之前的文章即可。 2、新建Clion工程 新建好CLion工程后(留神是C工程,不是C++工程),咱们就须要下载FFmpeg的源码了,留神这个源码的版本最好和后面集成FFmpeg的版本好一样,即便不一样版本呢之间也不要相差太远,不然可能会因为版本的起因导致一些性能或头文件对应不上。 3、生成config.h头文件 下载好FFmpeg的源码后,咱们须要进入到源码目录执行一下./configure命令行,这一步的目标是生成config.h头文件。而后找到文件config.h并拷贝到咱们新建CLion工程中去。 4、拷贝fftools目录下的相干文件 咱们将源码目录fftools下的三个文件拷贝进去咱们的CLion工程,这三个文件别离是cmdutils.c、cmdutils.h、 ffplay.c。 5、拷贝va_copy.h头文件 将源码目录compat下的头文件va_copy.h也拷贝进去咱们新建的CLion目录,到这里咱们所需的源文件是算是筹备实现了。 6、批改CMakeLists.txt 批改CMakeLists.txt如下,次要相干库的门路要替换成本人的FFmpeg库和SDL库的实在门路。 cmake_minimum_required(VERSION 3.21)# 这里是工程名称 C工程project(FFplay_debug C)set(CMAKE_C_STANDARD 99)cmake_minimum_required(VERSION 3.17)# FFmpeg的装置目录,能够通过命令"brew info ffmpeg"获取# 须要替换成本人的FFMpeg装置目录set(FFMPEG_DIR /opt/homebrew/Cellar/ffmpeg/5.0)# SDL2的装置目录,能够通过命令"brew info sdl2"获取# 须要替换成本人的SDL2装置目录set(SDL2_DIR /opt/homebrew/Cellar/sdl2/2.0.20)# 头文件搜寻门路include_directories(${FFMPEG_DIR}/include/)include_directories(${CMAKE_SOURCE_DIR})include_directories(${SDL2_DIR}/include/SDL2/)# 动态链接库或动态链接库的搜寻门路link_directories(${FFMPEG_DIR}/lib/)link_directories(${SDL2_DIR}/lib/)#将指定文件设置在FFmpeg_test_source变量中file(GLOB FFplay_debug_source ${CMAKE_SOURCE_DIR}/*.*)add_executable(FFplay_debug ${FFplay_debug_source} cmdutils.c)#链接库target_link_libraries(FFplay_debug #FFmpeg 库 avcodec avdevice postproc avfilter avformat avutil swresample swscale # SDL2库 SDL2 )7、debug测试 而后就能够欢快地进行断点调试了,找到ffplay.c的main函数,打一个断点,而后debug运行一下。 同理,如果童鞋们想要调试一下ffprobe.c或ffmpeg.c也能够应用同样的形式集成即可。 可能遇到的问题1、SDL2没有装置 ffplay是须要依赖SDL进行画面的展现和声音的播放的。所以集成的前提是须要装置好SDL2。 2、文件"cmdutils.c"找不到头文件"libavutil/libm.h" 笔者集成的时候就遇到了这个问题,解决方案就是间接正文掉即可。 举荐浏览FFmpeg连载1-开发环境搭建 FFmpeg连载2-拆散视频和音频 FFmpeg连载3-视频解码 FFmpeg连载4-音频解码 FFmpeg连载5-音视频编码 FFmpeg连载6-音频重采样 FFmpeg连载8-视频合并以及替换视频背景音乐实战 ...

October 12, 2022 · 1 min · jiezi

关于c++:FFmpeg连载8视频合并以及替换视频背景音乐实战

前言通过后面的实战,咱们实现音视频解封装提取、音视频解码、音视频编码、音频重采样等的性能,明天咱们就联合之前所做的性能,来做一个短视频APP中常见的性能: 1、提取多个mp3文件中的音频,从新编码为合并为aac 2、提取mp4中的视频,从新编码合并为h264 3、h264与aac合并成新的mp4文件因为咱们的目标是以实战为主,为了囊括之前所学的一些知识点,在这个实战中咱们不仅仅须要实现音视频解封装提取、音视频解码、音视频编码、音频重采样这些性能,咱们还须要联合多线程同步等知识点做好生产者消费者队列缓冲管制。还蕴含例如类成员函数作为线程执行函数的应用等知识点。 大抵框架这里要阐明一个常识就是如果音频如果须要合并的话要保障两个音频的采样率、采样格局以及通道数统一,所以须要重采样,为了测试,笔者把音频都重采样为22050hz。 同时视频也一样,如果视频须要合并也须要保障两个视频的分辨率是一样的,这里笔者对立把尺寸转换为720x1280。 笔者文笔不好,常常一句卧槽走天下,间接看图吧。。。 代码实现原本笔者想谋求简略,心愿用一个cpp文件实现的,前面写着写着发现代码量有点多,所以就拆分成了三个cpp文件,上面是代码详情: AudioHandle.cpp /** * 音频解决 * 解码音频,并且重采样为22050,而后编码成aac */#ifndef TARGET_AUDIO_SAMPLE_RATE// 采样率#define TARGET_AUDIO_SAMPLE_RATE 22050#endif#include <iostream>#include <vector>extern "C" {#include <libavcodec/avcodec.h>#include <libavformat/avformat.h>#include <libavutil/opt.h>#include <libavutil/channel_layout.h>#include <libavutil/audio_fifo.h>#include <libswresample/swresample.h>};class AudioHandle {public: void handle_audio(std::vector<char *> mp3_paths, std::function<void(const AVCodecContext *, AVPacket *, bool)> callback) { // 音频编码器相干 const AVCodec *avCodec = avcodec_find_encoder(AV_CODEC_ID_AAC); audio_encoder_context = avcodec_alloc_context3(avCodec); audio_encoder_context->sample_rate = TARGET_AUDIO_SAMPLE_RATE; // 默认的aac编码器输出的PCM格局为:AV_SAMPLE_FMT_FLTP audio_encoder_context->sample_fmt = AV_SAMPLE_FMT_FLTP; audio_encoder_context->channel_layout = AV_CH_LAYOUT_STEREO;// audio_encoder_context->bit_rate = 128 * 1024; audio_encoder_context->codec_type = AVMEDIA_TYPE_AUDIO; audio_encoder_context->channels = av_get_channel_layout_nb_channels(audio_encoder_context->channel_layout); audio_encoder_context->profile = FF_PROFILE_AAC_LOW; //ffmpeg默认的aac是不带adts,而fdk_aac默认带adts,这里咱们强制不带 audio_encoder_context->flags = AV_CODEC_FLAG_GLOBAL_HEADER; int ret = avcodec_open2(audio_encoder_context, avCodec, nullptr); if (ret < 0) { std::cout << "音频编码器关上失败" << std::endl; return; } // 初始化audiofifo audiofifo = av_audio_fifo_alloc(audio_encoder_context->sample_fmt, audio_encoder_context->channels, audio_encoder_context->frame_size); AVFormatContext *avFormatContext = nullptr; AVCodecContext *decoder_context = nullptr; AVPacket *avPacket = av_packet_alloc(); AVFrame *avFrame = av_frame_alloc(); std::vector<AVPacket *> pack_vector = std::vector<AVPacket *>(); while (!mp3_paths.empty()) { // 先开释旧的 avcodec_free_context(&decoder_context); avformat_free_context(avFormatContext); const char *mp3 = mp3_paths.at(0); mp3_paths.erase(mp3_paths.cbegin()); avFormatContext = avformat_alloc_context(); ret = avformat_open_input(&avFormatContext, mp3, nullptr, nullptr); if (ret < 0) { std::cout << "音频文件关上失败" << std::endl; break; } int audio_index = av_find_best_stream(avFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0); if (audio_index < 0) { for (int i = 0; i < avFormatContext->nb_streams; ++i) { if (AVMEDIA_TYPE_AUDIO == avFormatContext->streams[i]->codecpar->codec_type) { audio_index = i; std::cout << "找到音频流,audio_index:" << audio_index << std::endl; break; } } if (audio_index < 0) { std::cout << "没有找到音频流" << std::endl; break; } } const AVCodec *avCodec = avcodec_find_decoder(avFormatContext->streams[audio_index]->codecpar->codec_id); decoder_context = avcodec_alloc_context3(avCodec); avcodec_parameters_to_context(decoder_context, avFormatContext->streams[audio_index]->codecpar); ret = avcodec_open2(decoder_context, avCodec, nullptr); if (ret < 0) { std::cout << "音频解码器关上失败" << std::endl; break; } while (true) { ret = av_read_frame(avFormatContext, avPacket); if (ret < 0) { std::cout << "音频包读取结束" << std::endl; break; } if (avPacket->stream_index != audio_index) { av_packet_unref(avPacket); continue; } ret = avcodec_send_packet(decoder_context, avPacket); if (ret < 0) { std::cout << "音频包发送解码失败" << std::endl; break; } while (true) { ret = avcodec_receive_frame(decoder_context, avFrame); if (ret == AVERROR(EAGAIN)) { std::cout << "音频包获取解码帧:EAGAIN" << std::endl; break; } else if (ret < 0) { std::cout << "音频包获取解码帧:fail" << std::endl; break; } else { std::cout << "从新编码音频" << std::endl; // 先进行重采样 resample_audio(avFrame); pack_vector.clear(); encode_audio(pack_vector, out_frame); while (!pack_vector.empty()) { AVPacket *packet = pack_vector.at(0); pack_vector.erase(pack_vector.cbegin()); // 回调 callback(audio_encoder_context, packet, false); } } } av_packet_unref(avPacket); } } avcodec_free_context(&decoder_context); avformat_free_context(avFormatContext); // 回调完结 callback(audio_encoder_context, nullptr, true); }private: // 视频编码器 AVCodecContext *audio_encoder_context = nullptr; AVFrame *encode_frame = nullptr; AVAudioFifo *audiofifo = nullptr; int64_t cur_pts = 0; // 重采样相干 SwrContext *swrContext = nullptr; AVFrame *out_frame = nullptr; int64_t max_dst_nb_samples; void init_out_frame(int64_t dst_nb_samples){ av_frame_free(&out_frame); out_frame = av_frame_alloc(); out_frame->sample_rate = TARGET_AUDIO_SAMPLE_RATE; out_frame->format = AV_SAMPLE_FMT_FLTP; out_frame->channel_layout = AV_CH_LAYOUT_STEREO; out_frame->nb_samples = dst_nb_samples; // 调配buffer av_frame_get_buffer(out_frame,0); av_frame_make_writable(out_frame); } /** * 重采样 * @param avFrame */ void resample_audio(AVFrame *avFrame){ if (nullptr == swrContext) { /** * 以下能够应用 swr_alloc、av_opt_set_channel_layout、av_opt_set_int、av_opt_set_sample_fmt * 等API设置,更加灵便 */ swrContext = swr_alloc_set_opts(nullptr, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_FLTP, TARGET_AUDIO_SAMPLE_RATE, avFrame->channel_layout, static_cast<AVSampleFormat>(avFrame->format), avFrame->sample_rate, 0, nullptr); swr_init(swrContext); } // 进行音频重采样 int src_nb_sample = avFrame->nb_samples; // 为了放弃从采样后 dst_nb_samples / dest_sample = src_nb_sample / src_sample_rate max_dst_nb_samples = av_rescale_rnd(src_nb_sample, TARGET_AUDIO_SAMPLE_RATE, avFrame->sample_rate, AV_ROUND_UP); // 从采样器中会缓存一部分,获取缓存的长度 int64_t delay = swr_get_delay(swrContext, avFrame->sample_rate); // 相当于a*b/c int64_t dst_nb_samples = av_rescale_rnd(delay + avFrame->nb_samples, TARGET_AUDIO_SAMPLE_RATE, avFrame->sample_rate, AV_ROUND_UP); if(nullptr == out_frame){ init_out_frame(dst_nb_samples); } if (dst_nb_samples > max_dst_nb_samples) { // 须要重新分配buffer std::cout << "须要重新分配buffer" << std::endl; init_out_frame(dst_nb_samples); max_dst_nb_samples = dst_nb_samples; } // 重采样 int ret = swr_convert(swrContext, out_frame->data, dst_nb_samples, const_cast<const uint8_t **>(avFrame->data), avFrame->nb_samples); if(ret < 0){ std::cout << "重采样失败" << std::endl; } else{ // 每帧音频数据量的大小 int data_size = av_get_bytes_per_sample(static_cast<AVSampleFormat>(out_frame->format)); // 返回值才是真正的重采样点数 out_frame->nb_samples = ret; std::cout << "重采样胜利:" << ret << "----dst_nb_samples:" << dst_nb_samples << "---data_size:" << data_size << std::endl; } } void encode_audio(std::vector<AVPacket *> &pack_vector, AVFrame *avFrame) { int cache_size = av_audio_fifo_size(audiofifo); std::cout << "cache_size:" << cache_size << std::endl; av_audio_fifo_realloc(audiofifo, cache_size + avFrame->nb_samples); av_audio_fifo_write(audiofifo, reinterpret_cast<void **>(avFrame->data), avFrame->nb_samples); if (nullptr == encode_frame) { encode_frame = av_frame_alloc(); encode_frame->nb_samples = audio_encoder_context->frame_size; encode_frame->sample_rate = audio_encoder_context->sample_rate; encode_frame->channel_layout = audio_encoder_context->channel_layout; encode_frame->channels = audio_encoder_context->channels; encode_frame->format = audio_encoder_context->sample_fmt; av_frame_get_buffer(encode_frame, 0); } av_frame_make_writable(encode_frame); // todo 如果是冲刷最初几帧数据,不够的能够填充静音 av_samples_set_silence while (av_audio_fifo_size(audiofifo) > audio_encoder_context->frame_size) { int ret = av_audio_fifo_read(audiofifo, reinterpret_cast<void **>(encode_frame->data), audio_encoder_context->frame_size); if (ret < 0) { std::cout << "audiofifo 读取数据失败" << std::endl; return; } // 批改pts cur_pts += encode_frame->nb_samples; encode_frame->pts = cur_pts; ret = avcodec_send_frame(audio_encoder_context, encode_frame); if (ret < 0) { std::cout << "发送编码失败" << std::endl; return; } while (true) { AVPacket *out_pack = av_packet_alloc(); ret = avcodec_receive_packet(audio_encoder_context, out_pack); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { std::cout << "avcodec_receive_packet end:" << ret << std::endl; break; } else if (ret < 0) { std::cout << "avcodec_receive_packet fail:" << ret << std::endl; return; } else { pack_vector.push_back(out_pack); } } } }};VideoHandle.cpp ...

October 12, 2022 · 9 min · jiezi

关于c++:FFmpeg连载7mp3转码aac及AVAudioFifo的使用

前言现在以抖音、快手为代表的短视频秀无处不在,比方它们一个很一般的性能就是应用流行音乐替换作为视频的背景音乐。而在视频中音频个别都是以AAC的造成存在,但流行音乐大多以mp3的格局流传,因而须要实现背景音乐替换这个性能,其中的一个步骤就须要实现mp3转aac这样的一个音频转转码的过程。 依照咱们以往的教训,转码的大抵流程应该是这样的: 解封装->提取音频流->解码成PCM->从新编码成AAC流程是这样没错,然而外部的进去细节是怎么的呢?是mp3解码进去后的AVFrame能够通过函数avcodec_send_frame送进aac编码器即可吗? 很显著这是不行的,因为mp3每帧是1152个采样点,而aac每帧是1024个采样点。它们每帧的采样点数不同,所以不能间接通过avcodec_send_frame进行编码。 AVAudioFifo·AVAudioFifo是一个音频缓冲区,是一个先进先出的队列。应用它能够很不便地贮存咱们的音频缓冲数据,例如在mp3转码aac的过程中,因为它们的采样点数不同,咱们就能够把mp3解码进去的pcm数据放入到AVAudioFifo中去,而后每次从AVAudioFifo中获取1024个采样点送进aac编码器,这样的做法让咱们的音频转码变得十分的不便灵便。AVAudioFifo让咱们在采样层面做操作,而不必关怀底层的字节层面;而且它反对多种格局的单次采样,如反对planar或packed的采样格局,反对不同的通道数等等。 AVAudioFifo的API应用也非常简单,次要蕴含调配开释、获取可读写空间长度、写入音频数据、读取音频数据等相干函数: 首先是调配和开释操作: //调配一个AVAudioFifo。//sample_fmt指定采样格局//nb_samples则指定AVAudioFifo的缓冲区大小,能够通过av_audio_fifo_realloc重新分配AVAudioFifo *av_audio_fifo_alloc(enum AVSampleFormat sample_fmt, int channels,int nb_samples); //重新分配缓冲区大小//胜利返回0,失败返回负的谬误值int av_audio_fifo_realloc(AVAudioFifo *af, int nb_samples); //开释AVAudioFifovoid av_audio_fifo_free(AVAudioFifo *af);查问操作: //返回fifo中以后存储的采样数量int av_audio_fifo_size(AVAudioFifo *af); //返回fifo中以后可写的采样数量,即尚未应用的空间数量int av_audio_fifo_space(AVAudioFifo *af); // 以上两个函数的返回值之和等于AVAudioFifo的缓冲区大小读取操作: //将采样写入到AVAudioFifo//胜利则返回理论写入的采样数,如果写入胜利,返回值必然等于nb_samples,失败返回负的谬误值int av_audio_fifo_write(AVAudioFifo *af, void **data, int nb_samples); //peek:读取数据,但读到的数据并不会从fifo中删除int av_audio_fifo_peek(AVAudioFifo *af, void **data, int nb_samples); //从指定的偏移地位peek数据int av_audio_fifo_peek_at(AVAudioFifo *af, void **data, int nb_samples, int offset); //读取数据,读到的数据会从fifo中删除int av_audio_fifo_read(AVAudioFifo *af, void **data, int nb_samples); //从fifo中删除nb_samples个采样int av_audio_fifo_drain(AVAudioFifo *af, int nb_samples); //删除fifo中的所有采样,清空void av_audio_fifo_reset(AVAudioFifo *af);音频转码有了AVAudioFifo,那么咱们音频的转码流程就变成了以下这样子: 解封装 -> 提取音频流 -> 解码成PCM->将PCM数据写入AVAudioFifo -> 每次从AVAudioFifo获取1024个采样点送进aac编码器 -> 从新编码成AAC如果到了最初没有可输出的PCM数据了,然而AVAudioFifo中可读取的采样点数仍然不满足aac的1024个采样点的话,能够通过填充静音的形式补充... ...

October 12, 2022 · 4 min · jiezi

关于c++:FFmpeg连载6音频重采样

明天咱们的实战内容是将音频解码成PCM,并将PCM重采样成特定的采样率,而后输入到本地文件进行播放。 什么是重采样所谓重采样,一句话总结就是扭转音频的三元素,也就是通过重采样扭转音频的采样率、采样格局或者声道数。 例如音频A是采样率48000hz、采样格局为f32le、声道数为1,通过重采样能够将音频A的采样率变更为采样率44100hz、采样格局为s16le、声道数为2等。 为什么须要重采样个别进行重采样有两个起因,一是播放设施须要,二是音频合并、或编码器等须要。 例如有些声音设施只能播放44100hz的采样率、16位采样格局的音频数据,因而如果音频不是这些格局的,就须要进行重采样能力失常播放了。 例如FFmpeg默认的AAC编码器输出的PCM格局为:AV_SAMPLE_FMT_FLTP,如果须要应用FFMpeg默认的AAC编码器则须要进行重采样了。又比有些须要进行混音的业务需要,须要保障PCM三要素雷同能力进行失常混音。 如何进行音频重采样在重采样的过程中咱们要坚守一个准则就是音频通过重采样后它的播放工夫是不变的,如果一个10s的音频通过重采样后变成了15,那必定就是不行的。 影响音频播放时长的因素是每帧的采样数和采样率,上面举一个例子简略介绍下音频播放时长的问题: 如果现有mp3,它的采样率是采样率48000,mp3每帧采样点数是1152,那么每帧mp3的播放时长就是 1152/48000,每一个采样点的播放时长就是1/48000。 如果现有mp3,它的采样率是采样率44100,aac每帧采样点数是1024,那么每帧aac的播放时长就是 1024/44100,每个采样点的播放时长就是1/44100。从下面的例子中咱们能够看出,对于采样率不同的两个音频,不可能1帧mp3转换出1帧aac,它们的比例不是1:1的,对于下面的例子,那么1帧mp3能重采样出多少个aac的采样点呢?以工夫不变为根底,能够有这样的一个公式: 1152 / 48000 = 指标采样点数 / 44100也就是说:指标采样点数 = 1152 * 44100 / 48000这条公式能够用FFmpeg中的函数av_rescale_rnd来实现... 有了计算公式,上面咱们说说FFmpeg重采样的步骤: 1、调配SwrContext并配置音频输入输入参数 这里能够间接应用函数swr_alloc_set_opts实现,也能够应用swr_alloc、av_opt_set_channel_layout、av_opt_set_int、av_opt_set_sample_fmt等组合函数分步实现, 2、初始化SwrContext 调配好SwrContext 后,通过函数swr_init进行重采样上下文初始化。 3、swr_convert重采样 FFmpeg真正进行重采样的函数是swr_convert。它的返回值就是重采样输入的点数。应用FFmpeg进行重采样时外部是有缓存的,而外部缓存了多少个采样点,能够用函数swr_get_delay获取。也就是说调用函数swr_convert时你传递进去的第三个参数示意你心愿输入的采样点数,然而函数swr_convert的返回值才是真正输入的采样点数,这个返回值肯定是小于或等于你心愿输入的采样点数。 上面是残缺代码: #ifndef AUDIO_TARGET_SAMPLE#define AUDIO_TARGET_SAMPLE 48000#endif#include <iostream>extern "C" {#include "libavformat/avformat.h"#include <libswresample/swresample.h>#include <libavcodec/avcodec.h>#include <libavutil/frame.h>#include <libavutil/opt.h>#include <libavutil/channel_layout.h>}class AudioResample {public: // 将PCM数据重采样 void decode_audio_resample(const char *media_path, const char *pcm_path) { avFormatContext = avformat_alloc_context(); int ret = avformat_open_input(&avFormatContext, media_path, nullptr, nullptr); if (ret < 0) { std::cout << "输出关上失败" << std::endl; return; } // 寻找视频流 int audio_index = av_find_best_stream(avFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0); if (audio_index < 0) { std::cout << "没有可用的音频流" << std::endl; return; } // 配置解码相干 const AVCodec *avCodec = avcodec_find_decoder(avFormatContext->streams[audio_index]->codecpar->codec_id); avCodecContext = avcodec_alloc_context3(avCodec); avcodec_parameters_to_context(avCodecContext, avFormatContext->streams[audio_index]->codecpar); ret = avcodec_open2(avCodecContext, avCodec, nullptr); if (ret < 0) { std::cout << "解码器关上失败" << std::endl; return; } // 调配包和帧数据结构 avPacket = av_packet_alloc(); avFrame = av_frame_alloc(); // 关上yuv输入文件 pcm_out = fopen(pcm_path, "wb"); // 读取数据解码 while (true) { ret = av_read_frame(avFormatContext, avPacket); if (ret < 0) { std::cout << "音频包读取结束" << std::endl; break; } else { if (avPacket->stream_index == audio_index) { // 只解决音频包 ret = avcodec_send_packet(avCodecContext, avPacket); if (ret < 0) { std::cout << "发送解码包失败" << std::endl; return; } while (true) { ret = avcodec_receive_frame(avCodecContext, avFrame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { std::cout << "获取解码数据失败" << std::endl; return; } else { std::cout << "重采样解码数据" << std::endl; resample(); } } } } av_packet_unref(avPacket); } } ~AudioResample() { // todo 开释资源 }private: AVFormatContext *avFormatContext = nullptr; AVCodecContext *avCodecContext = nullptr; AVPacket *avPacket = nullptr; AVFrame *avFrame = nullptr; FILE *pcm_out = nullptr; SwrContext *swrContext = nullptr; AVFrame *out_frame = nullptr; int64_t max_dst_nb_samples; /** * 重采样并输入到文件 */ void resample() { if (nullptr == swrContext) { /** * 以下能够应用 swr_alloc、av_opt_set_channel_layout、av_opt_set_int、av_opt_set_sample_fmt * 等API设置,更加灵便 */ swrContext = swr_alloc_set_opts(nullptr, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_FLTP, AUDIO_TARGET_SAMPLE, avFrame->channel_layout, static_cast<AVSampleFormat>(avFrame->format), avFrame->sample_rate, 0, nullptr); swr_init(swrContext); } // 进行音频重采样 int src_nb_sample = avFrame->nb_samples; // 为了放弃从采样后 dst_nb_samples / dest_sample = src_nb_sample / src_sample_rate max_dst_nb_samples = av_rescale_rnd(src_nb_sample, AUDIO_TARGET_SAMPLE, avFrame->sample_rate, AV_ROUND_UP); // 从采样器中会缓存一部分,获取缓存的长度 int64_t delay = swr_get_delay(swrContext, avFrame->sample_rate); int64_t dst_nb_samples = av_rescale_rnd(delay + avFrame->nb_samples, AUDIO_TARGET_SAMPLE, avFrame->sample_rate, AV_ROUND_UP); if(nullptr == out_frame){ init_out_frame(dst_nb_samples); } if (dst_nb_samples > max_dst_nb_samples) { // 须要重新分配buffer std::cout << "须要重新分配buffer" << std::endl; init_out_frame(dst_nb_samples); max_dst_nb_samples = dst_nb_samples; } // 重采样 int ret = swr_convert(swrContext, out_frame->data, dst_nb_samples, const_cast<const uint8_t **>(avFrame->data), avFrame->nb_samples); if(ret < 0){ std::cout << "重采样失败" << std::endl; } else{ // 每帧音频数据量的大小 int data_size = av_get_bytes_per_sample(static_cast<AVSampleFormat>(out_frame->format)); std::cout << "重采样胜利:" << ret << "----dst_nb_samples:" << dst_nb_samples << "---data_size:" << data_size << std::endl; // 交织模式放弃写入 // 留神不要用 i < out_frame->nb_samples, 因为重采样进去的点数不肯定就是out_frame->nb_samples for (int i = 0; i < ret; i++) { for (int ch = 0; ch < out_frame->channels; ch++) { // 须要贮存为pack模式 fwrite(out_frame->data[ch] + data_size * i, 1, data_size, pcm_out); } } } } void init_out_frame(int64_t dst_nb_samples){ av_frame_free(&out_frame); out_frame = av_frame_alloc(); out_frame->sample_rate = AUDIO_TARGET_SAMPLE; out_frame->format = AV_SAMPLE_FMT_FLTP; out_frame->channel_layout = AV_CH_LAYOUT_STEREO; out_frame->nb_samples = dst_nb_samples; // 调配buffer av_frame_get_buffer(out_frame,0); av_frame_make_writable(out_frame); }};应用ffplay播放以下重采样后的PCM文件是否失常,播放命令是: ...

October 12, 2022 · 3 min · jiezi

关于c++:FFmpeg连载5音视频编码

导读在后面的咱们应用FFmpeg进行相干实际,对音视视频进行理解封装、解码等相干操作,明天咱们持续应用FFmpeg进行实际,应用FFmpeg进行音视频编码。 工作一: 在后面《FFmpeg连载4-音频解码》咱们将音频解码成PCM并输入到本地文件,明天咱们就把这个输入到本地的PCM文件进行读取从新编码成AAC音频文件并输入到本地。 工作二: 在《FFmpeg连载3-视频解码》一节中咱们将视频解码成YUV并且输入到本地文件,明天咱们读取这个输入的YUV本地文件进行从新编码成H264视频文件并输入到本地。 H264编码规格简介因为在设置编码器参数时须要用到profile,所以在这里简略介绍下H264的几种profile规格。 1、Baseline Profile 反对I/P帧,只反对无交织(Progressive)和CAVLC个别用于低阶或须要额定容错的利用,比方视频通话、手机视频等即时通信畛域 2、Extended Profile 在Baseline的根底上减少了额定的性能,反对流之间的切换,改良误码性能反对I/P/B/SP/SI帧,只反对无交织(Progressive)和CAVLC适宜于视频流在网络上的传输场合,比方视频点播 3、Main Profile 提供I/P/B帧,反对无交织(Progressive)和交织(Interlaced),反对CAVLC和CABAC用于支流消费类电子产品规格如低解码(相对而言)的MP4、便携的视频播放器、PSP和iPod等。 4、High Profile 最罕用的规格 在Main的根底上减少了8x8外部预测、自定义量化、无损视频编码和更多的YUV格局(如4:4:4) High 4:2:2 Profile(Hi422P) High 4:4:4 Predictive Profile(Hi444PP) High 4:2:2 Intra Profile High 4:4:4 Intra Profile 用于播送及视频碟片存储(蓝光影片),高清电视的利用 YUV视频编码在后面解码的文章中咱们介绍了一组解码的函数avcodec_send_packet和avcodec_receive_frame,同样对于编码也有对应的一组函数,它们是avcodec_send_frame和avcodec_receive_packet,同样一个的调用avcodec_send_frame须要对应多个avcodec_receive_packet的接管。 相干代码及正文如下: VideoEncoder.hclass VideoEncoder {public: void encode_yuv_to_h264(const char *yuv_path,const char *h264_path);};C++实现文件: #include "VideoEncoder.h"#include <iostream>extern "C"{#include "libavcodec/avcodec.h"#include <libavformat/avformat.h>#include <libavutil/avutil.h>#include <libavutil/opt.h>}static FILE *h264_out = nullptr;void encode_video(AVCodecContext* avCodecContext,AVFrame* avFrame,AVPacket* avPacket){ int ret = avcodec_send_frame(avCodecContext,avFrame); if(ret < 0){ std::cout << "yuv发送编码失败" << std::endl; } while (true){ ret = avcodec_receive_packet(avCodecContext,avPacket); if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){ std::cout << "须要输送更多yuv数据" << std::endl; break; } std::cout << "写入文件h264" << std::endl; fwrite(avPacket->data,1,avPacket->size,h264_out); av_packet_unref(avPacket); }}void VideoEncoder::encode_yuv_to_h264(const char *yuv_path, const char *h264_path) { const AVCodec *avCodec = avcodec_find_encoder(AV_CODEC_ID_H264); AVCodecContext *avCodecContext = avcodec_alloc_context3(avCodec); avCodecContext->time_base = {1,25}; // 配置编码器参数 avCodecContext->width = 720; avCodecContext->height = 1280; avCodecContext->bit_rate = 2000000; avCodecContext->profile = FF_PROFILE_H264_MAIN; avCodecContext->gop_size = 10; avCodecContext->time_base = {1,25}; avCodecContext->framerate = {25,1}; // b帧的数量 avCodecContext->max_b_frames = 1; avCodecContext->pix_fmt = AV_PIX_FMT_YUV420P; // 设置H264的编码器参数为提早模式,进步编码品质,然而会造成编码速度降落 av_opt_set(avCodecContext->priv_data,"preset","slow",0); // 关上编码器 int ret = avcodec_open2(avCodecContext,avCodec, nullptr); if(ret < 0){ std::cout << "编码器关上失败:" << strerror(ret) << std::endl; // todo 在析构函数中开释资源 return; } AVPacket *avPacket = av_packet_alloc(); AVFrame *avFrame = av_frame_alloc(); avFrame->width = avCodecContext->width; avFrame->height = avCodecContext->height; avFrame->format = avCodecContext->pix_fmt; // 为frame调配buffer av_frame_get_buffer(avFrame,0); av_frame_make_writable(avFrame); h264_out = fopen(h264_path,"wb"); // 读取yuv数据送入编码器 FILE *input_media = fopen(yuv_path,"r"); if(nullptr == input_media){ std::cout << "输出文件关上失败" << std::endl; return; } int pts = 0; while (!feof(input_media)){ int64_t frame_size = avFrame->width * avFrame->height * 3 / 2; int64_t read_size = 0; // 这里能够自行理解下ffmpeg字节对齐的问题 if(avFrame->width == avFrame->linesize[0]){ std::cout << "不存在padding字节" << std::endl; // 读取y read_size += fread(avFrame->data[0],1,avFrame->width * avFrame->height,input_media); // 读取u read_size += fread(avFrame->data[1],1,avFrame->width * avFrame->height / 4,input_media); // 读取v read_size += fread(avFrame->data[2],1,avFrame->width * avFrame->height / 4,input_media); } else{ std::cout << "存在padding字节" << std::endl; // 须要对YUV重量进行逐行读取 for (int i = 0; i < avFrame->height;i++) { // 读取y read_size += fread(avFrame->data[0] + i * avFrame->linesize[0],1,avFrame->width,input_media); } // 读取u和v for (int i = 0; i < avFrame->height / 2; i++) { read_size += fread(avFrame->data[1] + i * avFrame->linesize[1],1,avFrame->width / 2,input_media); read_size += fread(avFrame->data[2] + i * avFrame->linesize[2],1,avFrame->width / 2,input_media); } } pts += (1000000 / 25); avFrame->pts = pts; if(read_size != frame_size){ std::cout << "读取数据有误:" << std::endl; } encode_video(avCodecContext,avFrame,avPacket); } // 冲刷编码器 encode_video(avCodecContext, nullptr,avPacket); fflush(h264_out);}须要留神的是在读取YUV数据填充AVFrame时须要辨别开释存在字节对齐的问题。 ...

October 12, 2022 · 4 min · jiezi

关于c++:FFmpeg连载4音频解码

导读后面咱们介绍了应用FFmpeg解码视频,明天咱们应用FFmpeg解码音频。咱们的指标将mp4中的音频文件解码成PCM数据,并输入到本地文件,而后应用ffplay播放验证。 音频的解码过程就是将通过压缩后的数据从新还原成原始的PCM声音信号的过程。对于音频解码所用到的API和视频解码是一样的。 PCM基础知识PCM是指未通过压缩的原始声音脉冲信号数据,它次要通过采样率、采样格局(比方每个采样点是8位、16位、32位等)、声道数来形容。 在FFmpeg中有两种示意PCM数据包的模式,别离是planer和packed模式,那么它们有什么区别呢? 其中packed又叫做交织模式,而planer又叫立体模式,所谓交织或立体就是不同声道的声音信号排列贮存的形式,例如对于一个双声道的PCM数据来说,用packed模式示意是这样子的: // 咱们用L示意左声道数据,用R示意右声道数据LRLRLRLRLRLRLRLR而用laner模式示意的话,则是这样子的: // 咱们用L示意左声道数据,用R示意右声道数据LLLLLLLL RRRRRRRR在FFmpeg中,packed模式的格局有: AV_SAMPLE_FMT_U8, ///< unsigned 8 bitsAV_SAMPLE_FMT_S16, ///< signed 16 bitsAV_SAMPLE_FMT_S32, ///< signed 32 bitsAV_SAMPLE_FMT_FLT, ///< floatAV_SAMPLE_FMT_DBL, ///< double它的数据只存在于AVFrame的data[0]中。 而planer模式个别是FFmpeg外部贮存音频所应用的模式,例如通过个别planar模式的前面都有字母P标识,planar模式的格局有: AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planarAV_SAMPLE_FMT_S16P, ///< signed 16 bits, planarAV_SAMPLE_FMT_S32P, ///< signed 32 bits, planarAV_SAMPLE_FMT_FLTP, ///< float, planarAV_SAMPLE_FMT_DBLP, ///< double, planarAV_SAMPLE_FMT_S64, ///< signed 64 bitsAV_SAMPLE_FMT_S64P, ///< signed 64 bits, planar例如对于一帧planar格局的双声道的音频数据,AVFrame中的data[0]示意左声道的数据,data[1]示意的是右声道的数据。 在FFmpeg中咱们能够应用函数av_sample_fmt_is_planar来判断采样格局是planar模式还是packed模式。 须要留神的一点是planar仅仅是FFmpeg外部应用的贮存模式,咱们理论中所应用的音频都是packed模式的,也就是说咱们应用FFmpeg解码出音频PCM数据后,如果须要写入到输入文件,应该将其转为packed模式的输入。 咱们能够应用ffplay播放PCM原始音频数据,命令是: // -ar 示意采样率// -ac 示意音频通道数// -f 示意 pcm 格局,sample_fmts + le(小端)或者 be(大端) f32le示意的是 AV_SAMPLE_FMT_FLTP 的小端模式// sample_fmts能够通过ffplay -sample_fmts来查问// -i 示意输出文件,这里就是 pcm 文件ffplay -ar 44100 -ac 2 -f f32le -i pcm文件门路音频解码间接上代码吧,有正文: ...

October 12, 2022 · 3 min · jiezi