技术文档/从零做一个全平台播放器
从零做一个全平台播放器
按施工顺序重写的全新系列,七章:第一章 与平台无关的播放器(框架与数据源、解码接入与解码器选型、渲染分层与 Vulkan 出图、音频渲染与音画同步);第二章到第四章按平台竖着讲 Windows、Android、Apple,每章四篇(硬解接入、硬解帧的两条去处、这个平台的采集设备、这个平台的硬编);第五章 WebRTC;第六章 播放器进阶(seek 与变速、字幕、轨道切换、HDR、接进宿主);第七章 录制:帧源、软编、封装与推流。
第一章 与平台无关的播放器
- 播放器的线程骨架与实现注册机制1.1 播放器的线程骨架与实现注册机制:播放器里哪些类是一条线程,可替换的实现都挂在注册表上,日志第一天就要有。约 1857 字
- 播放器的命令队列与播放状态机1.2 播放器的命令队列与播放状态机:队列只保证顺序、不保证执行完,入队成功也不一定被执行,九个状态不是一条链。约 2766 字
- 播放器的数据源与码流包1.3 播放器的数据源与码流包:源对外只有打开、关闭与几个查询,一个基类下面两族实现、六条腿;解封装把容器和协议消化掉,交出单帧码流包;包上带什么、配置帧与关键帧标志两条规矩;直播这一路的特殊之处收在末节。约 2840 字
- 播放器的码流到帧链路1.4 播放器的码流到帧链路:两级队列各挡什么、深度照着什么量;再沿链按三段走一遍(IO / 解码 / 渲染),每段讲它做什么、动哪条队列、出问题时两条队列变成什么样;最后落到帧队列空了凭什么判断是真没数据还是解码跟不上。约 2798 字
- 播放器的解码接入与解码器选型1.5 播放器的解码接入与解码器选型:解码接口只有一套,帧分 CPU 与 GPU 两种落点;解码器按候选链选与退——首选名、备选、回退名、首项兜底。约 3165 字
- 播放器的渲染分层与 Vulkan 出图1.6 播放器的渲染分层与 Vulkan 出图:协调者、像素处理、呈现面三个角色各管什么,继承链为什么自下而上叠,一帧和节拍为什么反向;两条出图路各落到哪条链上,走 Vulkan 那条的节拍、上屏五步与交换链的取值。约 2639 字
- 播放器的音频渲染与设备输出1.7 播放器的音频渲染与设备输出:音频的节拍与进度由设备决定,两条线程两级队列,设备层四条平台实现——三条自己线程推、一条等设备来拉;交出去之前过格式与重采样几道关,播放位置从队列积压倒推。约 2225 字
- 播放器的音画同步与时钟1.8 播放器的音画同步与时钟:两条链的节奏怎么对齐到系统时间上(视频按帧率、音频由设备);时钟里存什么、主时钟默认取音频,视频侧改等待与丢帧、音频侧改送出去的数据量,起播就对不齐的源干脆关掉同步。约 2260 字
第二章 Windows
- 播放器的硬解抽象与 Windows 的 D3D11 实现2.1 播放器的硬解抽象与 Windows 的 D3D11 实现:硬解留给平台的两处是解码设备与出帧——建解码设备、在出帧那一刻把原生纹理包成 GPU 帧;四条硬解车道不是一种做法,安卓与苹果自己对接原生接口,Windows 与 Vulkan 借 FFmpeg 的封装,Windows 这个实现借 d3d11va 建设备并交给渲染侧复用同一台,纹理数组里那一张先拷进自建纹理再派发,尺寸变化要挡住初次赋值的误判。约 2396 字
- Windows 上硬解帧的两条去处2.2 Windows 上硬解帧的两条去处:平台像素层先把 NV12/P010 转成一张共享纹理,导进 Vulkan 管线那条靠 NT 句柄与共享栅栏导入,读方在 CPU 上等写方一手;不进 VK 那条由窗口自己的交换链贴成全屏四边形呈现,节奏靠垂直同步错开。约 1713 字
- Windows 的采集设备:屏幕、摄像头与声卡2.3 Windows 的采集设备:屏幕、摄像头与声卡。屏幕与窗口共用一套抓取实现,主路是 WinRT 图形捕获、回退是 GDI;摄像头走 Media Foundation 异步读、只认几种平面与打包 YUV;声音两套(麦克风事件驱动、声卡回环只能轮询)。采出来的帧:屏幕给纹理、摄像头给 CPU 平面、音频给 PCM 块。约 1946 字
- Windows 的硬编:编码器选型与输入这条路2.4 Windows 的硬编:编码器选型与输入这条路。首选名是自研那两个、但实现只留了壳,候选链落到 FFmpeg 带的那几个硬件编码器上;打开时按能否建出硬件设备在 QSV/AMF/NVENC 里挑一个,都不行才回落到 Media Foundation 那个封装。帧进编码器前要先回读成内存——纹理直喂那条路在仓里只有设计。约 1775 字
第三章 Android
- Android 的硬解接入:MediaCodec 与 Surface 生命周期3.1 Android 的硬解接入:MediaCodec 与 Surface 生命周期。JNI 边界一次抓齐 Java 方法、反向回调留一个门;打开分创建、配置、启动三段,参数集要先转成起始码形式且只改本地副本;Surface 有两条来路,失效统一走一个重置标志;seek 时这个实现可以原地 flush,别的硬解必须重建。约 1519 字
- Android 上硬解帧的两条去处:GL 与 VK 并存3.2 Android 上硬解帧的两条去处:GL 与 VK 并存。外部纹理先由平台渲染器转成一张 RGBA,一条路自建 EGLSurface 直接上屏,另一条把这块显存交给 Vulkan;跨过去的不是纹理对象而是一块 AHardwareBuffer,同一个缓冲在两个 API 各有一个视图;重建判据要连 EGL 上下文一起算。约 1572 字
- Android 的采集设备:Camera2 与 AudioRecord3.3 Android 的采集设备:Camera2 与 AudioRecord。相机的格式白名单只有一种平面格式,输出面走外部纹理(不用取帧接口);回调只置标志,取帧在自己的线程里,标志合并等于隐性丢帧;音频落在 Java 侧,同步读、没有队列;两条路都没有自动重连。约 1654 字
- Android 的硬编:MediaCodec 吃 GPU 纹理3.4 Android 的硬编:MediaCodec 吃 GPU 纹理。输入有两条路:平面帧拷进队列、纹理帧画进编码器的输入面(要用较新的系统接口,用上就不能再入队);画进去那一步要设成微秒时间戳,并按帧里带没带共享显存选采样器;产物带四字节起始码前缀,配置帧由系统回调给出。约 1607 字
第四章 Apple
- Apple 的硬解接入:VideoToolbox 与 ObjC 边界4.1 Apple 的硬解接入:VideoToolbox 与 ObjC 边界。类还是 C++ 的、只有实现文件是 ObjC++,唯一用到 ObjC 对象字面量的地方是建会话的属性字典;参数集按编码分流转成格式描述;输出像素格式有两处宁可放弃车道的判断;那一族黑屏花屏的根因在层与格式这一层。约 1510 字
- Apple 上硬解帧的两条去处:Metal 与 MoltenVK4.2 Apple 上硬解帧的两条去处:Metal 与 MoltenVK。纹理缓存按平面取两张纹理、借来的纹理与加引用的缓冲都活到命令缓冲跑完;交给 Vulkan 时共享的是那个面而不是纹理对象,而且没有跨 API 的显式同步;MoltenVK 上空的着色器模块、面的行对齐与缺兼容开关这几处会直接崩。约 1555 字
- Apple 的采集设备:AVCapture 音视频4.3 Apple 的采集设备:AVCapture 音视频。相机与麦克风落在同一套采集会话上,挂输入有固定六步;格式白名单只有一种平面格式,预设与上层分辨率是两套独立选择;回调挂在串行队列上、丢帧不上报;关流不停会话(为换相机秒切),没有中断与拔插监听。约 1516 字
- Apple 的硬编:VideoToolbox4.4 Apple 的硬编:VideoToolbox。压缩会话配实时、档次、不允许帧重排、平均码率、期望帧率与最大关键帧间隔;输入三条路(带共享面、系统像素缓冲、内存平面);产物四件事——取码流、算时间戳、判丢帧、判关键帧,配置帧要自己在关键帧那一刻补发。约 1535 字
第五章 WebRTC
- 编译 WebRTC:环境、拉代码与各平台的构建脚本5.1 编译 WebRTC:环境、拉代码与各平台的构建脚本:depot_tools 拉、GN 配、ninja 跑;Android 的 STL 命名空间那道坎;接进工程的几根线(外部目录取头文件与库、MT 运行库、openssl 分平台)。约 1629 字
- 把 WebRTC 的 3A 接进录音链:格式归一与分层5.2 把 WebRTC 的 3A 接进录音链:格式归一是改写不是拒收(44100 改 48000、转 S16、声道收到二、攒成 10ms 一段);重采样与分段沉基类、算法放子类;接在音频源与渲染两处。约 1539 字
- 自己的解码器与渲染,接进 WebRTC5.3 自己的解码器与渲染,接进 WebRTC:解码工厂换成自己的,H265 与 AAC 它没有;接管帧分 CPU 与原生两种形态;时间戳两头都要设,否则被判跟不上。约 1599 字
- 跨平台双向视频通话:五段链路与码流格式的统一5.4 跨平台双向视频通话:拉流、解码、图像处理、编码、推流五段各自可换;annexb 与 avcc/hvcc 先判定再按下游转,配置帧顺序 VPS→SPS→PPS。约 1560 字
- 自己的采集与硬编,接上 WebRTC 的轨道与编码器5.5 自己的采集与硬编,接上 WebRTC 的轨道与编码器:四个适配器各接一个;一次采集预览与编码各取一份;码率与关键帧都由 WebRTC 回填。约 1532 字
第七章 录制:帧源、软编、封装与推流
- 播放器的原始帧源与外部纹理入口7.1 播放器的原始帧源与外部纹理入口:原始帧源与编码数据源分在两条枝上,源交出来的帧只有 CPU 帧、GPU 纹理与音频数据块三种形态;采集的帧落在内存或纹理,下游按类型分流;外部纹理走渲染层那条口子进来,采集卡那一支则整块做成插件、往设备表里注册自己的槽位。约 2163 字
- 播放器的录制链与软件编码器接入7.2 播放器的录制链与软件编码器接入:录制链把解码源、离屏处理与编码器三件装配在一起,一帧从回调进来先过离屏处理、再回读成 CPU 帧入队;帧队列满了阻塞解码而不丢帧;软件编码器走注册表与候选链,开不起来才在首帧前换道;像素格式由出图侧定,时间戳原样带过。约 1911 字
- 播放器的封装层:三个层次、写线程与时间戳7.3 播放器的封装层:三个层次、写线程与时间戳——接口、只收包的一层、加编码的一层,写出去只有一条队列一条线程,时间戳要先首包归零再换到流自己的时间基;另外两种用法是不解码的转封装与单轨直拷,以及不落文件时的取帧与抽音频。约 1676 字
- 播放器的推流:同一个封装,目标换成一行地址7.4 播放器的推流:同一个封装,目标换成一行地址——录制与推流共用同一个封装实现与同一条流程,差别只在输出那一行地址;封装格式先按后缀猜、猜不到按协议映射(rtmp 系列映射成 flv);起流与写出去的失败都当场处置,推流的快慢由源与写出去的速度决定。约 1525 字