编码
1.空间冗余:图像相邻像素之间有较强的相关性
2.时间冗余:视频序列的相邻图像之间内容相似
3.编码冗余:不同像素值出现的概率不同
4.视觉冗余:人的视觉系统对某些细节不敏感
5.知识冗余:规律性的结构可由先验知识和背景知识得到
直播秒开
DNS缓存
DNS,就是Domain Name System的缩写,翻译过来就是域名系统,是互联网上作为域名和IP地址相互映射的一个分布式数据库。 DNS能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。 通过域名,最终得到该域名对应的IP地址的过程叫做域名解析(或主机名解析)。
DNS解析慢,为了有效降低 DNS 解析对首开的影响,我们可以提前完成播放域名->IP 地址的解析, 并缓存起来,播放的时候,直接传入带 IP 地址的播放地址,从而省去了DNS解析的耗时。 如果要支持用 IP 地址播放,是需要修改底层 ffmpeg 源码的。
ip拉流就是指将拉流url里面的域名,比如http://flv-meipai.8686c.com/live/59c3507b20a05d24f928d6cf.flv里面的flv-meipai.8686c.com预先用第三方dns库解析出来,然后直接替换掉,例如http://1.1.1.1/live/59c3507b20a05d24f928d6cf.flv这样的url,传给ffmpeg来拉流播放。
如果没有替换ip,那么在ffmpeg中的tcp.c文件中,tcp_open方法会调用getaddrinfo方法进行dns的请求和解析。具体代码如下:
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
snprintf(portstr, sizeof(portstr), "%d", port);
if (s->listen)
hints.ai_flags |= AI_PASSIVE;
int64_t start = av_gettime();
if (!hostname[0])
ret = getaddrinfo(NULL, portstr, &hints, &ai);
else
ret = getaddrinfo(hostname, portstr, &hints, &ai);
int64_t end = av_gettime();
struct sockaddr_in *ipv4 = (struct sockaddr_in *)ai->ai_addr;
char *ipAddress = (char *)malloc(INET_ADDRSTRLEN);
inet_ntop(AF_INET, &(ipv4->sin_addr), ipAddress, INET_ADDRSTRLEN);
ffmpeg_dns_success(uri,hostname,(end-start)/1000,ipAddress);
free(ipAddress);
在这段代码中hints.ai_family = AF_UNSPEC的意思是会请求这个域名对应的ipv4和ipv6的ip地址。
这样的配置会导致两个问题:
第一个问题,因为我们的域名只配置了ipv4的地址,没有配置ipv6的地址,所以,ipv4的地址经过160ms左右就返回了。 但ipv6的地址经过300ms左右才返回,并且返回的是错误的,没有具体的地址。 这里还有一个点应该是,ipv4的地址在各级域名服务器,如果之前有请求过,就会直接返回了,但ipv6的地址,从来没有获取到过,所以每次都需要回源到根域名服务器去查询,然后返回错误。 如果设置hints.ai_family = AF_NET 就表示只请求ipv4的地址,dns耗时就很短。当然我们这里可以直接修改ffmpeg代码,但是这就破坏了ffmpeg的强容错性,如果将来需要ipv6的地址,就容易出bug。 所以我们采用在外部传入ip地址进来,这样灵活性就可配置,并且可优化的空间更大。对于直播这种首屏要求很高的应用场景,即使是100ms也是优化的空间。
第二个问题,一般的域名解析都会返回一个有效期,然后由系统来缓存,一般是1分钟左右,也有3分钟的,具体看注册域名的时候是如何配置的(没搞过,所以不清楚)。 然后,系统缓存并不会自动去更新,所以1分钟后,缓存失效,也会导致拉流时从新解析耗时300多ms。 所以,直接传入ip大部分情况下会直接提升这300ms的时间。
目前有几种方法可以解析,可以用开源的HappyDNS,也可以用各厂商的httpdns。 HappyDNS是走的系统解析方法并且只请求了ipv4的地址,可以很好的避免这个问题。 当然httpdns就有更复杂的方法了,而且配合缓存机制,也能达到很快的速度,并且httpdns通常也能解决小运营商dns 域名劫持的问题,所以还是很有用的。
如果对应一些CDN厂商,比如网宿,在ffmpeg里直接用http://1.1.1.1/live/59c3507b20a05d24f928d6cf.flv类似这种http请求,就能拉取到数据了,但在某些CDN厂商那里不行,比如阿里云。 这是因为他们的服务器不仅仅支持某一个域名的服务,还支持其他域名的服务,所以需要在http的header里面设置Host这个参数。这样CDN服务器就能处理了。
在这里,我们可以通过设置参数的形式通过设置Host:这个参数给ffmpeg,这样ffmpeg就可以直接填充了。具体代码如下:(这里参照ijkplayer源码)
av_dict_set(&ffp->format_opts, "headers", "Host: flv-meipai.8686c.com", 0);
注意这里的Host:后面一定要有一个空格。这样,ffmpeg发起的http请求就有host参数了。
这里的关键是av_dict_set 方法如何将参数传入到ffmpeg内部。首先看下ffp->format_opts是什么数据结构
AVDictionary *format_opts;
struct AVDictionary {
int count;
AVDictionaryEntry *elems;
};
typedef struct AVDictionaryEntry {
char *key;
char *value;
} AVDictionaryEntry;
可以看出,AVDictionary就是一个封装了dict类型的数据结构。 然后是看这个ffp->format_opts合适传进去的。
err = avformat_open_input(&ic, is->filename, is->iformat, &ffp->format_opts);
avformat_open_input这个方法就是ffmpeg的打开流,并找到流媒体的头部信息的函数,具体可以参考另一篇文章Avformat_open_input函数的分析之--HTTP篇。传入以后,最终format_opts这个结构体会走到avio.c文件的ffurl_open_whitelist方法,并赋值给URLContext **puc结构体,代码如下:
if (options &&
(ret = av_opt_set_dict(*puc, options)) < 0)
goto fail;
if (options && (*puc)->prot->priv_data_class &&
(ret = av_opt_set_dict((*puc)->priv_data, options)) < 0)
goto fail;
// 然后最终会在http.c文件的发起http连接的函数http_connect方法中
char headers[HTTP_HEADERS_SIZE] = "";
if (!has_header(s->headers, "\r\nHost: "))
len += av_strlcatf(headers + len, sizeof(headers) - len,
"Host: %s\r\n", hoststr);
if (s->headers)
av_strlcpy(headers + len, s->headers, sizeof(headers) - len);
这段代码的意思,如果dict里面含有Host:这个字符串,然后就取后面的值来作为http的headers。否则取hoststr的值,这个值是之前从url里面解析出来的。
所以,经过上面几步,就完成了外部设置Host参数的功能,除了Host以外,http协议的所有headers里面的参数都可以设置。都可以通过这种方式来设置。这就是ffmpeg的强大的地方。
播放器端的策略
播放策略,很多侧重点播的播放器,为了减少卡顿,会有一些缓冲策略,当缓冲足够多的数据之后 ,再送入解码播放。
而为了加快首开效果,需要对播放的缓冲策略做一些调整,如果第一帧还没有渲染出来的情况下, 不要做任何缓冲,直接送入解码器解码播放,这样就可以保证没有任何因为「主动」缓冲带来的首开延时。
服务端的策略
播放参数设置,所有基于 ffmpeg 的播放器,都会遇到avformat_find_stream_info这个函数耗时比较久, 从而增大了首开时间,该函数主要作用是通过读取一定字节的码流数据, 来分析码流的基本信息,如编码信息、时长、码率、帧率等等,它由两个参数来控制其读取的数据量大小和时长, 一个是 probesize,一个是 analyzeduration。
减少 probesize 和 analyzeduration 可以有效地减少avformat_find_stream_info的函数耗时, 从而加快首开,但是需要注意的是,设置地太小可能会导致读取的数据量不足,从而无法解析出码流信息,导致播放失败, 或者出现只有音频没有视频,只有视频没有音频的问题。
直方图
灰度直方图的定义:灰度级的函数,描述图像中该灰度级的像素个数或该灰度级像素出现的频率。反映了图像灰度分布的情况。
灰度直方图只能反映图像的灰度分布情况,不能反映图像像素的位置,即所有的空间信息全部丢失。
直方图的应用:
a.数字化参数:判断一幅图像是否合理的利用了全部被允许的灰度级范围。一般一幅图应该利用全部或几乎全部可能的灰度级,否则等于增加了量化间隔,丢失的信息将不能恢复。
b.边界阈值选取(确定图像二值化的阈值):假定某图像的灰度直方图具有二峰性,则表明这个图像的较亮区域和较暗区域可以很好地分离,以这一点为阈值点,可以得到很好地2值处理效果(区分物体与背景)。
c.当物体部分的灰度值比其他部分的灰度值大时,可利用直方图统计图像中物体的面积。
d.计算图像的信息量H。
数字图像滤波
均值滤波(邻域平均法)、中值滤波(消除独立的噪声点)、高斯滤波(线性平滑滤波,消除高斯噪声,对整幅图像进行加权平均,每一个像素点的值都由其本身和邻域内的其他像素值经过加权平均后得到)、KNN滤波、高通滤波、低通滤波等。
图像特征
颜色、纹理(粗糙度、方向度、对比度)、形状(曲率、离心率、主轴方向)、色彩等。
衡量图像重建好坏的标准
SNR(信噪比)
PSNR=10*log10((2^n-1)^2/MSE) (MSE是原图像与处理图像之间均方误差,所以计算PSNR需要2幅图像的数据!)
SSIM (结构相似性分别从亮度对比度、对比度、结构3方面度量图像的相似性)
AAC和PCM
AAC在数据开始时候加了一些参数:采样率、声道、采样大小
H264存储的两个形态
a. Annex B : StartCode :NALU单元,开头一般是0001或者001 防竞争字节:为了区分 0 0 0 1,它采用0 0 0 0x3 1作为区分 多用于网络流媒体中:rtmp、rtp格式
b. AVCC : 表示NALU长度的前缀,不定长用4、2、1来存储这个NALU的长度 防竞争字节 多用于文件存储中mp4的格式
图片如何合成视频
av_register_all 为AVFormatContext 分配内存 打开文件 创建输出码流AVSream 找到编码器 打开编码器 写文件头,没有的就不写入 循环编码视频像素数据->视频压缩数据 循环编码音频采样数据->音频压缩数据 ———>AVFrame转化为AVPacket 将编码后的视频码流写入文件 ——>AVPacket转化为AVFormat函数 关闭编码器 写文件尾 关闭资源文件
解码流程:
av_register_all 创建AVFormatContext的对象上下文 打开文件 avformat_find_stream_info 找到解码器 打开解码器 创建AVCodecContext上下文 av_read_frame :将avPacket数据转换为avFrame数据 glUniform1i() ——>这个可以设置对应纹理的第几层 glTexSubImage2D() 和glTexImage2D区别————>替换纹理的内容
常见的音视频格式
MPEG(运动图像专家组)是Motion Picture Experts Group 的缩写。这类格式包括了MPEG-1,MPEG-2和MPEG-4在内的多种视频格式。 AVI,音频视频交错(Audio Video Interleaved)的英文缩写。AVI这个由微软公司发布的视频格式,在视频领域可以说是最悠久的格式之一。 MOV,使用过Mac机的朋友应该多少接触过QuickTime。QuickTime原本是Apple公司用于Mac计算机上的一种图像视频处理软件。 ASF(Advanced Streaming format高级流格式)。ASF 是MICROSOFT 为了和的Real player 竞争而发展出来的一种可以直接在网上观看视频节目的文件压缩格式。 WMV,一种独立于编码方式的在Internet上实时传播多媒体的技术标准,Microsoft公司希望用其取代QuickTime之类的技术标准以及WAV、AVI之类的文件扩展名。 NAVI,如果发现原来的播放软件突然打不开此类格式的AVI文件,那你就要考虑是不是碰到了n AVI。n AVI是New AVI 的缩写,是一个名为Shadow Realm 的地下组织发展起来的一种新视频格式。 3GP是一种3G流媒体的视频编码格式,主要是为了配合3G网络的高传输速度而开发的,也是目前手机中最为常见的一种视频格式。 REAL VIDEO(RA、RAM)格式由一开始就是定位在视频流应用方面的,也可以说是视频流技术的始创者。 MKV,一种后缀为MKV的视频文件频频出现在网络上,它可在一个文件中集成多条不同类型的音轨和字幕轨,而且其视频编码的自由度也非常大,可以是常见的DivX、XviD、3IVX,甚至可以是RealVideo、QuickTime、WMV 这类流式视频。 FLV是FLASH VIDEO的简称,FLV流媒体格式是一种新的视频格式。由于它形成的文件极小、加载速度极快,使得网络观看视频文件成为可能,它的出现有效地解决了视频文件导入Flash后,使导出的SWF文件体积庞大,不能在网络上很好的使用等缺点。 F4V,作为一种更小更清晰,更利于在网络传播的格式,F4V已经逐渐取代了传统FLV,也已经被大多数主流播放器兼容播放,而不需要通过转换等复杂的方式。
降低延迟,保证流畅度
产生原因 保证直播的流畅性是指在直播过程中保证播放不发生卡顿,卡顿是指在播放过程中声音和画面出现停滞,非常影响用户体验。造成卡顿的原因有几种情况: 推流端网络抖动导致数据无法发送到服务器,造成播放端卡顿; 播放端网络抖动导致数据累计在服务器上拉不下来,造成博凡卡顿。 由于从服务器到播放器的网络情况复杂,尤其是在 3G 和带宽较差的 WIFI 环境下,抖动和延迟经常发生,导致播放不流畅,播放不流畅带来的负面影响就是延时增大。如何在网络抖动的情况下保证播放的流畅性和实时性是保障直播性能的难点。
流畅度优化 目前主流的直播协议是 RTMP、HTTP-FLV 和 HLS,都是基于 TCP 的长连接。在播放的过程中,若播放端所处的网络环境在一个较佳的状态,此时播放会很流畅。若网络环境不是很稳定,经常会发生抖动,如果播放端没有做特殊处理,可能会经常发生卡顿,严重的甚至会出现黑屏。而移动直播由于其便捷性,用户可以随时随地发起和观看直播,我们无法保证用户的网络一直处于一个非常好的状态,所以,在网络不稳定的情况下保证播放的流畅度是非常重要的。 为了解决这个问题,首先播放器需要将拉流线程和解码线程分开,并建立一个缓冲队列用于缓冲音视频数据。拉流线程将从服务器上获取到的音视频流放入队列,解码线程从队列中获取音视频数据进行解码播放,队列的长度可以调整。当网络发生抖动时,播放器无法从服务器上获取到数据或获取数据的速度较慢,此时队列中缓存的数据可以起到一个过渡的作用,让用户感觉不到网络发生了抖动。
当然这是对于网络发生抖动的情况所采取的策略,如果播放端的网络迟迟不能恢复或服务器的边缘结点出现宕机,则需要应用层进行重连或调度。
花屏
1、正常花屏
有一种花屏是正常的,就是码率特别低的时候出现的大面积马赛克,我们俗称"画面糊了"。 比如我们告诉视频编码器要输出1280 720高清分辨率的画面,但同时要求它只用 200 kbps的码率*(码率是指编码器每秒产生的视频数据大小 ),编码器此时能做的事情就是无底线地降低画质,就会导致大面积的马赛克。 视频参数问题 比如当视频源修改过视频参数(如从720P修改1080P),此时客户端用于解码的SPS&PPS如果没有重新获取的话,就会出现整个画面花屏的现象。这种花屏的现象会一直持续下去,不会随着时间而恢复正常画面。
2、局部花屏
SO_SNDBUF的Buffer太小 当流媒体服务器的SO_SNDBUF的Buffer太小, 在网络环境不好时,导致部分直播数据丢失(比如丢失P帧),继而会导致部分花屏的现象。 解决方法: 增加SO_SNDBUF的Buffer大小。 SOCKET sSocket = … … int nRcvBufferLen = 10241024; int nSndBufferLen = 410241024; int nLen = sizeof(int); setsockopt(sSocket, SOL_SOCKET, SO_SNDBUF, (char)&nSndBufferLen, nLen); setsockopt(sSocket, SOL_SOCKET, SO_RCVBUF, (char*)&nRcvBufferLen, nLen); P帧丢失 I帧正常丢失P帧的情况下,画面的大部分区域是正常的,只有在发生变化的那部分区域会存在局部花屏。
3、绿屏
产生绿屏的主要是: 无法渲染的画面有些用黑色填充,有些用绿色填充,有些用上一帧画面填充。 视频参数改变, 而解码端的SPS&PPS信息未及时重新获取更新,会导致画面无法正常渲染,继而导致绿屏的现象出现。 References: 现象:播放画面出现图像紊乱,大面积的异常颜色的方块图,或者绿屏现象
3.1.丢失参考帧导致的
一般 H.264 码流有 I、B、P 三种帧类型,I 帧是关键帧,B 帧是双向预测内插编码帧,P 帧是前向预测编码帧。 I 帧由于是帧内压缩,因此可以独立解码播放,而 B 帧,一旦丢失了 I 帧或者后面的 P 帧,则会解码失败,而 P 帧一旦丢失了前面的 I/B/P 帧,也会导致解码失败。 对于丢失了参考帧而导致的解码失败,一般就会出现花屏的现象,花屏的严重程度依赖于丢失的参考帧对即将解码的帧的重要程度。 那么,什么情况下会丢失参考帧呢 ? 首先,推流/播放的代码层面,需要注意,不要丢弃编码后、解码前的视频帧数据,不过实际场景中,遇到下面的情况,难免还是会产生丢帧: 网络不好,编码后的数据发不出去 系统低内存,队列里面无法承受更多的帧数据 因此,在这些极端的情况下,不得不丢帧的话,最合理的策略就应该是一次丢一整个 GOP,即:一旦开始丢了一个 I 帧,那么在遇到下一个 I 帧之前的所有视频帧,均丢弃掉,这样即可有效避免播放器端产生解码花屏。 3.2.播放器没有从关键帧开始解码
原理依然如上面所述,如果不从关键帧开始解码,则必然会由于丢失了参考信息而导致解码花屏。 因此,播放器,无论是首播,还是断网重连后,都应该判断第一帧视频是否是关键帧,如果不是,则应该等到第一个关键帧到达之后再送入解码器。 3.3.码流中视频尺寸发生变化
很多直播平台,横屏直播和竖屏直播,使用的是不同的推流尺寸 ,当主播由竖屏推流改为横屏推流,同时又不改变推流地址的话,观众端拉到的流就会出现中间发生了视频尺寸的变化,比如:从 848 x 480 变成了 1280 x 720 等等。 播放器需要实时检测,如果发现视频尺寸发生了变化,则需要重置解码器以及相关逻辑,否则容易出现解码花屏或者出现内存越界等异常。 3.4.硬编硬解的兼容性问题
当然,如果使用的是硬编硬解,则难免会遇到硬编硬解没有失败报错,但是输出的图像确实异常的情况。 代码上小心仔细,充分考虑机型的兼容性,不轻易写死任何参数,剩下能做的就是靠白名单/黑名单了。 3.5.推流端图像尺寸和格式处理不当
图像的格式和尺寸,都是非常重要的参数,一定要严格配置正确。 比如:如果采集到的视频是 NV21 ,编码器只支持 I420,那么编码出来的图像自然会出现颜色问题。 比如:在一些场景切换的过程中,前后摄像头切换,视频的尺寸可能发生了变化,但是剪裁、处理、编码模块没有相应的修改尺寸,那么,也会出现各种视频错乱的现象。 4、播放闪屏
闪屏问题,从根源来看,就是播放的过程中,出现了两种不同的画面来回切换,从而看起来像 「闪屏」,比如,黑白两张图片交替渲染。下面我们再来看看直播场景下,什么原因会引发该现象。
4、1.播放器缓冲机制原因
网络不好的时候,播放器会频繁缓冲,在缓冲的时候,使用了一张广告图片,在某种极端弱网情况下,由于频繁缓冲,导致真实的播放画面和广告图片来回快速切换,导致闪屏现象。 这个情况是完全可以从播放器的缓冲策略上避免的,每次缓冲后,不要收到一帧后就立即渲染,而是适当地多缓冲一些数据,再发送缓冲结束的消息,从而可以频繁 ms 级别的缓冲切换产生的闪屏。
4、2.推流端的原因
推流端产生闪屏的流,往往发生在有画面合成的代码模块,比如:叠加水印、摄像头/图片切换推流、连麦合流等等。 画面的合成,一定要铭记一点,任何情况下,都要避免出现,有合成/没有合成两种画面的交替。
5、存粹的花屏,且花屏效果没有明显的规则型(比如撕裂、错位), 连续多帧存在花屏现象
5.1、视频帧时间戳(PTS)不对 5.2、由于大部分复用器(Muxer)都严格要求视频帧PTS是严格递增的,比如ffmpeg中mp4 Muxer如果当前帧的PTS小于或等于前一帧的PTS,那么该帧就不会被写入文件,ffmpeg会报"Invalid pts"错误。 5.3、视频向音频同步引发丢帧 5.4、缓冲队列溢出
6、分析工具
6.1、ffprobe
1.获取帧信息 ffprobe -show_frames We_Are_Young.mp4 > frames.info
2.统计I帧数量
keyframe=1 : key frame pict_type=I : I-frame
cat frames.info | grep “pict_type=I” |wc -l
3.统计视频帧数量
cat frames.info | grep “media_type=video” |wc -l ffprobe -show_format -show_streams filename
6.2、vlc
6.3、yuv播放
ffplay
ffplay -f rawvideo -video_size 640x480 test.yuv