封装

视频是什么?你可能知道 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: 这两行后面的内容我们暂时还不能读懂,但猜测是对视频数据和音频数据的参数的一些详细解读。

关于 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 其实目前有 三种分类:

  1. YIQ 适用于 NTSC 彩色电视制式
  2. YUV 适用于 PAL 和 SECAM 彩色电视制式
  3. YCbCr 适用于计算机用的显示器

我们做互联网音视频开发, 一般说的 YUV 是 指 YCbCr ,U 就是 Cb,V 就是 Cr。
大家经常在一些音视频书籍看到 YCbCr ,把它当成是 YUV 就行。实际上 YCbCr 才是比较准确的术语,JPEG、MPEG 标准 用的也是 YCbCr 。
本文后面说讲的 YUV 也是指 YCbCr ,不是指用于 PAL 和 SECAM 彩色电视的 YUV。

YUV 读取

YUV 可以通过软件 7YUV 打开,如图是一个 YUV 图片:

YUV图片

需要注意的是,YUV 格式的图片需要指定宽高,否则会展示异常。

YUV VS RGB

RGB, YUV 都是用于记录视频/图片数据的。
YUV 中最重要的是 Y,因为人眼对亮度比较敏感,UV 就是色彩信息。两个例子证明:

  1. 在黑暗中,颜色没有意义,因为看不到。
  2. 早年时候存在的黑白电视机就没有 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播放视频流程详解
视频和视频帧:视频和帧基础知识整理