-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 179 KB
/
content.json
File metadata and controls
1 lines (1 loc) · 179 KB
1
[{"title":"软件开发模式介绍和对比","date":"2018-03-23T09:01:00.000Z","path":"2018/03/23/pm/软件开发模式/","text":"一、瀑布模型介绍1970年温斯顿·罗伊斯(Winston Royce)提出了著名的“瀑布模型”,直到80年代早期,它一直是唯一被广泛采用的软件开发模型。 瀑布模型核心思想瀑布模型核心思想是按工序将问题化简,将功能的实现与设计分开,便于分工协作,即采用结构化的分析与设计方法将逻辑实现与物理实现分开。将软件生命周期划分为制定计划、需求分析、软件设计、程序编写、软件测试和运行维护等六个基本活动,并且规定了它们自上而下、相互衔接的固定次序,如同瀑布流水,逐级下落。 瀑布模型有以下优点 为项目提供了按阶段划分的检查点。 当前一阶段完成后,您只需要去关注后续阶段。 可在迭代模型中应用瀑布模型。 增量迭代应用于瀑布模型。迭代1解决最大的问题。每次迭代产生一个可运行的版本,同时增加更多的功能。每次迭代必须经过质量和集成测试。 瀑布模型有以下缺点 在项目各个阶段之间极少有反馈。 只有在项目生命周期的后期才能看到结果。 通过过多的强制完成日期和里程碑来跟踪各个项目阶段。 瀑布模型的突出缺点是不适应用户需求的变化。 二、迭代模型什么是迭代模型在某种程度上,开发迭代是一次完整地经过所有工作流程的过程:需求、分析设计、实施和测试工作流程。实质上,它类似小型的瀑布式项目。RUP认为,所有的阶段都可以细分为迭代。每一次的迭代都会产生一个可以发布的产品,这个产品是最终产品的一个子集。 迭代模型的使用条件 在项目开发早期需求可能有所变化。 分析设计人员对应用领域很熟悉。 高风险项目。 用户可不同程度地参与整个项目的开发过程。 使用面向对象的语言或统一建模语言(Unified Modeling Language,UML)。 使用CASE(Computer Aided Software Engineering,计算机辅助软件工程)工具,如Rose(Rose是非常受欢迎的物件软体开发工具。)。 具有高素质的项目管理者和软件研发团队。 迭代模型的优点与传统的瀑布模型相比较,迭代过程具有以下优点: 降低了在一个增量上的开支风险。如果开发人员重复某个迭代,那么损失只是这一个开发有误的迭代的花费。 降低了产品无法按照既定进度进入市场的风险。通过在开发早期就确定风险,可以尽早来解决而不至于在开发后期匆匆忙忙。 加快了整个开发工作的进度。因为开发人员清楚问题的焦点所在,他们的工作会更有效率。 由于用户的需求并不能在一开始就作出完全的界定,它们通常是在后续阶段中不断细化的。因此,迭代过程这种模式使适应需求的变化会更容易些。 三、敏捷开发模型什么是敏捷开发是一种从1990年代开始逐渐引起广泛关注的一些新型软件开发方法,是一种应对快速变化的需求的一种软件开发能力。相对于“非敏捷”,更强调程序员团队与业务专家之间的紧密协作、面对面的沟通(认为比书面的文档更有效)、频繁交付新的软件版本。能够很好地适应需求变化的代码编写和团队组织方法,也更注重软件开发中人的作用。敏捷建模(Agile Modeling,AM)的价值观包括了XP的四个价值观:沟通、简单、反馈、勇气,此外,还扩展了第五个价值观:谦逊。 敏捷开发特点 人和交互,重于过程和工具。 可以工作的软件 重于求全而完备的文档。 客户协作重于合同谈判。 随时应对变化重于循规蹈矩。 项目的敏捷开发,敏捷开发小组主要的工作方式可以归纳为: 作为一个整体工作 按短迭代周期工作 每次迭代交付一些成果:关注业务优先级 检查与调整。 最重要的因素恐怕是项目的规模。规模增长,面对面的沟通就愈加困难,因此敏捷方法更适用于较小的队伍,40、30、20、10人或者更少。 四、螺旋模型参考螺旋模型 五、快速原型模型参考快速原型模型 六、几种模型间的对比 传统的瀑布式开发,也就是从需求到设计,从设计到编码,从编码到测试,从测试到提交大概这样的流程,要求每一个开发阶段都要做到最好。特别是前期阶段,设计的越完美,提交后的成本损失就越少。 迭代式开发,不要求每一个阶段的任务做的都是最完美的,而是明明知道还有很多不足的地方,却偏偏不去完善它,而是把主要功能先搭建起来为目的,以最短的时间,最少的损失先完成一个“不完美的成果物”直至提交。然后再通过客户或用户的反馈信息,在这个“不完美的成果物”上逐步进行完善。 螺旋开发,很大程度上是一种风险驱动的方法体系,因为在每个阶段之前及经常发生的循环之前,都必须首先进行风险评估。 敏捷开发,相比迭代式开发两者都强调在较短的开发周期提交软件,但是,敏捷开发的周期可能更短,并且更加强调队伍中的高度协作。 敏捷方法有时候被误认为是无计划性和纪律性的方法,实际上更确切的说法是敏捷方法强调适应性而非预见性。 适应性的方法集中在快速适应现实的变化。当项目的需求起了变化,团队应该迅速适应。这个团队可能很难确切描述未来将会如何变化。","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"项目管理","slug":"项目管理","permalink":"http://cclk.cc/tags/项目管理/"},{"name":"UML","slug":"UML","permalink":"http://cclk.cc/tags/UML/"}]},{"title":"软件生命周期","date":"2018-03-23T08:32:00.000Z","path":"2018/03/23/pm/软件生命周期/","text":"定义软件生命周期(Software Life Cycle)是指从软件的立项开发到软件的最终消亡的全过程。一般分为以下几个阶段: 制定计划 需求分析 软件设计 软件编码 软件测试 运行与维护 传统的瀑布模型(Waterfall Model),它规定了软件周期的各阶段严格按顺序执行,前一阶段的任务没有完成,不能进入下一阶段的工作;每一阶段的工作成功都需要经过评审。 制定计划确定系统的目标,提出系统的功能、性能、接口、可靠性、可用性等方面的基本要求,进行系统开发的可行性分析,提出可行性分析报告,制定系统开发的实施计划 需求分析对系统的需求进行详细的分析,并给出明确的定义,编制系统需求分析说明书和初步的用户手册,作为今后系统开发工作的依据。 软件设计根据系统的需求设计系统的体系结构和软件模块,一般分为概要设计和详细设计。在概要设计中,主要任务是设计软件系统的总体结构,即模块结构,定义每个模块的主要功能和模块之间的联系。在详细设计中,主要任务是进行模块设计,详细定义各模块的数据结构、算法、接口等,作为以后编码的依据。 软件编码选择程序设计语言和工具,编码代码,实现各项功能。 软件测试测试软件,排除错误,确保开发得到的软件功能、性能达到规定的要求,保证软件的质量。一般分为单元测试、组装测试和系统测试;一般软件测试和软件编码是一起交叉进行的。 运行与维护部署到实际工作环境中,对系统进行考验,发现遗留的问题并予以改进。","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"项目管理","slug":"项目管理","permalink":"http://cclk.cc/tags/项目管理/"},{"name":"UML","slug":"UML","permalink":"http://cclk.cc/tags/UML/"}]},{"title":"c++11新特性","date":"2017-11-15T00:57:32.000Z","path":"2017/11/15/c++/c++_11/","text":"一、c++11C++11是曾经被叫做C++0x,是对目前C++语言的扩展和修正,C++11不仅包含核心语言的新机能,而且扩展了C++的标准程序库(STL),并入了大部分的C++ Technical Report 1(TR1)程序库(数学的特殊函数除外)。C++11包括大量的新特性:包括lambda表达式,类型推导关键字auto、decltype,和模板的大量改进。 二、兼容c99__STDC__等预定义宏,用于判断当前编译器对标准库的支持,略 __func__预定义标识符 Pragma同#pragma,如_Pragma(“once”),优点在于可使用在宏内 变长参数的宏定义及__VA_ARGS__变长参数宏使用示例: 12345#define LOG(...) {\\ fprintf(stderr, \"%s: line:%d\", __FILE__, __LINE__);\\ fprintf(stderr, __VA_ARGS__);\\ fprintf(stderr, \"\\n\");\\} 宽窄字符串连接加入u8、u、U支持,R()原生字符串支持,mbrtoc16(头文件< cuchar >)等函数支持 long long 整形标准要求long long可以在不同平台有不同长度,但至少是64位,我们在写常数字面量时,可以使用LL(或ll)表示long long,而ULL(ull、Ull、uLL)表示unsigned long long,如:long long i = -90000LL __cplisplus宏c++11内被预定义为201103L,可以检测编译器支持情况 noexcept表示修饰的函数不会抛出异常,在c++11中如果noexcept修饰的函数抛出了异常,编译器可以调用std::terminate函数来终止程序的运行。 快速初始化成员变量可以在类内直接初始化成员,如:12345class Test{private: int num = 1;} 非静态成员的sizeof如sizeof(People::hand) final/overridefinal,用于阻止派生类覆盖特定的虚方法或是阻止一个类成为基类;如果派生类在虚函数声明时使用了override描述符,那么该函数必须是重载的其基类中的同名函数,否则代码将无法通过编译,这样能避免不小心覆盖基类的虚函数。 模版函数的默认模版参数如template< typename T = int > void fun(){} 外部模版略 三、易用性1继承构造函数:如果派生类需要使用基类的构造函数,通常需要在构造函数内显示声明,俗称”透传”,如:123456789101112131415class A{public: A(int i){}};class B : A{public: B(int i) : A(i) { } }; 假设构造函数类型较多,写起来很不方便,可以使用using来简化,如:1234567891011class A{public: A(int i){}};class B : A{public: using A::A;}; 这里我们通过using A::A的声明,把基类中的构造函数悉数继承到派生类B中。 委派构造函数对于不同类型的构造函数,可以引用,如:1234567class Info{public: Info() { InitRest(); } Info(int i) : Info() { type = i; } Info(char e) : Info() { name = e; } }; std::move 左值:可以取地址的,有名字的就是左值 右值:不能取地址,没有名字的就是右值;右值分为将亡值和纯右值(如非引用返回的函数返回的临时变量值);c++中所有的值必属于左值、将亡值、纯右值三者之一 右值引用:两个引号&&是C++ 11提出的一个新的引用类型,如 T && a = XX; std::move:强制转化为右值, 详细可参考C++ 11 右值引用以及std::move。 注意1:std::move只是将参数转换为右值引用而已,它从来不会移动什么,比如说一个简单的std::move可以实现成这样:123456template <typename T>decltype(auto) move (T&& param){ using ReturnType = remove_reference_t<T>&&; return static_cast<ReturnType> (param);} 真正的移动操作是在移动构造函数或者移动赋值操作符中发生的:12T (T&& rhs);T& operator= (T&& rhs); std::move的作用只是为了让调用构造函数的时候告诉编译器去选择移动构造函数,所以只要把12std::string &&rr5 = std::move(str5);改成std::string rr5 = std::move(str5); 就会真的发生移动操作了。 注意2:有了右值引用,读者可能会写出下面的代码:12345Test&& fun() { Test t; return std::move(t); } 即return std::move(local_var) 是否有必要?答案是没有必要,因为编译器能自动进行返回值优化(RVO), 当函数返回一个对象时,编译器会将这个对象看作的一个右值(准确来说是将亡值)。所以无需在fun函数中,将return t写成return std::move(t) 完美转发 std::forward就可以保存参数的左值或右值特性。不管是T&&、左值引用、右值引用。 std::forward都会按照原来的类型完美转发。 不forward的话,传入某函数里面的参数类型永远是左值引用,永远掉不到右值引用的版本。 显示转换操作符explicit 略 初始化列表如:12345int a = 3 + 4;int a = { 3 + 4 }int a ( 3 + 4 )int a { 3 + 4 }int g { 2.0f } //无法编译通过 如上所示,好处为可防止”收窄”导致的问题 POD类型略,需要memcpy、memset等,需要考虑内存分布时才需要用到 非受限联合体略 用户自定义字面量用于自定义如RGB()结构体之类的变量,略 内联命名空间略 模版别名c++11内,定义别名已经不再是typedef的专属能力,使用using也可以。如:1using uint = unsigned int; 可以使用is_name判断2个类型是否一致 一般化的SFINEA规则即模版匹配失败不是错误,表示的是对重载的模版的参数进行展开的时候,如果展开导致了一些类型不匹配,编译器并不会报错,略 四、易用性2右尖括号 > 的改进c++98中,如果在实例化模版的时候出现连续的2个>哭号,那么他们之间需要一个空格隔离,以避免发生编译错误。因为会将>>优先解析为右移,c++11中,该限制已经取消。 autoauto的自动类型推导,用于从初始化表达式中推断出变量的数据类型。12atuo i = 1;atuo str = \"hello world\"; decltypedecltype类型说明符生成指定表达式的类型。在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。 语法为: 1decltype(expression) 示例:1234int var;const int&& fx(); struct A { double x; }const A* a = new A(); 语句 类型 注释 decltype(fx()); const int && 对左值引用的const int decltype(var); int 变量var的类型 decltype(a->x); double 成员访问的类型 decltype((a->x)); const double& 内部括号导致语句作为表达式而不是成员访问计算。由于a声明为 const指针,因此类型是对const double的引用。 using Type = decltype(var); int 定义Type类型为int 与auto类型推导不同,auto是不能带走cv(const volatile)限定符的,decltype是可以带走cv限定符的,但其成员不会继承cv限定符。 追踪返回类型如下所示:123456789101112131415161718192021222324//错误template <typename T1, typename T2>decltype(t1 + t2) sum(const T1& t1, const T2& t2) { return t1 + t2;}//正确,但不建议这样写template <typename T1, typename T2>auto sum(const T1& t1, const T2& t2) { return t1 + t2;}//正确,建议这样写:追踪返回类型template <typename T1, typename T2>auto sum(const T1& t1, const T2 & t2) -> decltype(t1 + t2) { return t1 + t2;}``` 追踪返回类型的函数和普通函数的声明最大的区别在于返回类型的后置,如:``` cppint func(char* a, int b);auto func(char* a, int b) -> int; 基于范围的for循环在C++中for循环可以使用类似java的简化的for循环,可以用于遍历数组,容器,string以及由begin和end函数定义的序列(即有Iterator)。 123456789101112131415161718192021map<string, int> m{{\"a\", 1}, {\"b\", 2}, {\"c\", 3}}; //1for (auto p : m){ cout<<p.first<<\" : \"<<p.second<<endl; } //2for (auto &p : m){ cout<<p.first<<\" : \"<<p.second<<endl; } int arr[5] = {1, 2, 3, 4, 5};//3for(auto &e : arr){} 五、提高类型安全强类型枚举在enum加上关键字class,如:1enum class Type {General, Light, Medium, Heavy}; 智能指针 auto_ptr:控制权可以随便转换,但是只有一个在用,用起来会受到诸多限制 unique_ptr:控制权唯一,不能随意转换;注意父子类转换时并不会自动析构 shared_ptr:可以直接赋值和调用拷贝构造函数,且不会清空原本的智能指针 weak_ptr:与std::shared_ptr最大的差别是在赋值时,不会引起智能指针计数增加 助手类enable_shared_from_this、shared_from_this会返回this的shared_ptr 六、提高性能及操作硬件的能力常量表达式const int a = 1;是我们熟知的常量,但这也仅仅是在运行时的常量,我们需要编译时的常量,int arry[a],这样就无法编译通过,虽然a是常量。可以用#define a 1来解决,但c++11中对编译时的常量的回答是constexpr,及常量表达式。上述可修改为:1constexpr int GetConst() { return 1; } 常量表达式函数需要有以下特点: 函数体只有单一的return语句 函数必须有返回值 在使用前必须已经定义 return语句,不能使用非常量表达式的函数、全局数据,且必须是一个常量表达式 变长模版:我们在C++中都用过pair,pair可以使用make_pair构造,构造一个包含两种不同类型的数据的容器。比如,如下代码:1auto p = make_pair(1, \"C++ 11\"); 由于在C++11中引入了变长参数模板,所以发明了新的数据类型:tuple,tuple是一个N元组,可以传入1个, 2个甚至多个不同类型的数据12auto t1 = make_tuple(1, 2.0, \"C++ 11\"); auto t2 = make_tuple(1, 2.0, \"C++ 11\", {1, 0, 2}); 这样就避免了从前的pair中嵌套pair的丑陋做法,使得代码更加整洁,另一个经常见到的例子是Print函数,在C语言中printf可以传入多个参数,在C++11中,我们可以用变长参数模板实现更简洁的Print:12345template<typename head, typename... tail> void Print(Head head, typename... tail) { cout<< head <<endl; Print(tail...); } Print中可以传入多个不同种类的参数,如下:1Print(1, 1.0, \"C++11\"); 变长模版语法:template<typename… Elements> class tuple;我们在标识符Elements前使用了三个点表示该参数是变长的。 原子操作std::atomic为C++11封装的原子数据类型。 什么是原子数据类型? 从功能上看,简单地说,原子数据类型不会发生数据竞争,能直接用在多线程中而不必我们用户对其进行添加互斥资源锁的类型。从实现上,大家可以理解为这些原子类型内部自己加了锁。 内存模型,顺序一致性及memory_order:参考Singleton & memory order,略 线程局部存储TLS(thread local storage),g++在全局或者静态变量的声明中加上关键字__thread,即可将变量声明为TLS变量,每个线程都将拥有独立的变量拷贝,一个线程对变量的读写并不会影响到另一个线程的变量数据。 在c++11里面对TLS作了统一规定,与__thread类似,通过thread_local来修饰,如:1int thread_local err_code; 一旦声明一个变量为thread_local,其值将在线程开始的时候被初始化,而在线程结束时,该值也将不再有效。对于thread_local变量地址取值(&),也只可以获得当前线程中的TLS变量的地址。 快速退出quick_exit和at_quick_exit,前者用户退出,同exit,但并不会调用析构,后者用于注册退出回调。 七、为改变思考方式而改变nullptrnullptr是为了解决原来C++中NULL的二义性问题而引进的一种新的类型,因为NULL实际上代表的是0。 默认函数控制c++中声明自定义的类,编译器默认生成一些默认函数,包括: 构造函数 拷贝构造函数 拷贝赋值函数(operator=) 移动构造函数 移动拷贝函数 析构函数 此外,还会为以下自定义类型提供全局默认操作符函数: operator, operator & operator && operator * operator -> operator ->* operator new operator delete c++中,如果程序员实现了这些函数的自定义版本,则编译器不会再为该类自动生成默认版本。c++11提供了default,delete控制默认函数的生成,如:123456class A{private: A() = default; A(const A &) = delete; } Lambda表达式lambda表达式可以方便地构造匿名函数,如果你的代码里面存在大量的小函数,而这些函数一般只被调用一次,那么不妨将他们重构成 lambda 表达式。 C++11的lambda表达式规范如下: 表达式 序号 [ capture ] ( params ) mutable exception attribute -> ret { body } (1) [ capture ] ( params ) -> ret { body } (2) [ capture ] ( params ) { body } (3) [ capture ] { body } (4) (1) 是完整的 lambda 表达式形式 mutable 修饰符说明 lambda 表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获对象的 non-const 方法。 exception 说明 lambda 表达式是否抛出异常(noexcept),以及抛出何种异常,类似于void f() throw(X, Y)。 attribute 用来声明属性。 (2) const 类型的 lambda 表达式,该类型的表达式不能改捕获(“capture”)列表中的值 (3) 省略了返回值类型的 lambda 表达式,但是该 lambda 表达式的返回类型可以按照下列规则推演出来: 如果 lambda 代码块中包含了 return 语句,则该 lambda 表达式的返回类型由 return 语句的返回类型确定。 如果没有 return 语句,则类似 void f(…) 函数。 (4) 省略了参数列表,类似于无参函数 f()。 capture 指定了在可见域范围内 lambda 表达式的代码内可见得外部变量的列表,具体解释如下: [a, &b] a变量以值的方式被捕获,b以引用的方式被捕获。 [this] 以值的方式捕获 this 指针。 [&] 以引用的方式捕获所有的外部自动变量。 [=] 以值的方式捕获所有的外部自动变量。 [] 不捕获外部的任何变量。 示例:12345678auto func1 = [](int i) { return i+4; };vector<int> iv{5, 4, 3, 2, 1}; int a = 2, b = 1; for_each(iv.begin(), iv.end(), [b](int &x){cout<<(x + b)<<endl;}); // (1) for_each(iv.begin(), iv.end(), [=](int &x){x *= (a + b);}); // (2) for_each(iv.begin(), iv.end(), [=](int &x)->int{return x * (a + b);}); // (3) 八、融入实际应用对齐支持略 通用属性略 unicode支持 char16_t、char32_t、u8、u、U等unicode字符类型 mbrtoc16等字符串转换函数 原生字符串R(“XXXX”) 九、标准库的变更标准库组件上的升级: 利用新特性实现标准库,如move,decltype等,略 线程支持 std::thread std::mutex,std::recursive_mutex等等 std::condition_variable和std::condition_variable_any std::lock_guard和std::unique_lock std::packaged_task std::future std::promise std::async 多元组类别 std::tuple 散列表 std::unordered_set std::unordered_multiset std::unordered_map std::unordered_multimap 正则表达式 std::regex std::cmatch 通用智能指针 shared_ptr weak_ptr unique_ptr auto_ptr将会被C++标准所废弃 可扩展的随机数功能 rand std::uniform_int_distribution std::mt19937 包装引用 std::ref 多态函数对象包装器 std::function std::plus std::bind 用于元编程的类别属性 对于那些能自行创建或修改本身或其它程序的程序,我们称之为元编程,略 用于计算函数对象回返类型的统一方法 要在编译期决定一个模板仿函数的回返值类别并不容易,特别是当回返值依赖于函数的参数时。 std::result_of iota 函数 std::iota 十、参考中文C++11介绍C++11 并发指南系列","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"c++","slug":"c","permalink":"http://cclk.cc/tags/c/"}]},{"title":"c++开发线程安全的SDK","date":"2017-11-14T06:37:32.000Z","path":"2017/11/14/c++/c++_线程安全SDK/","text":"背景平时在封装SDK接口给上层应用调用时,理论上我们希望该SDK是可以在多线程环境下运行的,那样就可以避免上层应用多线程乱入的问题。 但如何设计一个多线程安全的SDK呢,想到的常规做法可能是加锁。使用加锁的方式,简单的接口可能比较容易实现,但如果是复杂的类导出,用锁就比较麻烦了,因为要考虑到每个接口的性能,又要考虑接口递归可能导致死锁,并且锁的维护比较麻烦。 之前在学习webrtc时,发现有一些很奇怪宏,然后就查了一下这些宏的作用。原来webrtc是在接口部分通过一个proxy代理线程来进行真正的调用, 它将来自任意线程的API调用, 转向SDK内部的工作线程,这样,在应用的角度看来, 这个SDK是线程安全并且支持线程乱入的, 但是内部又保持了简单有序的操作。 实现参考上篇文章<<基于c++11的线程池>>,我们可以应用一个代理线程来将sdk的调用转移到SDK内部的工作线程内。比如我们导出一个class sdk。 内部实现类这个类原型一般为我们类内部实现的,会调用其他模块等。这里有2个方法newjob、deljob。12345678910111213141516171819202122232425class sdkimpl{public: int newjob(int b) { deljob(); a = new int; *a = b; return *a; } void deljob() { if (a) { delete a; a = nullptr; } }private: int *a = nullptr;}; 导出类这个类原型一般为导出类的真正接口,会在头文件内隐藏真正的实现。newjob和deljob是通常默认做法,safe_newjob和safe_deljob是线程安全的。1234567891011121314151617181920212223242526272829303132333435class __declspec(dllexport) sdk{public: sdk() : _pool(1) { _impl = new sdkimpl; } int newjob(int b) { return _impl->newjob(b); } void deljob() { _impl->deljob(); } int safe_newjob(int b) { auto ret = _pool.commit(std::bind(&sdkimpl::newjob, _impl, b)); return ret.get(); } void safe_deljob() { auto ret = _pool.commit(std::bind(&sdkimpl::deljob, _impl)); ret.get(); }private: sdkimpl *_impl; threadpool _pool;}; 测试这里有2个线程不同在调用sdk的2个接口。123456789101112131415161718192021222324252627282930sdk *proxy = nullptr;void newthread(){ while (true) { //proxy->newjob(100); proxy->safe_newjob(100); }}void delthread(){ while (true) { //proxy->deljob(); proxy->safe_deljob(); }}int main(int argc, char *argv[]){ proxy = new sdk; std::thread thnew(newthread); std::thread thdel(delthread); thnew.join(); thdel.join(); return 0;} 参考WebRTC的PROXY-如何解决应用中的线程乱入","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"c++","slug":"c","permalink":"http://cclk.cc/tags/c/"},{"name":"sdk","slug":"sdk","permalink":"http://cclk.cc/tags/sdk/"}]},{"title":"基于c++11的线程池","date":"2017-11-14T03:31:32.000Z","path":"2017/11/14/c++/c++_threadpool/","text":"背景c++11加入了线程库std::thread,很好的解决了c++跨平台创建线程等繁琐的问题,但对于多线程的应用还是比较低级的,稍微高级的用法都需要自己去实现。比如场景线程池操作。 我们期望一个线程池具有以下特性: 线程循环利用 线程执行函数可以拥有不同类型或不同数量的参数 线程执行函数可以为类的成员函数或普通函数 可以查询任务执行状态,并且可获得任务执行的结果 可以同步等待任务执行完毕 实现根据c++11的新特性,包括std::thread、std::function、std::mutex、std::condition_variable、std::atomic、std::packaged_task、std::future等,可以实现如下一个符合要求的线程池,基本满足日常要求。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108#include <functional>#include <thread>#include <condition_variable>#include <future>#include <atomic>#include <queue>#include <vector>class threadpool{ using Task = std::function<void()>;public: threadpool(size_t size = 4) : _stop(false) { size = size < 1 ? 1 : size; for (size_t i = 0; i < size; ++i) { _pool.emplace_back(&threadpool::schedual, this); } } ~threadpool() { shutdown(); } // 提交一个任务 template<class F, class... Args> auto commit(F&& f, Args&&... args) -> std::future<decltype(f(args...))> { using ResType = decltype(f(args...));// 函数f的返回值类型 auto task = std::make_shared<std::packaged_task<ResType()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...)); { // 添加任务到队列 std::lock_guard<std::mutex> lock(_taskMutex); _tasks.emplace([task]() { (*task)(); }); } _taskCV.notify_all(); //唤醒线程执行 std::future<ResType> future = task->get_future(); return future; }private: // 获取一个待执行的task Task get_one_task() { std::unique_lock<std::mutex> lock(_taskMutex); _taskCV.wait(lock, [this]() { return !_tasks.empty() || _stop.load(); }); // wait直到有task if (_stop.load()) { return nullptr; } Task task{ std::move(_tasks.front()) }; // 取一个task _tasks.pop(); return task; } // 任务调度 void schedual() { while (!_stop.load()) { if (Task task = get_one_task()) { task(); } } } // 关闭线程池,并等待结束 void shutdown() { this->_stop.store(true); _taskCV.notify_all(); for (std::thread &thrd : _pool) { thrd.join();// 等待任务结束, 前提:线程一定会执行完 } }private: // 线程池 std::vector<std::thread> _pool; // 任务队列 std::queue<Task> _tasks; // 同步 std::mutex _taskMutex; std::condition_variable _taskCV; // 是否关闭提交 std::atomic<bool> _stop;}; 测试demo1234567891011121314151617181920212223242526272829303132333435363738394041424344454647void func1(){ std::cout << \"hello, f !\" << std::endl;}struct struct1{ int operator()() { std::cout << \"hello, g !\" << std::endl; return 42; }};class class1{public: double class_func(double in) { return in; }};int main(int argc, char *argv[]){ threadpool executor(10); std::future<void> ret_func1 = executor.commit(func1); std::future<int> ret_struct1 = executor.commit(struct1{}); std::future<std::string> ret_lambda1 = executor.commit([]()->std::string { std::cout << \"hello, h !\" << std::endl; return \"hello,fh !\"; }); class1 class1_; std::future<double> ret_float1 = executor.commit(std::bind(&class1::class_func, &class1_, 0.9)); std::cout << \"ret_struct1:\" << ret_struct1.get() << \"\\nret_lambda1:\" << ret_lambda1.get() << \"\\nret_float1:\" << ret_float1.get() << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); std::future<int> ret_struct2 = executor.commit(struct1{}); int ret_struct2_int = ret_struct2.get(); std::cout << \"end...\" << std::endl; return 0;} 参考c++11线程池实现","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"c++","slug":"c","permalink":"http://cclk.cc/tags/c/"},{"name":"高并发","slug":"高并发","permalink":"http://cclk.cc/tags/高并发/"}]},{"title":"vscode同步配置","date":"2017-10-30T07:19:32.000Z","path":"2017/10/30/common/vscode_sync/","text":"Syncing可以使用Syncing这个vscode的插件实现,该插件是通过github gist来实现的。插件可在vscode应用市场内找到。 Syncing安装 gist是github的一个分享代码片段的功能,可以运用gist作很多有趣的事情。 gist申请 注意: 为了安全考虑,gist只有在第一次生成的时候才会显示token值,所以请牢记申请的gist token。 gist申请方式也可参考Syncing的插件说明。 安装 申请github gist,并记录tokens 安装vscode Syncing插件 F1打开vscode cmd,查找upload setting,输入gist token 之后就可以直接使用update和download同步功能","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"vscode","slug":"vscode","permalink":"http://cclk.cc/tags/vscode/"}]},{"title":"go学习2-go语言基础","date":"2017-09-27T03:08:32.000Z","path":"2017/09/27/go/go学习2-go语言基础/","text":"背景具体内容来自《Go web编程》链接 Go语言基础Go是一门类似C的编译型语言,但是它的编译速度非常快。这门语言的关键字总共也就二十五个,比英文字母还少一个,这对于我们的学习来说就简单了很多。先让我们看一眼这些关键字都长什么样: break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var 在接下来的这一章中,我将带领你去学习这门语言的基础。通过每一小节的介绍,你将发现,Go的世界是那么地简洁,设计是如此地美妙,编写Go将会是一件愉快的事情。等回过头来,你就会发现这二十五个关键字是多么地亲切。 目录 2.1 你好,Go 2.2 Go基础 2.3 流程和函数 2.4 struct类型 2.5 面向对象 2.6 interface 2.7 并发 2.8 总结 更多内容见:build-web-application-with-golang","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"高并发","slug":"高并发","permalink":"http://cclk.cc/tags/高并发/"},{"name":"go","slug":"go","permalink":"http://cclk.cc/tags/go/"}]},{"title":"go学习1-安装与初步使用","date":"2017-09-25T07:44:32.000Z","path":"2017/09/25/go/go学习1-安装与初步使用/","text":"一、下载安装从go主页,下载对应平台的安装包;下面以windows环境安装1.9版本示例。 打开下载的MSI文件,并按照提示进行安装go工具 如果第一步安装使用的是默认安装目录 c:\\Go\\,那么安装程序就已经将 GOROOT 和 Path 两个环境变量设置好了,无须再对其进行手工设置。 如果修改默认安装路径,则需要修改环境变量GOROOT和PATH内go的路径。 二、一些环境变量GOROOTGOROOT就是go的安装路径,默认为c:\\Go\\。 GOBINgo install编译存放路径。不允许设置多个路径;一般设置为%GOPATH%\\bin或%GOROOT%\\bin。 PATH系统环境变量,设置后可以直接执行go命令,而不用带上完整路径;一般将GOBIN和GOROOT\\bin目录添加进去; GOPATH GOPATH是用来设置包加载路径的重要变量。可以设置多个路径,windows下用分号(;)分隔 GOPATH是作为编译后二进制的存放目的地和import包时的搜索路径 (其实也是你的工作目录, 你可以在src下创建你自己的go源文件, 然后开始工作) GOPATH之下主要包含三个目录: bin、pkg、src bin目录主要存放可执行文件; pkg目录存放编译好的库文件, 主要是*.a文件; src目录下主要存放go的源文件 不要把GOPATH设置成go的安装路径 GOPATH可以是一个目录列表, go get下载的第三方库, 一般都会下载到列表的第一个目录里面 需要把GOPATH中的可执行目录也配置到环境变量中, 否则你自行下载的第三方go工具就无法使用了 三、编写第一个Go程序创建hello.go 文件并编辑其内容如下:1234567package main import \"fmt\" func main() { fmt.Printf(\"hello, world\\n\") } 保存后进入该目录,执行 go run hello.go 看到 “hello, world” 证明我们的 go 安装成功了。 四、go命令解释go build这个命令主要用于测试编译。在包的编译过程中,若有必要,会同时编译与之相关联的包。 如果是普通包,当你执行go build之后,它不会产生任何文件。如果你需要在$GOPATH/pkg下生成相应的文件,那就得执行go install了。 如果是main包,当你执行go build之后,它就会在当前目录下生成一个可执行文件。如果你需要在$GOPATH/bin下生成相应的文件,需要执行go install,或者使用go build -o 路径/a.exe。 如果某个项目文件夹下有多个文件,而你只想编译某个文件,就可在go build之后加上文件名,例如go build a.go;go build命令默认会编译当前目录下的所有go文件。 你也可以指定编译输出的文件名。我们可以指定go build -o astaxie.exe,默认情况是你的package名(非main包),或者是第一个源文件的文件名(main包)。 go build会忽略目录下以“_”或“.”开头的go文件。 如果你的源代码针对不同的操作系统需要不同的处理,那么你可以根据不同的操作系统后缀来命名文件。例如有一个读取数组的程序,它对于不同的操作系统可能有如下几个源文件(array_linux.go array_darwin.go array_windows.go array_freebsd.go),go build的时候会选择性地编译以系统名结尾的文件(Linux、Darwin、Windows、Freebsd)。例如Linux系统下面编译只会选择array_linux.go文件,其它系统命名后缀文件全部忽略。 go clean这个命令是用来移除当前源码包里面编译生成的文件。这些文件包括: _obj/ 旧的object目录,由Makefiles遗留 _test/ 旧的test目录,由Makefiles遗留 _testmain.go 旧的gotest文件,由Makefiles遗留 test.out 旧的test记录,由Makefiles遗留 build.out 旧的test记录,由Makefiles遗留 *.[568ao] object文件,由Makefiles遗留 DIR(.exe) 由go build产生 DIR.test(.exe) 由go test -c产生 MAINFILE(.exe) 由go build MAINFILE.go产生 利用这个命令清除编译文件,然后github提交源码,在本机测试的时候这些编译文件都是和系统相关的,但是对于源码管理来说没必要。 go fmt有过C/C++经验的读者会知道,一些人经常为代码采取K&R风格还是ANSI风格而争论不休。在go中,代码则有标准的风格。由于之前已经有的一些习惯或其它的原因我们常将代码写成ANSI风格或者其它更合适自己的格式,这将为人们在阅读别人的代码时添加不必要的负担,所以go强制了代码格式(比如左大括号必须放在行尾),不按照此格式的代码将不能编译通过,为了减少浪费在排版上的时间,go工具集中提供了一个go fmt命令 它可以帮你格式化你写好的代码文件,使你写代码的时候不需要关心格式,你只需要在写完之后执行go fmt <文件名>.go,你的代码就被修改成了标准格式,但是我平常很少用到这个命令,因为开发工具里面一般都带了保存时候自动格式化功能,这个功能其实在底层就是调用了go fmt。 使用go fmt命令,更多时候是用go fmt,而且需要参数-w,否则格式化结果不会写入文件。go fmt -w src,可以格式化整个项目。 go get这个命令是用来动态获取远程代码包的,目前支持的有BitBucket、GitHub、Google Code和Launchpad。这个命令在内部实际上分成了两步操作:第一步是下载源码包,第二步是执行go install。下载源码包的go工具会自动根据不同的域名调用不同的源码工具,对应关系如下: BitBucket (Mercurial Git) GitHub (Git) Google Code Project Hosting (Git, Mercurial, Subversion) Launchpad (Bazaar) 所以为了go get 能正常工作,你必须确保安装了合适的源码管理工具,并同时把这些命令加入你的PATH中。其实go get支持自定义域名的功能,具体参见go help remote。 go install这个命令在内部实际上分成了两步操作:第一步是生成结果文件(可执行文件或者.a包),第二步会把编译好的结果移到$GOPATH/pkg或者$GOPATH/bin。 go test执行这个命令,会自动读取源码目录下面名为*_test.go的文件,生成并运行测试用的可执行文件。输出的信息类似:1234ok archive/tar 0.011sFAIL archive/zip 0.022sok compress/gzip 0.033s... 默认的情况下,不需要任何的参数,它会自动把你源码包下面所有test文件测试完毕,当然你也可以带上参数,详情请参考go help testflag go doc(1.2rc1 以後沒有 go doc 指令, 只留下 godoc 指令) 很多人说go不需要任何的第三方文档,例如chm手册之类的(其实我已经做了一个了,chm手册),因为它内部就有一个很强大的文档工具。 如何查看相应package的文档呢? 例如builtin包,那么执行go doc builtin 如果是http包,那么执行go doc net/http 查看某一个包里面的函数,那么执行godoc fmt Printf 也可以查看相应的代码,执行godoc -src fmt Printf 通过命令在命令行执行 godoc -http=:端口号 比如godoc -http=:8080。然后在浏览器中打开127.0.0.1:8080,你将会看到一个golang.org的本地copy版本,通过它你可以查询pkg文档等其它内容。如果你设置了GOPATH,在pkg分类下,不但会列出标准包的文档,还会列出你本地GOPATH中所有项目的相关文档,这对于经常被墙的用户来说是一个不错的选择。 其他go还提供了其它很多的工具,例如下面的这些工具: go fix 用来修复以前老版本的代码到新版本,例如go1之前老版本的代码转化到go1 go version 查看go当前的版本 go env 查看当前go的环境变量 go list 列出当前全部安装的package go run 编译并运行Go程序以上这些工具还有很多参数没有一一介绍,用户可以使用go help 命令获取更详细的帮助信息。 五、代码结构基本原则 将所有Go代码放在一个工作空间(workspace)下。 一个工作空间包含多个代码仓库。 每个仓库有一个或多个包(packages)。 每个包又包含一个或多个Go代码源文件。 包的路径在import中体现。 工作空间(workspace)每个工作空间根目录下包括: src Go源代码 src文件夹下放置多个版本控制的仓库(项目)。 pkg 包对象 bin 生成的可执行文件go命令编译源文件,并生产对应的结果文件到pkg和bin目录。 包路径包路径用来唯一标记一个包,包路径反映了包在本地工作空间或远程代码库(接下来会详细说明)中的位置。 标准库中的包直接使用短包路径,比如“fmp”或“net/http”。但是对于自己编写的包,基础路径的名字不能与标准库和其它一些外部库冲突。 如果你的代码直接保存在一些版本控制的代码库里,应该直接使用代码库的根目录作为基本路径。比如,如果你的代码都存放在github的user用户下(github.com/user),那github.com/user就应该是基本路径。 即使你现在还没有打算把代码发布带远程代码仓库,最好还是按照以后准备提交代码仓库的方式组织代码结构。实际中可以使用任何组合来标记路径名称,只要对应的路径名称不与基本库冲突。 包命名所有Go源代码都以下边的语句开始:package name 其中name就是包引用是默认的名称。一个包中的所有文件必须使用同一个包名。 可执行命令的包名必须是main。 一个二进制文件下所有的包名不需要唯一,但是引用路径必须唯一。 测试Go自带了一个轻量级的测试框架,由go test命令和testing包组成。 你可以通过新建xx_test.go写一个测试,其中包含若干个TestXXX函数。测试框架会自动执行这些函数;如果函数中包含tError或t.Fail, 对应的测试会被判为失败。 如:12345678910111213141516171819package stringutilimport \"testing\"func TestReverse(t *testing.T) { cases := []struct { in, want string }{ {\"Hello, world\", \"dlrow ,olleH\"}, {\"Hello, 世界\", \"界世 ,olleH\"}, {\"\", \"\"}, } for _, c := range cases { got := Reverse(c.in) if got != c.want { t.Errorf(\"Reverse(%q) == %q, want %q\", c.in, got, c.want) } }} 然后通过go test执行测试:12go test github.com/user/stringutilok github.com/user/stringutil 0.165s 同样,在包文件夹下可以忽略包路径而直接执行go test命令:12$ go testok github.com/user/stringutil 0.165s 远程包包的引用路径用来描述如何通过版本控制系统获取包的源代码。go工具通过引用路径自动从远程代码仓库获取包文件。比如本文中用的例子也对应的保存在github.com/golang/example下。go可以通过包的代码仓库的url直接获取、生成、安装对应的包。123$ go get github.com/golang/example/hello$ $GOPATH/bin/helloHello, Go examples! 如果工作空间中不存在对应的包,go会将对应的包放到GOPATH环境变量指明的工作空间下。(如果包已经存在,go跳过代码拉去而直接执行go install)。","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"高并发","slug":"高并发","permalink":"http://cclk.cc/tags/高并发/"},{"name":"go","slug":"go","permalink":"http://cclk.cc/tags/go/"}]},{"title":"webrtc学习7-摄像头采集","date":"2017-09-25T06:44:32.000Z","path":"2017/09/25/webrtc/webrtc学习7-摄像头采集/","text":"背景webrtc的video_capture模块,为我们在不同端设备上采集视频提供了一个跨平台封装的视频采集功能,现webrtc的video_capture模块支持android、ios、linux、mac和windows各操作平台下的视频采集。我们可以在不同端设备上开发视频直播的功能,也可以使用该模块进行视频采集。 windows 61版本的实现源码路径为webrtc\\modules\\video_capture\\windows,其中提供了两套采集方案,分别为ds(direct show)和mf(media foundation),但mf方案实现是空的。 Sample样例路径如下:12webrtc\\modules\\video_capture\\test\\video_capture_unittest.ccwebrtc\\modules\\video_capture\\test\\video_capture_unittest.mm 其中后缀为mm对应mac系统。 windows使用注意事项 需要在包含头文件前增加宏定义WEBRTC_WIN 出现std::min等定义冲突,需要增加宏定义NOMINMAX Native Demo123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140#include <memory>#include <iostream>#include <time.h>#define NOMINMAX#define WEBRTC_WIN#include <webrtc/modules/video_capture/video_capture.h>#include <webrtc/modules/video_capture/video_capture_factory.h>using namespace webrtc;#pragma comment(lib, \"winmm.lib\")#pragma comment(lib, \"Strmiids.lib\")//dshowclass TestVideoCaptureCallback : public rtc::VideoSinkInterface<webrtc::VideoFrame>{public: TestVideoCaptureCallback() { } ~TestVideoCaptureCallback() { } void OnFrame(const webrtc::VideoFrame& videoFrame) override { std::cout << \"OnFrame w*h(\" << videoFrame.width() << \"*\" << videoFrame.height() << \")\" << std::endl; static bool first = false; static FILE* pf = nullptr; if (!first) { first = true; char path[1024] = { 0 }; sprintf(path, \"i420_%dx%d.yuv\", videoFrame.width(), videoFrame.height()); pf = fopen(path, \"wb+\"); } rtc::scoped_refptr<webrtc::VideoFrameBuffer> buffer = videoFrame.video_frame_buffer(); rtc::scoped_refptr<const I420BufferInterface> i420 = buffer->GetI420(); if (pf) { int size = videoFrame.width() * videoFrame.height(); fwrite(i420->DataY(), 1, size, pf); fwrite(i420->DataU(), 1, size / 4, pf); fwrite(i420->DataV(), 1, size / 4, pf); } }};int TestWebrtcVideoCapture(){ std::unique_ptr<VideoCaptureModule::DeviceInfo> device_info(VideoCaptureFactory::CreateDeviceInfo()); uint32_t nums = device_info->NumberOfDevices(); if (0 == nums) { std::cout << \"video capture devices is 0.\" << std::endl; return 0; } //get first devices std::string my_unique_name; for (uint32_t i = 0; i < nums; ++i) { char device_name[256]; char unique_name[256]; uint32_t ret = device_info->GetDeviceName(i, device_name, 256, unique_name, 256); if (0 != ret) { std::cout << \"GetDeviceName faild index:\" << i << \"|ret:\" << ret; continue; } //remember std::cout << \"==================>device_name:\" << device_name << \"|unique_name:\" << unique_name << std::endl; if (my_unique_name.empty()) { my_unique_name = unique_name; } //Capability int32_t cabs = device_info->NumberOfCapabilities(unique_name); for (int32_t j = 0; j < cabs; ++j) { VideoCaptureCapability capability; device_info->GetCapability(unique_name, j, capability); std::cout << \"w*h(\" << capability.width << \"*\" << capability.height << \")\" << \"|VideoType:\" << static_cast<int>(capability.videoType) << \"|maxFPS:\" << capability.maxFPS << std::endl; } } if (my_unique_name.empty()) { std::cout << \"video capture devices unique name is error.\" << std::endl; return 0; } //start TestVideoCaptureCallback videoback; rtc::scoped_refptr<VideoCaptureModule> module = VideoCaptureFactory::Create(my_unique_name.c_str()); module->RegisterCaptureDataCallback(&videoback); VideoCaptureCapability settings; settings.width = 1280; settings.height = 768; settings.maxFPS = 25; settings.videoType = webrtc::VideoType::kUnknown; uint32_t ret = module->StartCapture(settings); if (0 != ret) { std::cout << \"StartCapture faild:\" << ret; return 0; } //wait time_t tBegin = time(nullptr); while (true) { time_t tEnd = time(nullptr); if ((tEnd - tBegin) > 10) { break; } Sleep(10); } //clean module->StopCapture();//StartCapture return 0;}int main(){ TestWebrtcVideoCapture(); return 0;}","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"音视频","slug":"音视频","permalink":"http://cclk.cc/tags/音视频/"},{"name":"webrtc","slug":"webrtc","permalink":"http://cclk.cc/tags/webrtc/"}]},{"title":"webrtc学习6-麦克风采集","date":"2017-09-21T06:57:32.000Z","path":"2017/09/21/webrtc/webrtc学习6-麦克风采集/","text":"一、背景webrtc的本地音频的采集由AudioDeviceModule接口统一封装。 AudioDeviceModule是个大而全的接口,具体包括:枚举音频采集设备(Record)和播放设备(Playout)、设置当前的采集设备/播放设备、开始/停止音频的采集/播放、设置音频增益控制开关(AGC)等。 AudioTransport是个关键的对外接口,负责音频数据的传入(调用NeedMorePlayData方法,供Playout使用)和输出(调用RecordedDataIsAvailable方法,数据由Record采集操作产生)。 二、windows实现61版本的webrtc麦克风采集在windows上的实现代码是在下面文件内:12webrtc\\modules\\audio_device\\win\\audio_device_core_win.hwebrtc\\modules\\audio_device\\win\\audio_device_core_win.cc 内部实现方式为WASAPI:WASAPI的全称是Windows Audio Session API(Windows音频会话API),是从Windows Vista之后引入的UAA(Universal Audio Architecture)音频架构所属的API。WASAPI在Windows Vista、Windows 7、Windows Server 2008 R2系统中所使用。WASAPI允许传输未经修改的比特流到音频设备,从而避开SRC(Sample Rate Conversion,取样率转换器)的干扰。对于Windows XP来说,与WASAPI类似的通道为ASIO。 只支持Vista SP1及以上版本。 三、Native Demo123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183#include <webrtc/modules/audio_device/include/audio_device.h>#include <iostream>#include <time.h>#ifdef _WIN32#include <windows.h>#pragma comment(lib, \"winmm.lib\")#pragma comment(lib, \"Msdmo.lib\")#pragma comment(lib, \"Dmoguids.lib\")#pragma comment(lib, \"wmcodecdspuuid.lib\")#endifusing namespace webrtc;class AudioTransportAPI : public AudioTransport{public: AudioTransportAPI(const rtc::scoped_refptr<AudioDeviceModule>& audioDevice) {} ~AudioTransportAPI() override {} int32_t RecordedDataIsAvailable(const void* audioSamples, const size_t nSamples, const size_t nBytesPerSample, const size_t nChannels, const uint32_t sampleRate, const uint32_t totalDelay, const int32_t clockSkew, const uint32_t currentMicLevel, const bool keyPressed, uint32_t& newMicLevel) override { char temp[1024] = { 0 }; sprintf(temp, \"audio-%dx%dx%d\", sampleRate, nBytesPerSample * 8 / nChannels, nChannels); static FILE* pf_ = fopen(temp, \"wb+\"); if (nChannels == 1) { // mono } else if ((nChannels == 2) && (nBytesPerSample == 2)) { // stereo but only using one channel } else if ((nChannels == 2) && (nBytesPerSample == 4)) { fwrite(audioSamples, 1, nSamples * nBytesPerSample, pf_); } else { // stereo } return 0; } int32_t NeedMorePlayData(const size_t nSamples, const size_t nBytesPerSample, const size_t nChannels, const uint32_t sampleRate, void* audioSamples, size_t& nSamplesOut, int64_t* elapsed_time_ms, int64_t* ntp_time_ms) override { return 0; } void PushCaptureData(int voe_channel, const void* audio_data, int bits_per_sample, int sample_rate, size_t number_of_channels, size_t number_of_frames) override {} void PullRenderData(int bits_per_sample, int sample_rate, size_t number_of_channels, size_t number_of_frames, void* audio_data, int64_t* elapsed_time_ms, int64_t* ntp_time_ms) override {}};int TestWebrtcAudioCapture(){ // Windows: // if (WEBRTC_WINDOWS_CORE_AUDIO_BUILD) // user can select only the default (Core) rtc::scoped_refptr<AudioDeviceModule> audio = webrtc::AudioDeviceModule::Create(0, AudioDeviceModule::kPlatformDefaultAudio); if (!audio) { std::cout << \"Create device faild:\" << std::endl; return 0; } int32_t ret = audio->Init(); if (0 != ret) { std::cout << \"audio->Init faild:\" << ret << std::endl; return 0; } int num = audio->RecordingDevices(); if (0 == num) { std::cout << \"Audio input devices is 0\" << std::endl; return 0; } std::cout << \"Audio input devices:\" << num << std::endl; for (int i = 0; i < num; i++) { char name[webrtc::kAdmMaxDeviceNameSize] = { 0 }; char guid[webrtc::kAdmMaxGuidSize] = { 0 }; int ret = audio->RecordingDeviceName(i, name, guid); if (ret != -1) { std::cout << \"Index:\" << i << \" | name:\" << name << \" | guid:\" << guid << std::endl; } } ret = audio->SetRecordingDevice(0); if (0 != ret) { std::cout << \"audio->SetRecordingDevice faild:\" << ret << std::endl; return 0; } ret = audio->SetAGC(true); if (0 != ret) { std::cout << \"audio->SetAGC faild:\" << ret << std::endl; return 0; } ret = audio->InitRecording(); if (0 != ret) { std::cout << \"audio->InitRecording faild:\" << ret << std::endl; return 0; } AudioTransportAPI callback(audio); ret = audio->RegisterAudioCallback(&callback); if (0 != ret) { std::cout << \"audio->RegisterAudioCallback faild:\" << ret << std::endl; return 0; } ret = audio->StartRecording(); if (0 != ret) { std::cout << \"audio->StartRecording faild:\" << ret << std::endl; return 0; } //wait time_t tBegin = time(nullptr); while (true) { time_t tEnd = time(nullptr); if ((tEnd - tBegin) > 10) { break; } Sleep(10); } //clean audio->StopRecording();//StartRecording audio->Terminate();//Init return 0;}int main(){ TestWebrtcAudioCapture(); return 0;}","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"音视频","slug":"音视频","permalink":"http://cclk.cc/tags/音视频/"},{"name":"webrtc","slug":"webrtc","permalink":"http://cclk.cc/tags/webrtc/"}]},{"title":"Singleton & memory order","date":"2017-09-13T01:09:32.000Z","path":"2017/09/13/c++/memory_order/","text":"一、背景先看一段c++单例常见代码:12345678910111213Singleton* Singleton::instance() { if (pInstance == nullptr) //第一次检查 { Lock lock; if (pInstance == nullptr) //第二次检查 { pInstance = new Singleton; } } return pInstance;} 在上面的代码中,第一次检查并没有加锁,就避免了每次调用instance()时都要加锁的问题。貌似这个方法很完美了吧,逻辑上无懈可击。 其实上述设计方式使用到了双重检查锁定模式(DCLP),下面介绍下什么是DCLP。 双重检查锁定模式(DCLP):DCLP(double-checked locking pattern)的关键之处在于我们观察到的这一现象:调用者在调用instance()时,pInstance在大部分时候都是非空的,因此没必要再次初始化。所以,DCLP在加锁之前先做了一次pInstance是否为空的检查。只有判断结果为真(即pInstance还未初始化),加锁操作才会进行,然后再次检查pInstance是否为空(这就是该模式被命名为双重检查的原因)。第二次检查是必不可少的,因为,正如我们之前的分析,在第一次检验pInstance和加锁之间,可能有另一个线程对pInstance进行初始化。 DCLP前提:DCLP的执行过程中必须确保机器指令是按一个可接受的顺序执行的。 二、DCLP与指令执行顺序思考一下初始化pInstance的这行代码:1pInstance = new Singleton; 这条语句实际做了三件事情: 步骤1:为Singleton对象分配一片内存 步骤2:构造一个Singleton对象,存入已分配的内存区 步骤3:将pInstance指向这片内存区 这里至关重要的一点是:我们发现编译器并不会被强制按照以上顺序执行!实际上,编译器有时会交换步骤2和步骤3的执行顺序。 优化编译器会仔细地分析并重新排序你的代码,使得程序执行时,在可见行为的限制下,同一时间能做尽可能多的事情。在串行代码中发现并利用这种并行性是重新排列代码并引入乱序执行最重要的原因,但并不是唯一原因,以下几个原因也可能使编译器(和链接器)将指令重新排序: 避免寄存器数据溢出; 保持指令流水线连续; 公共子表达式消除; 降低生成的可执行文件的大小; 让我们先专注于如果编译这么做了,会发生些什么。 请看下面这段代码。我们将pInstance初始化的那行代码分解成我们上文提及的三个步骤来完成,把步骤1(内存分配)和步骤3(指针赋值)写成一条语句,接着写步骤2(构造Singleton对象)。正常人当然不会这么写代码,可是编译器却有可能将我们上文写出的DCLP源码生成出以下形式的等价代码。 12345678910111213141516Singleton* Singleton::instance(){ if (pInstance == 0) { Lock lock; if (pInstance == 0) { pInstance = //步骤3 operator new(sizeof(Singleton)); //步骤1 new (pInstance) Singleton; //步骤2 } } return pInstance;} 根据上述转化后的等价代码,我们来考虑以下场景: 线程A进入instance(),检查出pInstance为空,请求加锁,而后执行由步骤1和步骤3组成的语句。之后线程A被挂起。此时,pInstance已为非空指针,但pInstance指向的内存里的Singleton对象还未被构造出来。 线程B进入instance(), 检查出pInstance非空,直接将pInstance返回(return)给调用者。之后,调用者使用该返回指针去访问Singleton对象————显然这个Singleton对象实际上还未被构造出来呢! 只有步骤1和步骤2在步骤3之前执行,DCLP才有效。 因为c++编译器在编译过程中会对代码进行优化,所以实际的代码执行顺序可能被打乱,另外因为CPU有一级二级缓存(cache),CPU的计算结果并不是及时更新到内存的,所以在多线程环境,不同线程间共享内存数据存在可见性问题,从而导致使用DCLP也存在风险。 三、内存栅栏技术我们知道的双重检查锁定模式存在风险,那么有没有办法改进呢? 办法是有,这就是内存栅栏技术(memory fence),也称内存栅障(memory barrier) 。 内存栅栏的作用在于保证内存操作的相对顺序, 但并不保证内存操作的严格时序, 确保第一个线程更新的数据对其他线程可见。 一个 memory fence之前的内存访问操作必定先于其之后的完成。 以下是使用内存栅栏技术来实现DCLP的伪代码1234567891011121314151617181920212223Singleton* Singleton::instance() { Singleton* tmp = m_instance; ... // 插入内存栅栏指令 if (tmp == nullptr) { Lock lock; tmp = m_instance; if (tmp == nullptr) { tmp = new Singleton; // 语句1 ... // 插入内存栅栏指令,确保语句2执行时,tmp指向的对象已经完成初始化构造函数 m_instance = tmp;//语句2 } } return tmp;} 这里,我们可以看到:在m_instance指针为NULL时,我们做了一次锁定,这个锁定确保创建该对象的线程对m_instance 的操作对其他线程可见。在创建线程内部构造块中,m_instance被再一次检查,以确保该线程仅创建了一份对象副本。 四、atomic上节的代码使用内存栅栏锁定技术可以很方便地实现双重检查锁定。但是看着实在有点麻烦,在C++11中更好的实现方式是直接使用原子操作。 123456789101112131415161718192021std::atomic<Singleton*> Singleton::m_instance;std::mutex Singleton::m_mutex;Singleton* Singleton::instance() { Singleton* tmp = m_instance.load(std::memory_order_acquire); if (tmp == nullptr) { std::lock_guard<std::mutex> lock(m_mutex); tmp = m_instance.load(std::memory_order_relaxed); if (tmp == nullptr) { tmp = new Singleton; m_instance.store(tmp, std::memory_order_release); } } return tmp;} 如果你对memory_order的概念还是不太清楚,那么就使用C++顺序一致的原子操作,所有std::atomic的操作如果不带参数默认都是std::memory_order_seq_cst,即顺序的原子操作(sequentially consistent),简称SC,使用(SC)原子操作库,整个函数执行指令都将保证顺序执行,这是一种最保守的内存模型策略。 下面的代码就是使用SC原子操作实现双重检查锁定: 123456789101112131415161718192021std::atomic<Singleton*> Singleton::m_instance;std::mutex Singleton::m_mutex;Singleton* Singleton::getInstance() { Singleton* tmp = m_instance.load(); if (tmp == nullptr) { std::lock_guard<std::mutex> lock(m_mutex); tmp = m_instance.load(); if (tmp == nullptr) { tmp = new Singleton; m_instance.store(tmp); } } return tmp;} 五、call_oncecall_one保证函数fn只被执行一次,如果有多个线程同时执行函数fn调用,则只有一个活动线程(active call)会执行函数,其他的线程在这个线程执行返回之前会处于”passive execution”(被动执行状态)—不会直接返回,直到活动线程对fn调用结束才返回。对于所有调用函数fn的并发线程的数据可见性都是同步的(一致的)。 如果活动线程在执行fn时抛出异常,则会从处于”passive execution”状态的线程中挑一个线程成为活动线程继续执行fn,依此类推。 一旦活动线程返回,所有”passive execution”状态的线程也返回,不会成为活动线程。 由上面的说明,我们可以确信call_once完全满足对多线程状态下对数据可见性的要求。所以利用call_once再结合lambda表达式,前面几节那么多复杂代码,在这里千言万语凝聚为一句话: 1234567Singleton* Singleton::m_instance;Singleton* Singleton::instance() { static std::once_flag oc;//用于call_once的局部静态变量 std::call_once(oc, [&] { m_instance = new Singleton();}); return m_instance;} 六、多线程与static可以看出上面的代码相比较之前的示例代码来说已经相当的简洁了,但是在C++memory model中对static local variable,说道:The initialization of such a variable is defined to occur the first time control passes through its declaration; for multiple threads calling the function, this means there’s the potential for a race condition to define first. 因此,我们将会得到一份最简洁也是效率最高的单例模式的C++11实现:(vc2015简单测试通过) 12345Singleton& Singleton::instance() { static Singleton instance; return instance;} 七、日常应用中可能会踩的坑线程A代码:123456int flag = 0;int params = 0;params = 1;flag = 1;//start thread B; 有线程B引用线程A中的数据:1234if(1 == flag){ //using params;} 由于编译器会进行优化,不能确保params = 1的赋值操作在flag = 1之前进行。 优化方案:1234if (flag && params) //using flag and params{ //start thread B} 八、参考double-checked-locking-is-fixed-in-cpp11 当我们在谈论 memory order 的时候,我们在谈论什么 C++和双重检查锁定模式(DCLP)的风险 c++11单实例(singleton)初始化的几种方法 漫谈C++11多线程内存模型","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"c++","slug":"c","permalink":"http://cclk.cc/tags/c/"}]},{"title":"webrtc学习5-自动增益","date":"2017-08-23T02:35:32.000Z","path":"2017/08/23/webrtc/webrtc学习5-自动增益/","text":"一、背景AGC(Auto Gain Control,自动增益控制),较新的webrtc已经把原来的agc模块移动到了一个叫做legacy的文件夹。具体代码路径“webrtc/modules/audio_processing/agc/legacy”。具有三种模式: kAgcModeAdaptiveAnalog带有模拟音量调节的功能; kAgcModeAdaptiveDigital是可变增益agc,但是不调节系统音量; kAgcModeFixedDigital是固定增益的agc; 具体原理可以参考webrtc源码。 二、注意事项只支持以下采样率和帧长的输入数据: 8K采样率,10或者20ms长度数据,采样数为80; 16K采样率,10或者20ms长度数据,采样数为160; 32K采样率,5或者10ms长度数据,采样数为160; DemoC++ code123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128#include <webrtc/modules/audio_processing/agc/legacy/gain_control.h>//自动增益struct SampleConfig{ int channels = 1; //采样声道数 int sampleRate = 44100; //采样频率 int bitsPerSample = 16; //采样位数};int GetSamplesPer10ms(int fs){ switch (fs) { case 8000: return 80; case 16000: case 32000: return 160; default: return -1; }}void TestAutoGainControl(const char* filePathIn, const char* filePathOut, SampleConfig cfg){ void *agcInst = nullptr; FILE *fpIn = fopen(filePathIn, \"rb\"); FILE *fpOut = fopen(filePathOut, \"wb\"); int samples = GetSamplesPer10ms(cfg.sampleRate); char *frame_in_c = new char[samples * 2]; //读取文件的字节 short *frame_in_s = new short[samples]; //声音存储short数据 short *frame_out_s = new short[samples]; //ns后float转short数据 do { if (!fpIn || !fpOut) { std::cout << \"open file\" << std::endl; break; } agcInst = WebRtcAgc_Create(); if (!agcInst) { std::cout << \"WebRtcAgc_Create error\" << std::endl; break; } int minLevel = 0; int maxLevel = 255; int agcMode = kAgcModeFixedDigital; //kAgcModeAdaptiveAnalog 带有模拟音量调节的功能 //kAgcModeAdaptiveDigital 是可变增益agc,但是不调节系统音量 //kAgcModeFixedDigital是固定增益的agc if (0 != WebRtcAgc_Init(agcInst, minLevel, maxLevel, agcMode, cfg.sampleRate)) { std::cout << \"WebRtcAgc_Init error\" << std::endl; break; } WebRtcAgcConfig agcConfig; agcConfig.compressionGaindB = 20; agcConfig.limiterEnable = 1; agcConfig.targetLevelDbfs = 3; if (0 != WebRtcAgc_set_config(agcInst, agcConfig)) { std::cout << \"WebRtcAgc_set_config error\" << std::endl; break; } int spanTick = GetTickCount(); int micLevelIn = 0; int micLevelOut = 0; while (1) { if (samples == fread(frame_in_c, sizeof(char) * 2, samples, fpIn)) { //1 for (int i = 0; i < samples; ++i) { frame_in_s[i] = (frame_in_c[i * 2 + 1] << 8) | (frame_in_c[i * 2] & 0xFF);//两个char型拼成一个short } //2 short* const p = frame_in_s; const short* const* inNear = &p; short* const q = frame_out_s; short* const* outframe = &q; uint8_t saturationWarning = 0; //3 if (0 != WebRtcAgc_Process(agcInst, inNear, 1, samples, outframe, micLevelIn, &micLevelOut, 0, &saturationWarning)) { std::cout << \"WebRtcAgc_Process error\" << std::endl; break; } micLevelIn = micLevelOut; fwrite(frame_out_s, sizeof(short), samples, fpOut); } else { break; } } spanTick = GetTickCount() - spanTick; std::cout << \"agc spanTick:\" << spanTick << std::endl; } while (0); //clean if (agcInst) WebRtcAgc_Free(agcInst); if (fpIn) fclose(fpIn); if (fpOut) fclose(fpOut); delete[]frame_in_c; delete[]frame_in_s; delete[]frame_out_s;} 测试文件自动增益测试音频 1C_16bit_32K_lhydd_ns.pcm","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"音视频","slug":"音视频","permalink":"http://cclk.cc/tags/音视频/"},{"name":"webrtc","slug":"webrtc","permalink":"http://cclk.cc/tags/webrtc/"}]},{"title":"webrtc学习4-噪声消除","date":"2017-08-23T02:21:32.000Z","path":"2017/08/23/webrtc/webrtc学习4-噪声消除/","text":"一、背景NS(Noise Suppression,噪声抑制),webrtc的噪声处理模块源码在“webrtc/modules/audio_processing/ns”内,包括两种方式: noise_suppression.h 去噪浮点算法 noise_suppression_x.h 去噪定点算法,可以在性能较低的嵌入式设备上使用 具体去噪原理可以参考webrtc源码。 二、注意事项 webrtc默认接口都是只支持输入10ms的采样数据,并且只支持8000,16000,32000的采样率,非上述类型采样率,需要重采样后才能进行处理。 最新61版本,去噪模块支持输入32k的采样率,但采样个数为160,与上述不符合,需要进一步研究。 DemoC++ code123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123#include <webrtc/modules/audio_processing/ns/noise_suppression.h> //去噪浮点算法struct SampleConfig{ int channels = 1; //采样声道数 int sampleRate = 44100; //采样频率 int bitsPerSample = 16; //采样位数};int GetSamplesPer10ms(int fs){ switch (fs) { case 8000: return 80; case 16000: case 32000: return 160; default: return -1; }}/***@fs 采样率**@mode 设置噪声抑制的级别 0: Mild-轻微, 1: Medium-中等 , 2: Aggressive-积极的*/void TestNoiseSuppression(const char* filePathIn, const char* filePathOut, int mode, SampleConfig cfg){ NsHandle *nsInst = nullptr; FILE *fpIn = fopen(filePathIn, \"rb\"); FILE *fpOut = fopen(filePathOut, \"wb\"); int samples = GetSamplesPer10ms(cfg.sampleRate); char *frame_in_c = new char[samples * 2]; //读取文件的字节 short *frame_in_s = new short[samples]; //声音存储short数据 float *frame_in_f = new float[samples]; //声音存储float数据 short *frame_out_s = new short[samples]; //ns后float转short数据 float *frame_out_f = new float[samples]; //ns后返回得到的float数据 do { if (!fpIn || !fpOut) { std::cout << \"open file\" << std::endl; break; } nsInst = WebRtcNs_Create(); if (!nsInst) { std::cout << \"WebRtcNs_Create error\" << std::endl; break; } if (0 != WebRtcNs_Init(nsInst, cfg.sampleRate)) { std::cout << \"WebRtcNs_Init error\" << std::endl; break; } //设置噪声抑制的级别 0: Mild-轻微, 1: Medium-中等 , 2: Aggressive-积极的 if (0 != WebRtcNs_set_policy(nsInst, mode)) { std::cout << \"WebRtcNs_set_policy error\" << std::endl; break; } int spanTick = GetTickCount(); while (1) { if (samples == fread(frame_in_c, sizeof(char) * 2, samples, fpIn)) { //1 for (int i = 0; i < samples; ++i) { frame_in_s[i] = (frame_in_c[i * 2 + 1] << 8) | (frame_in_c[i * 2] & 0xFF);//两个char型拼成一个short frame_in_f[i] = frame_in_s[i];//转float型接口需要 } //2 float* const p = frame_in_f; const float* const* spframe = &p; float* const q = frame_out_f; float* const* outframe = &q; //3 WebRtcNs_Analyze(nsInst, frame_in_f); WebRtcNs_Process(nsInst, spframe, 1, outframe); for (int i = 0; i < samples; ++i) { frame_out_s[i] = frame_out_f[i]; } fwrite(frame_out_s, sizeof(short), samples, fpOut); } else { break; } } spanTick = GetTickCount() - spanTick; std::cout << \"ns spanTick:\" << spanTick << std::endl; } while (0); //clean if (nsInst) WebRtcNs_Free(nsInst); if (fpIn) fclose(fpIn); if (fpOut) fclose(fpOut); delete[]frame_in_c; delete[]frame_in_s; delete[]frame_in_f; delete[]frame_out_s; delete[]frame_out_f;} 测试文件噪声测试音频 1C_16bit_32K_lhydd.pcm","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"音视频","slug":"音视频","permalink":"http://cclk.cc/tags/音视频/"},{"name":"webrtc","slug":"webrtc","permalink":"http://cclk.cc/tags/webrtc/"}]},{"title":"流媒体的音视频同步","date":"2017-08-21T01:22:32.000Z","path":"2017/08/21/audio-video/流媒体的音视频同步/","text":"背景播放音视频过程中,每一帧音频或视频都有一个持续时间,并且声音同画面要一致。一般在播放端,音视频渲染都需要消耗一定的系统资源,所以在正常情况下,会将音视频的渲染分为多个线程去处理。这就要求对音视频播放的开始和持续时间有要求,才能保证画面同声音同步并且画面流畅。 根据音频去同步视频在windows端,声音的播放是需要连续的,并且调用系统API接口时,会等到音频播放完毕才会通知上层应用。所以在windows端音频播放有以下几个特点: 必须连续,反之则存在噪声(如滋滋声、电流声) 播放时间是固定的,播放完毕才会通知上应用 由上可知,在windows端,声音的播放时间是相对固定的,并且为了减少噪音,我们需要尽量不丢掉音频数据,所以我们可以根据音频播放的时间,去进行音视频同步。 可能存在的几种情况实际应用中,根据音频来同步视频可能存在以下几个情况: 网络拥塞时,可能会导致音频播放完毕,但新的音频数据没有到达 网络拥塞时,可能会导致视频播放完毕,但新的视频数据没有到达 系统声卡故障,导致音频播放会比正常稍慢,导致后续一直积累数据 系统显卡故障,导致视频渲染耗时过多,导致后续一直积累数据 只有视频,没有音频 系统时钟相差过大,如采集端可能已经过去相对时间A1ms,但播放端相对时间为A2ms,两者相差较大 流程图关键流程如下图: 从上图可了解,上述几种情况,可作如下处理: 网络拥塞时:缓存适当的音视频数据,如果还无法解决,降低码流 渲染慢:音频加快渲染速度,视频丢弃渲染数据 无音频:通过pts的相对时间差修正渲染时长 系统时钟相差大:定时进行时钟矫正","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"音视频","slug":"音视频","permalink":"http://cclk.cc/tags/音视频/"}]},{"title":"webrtc学习3-回声消除","date":"2017-08-16T01:32:32.000Z","path":"2017/08/16/webrtc/webrtc学习3-回声消除/","text":"一、回声消除从通讯回音产生的原因看,可以分为声学回音(Acoustic Echo)和线路回音(Line Echo),相应的回声消除技术就叫声学回声消除(Acoustic Echo Cancellation,AEC)和线路回声消除(Line Echo Cancellation, LEC)。 声学回音是由于在免提或者会议应用中,扬声器的声音多次反馈到麦克风引起的(比较好理解); 线路回音是由于物理电子线路的二四线匹配耦合引起的(比较难理解)。 二、产生原因回音的产生主要有两种原因 由于空间声学反射产生的声学回音如下图: 图中的男子说话,语音信号(speech1)传到女士所在的房间,由于空间的反射,形成回音speech1(Echo)重新从麦克风输入,同时叠加了女士的语音信号(speech2)。此时男子将会听到女士的声音叠加了自己的声音,影响了正常的通话质量。此时在女士所在房间应用回音抵消模块,可以抵消掉男子的回音,让男子只听到女士的声音。 由于2-4线转换引入的线路回音如下图: 在ADSL Modem和交换机上都存在2-4线转换的电路,由于电路存在不匹配的问题,会有一部分的信号被反馈回来,形成了回音。如果在交换机侧不加回音抵消功能,打电话的人就会自己听到自己的声音。 三、AEC回声消除看下面的AEC声学回声消除框图 其中,我们可以得到两个信号:一个是蓝色和红色混合的信号1,也就是实际需要发送的speech和实际不需要的echo混合而成的语音流;另一个就是虚线的信号2,也就是原始的引起回音的语音。那大家会说,哦,原来回声消除这么简单,直接从混合信号1里面把把这个虚线的2减掉不就行了?请注意,拿到的这个虚线信号2和回音echo是有差异的,直接相减会使语音面目全非。我们把混合信号1叫做近端信号ne,虚线信号2叫做远端参考信号fe,如果没有fe这个信号,回声消除就是不可能完成的任务,就像“巧妇难为无米之炊”。 虽然参考信号fe和echo不完全一样,存在差异,但是二者是高度相关的,这也是echo称之为回音的原因。至少,回音的语义和参考信号是一样的,也还听得懂,但是如果你说一句,马上又听到自己的话回来一句,那是比较难受的。既然fe和echo高度相关,echo又是fe引起的,我们可以把echo表示为fe的数学函数:echo=F(fe)。函数F被称之为回音路径。在声学回声消除里面,函数F表示声音在墙壁,天花板等表面多次反射的物理过程;在线路回声消除里面,函数F表示电子线路的二四线匹配耦合过程。很显然,我们下面要做的工作就是求解函数F。得到函数F就可以从fe计算得到echo,然后从混合信号1里面减掉echo就实现了回声消除。 尽管回声消除是非常复杂的技术,但我们可以简单的描述这种处理方法: 房间A的音频会议系统接收到房间B中的声音 声音被采样,这一采样被称为回声消除参考 随后声音被送到房间A的音箱和声学回声消除器中 房间B的声音和房间A的声音一起被房间A的话筒拾取 声音被送到声学回声消除器中,与原始的采样进行比较,移除房间B的声音 自适应滤波器(数学太渣,看不懂,有兴趣的翻参考材料,略)四、webrtc aecwebrtc的回声抵消(aec、aecm)算法主要包括以下几个重要模块: 回声时延估计 NLMS(归一化最小均方自适应算法) NLP(非线性滤波) CNG(舒适噪声产生) 回声时延估计 这张图很多东西可以无视,我们重点看T0,T1,T2三项。 T0代表着声音从扬声器传到麦克风的时间,这个时间可以忽略,因为一般来说话筒和扬声器之间距离不会太远,考虑到声音340米每秒的速度,这个时间都不会超过1毫秒。 T1代表远处传到你这来的声音,这个声音被传递到回声消除远端接口(WebRtcAec_BufferFarend)的到播放出来的时间。一般来说接收到的音频数据传入这个接口的时候也就是上层传入扬声器的时刻,所以可以理解成该声音防到播放队列中开始计时,到播放出来的时间。 T2代表一段声音被扬声器采集到,然后到被送到近端处理函数(WebRtcAec_Process)的时刻,由于声音被采集到马上会做回声消除处理,所以这个时间可以理解成麦克风采集到声音开始计时,然后到你的代码拿到音频PCM数据所用的时间。 delay=T0+T1+T2,其实也就是T1+T2。 一般来说,一个设备如果能找到合适的delay,那么这个设备再做回声消除处理就和降噪增益一样几乎没什么难度了。如iPhone的固定delay是60ms。 NLMS(归一化最小均方自适应算法) LMS/NLMS/AP/RLS等都是经典的自适应滤波算法,此处只对webrtc中使用的NLMS算法做简略介绍。 设远端信号为x(n),近段信号为d(n),W(n),则误差信号e(n)=d(n)-w’(n)x(n) (此处‘表示转秩),NLMS对滤波器的系数更新使用变步长方法,即步长u=u0/(gamma+x’(n) * x(n))。其中u0为更新步长因子,gamma是稳定因子,则滤波器系数更新方程为 W(n+1)=W(n)+u*e(n)*x(n); NLMS比传统LMS算法复杂度略高,但收敛速度明显加快。LMS/NLMS性能差于AP和RLS算法。 webrtc使用了分段块频域自适应滤波(PBFDAF)算法,这也是自适应滤波器的常用算法。 自适应滤波的更多资料可以参考simon haykin 的《自适应滤波器原理》。 NLP(非线性滤波)webrtc采用了维纳滤波器。此处只给出传递函数的表达式,设估计的语音信号的功率谱为Ps(w),噪声信号的功率谱为Pn(w),则滤波器的传递函数为H(w)=Ps(w)/(Ps(w)+Pn(w))。 CNG(舒适噪声产生)webrtc采用的舒适噪声生成器比较简单,首先生成在[0 ,1 ]上均匀分布的随机噪声矩阵,再用噪声的功率谱开方后去调制噪声的幅度。 应用场景webrtc AEC算法是属于分段快频域自适应滤波算法,Partioned block frequeney domain adaPtive filter(PBFDAF)。具体可以参考Paez Borrallo J M and Otero M G 使用该AEC算法要注意两点: 延时要小,因为算法默认滤波器长度是分为12块,每块64点,按照8000采样率,也就是12*8ms=96ms的数据,而且超过这个长度是处理不了的。 延时抖动要小,因为算法是默认10块也计算一次参考数据的位置(即滤波器能量最大的那一块),所以如果抖动很大的话找参考数据时不准确的,这样回声就消除不掉了。 DemoC++ code1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889#include <webrtc/modules/audio_processing/aec/echo_cancellation.h>using namespace webrtc;#define NN 160int webrtcAecTest1(){#define NN 160 char far_frame_c[NN * 2]; char near_frame_c[NN * 2]; short far_frame_s[NN]; short near_frame_s[NN]; short out_frame_s[NN]; void *aecmInst = NULL; FILE *fp_far = fopen(\"speaker.pcm\", \"rb\"); FILE *fp_near = fopen(\"micin.pcm\", \"rb\"); FILE *fp_out = fopen(\"out1.pcm\", \"wb\"); float far_frame_f[NN]; float near_frame_f[NN]; float out_frame_f[NN]; float; do { if (!fp_far || !fp_near || !fp_out) { printf(\"WebRtcAecTest open file err \\n\"); break; } aecmInst = WebRtcAec_Create(); WebRtcAec_Init(aecmInst, 8000, 8000); AecConfig config; config.nlpMode = kAecNlpConservative; WebRtcAec_set_config(aecmInst, config); while (1) { if (NN == fread(far_frame_c, sizeof(char) * 2, NN, fp_far)) { //1 for (int i = 0; i < NN; ++i) { far_frame_s[i] = (far_frame_c[i * 2 + 1] << 8) | (far_frame_c[i * 2] & 0xFF);//两个char型拼成一个short far_frame_f[i] = far_frame_s[i];//转float型接口需要 } WebRtcAec_BufferFarend(aecmInst, far_frame_f, NN);//对参考声音(回声)的处理 //2 fread(near_frame_c, sizeof(char) * 2, NN, fp_near); for (int i = 0; i < NN; ++i) { near_frame_s[i] = (near_frame_c[i * 2 + 1] << 8) | (near_frame_c[i * 2] & 0xFF); near_frame_f[i] = near_frame_s[i]; } float* const p = near_frame_f; const float* const* nearend = &p; float* const q = out_frame_f; float* const* out = &q; //3 WebRtcAec_Process(aecmInst, nearend, 1, out, NN, 40, 0);//回声消除 for (int i = 0; i < NN; ++i) { out_frame_s[i] = out_frame_f[i]; } fwrite(out_frame_s, sizeof(short), NN, fp_out); } else { break; } } } while (0); fclose(fp_far); fclose(fp_near); fclose(fp_out); WebRtcAec_Free(aecmInst); return 0;} 测试文件麦克风输入音频,包括回音 micin.pcm 回音参考音频 speaker.pcm 五、直播应用方案根据直播应用场景,有两种可能需要回声消除的情况: 场景A:主播端具有麦克风输入,并且音箱外放声音,需要过滤掉音响的回音; 场景B:主播端同某些客户端在同一房间,并且客户端在音响外放; 场景A可以利用webrtc的aec模块进行回声消除,在windows端,需要计算出音频输出到音响,麦克风采集到音频的时间间隔。但实际应用中,一般主播端是无外放功能,回声消除的作用不是特别广泛。 场景B很难计算每个客户端到采集端的delay时间,需要一些ntp之类的时间同步过程,相对复杂,技术难较高,效果也不会特别明显;实际应用过程中,主播端应该是在一个安静的环境中,应用范围低。 综上两种场景,结合直播的实际情况,在直播应用中加入回声消除,适用面窄,不适合加入回声消除方案。 六、参考解密回声消除技术之一(理论篇) 维基百科-回音消除 单独编译和使用webrtc音频回声消除模块(附完整源码+测试音频文件)","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"音视频","slug":"音视频","permalink":"http://cclk.cc/tags/音视频/"},{"name":"webrtc","slug":"webrtc","permalink":"http://cclk.cc/tags/webrtc/"}]},{"title":"webrtc学习2-音频预处理模块","date":"2017-08-14T07:38:32.000Z","path":"2017/08/14/webrtc/webrtc学习2-音频预处理模块/","text":"VoiceEngineWebRtc中VoiceEngine(VoE)可以完成大部分的VOIP相关任务,包括采集、自动增益、噪声消除、回声抑制、编解码、RTP传输。 APM对于非webrtc的项目,如果需要用到webrtc中的音频算法处理模块,可以使用仅次于VoE层级的模块APM(Audio Preprocessing Module),一个纯粹的音频预处理单元。 源码APM的整体编译需要WebRTC源码目录下的如下资源: common_audio 整个目录 modules 目录(不包含 video 部分) system_wrappers 整个目录 位于 WebRTC 源码根目录下的 common_types.h | common.h | typedefs.h 三个头文件。 模块APM包括以下几个核心算法模块: 简写 英文 中文 AEC Acoustic Echo Canceller 声学回声消除 AECM Acoustic Echo Canceller for Mobile 声学回声消除for Mobile VAD Voice Activity Detection 静音检测 AGC Auto Gain Control 自动增益控制 NS Noise Suppression 噪声抑制 使用注意事项音频格式 音频处理的时候webrtc一次仅能处理10ms数据,小于10ms的数据不要传入,因为即使是传入小于10ms的数据最后传入也是按照10ms的数据传出,此时会出现问题。另外支持采样率也只有8K,16K,32K三种,不论是降噪模块,或者是回声消除增益等等均是如此。 如果采样率是8K,则对应8000/1000*10=80个采样点,16K是160个采样点,32K是320个采样点。 对于8000和16000采样率的音频数据在使用时可以不管高频部分,只需要传入低频数据即可,但是对于32K采样率的数据就必须通过滤波接口将数据分为高频和低频传入,传入降噪后再组合成音频数据。大于32K的音频文件就必须要通过重采样接口降频到对应的采样率再处理(旧版本方法,新版本尝试直接输入160个采样点的32K数据也可成功,但输入320个采样点则不成功)。 接口类型比如回声消除接口1234567int32_t WebRtcAec_Process(void* aecInst, const float* const* nearend, size_t num_bands, float* const* out, size_t nrOfSamples, int16_t msInSndCardBuf, int32_t skew); webrtc接口使用类型为float类型,音频数据类型可能是short类型,网络传输类型可能是char类型,需要进行转换。 webrtc接口为const float* const*,float* const*之类的,不是很常见,需要转换。 char转short, short转float1234567891011121314#define NN 160char far_frame_c[NN * 2] = { 0 };short far_frame_s[NN] = { 0 };FILE *fp_far = fopen(\"far.pcm\", \"rb\");fread(far_frame_c, sizeof(char) * 2, NN, fp_far);for (int i = 0; i < NN; ++i){ far_frame_s[i] = (far_frame_c[i * 2 + 1] << 8) | (far_frame_c[i * 2] & 0xFF);//两个char型拼成一个short far_frame_f[i] = far_frame_s[i];//转float型接口需要} float转short123456789#define NN 160short out_frame_s[NN] = { 0 };float out_frame_f[NN] = { 0 };for (int i = 0; i < NN; ++i){ out_frame_s[i] = out_frame_f[i];} float*转const float* const*123456#define NN 160float near_frame_f[NN] = { 0 };float* const p = near_frame_f;const float* const* nearend = &p; float*转float* const*123456#define NN 160float out_frame_f[NN] = { 0 };float* const q = out_frame_f;float* const* out = &q;","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"音视频","slug":"音视频","permalink":"http://cclk.cc/tags/音视频/"},{"name":"webrtc","slug":"webrtc","permalink":"http://cclk.cc/tags/webrtc/"}]},{"title":"RGB,YUV","date":"2017-08-12T02:55:32.000Z","path":"2017/08/12/audio-video/rgb-yuv/","text":"一、RGB计算机彩色显示器显示色彩的原理与彩色电视机一样,都是采用R(Red)、G(Green)、B(Blue)相加混色的原理:通过发射出三种不同强度的电子束,使屏幕内侧覆盖的红、绿、蓝磷光材料发光而产生色彩。这种色彩的表示方法称为RGB色彩空间表示(它也是多媒体计算机技术中用得最多的一种色彩空间表示方法)。 根据三基色原理,任意一种色光F都可以用不同分量的R、G、B三色相加混合而成。F = r [ R ] + g [ G ] + b [ B ] 其中,r、g、b分别为三基色参与混合的系数。当三基色分量都为0(最弱)时混合为黑色光;而当三基色分量都为k(最强)时混合为白色光。调整r、g、b三个系数的值,可以混合出介于黑色光和白色光之间的各种各样的色光。 那么YUV又从何而来呢?在现代彩色电视系统中,通常采用三管彩色摄像机或彩色CCD摄像机进行摄像,然后把摄得的彩色图像信号经分色、分别放大校正后得到RGB,再经过矩阵变换电路得到亮度信号Y和两个色差信号R-Y(即U)、B-Y(即V),最后发送端将亮度和色差三个信号分别进行编码,用同一信道发送出去。这种色彩的表示方法就是所谓的YUV色彩空间表示。 采用YUV色彩空间的重要性是它的亮度信号Y和色度信号U、V是分离的。如果只有Y信号分量而没有U、V分量,那么这样表示的图像就是黑白灰度图像。彩色电视采用YUV空间正是为了用亮度信号Y解决彩色电视机与黑白电视机的兼容问题,使黑白电视机也能接收彩色电视信号。 互相转换YUV与RGB相互转换的公式如下(RGB取值范围均为0-255): Y = 0.299R + 0.587G + 0.114BU = -0.147R - 0.289G + 0.436BV = 0.615R - 0.515G - 0.100B R = Y + 1.14VG = Y - 0.39U - 0.58VB = Y + 2.03U 常见的RGB和YUV格式在DirectShow中,常见的RGB格式有RGB1、RGB4、RGB8、RGB565、RGB555、RGB24、RGB32、ARGB32等;常见的YUV格式有YUY2、YUYV、YVYU、UYVY、AYUV、Y41P、Y411、Y211、IF09、IYUV、YV12、YVU9、 YUV411、YUV420等。 GUID 格式描述 MEDIASUBTYPE_RGB1 2色,每个像素用1位表示,需要调色板 MEDIASUBTYPE_RGB4 16色,每个像素用4位表示,需要调色板 MEDIASUBTYPE_RGB8 256色,每个像素用8位表示,需要调色板 MEDIASUBTYPE_RGB565 每个像素用16位表示,RGB分量分别使用5位、6位、5位 MEDIASUBTYPE_RGB555 每个像素用16位表示,RGB分量都使用5位(剩下的1位不用) MEDIASUBTYPE_RGB24 每个像素用24位表示,RGB分量各使用8位 MEDIASUBTYPE_RGB32 每个像素用32位表示,RGB分量各使用8位(剩下的8位不用) MEDIASUBTYPE_ARGB32 每个像素用32位表示,RGB分量各使用8位(剩下的8位用于表示Alpha通道值) MEDIASUBTYPE_YUY2 YUY2格式,以4:2:2方式打包 MEDIASUBTYPE_YUYV YUYV格式(实际格式与YUY2相同) MEDIASUBTYPE_YVYU YVYU格式,以4:2:2方式打包 MEDIASUBTYPE_UYVY UYVY格式,以4:2:2方式打包 MEDIASUBTYPE_AYUV 带Alpha通道的4:4:4 YUV格式 MEDIASUBTYPE_Y41P Y41P格式,以4:1:1方式打包 MEDIASUBTYPE_Y411 Y411格式(实际格式与Y41P相同) MEDIASUBTYPE_Y211 Y211格式 MEDIASUBTYPE_IF09 IF09格式 MEDIASUBTYPE_IYUV IYUV格式 MEDIASUBTYPE_YV12 YV12格式 MEDIASUBTYPE_YVU9 YVU9格式 RGB1、RGB4、RGB8RGB1、RGB4、RGB8都是调色板类型的RGB格式,在描述这些媒体类型的格式细节时,通常会在BITMAPINFOHEADER数据结构后面跟着一个调色板(定义一系列颜色)。它们的图像数据并不是真正的颜色值,而是当前像素颜色值在调色板中的索引。以RGB1(2色位图)为例,比如它的调色板中定义的两种颜色值依次为0x000000(黑色)和0xFFFFFF(白色),那么图像数据001101010111…(每个像素用1位表示)表示对应各像素的颜色为:黑黑白白黑白黑白黑白白白…。 RGB565 RGB565使用16位表示一个像素,这16位中的5位用于R,6位用于G,5位用于B。程序中通常使用一个字(WORD,一个字等于两个字节)来操作一个像素。当读出一个像素后,这个字的各个位意义如下:| 高字节 | 低字节 || :——– | ——–:|| RRRRRGGG | GGGBBBBB|可以组合使用屏蔽字和移位操作来得到RGB各分量的值: 123456#define RGB565_MASK_RED 0xF800#define RGB565_MASK_GREEN 0x07E0#define RGB565_MASK_BLUE 0x001FR = (wPixel & RGB565_MASK_RED) >> 11; // 取值范围0-31G = (wPixel & RGB565_MASK_GREEN) >> 5; // 取值范围0-63B = wPixel & RGB565_MASK_BLUE; // 取值范围0-31 RGB555RGB555是另一种16位的RGB格式,RGB分量都用5位表示(剩下的1位不用)。使用一个字读出一个像素后,这个字的各个位意义如下(X表示不用,可以忽略):| 高字节 | 低字节 || :——– | ——–:|| XRRRRRGG | GGGBBBBB|可以组合使用屏蔽字和移位操作来得到RGB各分量的值: 123456#define RGB555_MASK_RED 0x7C00#define RGB555_MASK_GREEN 0x03E0#define RGB555_MASK_BLUE 0x001FR = (wPixel & RGB555_MASK_RED) >> 10; // 取值范围0-31G = (wPixel & RGB555_MASK_GREEN) >> 5; // 取值范围0-31B = wPixel & RGB555_MASK_BLUE; // 取值范围0-31 RGB24RGB24使用24位来表示一个像素,RGB分量都用8位表示,取值范围为0-255。注意在内存中RGB各分量的排列顺序为:BGR BGR BGR…。通常可以使用RGBTRIPLE数据结构来操作一个像素,它的定义为: 12345typedef struct tagRGBTRIPLE { BYTE rgbtBlue; // 蓝色分量BYTE rgbtGreen; // 绿色分量BYTE rgbtRed; // 红色分量} RGBTRIPLE; RGB32、ARGB32使用32位来表示一个像素,RGB分量各用去8位,剩下的8位用作Alpha通道或者不用。(ARGB32就是带Alpha通道的 RGB32。)注意在内存中RGB各分量的排列顺序为:BGRA BGRABGRA…。通常可以使用RGBQUAD数据结构来操作一个像素,它的定义为: 123456typedef struct tagRGBQUAD {BYTE rgbBlue; // 蓝色分量BYTE rgbGreen; // 绿色分量BYTE rgbRed; // 红色分量BYTE rgbReserved; // 保留字节(用作Alpha通道或忽略)} RGBQUAD; 二、YUV关于YUV格式摘自 维基百科,YUV,是一种颜色编码方法。YUV是编译true-color颜色空间(color space)的种类,Y’UV, YUV, YCbCr,YPbPr等专有名词都可以称为YUV,彼此有重叠。“Y”表示明亮度(Luminance、Luma),“U”和“V”则是色度、浓度(Chrominance、Chroma),Y’UV, YUV, YCbCr, YPbPr 常常有些混用的情况,其中 YUV 和 Y’UV 通常用来描述类比讯号,而相反的 YCbCr 与 YPbPr 则是用来描述数位的影像讯号,例如在一些压缩格式内 MPEG、JPEG 中,但在现今,YUV 通常已经在电脑系统上广泛使用。YUV Formats分成两个格式: 紧缩格式(packed formats):将Y、U、V值储存成Macro Pixels阵列,和RGB的存放方式类似。 平面格式(planar formats):将Y、U、V的三个份量分别存放在不同的矩阵中。 紧缩格式(packed format)中的YUV是混合在一起的,对于YUV4:4:4格式而言,用紧缩格式很合适的,因此就有了UYVY、YUYV等。平面格式(planar formats)是指每Y份量,U份量和V份量都是以独立的平面组织的,也就是说所有的U份量必须在Y份量后面,而V份量在所有的U份量后面,此一格式适用于采样(subsample)。平面格式(planar format)有I420(4:2:0)、YV12、IYUV等。像是一个三维平面一样。 常见的YUV格式为节省带宽起见,大多数YUV格式平均使用的每像素位数都少于24位元。主要的抽样(subsample)格式有YCbCr 4:2:0、YCbCr 4:2:2、YCbCr 4:1:1和YCbCr 4:4:4。YUV的表示法称为A:B:C表示法: 4:4:4表示完全取样。表示色度值(UV)没有减少采样。即Y,U,V各占一个字节,加上Alpha通道一个字节,总共占4字节.这个格式其实就是24bpp的RGB格式了。 4:2:2表示2:1的水平取样,垂直完全采样。表示UV分量采样减半,比如第一个像素采样Y,U,第二个像素采样Y,V,依次类推,这样每个点占用2个字节.二个像素组成一个宏像素。 4:2:0表示2:1的水平取样,垂直2:1采样。 这种采样并不意味着只有Y,Cb而没有Cr分量,这里的0说的U,V分量隔行才采样一次。比如第一行采样 4:2:0 ,第二行采样 4:0:2 ,依次类推…在这种采样方式下,每一个像素占用16bits或10bits空间。 4:1:1表示4:1的水平取样,垂直完全采样。可以参考4:2:2分量,是进一步压缩,每隔四个点才采一次U和V分量。一般是第0点采Y,U,第1点采Y,第3点采YV,第四点采Y,依次类推。 除了4:4:4采样,其余采样后信号重新还原显示后,会丢失部分UV数据,只能用相临的数据补齐,但人眼对UV不敏感,因此总体感觉损失不大。 用三个图来直观地表示采集的方式吧,以黑点表示采样该像素点的Y分量,以空心圆圈表示采用该像素点的UV分量。 先记住下面这段话,以后提取每个像素的YUV分量会用到。 YUV 4:4:4采样,每一个Y对应一组UV分量。 YUV 4:2:2采样,每两个Y共用一组UV分量。 YUV 4:2:0采样,每四个Y共用一组UV分量。 最常用Y:UV记录的比重通常1:1或2:1,DVD-Video是以YUV 4:2:0的方式记录,也就是我们俗称的I420。至于其他常见的YUV格式有YUY2、YUYV、YVYU、UYVY、AYUV、Y41P、Y411、Y211、IF09、IYUV、YV12、YVU9、YUV411、YUV420等。 4:2:2示例 如果原始数据三个像素是 Y0 U0 V0 ,Y1 U1 V1,Y2 U2 V2,Y3 U3 V3 经过4:2:2采样后,数据变成了 Y0 U0 ,Y1 V1 ,Y2 U2,Y3 V3 如果还原后,因为某一些数据丢失就补成 Y0 U0 V1,Y1 U0 V1,Y2 U2 V3 ,Y3 U3 Y2 4:1:1示例 原来四个像素为: [Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3] 存放的码流为: Y0 U0 ,Y1 , Y2 V2, Y3 还原出像素点为:[Y0 U0 V2] [Y1 U0 V2] [Y2 U0 V2] [Y3 U0 V2] 4:2:0示例 下面八个像素为:[Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3] [Y5 U5 V5] [Y6 U6 V6] [Y7U7 V7] [Y8 U8 V8] 存放的码流为: Y0 U0 ,Y1, Y2 U2, Y3 ,Y5 V5, Y6, Y7 V7, Y8 映射出的像素点为:[Y0 U0 V5] [Y1 U0 V5] [Y2 U2 V7] [Y3 U2 V7] [Y5 U0 V5] [Y6 U0 V5] [Y7U2 V7] [Y8 U2 V7] YUY2、YUYVYUY2(和YUYV)格式为每个像素保留Y分量,而UV分量在水平方向上每两个像素采样一次。一个宏像素为4个字节,实际表示2个像素。(4:2:2的意思为一个宏像素中有4个Y分量、2个U分量和2个V分量。)图像数据中YUV分量排列顺序如下:Y0 U0 Y1 V0 Y2 U2 Y3 V2 … YVYUYVYU格式跟YUY2类似,只是图像数据中YUV分量的排列顺序有所不同:Y0 V0 Y1 U0 Y2 V2 Y3 U2 … UYVYUYVY格式跟YUY2类似,只是图像数据中YUV分量的排列顺序有所不同:U0 Y0 V0 Y1 U2 Y2 V2 Y3 … AYUVAYUV格式带有一个Alpha通道,并且为每个像素都提取YUV分量,图像数据格式如下:A0 Y0 U0 V0 A1 Y1 U1 V1 … Y41P、Y411 Y41P(和Y411)格式为每个像素保留Y分量,而UV分量在水平方向上每4个像素采样一次。一个宏像素为12个字节,实际表示8个像素。图像数据中YUV分量排列顺序如下:U0 Y0 V0 Y1 U4 Y2 V4 Y3 Y4 Y5 Y6 Y8 … Y211Y211格式在水平方向上Y分量每2个像素采样一次,而UV分量每4个像素采样一次。一个宏像素为4个字节,实际表示4个像素。图像数据中YUV分量排列顺序如下:Y0 U0 Y2 V0 Y4 U4 Y6 V4 … YVU9YVU9格式为每个像素都提取Y分量,而在UV分量的提取时,首先将图像分成若干个4 x 4的宏块,然后每个宏块提取一个U分量和一个V分量。图像数据存储时,首先是整幅图像的Y分量数组,然后就跟着U分量数组,以及V分量数组。IF09格式与YVU9类似。 IYUVIYUV格式为每个像素都提取Y分量,而在UV分量的提取时,首先将图像分成若干个2 x 2的宏块,然后每个宏块提取一个U分量和一个V分量。YV12格式与IYUV类似。 YUV411、YUV420YUV411、YUV420格式多见于DV数据中,前者用于NTSC制,后者用于PAL制。YUV411为每个像素都提取Y分量,而UV分量在水平方向上每4个像素采样一次。YUV420并非V分量采样为0,而是跟YUV411相比,在水平方向上提高一倍色差采样频率,在垂直方向上以U/V间隔的方式减小一半色差采样。 YUV转UYVY格式12345678910111213141516void YUVtoUYVY(uint8_t *y_plane, uint8_t *u_plane, uint8_t *v_plane, int y_stride, int uv_stride, OUT uint8_t *pDstBuf, int width, int height){ for (int row = 0; row < height; row = row + 1) { for (int col = 0; col < width; col=col + 2) { pDstBuf[0] = u_plane[row/2 * uv_stride + col/2]; pDstBuf[1] = y_plane[row * y_stride + col]; pDstBuf[2] = v_plane[row/2 * uv_stride + col/2]; pDstBuf[3] = y_plane[row * y_stride + col + 1]; pDstBuf += 4; } }} 三、FOURCCFourCC全称Four-Character Codes,代表四字符代码 (four character code), 它是一个32位的标示符,其实就是typedef unsigned int FOURCC;是一种独立标示视频数据流格式的四字符代码。 1234567891011VC++转换方法:DWORD fccYUY2 = MAKEFOURCC('Y','U','Y','2');DWORD fccYUY2 = FCC('YUY2');DWORD fccYUY2 = '2YUY'; // Declares the FOURCC 'YUY2'.GUID:FOURCCMap fccMap(FCC('YUY2'));GUID g1 = (GUID)fccMap;//Equivalent:GUID g2 = (GUID)FOURCCMap(FCC('YUY2')); FOURCC for YUV参考官网 Packed YUV Formats Planar YUV Formats 四、YUV packed1、YUYV、YUY2(属于YUV422)相邻的2个Y共用其相邻的Cb、Cr分析,对于像素点Y’00、Y’01而言,其Cb、Cr的值均为Cb00、Cr00,其他的像素点的YUV取值依次类推。Y0 U0 Y1 V0 Y2 U2 Y3 V2 2、UYVY(属于YUV422)UYVY格式也是YUV422采样的存储格式中的一种,只不过与YUYV不同的是UV的排列顺序不一样而已,还原其每个像素点的YUV值的方法与上面一样。 U0 Y0 V0 Y1 U2 Y2 V2 Y3 五、YUV planar1、YUV422P(属于YUV422)这里,Y U V数据是分开存放的,每两个水平Y采样点,有一个Cb和一个Cr采样点,如下图: 2、YUV422 Semi-PlanarSemi 是’半‘的意思 我的理解这个半平面模式,这个格式的数据量跟YUV422 Planar的一样,但是U、V是交叉存放的,如下图: 3、YUV420P(I420、IYUV)这个格式跟YUV422 Planar 类似,但对于Cb和Cr的采样在水平和垂直方向都减少为2:1,如下图: 4、YV12、YU12(属于YUV420)YU12和YV12属于YUV420格式,也是一种Plane模式,将Y、U、V分量分别打包,依次存储。其每一个像素点的YUV数据提取遵循YUV420格式的提取方式,即4个Y分量共享一组UV。 5、NV12、NV21(属于YUV420)NV12和NV21属于YUV420格式,是一种two-plane模式,即Y和UV分为两个Plane,但是UV(CbCr)为交错存储,而不是分为三个plane。其提取方式与上一种类似,即Y’00、Y’01、Y’10、Y’11共用Cr00、Cb00 NV12","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"音视频","slug":"音视频","permalink":"http://cclk.cc/tags/音视频/"}]},{"title":"webrtc学习1-全平台编译","date":"2017-08-03T06:31:32.000Z","path":"2017/08/03/webrtc/webrtc学习1-全平台编译/","text":"一、前提因为webrtc很多依赖源是在墙外,需要翻墙工具。 二、安装depot tools是一套脚本,用于管理代码签出和审查。 参考Install depot_tools Windows git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git 设置depot_tools到PATH环境变量 Linux(Android)/Mac(iOS) git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git 把depot_tools目录加入PATH:export PATH=pwd/depot_tools:”$PATH” 三、安装依赖软件Windows 安装”Visual Studio 2015 Update 3“,其他版本都不受官方支持。 操作系统必须是Windows 7 x64及以上版本,x86操作系统都不支持。 安装VS2015时必须有下列组件: Visual C++, which will select three sub-categories including MFC Universal Windows Apps Development Tools > Tools Universal Windows Apps Development Tools > Windows 10 SDK (10.0.10586) Linux 参考后面 Android 安装Java OpenJDK1234567$ sudo apt-get install openjdk-7-jdk$ sudo update-alternatives --config javac$ sudo update-alternatives --config Java$ sudo update-alternatives --config javaws$ sudo update-alternatives --config javap$ sudo update-alternatives --config jar$ sudo update-alternatives --config jarsigner Mac(IOS) 安装最新XCode 四、同步源码参考Native Code Development 先创建源码目录12mkdir webrtc-checkoutcd webrtc-checkout Windows12fetch --nohooks webrtc #第一次拉去时使用,后续更新不需要gclient sync Linux123456export GYP_DEFINES=\"OS=linux\"fetch --nohooks webrtc_androidgclient synccd src./build/install-build-deps.sh Android123456export GYP_DEFINES=\"OS=android\"fetch --nohooks webrtc_androidgclient synccd src. build/install-build-deps-android.sh Mac123export GYP_DEFINES=\"OS=mac\"fetch --nohooks webrtc_iosgclient sync IOS123export GYP_DEFINES=\"OS=ios\"fetch --nohooks webrtc_iosgclient sync 五、Working with Release BranchesTo see available release branches, run:1git branch -r NOTICE: If you only see your local branches, you have a checkout created before our switch to Git (March 24, 2015). In that case, first run: 123cd /path/to/webrtc/srcgclient sync --with_branch_headsgit fetch origin You should now have an entry like this under [remote “origin”] in .git/config: 1fetch = +refs/branch-heads/*:refs/remotes/branch-heads/* To create a local branch tracking a remote release branch (in this example, the 43 branch): 12git checkout -b my_branch refs/remotes/branch-heads/43gclient sync 六、编译生成ninjia项目Windows/Linux123456#生成debug版ninja项目文件:gn gen out/Default#生成release版ninja项目文件:gn gen out/Default --args='is_debug=false'#清空ninja项目文件:gn clean out/Default Android12345678#使用gn生成:gn gen out/Default --args='target_os=\"android\" target_cpu=\"arm\"'#生成ARM64版:gn gen out/Default --args='target_os=\"android\" target_cpu=\"arm64\"'#生成32位 x86版:gn gen out/Default --args='target_os=\"android\" target_cpu=\"x86\"'#生成64位 x64版:gn gen out/Default --args='target_os=\"android\" target_cpu=\"x64\"' Mac12#使用gn生成:gn gen out/Debug-mac --args='target_os=\"mac\" target_cpu=\"x64\" is_component_build=false' IOS12345678#生成ARM版:gn gen out/Debug-device-arm32 --args='target_os=\"ios\" target_cpu=\"arm\" is_component_build=false'#生成ARM64版:gn gen out/Debug-device-arm64 --args='target_os=\"ios\" target_cpu=\"arm64\" is_component_build=false'#生成32位模拟器版:gn gen out/Debug-sim32 --args='target_os=\"ios\" target_cpu=\"x86\" is_component_build=false'#生成64位模拟器版:gn gen out/Debug-sim64 --args='target_os=\"ios\" target_cpu=\"x64\" is_component_build=false' 编译源码Windows/Linux/Android/Mac/IOS1ninja -C out/Default 七、备注windows编译脚本备份123456789101112131415161718192021222324set DEPOT_TOOLS_WIN_TOOLCHAIN=0mkdir webrtc-checkoutcd webrtc-checkoutWindows:fetch --nohooks webrtc #第一次gclient synccd src#生成编译ninja项目文件:#debug x86 MT is_component_build=true不支持:gn gen out/Debug \"--args=is_debug=true target_cpu=\\\"x86\\\" \"ninja -C out/Debug#release x86 MT:gn gen out/Release \"--args=is_debug=false target_cpu=\\\"x86\\\"\"ninja -C out/Release#生成vs工程,x86 release MTgn gen out/msvc --ide=\"vs2015\" \"--args=is_debug=false target_cpu=\\\"x86\\\"\"#上述生成默认mt模式 depot_tools更新失败出现类似如下错误123Ensuring CIPD client is up-to-dateGET https://chrome-infra-packages.appspot.com/_ah/api/repo/v1/instance/resolve?version=bccdb9a605037e3dd2a8a64e79e08f691a6f159d&package_name=infra%2Ftools%2Fcipd%2Fwindows-amd64Failed to fetch https://chrome-infra-packages.appspot.com/_ah/api/repo/v1/instance/resolve?version=bccdb9a605037e3dd2a8a64e79e08f691a6f159d&package_name=infra%2Ftools%2Fcipd%2Fwindows-amd64 开启ShadowSocket 访问URL返回如下内容表示可以正常翻墙 123456{ \"status\": \"SUCCESS\", \"instance_id\": \"d8a0231b483ecdf618cbfd191ea2dd49c0f9b726\", \"kind\": \"repo#resourcesItem\", \"etag\": \"\\\"pR8slN9LauG_o1VyIXSmbfx7GCI/-JA0lET_3WnwfrRbDzJz7f_ejjI\\\"\"} 设置git netsh http https代理 12345678910#Git的代理设置git config --global http.proxy http://127.0.0.1:1080git config --global https.proxy https://127.0.0.1:1080#winhttp的代理设置(需要管理员权限)netsh winhttp set proxy 127.0.0.1:1080#cipd_client代理set HTTP_PROXY=http://127.0.0.1:1080set HTTPS_PROXY=https://127.0.0.1:1080 还原代理 12345678git config --global --unset http.proxygit config --global --unset https.proxynetsh winhttp reset proxynetsh winhttp show proxyset HTTP_PROXY=set HTTPS_PROXY= gclient sync此应用无法在你电脑上运行原因是.cipd_client.exe下载错误,删除depot_tools下缓存的.cipd_client.exe文件,重新执行gclient sync。 gn参考gn","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"音视频","slug":"音视频","permalink":"http://cclk.cc/tags/音视频/"},{"name":"webrtc","slug":"webrtc","permalink":"http://cclk.cc/tags/webrtc/"}]},{"title":"Proactor","date":"2017-05-18T10:48:32.000Z","path":"2017/05/18/c++/Proactor/","text":"概述Proactor([proˈæktə(r)]、前摄器)是异步模式的网络处理器,ACE中叫做“前摄器”。先讲几个概念: 前摄器(Proactor)-异步的事件多路分离器、处理器,是核心处理类。启动后由3个线程组成(你不需要关心这三个线程,我只是让你知道一下有这回事存在)。 接受器(Acceptor)-用于服务端,监听在一个端口上,接受用户的请求。 连接器(Connector)-用于客户端,去连接远程的监听。当然,如果远程是ACE写的,就是Acceptor。 场景异步模式-即非阻塞模式。网络的传输速度一般来讲为10Mbps、100Mbps、1000Mbps。拿千兆网来说,实际的传输速度为 1000Mbps/8大概为128MB左右。我们的CPU一般为P4 3.0GHZ,如果是32位的处理器,一秒钟大概可以处理6G的字节,那么,128MB的网络速度是远远及不上处理器的速度的。网络发送数据是一位一位发 送出去的,如果CPU等在这里,发送完成函数才结束,那么,处理器浪费了大量时间在网络传输上。 操作系统提供了异步的模式来传输网络数 据,工作模式即:应用程序把要发送的数据交给操作系统,操作系统把数据放在系统缓冲区后就告诉应用程序OK了,我帮你发,应用程序该干嘛干嘛去。操作系统 发送完成后,会给应用系统一个回执,告诉应用程序:刚才那个包发送完成了! 举个例子:你有几封邮件和包裹要发,最有效率的办法是什么?你把邮件和包裹及交给总台,总台MM说,好了,你帮你发,你忙去吧!然后你去工作了。过了一 会,总台MM打电话告诉你:“刚才我叫快递公司的人来了,把你的包裹发出去了。邮局的人也来了,取走了邮件,放心好了”。同样,如果你知道今天会有包裹 来,比如你在淘宝上购物了,你能成天等在总台?你应该告诉总台MM:“今天可能有我的一个快递,你帮我收一下,晚上请你肯德基!”。MM:“看在肯得基的 面子上,帮你收了”。某个时间,MM打电话来了:“帅哥,你的包裹到了,我帮你签收了,快来拿吧。” 因为操作系统是很有效率的,所有,他在后台收发是很快的。应用程序也很简单。Proactor就是这种异步模式的。Proactor就是总台MM.","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"c++","slug":"c","permalink":"http://cclk.cc/tags/c/"},{"name":"架构","slug":"架构","permalink":"http://cclk.cc/tags/架构/"}]},{"title":"Reactor和Proact区别","date":"2017-05-18T10:47:32.000Z","path":"2017/05/18/c++/Proactor-Reactor/","text":"概述在高性能的I/O设计中,有两个比较著名的模式Reactor和Proactor模式,其中Reactor模式用于同步I/O,而Proactor运用于异步I/O操作。 背景在比较这两个模式之前,我们首先的搞明白几个概念,什么是阻塞和非阻塞,什么是同步和异步,同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪,而异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知(异步的特点就是通知)。而阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作函数的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入函数会立即返回一个状态值。 一般来说I/O模型可以分为:同步阻塞,同步非阻塞,异步阻塞,异步非阻塞IO。 同步阻塞IO在此种方式下,用户进程在发起一个IO操作以后,必须等待IO操作的完成,只有当真正完成了IO操作以后,用户进程才能运行。Java传统的IO模型属于此种方式! 同步非阻塞IO在此种方式下,用户进程发起一个IO操作以后边可返回做其它事情,但是用户进程需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问,从而引入不必要的CPU资源浪费。其中目前JAVA的NIO就属于同步非阻塞IO。 异步阻塞IO此种方式下是指应用发起一个IO操作以后,不等待内核IO操作的完成,等内核完成IO操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问IO是否完成,那么为什么说是阻塞的呢?因为此时是通过select系统调用来完成的,而select函数本身的实现方式是阻塞的,而采用select函数有个好处就是它可以同时监听多个文件句柄(如果从UNP的角度看,select属于同步操作。因为select之后,进程还需要读写数据),从而提高系统的并发性! 异步非阻塞IO在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。目前Java中还没有支持此种IO模型。 搞清楚了以上概念以后,我们再回过头来看看,Reactor模式和Proactor模式。 区别其实阻塞与非阻塞都可以理解为同步范畴下才有的概念,对于异步,就不会再去分阻塞非阻塞。对于用户进程,接到异步通知后,就直接操作进程用户态空间里的数据好了。 Reactor首先来看看Reactor模式,Reactor模式应用于同步I/O的场景。我们分别以读操作和写操作为例来看看Reactor中的具体步骤: 读取操作: 应用程序注册读就绪事件和相关联的事件处理器 事件分离器等待事件的发生 当发生读就绪事件的时候,事件分离器调用第一步注册的事件处理器 事件处理器首先执行实际的读取操作,然后根据读取到的内容进行进一步的处理 写入操作类似于读取操作,只不过第一步注册的是写就绪事件。 Proactor下面我们来看看Proactor模式中读取操作和写入操作的过程: 读取操作: 应用程序初始化一个异步读取操作,然后注册相应的事件处理器,此时事件处理器不关注读取就绪事件,而是关注读取完成事件,这是区别于Reactor的关键。 事件分离器等待读取操作完成事件 在事件分离器等待读取操作完成的时候,操作系统调用内核线程完成读取操作(异步IO都是操作系统负责将数据读写到应用传递进来的缓冲区供应用程序操作,操作系统扮演了重要角色),并将读取的内容放入用户传递过来的缓存区中。这也是区别于Reactor的一点,Proactor中,应用程序需要传递缓存区。 事件分离器捕获到读取完成事件后,激活应用程序注册的事件处理器,事件处理器直接从缓存区读取数据,而不需要进行实际的读取操作。 Proactor中写入操作和读取操作,只不过感兴趣的事件是写入完成事件。 总结从上面可以看出,Reactor和Proactor模式的主要区别就是真正的读取和写入操作是有谁来完成的,Reactor中需要应用程序自己读取或者写入数据,而Proactor模式中,应用程序不需要进行实际的读写过程,它只需要从缓存区读取或者写入即可,操作系统会读取缓存区或者写入缓存区到真正的IO设备。综上所述,同步和异步是相对于应用和内核的交互方式而言的,同步 需要主动去询问,而异步的时候内核在IO事件发生的时候通知应用程序,而阻塞和非阻塞仅仅是系统在调用系统调用的时候函数的实现方式而已。 标准的经典的 Reactor模式: 步骤 1) 等待事件 (Reactor 的工作) 步骤 2) 发”已经可读”事件发给事先注册的事件处理者或者回调 ( Reactor 要做的) 步骤 3) 读数据 (用户代码要做的) 步骤 4) 处理数据 (用户代码要做的) 模拟的Proactor模式: 步骤 1) 等待事件 (Proactor 的工作) 步骤 2) 读数据(看,这里变成成了让 Proactor 做这个事情) 步骤 3) 把数据已经准备好的消息给用户处理函数,即事件处理者(Proactor 要做的) 步骤 4) 处理数据 (用户代码要做的)","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"c++","slug":"c","permalink":"http://cclk.cc/tags/c/"},{"name":"架构","slug":"架构","permalink":"http://cclk.cc/tags/架构/"}]},{"title":"Reactor","date":"2017-05-18T10:47:32.000Z","path":"2017/05/18/c++/Reactor/","text":"概述Reactor([riˈæktə(r)]、反应器)这个词译成汉语还真没有什么合适的,很多地方叫反应器模式,但更多好像就直接叫reactor模式了,其实我觉着叫应答者模式更好理解一些。通过了解,这个模式更像一个侍卫,一直在等待你的召唤,或者叫召唤兽。并发系统常使用reactor模式,代替常用的多线程的处理方式,节省系统的资源,提高系统的吞吐量。 场景先用比较直观的方式来介绍一下这种方式的优点,通过和常用的多线程方式比较一下,可能更好理解。以一个餐饮为例,每一个人来就餐就是一个事件,他会先看一下菜单,然后点餐。就像一个网站会有很多的请求,要求服务器做一些事情。处理这些就餐事件的就需要我们的服务人员了。 在多线程处理的方式会是这样的:一个人来就餐,一个服务员去服务,然后客人会看菜单,点菜。 服务员将菜单给后厨。二个人来就餐,二个服务员去服务……五个人来就餐,五个服务员去服务…… 这个就是多线程的处理方式,一个事件到来,就会有一个线程服务。很显然这种方式在人少的情况下会有很好的用户体验,每个客人都感觉自己是VIP,专人服务的。如果餐厅一直这样同一时间最多来5个客人,这家餐厅是可以很好的服务下去的。 来了一个好消息,因为这家店的服务好,吃饭的人多了起来。同一时间会来10个客人,老板很开心,但是只有5个服务员,这样就不能一对一服务了,有些客人就要没有人管了。老板就又请了5个服务员,现在好了,又能每个人都受VIP待遇了。 越来越多的人对这家餐厅满意,客源又多了,同时来吃饭的人到了20人,老板高兴不起来了,再请服务员吧,占地方不说,还要开工钱,再请人就攒不到钱了。怎么办呢?老板想了想,10个服务员对付20个客人也是能对付过来的,服务员勤快点就好了,伺候完一个客人马上伺候另外一个,还是来得及的。综合考虑了一下,老板决定就使用10个服务人员的线程池啦~~~ 但是这样有一个比较严重的缺点就是,如果正在接受服务员服务的客人点菜很慢,其他的客人可能就要等好长时间了。有些火爆脾气的客人可能就等不了走人了。 Reactor如何处理这个问题呢:老板后来发现,客人点菜比较慢,大部服务员都在等着客人点菜,其实干的活不是太多。老板能当老板当然有点不一样的地方,终于发现了一个新的方法,那就是:当客人点菜的时候,服务员就可以去招呼其他客人了,等客人点好了菜,直接招呼一声“服务员”,马上就有个服务员过去服务。嘿嘿,然后在老板有了这个新的方法之后,就进行了一次裁员,只留了一个服务员!这就是用单个线程来做多线程的事。 实际的餐馆都是用的Reactor模式在服务。一些设计的模型其实都是从生活中来的。 Reactor模式主要是提高系统的吞吐量,在有限的资源下处理更多的事情。 在单核的机上,多线程并不能提高系统的性能,除非在有一些阻塞的情况发生。否则线程切换的开销会使处理的速度变慢。就像你一个人做两件事情,1、削一个苹果。2、切一个西瓜。那你可以一件一件的做,我想你也会一件一件的做。如果这个时候你使用多线程,一会儿削苹果,一会切西瓜,可以相像究竟是哪个速度快。这也就是说为什么在单核机上多线程来处理可能会更慢。 但当有阻碍操作发生时,多线程的优势才会显示出来,现在你有另外两件事情去做,1、削一个苹果。2、烧一壶开水。我想没有人会去做完一件再做另一件,你肯定会一边烧水,一边就把苹果削了。 理论的东西就不多讲了,请大家参考一下附件《reactor-siemens.pdf》。图比较多,E文不好也可以看懂的。","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"c++","slug":"c","permalink":"http://cclk.cc/tags/c/"},{"name":"架构","slug":"架构","permalink":"http://cclk.cc/tags/架构/"}]},{"title":"Direct3d纹理渲染播放视频c++类","date":"2017-05-17T04:01:32.000Z","path":"2017/05/17/audio-video/d3d_texture/","text":"背景Direct3d纹理渲染播放视频c++类 头文件(D3DTextureImpl.h) 12345678910111213141516171819202122232425262728293031323334353637383940#ifndef d3d_render_h__#define d3d_render_h__#include <memory>#include <windows.h>#include <d3d9.h>class D3DTextureImpl{public: D3DTextureImpl(); ~D3DTextureImpl();public: bool Create(HWND hWnd, size_t rgbWidth, size_t rgbHeight); void Destroy(); bool RenderBGRA(uint8_t *rgb, uint32_t rgbWidth, uint32_t rgbHeight);private: bool ResizeTexture(uint32_t rgbWidth, uint32_t rgbHeight); void SetSamplerState(); bool TestCooperativeLevel(uint32_t rgbWidth, uint32_t rgbHeight); bool IsNeedReCreate(size_t rgbWidth, size_t rgbHeight);private: HWND m_hWnd; size_t m_rgbWidth; size_t m_rgbHeight; size_t m_wndWidth; size_t m_wndHeight; IDirect3D9 *m_d3d; IDirect3DDevice9 *m_d3dDevice; IDirect3DVertexBuffer9 *m_d3dVertexBuffer; IDirect3DTexture9 *m_d3dTexture;};#endif // d3d_render_h__ 源文件(D3DTextureImpl.cpp)123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354#include \"D3DTextureImpl.h\"#include <glog/logger.h>#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_TEX1)struct D3dCustomVertex { float x, y, z; float u, v;};D3DTextureImpl::D3DTextureImpl() : m_hWnd(NULL) , m_rgbWidth(0) , m_rgbHeight(0) , m_wndWidth(0) , m_wndHeight(0) , m_d3d(NULL) , m_d3dDevice(NULL) , m_d3dVertexBuffer(NULL) , m_d3dTexture(NULL){}D3DTextureImpl::~D3DTextureImpl(){ Destroy();}bool D3DTextureImpl::Create(HWND hWnd, size_t rgbWidth, size_t rgbHeight){ do { Destroy(); if (NULL == hWnd) { LOG_INFO << \"hWnd is null\"; break; } else { m_hWnd = hWnd; } m_d3d = Direct3DCreate9(D3D_SDK_VERSION); if (nullptr == m_d3d) { LOG_DEBUG << \"Direct3DCreate9 faild\"; break; } D3DPRESENT_PARAMETERS d3d_params = {}; d3d_params.Windowed = TRUE; d3d_params.SwapEffect = D3DSWAPEFFECT_COPY; IDirect3DDevice9* d3d_device = NULL; HRESULT hRet = m_d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_MULTITHREADED, &d3d_params, &d3d_device); if (D3D_OK != hRet) { LOG_DEBUG << \"CreateDevice faild:\" << hRet; break; } m_d3dDevice = d3d_device; IDirect3DVertexBuffer9* vertex_buffer = NULL; const int kRectVertices = 4; hRet = m_d3dDevice->CreateVertexBuffer(kRectVertices * sizeof(D3dCustomVertex), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_MANAGED, &vertex_buffer, NULL); if (D3D_OK != hRet) { LOG_DEBUG << \"CreateVertexBuffer faild:\" << hRet; break; } m_d3dVertexBuffer = vertex_buffer; if (!ResizeTexture(rgbWidth, rgbHeight)) { LOG_DEBUG << \"ResizeTexture faild\"; break; } m_d3dDevice->Present(NULL, NULL, NULL, NULL); RECT rtWnd; ::GetClientRect(m_hWnd, &rtWnd); size_t wndWidth = abs(rtWnd.right - rtWnd.left); size_t wndHeight = abs(rtWnd.bottom - rtWnd.top); m_rgbWidth = rgbWidth; m_rgbHeight = rgbHeight; m_wndWidth = wndWidth; m_wndHeight = wndHeight; LOG_INFO << \"D3DTexture Create success\"; return true; } while (0); Destroy(); return false;}void D3DTextureImpl::Destroy(){ if (m_d3dTexture) { m_d3dTexture->Release(); m_d3dTexture = NULL; } if (m_d3dVertexBuffer) { m_d3dVertexBuffer->Release(); m_d3dVertexBuffer = NULL; } if (m_d3dDevice) { m_d3dDevice->Release(); m_d3dDevice = NULL; } if (m_d3d) { m_d3d->Release(); m_d3d = NULL; }}bool D3DTextureImpl::RenderBGRA(uint8_t *rgb, uint32_t rgbWidth, uint32_t rgbHeight){ if (IsNeedReCreate(rgbWidth, rgbHeight)) { if (!Create(m_hWnd, rgbWidth, rgbHeight)) { LOG_INFO << \"Create faild\"; return false; } } if (!TestCooperativeLevel(rgbWidth, rgbHeight)) { LOG_INFO << \"TestCooperativeLevel faild\"; return false; } D3DLOCKED_RECT lock_rect; HRESULT hr = m_d3dTexture->LockRect(0, &lock_rect, NULL, 0); if (hr != D3D_OK) { LOG_INFO << \"LockRect faild:\" << hr; return false; } //to do copy char * pDest = reinterpret_cast<char*>(lock_rect.pBits); const char * pSrc = reinterpret_cast<const char*>(rgb); int stride = lock_rect.Pitch; int pixel_w_size = rgbWidth * 32 / 8; for (uint32_t i = 0; i < rgbHeight; i++) { memcpy(pDest, pSrc, pixel_w_size); pDest += stride; pSrc += pixel_w_size; } m_d3dTexture->UnlockRect(0); //draw m_d3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(100, 100, 100), 1.0f, 0); m_d3dDevice->BeginScene(); m_d3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX); m_d3dDevice->SetStreamSource(0, m_d3dVertexBuffer, 0, sizeof(D3dCustomVertex)); m_d3dDevice->SetTexture(0, m_d3dTexture);//启用纹理 m_d3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);//利用索引缓存配合顶点缓存绘制图形 m_d3dDevice->EndScene(); m_d3dDevice->Present(NULL, NULL, NULL, NULL); return true;}bool D3DTextureImpl::ResizeTexture(size_t rgbWidth, size_t rgbHeight){ if (m_d3dTexture) { m_d3dTexture->Release(); m_d3dTexture = NULL; } IDirect3DTexture9* texture = NULL; m_d3dDevice->CreateTexture(static_cast<UINT>(rgbWidth), static_cast<UINT>(rgbHeight), 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &texture, NULL); if (nullptr == texture) { LOG_INFO << \"CreateTexture texture is null\"; return false; } else { m_d3dTexture = texture; } // Vertices for the video frame to be rendered to. static const D3dCustomVertex rect[] = { { -1.0f, -1.0f, 0.0f, 0.0f, 1.0f }, { -1.0f, 1.0f, 0.0f, 0.0f, 0.0f }, { 1.0f, -1.0f, 0.0f, 1.0f, 1.0f }, { 1.0f, 1.0f, 0.0f, 1.0f, 0.0f }, }; void* buf_data; HRESULT hr = m_d3dVertexBuffer->Lock(0, 0, &buf_data, 0); if (hr != D3D_OK) { LOG_INFO << \"m_d3dVertexBuffer Lock faild:\" << hr; return false; } memcpy(buf_data, &rect, sizeof(rect)); m_d3dVertexBuffer->Unlock(); SetSamplerState(); return true;}void D3DTextureImpl::SetSamplerState(){ IDirect3DDevice9_SetVertexShader(m_d3dDevice, NULL); IDirect3DDevice9_SetFVF(m_d3dDevice, D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1); IDirect3DDevice9_SetRenderState(m_d3dDevice, D3DRS_ZENABLE, D3DZB_FALSE); IDirect3DDevice9_SetRenderState(m_d3dDevice, D3DRS_CULLMODE, D3DCULL_NONE); IDirect3DDevice9_SetRenderState(m_d3dDevice, D3DRS_LIGHTING, FALSE); /* Enable color modulation by diffuse color */ IDirect3DDevice9_SetTextureStageState(m_d3dDevice, 0, D3DTSS_COLOROP, D3DTOP_MODULATE); IDirect3DDevice9_SetTextureStageState(m_d3dDevice, 0, D3DTSS_COLORARG1, D3DTA_TEXTURE); IDirect3DDevice9_SetTextureStageState(m_d3dDevice, 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); /* Enable alpha modulation by diffuse alpha */ IDirect3DDevice9_SetTextureStageState(m_d3dDevice, 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); IDirect3DDevice9_SetTextureStageState(m_d3dDevice, 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); IDirect3DDevice9_SetTextureStageState(m_d3dDevice, 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); /* Enable separate alpha blend function, if possible */ IDirect3DDevice9_SetRenderState(m_d3dDevice, D3DRS_SEPARATEALPHABLENDENABLE, TRUE); /* Disable second texture stage, since we're done */ IDirect3DDevice9_SetTextureStageState(m_d3dDevice, 1, D3DTSS_COLOROP, D3DTOP_DISABLE); IDirect3DDevice9_SetTextureStageState(m_d3dDevice, 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE); /* Set an identity world and view matrix */ D3DMATRIX matrix; matrix.m[0][0] = 1.0f; matrix.m[0][1] = 0.0f; matrix.m[0][2] = 0.0f; matrix.m[0][3] = 0.0f; matrix.m[1][0] = 0.0f; matrix.m[1][1] = 1.0f; matrix.m[1][2] = 0.0f; matrix.m[1][3] = 0.0f; matrix.m[2][0] = 0.0f; matrix.m[2][1] = 0.0f; matrix.m[2][2] = 1.0f; matrix.m[2][3] = 0.0f; matrix.m[3][0] = 0.0f; matrix.m[3][1] = 0.0f; matrix.m[3][2] = 0.0f; matrix.m[3][3] = 1.0f; IDirect3DDevice9_SetTransform(m_d3dDevice, D3DTS_WORLD, &matrix); IDirect3DDevice9_SetTransform(m_d3dDevice, D3DTS_VIEW, &matrix); //设置Mipmap纹理采样器参数 int SamplerIdx = 0; m_d3dDevice->SetTexture(SamplerIdx, m_d3dTexture); m_d3dDevice->SetSamplerState(SamplerIdx, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP); m_d3dDevice->SetSamplerState(SamplerIdx, D3DSAMP_ADDRESSV, D3DTADDRESS_MIRROR); m_d3dDevice->SetSamplerState(SamplerIdx, D3DSAMP_ADDRESSW, D3DTADDRESS_WRAP); m_d3dDevice->SetSamplerState(SamplerIdx, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); m_d3dDevice->SetSamplerState(SamplerIdx, D3DSAMP_MINFILTER, D3DTEXF_ANISOTROPIC); m_d3dDevice->SetSamplerState(SamplerIdx, D3DSAMP_MIPFILTER, D3DTEXF_POINT); m_d3dDevice->SetSamplerState(SamplerIdx, D3DSAMP_MIPMAPLODBIAS, 1);// Mipmap层级向高精度偏移1个层级 m_d3dDevice->SetSamplerState(SamplerIdx, D3DSAMP_MAXANISOTROPY, 4);// 最大异性采样阈值设置为4}bool D3DTextureImpl::TestCooperativeLevel(uint32_t rgbWidth, uint32_t rgbHeight){ if (NULL == m_d3dDevice) { return false; } HRESULT hState = m_d3dDevice->TestCooperativeLevel(); switch (hState) { case D3DERR_DEVICELOST://设备丢失 return false; case D3DERR_DEVICENOTRESET://设备可以恢复 m_wndHeight = 0; m_wndWidth = 0; return Create(m_hWnd, rgbWidth, rgbHeight); case D3D_OK: break; default: return false; } return true;}bool D3DTextureImpl::IsNeedReCreate(size_t rgbWidth, size_t rgbHeight){ RECT rtWnd; ::GetClientRect(m_hWnd, &rtWnd); size_t wndWidth = abs(rtWnd.right - rtWnd.left); size_t wndHeight = abs(rtWnd.bottom - rtWnd.top); if ((m_rgbWidth == rgbWidth) && (m_rgbHeight == rgbHeight) && (m_wndWidth == wndWidth) && (m_wndHeight == wndHeight) && (nullptr != m_d3d) && (nullptr != m_d3dDevice)) { return false; } m_rgbWidth = rgbWidth; m_rgbHeight = rgbHeight; m_wndWidth = wndWidth; m_wndHeight = wndHeight; return true;}","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"音视频","slug":"音视频","permalink":"http://cclk.cc/tags/音视频/"},{"name":"ffmpeg","slug":"ffmpeg","permalink":"http://cclk.cc/tags/ffmpeg/"},{"name":"direct3d","slug":"direct3d","permalink":"http://cclk.cc/tags/direct3d/"}]},{"title":"ffmpeg利用filter渲染文字","date":"2017-05-17T03:50:32.000Z","path":"2017/05/17/audio-video/ffmpeg_text_2/","text":"背景ffmpeg渲染视频文字c++类。 头文件(VideoDrawText.h) 123456789101112131415161718192021222324252627282930313233343536373839404142434445#ifndef VideoDrawText_h__#define VideoDrawText_h__#include <memory>#include <string>struct AVCodec;struct AVCodecContext;struct AVPacket;struct AVFrame;struct SwsContext;struct AVFilterGraph;struct AVFilterContext;struct AVFilterInOut;class VideoDrawText{public: VideoDrawText(); ~VideoDrawText(); AVFrame *drawText(AVFrame *frameIn, std::string text);private: bool init(int w, int h); void uninit(); std::string GetModulePath();private: AVFrame *m_frame_out; unsigned char *m_frame_buffer_out; AVFilterGraph *m_filter_graph; AVFilterContext *m_buffersrc_ctx; AVFilterContext *m_buffersink_ctx; AVFilterInOut *m_outputs; AVFilterInOut *m_inputs; int m_width; int m_height; std::string m_lastText;};#endif // VideoDrawText_h__ 源文件(VideoDrawText.cpp)#include \"VideoDrawText.h\" #include <windows.h> extern \"C\" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> #include <libavdevice/avdevice.h> #include <libavutil/common.h> #include <libavutil/avstring.h> #include <libavutil/bprint.h> #include <libavutil/time.h> #include <libavutil/timestamp.h> #include <libavutil/pixdesc.h> #include <libavutil/avassert.h> #include <libavutil/intreadwrite.h> #include <libavutil/avutil.h> #include <libavutil/imgutils.h> #include <libavutil/pixfmt.h> #include <libavfilter/buffersink.h> #include <libavfilter/avfilter.h> #include <libavutil/eval.h> #include <libavutil/parseutils.h> #include <libavfilter/avfiltergraph.h> #include <libavfilter/buffersink.h> #include <libavfilter/buffersrc.h> #include <libavutil/opt.h> #include <libavutil/imgutils.h> }; void my_logoutput(void* ptr, int level, const char* fmt, va_list vl) { #ifdef _DEBUG FILE *fp = fopen(\"my_log.txt\", \"a+\"); if (fp) { vfprintf(fp, fmt, vl); fflush(fp); fclose(fp); } #endif } VideoDrawText::VideoDrawText() : m_frame_out(nullptr) , m_frame_buffer_out(nullptr) , m_filter_graph(nullptr) , m_buffersrc_ctx(nullptr) , m_buffersink_ctx(nullptr) , m_outputs(nullptr) , m_inputs(nullptr) , m_width(0) , m_height(0) { avfilter_register_all(); av_log_set_callback(my_logoutput); } VideoDrawText::~VideoDrawText() { uninit(); } AVFrame * VideoDrawText::drawText(AVFrame *frameIn, std::string text) { if (nullptr == frameIn) { return nullptr; } //set path std::string appPath = GetModulePath(); SetCurrentDirectoryA(appPath.c_str()); if (m_width != frameIn->width || m_height != frameIn->height || m_lastText != text) { //init if (!init(frameIn->width, frameIn->height)) { std::cout << \"init faild\"; return nullptr; } m_width = frameIn->width; m_height = frameIn->height; //input int fontSize = frameIn->width / 40; char filter_descr[1024] = { 0 };// \"drawtext=text='hello world':x=100:y=100:fontsize=100:fontfile=FreeSans.ttf\"; sprintf(filter_descr, \"drawtext=text='%s':x=50:y=50:fontsize=%d:fontcolor=green:fontfile=FreeSans.ttf\", text.c_str(), fontSize); int ret = avfilter_graph_parse_ptr(m_filter_graph, filter_descr, &m_inputs, &m_outputs, NULL); if (ret < 0) { std::cout << \"avfilter_graph_parse_ptr faild:\" << ret; return nullptr; } ret = avfilter_graph_config(m_filter_graph, NULL); if (ret < 0) { std::cout << \"avfilter_graph_config faild:\" << ret; return nullptr; } } //output av_frame_unref(m_frame_out); int ret = av_buffersrc_add_frame(m_buffersrc_ctx, frameIn); if (ret < 0) { std::cout << \"av_buffersrc_add_frame faild:\" << ret; return nullptr; } /* pull filtered pictures from the filtergraph */ ret = av_buffersink_get_frame(m_buffersink_ctx, m_frame_out); if (ret < 0) { std::cout << \"av_buffersink_get_frame faild:\" << ret; return nullptr; } m_lastText = text; return m_frame_out; } bool VideoDrawText::init(int width, int height) { uninit(); m_filter_graph = avfilter_graph_alloc(); /* buffer video source: the decoded frames from the decoder will be inserted here. */ char args[512] = { 0 }; snprintf(args, sizeof(args), \"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d\", width, height, AV_PIX_FMT_YUV420P, 1, 25, 1, 1); AVFilter *buffersrc = avfilter_get_by_name(\"buffer\"); int ret = avfilter_graph_create_filter(&m_buffersrc_ctx, buffersrc, \"in\", args, NULL, m_filter_graph); if (ret < 0) { std::cout << \"avfilter_graph_create_filter faild:\" << ret; return false; } /* buffer video sink: to terminate the filter chain. */ AVFilter *buffersink = avfilter_get_by_name(\"buffersink\"); enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE }; AVBufferSinkParams *buffersink_params = av_buffersink_params_alloc(); buffersink_params->pixel_fmts = pix_fmts; ret = avfilter_graph_create_filter(&m_buffersink_ctx, buffersink, \"out\", NULL, buffersink_params, m_filter_graph); av_free(buffersink_params); if (ret < 0) { std::cout << \"avfilter_graph_create_filter faild:\" << ret; return false; } /* Endpoints for the filter graph. */ m_outputs = avfilter_inout_alloc(); m_inputs = avfilter_inout_alloc(); m_outputs->name = av_strdup(\"in\"); m_outputs->filter_ctx = m_buffersrc_ctx; m_outputs->pad_idx = 0; m_outputs->next = NULL; m_inputs->name = av_strdup(\"out\"); m_inputs->filter_ctx = m_buffersink_ctx; m_inputs->pad_idx = 0; m_inputs->next = NULL; m_frame_out = av_frame_alloc(); m_frame_buffer_out = (unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, width, height, 1)); av_image_fill_arrays(m_frame_out->data, m_frame_out->linesize, m_frame_buffer_out, AV_PIX_FMT_YUV420P, width, height, 1); return true; } void VideoDrawText::uninit() { if (m_frame_buffer_out) { av_free(m_frame_buffer_out); m_frame_buffer_out = nullptr; } if (m_frame_out) { av_frame_free(&m_frame_out); m_frame_out = nullptr; } if (m_inputs) { avfilter_inout_free(&m_inputs); m_inputs = nullptr; } if (m_outputs) { avfilter_inout_free(&m_outputs); m_outputs = nullptr; } if (m_filter_graph) { avfilter_graph_free(&m_filter_graph); m_filter_graph = nullptr; } } std::string VideoDrawText::GetModulePath() { std::string _appPath; #ifdef _WIN32 char szAppPath[MAX_PATH] = { 0 }; GetModuleFileNameA(NULL, szAppPath, MAX_PATH); (strrchr(szAppPath, '\\\\'))[0] = 0; //结尾无斜杠 //(strrchr(szAppPath, '\\\\'))[1] = 0; // 结尾有斜杠 _appPath = szAppPath; #else char szAppPath[1024] = { 0 }; int rslt = readlink(\"/proc/self/exe\", szAppPath, 1023); if (rslt < 0 || (rslt >= 1023)) { _appPath = \"\"; } else { szAppPath[rslt] = '\\0'; for (int i = rslt; i >= 0; i--) { if (szAppPath[i] == '/') { szAppPath[i] = '\\0'; _appPath = szAppPath; break; } } } #endif return _appPath; }","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"音视频","slug":"音视频","permalink":"http://cclk.cc/tags/音视频/"},{"name":"ffmpeg","slug":"ffmpeg","permalink":"http://cclk.cc/tags/ffmpeg/"}]},{"title":"ffmpeg渲染文字字体路径问题","date":"2017-05-17T03:40:32.000Z","path":"2017/05/17/audio-video/ffmpeg_text/","text":"背景windows下想在视频上写字, 使用Drawtext滤镜,但路径是个问题: 1drawtext=\"fontfile=/usr/share/fonts/truetype/freefont/FreeSerif.ttf: text='Test Text'\" 以上是官网的使用方法, 参数间用冒号分隔, 但是在windows下fontfile在C:\\WINDOWS\\FONTS\\目录下, 导致使用时总是cannot load font “C”:impossible to find a matching font”。 原因是fontfile使用的路径为linux风格。不适用于windows,windows中有冒号且使用反斜杠。 查看ffmpeg源代码,avfilter_graph_parse_ptr->parse_filter->create_filter->avfilter_init_str,参数在windows下使用时, 到冒号就自动截断了, 所以fontfile总是加载失败。 目前的解决方法是拷贝字体文件到执行文件目录下,直接使用当前文件解决,中文的话需要字体支持;最好在调用前,设置工作目录为当前目录。","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"音视频","slug":"音视频","permalink":"http://cclk.cc/tags/音视频/"},{"name":"ffmpeg","slug":"ffmpeg","permalink":"http://cclk.cc/tags/ffmpeg/"}]},{"title":"音频采样位数,采样率,比特率","date":"2017-04-18T03:01:32.000Z","path":"2017/04/18/audio-video/audio_basic/","text":"数字音频数字音频是指使用数字编码的方式,也就是使用0和1来记录音频信息,它是相对于模拟音频来说的。在CD光盘和计算机技术未出现之前都是模拟音频(如录音带),其中数字/模拟转换器简称:DAC、模拟/数字转换器简称:ADC 。 数字音频几个重要参数采样位数可以理解数字音频设备处理声音的解析度,即对声音的辨析度。就像表示颜色的位数一样(8位表示256种颜色,16位表示65536种颜色),有8位,16位,24位等。这个数值越大,解析度就越高,录制和回放的声音就越真实。 采样频率就是对声音信息1秒钟采样多少次,以记录成数字信息。如CD音频是44.1KHz采样率,它对声音以每秒44100次的频率来记录信息。原则上采样率越高,声音的质量越好。 在数字音频领域,常用的采样率有: 8,000 Hz - 电话所用采样率, 对于人的说话已经足够 11,025 Hz 22,050 Hz - 无线电广播所用采样率 32,000 Hz - miniDV 数码视频 camcorder、DAT (LP mode)所用采样率 44,100 Hz - 音频 CD, 也常用于 MPEG-1 音频(VCD, SVCD, MP3)所用采样率 47,250 Hz - Nippon Columbia (Denon)开发的世界上第一个商用 PCM 录音机所用采样率 48,000 Hz - miniDV、数字电视、DVD、DAT、电影和专业音频所用的数字声音所用采样率 50,000 Hz - 二十世纪七十年代后期出现的 3M 和 Soundstream 开发的第一款商用数字录音机所用采样率 50,400 Hz - 三菱 X-80 数字录音机所用所用采样率 96,000 或者 192,000 Hz - DVD-Audio、一些 LPCM DVD 音轨、BD-ROM(蓝光盘)音轨、和 HD-DVD (高清晰度 DVD)音轨所用所用采样率 2.8224 MHz - SACD、 索尼 和 飞利浦 联合开发的称为 Direct Stream Digital 的 1 位 sigma-delta modulation 过程所用采样率。 比特率表示单位时间(1秒)内传送的比特数bps(bit per second,位/秒)的速度。作为一种数字音乐压缩效率的参考性指标,通常使用kbps(通俗地讲就是每秒钟1024比特)作为单位。 压缩率通常指音乐文件压缩前和压缩后大小的比值,用来简单描述数字声音的压缩效率。 音频大小数字音频文件大小的计算公式为:数据量Byte=采样频率Hz ×(采样位数/8)× 声道数 × 时间(秒) 例如果采样频率为44.1kHz,分辨率为16位,立体声,录音时间为10s,符合CD音质的声音文件的大小是多少? 根据计算公式:数据量Byte=44100Hz×(16/8)×2×10s=1764KByte然后转化为相应的单位 假设音频大小为len,则一个声道数据大小为:len/声道数/(采样位数/8)","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"音视频","slug":"音视频","permalink":"http://cclk.cc/tags/音视频/"}]},{"title":"c++模块日志设计","date":"2017-04-17T03:40:32.000Z","path":"2017/04/17/c++/c++_mlog/","text":"C++ Module日志设计背景在程序中写日志是一件非常重要,但是很容易被开发人员忽视的地方。C++有挺多的日志库(如glog,log4cpp等),方便开发人员写日志。但一般这种log库考虑的是应用写log的方法。而C++作为轮子的制造者,迫切的需求是有一个轻量级的模块日志,方便开发人员在库内写日志。 在库内写日志,一般我们使用printf、std::cout等,又或者嵌入一个log模块。当调用者需要按自己的方式写log时,这些常见的方法显得无能为力。如果存在一个log模块专为C++的导出模块设计,那么它应该具有哪些功能呢? 设计方法设计前提graph LR id1[sub module log]-->id2[module log] id2-->idm[application] id3[module log]-->idm id4[module log]-->idm Module内写日志,一般有以下几个问题需要考虑: 不依赖具体log落地实现:如可以自定义输出到文件、输出到日志采集系统、输出到syslog等;还可定义如需要输出哪些log级别、插入一些traceID等;自定义输出log的格式等。 独立:各模块间日志插拔式设计,互不影响,可以选择只记录其中某一个或几个模块的log。 轻量:加入log最好不需要引入太多东西(比如需要编译,导入类等),最好是include导入就可以使用。 简单而易于扩展:定义好接口之后,不需要修改源码来实现log的多样化;最好是一次发布,基本不用更新。 能够跨语言:轮子是给各个技术栈使用,跨语言也是一个常见要求。 设计原则 只有头文件:方便直接引入,不需要额外的编译。 接口/功能简单灵活:定义好后,后续基本不需要改变。 热插拔:各模块日志独立。 实现技巧 定义2个头文件:一个为回调定义,方便外部接入时,不需要引入mlog的具体实现。 接口:通过宏方式获取文件、函数等信息;通过RAII构造初始化这些信息,析构调用log回调函数,进行日志上报。 热插拔:不使用全局变量,而使用模版+静态变量的方法,使得各Module都有自己单独的回调函数,并且避免重复定义的问题。 易用:使用stream进行日志输出,避免格式化出错。 实现 Module日志回调的定义123456789101112//mlog_def.hpp#pragma once/*** 日志回调函数原型** @file 日志所在文件** @line 日志所在代码行** @func 日志所在函数** @severity 日志级别** @context 日志内容*/typedef void(*MLogCallBack)(const char *file, int line, const char *func, int severity, const char *content); Module日志库实现1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586//mlog.hpp#pragma once#include <sstream>#include <iostream>#include <functional>#include <string>#include \"mlog_def.hpp\"/*** 用于在头文件内生成全局唯一对象*/template<typename T>class GlobalVar {public: static T VAR;};template<typename T> T GlobalVar<T>::VAR = nullptr;/*** 日志级别*/#define MLOG_DEBUG 0#define MLOG_INFO 1#define MLOG_WARN 2#define MLOG_ERROR 3#define MLOG_FATAL 4/*** mlog*/namespace mlog{ class LogMessage; /* ** 日志回调设置函数 */ static void SetMlogCallBack(MLogCallBack func) { GlobalVar<MLogCallBack>::VAR = func; }}/*** 日志回调生成类*/class mlog::LogMessage{public: LogMessage(const char* file, int line, const char* func, int severity, MLogCallBack callback) : _file(file) , _line(line) , _func(func) , _severity(severity) , _callback(callback) { } ~LogMessage() { if (_callback) { std::string content = _stream.str(); _callback(_file.c_str(), _line, _func.c_str(), _severity, content.c_str()); } } std::ostringstream &stream() { return _stream; }private: std::string _file; int _line; std::string _func; int _severity; MLogCallBack _callback; std::ostringstream _stream;};/*** 实际使用宏*/#define LOG_DEBUG mlog::LogMessage(__FILE__, __LINE__, __FUNCTION__, MLOG_DEBUG, GlobalVar<MLogCallBack>::VAR).stream()#define LOG_INFO mlog::LogMessage(__FILE__, __LINE__, __FUNCTION__, MLOG_INFO, GlobalVar<MLogCallBack>::VAR).stream()#define LOG_WARN mlog::LogMessage(__FILE__, __LINE__, __FUNCTION__, MLOG_WARN, GlobalVar<MLogCallBack>::VAR).stream()#define LOG_ERROR mlog::LogMessage(__FILE__, __LINE__, __FUNCTION__, MLOG_ERROR, GlobalVar<MLogCallBack>::VAR).stream()#define LOG_FATAL mlog::LogMessage(__FILE__, __LINE__, __FUNCTION__, MLOG_FATAL, GlobalVar<MLogCallBack>::VAR).stream() 使用首先,Module中增加一个导出函数。 1234567891011//mylibrary.h#pragma once #ifdef MYLIBRARY_EXPORTS#define MYLIBRARY_API __declspec(dllexport)#else#define MYLIBRARY_API __declspec(dllimport)#endif #include <mlog/mlog_def.hpp>MYLIBRARY_API void SetMyLibraryLogCallback(MLogCallBack logCallback); 12345678//mylibrary.cpp#include \"mylibrary.h\"#include <mlog/mlog.hpp> MYLIBRARY_API void SetMyLibraryLogCallback(MLogCallBack logCallback){ mlog::SetMlogCallBack(logCallback);} 在使用Module的程序或Module中,调用该函数设置日志回调。 1234567891011121314151617//myapplication.cpp#include <mlog/mlog_def.hpp>#include \"mylibrary.h\"void LogCallBack(const char *file, int line, const char *func, int severity, const char *content){ //日志处理,可以写,也可以干其他的} int main(int ,const char*[]){ //必要的各种初始化 SetMyLibraryLogCallback(LogCallBack); //... return 0;}","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"c++","slug":"c","permalink":"http://cclk.cc/tags/c/"}]},{"title":"c++通用单例模版类","date":"2017-04-17T02:57:32.000Z","path":"2017/04/17/c++/c++_singleton/","text":"背景c++平时在开发过程中,需要用到单例模式比较多,如果每个都需要去实现,比较麻烦,可实现一个通用的单例模版类。 PS:可以使用C++11的static快速实现单例(static在C++11里面规定是线程安全的),如: 12345MyClass & Instance(){ static MyClass my; return my;} 实现 12345678910111213141516171819202122232425262728293031323334353637383940414243444546#ifndef cc_singleton_h__#define cc_singleton_h__#include <mutex>#include <memory>namespace utils{template <typename T>class singleton{public: // 创建单例实例 template<typename ...Args> static std::shared_ptr<T> initial(Args&& ...args) { std::call_once(m_flag, [&] {m_instance = std::make_shared<T>(std::forward<Args>(args)...); }); return m_instance; } // 获取单例 static std::shared_ptr<T> get() { return m_instance; }private: singleton() = default; ~singleton() = default; singleton(const singleton &) = delete; singleton(singleton &&) = delete; singleton &operator=(const singleton &) = delete; singleton &operator=(singleton &&) = delete;private: static std::shared_ptr<T> m_instance; static std::once_flag m_flag;};template<typename T> std::once_flag singleton<T>::m_flag;template<typename T> std::shared_ptr<T> singleton<T>::m_instance;}#endif // cc_singleton_h__ 使用示例1234567891011121314151617181920212223242526272829303132333435363738#include <utils/singleton.hpp>#include <iostream>class SingleTest{public: SingleTest(int x, double y) :m_iX(x), m_dY(y){} double sum(){ return m_iX + m_dY; }private: int m_iX; double m_dY;};class SingleTestB{public: SingleTestB() :m_iX(0), m_dY(5.0){} double sum(){ return m_iX + m_dY; }private: int m_iX; double m_dY;};int main(int argc, char* argv[]){ int a = 1; double b = 3.14; utils::singleton<SingleTest>::initial(a, b); utils::singleton<SingleTest>::initial(2, 3.14); std::cout << utils::singleton<SingleTest>::get()->sum() << std::endl; utils::singleton<SingleTestB>::initial(); std::cout << utils::singleton<SingleTestB>::get()->sum() << std::endl; return 0;}","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"c++","slug":"c","permalink":"http://cclk.cc/tags/c/"}]},{"title":"c++线程安全的map","date":"2017-04-17T02:54:32.000Z","path":"2017/04/17/c++/c++_safe_map/","text":"背景std::map不是线程安全的,平时在多线程处理时,需要加锁,比较麻烦,可以简单封装,使之支持多线程安全。 实现需要手动释放资源,建议传入智能指针或者对象,这样就不需要关心资源释放问题。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980#ifndef UTILS_MAP_HPP__#define UTILS_MAP_HPP__#include <map>#include <memory>#include <mutex>namespace utils{//thread safe map, need to free data youselftemplate<typename TKey, typename TValue>class map{public: map() { } virtual ~map() { std::lock_guard<std::mutex> locker(m_mutexMap); m_map.clear(); } bool insert(const TKey &key, const TValue &value, bool cover = false) { std::lock_guard<std::mutex> locker(m_mutexMap); auto find = m_map.find(key); if (find != m_map.end() && cover) { m_map.erase(find); } auto result = m_map.insert(std::pair<TKey, TValue>(key, value)); return result.second; } void remove(const TKey &key) { std::lock_guard<std::mutex> locker(m_mutexMap); auto find = m_map.find(key); if (find != m_map.end()) { m_map.erase(find); } } bool lookup(const TKey &key, TValue &value) { std::lock_guard<std::mutex> locker(m_mutexMap); auto find = m_map.find(key); if (find != m_map.end()) { value = (*find).second; return true; } else { return false; } } int size() { std::lock_guard<std::mutex> locker(m_mutexMap); return m_map.size(); }public: std::mutex m_mutexMap; std::map<TKey, TValue> m_map;};}#endif // UTILS_MAP_HPP__ 使用示例12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758#include <utils/map.hpp>#include <iostream>class TestMapClass{public: TestMapClass(int i) { a = i; } ~TestMapClass() { }private: int a;};int main(int argc, char* argv[]){ //point { TestMapClass *p1 = new TestMapClass(1); TestMapClass *p2 = new TestMapClass(2); utils::map<std::string, TestMapClass*> MapTest; bool ret = MapTest.insert(\"1\", p1); ret = MapTest.insert(\"1\", p2); ret = MapTest.insert(\"2\", p2); TestMapClass *lRet = NULL; MapTest.remove(\"2\"); ret = MapTest.lookup(\"2\", lRet); ret = MapTest.lookup(\"1\", lRet); } //ptr { std::shared_ptr<TestMapClass> p1(new TestMapClass(1)); std::shared_ptr<TestMapClass> p2(new TestMapClass(2)); utils::map<std::string, std::shared_ptr<TestMapClass> > MapTest; bool ret = MapTest.insert(\"1\", p1); ret = MapTest.insert(\"1\", p2); ret = MapTest.insert(\"2\", p2); std::shared_ptr<TestMapClass> lRet; MapTest.remove(\"2\"); ret = MapTest.lookup(\"2\", lRet); ret = MapTest.lookup(\"1\", lRet); } return 0;}","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"c++","slug":"c","permalink":"http://cclk.cc/tags/c/"}]},{"title":"c++双队列","date":"2017-04-17T02:41:32.000Z","path":"2017/04/17/c++/c++_double_deque/","text":"背景平时在做一些数据处理中,会遇到一个读线程,一个写线程的情形,为了方便使用,可以简单封装一下线程安全的队列。 线程安全的队列 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960#ifndef double_deque_h__#define double_deque_h__#include <deque>#include <mutex>template<typename val_type>class utils_deque{public: utils_deque() {} ~utils_deque() { std::lock_guard<std::mutex> locker(m_mutex); while (!m_deque.empty()) { val_type* var = m_deque.front(); if (var) { delete var; var = NULL; } m_deque.pop_front(); } } void push(val_type *var) { std::lock_guard<std::mutex> locker(m_mutex); m_deque.push_back(var); } val_type *pop() { std::lock_guard<std::mutex> locker(m_mutex); if (m_deque.empty()) { return nullptr; } else { val_type* var = m_deque.front(); m_deque.pop_front(); return var; } } size_t size() { return m_deque.size(); }private: std::mutex m_mutex; std::deque<val_type* > m_deque;};#endif // double_deque_h__ 为了方便自动释放空间,设计只支持传入指针对象。 双队列但平时实际运用中,可能的需求是一个队列用于读,一个队列用于写;读队列用完之后会放入写队列;为了简化外部使用和自动管理,可以使用模版双队列。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182#ifndef double_deque_h__#define double_deque_h__#include <deque>#include <mutex>template<typename val_type>class utils_deque_rw{public: utils_deque_rw(utils_deque<val_type> *read, utils_deque<val_type> *write) : m_pop(read) , m_push(write) , m_var(NULL) { if (m_pop) { m_var = m_pop->pop(); } } ~utils_deque_rw() { if (m_push && m_var) { m_push->push(m_var); } } val_type* get() { return m_var; } //获取双队列总大小 int size() { if (m_pop && m_push) { if (m_var) { return m_pop->size() + m_push->size() + 1; } else { return m_pop->size() + m_push->size(); } } else { return -1; } } //重载-> 方便可以直接访问对象数据 val_type* operator->() { return m_var; } //重载A a;中if(a) typedef void(*unspecified_bool_type)(); static void unspecified_bool_true() {} operator unspecified_bool_type() const { return m_var == NULL ? 0 : unspecified_bool_true; } //重载A a;中if(!a) bool operator!() const { return m_var == NULL; }private: utils_deque<val_type> *m_pop; //出队列,需要读取的数据队列 utils_deque<val_type> *m_push; //入队列,数据用完之后放入的空闲队列 val_type* m_var;//获取到的对象数据};#endif // double_deque_h__ 使用示例定义123456789#define CACHE_SIZE 10struct data{}utils_deque<data> m_used;utils_deque<data> m_idle; 初始化12345for (size_t i = 0; i < CACHE_SIZE; ++i){ data *var = new data(); m_idle.push(var);} 读写123456789utils_deque_rw<data> rw(&m_idle, &m_used);if (!rw){ //do something}else{ //do something}","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"c++","slug":"c","permalink":"http://cclk.cc/tags/c/"}]},{"title":"c++判断类是否可用","date":"2017-04-17T01:59:32.000Z","path":"2017/04/17/c++/c++_operator/","text":"背景在c++ 中,有时候我们需要一个自定义类型能够支持 if(obj) 和 if(!obj) 之类的语法,也就是说12345678910A obj;if(obj){ //do something}if(!obj){ //do something} 这个需要在智能指针的实现中尤其明显,因为它可以保证与原生C++ 指针在用法上的一致性。明显的解决方法是重载 operator bool() 转换,但是这样问题太多,Effective C++ 里面有讨论。还有一个办法是重载 operator ! ,但是这样我们就不得不用 if(!!obj) 这样丑陋的语法来表达 if(obj) 。 解决办法参考boost12345678910111213//重载A a;中if(a)typedef void(*unspecified_bool_type)();static void unspecified_bool_true() {}operator unspecified_bool_type() const{ return m_var == NULL ? 0 : unspecified_bool_true;}//重载A a;中if(!a)bool operator!() const{ return m_var == NULL;} 解读1typedef void(*unspecified_bool_type)() 这一句申明了一个指向本类成员变量的指针的类型,声明一个指向类成员变量的指针类型的格式:类型 类名::*指针类型,不明白可以参考下指向类成员函数的指针。通过这句代码得到的信息有: unspecified_bool_type是个类型,而不是变量,由typedef得知。 unspecified_bool_type是该类的成员变量的类型,该成员变量的类型是void *。 1234operator unspecified_bool_type() const{ return m_var == NULL ? 0 : unspecified_bool_true;} operator关键字除了操作符重载外,还有另外一种用法,那就是隐式类型转换,格式如下:12operator type() {} 执行if (ptr)时候便会执行operator unspecified_bool_type() const,转嫁成判断unspecified_bool_type类型的指针是否为空。","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"c++","slug":"c","permalink":"http://cclk.cc/tags/c/"}]},{"title":"c++单一头文件全局变量导出","date":"2017-04-17T01:11:32.000Z","path":"2017/04/17/c++/include_head/","text":"场景当我们需要提供一些简单的接口时,可能只需要提供单一头文件即可(不需要静、动态库)。但如果需要存在一些全局变量需要定义时则不好处理,本文提供一种方法,通过模版初始化的特性进行导出单一头文件的全局变量。下面介绍一些现有静、动态库定义全局变量的方法。 externextern可置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量或函数时,在其它模块中寻找其定义。另外,extern也可用来进行链接指定。 防重复定义 123#ifndef XXX#define XXX#endif 这类条件编译是为了防止同一个.c文件包含同一个头文件多次。 要明白每一个.c文件最后都会编译生成对应的.obj文件的。所以两个.c文件对应的两个.obj文件都会有定义的那个全局变量的,链接的时候,链接器就会发现有定义了两个同名变量,于是就报multiple definition错误。正确的做法是:是其中一个.c文件定义这个变量,在另外一个.c文件用 1extern int g_var; 声明,这就可以在两个.c都使用这个变量了。 结论由于我们的需求是导出单一的头文件,就无法使用extern方法;使用防重复定义的方法后,导出后被多个cpp引用的话也是会报编译错误,所以上述方法无法解决我们的问题。 模版我们可以根据模版的特性,编译时才会生成确定对象,用以解决这个问题。 模板实例化 编译器使用模板,通过更换模板参数来创建数据类型。这个过程就是模板实例化(Instantiation)。 从模板类创建得到的类型称之为特例(specialization)。 模板实例化取决于编译器能够找到可用代码来创建特例(称之为实例化要素,point of instantiation)。 要创建特例,编译器不但要看到模板的声明,还要看到模板的定义。 模板实例化过程是迟钝的,即只能用函数的定义来实现实例化。 实现12345template<typename T>class GlobalVar{public: static T VAR;}; 我们可以在一个编译单元中使用如下代码为变量赋值:1template<typename T> T GlobalVar<T>::VAR = nullptr; 而在另一个编译单元中读取该变量:1std::cout << GlobalVar<XXX>::VAR << std::endl; 扩展上述实现只是单一参数,如果需要多个参数,可以增加模版的参数个数来进行。12345678template<typename T, int n>class GlobalVar{public: static T VAR;};template<typename T, int n> T GlobalVar<T, 1>::VAR = 12345;std::cout << GlobalVar<int, 1>::VAR << std::endl; 详细解释在这里,我们利用了C++模板的一个特性——编译器保证具有相同模板参数的模板只实例化一次。也就是说,具有相同模板参数的GlobalVar中的静态变量var只被实例化一次。此时,以上方法定义的全局变量在视觉上,不大像全局变量。但是大家可以使用宏定义等技巧来把它变得更像一点。","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"},{"name":"c++","slug":"c","permalink":"http://cclk.cc/tags/c/"}]},{"title":"开源协议及常见开源软件使用的协议类型","date":"2015-05-14T01:24:32.000Z","path":"2015/05/14/c++/open_source/","text":"一、GPLGPL,是General Public License的缩写,是一份GNU通用公共授权非正式的中文翻译。它并非由自由软件基金会所发表,亦非使用GNU通用公共授权的软件的法定发布条款─直有GNU通用公共授权英文原文的版本始具有此等效力。 使用只要你用了任何该协议的库、甚至是一段代码,那么你的整个程序,不管以何种方式链接,都必须全部使用GPL协议、并遵循该协议开源。商业软件公司一般禁用GPL代码,但可以使用GPL的可执行文件和应用程序。 商业软件商业软件不能使用GPL协议的代码。 常见开源软件 Linux MySQL GIT OpenJDK GCC 二、AGPLAGPL是GPL的一个补充, 在GPL的基础上加了一些限制。原有的GPL协议,由于现在网络服务公司兴起(如:Google)产生了一定的漏洞,比如使用GPL的自由软件,但是并不发布与网络之中,则可以自由的使用GPL协议却不开源自己私有的解决方案。AGPL则增加了对此做法的约束。 AGPL这个协议的制定是为了避免一个GPL/LGPL协议中的漏洞,称之为 Web Service Loophole。这主要是由于 GPL是针对传统的软件分发模式的商业模式(以微软为代表), 如果你使用的GPL的代码作为基础完成你自己的软件,如果你要分发你的软件,你的软件必须也是GPL的。随着以Google为代表的软件作为服务的互联网公司的兴起,它们的“不分发软件,为客户提供网络服务”的商业模式就不受GPL协议的约束,所以Google公司在构筑他的搜索引擎的时候可以随心所欲的拿现有的GPL协议的开源代码,无需开源他的修改成果。AGPL协议在GPL协议的基础上加上了这个约束。 GPL的约束生效的前提是“发布”软件,即使用了GPL成分的软件通过互联网或光盘release软件,就必需明示地附上源代码,并且源代码和产品也受GPL保护。这样如果不“发布”就可以不受约束了。比如使用GPL组件编写一个Web系统,不发布这个系统,但是用这个系统在线提供服务,同时不开源系统代码。 使用即Affero GPL,是GPL的更严格版本。只要你用了任何该协议的库、甚至是一段代码,那么运行时和它相关的所有软件、包括通过网络联系的所有软件,必须全部遵循该协议开源。据律师说,它的要求范围连硬件都包括。所以,一般公司通常禁用任何AGPL代码。 商业软件商业软件不能使用AGPL协议的代码。 三、LGPLLGPL是 GNU Lesser General Public License (GNU 宽通用公共许可证)的缩写形式,旧称GNU Library General Public License (GNU 库通用公共许可证),后来改称作Lesser GPL,即为更宽松的GPL,在宽松程度上与BSD,Apache,XFree86 许可证相似。GPL(General Public License)和LGPL是GNU的两种License。越来越多的自由软件(Free Software)使用GPL作为其授权声明,如果对GPL一点都不了解,有可能在使用自由软件时违反GPL的授权,恐怕会有被起诉的风险。所以任何公司在使用自由软件之前应该保证在LGPL或其它GPL变种的授权下。 使用就是GPL针对动态链接库放松要求了的版本,即允许非LGPL的代码动态链接到LGPL的模块。注意:不可以静态链接,否则你的代码也必须用LGPL协议开源。 商业软件 商业软件可以使用,但不能修改LGPL协议的代码。 LGPL 允许商业软件通过类库引用(link)方式使用LGPL类库而不需要开源商业软件的代码。 采用LGPL的代码,一般情况下它本身就是一个第三方库(别忘了LGPL最早的名字就是Library GPL),这时候开发人员仅仅用到了它的功能,而没有对库本身进行任何修改,那么开发人员也不必公布自己的商业源代码。但是如果你修改了这个库的代码,那么对不起,你修改的代码必须全部开源,并且协议也是LGPL,但除了库源码之外的商业代码,仍不必公布。 如果以动态库方式调用,允许闭源发布商业软件,但是不得发布基于LGPL授权本身的产品(防止商业化,违反开源精神)。如果使用静态库方式链接则退化为GPL授权。 如果以静态库方式,则必须遵循以下规则: 你必须在你的文档中说明,你的程序中使用了 LGPL 库,并且说明这个库是基于 LGPL 发布的; 你必须在你的应用程序发布中包含一份 LGPL协议,通常就是那个文本文件; 你必须开放使用了 LGPL 库代码的所有代码,例如某些封装器。但是,其他使用这些封装器的代码就不需要开放了;简单来说,LGPL协议要求,如果你的类使用了LGPL库的代码,那么必须把这个类开源。例如,如果你的程序 app.exe 每个源文件都使用了 LGPL 库的代码,那么你的所有源代码都要开源。为了避免这种情况,我们通常编写一个封装器,把 LGPL库的代码封装起来,这样就只需要开放这个封装器的代码,而其他使用了这个封装器的代码就不需要开放。 你必须包含你的应用程序的余下部分的目标文件(通常就是我们所说的 .o 等等),或者是其他等价的文件。源代码并不是必须的。那些使用了封装器的程序不需要开源,但是你必须把你编译的那些中间文件开放出来,Windows 下就是那些 .o 文件。 常见开源软件 7-Zip FFmpeg 四、ApacheApache Licence是著名的非盈利开源组织Apache采用的协议。该协议和BSD类似,同样鼓励代码共享和尊重原作者的著作权,同样允许代码修改,再发布(作为开源或商业软件)。需要满足的条件也和BSD类似: 需要给代码的用户一份Apache Licence如果你修改了代码,需要在被修改的文件中说明。在延伸的代码中(修改和有源代码衍生的代码中)需要带有原来代码中的协议,商标,专利声明和其他原来作者规定需要包含的说明。如果再发布的产品中包含一个Notice文件,则在Notice文件中需要带有Apache Licence。你可以在Notice中增加自己的许可,但不可以表现为对Apache Licence构成更改。Apache Licence也是对商业应用友好的许可。使用者也可以在需要的时候修改代码来满足需要并作为开源或商业产品发布/销售。 使用修改版本必须保持其原始版权声明;修改过的文件要标明改动。 商业软件商业软件可以使用,也可以修改使用Apache协议的代码。 常见开源软件 Tomcat Subversion 五、BSD2使用修改版本必须保持其原始版权声明。 商业软件商业软件可以使用,也可以修改使用BSD2协议的代码。 六、BSD3使用修改版本必须保持其原始版权声明。未经许可不得使用原作者或公司的名字做宣传。 商业软件商业软件可以使用,也可以修改使用BSD3协议的代码。 七、MIT使用修改版本必须保持其原始版权声明。 商业软件商业软件可以使用,也可以修改使用MIT协议的代码。 八、MIT使用修改版本必须保持其原始版权声明。如果发布了编译后的可执行文件,那么必须让对方可以取得MPL协议下程序的源码。 商业软件商业软件可以使用,也可以修改MPL协议的代码,但修改后的代码版权归软件的发起者。 九、常见c++开源类库 库 协议 商业软件是否可以使用 boost boost许可协议 可以使用 tinyxml zlib/libpng许可协议 可以使用 jsonCPP MIT 可以使用 jrtplib 无(官方有协议) 可以使用(you can use the library in any way you like) log4cpp LGPL 可以使用(动态库方式,0.2.1以上版本) ffmpeg GPL/LGPL 可以使用(动态库方式);默认编译模块(不包含GPL,不支持H264) libx264 GPL 不可以 vlc GPL 不可以 qt LGPL 可以使用(动态库方式,4.5以上版本) live555 LGPL 可以使用(动态库方式) librtmp LGPL 可以使用(动态库方式) curl 无(官方有协议) 可以使用 protobuf New BSD 可以使用 openssl Apache 可以使用 glog google 可以使用","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"}]},{"title":"github+hexo搭建个人博客","date":"2015-04-13T06:39:32.000Z","path":"2015/04/13/common/github_hexo/","text":"前言使用github创建的博客是属于静态网站博客,也就是把写好的文章生成HTML网页,然后上传到github网站,显示的也就是HTML网页,所以加载速度会很快。 安装申请Github申请帐号这里我们就不多讲了,小伙伴们可以点击Github,进入官网进行注册。 创建仓库登录账号后,在Github页面的右上方选择New repository进行仓库的创建。在仓库名字输入框中输入: 1你想要的名字.github.io 然后点击Create repository即可。 生成添加秘钥在终端(Terminal)输入:1ssh-keygen -t rsa -C "Github的注册邮箱地址" 一路enter过来就好,待秘钥生成完毕,会得到两个文件id_rsa和id_rsa.pub,用带格式的记事本打开id_rsa.pub,Ctrl + A复制里面的所有内容,然后进入github-ssh将复制的内容粘贴到Key的输入框,随便写好Title里面的内容,点击Add SSH key按钮即可。 申请域名(非必须)为了方便用其他域名访问blog,也可以使用默认的名字,如xxx.github.io也可以访问。 安装git作用:把本地的hexo内容提交到github上去。Git官网下载相应平台的最新版本,一路安装即可。Git的下载与安装 安装nodejs作用:用来生成静态页面的Node.js官网下载相应平台的最新版本,一路安装即可。Node.js 安装配置 安装Hexo安装1234567$ cd d:/hexo$ npm install hexo-cli -g$ hexo init blog$ cd blog$ npm install$ hexo g # 或者hexo generate$ hexo s # 或者hexo server,可以在http://localhost:4000/ 查看 修改网站相关信息123456title: inerdstacksubtitle: the stack of it nerdsdescription: start from zeroauthor: inerdstacklanguage: zh-CNtimezone: Asia/Shanghai 注意:每一项的填写,其:后面都要保留一个空格,下同。 配置统一资源定位符(个人域名)1url: http://cclk.cc 部署配置1234deploy: type: git repo: https://github.com/cclk/cclk.github.io.git branch: master 部署然后执行命令(为了hexo支持git):1npm install hexo-deployer-git --save 每次部署的步骤,可按以下三步来进行。123hexo cleanhexo generatehexo deploy 一些常用命令:12345678hexo new "postName" #新建文章hexo new page "pageName" #新建页面hexo generate #生成静态页面至public目录hexo server #开启预览访问端口(默认端口4000,'ctrl + c'关闭server)hexo s -p 5000 #开启预览访问端口(端口5000)hexo deploy #将.deploy目录部署到GitHubhexo help # 查看帮助hexo version #查看Hexo的版本 一些基本路径文章在source/_posts, 文章支持Markdown语法,可以使用一些MarkDown渲染工具。如果想修改头像可以直接在主题的_config.yml文件里面修改,友情链接,之类的都在这里。 安装Hexo主题-yilia介绍下hexo的主题yilia,风格布局都很好。 安装在hexo的工作目录12cd d:/hexogit clone https://github.com/litten/hexo-theme-yilia.git themes/yilia 然后打开Hexo文件夹下面的_config.yml文件,修改里面的theme为yilia。可以在themes/yilia内找到_config.yml文件,进行头像等定制。 绑定域名1、在source文件夹中新建一个CNAME文件(无后缀名),然后用文本编辑器打开,在首行添加你的网站域名,如xxxx.com,注意前面没有 http:// ,(前面可以加www,就可以用www.xxx.com访问),然后使用hexo g && hexo d上传部署。2、在域名解析提供商 (1)先添加一个CNAME,主机记录写@,后面记录值写上你的xxxx.github.io (2)再添加一个CNAME,主机记录写www,后面记录值也是xxxx.github.io这样别人用www和不用www都能访问你的网站(其实www的方式,会先解析成 http://xxxx.github.io 然后根据CNAME再变成 http://xxx.com 即中间是经过一次转换的)。上面,我们用的是CNAME别名记录,也有人使用A记录,后面的记录值是写github page里面的ip地址,但有时候IP地址会更改,导致最后解析不正确,所以还是推荐用CNAME别名记录要好些,不建议用IP。3、等十分钟左右,刷新浏览器,用你自己域名访问下试试 npm速度慢的问题可以使用国内的npm镜像,如淘宝npm hexo支持流程图12cd blogyarn add hexo-filter-mermaid-diagrams 具体见:hexo-filter-mermaid-diagrams","tags":[{"name":"blog","slug":"blog","permalink":"http://cclk.cc/tags/blog/"}]}]