音视频面试题集锦
采集
iOS 如何实现夜晚自动提示打开手电筒?
当夜晚使用共享单车扫码时,应该都见过提示“打开手电筒”,在
iOS
中我们如何实现呢?主要基于图像环境光参数,参考如下代码。iOS
视频采集设置AVCaptureVideoDataOutput AVCaptureVideoDataOutputSampleBufferDelegate
,通过获取CMSampleBufferRef
每一帧视频数据环境参数进行判断即可。
谈谈 iOS 音视频采集相关接口和数据结构的设计?
https://mp.weixin.qq.com/s/-cwQbkbHqmMKRPk0YhBG6g
通常我们通过 AVCaptureSession 相关的 API 来进行音视频的采集,其中主要组件分为 Input、Output、Session 几个部分
对于视频采集,一般直接使用 AVCaptureSession 的 API 即可
对于音频采集,除了可以使用 AVCaptureSession 来进行音频采集外,还可以使用 AudioUnit。
音频算法
你对视频倍速播放的时候,是否有改变音调?
倍速变大和变小都会带来变调的问题,目前流行的开源项目有 soundtouch 和 sonic 来达到变速不变调的效果,最经典的就是使用时域压扩(TSM)的算法。
直播中发现有回声,可能的原因是什么?
聊聊对音视频同步的理解?
https://mp.weixin.qq.com/s/Ampeeb15Wk2WfgLWMkHywg
音视频对齐方式有三种:
以音频时钟为基准
以视频时钟为基准
以第三方时钟为基准
实际情况下,如果用上面那种简单的方式,慢慢的就会出现音视频不同步的情况,主要原因是同步时,时间粒度太大了,难以精准控制。所以需要引入一个参考时钟(要求参考时钟上的时间是恒定线性递增的)来提高音频时钟的时间粒度,比如:系统时间,进而进行精准时钟对齐。最后以音频时钟为准,视频放快了就减慢播放速度,播放快了就丢帧或加快播放的速度。
图像算法
为什么在 YUV 转 RGB 转换中 UV 分量要减去 0.5?
https://mp.weixin.qq.com/s/HlLwri7rFNSs9C6rxZ90gw
在进行 YUV 到 RGB 的转换时,为了将 U 和 V 的取值范围从对称的 -128 到 127 归一化为非对称的 0 到 255,并且将中心点从 128 移动到 0,需要对 U 和 V 进行偏移量的减法操作。具体来说,通过减去 0.5(或 128 对应的小数形式),可以将 U 和 V 的取值范围转换为 0 到 255,从而与 RGB 的取值范围相匹配。
纹理抗锯齿有哪些算法?各有哪些利弊?
https://mp.weixin.qq.com/s/N-PzPA4rSHGtE6KIjnEX4Q
FXAA(快速近似抗锯齿)
SSAA(超级采样抗锯齿)
MSAA(多重采样抗锯齿)
特效
OpenGL 如何实现二分屏效果?
https://mp.weixin.qq.com/s/-cwQbkbHqmMKRPk0YhBG6g
以纹理 y 坐标中间分屏为例,代码如下:
- precision highp float; varying lowp vec2 varyTextCoord; uniform sampler2D inputTexture;
void main() { float y; if (varyTextCoord.y >= 0.0 && varyTextCoord.y <= 0.5) { y = varyTextCoord.y + 0.25; } else { y = varyTextCoord.y - 0.25; } gl_FragColor = texture2D(inputTexture, vec2(varyTextCoord.x, y)); }
编解码
什么是 DTS 和 PTS?它们有什么区别?
DTS 是解码时间戳;PTS 是显示时间戳。 虽然 DTS、PTS 是用于指导播放端的行为,但它们是在编码的时候由编码器生成的。 当视频流中没有 B 帧时,通常 DTS 和 PTS 的顺序是一致的。但如果有 B 帧时,就回到了我们前面说的问题:解码顺序和播放顺序不一致了。DTS 告诉我们该按什么顺序解码这几帧图像,PTS 告诉我们该按什么顺序显示这几帧图像。
什么是 IDR 帧?它和 I 帧有什么区别?
IDR 帧全称叫做 Instantaneous Decoder Refresh,是 I 帧的一种。IDR 帧的作用是立刻刷新,重新算一个新的序列开始编码,使错误不致传播。 IDR 帧有如下特性:
IDR 帧一定是 I 帧,严格来说 I 帧不一定是 IDR 帧(但一般 I 帧就是 IDR 帧);
对于 IDR 帧来说,在 IDR 帧之后的所有帧都不能引用任何 IDR 帧之前的帧的内容。与此相反,对于普通的 I 帧来说,位于其之后的 B 和 P 帧可以引用位于普通 I 帧之前的 I 帧(普通 I 帧有被跨帧参考的可能);
播放器永远可以从一个 IDR 帧播放,因为在它之后没有任何帧引用之前的帧。因此,视频开头的 I 帧一定是 IDR 帧;一个封闭类 GOP 的开头的 I 帧也一定是 IDR 帧。 所以,在直播场景通常每个 I 帧都是 IDR 帧,这样服务端下发流数据的时候总是从一个 I 帧开始,播放器就可以立即开始播放。
什么是 SPS 和 PPS?它们有什么区别?
SPS,Sequence Paramater Set,保存了一组编码后的图像序列所依赖的全局参数。 PPS,Picture Paramater Set,保存了每一帧编码后的图像所依赖的参数。 SPS 中的信息至关重要,如果其中的数据丢失,解码过程就可能失败。SPS 和 PPS 通常作为解码器的初始化参数。一般情况,SPS 和 PPS 所在的 NAL 单元位于整个码流的起始位置,但是在某些场景下,在码率中间也可能出现这两种结构:
解码器要在码流中间开始解码。比如,直播流。
编码器在编码过程中改变了码率的参数。比如,图像的分辨率。
什么是 SEI?我们可以用它来做什么?
SEI 即补充增强信息(Supplemental Enhancement Information),属于码流范畴,它提供了向视频码流中加入额外信息的方法,是 H.264 标准的特性之一。 SEI的基本特征如下:
并非解码过程的必须选项;
可能对解码过程(容错、纠错)有帮助;
集成在视频码流中。 在直播场景,我们通常使用 SEI 来携带推流端的信息,一直随着直播流传输到播放端。由于 SEI 是绑定着视频帧,所以它可以支持诸如:
统计直播推流端到播放端延时。
支持和视频帧绑定的内容交互。比如,直播答题在播放端的弹窗等。
如何根据 NALU 裸流数据来判断其是 H.264 编码还是 H.265 编码?
1)通常在处理音视频数据时,我们如何选择解码器? 通常我们不是根据 NALU 裸流数据中的信息来选择解码器,而是根据媒体封装层的信息来确定解码器。 媒体封装层是表示媒体数据是什么封装格式的,比如 MP4、FLV。在这层信息里,通常会携带码流编码格式的信息。 拿 MP4 来说,我们可以根据 Sample Description Box(moov/trak/mdia/minf/stbl/stsd) 中的信息来确定其封装的码流的编码格式。 对于 FLV,我们可以根据 VideoTagHeader 中的 CodecID 等信息来确定其封装的码流的编码格式。 这样的好处是效率比较高,解封装的时候就可以确定选择何种解码器了。 2)怎么识别 NALU 裸流数据的编码格式是 H.264 还是 H.265? 但是,如果出现题目中的情况,没有对码流进行封装,而是直接传输码流时,这时候 NALU 中有什么字段能标识自己的编码格式吗?答案是,没有这样明确的字段能标识码流的编码格式。 但是这个问题也不是不能解决,因为 H.264、H.265 码流本身也是遵循一定格式规范的,我们可以按照它的格式规范进行探测,如果能解析出来正确的信息,那也可以确定它的编码格式。 比如,拿 H.265 来说,FFmpeg 中 hevcdec.c 就有对其码流数据进行探测的函数 hevc_probe(…)。 所以,我们可以按照编码格式规范探测,比如 H.265 如果解析出了 pps、sps、vps 的各字段信息符合规范,就认为它是 H.265 的编码;如果不是,在你们的码流格式范围中就只剩 H.264 了;接下来将码流数据交给对应的解码器解码即可。
什么是 Open GOP?什么是 Closed GOP ?
- 这里说的封闭类 GOP,也就是我们常说的 Closed GOP。 Closed GOP 即只允许 GOP 中的帧参考当前 GOP 中的其他帧。 相对与 Closed GOP,还存在 Open Gop,Open Gop 允许一个 Gop 中的帧跨 GOP 参考其他 GOP 中的帧。 在实际应用中,如果是在 HLS、DASH 等自适应流媒体的场景下,为了实现码流之间的无缝切换,也必须使用 ClosedGOP 。 在 x264 中,Open GOP 是默认关闭的。 在 x265 中,Open GOP 则是默认开启的,如果要关闭 Open GOP选项,则需要在你的 x265 配置中加入以下代码 open-gop=0。
为什么会有 YUV 这种数据?它相比 RGB 数据有什么优点?
RGB 工业显示器要求一幅彩色图像由分开的 R、G、B 信号组成,而电视显示器则需要混合信号输入,为了实现对这两种标准的兼容,NTSC(美国国家电视系统委员会)制定了 YIQ 颜色模型,它的主要优点是可以实现对彩色电视和黑白电视的兼容,即可以用黑白电视收看彩色电视信号。YUV 颜色模型则是在 YIQ 的基础上发展而来。 YUV 颜色模型中用亮度、色度来表示颜色。它的亮度信息和色度信息是分离的,其中 Y 表示亮度通道,U 和 V 则表示色度通道。如果只有 Y 信息,没有 U、V 信息,那么表示的图像就是灰度图像。YUV 常用在各种影像处理场景中。YUV 在对照片或视频编码时,考虑到人眼对亮度信息的敏感度高于色度信息,允许降低色度的带宽。这样一来就可以对色度信息进行压缩,所以 YUV 可以相对 RGB 使用更少的数据带宽。比如常见的采样格式有:4:2:1、4:1:1、4:2:0 等,它们分别相对 RGB 压缩了 33.3%、50%、50% 的数据量。
对 YUV 格式有了解吗?YUV 数据做转换是怎样实现的,比如说 YUV422 转为 YUV420?
YUV 格式是传输视频常用的格式,因为相对于 RGB 格式它可以节省更多空间。
YUV 的格式有很多,例如:YUV444、YUV422、YUV420,常用的 YUV 格式是 YUV420 格式。Y 表示亮度信息,是人眼最敏感的分量,UV 则表示色度信息。
YUV420 表示采样方式:UV 分量具有 2:1 的水平采样,2:1 的垂直采样,这里并不是指只有 U,没有 V,而是对于每一行,只有一个 U 或者 V 分量,如果第一行是 4:2:0,那么下一行就是 4:0:2。
可以用工具 YUVView 直接打开 YUV 格式的数据。
YUV 数据因为计算量大和数据量大可以都放到 GPU 存储和计算,YUV422 转 YUV420 可以利用 OpenGL 将 YUV422 的 UV 数据转换成 texture 纹理,编写 shader 做格式转换逻辑继而生成 YUV420 的 UV texture,再通过 readPixel 将显存的 UV 数据读取出来。转换逻辑即将纹理 UV 分量隔行采样。
PCM 音频数据是怎么组织的?
模拟数据 → 采样 → 量化 → 编码 → 数字信号
说一下对信号时域、频域的理解?
PCM 数据经过 AAC 编码器编码后,直接写 .aac 文件会怎么样?
正常播放器会没法识别播放(因为不知道声道数,采样率等信息)。一般要这样做:正常需要在编码每帧数据后,结合编码后的数据生成 ADTS 头,然后将
ADTS 头 + 编码后的数据
整体写入文件,循环往复,才能生成可正常播放的 .aac 文件(当然也可以是:1 个 ADTS + 多帧编码数据
这样的组合)。
PCM 数据操作的最小单元是多少字节?
https://mp.weixin.qq.com/s/lYKGrt4rOISncyFwYRM2cQ
音频 PCM 数据,如果要进行编辑,那么其最小操作单元是:
声道数 * 位数 / 8 * 1 个采样点
。
简要介绍一下对 H.264 的了解?
H.264 编码框架分层目的是什么?
对 H.264 编码框架进行分层的主要目标是为了有高的视频压缩比和良好的网络亲和性。
VCL 层负责视频的信号处理,包含压缩,量化等处理,NAL 层则负责解决编码后数据的网络传输。
这样可以将 VCL 和 NAL 的处理放到不同平台来处理,可以减少因为网络环境不同对 VCL 的比特流进行重构和重编码。
这样将编码和网络传输进行隔离,使功能单一、便于维护。
H.264 如何根据 NALU 判断当前视频帧的类型?
NALU 结构一般为:
[NALU Header][NALU Payload]
,可以根据[NALU Header]
这 1 个字节来获取帧类型
介绍一下 I、P、B 帧编码、解码、显示顺序?
H.264 与 H.265 有什么区别?
- H.265 也称为高效视频编码 (HEVC),是 H.264 的升级和更高级的版本;
- H.265 的编码架构大致上 和 H.264 的架构相似,主要也包含:帧内预测(intra prediction)、帧间预测(inter prediction)、转换(transform)、量化(quantization)、去区块滤波器(deblocking filter)、熵编码(entropy coding)等模块。但在 H.265 编码架构中,整体被分为了三个基本单位,分别是编码单位(coding unit, CU)、预测单位(predict unit, PU)和转换单位(transform unit, TU);
- 比起 H.264,H.265 提供了更多不同的工具来降低码率,以编码单位来说,H.264 中每个宏块(macroblock/MB)大小最大为 16x16 像素,而 H.265 的编码单位最大为 64x64;
- H.265 的帧内预测模式支持 35 种方向(而 H.264 只支持 8 种),并且提供了更好的运动补偿处理和矢量预测方法。
如何代码实现 PSNR 来评估编码质量?
如何测试码率质量甜点?
在视频领域,质量甜点指的是在既定的码率和屏幕大小下通过设定合理的分辨率和帧率来得到最佳视频主观质量体验。因为编码复杂度和编解码质量亦不是线性关系,两者之间也存在一个质量甜点。在音频领域也有类似的情况,针对具体的情况,我们可以测试手机的编码质量来选择指定分辨率、帧率时对应的码率甜点。
Android Surface 解码如何支持带角度视频?
1)直接解码到 Surface
- 需要通过
MediaFormat
设置解码参,通过MediaFormat.KEY_ROTATION
配置旋转角度,则可以正确显示。
- 需要通过
2)解码到 SurfaceTexture
- 解码到
SurfaceTexture
,通过MediaFormat.KEY_ROTATION
配置旋转角度,但输出纹理提供接口获取旋转矩阵,mSurfaceTexture.getSurfaceTexture().getTransformMatrix
,拿到旋转矩阵后通过FBO
渲染调整为正确尺寸,这种模式好处可以将解码后数据经过自定义处理传递给编码层与渲染上屏。
- 解码到
iOS 如何实现 HDR 转 SDR?
介绍一下 FFmpeg 中关于 timebase 的基础知识与应用?
timebase 在 FFmpeg 的定义是一个 AVRational 结构体,用分子和分母表示。
如何识别一个视频是 HDR 视频?
iOS 判断一个视频是否是 HDR 视频的方法:判断是否带有 HDR 特征的 track 即可。
- NSArray<AVAssetTrack > hdrTracks = [asset tracksWithMediaCharacteristic:AVMediaCharacteristicContainsHDRVideo]; if (hdrTracks.count > 0){ return YES; }
Android 需要我们自己解析出 colortransforfunction和ccolorStandard。
请问 Android 上如何识别一个视频是哪种格式的 HDR 视频:HDR10+/DolbyVision/HLG/HDR10?
Dolby:MediaExtractor 可以解析出 minetype,如果是 video/dolby-vision 则格式为 Dolby 视频;
HLG:解析出 METADATA_KEY_COLOR_TRANSFER,如果颜色转换函数是 HLG 则格式为 HLG 视频;
HDR10/HDR10+:可以通过硬件解码出来的 mediaFormat 变化回调 onOutputFormatChanged 来判断,代码如下: public void onOutputFormatChanged(@NonNull MediaCodec mediaCodec, @NonNull MediaFormat mediaFormat) { if (format.containsKey(MediaFormat.KEY_HDR10_PLUS_INFO)){ Log.d(TAG, “hdr10”); } else if (format.containsKey(MediaFormat.KEY_HDR_STATIC_INFO)){ Log.d(TAG, “hdr10+”); } }
HEVC OpenGOP 的新增的帧类型有哪些,在开发中需要注意什么?
IRAP(Intra Random Access Pictures):随机访问帧。解码器可以从该帧开始解码。IRAP 包含三种帧类型:瞬时解码器刷新帧(IDR)、干净随机访问帧(CRA)、断开链路访问帧(BLA)。视频的解码过程始终要从 IRAP 帧开始。
前导帧(Leading pictures):按输出顺序位于随机访问点图片之前,但在编码视频序列中在随机访问点图片之后进行编码。
RADL(Random Access Decodable Leading pictures):按照编码顺序独立于随机访问点之前的图片的引导帧被称为随机访问可解码前导帧。
RASL(Random Access Skipped Leading pictures):按照编码顺序使用随机访问点之前的图片进行预测的前导帧可能会被损坏。这些被称为随机访问跳过前导帧。
尾随帧(Trailing pictures):在输出和解码顺序上均在 IRAP 和前导图片之后。
TSA(Temporal Sublayer Access)和 STSA(Stepwise Temporal Sublayer Access):标记解码器需要切换视频分辨率的帧,也是编码分层逻辑里面的分层转换点。
介绍一下 Android 14 引入了 Ultra HDR Image 格式?
Ultra HDR 图片格式的原理是结合了标准 8-bit JPEG 基础图像与一个较低分辨率的、带有增益映射的 JPEG 图像以及用于 HDR 重建的元数据。首先,它通过加入一个标准 8-bit 的 JPEG 压缩图像,这个图像提供了基础的色彩和细节。然后,它关联了一个较低分辨率的 JPEG 图像,这个图像带有增益映射,可以提供额外的细节和动态范围。最后,它还包含了用于 HDR 重建的元数据,这些元数据可以用来创建 HDR 图像。
iOS 平台上如何判断 VideoToolbox 是否支持某种编码格式(H.264、HEVC 等)或者某种颜色空间呢?
1)判断 VideoToolbox 是否支持一种编码格式,首先可以查一下
CMFormatDescription.h
文件的CMVideoCodecType
枚举中有没有对应的编码格式。对于具体的设备是否支持某种编码格式,可以用类似下面的代码检查:
1 | BOOL supportHEVC = NO; |
- 2)判断是否支持某一种类型的颜色空间,可以先看看
CVPixelBuffer.h
文件中的kCVPixelFormatType
关于颜色空间的声明。
Android 平台上使用 MediaCodec 编码,如何告知编码器结束编码?
当我们想要告知编码器结束编码的时候,其实是在 Executing 状态中,从 Running 子状态中流转到 End-of-Stream 子状态中,并且在 MediaCodec 使用结束后,调用
release()
方法将其释放。当 MediaCodec 的输入队列中接受了一个带有
_BUFFER_FLAG_END_OF_STREAM_
标志的输入帧时,MediaCodec 会继续输出剩余的编码数据。直到收到 End-of-Stream 的编码帧后,可以认为编码器已经从之前的 Running 状态运行到 End-of-Stream 状态。在输入带有
_BUFFER_FLAG_END_OF_STREAM_
标志的输入帧后,不可再继续向 MediaCodec 输入数据,除非 MediaCodec 已经被 Flush、Stop 或 Restart。
Android 平台上使用 MediaCodec 异步编码,使用 Surface 作为编码输入源的流程和使用 ByteBuffer 作为编码输入源有什么区别?
使用 ByteBuffer 异步编码的时候,Surface 和 ByteBuffer 都需要设置 MediaCodec 的
setCallback
方法设置相关的回调。区别在于:- 在使用 ByteBuffer 作为输入源的时候,我们需要在
onInputBufferAvailable
的时候根据 index 拿到空闲的 ByteBuffer,塞入有效的数据后,再调用queueInputBuffer
传回给 codec 进行编码。
- 在使用 ByteBuffer 作为输入源的时候,我们需要在
- 在使用 Surface 作为输入源的时候,Surface 会自己管理
buffer,
dequeueInputBuffer
和getInputBuffers
的调用都被认为是非法的。因此onInputBufferAvailable
方法不会回调,我们需要把数据直接送到 Surface 上进行渲染。
- 在使用 Surface 作为输入源的时候,Surface 会自己管理
buffer,
聊聊 iOS CVPixelBufferRef 相关的细节?
https://mp.weixin.qq.com/s/Ampeeb15Wk2WfgLWMkHywg
CVPixelBufferRef 像素缓冲区,是 iOS 平台进行视频编解码及图像处理相关最重要的数据结构之一。它的定义是 typedef CVImageBufferRef CVPixelBufferRef。CVPixelBuffer 是在 CVImageBuffer 的基础上实现了内存存储。并且,CVPixelBuffer 还可以实现 CPU 和 GPU 共享内存,为图像处理提供更高的效率。
如何获取视频流中的 QP 值?
- https://mp.weixin.qq.com/s/HlLwri7rFNSs9C6rxZ90gw
视频编码对 QP 值的控制有哪些?
- https://mp.weixin.qq.com/s/HlLwri7rFNSs9C6rxZ90gw
iOS 如何使用分段转码,如何设置分片大小?
https://mp.weixin.qq.com/s/N-PzPA4rSHGtE6KIjnEX4Q
初始化 AVAssetWriter 时可以设置 outputFileTypeProfile = AVFileTypeProfileMPEG4AppleHLS 即可打开 MP4 分段转码。
通过 preferredOutputSegmentInterval 设置转码后的每片大小。如果想手动切片,需要设置 preferredOutputSegmentInterval = kCMTimeIndefinite,并且在每次想要切片的位置调用 flushSegment 接口强制分片,注意调用接口的时机必须是 Sync 帧前。即每片的开始帧都是 Sync 帧。
VideoToolbox 中是不是不存在平面格式(planar)对应的 YUV420、YUV422 和 YUV444 的 OSType 常量?
https://mp.weixin.qq.com/s/N-PzPA4rSHGtE6KIjnEX4Q
iOS 的 API 中能找到的平面格式有:kCVPixelFormatType_420YpCbCr8Planar、kCVPixelFormatType_420YpCbCr8PlanarFullRange,这两种平面格式分别对应 y420 和 f420。对这两种格式测试下来,目前系统播放器支持,系统相机不支持。yuv422 和 yuv444 的平面格式目前没找到。
iOS 中系统 API 提供了哪些视频编码的方式?
https://mp.weixin.qq.com/s/sKvPn2pLRYdJzx6RepKp1Q
AVFoundation 框架:通过 AVFoundation 框架,可以使用 AVAssetWriter 和 AVAssetWriterInput 类来实现编码视频。
VideoToolbox 框架:使用 VideoToolbox,可以利用 iOS 设备上的硬件编码器来实现高效的视频编码。
Videotoolbox 视频帧解码失败以后应该如何重试?
https://mp.weixin.qq.com/s/sKvPn2pLRYdJzx6RepKp1Q
1、重新初始化解码器:尝试重新初始化 Videotoolbox 解码器,有时候重新初始化可以解决解码过程中的一些临时问题。
2、检查视频文件:确保视频文件没有损坏或者格式不正确。有时候解码失败是因为视频文件本身的问题,可以尝试使用其他工具或者重新获取视频文件。
3、检查当前内存:在解码过程中如果 CMSampleBuffer 不及时释放,可能会导致内存过高导致解码器报 -11800 通用错误。
4、尝试重新解码当前帧:将当前帧以及当前 gop 内前序帧都重新输入给解码器。
如何使用 PSNR 对视频转码质量进行评估?
https://mp.weixin.qq.com/s/sKvPn2pLRYdJzx6RepKp1Q
1、计算图像差异:获得原始视频帧和转码后的未经过任何图像效果处理的视频帧使用同一解码器解码,并将它们的每一帧转换成相同的格式(比如 YUV 格式)。
2、计算 PSNR 值:使用以下公式计算每一帧的 PSNR 值。
3、计算平均 PSNR:将所有帧的 PSNR 值求平均,得到视频的平均 PSNR 值。
4、分析结果:根据平均 PSNR 值来评估转码后视频的质量。较高的 PSNR 值表示转码后的视频质量与原始视频相似度较高,而较低的 PSNR 值则表示质量损失较大。
VideoToolbox 遵循哪种视频码率控制策略?如何设置?
https://mp.weixin.qq.com/s/JbEGcM95fL3i7g30dTDtOw
CBR(Constant Bit Rate)恒定码率:一定时间范围内比特率基本保持的恒定。
VBR(Variable Bit Rate)可变码率:码率分配根据图像内容的复杂度进行。
ABR(Average Bitrate)平均目标码率:控制一段时间内的编码平均码率。
Annex B 如何转换为 AVCC?
https://mp.weixin.qq.com/s/JbEGcM95fL3i7g30dTDtOw
1、解析 Annex B 格式:读取字节流,识别每个 NAL 单元的起始码,确定每个 NAL 单元的开始和结束位置。
2、去除起始码:去除每个 NAL 单元的起始码。
3、计算长度:对于每个 NAL 单元,计算其长度(以字节为单位)。
4、写入长度前缀:将每个 NAL 单元的长度作为字节序列写入到 AVCC 格式的流中,可能 1 个字节,2 字节或者 4 字节(较为常见),NAL 单元长度会存储在 AVCC 的 extradata 中。
5、根据 Annex B 的 SPS 和 PPS 生成对应的 extradata。
6、写入 NAL 单元数据:在长度字段后面写入去除起始码后的 NAL 单元数据。
iOS 中如何判断一个视频帧是不是关键帧?
https://mp.weixin.qq.com/s/JbEGcM95fL3i7g30dTDtOw
在 VideoToolbox 中,可以通过检查给定的
CMSampleBuffer
是否是视频帧,并且是否是关键帧。
FFmpeg 编码的流程是什么?
https://mp.weixin.qq.com/s/lYKGrt4rOISncyFwYRM2cQ
av_register_all()
:注册 FFmpeg 所有编解码器。avformat_alloc_context()
:初始化输出码流的 AVFormatContext。avio_open()
:打开输出文件。av_new_stream()
:创建输出码流的 AVStream。avcodec_find_encoder()
:查找编码器。avcodec_open2()
:打开编码器。avformat_write_header()
:写文件头(对于某些没有文件头的封装格式,不需要此函数,比如 MPEG2TS)。avcodec_send_frame()
:编码核心接口新接口,发送一帧视频给编码器。即是 AVFrame(存储YUV像素数据)。avcodec_receive_packet()
:编码核心接口新接口,接收编码器编码后的一帧视频,AVPacket(存储 H.264 等格式的码流数据)。av_write_frame()
:将编码后的视频码流写入文件。flush_encoder()
:输入的像素数据读取完成后调用此函数。用于输出编码器中剩余的 AVPacket。av_write_trailer()
:写文件尾(对于某些没有文件头的封装格式,不需要此函数,比如 MPEG2TS)。
iOS 音频帧 CMSampleBufferRef 中的 kCMFormatDescriptionExtension_VerbatimISOSampleEntry 保存哪些信息,是否可以去掉?
- https://mp.weixin.qq.com/s/lYKGrt4rOISncyFwYRM2cQ
媒体封装
什么是 MP4 的 moov Box?我们在封装 MP4 时通常怎么处理它?为什么?
moov Box 即 Movie Box,MP4 中存储所有媒体数据的索引信息的 Box。moov Box 可以说是 MP4 文件中最重要的 Box,一般播放器的实现都需要读取到 moov 的数据才能开始播放流程。 对于通过网络播放 MP4 视频的场景,都建议将视频处理为 moov 前置。因为 moov 前置后,从网络读取和播放 MP4 文件时,就可以较快获取到 moov 的数据并开始播放。
AAC 封装到 MP4 中,是否需要为每个 AAC packet 添加 ADTS?
不需要,原因如下:
1)我们所说的 AAC 文件实际是 AAC 封装格式,其实在 AAC 编码格式的基础上添加了 ADTS 头等信息,组装成 AAC 封装格式的;
2)将 AAC 编码后的数据存放到 MP4 中,就需要按照 MP4 的封装格式来进行存储;
3)MP4 中实际将类似 ADTS 这些信息存放到了 moov 中的音频通道对应的 box 中了。
iOS 如何支持封装 FMP4 格式?
AVAssetWriter
设置以下参数即可,系统会按照设置的切片时长回调didOutputSegmentData
返回数据,仅 iOS 14 系统以上才可以。
FFmpeg 如何支持封装 FMP4 格式?
FMP4
数据格式参考第 2 题,FFmpeg
实现FMP4
需要通过Muxer
配置参数,设置avio_alloc_context
回调数据,具体分片通过控制av_write_frame(formatContext, NULL)
调用时机执行相关策略。
媒体传输
RTMP 消息分优先级的设计有什么好处?
RTMP 的消息优先级是:控制消息 > 音频消息 > 视频消息。当网络传输能力受限时,优先传输高优先级消息的数据。 要使优先级能够有效执行,分块也很关键:将大消息切割成小块,可以避免大的低优先级的消息(如视频消息)堵塞了发送缓冲从而阻塞了小的高优先级的消息(如音频消息或控制消息)。
CDN 在直播中有哪些运用?
CDN 的全称为 Content Delivery Network,即内容分发网络,是一个策略性部署的整体系统,主要用来解决由于网络带宽小、用户访问量大、网点分布不均匀等导致用户访问网站速度慢的问题。这中间就有了很多的 CDN 节点。 具体实现是通过在现有的网络中,增加一层新的网络架构,将网站的内容发布到离用户最近的网络节点上,这样用户可以就近获取所需的内容,解决之前网络拥塞、访问延迟高的问题,提高用户体验。 CDN 直播中常用的流媒体协议包括 RTMP、HLS、HTTP FLV 等。RTMP(Real Time Messaging Protocol)是基于 TCP 的,由 Adobe 公司为 Flash 播放器和服务器之间音频、视频传输开发的开放协议。HLS(HTTP Live Streaming)是基于 HTTP 的,是 Apple 公司开放的音视频传输协议。HTTP FLV 则是将 RTMP 封装在 HTTP 协议之上的,可以更好的穿透防火墙等。 CDN 架构设计比较复杂,不同的 CDN 厂商,也在对其架构进行不断的优化,所以架构不能统一而论。这里只是对一些基本的架构进行简单的剖析。CDN 主要包含:源站、缓存服务器、智能 DNS、客户端等几个主要组成部分。
-源站:是指发布内容的原始站点。添加、删除和更改网站的文件,都是在源站上进行的;另外缓存服务器所抓取的对象也全部来自于源站。对于直播来说,源站为主播客户端。
-缓存服务器:是直接提供给用户访问的站点资源,由一台或数台服务器组成;当用户发起访问时,他的访问请求被智能 DNS 定位到离他较近的缓存服务器。如果用户所请求的内容刚好在缓存里面,则直接把内容返还给用户;如果访问所需的内容没有被缓存,则缓存服务器向邻近的缓存服务器或直接向源站抓取内容,然后再返还给用户。
-智能 DNS:是整个 CDN 技术的核心,它主要根据用户的来源,以及当前缓存服务器的负载情况等,将其访问请求指向离用户比较近且负载较小的缓存服务器。通过智能 DNS 解析,让用户访问同服务商下、负载较小的服务器,可以消除网络访问慢的问题,达到加速作用。
-客户端:即发起访问的普通用户。对于直播来说,就是观众客户端,例如手机客户端,PC 客户端。
为什么视频会议用 UDP?如果用 TCP 实现音视频,需要建立几次连接?用 UDP 实现音视频,有什么方法可以保证通话质量?
1)为什么视频会议用 UDP? 视频会议场景最重要的体验指标一般是『通话延时』和『语音音质』两方面。 在传输层使用 UDP 的主要考虑是为了降低通话延时。因为 UDP 的不需要 TCP 那样的面向连接、可靠传输、拥塞控制等机制,这些机制(比如三次握手建连、丢包重传等)通常都会带来相对 UDP 更高的延时。 当然,另外一方面原因是人们对视频会议中图像信息的损失容忍度是比较高的,这样即使 UDP 无法保证可靠性,有时候还是可以接受的。 2)如果用 TCP 实现音视频,需要建立几次连接? 可以做到只建连一次,多路复用。 也可以音频和视频各使用一路连接。 3)用 UDP 实现音视频,有什么方法可以保证通话质量? 使用 UDP 享受了低延时,牺牲了可靠性。但可靠性牺牲太多导致不可用也是不可接受的,所以还需要做一些机制来保证一定的可靠性,比如我们可以参考 WebRTC 的机制: -NACK:通过丢包重传解决丢包问题,会增加延时。 -FEC:通过冗余数据解决丢包问题,会增加带宽占用。 -JitterBuffer:通过队列对接收到的数据进行缓冲,出队时将数据包均匀平滑的取出,解决视频的乱序与抖动。 -NetEQ:类似 JitterBuffer,解决音频的乱序与抖动。
RTMP 和 RTSP 有什么区别?使用 RTSP 是基于 UDP 传输的话,我们怎样进行乱序重排?
RTMP 和 RTSP 的区别:
RTMP 使用 TCP 作为传输层协议,能保证不丢包和接收顺序,传输质量高。
RTSP 使用 RTP 格式协议和 RTCP 控制协议,命令与数据分离。传输层协议一般会选择 UDP,延迟比较低,传输效率高。
RTSP 中的 RTP 格式头中有 SequenceNumber 字段,可以通过这个序号实现排序。
假如给你一堆乱序的 RTP 包,应该怎样实现乱序重排?
可以利用接收 RTP 包缓冲队列使用包的序号进行排序。
在丢包情况下为保证传输质量会引入 NACK 和 FEC 机制。
NACK 表示接收端通知发送端一些包丢失,发送 NACK 包请求重传;FEC 前向纠错值的是每个包携带一些冗余信息可以在部分包丢失的时候利用其他包进行重建。
如果重传次数过多,包无法重建,或者丢的包过多,此时可以丢帧直接跳过丢失的部分。
你在项目中是怎么降低端到端的延时的?
下面是直播走 RTMP 推流、HTTP-FLV 播放方案降低端到端延迟的思路:
推流端的延迟包含编码延迟和发送缓存队列引入的延迟。可以通过调节编码参数(B 帧、码率、帧率)减小编码延迟但会影响画质。另外可以提高上传的传输性能来减小传输时长。
CDN 链路上的传输延迟。包括推流的链路和播放回源的链路,这部分延迟不是太大,但依旧会引入几百 ms 的延迟。
CDN 拉流边缘节点的吐流策略会直接影响延迟的大小。直播流编码的 GOP 的长度,CDN 在客户端拉流时吐几秒的数据、按照 GOP 分隔如何丢数据,这些策略都会影响延时。
播放端可以通过对当前已下载的 buffer 进行倍速播放和跳帧来降低缓存从而达到降低延迟。注意如果倍速过大,声音是会明显变调的,需要通过算法来调整。跳帧一定要注意视频跳到 I 帧,音频对齐视频进行丢弃。
渲染
OpenGL 是按照什么架构设计的?
OpenGL 的渲染架构是 Client/Server 模式:Client(客户端)指的是我们在 CPU 上运行的一些代码,比如我们会编写 OC/C++/Java 代码调用 OpenGL 的一些 API;而 Server(服务端)则对应的是图形渲染管线,会调用 GPU 芯片。我们开发的过程就是不断用 Client 通过 OpenGL 提供的通道去向 Server 端传输渲染指令,来间接的操作 GPU 芯片。
什么是渲染上下文(Context)?
OpenGL 自身是一个巨大的状态机(State Machine):一系列的变量描述 OpenGL 此刻应当如何运行。OpenGL 的状态通常被称为 OpenGL 上下文(Context)。我们通过改变上下文中的状态来改变接下来绘画的属性和操作的缓冲对象,然后 OpenGL 利用当前的上下文(Context)的状态去渲染。因此状态的改变要非常小心,因为是状态是全局,会影响接下来的所有渲染操作。
什么是离屏渲染?
GPU 渲染机制:CPU 计算好显示内容提交到 GPU,GPU 渲染完成后将渲染结果放入帧缓冲区,随后屏幕控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。
当前屏幕渲染,指的是 GPU 的渲染操作是在当前用于显示的屏幕缓冲区中进行。
离屏渲染,指的是 GPU 在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。
特殊的离屏渲染:如果将不在 GPU 的当前屏幕缓冲区中进行的渲染都称为离屏渲染,那么就还有另一种特殊的离屏渲染方式:CPU 渲染。
为什么离屏渲染会造成性能损耗?
当使用离屏渲染的时候会很容易造成性能消耗,因为离屏渲染会单独在内存中创建一个屏幕外缓冲区并进行渲染,而屏幕外缓冲区跟当前屏幕缓冲区上下文切换是很耗性能的。
由于垂直同步的机制,如果在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。这就是界面卡顿的原因。
什么是 OpenGL 渲染管线(Pipeline)?
OpenGL 渲染管线就是 OpenGL 的工作流程,指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程。
图形渲染管线可以大致被划分为两个主要部分:第一部分把你的 3D 坐标转换为 2D 坐标;第二部分是把 2D 坐标转变为实际的有颜色的像素。
OpenGL 渲染管线主要包含哪些部分?
OpenGL 的渲染管线其实也是类似的一个过程,它的工序包括:顶点着色器 → 图元装配 → 几何着色器 → 光栅化 → 片段着色器 → 测试与混合。
为什么说 OpenGL 渲染管线中的着色器(Shader)是可编程管线?
OpenGL 渲染管线中着色器允许开发者自己配置,这样我们就可以使用 GLSL(OpenGL Shading Language)来编写自己的着色器替换默认的着色器,从而更细致地控制图形渲染管线中的特定部分。
有哪些着色器可以由程序员进行编程?
可编程的着色器有:顶点着色器(Vertex Shader)、几何着色器(Geometry Shader)、片段着色器(Fragment Shader)。常用的是顶点着色器和片段着色器。
什么是 VBO、EBO 和 VAO?
可以认为它们是在 OpenGL 中处理数据的三大类缓冲内存对象。
VBO(Vertex Buffer Objects)顶点缓冲区对象,指的是在 GPU 显存里面存储的顶点数据(位置、颜色)。
EBO(Element Buffer Object)图元索引缓冲区对象,指的是为了更高效的利用数据,存储索引来达到减少重复数据的索引数据。
VAO(Vertex Array Object)顶点数组对象,主要作用是用于管理 VBO 或 EBO,减少 glBindBuffer、glEnableVertexAttribArray、glVertexAttribPointer 这些调用操作,高效地实现在顶点数组配置之间切换。
Vertex Buffer Object 的布局格式是怎样的?
Vertex Array Object 的布局格式是怎样的?
当 VAO 只管理 VBO 时,布局格式如下图所示:
当 VAO 管理 VBO 和 EBO 时,布局格式如下图所示:
你在项目中使用过 SDL 进行渲染,能否讲一下 SDL 渲染?
SDL(Simple DirectMedia Layer)是一套开放源代码的跨平台多媒体开发库,使用 C 语言写成。其主要用于游戏开发中的多媒体处理,如:视频渲染、音频播放、鼠标键盘控制等操作。它对外接供了一套统一的接口,但在内部,它会根据不同平台调用不同的底层 API 库。
SDL 的基本流程如下:
1、初始化 SDL
2、创建窗口
3、创建渲染器
4、清空缓冲区
5、绘制要显示的内容
6、最终将缓冲区内容渲染到 Window 窗口上
7、销毁渲染器
8、销毁窗口
9、退出 SDL
OpenGL 的双缓冲机制是什么?eglCreateWindowSurface、eglCreatePbuffferSurface 和双缓冲机制有什么关联吗?
双缓冲机制主要目的是为了解决计算机图形学中的屏幕闪烁和画面流畅性问题。该机制通过在内存中创建两个缓冲区:一个用于绘制图像的后缓冲区,一个用于显示图像的前缓冲区,来避免因为输入输出速度不匹配造成的界面闪烁、卡顿等现象。这个问题是很老的问题了,目前的系统基本都已经支持双缓冲了。
双缓冲机制与的 eglCreateWindowSurface、eglCreatePbuffferSurface 这两个方法没有直接的关系。这两个方法是为了实现当前屏幕渲染和离屏渲染的功能,eglCreateWindowSurface 是创建屏幕上的渲染区域来实现屏幕渲染,eglCreatePbuffferSurface 是创建屏幕外的渲染区域来实现离屏渲染。也就是说你创建 eglCreateWindowSurface 就自动支持双缓冲机制了。
想要把 iOS、Android 应用开发中 OpenGL ES 渲染相关模块下沉到 C++ 实现双端共用要怎么实现?
- 1、创建一个 C++ 的渲染引擎模块。该模块将负责处理 OpenGL ES 的渲染逻辑。您可以使用现有的 C++ 图形库,如 Angle、Diligent Engine 等,或者自己编写一个。
- 2、将渲染逻辑从 iOS 和 Android 应用中移动到 C++ 渲染业务模块。包括创建 OpenGL ES 上下文、编译和链接着色器程序、设置渲染状态、绘制图形等。
- 3、封装 C++ 接口层。为了在 iOS 和 Android 应用中使用 C++ 渲染模块,您需要封装 C++ 接口层,该接口层需要定义 iOS 和 Android 业务层代码与渲染模块交互的函数和方法。这样,应用程序可以通过调用这些接口来使用 C++ 渲染模块。
- 4、处理平台特定的差异。由于 iOS 和 Android 平台的差异,您可能需要处理一些平台特定的差异,例如,处理不同的输入事件、处理不同的窗口管理等。在 C++ 接口中,可以定义平台特定的函数,然后在 iOS 和 Android 应用中实现这些函数。
使用 OpenGL 绘制时对于二维坐标需要注意什么?
https://mp.weixin.qq.com/s/-cwQbkbHqmMKRPk0YhBG6g
搞清楚顶点坐标与纹理坐标
FBO 与 glViewport
聊聊 OpenGL glFlush 和 glFinish 区别?
https://mp.weixin.qq.com/s/-oC7782W_Sh0qxTQAZ7Y8g
一般来说,我们在使用 OpenGL 的时候,指令不是立即执行的。它们首先被送到指令缓冲区,然后才被送到硬件执行。glFinish 和 glFlush 都是强制将命令缓冲区的内容提交给硬件执行。
一般使用 glFlush 的目的是确保在调用之后,CPU 没有 OpenGL 相关的事情需要做,命令会送到硬件执行。调用 glFinish 的目的是确保当返回之后,没有相关工作留下需要继续做。
怎么实现 OpenGL 多线程同步?
https://mp.weixin.qq.com/s/-oC7782W_Sh0qxTQAZ7Y8g
一般情况下我们调用 OpenGL 方法后,并不是马上有效果的,如果在 B 线程使用 A 线程的纹理有概率出现渲染异常,因为 A 纹理还没有渲染完成。
glFinish 同步方案
Fence 同步方案
如何实现 OpenGL 资源共享?
https://mp.weixin.qq.com/s/-oC7782W_Sh0qxTQAZ7Y8g
在使用 eglCreateContext 时, 可以传入一个已创建成功的上下文, 这样就可以得到一个共享的上下文 (Shared Context)。
可以共享的资源:
纹理;
shader;
program 着色器程序;
buffer 类对象,如 VBO、 EBO、 RBO 等 。
不可以共享的资源:
- FBO 帧缓冲区对象(不属于 buffer 类); VAO 顶点数组对象(不属于 buffer 类)。 在不可以共享的资源中,FBO 和 VAO 属于资源管理型对象,FBO 负责管理几种缓冲区,本身不占用资源,VAO 负责管理 VBO 或 EBO ,本身也不占用资源。
OpenGL 纹理缓存要如何设计?
https://mp.weixin.qq.com/s/-oC7782W_Sh0qxTQAZ7Y8g
需要一个可复用的纹理数组,设置一个最大上限。
每个纹理需要忙碌或空闲的状态,当空闲情况下可以进行复用。
一个 FBO 频繁更换绑定不同的纹理,将内容数据刷新到指定纹理上。
外层纹理使用完成后将纹理状态设置为空闲。
介绍一下 glReadPixels?
https://mp.weixin.qq.com/s/QyK-l2LX-xbauRop3AJ9xQ
glReadPixels 是 OpenGL ES 的 API,通常用于从帧缓冲区中读取像素数据,OpenGL ES 2.0 和 3.0 均支持。使用非常方便,但是效率也是最低的。
介绍一下 ImageReader?
https://mp.weixin.qq.com/s/QyK-l2LX-xbauRop3AJ9xQ
ImageReader 是 Android 中的一个类,用于获取相机设备的图像数据。它可以用于捕获相机拍摄的静态图像或实时预览帧,并提供对图像数据的访问和处理。
介绍一下 PBO(Pixel Buffer Object)?
https://mp.weixin.qq.com/s/QyK-l2LX-xbauRop3AJ9xQ
OpenGL PBO(Pixel Buffer Object),被称为像素缓冲区对象,主要被用于异步像素传输操作。PBO 仅用于执行像素传输,不连接到纹理,且与 FBO (帧缓冲区对象)无关。OpenGL PBO(像素缓冲区对象) 类似于 VBO(顶点缓冲区对象),PBO 开辟的也是 GPU 缓存,而存储的是图像数据。PBO 是 OpenGL ES 3.0 开始提供的一种方式,主要应用于从内存快速复制纹理到显存,或从显存复制像素数据到内存。
介绍一下 HardwareBuffer?
https://mp.weixin.qq.com/s/QyK-l2LX-xbauRop3AJ9xQ
HardwareBuffer 官方介绍为一种底层的内存 buffer 对象,可在不同进程间共享,可映射到不同硬件系统,如 GPU、传感器等,从构造函数可以看出,其可以指定 format 和 usage,用来让底层选择最合适的实现,目前 format 主要是渲染相关的纹理格式,Android 11 之后支持了 BLOB 格式,可用来做 NN 相关的数据共享。
使用 OpenGL PBO 为什么能提高效率?
https://mp.weixin.qq.com/s/N-PzPA4rSHGtE6KIjnEX4Q
减少 CPU 等待:PBO 支持异步传输,这意味着 CPU 在发起传输请求后不必等待 GPU 完成传输,可以继续执行其他任务。
减少数据拷贝:使用 PBO 可以减少从 CPU 内存到 GPU 内存的数据拷贝次数。例如,当更新纹理时,可以先将数据复制到 PBO,然后由 GPU 直接从 PBO 读取,而不是每次都从 CPU 内存中复制。
提高带宽利用:PBO 允许更有效地利用内存带宽,因为它减少了 CPU 和 GPU 之间的数据传输量。
优化显存利用:使用 PBO 可以避免在每次更新纹理时销毁和重新创建纹理内存,从而优化显存的利用率。
双缓冲或多缓冲技术:通过使用两个或多个 PBO,可以在一个 PBO 进行 GPU 操作的同时,使用 CPU 填充另一个 PBO,从而实现更高效的流水线操作。
什么是 VAO,什么是 VBO,它们的作用是什么?
https://mp.weixin.qq.com/s/sKvPn2pLRYdJzx6RepKp1Q
VBO
VBO 主要用于存储顶点数据,如顶点坐标、法线、颜色等。
通过将顶点数据存储在 GPU 的显存中,可以提高渲染效率,因为 GPU 能够更快地访问这些数据,而无需反复从 CPU 内存中读取。
VAO
VAO(Vertex Array Object)顶点数组对象,主要作用是用于管理 VBO 或 EBO。
VBO 保存了一个模型的顶点属性信息,每次绘制模型之前需要绑定顶点的所有信息,当数据量很大时,重复这样的动作变得非常麻烦。VAO 可以把这些所有的配置都存储在一个对象中,每次绘制模型时,只需要绑定这个 VAO 对象就可以了,可以减少 glBindBuffer 、glEnableVertexAttribArray、 glVertexAttribPointer 这些调用操作,高效地实现在顶点数组配置之间切换。
纹理有哪些环绕方式(wrapping)?
https://mp.weixin.qq.com/s/JbEGcM95fL3i7g30dTDtOw
重复(GL_Repeat)
镜像重复(GL_MIRRORED_REPEAT)
夹取到边缘(GL_CLAMP_TO_EDGE)
夹取到边框(GL_CLAMP_TO_BORDER)
调试 OpenGL 特效的时候图像不对,有什么调试技巧能快速排查原因?
- https://mp.weixin.qq.com/s/ktWZ0kTtVO8iqhTIYqTJ3w
自己实现播放器时利用 FFmpeg 拿到解码后数据封装成 CVPixelbuffer 缓存用于渲染,但是缓存后数据只有几帧,但为什么内存占用有时候会有几百兆?
https://mp.weixin.qq.com/s/ktWZ0kTtVO8iqhTIYqTJ3w
自己创建
CVPixelBuffer
缓存即使你在程序中及时释放但是系统真正释放的时机可能会延迟,导致占用内存过高。
直播推流
直播打开成功率如何优化?
- https://mp.weixin.qq.com/s/xjChNR6Y0zgJzmyS-2n8gQ
直播播放秒开如何优化?
- https://mp.weixin.qq.com/s/xjChNR6Y0zgJzmyS-2n8gQ
直播播放卡顿如何优化?
- https://mp.weixin.qq.com/s/xjChNR6Y0zgJzmyS-2n8gQ
直播延时如何优化?
- https://mp.weixin.qq.com/s/xjChNR6Y0zgJzmyS-2n8gQ
直播美颜如何实现?
- https://mp.weixin.qq.com/s/IKDY_slQg_J_01xyE_wENA
直播间礼物特效的如何实现?
- https://mp.weixin.qq.com/s/IKDY_slQg_J_01xyE_wENA
直播连麦的如何实现?
- https://mp.weixin.qq.com/s/IKDY_slQg_J_01xyE_wENA
直播间的回声消除如何实现?
- https://mp.weixin.qq.com/s/IKDY_slQg_J_01xyE_wENA
在实现类似 OBS 的实时的图片、GIF 贴片叠加和替换效果时遇到了性能瓶颈,请问如何实现快速的 GIF 贴片叠加和替换?
- https://mp.weixin.qq.com/s/ktWZ0kTtVO8iqhTIYqTJ3w
视频生产及编辑
视频编辑中如何实现视频倒放?
如果用最直接的思路去实现视频倒放,那就是把视频中的每一帧图像都解码出来逆排序一下,然后将原视频的 pts 时间戳一一对应的关联上逆排序后的每一帧,再重新编码就可以了。
这个思路在实际实现时会有几个问题:解码后的视频帧放在内存或磁盘可能都还挺大的;完整的解码、逆排序、编码一整个视频可能耗时较长。
对于这些问题我们可以采取分而治之、并行提效的思路。因为视频本身可以按照 GOP 单元独立编解码,所以我们可以把视频的每一个 GOP 单元取出来分别做解码、逆排序、编码,最后再把处理后的所有 GOP 重新 remux 封装起来即可。
其中逆排序过程中,对于一个 GOP 的各帧处理流程大致是这样的:比如一个 GOP 的各视频帧及对应的 pts 分别是
1(0), 2(30), 3(60), 4(90)
,那逆排序后就是4(0), 3(30), 2(60), 1(90)
。
拍短视频想把同时播放的音乐录制下来一般要经过怎样的处理流程?
有一种方案是把麦克风采集声音中的外放音乐声进行回声消除,然后再添加上音乐的原始音轨。一般在 iOS 设备上可以考虑这种方案。但是在 Android 设备上我们通常不这样做,原因有下面几点:
对音乐进行回声消除的同时也会对麦克风采集到的其他声音有抑制效果,导致最后整体的声音效果不好;
手机播放音乐到麦克风采集到声音之间有一定的延时,不同的 Android 设备的延时差异较大,这个延时估算不准确会影响回声消除的效果。
一般情况我们可以按照下面的流程来处理:
1)音乐外放的情况,直接通过外放播放音乐,声音通过麦克风录制下来;
2)戴耳机或手机静音的情况,音乐不会被麦克风录制下来;
3)录制完成时,将录制得到的视频中的音轨(这里面可能包含已经和其他外音被采集下来的音乐)和音乐原始的音轨进行叠加增强。
Android Camera 如何优化视频录制的卡顿?
1)视频录制流程
- 打开
Camera
。
- 打开
- 创建
SurfaceTextue
,将Camera
输出的数据渲染到SurfaceTextue
。
- 创建
SurfaceTexture
拿到的结果进行特效处理。
- 特效处理的结果异步分发给
RenderView
预览与MediaCodec
编码。
- 特效处理的结果异步分发给
- 编码后的结果进行
Muxer
合成MP4
视频。
- 编码后的结果进行
2)视频录制流程优化
- 相机、编码根据不同机型控制不同帧率、分辨率。
- 实现丢帧模块,将采集后的帧进入丢帧模块进行控制帧率,降低渲染以及编码性能。
- 采集、渲染、编码按照多个线程处理,每个模块发挥最优性能。
- 预览视图优先采用
SurfaceView
,少量使用TextureView
,因为TextureView
占用主线程渲染。
- 预览视图优先采用
- 编码优先使用
Surface
异步编码。
- 编码优先使用
转码速度优化的几点建议?
1)解码
- 优先使用系统硬件解码,软件解码仅兜底使用。
- 解码方式优先使用异步。
- 解码器可以创建复用池。
- Android 解码优先考虑
Surface
解码方式。
- Android 解码优先考虑
2)多线程并发
- 转码分为解码与编码,通常编码更加耗时,这样解码线程可保持几帧
Cache
数据供编码获取。
- 转码分为解码与编码,通常编码更加耗时,这样解码线程可保持几帧
- 同一个视频可以分片转码为
FMP4
,最终拼接为一个FMP4
,并发数目设置参考 2-5。
- 同一个视频可以分片转码为
3)其它
- 码率不高的视频无需转码。
- 对于帧率过高、码率过高视频可降低相关参数,提高转码速度。
- 视频转码与发布可结合优化策略,例如一边分片转码一边上传。
- 针对不同场景进行指定策略处理,例如添加了音乐仅转码音频,仅转换封装格式使用
Remuxer
。
- 针对不同场景进行指定策略处理,例如添加了音乐仅转码音频,仅转换封装格式使用
Seek 优化的几点建议?
1)解码
- 优先使用系统硬件解码,软件解码仅兜底使用。
- 解码方式优先使用异步。
- 解码器可以创建复用池。
- Android 解码优先考虑
Surface
。
- Android 解码优先考虑
- 对于 Seek 过程中的帧可以不输出数据,例如 iOS
kVTDecodeFrame_DoNotOutputFrame
。
- 对于 Seek 过程中的帧可以不输出数据,例如 iOS
2)其它
- 对于 Seek 过程中的帧可以丢弃非参考帧。
- 优先判断 Seek 帧是否命中缓存。
- 优先找到 Seek 帧最近的关键帧。
- 对于向右侧 Seek 优先判断是否与当前解码帧同一个
GOP
,同一个GOP
无需Flush
,继续解码即可。
- 对于向右侧 Seek 优先判断是否与当前解码帧同一个
- 对于 Seek 位置精准度可以给一点空隙,例如 100ms 内偏差用户无感,这样可以利用缓存优势以及最近关键帧。
- 对于 Seek 超级频繁可以选择丢弃某些帧。
音视频编辑 SDK 一般包含哪些模块?各模块是什么职责?
音视频编辑中转码流程 pipeline 的线程模型和缓冲区要怎么设计?
如何降低处理音视频链路中的内存峰值?
https://mp.weixin.qq.com/s/-cwQbkbHqmMKRPk0YhBG6g
降低采集参数:
降低采集视频分辨率
降低采集视频帧率
降低并发任务数量:
- 将任务分优先级,按照优先级串行执行,这样既能降低内存峰值,也会降低 CPU 峰值
在编辑 SDK 中的播放器和播放 SDK 中的视频播放存在哪些区别呢?编辑场景的播放器对 OpenGL 的使用有哪些进阶的用法?
https://mp.weixin.qq.com/s/HlLwri7rFNSs9C6rxZ90gw
剪辑方向的视频播放与播放器的视频播放相比最大的区别就是:需要处理更复杂渲染场景。
处理复杂的输入和渲染。
处理多线程渲染。
渲染流程结构可以做优化设计。
调试和报错。
iOS 动态图片如何获取原始视频?
- https://mp.weixin.qq.com/s/ktWZ0kTtVO8iqhTIYqTJ3w
为什么自制的动态图片导出到相册无法识别成动态图片?
- https://mp.weixin.qq.com/s/-MeeGfwy3MDiZsuxL-VKHw
iOS 如何实现音频内录,录制当前所有手机的声音集合?
https://mp.weixin.qq.com/s/-MeeGfwy3MDiZsuxL-VKHw
使用 ReplayKit。
应该选择哪种复杂字体渲染的方案 (非 UIView 的能力)?
- https://mp.weixin.qq.com/s/-MeeGfwy3MDiZsuxL-VKHw
iOS 如何在不解码的情况下给视频添加 Metadata?
- https://mp.weixin.qq.com/s/-MeeGfwy3MDiZsuxL-VKHw
播放器
点播的倍速播放要如何实现?
点播的倍速播放分为视频处理和音频处理部分。
1)视频处理
对应视频数据的处理,核心逻辑就是按照倍速重新计算各视频帧的 pts 时间戳。
比如,对一个视频做 2 倍速播放,假设原来各视频帧的 pts 依次是
0, 30, 60, 90 ...
,倍速处理及将它们除以 2 变成0, 15, 30, 45 ...
。这样处理后,视频的帧率和总时长相应的也发生了变化,帧率变为原来的 2 倍,总时长变为原来的 1/2。但是,如果对视频进行高倍速播放,比如 10 倍速,这时候如果只处理 pts,原视频的时间戳除以 10 变成
0, 3, 6, 9 ...
,这时候 3ms 一帧,帧率达到了 333fps,已经超过了屏幕硬件的刷新率,根本渲染不过来。所以对于高倍速播放:第一步,我们像上面一样在处理完 pts;第二步,我们还需要设定最大帧率的限制,并按照这个最大帧率来进行丢帧。假如我们设定最大帧率是 60fps,这时候我们每 17ms 只需要一帧,上面的
0, 3, 6, 9, 12, 15, 18 ...
经过丢帧和帧率处理可能就变成了0, 17(18→17), 34(33→34) ...
。高倍速播放还有另外的问题:解码性能是否跟得上、网络视频的下载速度是否跟得上等等。对于这些问题,我们可能还需要其他方案来解决,比如:在客户端在解码前就要丢弃非参考帧,对不需要解码的帧直接丢弃等等;在服务端对高倍速视频进行预处理,提前做好时间戳和丢帧处理,当用户切换高倍速时,帮用户切换资源即可。
2)音频处理
- 音频是每秒几 K 到几十 K 的采样点,对于音频数据的处理,如果只是跟视频一样,简单的处理 pts 时间戳会出现噪音、杂音,体验很差。音频一般需要进行重采样处理。比如,原来的音频是 48K 的采样率,播放设置了使用 48K 的采样率进行音频渲染,这时候要对音频做 2 倍速播放,可以将音频数据每秒 48K 个采样点重采样降低到 24K 个(把音频数据的采样率处理为 24K),当播放还是使用 48K 的采样率来播放时,每秒需要 48K 个采样点,这时候就需要 2s 的数据,这时候音频的播放速度就变成了原来的 2 倍。使用这样的方式来实现音频倍速,可以解决只简单处理 pts 带来的噪音、杂音问题,但是音频的播放会变调:快播时,声调会变高,听起来尖细;慢播时,声调会变低,听起来低沉。
播放器解码后的帧缓冲区一般设置多大合适?
对于播放器来说,在渲染之前一般会有一个解码后的帧缓冲区用于后续渲染。
使用 FFmpeg 软解码、Android MediaCodec 硬解码时,给编码器的数据是按照 dts 顺序输入的,解码器输出数据是按照 pts 输出的。但是使用 iOS VideoToolbox 硬解码时,解码器输出数据并没有按照 pts 顺序,而是解一帧出一帧,需要我们自己排序。
这样在使用 iOS VideoToolbox 硬解码时,还可以在这个缓冲区还可以用来对解码后的帧按 pts 做重排来保证正确的渲染顺序。
这个重排主要是针对 B 帧,因为 B 帧可能会依赖后面的帧才能完成解码,如果解码后的帧缓冲区太小,可能导致按照渲染顺序本该渲染的 B 帧由于依赖帧还未解码而出现播放卡顿。但是解码后的帧缓冲区也不是越大越好,因为解码后的视频帧数据是比较大的,会占用不少内存,缓冲区过大会造成播放器的内存占用过大。
那么这个缓冲区应该设置多大比较合适呢?这个其实取决于解码器需要的重排窗口大小,解码后的帧缓冲区大小只要不超过这个重排窗口尺寸即可。要计算重排窗口的大小通常可能会用到下面这几个参数:
max_ref_frames
max_num_reorder_frames
如何监控视频播放黑屏、花屏、绿屏等异常?
视频播放时如果遇到黑屏、花屏、绿屏通常会伴有解码器的报错或异常信息,我们可以上报这些异常信息来实现对这些情况的监控。
但是也有一些情况,即使出现黑屏、花屏、绿屏的情况了,解码器也没有报错或异常,这时候就需要我们对解码后的画面进行检测来识别这些问题。一般可以这样:
- 用传统的图像处理算法来识别。对于黑屏、绿屏可以用传统图像处理算法来进行识别,但这里也会有一些误识别的问题,比如视频本身就有些亮度较低、正常全黑帧、正常全绿帧的情况,可能也会被识别为异常。
- 训练 AI 模型类识别。对于花屏,可以训练 AI 模型来进行识别。训练过程可以通过人工构造丢帧视频的方法来生成花屏样本,同时筛选出无花屏问题的正常样本,基于这两类样本来做二分类模型的训练。
如何通过优化播放器来优化音乐播放体验,比如提升音质或音效?
在播放侧可以使用自动增益控制算法(AGC)来提升音效。AGC 算法通过自动调整音频信号的增益,使其保持在一定的范围内,这种算法可以避免因音频信号的幅度变化而引起的声音过大或过小的问题,保证了音频信号的稳定性和可听性,目前有开源的实现例如 webrtcagc,可以把算法移植到自己的项目中。
如果让你设计一个播放器的架构,你会分哪几层?
FFmpeg 中 avformat_open_input() 经历了什么步骤?
https://mp.weixin.qq.com/s/lYKGrt4rOISncyFwYRM2cQ
打开输入媒体流
初始化输入媒体流
探测输入格式,根据当前参数进行打分,最终根据分数最高的那个配置作为最终格式
打开 avio
综合多媒体框架及服务
WebRTC 中的 ICE 作用?
https://mp.weixin.qq.com/s/zujLvqBgOadxcZuer7uXJA
主要负责帮助位于不同网络环境(尤其是 NAT 之后)的两个端点建立直接的连接。
WebRTC 中媒体协商过程?
https://mp.weixin.qq.com/s/zujLvqBgOadxcZuer7uXJA
创建 Offer
设置 Local Description
发送 Offer
接收 Offer 和创建 Answer
设置 Remote Description
设置 Answer 的 Local Description
发送 Answer
接收 Answer 和设置 Remote Description
ICE Candidate 交换
连接建立和媒体流传输
WebRTC NAT 有几种类型?
https://mp.weixin.qq.com/s/zujLvqBgOadxcZuer7uXJA
完全锥型(Full Cone NAT)
地址限制锥型(Address-Restricted Cone NAT)
端口限制锥型(Port-Restricted Cone NAT)
对称型 NAT(Symmetric NAT)
WebRTC 中的 GCC 机制?
https://mp.weixin.qq.com/s/zujLvqBgOadxcZuer7uXJA
一种重要的拥塞控制机制,用于自适应地调整视频流的码率,以适应网络条件的变化,确保音视频通信的流畅性和清晰度。
基于RTCP反馈的码率控制
REMB(Receiver Estimated Maximum Bitrate)
发送端的码率控制
工具
介绍一下 SIMD 以及它在音视频处理中的应用?
SIMD(Single Instruction Multiple Data)是一种并行计算的技术,它允许在单个指令中同时处理多个数据元素。SIMD 指令集通常由处理器提供,用于加速向量化计算,从而提高程序的性能。
1)在音频处理中,SIMD 可以用于实时音频效果处理,如均衡器、压缩器、混响器等,通过同时处理多个音频样本,可以提高音频处理的效率和实时性。
2)在视频处理中,SIMD 可以用于加速图像处理算法,如图像滤波、边缘检测、图像压缩等,通过同时处理多个像素,可以提高图像处理的速度和质量。
3)在视频编码中,SIMD 可以用于加速压缩和解压算法,如 H.264、H.265 编码器一些实现中,可以通过并行处理视频数据来提高视频编解码的效率和性能。