封装
视频是什么?你可能知道 MP4,他是视频常用模式,你可能也听说过 AVI,FLV 等等很多视频格式,但她们内部是怎么构成的,你又不完全清楚。
这里先和大家分享下,我们知道的这些视频文件格式其实只是封装方式。视频文件内部包含了视频数据和音频数据,把他们通过某种方式封装起来,使得视频和音频数据同步就是封装要做的事情。
工具
视频播放涉及太多知识,泛泛而谈毫无意义,比如上一节,我说了封装,但你听了以后肯定还是摸不着头脑。
但如果有个工具可以边学习边操作理解起来就容易的多,这里我选择 FFmpeg。当然,OpenCV,VLC 原理也都差不多,学习其中的任何一种即可举一反三。
下载 FFmpeg
直接官网下载即可:https://ffmpeg.org/,在 download 选项下,找到您对应平台的二进制文件即可。
代码时间
有了FFmpeg,我这边直接通过如下命令来表明,视频文件是通过封装视频数据和音频数据来实现的:
./ffmpeg -i LRV_20230927_182949_01_029.mp4 ffmetadata FFMETADATAFILE
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'LRV_20230927_182949_01_029.mp4':
Metadata:
major_brand : avc1
minor_version : 0
compatible_brands: avc1isom
creation_time : 2023-09-27T18:29:39.000000Z
cycle : 16
gop : 16
Duration: 00:00:12.88, start: 0.000000, bitrate: 13071 kb/s
Stream #0:0[0x1](eng): Video: h264 (Main) (avc1 / 0x31637661), yuvj420p(pc, bt709, progressive), 368x640 [SAR 1:1 DAR 23:40], 3948 kb/s, 24 fps, 24 tbr, 24k tbn (default)
Metadata:
creation_time : 2023-09-27T18:29:39.000000Z
handler_name : Ambarella AVC
vendor_id : [0][0][0][0]
encoder : Ambarella AVC encoder
Side data:
displaymatrix: rotation of 90.00 degrees
Stream #0:1[0x2](eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 192 kb/s (default)
Metadata:
creation_time : 2023-09-27T18:29:39.000000Z
handler_name : Ambarella AAC
vendor_id : [0][0][0][0]
其中 Stream #0:0[0x1](eng): Video:
表示的就是视频流;Stream #0:1[0x2](eng): Audio:
表示的就是音频流。
小结
FFmpeg 看起来很强大,能直接知道读出视频文件中的视频部分和音频部分,虽然 Stream #0:0[0x1](eng): Video:
和 Stream #0:1[0x2](eng): Audio:
这两行后面的内容我们暂时还不能读懂,但猜测是对视频数据和音频数据的参数的一些详细解读。
帧
帧(Frame)是视频的基本组成单元。视频是由一系列连续播放的静态图像(帧)组成的,通过快速连续播放这些帧,我们可以看到平滑的运动效果。通常,帧以每秒显示的帧数(FPS, Frames Per Second)来衡量。
在视频编码中,帧不仅仅是简单的图像,还可以分为不同类型,它们在视频压缩和解码过程中起到不同的作用。视频编码中的帧通常分为三种主要类型:I帧(Intra-frame)、P帧(Predictive frame) 和 B帧(Bidirectional frame)。它们在编码时的压缩效率和解码复杂度不同。
帧类型 | 定义 | 特点 | 优缺点 |
---|---|---|---|
I 帧 | 自我包含的完整帧 | 独立解码,不依赖其他帧 | 占用空间大,解码简单,通常用作关键帧 |
P 帧 | 预测帧,依赖前面的 I 帧或 P 帧 | 只存储差异,压缩效率较高 | 解码依赖于参考帧,压缩效率中等 |
B 帧 | 双向预测帧,依赖前后帧 | 双向参考,压缩效率最高 | 解码依赖前后帧,压缩效率最高,复杂度高 |
关于 FFmpeg
FFmpeg 并不能直接操作音视频,而是操作 PCM 或者 YUV
随之而来的问题:
YUV 是什么
无论是 RGB 还是 YUV ,他们都是 表达 色彩信息的一种方式。
首先,讲一下为什么 YUV 色彩空间 在音视频开发,编码压缩领域更加常用,这个涉及到 HVS (Human Visual System)人类视觉系统 对 色彩空间的感知能力。
视觉心理学研究表明,人的视觉系统对光的感知程度可以用两个属性来描述:亮度(luminance)跟 色度(chrominance),这里的色度也叫做 饱和度或彩度,总之 色度的叫法有很多,要注意上下文来区分语义。
然后 色度感知 包含两个维度:色调(Hue)和 色饱和度(saturation)。色调是由光波的峰值定义的,描述的是光的颜色。色饱和度是由光波的谱宽定义的,描述的是光的纯度。
因此 HVS 对色彩的感知主要有 3个属性:亮度(luminance),色调(Hue)和 色饱和度(saturation)。也就是 YUV 色彩空间,Y 代表 亮度,U代表色调,V代表色饱和度。
经过大量研究实验表明,视觉系统 对 色度 的敏感度 是远小于 亮度的。所以可以对 色度 采用更小的采样率来压缩数据,对亮度采用正常的采样率即可,这样压缩数据不会对视觉体验产生太大的影响。简单来说就是用更少的数据/信息来表达 色度(chroma),用更多的数据/信息来表达 亮度(luminance)。
YUV 分类
YUV 其实目前有 三种分类:
- YIQ 适用于 NTSC 彩色电视制式
- YUV 适用于 PAL 和 SECAM 彩色电视制式
- YCbCr 适用于计算机用的显示器
我们做互联网音视频开发, 一般说的 YUV 是 指 YCbCr ,U 就是 Cb,V 就是 Cr。
大家经常在一些音视频书籍看到 YCbCr ,把它当成是 YUV 就行。实际上 YCbCr 才是比较准确的术语,JPEG、MPEG 标准 用的也是 YCbCr 。
本文后面说讲的 YUV 也是指 YCbCr ,不是指用于 PAL 和 SECAM 彩色电视的 YUV。
YUV 读取
YUV 可以通过软件 7YUV 打开,如图是一个 YUV 图片:
需要注意的是,YUV 格式的图片需要指定宽高,否则会展示异常。
YUV VS RGB
RGB, YUV 都是用于记录视频/图片数据的。
YUV 中最重要的是 Y,因为人眼对亮度比较敏感,UV 就是色彩信息。两个例子证明:
- 在黑暗中,颜色没有意义,因为看不到。
- 早年时候存在的黑白电视机就没有 UV,只有 Y
为什么不使用 RGB 而是 YUV? 原因:YUV的优点是可以对其中两个分量CbCr进行采样而不太破坏图像的显示, rgb就不行会导致图像严重失真。
RGB 转 YUV
将一个宽高像素分别为 864x549 的 RGB 图片转成 YUV 命令如下:
ffmpeg -i mov_01.jpg -s 864x549 -pix_fmt yuvj444p mov_yuv_444.yuv
我们可以看到如下输出:
ffmpeg version N-115585-g7d46ab9e12 Copyright (c) 2000-2024 the FFmpeg developers
built with Apple clang version 15.0.0 (clang-1500.3.9.4)
configuration: --disable-optimizations --disable-stripping --enable-debug=3 --disable-doc
libavutil 59. 21.100 / 59. 21.100
libavcodec 61. 6.101 / 61. 6.101
libavformat 61. 3.104 / 61. 3.104
libavdevice 61. 2.100 / 61. 2.100
libavfilter 10. 2.102 / 10. 2.102
libswscale 8. 2.100 / 8. 2.100
libswresample 5. 2.100 / 5. 2.100
Input #0, png_pipe, from 'mov_01.jpg':
Duration: N/A, bitrate: N/A
Stream #0:0: Video: png, rgba(pc, gbr/unknown/unknown), 864x549, 25 fps, 25 tbr, 25 tbn
Stream mapping:
Stream #0:0 -> #0:0 (png (native) -> rawvideo (native))
Press [q] to stop, [?] for help
[swscaler @ 0x7f8246d49000] deprecated pixel format used, make sure you did set range correctly
Output #0, rawvideo, to 'mov_yuv_444.yuv':
Metadata:
encoder : Lavf61.3.104
Stream #0:0: Video: rawvideo (444P / 0x50343434), yuvj444p(pc, progressive), 864x549, q=2-31, 284601 kb/s, 25 fps, 25 tbn
Metadata:
encoder : Lavc61.6.101 rawvideo
Side data:
ICC Profile
[out#0/rawvideo @ 0x7f8246806800] video:1390KiB audio:0KiB subtitle:0KiB other streams:0KiB global headers:0KiB muxing overhead: 0.000000%
frame= 1 fps=0.0 q=-0.0 Lsize= 1390KiB time=00:00:00.04 bitrate=284601.6kbits/s speed=2.06x
说明转换成功了
MP4 格式
视频可以看作是一系列图片快速播放形成的,前面我们已经了解过图片格式,现在我们开始讲视频。
视频格式有很多种,我们先讲比较常见的 MP4 格式,这里先发挥读者的主观能动性:如果让你设计视频格式,你会怎么设计呢?
我们随便找个 MP4 文件,然后通过 MP4Box 工具或者 MP4 Explorer 工具查看 MP4 文件的构成。
PCM 是什么
IJKPlayer
IJKPlayer 简介
ijkplayer是一个基于 FFmpeg 和 SDL 的开源 Android/iOS 视频播放器,由 bilibili 开发。它支持几乎所有视频格式,具有高效的硬件解码和渲染能力,可以实现流畅的视频播放体验。ijkplayer 还提供了丰富的 API 接口,支持多种播放控制方法和事件监听,可以让开发者灵活地集成和定制播放器。此外,ijkplayer 还提供了一些强大的剪辑和水印功能,可以实现视频编辑和个性化定制。
ijkplayer 和 FFMpeg、SDL 的关系是如何的呢?简单来说 ffmpeg 提供最基础的解码能力,将视频文件解析成特定的二进制流数据(比如我们常见的RGB、YUV),而SDL负责将特定的二进制流文件渲染出来。
IJKPlayer 在 iOS 和 Android 平台上实现的主要差异表现在视频硬解码方案和音视频渲染方案上:
iOS
- 视频硬解码:VideoToolbox
- 音频渲染:AudioQueue
- 视频渲染:OpenGL ES
Android
- 视频硬解码:MediaCodec
- 音频渲染:OpenSL ES、AudioTrack
- 视频渲染:OpenGL ES、MediaCodec
IJKPlayer 项目的目录结构
- tool:初始化项目工程脚本。
- config:编译 FFmpeg 使用的配置文件。
- extra:存放编译 IJKPlayer 所需的依赖源文件, 如 FFmpeg、OpenSSL 等。
- ijkmedia:核心代码。
- ijkplayer:播放器数据下载及解码相关。
- ijksdl:音视频数据渲染相关。
- ios:iOS平台上的上层接口封装以及平台相关方法。
- android:android平台上的上层接口封装以及平台相关方法。
IJKPlayer 解码(硬解/软解)
IJKPlayer 在视频解码上支持软解和硬解两种方式,可在起播前配置优先使用的解码方式,播放过程中不可切换。IJKPlayer 中的音频解码只支持软解,暂不支持硬解。
问题一:bilibili 国际版硬解转软解
问题二:卡顿率(Buffer队列)
问题三:seek操作
问题四:各平台渲染引擎
Android : ANativeWindow
参考
解析 IJKPlayer
音视频 ijkplayer 源码解析系列1--播放器介绍
我们为什么使用DASH
FFmpeg播放视频流程详解
视频和视频帧:视频和帧基础知识整理