【移动开】关于视频直播技术,你想如果了解的还在这边了(三)编码和包裹

有关直播的技艺文章未掉,成网之匪多。我们拿因此七首稿子,更系统化地介绍这大热的视频直播各环节的关键技术,帮助视频直播创业者们又宏观、深入地了解视频直播技术,更好地技术选型。

倘若说书籍是人类前行的台阶,那么精彩之开源代码就是程序员提升的桥梁。研读源码可以学中的框架和模式,
代码技巧,
算法等,然后连总结下,最终这些会化温馨的东西,编程水平自吧加强了。

视频编码是视频直播技术多元文章的老三篇,是按照系列一个大重大的局部,是挪开必修的功底教程,本篇文章由理论到实施一网自尽主流编码器。

FBKVOController是Facebook开源之接口设计优雅的KVO框架。笔者研读之后确实受益匪浅,本着学以致用的尺度,笔者借鉴其接口设计之方式贯彻了扳平模仿完整的小红点(推送消息)解决方案RJBadgeKit,
有趣味的同学可以参见一下。

假设拿全路流媒体比喻成一个物流体系,那么编解码就是中配货及装货的长河,这个过程充分重大,它的速度与削减比对物流体系的意义十分充分,影响物流系统的共同体进度及财力。同样,对流媒体传输来说,编码为要命重大,它的编码性能、编码速度与编码压缩比会直接影响整个流媒体传输的用户体验及传导成本。

由于目前已经发生众多有关FBKVOController源码分析的博文,本文会尝试从另外一个角度,以提炼和剖析具体知识点的法来总结FBKVOController中我们可借鉴与读书之地方。

论系列文章大纲之类,想复习之前文章的第一手点击上链接:

宏定义

一般性在增长观察者的时节都急需指定一个观路径(keyPath),
这个路子是直为字符串的点子供的,比如我们发个类RJPhoto的对象photo,
需要着眼其的name路径:

[self.KVOController observe:photo keyPath:@"name"];

要是字符串拼写错误,或者被observe的对象没name夫特性,编译器并无会见报错,只有等交运行时才见面发现题目。我们来拘禁下FBKVOController是怎么通过宏定义来解决这个题材之:

#define FBKVOKeyPath(KEYPATH) \
@(((void)(NO && ((void)KEYPATH, NO)), \
({ const char *fbkvokeypath = strchr(#KEYPATH, '.'); NSCAssert(fbkvokeypath, @"Provided key path is invalid."); fbkvokeypath + 1; })))

#define FBKVOClassKeyPath(CLASS, KEYPATH) \
@(((void)(NO && ((void)((CLASS *)(nil)).KEYPATH, NO)), #KEYPATH))

产生矣立即点儿单特大,被观察者的keyPath可以经过宏传入,其利益在让该宏会进行编译检查以及代码提示,如果keyPath莫存或者拼写错误,会唤起错误。

[self.KVOController observe:photo keyPath:FBKVOKeyPath(photo.name)];
[self.KVOController observe:photo keyPath:FBKVOClassKeyPath(RJPhoto, name)];

上面的宏是怎么就编译检查和代码提示的呢?我们先分析第二只相对比较复杂的巨FBKVOClassKeyPath,
其整体是一个C语言的逗号表达式,逗号表达式的格式: e.g.
int a = (b, c);逗号表达式取后面的值,故而a将让赋值成c,
此时b在赋值运算被即使被忽略了,没有叫应用,所以编译器会叫闹警示,为了破除这warning我们用以b前面加上(void)做只门类强转操作。

逗号表达式的前项和NO进行了同操作,这个关键是为吃编译器忽略第一独价,因为咱们真的赋值的是表达式后面的价值。预编译的早晚看见了NO,
就会快速的跳过判断标准。我猜想你看到这儿肯定会奇怪了,既然要不经意,那也甚还要因此个逗号表达式呢,直接赋值不纵吓了?

此地要是对传播的首先只参数CLASS的目标(CLASS *)(nil)跟次独正使输入的KEYPATH做了.操作,这为正是为何输入第二独参数时编辑器会为有是的代码提示(只要是作表达式的同一片,
Xcode自动会提示)。如果传入的KEYPATH不是CLASS对象的性质,那么(CLASS *)(nil).KEYPATH不怕不是一个法定的表达式,所以当编译就无见面由此了。

FBKVOKeyPath接受一个参数,前半段和地方是千篇一律的,不同之凡逗号表达式的晚一样段落strchr(# photo.name, '.') + 1,
函数strchar凡是C语言中的函数,用来寻觅某字符在字符串中首糟出现的岗位,这里用来当photo.name(注意眼前加了#字符串化)中查找.起的位置,再添加1纵使是返回.后面keyPath的地点了。也尽管是strchr('photo.name', '.')回来的凡一个C字符串,这个字符串从找到'photo.name'中为'.'的字符开始为后,即'name'.

立边还为此到了断言宏NSCAssert(x, y), xBOOL值, y也字符串类型,
xNO常产生断言退出并打印y字符串内容.
需要留意的是NSCAssert当C语言函数下以,
NSAssert尽管是Objective-C函数下下

关于宏定义的详尽分解与端所陈述之接近宏定义的用及分析,可以参见笔者之博文Hello,
宏定义魔法世界。

(一)采集

自释放

FBKVOController通过由释放的编制来兑现observer的机关移除,具体来说就是给observer添加一个FBKVOController的积极分子变量,比如:

#import "RJViewController.h"
#import "KVOController.h"

@interface RJViewController ()

@property (nonatomic, strong) FBKVOController *kvoController;

@end

@implementation RJViewController

- (instancetype)init
{
  self = [super init];
  if (nil != self) {
      _kvoController = [FBKVOController controllerWithObserver:self];
  }
  return self;
}

观察者RJViewController概念了一个FBKVOController的分子变量kvoController,
RJViewController放活后,其成员变量kvoController否会见相应释放,FBKVO自动移除观察者的trick就是于FBKVOControllerdealloc里面做remove
observer的操作。

(二)处理

初始化

咱俩事先来瞧FBKVOController是怎提供初始化接口的:

+ (instancetype)controllerWithObserver:(nullable id)observer;
- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithObserver:(nullable id)observer;

凡有3只初始化函数,其中第一独跟老三个也方便初始化函数(convenience
initializer),
第二单上加了NS_DESIGNATED_INITIALIZER巨,为指定初始化函数(designated
initializer).

初始化接口的平整:
a) 指定初始化方法必须调用父类的指定初始化方法
b)
便利初始化方法必须调用其他的初始化方法,直到最终对指定初始化方法
c) 具有指定初始化方法的子类必须贯彻所有父类的指定初始化方法

知晓了由释放的原理,初始化的规则就是挺肯定了,FBKVOController必须作为observer的成员变量是。那如若要是用方忽视了是规则或者不思量这么繁琐,有无出更简单的艺术呢?有!我们来拘禁下FBKVO是怎提供最简化convenience
initializer的:

@interface NSObject (FBKVOController)

@property (nonatomic, strong) FBKVOController *KVOController;
@property (nonatomic, strong) FBKVOController *KVOControllerNonRetaining;

@end

FBKVOController创建了NSObject的Category,
通过AssociateObject给NSObject提供一个Retain和nonRetain的KVOController(这里其实也是于成员变量KVOController的Get函数里面调用了+ controllerWithObserver艺术)。所以任意observer都好直接调用observer.KVOController来动态变化一个FBKVOController对象,非常便利!

至这时候看起初始化接口就大完整了,但类似还有个问题,万一使用方不循套路直接来个网默认的初始化函数[[FBKVOController alloc] init]或者[FBKVOController new]那Observer岂不是不怕无了。怎么开才能够唤醒要用方不要调用系统的初始化函数呢?

/**
 @abstract Allocates memory and initializes a new instance into it.
 @warning This method is unavaialble. Please use `controllerWithObserver:` instead.
 */
- (instancetype)init NS_UNAVAILABLE;

+ (instancetype)new NS_UNAVAILABLE;

答案就是是当当下片独默认初始化函数后面长NS_UNAVAILABLE庞大,这样只要采用方误用了系默认的初始化函数时会见让闹警示,提醒他该用模块指定的初始化接口方法。

(三)编码和包裹

NSHashTable & NSMapTable

NSHashTable可以知晓呢再次常见意义上的NSMutableSet,
与后者相比NSMapTable主要出如下特点:

  • NSHashTable是可变的, 没有不可变版本
  • 足死引用所蕴藏的因素, 当元素释放后会自动为移除
  • 好当添加元素的早晚复制元素后再也存

以及NSMutableSet相同之处(与NSMutableArray不同之处)则是:

  • 素还是无序存放的
  • 根据hashisEqual来对素进行比
  • 未见面存放相同之要素

至于对比,我们如果先区分==运算符和isEqual方法:

UIColor *color1 = [UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:1.0];
UIColor *color2 = [UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:1.0];

于上头的演示中color1 == color2返回false,
[color1 isEqual:color2]倒是返回true,
原因在==举凡一直的指针比较,显然color1和color2的地点是殊的,而isEqual则是判定该颜色内容是否一律。

看似的尚连NSString isEqualToString / NSDate isEqualToDate

回去NSHashTable中来,
NSHashTable可以任意的囤指针并且采用指针的唯一性来拓展hash同一性检查(检查成员元素是否来还)和对比操作(isEqual),
当然我们吧堪重写hash/isEqual方法来设定元素对比和齐的规则(其实isEqual是NSObject定义之Protocol).
我们来拘禁下面是示例:

@interface Person : NSObject

@property (nonatomic,   copy) NSString *name;
@property (nonatomic, strong) NSDate   *birthday;

+ (instancetype)personWithName:(NSString *)name birthday:(NSDate *)date;

@end

咱们定义一个Person类,其包括name和birthday两只特性,在我们的正常认知下,如果个别个Persion对象的立片单特性是一模一样的,那么她们就同一个丁,所以类似上面UIColor的例子,我们得再行写下Person的isEqual函数:

- (BOOL)isEqual:(id)object
 {
    if (nil == object) {
        return NO;
    }
    if (self == object) {
        return YES;
    }
    if (![object isKindOfClass:[Person class]]) {
        return NO;
    }    
    return [self isEqualToPerson:(Person *)object];
}

- (BOOL)isEqualToPerson:(Person *)person
 {
    if (!person) return NO;

    BOOL haveEqualNames     = (!self.name && !person.name) || [self.name isEqualToString:person.name];
    BOOL haveEqualBirthdays = (!self.birthday && !person.birthday) || [self.birthday isEqualToDate:person.birthday];

    return haveEqualNames && haveEqualBirthdays;
}

这里的isEqual函数的兑现分为四步,也是咱们推荐的best pratice:

  1. 看清目标是否也空
  2. 判断是否同样对象(内存地址是否等)
  3. 比方无是与一个class那必然不是平等对象
  4. 判断目标的各属性值是否等于

Person *person1 = [Person personWithName:@"Ryan Jin" birthday:self.date];
Person *person2 = [Person personWithName:@"Ryan Jin" birthday:self.date];

当今一经判断[person1 isEqual person2]便是回true了,不过怎么感觉缺少了点啊,
hash好像还不曾因此到呀,难道不待重写hash方法吗?

答案当然是要,当成员给投入到NSHashTable(也包括NSSet)中经常,会叫分配一个hash值,以标识该成员在汇聚中之岗位,通过者职务标识可以极大的晋级成员查找的频率(这为是怎么NSHashTable
查找元素的速会急忙为NSArray).

出于NSHashTable/NSSet在添加元素的时段会便行判等操作,当某个元素就有时时未会见另行添加,这个判等的操作包括个别步:

  1. 少只分子的hash值是否当,如无齐则这判断也歧因素
  2. 若hash值相等,则再判断isEqual是否回一致

单来些许独元素的hashisEqual还为同的状态下才看清也同样对象。好了,明白了此标准,我们来又写下Person的hash方法:

- (NSUInteger)hash {
    return [self.name hash] ^ [self.birthday hash]; // best practice
}

鉴于系统的NSString和NSDate在内容一律之图景下会回去相同的hash值,所以马上边的超级实践是返回各国属性之各或运算。这边欲专注的是无能够大概的回[super hash],
因为默认的hash值为该对象的内存地址,所以地方的person1person2它们的[super hash]凡殊的,所以会于判定为歧之元素,而我们怀念使落实之是当Person的各属性一致的时候它就是为同一元素。

NSHashTable/NSSet在补偿加元素和判有元素是否是(member:/containsObject:)时会见调用hash方法,
另外NSDictionary在物色key时(key为非字符串对象),
也会见使hash值来提高查找效率

俺们来拘禁下FBKVO里面所以到NSHashTable地方:

NSHashTable *infos = [[NSHashTable alloc] initWithOptions:NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];

此处初始化了一个NSHashTable,
存放类型也NSPointerFunctionsWeakMemory就算死持有成员元素都当元素释放后活动从NSHashTable移除。另外,判等档也NSPointerFunctionsObjectPointerPersonality哪怕直接行使指针地址是否当来判定。如果类型设置也NSPointerFunctionsObjectPersonality虽然使地方所讲述hash和isEqual来判定。

FBKVO这边下直接指针地址进行元素于的由来是单例_FBKVOSharedControllerinfos其间所存放的_FBKVOInfo都是从FBKVOController传过来的,已经通过了判等操作,不会见现出同之目标,所以_infos拍卖这些_FBKVOInfo素直接用指针比较就是吓了,没必要再错过调用hashisEqual方法。另外NSPointerFunctionsWeakMemory设置是为在_FBKVOInfo释放后活动从_infos中间移除它,
_FBKVOInfo都非存了,放在中间为无意义了。从这边可以看出FBKVO设计之真正怪细。

NSMapTable可以掌握为还广意义上之NSMutableDictionary,
其各特色和NSHashTable基本相同:

  • NSMapTable是可变的, 没有不可变版本
  • 好去世引用持有keys和values, 当key或value释放后存储的实体会吃移除
  • NSMapTable可以于增长value的时针对value进行复制

NSMapTable *keyToObjectMapping = [NSMapTable mapTableWithKeyOptions:NSMapTableCopyIn
                                                       valueOptions:NSMapTableStrongMemory];

若是仍点这么设置NSMapTable会和NSMutableDictionary用起了一样: 复制
key,
并对其的object引用计数加一。同样,我们啊来拘禁下FBKVO使用NSMapTable的地方:

- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved
{
  self = [super init];
  if (nil != self) {
    _observer = observer;
    NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;
    _objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
  }
  return self;
}

即边定义了一个_objectInfosMap: key为受考察的目标,
value则为寄放着_FBKVOInfo的NSMutableSet,
这边当初始化的下长了retainObserved变量用来号是否将key强引用,具体调用示例如下:

[self.KVOController observe:self.photos                        
                    keyPath:@"count"
                    options:NSKeyValueObservingOptionNew
                      block:^(id observer, id object, NSDictionary *change) {
    // observer -> RJViewController -> __weak self
    // object   -> self.photos    
    // change   -> NSKeyValueChangeKey + FBKVONotificationKeyPathKey    
}];

默认情况下本着key(被观察的目标)也便是当下边的self.photos做强引用,但是如果我们observe的目标也self本身,那么是匪可知举行强引用持有的,否则便循环引用了。所以我们当这个情景下需要retainObserved传入NO,
这也是胡NSObject+FBKVOController会有一个KVOController和KVOControllerNonRetaining了。

至于_objectInfoMap的value,
因为是NSMutableSet所以直接使用默认的大引用持有,这边大家可能有问题,为什么value值又下了NSMutableSet而未用NSHashTable呢?原因是此处并不需要弱引用持有各个_FBKVOInfo对象,而且多数动静下利用NSMutableSet更加惠及,比如NSHashTable就无enumerateObjectsUsingBlock的枚举方法。而NSSet相较NSArray的分别是前者无序存放,且下hash查找元素效率快,但是后者于前者上加元素的快慢快多,所以当选择采取谁容器的时候需要根据具体情况来挑选。

FBKVO对_FBKVOInfo的hash和isEqual进行了重写,以keyPath来展开判等。所以就边对value设置也NSPointerFunctionsObjectPersonality因为hash/isEqual进行判断,而key值(被观察者)则装也NSPointerFunctionsObjectPointerPersonality直坐指针地址做判断。

最后咱们一直引用Mattt大神对于什么时用NSMapTable什么时用NSDictionary的证明来收就同稍微节:

As always, it’s important to remember that programming is not about
being clever: always approach a problem from the highest viable level
of abstraction. NSSet and NSDictionary are great classes. For 99% of
problems, they are undoubtedly the correct tool for the job. If,
however, your problem has any of the particular memory management
constraints described above, then NSHashTable & NSMapTable may be
worth a look.

(四)推流和传导

循环引用

咱们解,使用block的下最容易出现循环引用,通常使用方需要以block内部团结声明一个weak化的self来避免此题材。那起没起艺术看去就无异步呢?是的,FBKVO在接口设计之早晚也设想到了之题材,解决方法是以block回调接口增加一个observer参数,而这个observer于FBKVOController内部做了weak化处理,在方示例中,id observer就观察者(即RJViewController *observer),也就是初始化接口时传出的self,
所以在block内部一直利用[observer doSomething]来代替[self doSomething]即可避免循环引用的问题。

FBKVO避免循环引用的规划真正怪精细,我们来就看下面是情景:

[self.KVOController observe:self.photos                        
                    keyPath:@"count"
                    options:NSKeyValueObservingOptionNew
                      block:^(id observer, id object, NSDictionary *change) {
    // observer -> RJViewController -> __weak self
    // object   -> self.photos    
    // [self doSomething] -> [observer doSomething]
    [self.KVOController unobserve:self.photos keyPath:@"count"]
}];

这里在block里面用self调用了unobserve办法,按照我们前的晓,那这边肯定会现出循环引用了,应该变更成为:

[observer.KVOController unobserve:observer.photos keyPath:@"count"]

然而事实是以这景下,即采取self否非会见惹循环引用,这是怎吧?原因是开了unobserve操作后,存储KVO信息的_FBKVOInfo会面被释放掉,这样她所指向的手上这block也会让置为nil,
这样block引用self这链端就叫打破了,也即不见面并发循环引用的题材了。所以打破循环引用除了当block内使用weakSelf外,也可以在事件处理完后以目前之block置为nil来实现。

(五)现代播放器原理

线程锁

FBKVO使用pthread_mutex_t作线程锁,关于iOS各种线程锁之牵线好参照这篇博文。我们一直来拘禁下FBKVO使用的其中一个地方:

- (void)_unobserveAll
{
  // lock
  pthread_mutex_lock(&_lock);

  NSMapTable *objectInfoMaps = [_objectInfosMap copy];

  // clear table and map
  [_objectInfosMap removeAllObjects];

  // unlock
  pthread_mutex_unlock(&_lock);

  _FBKVOSharedController *shareController = [_FBKVOSharedController sharedController];

  for (id object in objectInfoMaps) {
    // unobserve each registered object and infos
    NSSet *infos = [objectInfoMaps objectForKey:object];
    [shareController unobserve:object infos:infos];
  }
}

沿之中坚规则是有所对公共数据拍卖的地方都亟需加锁,上面的代码中_objectInfosMap呢全局的NSMapTable,
对那个修改操作(Add/Remove)都需加锁,
FBKVO这边的操作十分值得借鉴,先拷贝一客临时变量,然后以_objectInfosMap清空,这无异于步于沿中间操作,之后吃拷贝出之那份非全局或者说非共享变量再错过做相应的累操作。

这样的话即使有多独线程访问_unobserveAll否未会见出任何问题,因为只有首先单线程会访问到_objectInfosMap,
第二独线程等解锁后再次失去拜谒时_objectInfosMap早已也空了,拷贝的目标objectInfoMaps为自也空,所以锁并不需要加满整个_unobserveAll函数范围。

假定需要采取互斥类型的pthread_mutex_t絮,比如以递归函数中加锁,那要用pthread_mutex_t初始化为互斥类型:

pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&_mutex, &attr);
pthread_mutexattr_destroy(&attr);

吊使用完后记要以dealloc里面销毁掉:

- (void)dealloc {
    pthread_mutex_destroy(&_mutex);
}

(六)延迟优化

DEBUG描述

DEBUG描述(debugDescription)其实和description是一律的力量,只是debugDescription是在Xcode控制台里使用po命令的时光调用显示的。如果没实现debugDescription方法,那么打印该目标的上就显示内存地址,而未见面展示该对象的一一属性值。

- (NSString *)debugDescription
{
  NSMutableString *s = [NSMutableString stringWithFormat:@"<%@:%p keyPath:%@", NSStringFromClass([self class]), self, _keyPath];
  if (0 != _options) {
    [s appendFormat:@" options:%@", describe_options(_options)];
  }
  if (NULL != _action) {
    [s appendFormat:@" action:%@", NSStringFromSelector(_action)];
  }
  if (NULL != _context) {
    [s appendFormat:@" context:%p", _context];
  }
  if (NULL != _block) {
    [s appendFormat:@" block:%p", _block];
  }
  [s appendString:@">"];
  return s;
}

上面是FBKVO实现的_FBKVOInfo的debugDescription,
将逐条属性值拼接成字符串显示出。那如若某对象来N多只特性,这样一个个拼接会非常麻烦,这种状况下好采用runtime来动态获取属性并返:

- (NSString *)debugDescription // prefer super class
{ 
    NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];

    // fetch class's all properties
    uint count;
    objc_property_t *properties = class_copyPropertyList([self class], &count);

    // loop to get each property via KVC
    for (int i = 0; i<count; i++) {
        objc_property_t property = properties[I];
        NSString *name = @(property_getName(property));
        id value = [self valueForKey:name]?:@"nil"; // default nil string
        [dictionary setObject:value forKey:name]; // add to dicionary
    }
    free(properties);

    return [NSString stringWithFormat:@"<%@-%p> -- %@",[self class],self,dictionary];
}

(七)SDK 性能测试模型

数据结构

KVOController是框架的对外接口类,作为KVO的管理者,其持有了目前观察者对象同让观察者的KVO信息。
观察者对象为weak性存储于_observer中,而_objectInfosMap中将被观察者以key进行仓储,
value则存储了对应的_ FBKVOInfo汇聚(图片引用自Draveness的博文)。

KVOController

FBKVO为各国一个被observe的目标还很成了一个_ FBKVOInfo对象,该对象存储了拥有与KVO相关的音讯,包括路径,回调等等。

FBKVOInfo

FBKVO的调用流程如下图所显示,
FBKVOController的作用就是长相应的叫观察者记录,以及变化对应的FBKVOInfo信息,最终见面由于FBKVOSharedController
这个单例来调用系统KVO方法实现对性能的监听,并且以回调方法中将事件分发给
KVO 的观察者。

调用流程

FBKVO中还有一个比好玩的地方是因此_来区别内部接口和外部接口:

- (void)_unobserve:(id)object info:(_FBKVOInfo *)info // -> private method
- (void)unobserve:(nullable id)object keyPath:(NSString *)keyPath // -> public method

席卷类名也是这般:

@interface FBKVOController : NSObject        // -> public 
@interface _FBKVOInfo : NSObject             // -> internal 
@interface _FBKVOSharedController : NSObject // -> internal 

FBKVO的代码量虽然未多,但那个框架流程,接口设计以及代码中应用到的细小技术点确实尽富有水平,希望本文总结与提炼的各种姿势可以被大家产生有抱,也接大家留言讨论。完。

视频编码的义

  • 原始视频数据存储空间充分,一个 1080P 的 7 s 视频需要 817 MB
  • 原来视频数据传占带富大,10 Mbps 的带富传输上述 7 s 视频需要 11
    分钟

只要通过 H.264 编码压缩后,视频大小只有 708 k ,10 Mbps 的带宽仅仅待
500 ms
,可以满足实时传输的要求,所以由视频采访传感器收集来的原始视频势必要通过视频编码。

基本原理

那么为什么巨大的本来视频可以编码成雅有些之视频也?这个中的艺是什么啊?
核心思想就是去冗余信息:

  • 空中冗余:图像相邻像素之间时有发生比较强的相关性
  • 日子冗余:视频序列的邻座图像里内容类同
  • 编码冗余:不同像素值出现的概率不同
  • 视觉冗余:人的视觉系统对某些细节无灵敏
  • 知识冗余:规律性的构造可由先验知识及背景知识得到

视频本质上说是一模一样系列图片连续快速的播放,最简便易行的压缩方式就是指向各个一样轴图片进行压缩,例如比较古老的
MJPEG
编码就是这种编码方式,这种编码方式只出帧内编码,利用空间达到之取样预测来编码。形象的比方就是把每帧都作同样摆设图纸,采用
JPEG
的编码格式对图片进行压缩,这种编码只考虑了平等张图纸内的冗余信息压缩,如图
1,绿色的片就是是时下待编码的区域,灰色就是尚未编码的区域,绿色区域可以根据已经编码的一些开展展望(绿色的左手,下边,左下等)。

图1

然而帧和帧之间为时的相关性,后续开发出了部分比较高档的编码器可以下帧间编码,简单点说就是由此搜索算法选定了帧上的少数区域,然后经过测算时帧和上下参考帧的往量差进行编码的均等种植形式,通过下两单图
2
连续帧我们可以看出,滑雪之同窗是前进位移的,但实际是雪景在为后各移,P
帧通过参考帧(I 或任何 P
帧)就可开展编码了,编码之后的轻重缓急很小,压缩比坏大。

图 2

或者有同学对当时半布置图怎么来的感谢兴趣,这里用了 FFmpeg
的有数尽命令来落实,具体 FFmpeg 的再度多内容请圈后续章节:

  • 首先行生成带有移动矢量的视频
  • 其次履行把每一样轴都输出成图

ffmpeg  -flags2 +export_mvs -i tutu.mp4 -vf codecview=mv=pf+bf+bb tutudebug2.mp4

ffmpeg -i tutudebug2.mp4 'tutunormal-%03d.bmp'

除此之外空间冗余和时间冗余的滑坡,主要还有编码压缩和视觉减少,下面是一个编码器主要的流程图:

图 3

图 4

图 3、图 4 两个流程,图 3 是帧内编码,图 4
凡是帧间编码,从图上看到的要害分就是首先步不相同,其实这有限只流程也是完结合在一起的,我们平常说之
I 帧和 P 帧就是独家下了帧内编码和帧间编码。

编码器的挑

面前梳理了一下编码器的规律同着力流程,编码器经历了数十年的迈入,已经由初始之只支持帧内编码演进到现的
H.265 和 VP9
为代表的初一代编码器,就当下部分广泛的编码器进行剖析,带大家探讨一下编码器的社会风气。

H.264

简介

H.264/AVC
项目意向创建同种植视频正式。与老标准相比,它亦可以重复低带宽下提供优质视频(换言之,只有
MPEG-2,H.263 或 MPEG-4 第 2
有些之一半牵动富或更少),也无长极其多设计复杂度使得无法实现或实现基金过高。另一样目的是供足够的灵活性以在各种应用、网络及系统受以,包括大、低带宽,高、低视频分辨率,广播,DVD
存储,RTP/IP 网络,以及 ITU-T 多媒体公用电话系统。

H.264/AVC
包含了同样文山会海初的特点,使得它比由以前的编解码器不但会还实用之拓编码,还能够当各种网络环境下之运用中采取。这样的技术基础为
H.264 成为包括 YouTube
在内的在线视频公司使用它当作首要的编解码器,但是下其并无是平项大轻松的事务,理论及道用
H.264 需要交纳不菲的专利费用。

专利许可

与 MPEG-2 第一组成部分、第二组成部分,MPEG-4第二部分雷同,使用 H.264/AVC
的制品制造商与服务提供商需要往他们的活所用的专利的持有者支付专利许可费用。这些专利许可的根本根源是平下名为
MPEG-LA LLC 的民用组织,该团队以及 MPEG
标准化组织并未外关联,但是该团伙为管理著 MPEG-2
第一有些系、第二有的视频、MPEG-4
第二有的视频及其它一些技能之专利许可。

另的专利许可则要往其它一样下名为 VIA Licensing
的私组织申请,这家商店另外为管理偏向音频压缩的正规化要 MPEG-2 AAC 及
MPEG-4 Audio 的专利许可。

H.264 的开源实现

  • openh264
  • x264

openh264
是思科实现的开源 H.264 编码,虽然 H.264
需要交纳不菲的专利费用,但是专利费有一个春上限,思科把 OpenH264
实现的年份专利费交满后,OpenH264 事实上便可免费自由的施用了。

x264
x264是一个利用GPL授权的视频编码自由软件。x264 的要紧功用在于进行
H.264/MPEG-4 AVC 的视频编码,而无是当解码器(decoder)之用。

除外开销问题比来拘禁:

  • openh264 CPU 的占据相对 x264低多
  • openh264 只支持 baseline profile,x264 支持更多 profile

HEVC/H.265

简介

愈效率视频编码(High Efficiency Video
Coding,简称HEVC)是均等栽视频压缩标准,被视为是 ITU-T H.264/MPEG-4 AVC
标准的后人。2004 年始由 ISO/IEC Moving Picture Experts
Group(MPEG)和 ITU-T Video Coding Experts Group(VCEG)作为 ISO/IEC
23008-2 MPEG-H Part 2 或如作 ITU-T H.265 开始制订。第一版本的 HEVC/H.265
视频压缩标准以 2013 年 4 月 13
日被接受吗国际电信联盟(ITU-T)的专业标准。HEVC
被看不但提升视频质量,同时为能够达 H.264/MPEG-4 AVC
两倍增之压缩率(等同于一致画面质量下比特率减少了 50%),可支撑 4K
分辨率甚至到超高清电视(UHDTV),最高分辨率可上
8192×4320(8K分辨率)。

H.265 的开源实现

  • libde265
  • x265

libde265
HEVC 由 struktur 公司因为开源许可证 GNU LesserGeneral Public License
(LGPL)
提供,观众得以较缓慢的网速下欣赏到嵩品质的像。跟以前基于H.264标准的解码器相比,libde265
HEVC 解码器可以用公的全高清内容带吃多上两倍增之受众,或者,减少 50%
流媒体播发所待的带富。高清或者 4K/8K
超高清流媒体播发,低顺延/低带宽视频会议,以及完整的位移装备覆盖。具有「拥塞感知」视频编码的长治久安,十分可下在
3/4G 和 LTE 网络。

专利许可

HEVC Advance 要求所有包括苹果、YouTube、Netflix、Facebook、亚马逊等使用
H.265 技术之情节制造商上缴内容收入的
0.5%作为技术使用费,而整个流媒体市场每年达约 1000
亿美元的局面,且不停加强中,征收
0.5%万万是平等画大的开支。而且她们还从未放开了设备制造商,其中电视厂商用开发每令
1.5 美元、移动装备厂商每台 0.8
美元之专利费。他们还是没放了蓝光设备播放器、游戏机、录像机这样的厂商,这些厂商必须开每令
1.1 美元的开销。最无法令人接受的凡,HEVC Advance
的专利使用权追溯至了厂商的「」”,意思是事先曾经卖的活还是要追缴费用。

x265 是由
MulticoreWare 开发,并开源。采用 GPL
协议,但是资助这个类型的几乎独局重组了同盟可以以非 GPL
协议下利用这个软件。

VP8

简介

VP8 凡是一个怒放之视频压缩格式,最早由 On2 Technologies 开支,随后出于
Google 发布。同时 Google 也揭晓了 VP8 编码的实做库:libvpx,以 BSD
授权条款的不二法门发行,随后为增大了专利使用权。而于通过一些争执过后,最终
VP8 的授权确认为一个盛开源代码授权。

眼前支撑 VP8 的网页浏览器有 Opera、Firefox 和 Chrome。

专利许可

2013 年三月,Google 和 MPEG LA 及 11 单专利持有者达成协议,让Google 获取
VP8 以及其前的 VPx 等编码所可能侵犯的专利授权,同时 Google
也可以无偿再次授权相关专利为 VP8 的用户,此协议而适用于下同样替 VPx
编码。至此 MPEG LA 放弃成立 VP8 专利集中授权联盟,VP8
的用户以只是规定义务使用是编码而毫不担心或的专利侵权授权金的问题。

VP8 的开源实现

  • libvpx

libvpx
是 VP8 的唯一开源实现,由 On2 Technologies 支出,Google
收购后拿其开放源码,License 非常宽松可以自由使用。

VP9

简介

VP9 的开销从 2011 年第三季开始,目标是以跟画质下,比 VP8 编码减少
50%底文件大小,另一个对象虽然是如果于编码效率上超过 HEVC 编码。

2012 年 12 月 13 日,Chromium 浏览器在了 VP9 编码的支撑。Chrome
浏览器虽然是当 2013 年 2 月 21 日始于支持 VP9 编码的视频播放。

Google 宣布会在 2013 年 6 月 17 日完成 VP9 编码的制定干活,届时Chrome
浏览器将见面把 VP9 编码默认引导。2014 年 3 月 18 日,Mozilla 在 Firefox
浏览器被入了 VP9 的支撑。

2015 年 4 月 3 日,谷歌宣布了 libvpx1.4.0 增加了针对性 10 位和 12
位的比特深度支持、4:2:2 跟 4:4:4 色度抽样,并 VP9 多为重编/解码。

专利许可

VP9 凡是一个怒放格式、无权利金的视频编码格式。

VP9 的开源实现

  • libvpx

libvpx
是 VP9 的唯一开源实现,由 Google 开发保护,里面来部分代码是 VP8 和 VP9
公用的,其余分别是 VP8 和 VP9 的编解码实现。

VP9 和 H.264 和 HEVC 比较

Codec HEVC x264 vp9
HEVC -42.2% 32.6%
x264 75.8% 18.5%
vp9 48.3% -14.6%
Codec HEVC vs. VP9(in %) VP9 vs. x264 (in %)
Total Average 612 39399

引用 Comparative Assessment of H.265/MPEG-HEVC, VP9, and
H.264/MPEG-AVC Encoders for Low-Delay Video Applications
这首比较新的论文对,低延迟视频进行编码的测试结果。

HEVC 和 H.264 在不同分辨率下的比较

跟 H.264/MPEG-4 相比,HEVC 的平分比特率减低值为:

分辨率 480P 720P 1080P 4K UHD
HEVC 52% 56% 62% 64%

可见码率下降了 60% 以上。

  • HEVC (H.265) 对 VP9 和 H.264 在码率节省上出比充分的优势,在平等 PSNR
    下独家节省了 48.3% 和 75.8%。
  • H.264 在编码时间达到发出伟优势,对比 VP9 和 HEVC(H.265) ,HEVC 是 VP9
    的6倍,VP9 是 H.264 的靠近 40 倍增

FFmpeg

谈到视频编码相关内容就不得不提一个英雄之软件包 — FFmpeg。

FFmpeg
是一个自由软件,可以运作音频和视频又格式的录影、转换、流功能,包含了
libavcodec ——这是一个用来多独品种面临音频和视频的解码器库,以及
libavformat ——一个板与视频格式转换库。

FFmpeg 这个单词遭之 FF 指的是 Fast Forward。有些新手写信给 FFmpeg
的品类主任,询问 FF 是未是意味着 Fast Free 或者 Fast Fourier
等意思,FFmpeg 的门类领导回信说:「Just for the record, the original
meaning of FF in FFmpeg is Fast Forward…」

这个类型前期是由 Fabrice Bellard 发起的,而如今是出于 Michael Niedermayer
在进行维护。许多FFmpeg的开发者同时为是 MPlayer 项目之分子,FFmpeg 在
MPlayer 项目被是于规划啊服务器版本进行开。

FFmpeg 下充斥地址是 : FFmpeg
Download

  • 得浏览器输入下载,目前支持 Linux ,Mac OS,Windows
    三个主流的平台,也得以自己编译到 Android 或者 iOS 平台。
  • 要是是 Mac OS ,可以经过 brew 安装
    brew install ffmpeg --with-libvpx --with-libvorbis --with-ffplay

咱们得用 FFmpeg
来举行哪些有因此起风趣的业务也?通过一致密密麻麻小尝试来拉动大家了解 FFmpeg
的神奇和精。

FFmpeg 录屏

经一个多少例子看一下怎么当 Mac OS 下面采用 FFmpeg 进行录屏:

输入:

ffmpeg -f avfoundation -list_devices true -i ""

输出:

[AVFoundation input device @ 0x7fbec0c10940] AVFoundation video devices:
[AVFoundation input device @ 0x7fbec0c10940] [0] FaceTime HD Camera
[AVFoundation input device @ 0x7fbec0c10940] [1] Capture screen 0
[AVFoundation input device @ 0x7fbec0c10940] [2] Capture screen 1
[AVFoundation input device @ 0x7fbec0c10940] AVFoundation audio devices:
[AVFoundation input device @ 0x7fbec0c10940] [0] Built-in Microphone

给有了时配备支撑的具有输入设备的列表和编号,我本地有半点片显示器,所以 1
和 2 都是自家屏幕,可以选择一样片进行录屏。

翻时的 H.264 编解码器:

输入:

ffmpeg -codecs | grep 264

输出:

 DEV.LS h264                 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (decoders: h264 h264_vda ) (encoders: libx264 libx264rgb )

翻开时之 VP8 编解码器:

输入:

ffmpeg -codecs | grep vp8

输出:

  DEV.L. vp8                  On2 VP8 (decoders: vp8 libvpx ) (encoders: libvpx )

可以挑选用 vp8 或者 h264 做编码器

ffmpeg -r 30 -f avfoundation -i 1 -vcodec vp8 -quality realtime screen2.webm
# -quality realtime 用来优化编码器,如果不加在我的 Air 上帧率只能达到 2

or

ffmpeg -r 30 -f avfoundation -i 1 -vcodec h264 screen.mp4

下一场用 ffplay 播放就得了

ffplay screen.mp4

or

ffplay screen2.webp

FFmpeg 视频转换成为 gif

发出一个特地有效之急需,在网上发现了一个专门好玩的视频想管其换成一个动态表情,作为一个
IT
从业者,我先是只想到的无是下充斥一个转码器,也未是错开探寻一个在线转换网站,直接采用手边的家伙
FFmpeg,瞬间就是完了了转码:

ffmpeg -ss 10 -t 10  -i tutu.mp4  -s 80x60  tutu.gif
## -ss 指从 10s 开始转码,-t 指转换 10s 的视频 -s

FFmpeg 录制屏幕并直播

足持续扩大例子1,直播时屏幕的情节,向大家介绍一下庸通过几执命令搭建筑一个测试用之直播服务:

Step 1:首先安装 docker:
访问 Docker
Download
,按操作系统下载安装。

Step 2:下载 nginx-rtmp 镜像:

docker pull chakkritte/docker-nginx-rtmp

Step 3:创建 nginx html 路径,启动 docker-nginx-rtmp

mkdir ~/rtmp

docker run -d -p 80:80 -p 1935:1935 -v ~/rtmp:/usr/local/nginx/html chakkritte/docker-nginx-rtmp

Step 4:推送屏幕录制到 nignx-rtmp

ffmpeg -y -loglevel warning -f avfoundation -i 2 -r 30 -s 480x320 -threads 2 -vcodec libx264  -f flv rtmp://127.0.0.1/live/test

Step 5:用 ffplay 播放

ffplay rtmp://127.0.0.1/live/test

总结一下,FFmpeg
是独美好的家伙,可以通过它们做到很多普普通通的工作和试验,但是距提供真正可用之流媒体服务、直播服务还有很多的办事而做,这地方可参照七牛云发布的
七牛直播云服务

封装

介绍了了视频编码后,再来介绍部分包裹。沿用前的比喻,封装好知道吧用哪种货车去运输,也即是传媒之容器。

所谓容器,就是将编码器生成的多媒体内容(视频,音频,字幕,章节信息等)混合封装于共的专业。容器使得不同多媒体内容并广播变得格外简单,而容器的旁一个图就是是也多媒体内容提供索引,也就是说要没容器在的语一样管辖影片而不得不由平开始观看最后,不可知拖动进度长条(当然这种状态下局部播放器会话比较丰富的日子即创办索引),而且如果您不谐和失去手动另外载入音频就是从未有过声息,下面介绍几种常见的封装格式和优缺点:

  1. AVI 格式(后缀为 .AVI): 它的英文全称为 Audio Video Interleaved
    ,即音频视频交错格式。它吃 1992 年被 Microsoft 公司生产。
    这种视频格式的优点是图像质量好。由于无损AVI可以保留 alpha
    通道,经常为我们采取。缺点最多,体积过于庞大,而且愈糟糕的凡减正式不联合,最广的现象便是青出于蓝版本
    Windows 媒体播放器播放不了应用早期编码编辑的AVI格式视频,而低版本
    Windows
    媒体播放器又播放不了采用新式编码编辑的AVI格式视频,所以我们在拓展有AVI格式的视频播放时会面世由于视频编码问题如果导致的视频不克播放还是就会播放,但有未可知调节播放进度和播音时就出动静没有图像等片段不三不四的问题。

  2. DV-AVI 格式(后缀为 .AVI): DV的英文全称是 Digital Video Format
    ,是出于索尼、松下、JVC 等大多小厂商同提出的等同种植家用数字视频格式。
    数字摄像机就是下这种格式记录视频数据的。它可以通过电脑的 IEEE 1394
    端口传输视频数据到计算机,也可将微机遭到编好的的视频数据回录到数量摄像机中。这种视频格式的文书扩展名吧是
    avi。电视台以录像带记录模拟信号,通过 EDIUS 由IEEE
    1394端口采集卡从录像带中集出来的视频就是是这种格式。

  3. QuickTime File Format 格式(后缀为 .MOV):
    美国Apple公司开支之一模一样种视频格式,默认的播放器是苹果之QuickTime。
    享有比高的压缩比率和比完善的视频清晰度等特点,并可保存alpha通道。

  4. MPEG 格式(文件后缀可以是 .MPG .MPEG .MPE .DAT .VOB .ASF .3GP
    .MP4等) : 它的英文全称为 Moving Picture Experts
    Group,即移动图像专家组格式,该专家组建于1988年,专门负责吗 CD
    建立视频及音频标准,而成员都是吧视频、音频及系统领域的技巧专家。
    MPEG 文件格式是挪图像压缩算法的国际标准。MPEG
    格式目前发三独压缩正式,分别是 MPEG-1、MPEG-2、和MPEG-4
    。MPEG-1、MPEG-2 目前已经以比较少,着重介绍
    MPEG-4,其制定为1998年,MPEG-4
    是为播放流式媒体的大质量视频一经专门设计之,以求用最少的数额获得最佳的图像质量。目前
    MPEG-4 最有吸引力的地方在于其会保留接近被DVD画质的小体积视频文件。

  5. WMV 格式(后缀为.WMV .ASF): 它的英文全称为Windows Media
    Video,也是微软推出的一样栽采取单独编码方式并且可一直在网上实时看到视频节目的文本压缩格式。
    WMV格式的关键优点包括:本地或网络回放,丰富的流间关系及扩展性等。WMV
    格式需要以网站及广播,需要设置 Windows Media Player( 简称 WMP
    ),很无便宜,现在曾经几乎从来不网站使用了。

  6. Real Video 格式(后缀为 .RM .RMVB): Real Networks
    公司所制定的音频视频压缩正式称为Real Media。
    用户可采用 RealPlayer
    根据不同的网传输速率制定出不同之压缩比率,从而实现在低速率的纱及拓展形象数据实时传送和播放。RMVB
    格式:这是相同栽由RM视频格式升级延伸出的初视频格式,当然性能上出死酷之晋级。RMVB
    视频也是兼具比较明显的优势,一管辖大小也700MB左右之 DVD
    影片,如果拿其转录成同样品质之 RMVB 格式,其个头最多为就是 400MB
    左右。大家也许注意到了,以前在网上下载电影与视频的上,经常接触到
    RMVB
    格式,但是随着时代的发展这种格式为越来越多之重新优质的格式替代,著名的人们影视字幕组在2013年曾经公布不再限于
    RMVB 格式视频。

  7. Flash Video 格式(后缀为 .FLV):由 Adobe Flash
    延伸出的的一律种植流行网络视频封装格式。随着视频网站的丰富,这个格式就十分普及。

  8. Matroska 格式(后缀为
    .MKV):是千篇一律种植新的多媒体封装格式,这个封装格式可管多种不同编码的视频以及16久或上述不同格式的节拍和语言不同之字幕封装到一个
    Matroska Media
    档内。它呢是里面同样栽开放源代码的多媒体封装格式。Matroska
    同时还好提供特别好之交互作用,而且比 MPEG 的方便、强大。

  9. MPEG2-TS 格式 (后缀为 .ts)(Transport
    Stream“传输流”;又如MTS、TS)是平种传输和仓储包含音效、视频以及通信协议各种数据的正式格式,用于数字电视广播系统,如DVB、ATSC、IPTV等等。
    MPEG2-TS 定义为 MPEG-2
    第一片段,系统(即原来之ISO/IEC标准13818-1要ITU-T Rec. H.222.0)。
    Media Player Classic、VLC
    多媒体播放器等软件可一直播放MPEG-TS文件。

即,我们以流媒体传输,尤其是直播中重要性行使的饶是 FLV 和 MPEG2-TS
格式,分别用于 RTMP/HTTP-FLV 和 HLS 协议。

产一致巴我们以系统讲授视频直播的推流和导,尽请期待~

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图