├── LICENSE ├── README.md ├── android ├── Android知识点.md └── SurfaceView和TextureView比较分析.md ├── network ├── HTTP2.0协议分析.md ├── QUIC协议分析.md ├── RTMP协议分析.md └── 传输协议分析.md ├── opengl ├── FBO纹理坐标系.png ├── GLSurfaceView流程图.png ├── openGL拓展知识点.md ├── openGL知识点.md └── opengl坐标系.png ├── player ├── 播放器卡顿优化.md ├── 播放器成功率优化.md ├── 播放器架构分析.md ├── 播放器知识点.md └── 播放器首帧速度优化.md └── 音视频平凡之路 ├── 01-音视频全链路技术栈.md ├── 02-files ├── 01.png ├── 02.png ├── 03.png ├── 04.png ├── 05.png ├── 06.png ├── 07.png ├── 08.png ├── 09.png ├── 10.png ├── 11.png ├── 12.png ├── 13.png ├── 14.png ├── 15.png ├── 16.png ├── 17.png ├── 18.png ├── hello ├── hello-android └── hello.c ├── 02-音视频基础知识.md ├── 03-files ├── 01.png ├── 02.png ├── 03.png ├── 04.png ├── 05.png ├── 06.png ├── 07.png ├── 08.png ├── 09.png ├── 10.png ├── 11.png ├── 12.png ├── 13.png ├── 14.png ├── 15.png ├── 16.png ├── 17.png ├── 18.jpeg ├── 19.png ├── 20.jpeg ├── 21.jpeg ├── 22.png ├── 23.png ├── 24.png ├── 25.png ├── 26.png ├── 27.png ├── 28.png ├── 29.png ├── 30.png ├── 31.png ├── 32.png ├── 33.png └── 34.png ├── 03-音视频格式剖析.md ├── 04-播放器全面剖析.md ├── 05-FFmpeg全面剖析.md ├── 05-files └── 01.png ├── 06-OpenGL知多少.md ├── 07-音视频SDK怎么做.md ├── audioplayer ├── JeffAudioPlayer.jpg ├── audioplayer_mem_1.png ├── audioplayer_mem_2.png ├── audioplayer_mem_3.png └── 音频播放器完整流程.png ├── opengl ├── OpenGL-native-api1.png ├── OpenGL-native-so.png ├── OpenGL坐标系.png └── 光栅化流程.png ├── 从0开始搭建一个视频播放器.md ├── 从0开始搭建一个音频播放器.md ├── 如何在工程中引入Asan解决内存问题.md ├── 如何编译FFmpeg库.md └── 深入浅出OpenGL.md /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 音视频技术文档汇总 2 | 3 | 音视频的技术范围很广,可以从四个方面阐释 4 | > * 音视频生产侧:主要负责生产视频,例如音视频编辑SDK或者相机处理SDK都输入这个范畴 5 | > * 音视频传输侧:负责音视频文件或者数据包的网络传输,包括协议、弱网控制、后台交互等等,例如熟知的RTC就属于这个范畴 6 | > * 音视频消费侧:传统的音视频渲染模块,例如播放器,包括播放控制、性能优化等等 7 | > * 音视频算法侧:音视频全链路过程中用到的算法,例如人脸识别、超分等等。 8 | 9 | |章节|章节名|链接| 10 | |:-|:-|:-| 11 | |第一章|音视频全链路技术栈|[点我看具体内容](./音视频平凡之路/01-音视频全链路技术栈.md)| 12 | |第二章|音视频基础知识|[点我看具体内容](./音视频平凡之路/02-音视频基础知识.md)| 13 | |第三章|音视频格式剖析|[点我看具体内容](./音视频平凡之路/03-音视频格式剖析.md)| 14 | |第四章|播放器完全剖析|[点我看具体内容](./音视频平凡之路/04-播放器完全剖析.md)| 15 | |第五章|FFmpeg全面剖析|[点我看具体内容](./音视频平凡之路/05-FFmpeg全面剖析.md)| 16 | |第六章|OpenGL知多少|[点我看具体内容](./音视频平凡之路/06-OpenGL知多少.md)| 17 | |第七章|音视频SDK怎么做|[点我看具体内容](./音视频平凡之路/07-音视频SDK怎么做.md)| 18 | *** 19 | 20 | [更新github markdown中目录的文档](https://discuss.helloflask.com/t/topic/172) 21 | -------------------------------------------------------------------------------- /android/Android知识点.md: -------------------------------------------------------------------------------- 1 | ## Android知识点 2 | ### 1.Dalvik vs ART 3 | #### 1.1 Dalvik 4 | #### 1.2 ART 5 | 6 | **** 7 | 8 | ### 2.GC-Root有哪些 9 | 10 | **** 11 | 12 | ### 3.SurfaceView vs TextureView 13 | #### 3.1 SurfaceView 14 | #### 3.2 TextureView 15 | 16 | **** 17 | 18 | ### 4.GLSurfaceView源码分析 -------------------------------------------------------------------------------- /android/SurfaceView和TextureView比较分析.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/android/SurfaceView和TextureView比较分析.md -------------------------------------------------------------------------------- /network/HTTP2.0协议分析.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/network/HTTP2.0协议分析.md -------------------------------------------------------------------------------- /network/QUIC协议分析.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/network/QUIC协议分析.md -------------------------------------------------------------------------------- /network/RTMP协议分析.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/network/RTMP协议分析.md -------------------------------------------------------------------------------- /network/传输协议分析.md: -------------------------------------------------------------------------------- 1 | - [传输协议分析](#传输协议分析) 2 | - [1.RTMP协议分析](#1rtmp协议分析) 3 | - [1.1 RTMP协议介绍](#11-rtmp协议介绍) 4 | - [1.2 RTMP为什么还要建连](#12-rtmp为什么还要建连) 5 | - [1.3 RTMP建连流程](#13-rtmp建连流程) 6 | - [2.HTTP2.0协议分析](#2http20协议分析) 7 | - [2.1 优化的多路复用](#21-优化的多路复用) 8 | - [2.2 二进制分帧](#22-二进制分帧) 9 | - [2.3 头部压缩](#23-头部压缩) 10 | - [2.4 服务器推送](#24-服务器推送) 11 | - [3.QUIC协议分析](#3quic协议分析) 12 | - [3.1 链接耗时更短](#31-链接耗时更短) 13 | - [3.2 拥塞控制更出色](#32-拥塞控制更出色) 14 | - [3.3 更好的多路复用](#33-更好的多路复用) 15 | - [3.4 前向纠错特性](#34-前向纠错特性) 16 | - [3.5 链接迁移特性](#35-链接迁移特性) 17 | 18 | ## 传输协议分析 19 | ### 1.RTMP协议分析 20 | #### 1.1 RTMP协议介绍 21 | RTMP协议是应用层协议,是要靠底层可靠的传输层(TCP), 22 | 协议(通常是TCP)来保证信息传输的可靠性的。在基于传输层协议的链接建立完成后,RTMP协议也要客户端和服务器通过“握手”来建立基于传输层链接之上的RTMP Connection链接。播放一个RTMP协议的流媒体需要经过以下几个步骤:握手,建立网络连接,建立网络流,播放。服务器和客户端之间只能建立一个网络连接,但是基于该连接可以创建很多网络流。 23 | #### 1.2 RTMP为什么还要建连 24 | 这儿埋下一个小疑问?为什么传输层已经建立了TCP连接,RTMP还需要再次建立一个连接,有这个必要吗? 25 | 26 | RTMP协议传输时会对数据做自己的格式化,这种格式的消息我们称之为RTMP Message,而实际传输的时候为了更好地实现多路复用、分包和信息的公平性,发送端会把Message划分为带有Message ID的Chunk,每个Chunk可能是一个单独的Message,也可能是Message的一部分,在接受端会根据chunk中包含的data的长度,message id和message的长度把chunk还原成完整的Message,从而实现信息的收发。 27 | 28 | 因为它们需要商量一些事情,保证以后的传输能正常进行。主要就是两个事情,一个是版本号,如果客户端、服务器的版本号不一致,则不能工作。另一个就是时间戳,视频播放中,时间是很重要的,后面的数据流互通的时候,经常要带上时间戳的差值,因而一开始双方就要知道对方的时间戳。 29 | 30 | 31 | #### 1.3 RTMP建连流程 32 | > * RTMP握手 33 | > * 拉流 34 | > * 播放 35 | 36 | RTMP握手流程: 37 | > * 客户端发送 C0、C1、 C2,服务器发送 S0、 S1、 S2。 38 | > * 首先,客户端发送 C0 表示自己的版本号,不必等对方的回复,然后发送 C1 表示自己的时间戳。 39 | > * 服务器只有在收到 C0 的时候,才能返回 S0,表明自己的版本号,如果版本不匹配,可以断开连接。 40 | > * 服务器发送完 S0 后,也不用等什么,就直接发送自己的时间戳 S1。客户端收到 S1 的时候,发一个知道了对方时间戳的 ACK C2。同理服务器收到 C1 的时候,发一个知道了对方时间戳的 ACK S2。 41 | > * 握手建立完成。 42 | 43 | RTMP拉流流程: 44 | > * 建立网络连接---> Connect 45 | > * 创建网络流---> Create Stream 46 | > * 播放---> Play 47 | 48 | **** 49 | 50 | ### 2.HTTP2.0协议分析 51 | #### 2.1 优化的多路复用 52 | 多路复用允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息, 53 | 在HTTP1.1中,浏览器客户端针对同一个域名下的请求有数量限制,超过限制数目的请求会被阻塞。 54 | #### 2.2 二进制分帧 55 | HTTP1.1是纯文本的传输,HTTP2.0是数据帧的传输,信息会被压缩成数据帧,然后分为多个帧分别传输,传输到对端之后再组装帧,这样可以大大减少传输的延迟,提升了传输的性能。 56 | #### 2.3 头部压缩 57 | HTTP2.0使用HPACK头部压缩方法压缩头部。这样减少传输的数据量。 58 | #### 2.4 服务器推送 59 | 服务端推送是一种在客户端请求之前发送数据的机制。在 HTTP/2 中,服务器可以对客户端的一个请求发送多个响应。 60 | 61 | **** 62 | 63 | ### 3.QUIC协议分析 64 | #### 3.1 链接耗时更短 65 | #### 3.2 拥塞控制更出色 66 | 无论HTTP2(HTTP/2 over TCP)、HTTP1.x协议还是SPDY协议都基于TCP协议,上层可以解决HOL问题,但是传输层的HOL依然存在。说起传输层TCP协议的队头阻塞,这要从TCP的流量控制策略说起了。 67 | #### 3.3 更好的多路复用 68 | #### 3.4 前向纠错特性 69 | #### 3.5 链接迁移特性 70 | -------------------------------------------------------------------------------- /opengl/FBO纹理坐标系.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/opengl/FBO纹理坐标系.png -------------------------------------------------------------------------------- /opengl/GLSurfaceView流程图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/opengl/GLSurfaceView流程图.png -------------------------------------------------------------------------------- /opengl/openGL拓展知识点.md: -------------------------------------------------------------------------------- 1 | - [openGL拓展知识点](#opengl拓展知识点) 2 | - [1.正交投影](#1正交投影) 3 | - [1.1 顶点着色器中添加矩阵](#11-顶点着色器中添加矩阵) 4 | - [1.2 根据图像的宽高计算最终的大小](#12-根据图像的宽高计算最终的大小) 5 | - [1.3 应用矩阵](#13-应用矩阵) 6 | - [2.矩阵旋转](#2矩阵旋转) 7 | - [3.多surface渲染同一纹理](#3多surface渲染同一纹理) 8 | - [4.单surface渲染多纹理](#4单surface渲染多纹理) 9 | 10 | ## openGL拓展知识点 11 | ### 1.正交投影 12 | 正交投影就是将物体统一在归一化坐标中,使用正交投影,不管物体多远多近,物体看起来总是形状、比例相同的。
13 | 14 | 需要用到投影矩阵来改变顶点坐标的范围,最后统一归一化处理即可。
15 | #### 1.1 顶点着色器中添加矩阵 16 | ``` 17 | attribute vec4 v_Position; 18 | attribute vec2 f_Position; 19 | varying vec2 ft_Position; 20 | uniform mat4 u_Matrix; 21 | void main() { 22 | ft_Position = f_Position; 23 | gl_Position = v_Position * u_Matrix; 24 | } 25 | ``` 26 | #### 1.2 根据图像的宽高计算最终的大小 27 | ``` 28 | orthoM(float[] m, int mOffset, float left, float right, float bottom, float top, float near, float far) 29 | Matrix.orthoM(matrix, 0, -width / ((height / 702f * 526f)), width / ((height / 702f * 526f)), -1f, 1f, -1f, 1f); 30 | Matrix.orthoM(matrix, 0, -1, 1, - height / ((width / 526f * 702f)), height / ((width / 526f * 702f)), -1f, 1f); 31 | 32 | ``` 33 | #### 1.3 应用矩阵 34 | GLES20.glUniformMatrix4fv(umatrix, 1, false, matrix, 0);
35 | 36 | 在摄像头中应用比较多。 37 | 38 | **** 39 | 40 | ### 2.矩阵旋转 41 | Matrix.ratateM(matrix, o, a, x, y, z);
42 | a : 正数表示逆时针旋转;负数表示顺时针旋转。
43 | 44 | 上下翻转一下图片:
45 | Matrix.rotateM(mMatrix, 0, 180, 1, 0, 0); 46 | 47 | **** 48 | 49 | ### 3.多surface渲染同一纹理 50 | > * 首先利用离屏渲染把图像渲染到纹理texture中 51 | > * 通过共享EGLContext和texture, 实现纹理共享 52 | > * 然后再新的Render里面可以对texture进行新的滤镜操作 53 | 54 | **** 55 | 56 | ### 4.单surface渲染多纹理 57 | 主要是利用opengl es绘制多次,把不同的纹理绘制到纹理或者窗口上。
58 | 59 | 实际只需要改变opengl es从顶点数组开始取点的位置就行了。
60 | > * vertexBuffer.position(index); index是内存中的起始位置 61 | > * GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8, index); -------------------------------------------------------------------------------- /opengl/openGL知识点.md: -------------------------------------------------------------------------------- 1 | - [OpenGL知识点](#opengl知识点) 2 | - [1.OpenGL中重要的概念](#1opengl中重要的概念) 3 | - [1.1 两种坐标系](#11-两种坐标系) 4 | - [1.2 绘制的图形](#12-绘制的图形) 5 | - [1.3 shader--->glsl语言体系](#13-shader---glsl语言体系) 6 | - [1.4 VBO顶点缓冲对象](#14-vbo顶点缓冲对象) 7 | - [1.5 FBO帧缓冲对象](#15-fbo帧缓冲对象) 8 | - [2.EGL环境创建流程](#2egl环境创建流程) 9 | - [2.1 什么是EGL](#21-什么是egl) 10 | - [2.2 为什么要创建EGL环境](#22-为什么要创建egl环境) 11 | - [2.3 EGL环境创建流程](#23-egl环境创建流程) 12 | - [2.4 opengl es加载shader](#24-opengl-es加载shader) 13 | - [2.5 opengl es绘制纹理的过程](#25-opengl-es绘制纹理的过程) 14 | - [2.6 共享上下文实现共享绘制](#26-共享上下文实现共享绘制) 15 | - [3.自定义GLSurfaceView](#3自定义glsurfaceview) 16 | - [3.1 自定义GLSurfaceView的流程](#31-自定义glsurfaceview的流程) 17 | 18 | ## OpenGL知识点 19 | ### 1.OpenGL中重要的概念 20 | #### 1.1 两种坐标系 21 | > * 顶点坐标系 22 | > * 纹理坐标系 23 | 24 | ![OpenGL坐标系](./opengl坐标系.png) 25 | 26 | 如果要绘制纹理的话,需要将两种坐标系映射起来看。
27 | 定点坐标是绘制位置,纹理坐标是填充数据。 28 | #### 1.2 绘制的图形 29 | OpenGL基础可以绘制的是点、线和三角形,其他的形状都可以通过这三种图像拼接得到,例如四边形实际上是两个三角形拼接而成的。 30 | 31 | #### 1.3 shader--->glsl语言体系 32 | > * attribute只能在vertex中使用 33 | > * varying用户vertex和fragment之间传递数据 34 | > * uniform用于在application中向vertex和fragment中传递值 35 | 36 | 顶点glsl: 37 | ``` 38 | attribute vec4 v_Position; 39 | attribute vec2 f_Position; 40 | varying vec2 ft_Position; 41 | void main() { 42 | ft_Position = f_Position; 43 | gl_Position = v_Position; 44 | } 45 | ``` 46 | 47 | 片段glsl: 48 | ``` 49 | precision mediump float; 50 | varying vec2 ft_Position; 51 | uniform sampler2D sTexture; 52 | void main() { 53 | gl_FragColor=texture2D(sTexture, ft_Position); 54 | } 55 | ``` 56 | > * gl_FragColor是opengl内置的变量 57 | > * gl_Position是opengl内置的变量 58 | 59 | #### 1.4 VBO顶点缓冲对象 60 | VBO是Vertex Buffer Object,为什么要使用VBO呢?
61 | 不使用VBO的情况下,我们每次绘制图形时都是从本地内存处获取顶点数据然后传输给opengl绘制,这样就会频繁地操作CPU->GPU增大开销,降低效率。
62 | 使用VBO的情况下,我们就能把顶点数据缓存到GPU开辟的一段内存中,然后使用时不必再从本地获取,而是直接从显存中获取,这样就会提升绘制的效率。
63 | 64 | 创建VBO的完整流程: 65 | > * 创建VBO: 66 | GLES20.glGenBuffers(1, vbos, 0); 67 | > * 绑定VBO: 68 | GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbos[0]); 69 | > * 分配VBO需要的缓存大小: 70 | GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vertex.length * 4, null, GLES20.GL_STATIC_DRAW); 71 | > * 为VBO设置顶点数据的值 72 | GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, 0, vertexData,length * 4, vertexBuffer); 73 | > * 解绑VBO 74 | GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); 75 | 76 | 使用VBO的完整流程: 77 | > * 绑定VBO 78 | GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbos[0]); 79 | > * 设置顶点数据 80 | GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8, 0); 81 | > * 解绑VBO 82 | GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); 83 | 84 | #### 1.5 FBO帧缓冲对象 85 | FBO是Frame Buffer Object,为什么要用FBO呢?
86 | 当我们需要对纹理进行多次渲染采样时,而这些渲染采样时不需要展示给用户看的,所以我们就可以用一个单独的缓冲对象(离屏渲染)来存储我们的这几次渲染采样的结果,等处理完了才显示到窗口上来。
87 | **优点:提高渲染效率,避免闪屏,可以很方便的实现纹理共享等。**
88 | 89 | FBO的渲染方式: 90 | > * 渲染到缓冲区: 深度测试和模板测试,用在3D测试中。 91 | > * 渲染到纹理: 图像渲染。 92 | 93 | 创建FBO的完整流程: 94 | > * 创建FBO 95 | GLES20.glGenBuffers(1, fbos, 0); 96 | > * 绑定FBO 97 | GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbos[0]); 98 | > * 设置FBO分配内存的大小 99 | GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, 720, 1280, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null); 100 | > * 把纹理绑定到FBO 101 | GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, textureid, 0); 102 | > * 检查FBO是否绑定成功 103 | GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER) != GLES20.GL_FRAMEBUFFER_COMPLETE; 104 | > * 解绑FBO 105 | GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); 106 | 107 | 使用FBO的完整流程: 108 | > * 绑定FBO 109 | GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbos[0]); 110 | > * 获取需要绘制的图片纹理,然后绘制渲染 111 | > * 解绑FBO 112 | GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbos[0]); 113 | > * 再把绑定到FBO的纹理绘制渲染出来 114 | 115 | 非常需要注意的一点是:FBO的坐标系和正常的坐标系是不一样的,我们使用FBO渲染出来的图片,是经过X对称然后翻转的。
116 | ![FBO坐标系](./FBO纹理坐标系.png) 117 | 118 | **** 119 | 120 | ### 2.EGL环境创建流程 121 | #### 2.1 什么是EGL 122 | EGL是OpenGL ES和本地窗口系统的接口,不同平台上的EGL配置是不一样的,但是调用OpenGL的方式是一样的,就是说OpenGL能够跨平台就是依赖EGL接口 123 | #### 2.2 为什么要创建EGL环境 124 | 当我们需要把同一个场景渲染到不同的Surface上时,此时系统的GLSurfaceView就不能满足要求了,所以我们需要自己创建EGL环境来实现渲染的操作。一般较为复杂的OpenGL渲染操作都需要自定义GLSurfaceView来实现渲染功能。
125 | OpenGL整体是一个状态机,通过改变状态就能改变后续的渲染方式,而EGLContext(EGL上下文)就保存所有的状态,因此可以通过共享EGLContext来实现同一场景渲染到不同的Surface上 126 | #### 2.3 EGL环境创建流程 127 | > * 创建EGL实例 128 | > * 得到默认的显示设备,就是显示窗口 129 | > * 初始化默认的显示设备 130 | > * 设置显示设备的属性 131 | > * 从系统中获取对应属性的配置 132 | > * 创建EGLContext实例 133 | > * 创建渲染的Surface 134 | > * 绑定EGLContext和Surface到显示设备中 135 | > * 刷新数据,显示渲染场景 136 | 137 | 下面是具体的代码流程: 138 | ``` 139 | /** 140 | * EGL环境创建的类 141 | */ 142 | public class EglHelper { 143 | private EGL10 mEgl10; 144 | private EGLContext mEglContext; 145 | private EGLDisplay mEglDisplay; 146 | private EGLSurface mEglSurface; 147 | 148 | public void initEgl(Surface surface, EGLContext context) { 149 | //1. 150 | mEgl10 = (EGL10) EGLContext.getEGL(); 151 | 152 | //2. 153 | mEglDisplay = mEgl10.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); 154 | if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { 155 | throw new RuntimeException("eglGetDisplay failed"); 156 | } 157 | 158 | //3. 159 | int[] version = new int[2]; 160 | if (!mEgl10.eglInitialize(mEglDisplay, version)) { 161 | throw new RuntimeException("eglInitialize failed"); 162 | } 163 | 164 | //4.设置属性 165 | int[] attributes = new int[]{ 166 | EGL10.EGL_RED_SIZE, 8, 167 | EGL10.EGL_GREEN_SIZE, 8, 168 | EGL10.EGL_BLUE_SIZE, 8, 169 | EGL10.EGL_ALPHA_SIZE, 8, 170 | EGL10.EGL_DEPTH_SIZE, 8, 171 | EGL10.EGL_STENCIL_SIZE, 8, 172 | EGL10.EGL_RENDERABLE_TYPE, 4, 173 | EGL10.EGL_NONE 174 | }; 175 | 176 | int[] numConfigs = new int[1]; 177 | if (!mEgl10.eglChooseConfig(mEglDisplay, attributes, null, 1, numConfigs)) { 178 | throw new RuntimeException("eglChooseConfig failed"); 179 | } 180 | 181 | int numConfig = numConfigs[0]; 182 | if (numConfig <= 0) { 183 | throw new RuntimeException("No match configs"); 184 | } 185 | 186 | //5. 187 | EGLConfig[] configs = new EGLConfig[numConfig]; 188 | if (!mEgl10.eglChooseConfig(mEglDisplay, attributes, configs, numConfig, numConfigs)) { 189 | throw new RuntimeException("eglChooseConfig failed final"); 190 | } 191 | 192 | //6. 193 | //这句代码很重要,因为没有这个支持,并不能加载shader 194 | int[] attrib_list = { 195 | EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, 196 | EGL10.EGL_NONE 197 | }; 198 | if (context != null) { 199 | mEglContext = mEgl10.eglCreateContext(mEglDisplay, configs[0], context, attrib_list); 200 | } else { 201 | mEglContext = mEgl10.eglCreateContext(mEglDisplay, configs[0], EGL10.EGL_NO_CONTEXT, attrib_list); 202 | } 203 | 204 | //7. 205 | mEglSurface = mEgl10.eglCreateWindowSurface(mEglDisplay, configs[0], surface, null); 206 | 207 | //8. 208 | if (!mEgl10.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { 209 | throw new RuntimeException("eglMakeCurrent failed"); 210 | } 211 | } 212 | 213 | //刷新数据 214 | public boolean swapBuffers() { 215 | if (mEgl10 != null) { 216 | return mEgl10.eglSwapBuffers(mEglDisplay, mEglSurface); 217 | } else { 218 | throw new RuntimeException("EGL is empty"); 219 | } 220 | } 221 | 222 | public EGLContext getEglContext() { 223 | return mEglContext; 224 | } 225 | 226 | public void destoryEgl() { 227 | if (mEgl10 != null) { 228 | mEgl10.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); 229 | mEgl10.eglDestroySurface(mEglDisplay, mEglSurface); 230 | mEglSurface = null; 231 | 232 | mEgl10.eglDestroyContext(mEglDisplay, mEglContext); 233 | mEglContext = null; 234 | 235 | mEgl10.eglTerminate(mEglDisplay); 236 | mEglDisplay = null; 237 | mEgl10 = null; 238 | } 239 | } 240 | } 241 | ``` 242 | 243 | #### 2.4 opengl es加载shader 244 | 上面创建的shader都是以字符串的形式创建的,还需要加载到程序中,然后获取变量,进行设置。
245 | 246 | 加载shader的流程: 247 | > * 创建shader着色器,顶点和片元着色器 248 | int shader = GLES20.glCreateShader(shaderType); 249 | > * 加载shader源码并编译shader 250 | GLES20.glShaderSource(shader, source); 251 | GLES20.glCompileShader(shader); 252 | > * 检查是否编译成功: 253 | GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); 254 | > * 创建一个渲染程序: 255 | int program = GLES20.glCreateProgram(); 256 | > * 将着色器程序添加到渲染程序中: 257 | GLES20.glAttachShader(program, vertexShader); 258 | > * 链接源程序: 259 | GLES20.glLinkProgram(program); 260 | > * 检查链接源程序是否成功: 261 | GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); 262 | > * 得到着色器中的属性: 263 | int vPosition = GLES20.glGetAttribLocation(program, "v_Postion"); 264 | > * 使用源程序 : 265 | GLES20.glUseProgram(program); 266 | > * 使顶点属性数组有效: 267 | GLES20.glEnableVertexAttribArray(vPosition); 268 | > * 为顶点属性赋值: 269 | GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer); 270 | > * 绘制图形: 271 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); 272 | 273 | opengl es加载shader的代码流程: 加载shader和链接program的完整代码流程 274 | ``` 275 | private static int loadShader(int shaderType, String source) { 276 | int shader = GLES20.glCreateShader(shaderType); 277 | if(shader != 0) { 278 | GLES20.glShaderSource(shader, source); 279 | GLES20.glCompileShader(shader); 280 | 281 | int[] compile = new int[1]; 282 | GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compile, 0); 283 | if(compile[0] != GLES20.GL_TRUE) { 284 | Log.d(TAG, "shader compile error"); 285 | GLES20.glDeleteShader(shader); 286 | shader = 0; 287 | } 288 | return shader; 289 | } 290 | else { 291 | return 0; 292 | } 293 | } 294 | 295 | public static int createProgram(String vertexSource, String fragmentSoruce) { 296 | int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); 297 | int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSoruce); 298 | 299 | if(vertexShader != 0 && fragmentShader != 0) { 300 | int program = GLES20.glCreateProgram(); 301 | 302 | GLES20.glAttachShader(program, vertexShader); 303 | GLES20.glAttachShader(program, fragmentShader); 304 | 305 | GLES20.glLinkProgram(program); 306 | return program; 307 | } 308 | return 0; 309 | } 310 | 311 | ``` 312 | 313 | #### 2.5 opengl es绘制纹理的过程 314 | > * 加载shader和生成program的流程不变 315 | > * 创建和绑定纹理: 316 | GLES20.glGenTextures(1, textureId, 0); 317 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureid); 318 | > * 设置环绕和过滤方式 319 | > * 设置图片: 320 | GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); 321 | > * 绑定顶点坐标和纹理坐标 322 | > * 绘制图形 323 | 324 | 环绕(超出纹理坐标的范围) :
325 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
326 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
327 | 328 | 过滤(纹理像素映射到坐标点) : 可能会缩小、放大,其实是线性的方式
329 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
330 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
331 | 332 | #### 2.6 共享上下文实现共享绘制 333 | 通过共享EGLContext来实现共享上下文 334 | 335 | **** 336 | 337 | ### 3.自定义GLSurfaceView 338 | GLSurfaceView内部拥有三个比较重要的变量: 339 | > * GLThread : OpenGL ES的运行线程,包含创建EGL环境、调用GLRender的onSurfaceCreated、onSurfaceChanged、onDrawFrame方法以及生命周期的有效管理 340 | > * EglHelper : 负责创建EGL环境 341 | > * GLSurfaceView :负责提供Surface和提供状态的改变 342 | 343 | #### 3.1 自定义GLSurfaceView的流程 344 | > * 继承SurfaceView,并实现其Callback回调 345 | > * 自定义GLThread线程类,主要用于OpenGL的绘制操作 346 | > * 添加设置Surface和EGLContext的方法 347 | > * 提供和系统GLSurfaceView相同的回调方法 348 | 349 | ![GLSurfaceView持有流程](./GLSurfaceView流程图.png)
350 | 自定义GLSurfaceView的核心代码: 欢迎参考[https://github.com/JeffMony/OpenGLDemo](https://github.com/JeffMony/OpenGLDemo) -------------------------------------------------------------------------------- /opengl/opengl坐标系.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/opengl/opengl坐标系.png -------------------------------------------------------------------------------- /player/播放器卡顿优化.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/player/播放器卡顿优化.md -------------------------------------------------------------------------------- /player/播放器成功率优化.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/player/播放器成功率优化.md -------------------------------------------------------------------------------- /player/播放器架构分析.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/player/播放器架构分析.md -------------------------------------------------------------------------------- /player/播放器知识点.md: -------------------------------------------------------------------------------- 1 | - [播放器知识点](#播放器知识点) 2 | - [MediaPlayer详解](#mediaplayer详解) 3 | - [MediaPlayer播放状态详情](#mediaplayer播放状态详情) 4 | - [MediaPlayer架构详情](#mediaplayer架构详情) 5 | - [AwesomePlayer和NuPlayer2区别](#awesomeplayer和nuplayer2区别) 6 | - [ExoPlayer详解](#exoplayer详解) 7 | - [ExoPlayer模块划分](#exoplayer模块划分) 8 | - [ExoPlayer哪些接口和标准接口不同](#exoplayer哪些接口和标准接口不同) 9 | - [ExoPlayer可以外接哪些库](#exoplayer可以外接哪些库) 10 | - [ExoPlayer倍速怎么做的](#exoplayer倍速怎么做的) 11 | - [ijkplayer详解](#ijkplayer详解) 12 | - [ijkplayer编译介绍](#ijkplayer编译介绍) 13 | - [ijkplayer工作流程](#ijkplayer工作流程) 14 | - [ijkplayer核心option介绍](#ijkplayer核心option介绍) 15 | - [ijkplayer中的libijkffmpeg.so可以被复用吗](#ijkplayer中的libijkffmpegso可以被复用吗) 16 | - [优化播放器成功率](#优化播放器成功率) 17 | - [音视频格式支持](#音视频格式支持) 18 | - [HTTP Pipeline优化](#http-pipeline优化) 19 | - [DNS优化](#dns优化) 20 | - [连接优化](#连接优化) 21 | - [codec优化](#codec优化) 22 | - [优化播放器首帧速度](#优化播放器首帧速度) 23 | - [本地代理模式](#本地代理模式) 24 | - [脱离播放器预加载](#脱离播放器预加载) 25 | - [自定义load control](#自定义load-control) 26 | - [HTTP Pipeline定制优化](#http-pipeline定制优化) 27 | - [codec复用](#codec复用) 28 | - [surfaceview复用](#surfaceview复用) 29 | - [多码率支持](#多码率支持) 30 | - [HTTP2.0或者QUIC协议支持](#http20或者quic协议支持) 31 | - [优化播放器卡顿](#优化播放器卡顿) 32 | - [定制化load control](#定制化load-control) 33 | - [seek优化](#seek优化) 34 | - [多码率支持](#多码率支持-1) 35 | - [HTTP2.0或者QUIC协议支持](#http20或者quic协议支持-1) 36 | - [本地代理](#本地代理) 37 | - [媒体类型解析模块](#媒体类型解析模块) 38 | - [MP4文件结构识别模块](#mp4文件结构识别模块) 39 | - [MP4 video-range处理模块](#mp4-video-range处理模块) 40 | - [HLS-TS适配模块](#hls-ts适配模块) 41 | - [local server seek优化模块](#local-server-seek优化模块) 42 | - [load control控制技术](#load-control控制技术) 43 | - [直播优化](#直播优化) 44 | - [直播首帧优化](#直播首帧优化) 45 | - [直播成功率优化](#直播成功率优化) 46 | - [直播卡顿优化](#直播卡顿优化) 47 | - [音视频同步技术点](#音视频同步技术点) 48 | - [MediaPlayer音视频同步](#mediaplayer音视频同步) 49 | - [ExoPlayer音视频同步](#exoplayer音视频同步) 50 | - [ijkplayer音视频同步](#ijkplayer音视频同步) 51 | 52 | ## 播放器知识点 53 | 54 | ### MediaPlayer详解 55 | #### MediaPlayer播放状态详情 56 | #### MediaPlayer架构详情 57 | #### AwesomePlayer和NuPlayer2区别 58 | 59 | ### ExoPlayer详解 60 | #### ExoPlayer模块划分 61 | #### ExoPlayer哪些接口和标准接口不同 62 | #### ExoPlayer可以外接哪些库 63 | #### ExoPlayer倍速怎么做的 64 | 65 | ### ijkplayer详解 66 | #### ijkplayer编译介绍 67 | #### ijkplayer工作流程 68 | #### ijkplayer核心option介绍 69 | #### ijkplayer中的libijkffmpeg.so可以被复用吗 70 | 71 | ### 优化播放器成功率 72 | #### 音视频格式支持 73 | #### HTTP Pipeline优化 74 | ##### DNS优化 75 | ##### 连接优化 76 | #### codec优化 77 | 78 | ### 优化播放器首帧速度 79 | #### 本地代理模式 80 | ##### 脱离播放器预加载 81 | ##### 自定义load control 82 | ##### HTTP Pipeline定制优化 83 | #### codec复用 84 | #### surfaceview复用 85 | #### 多码率支持 86 | #### HTTP2.0或者QUIC协议支持 87 | 88 | ### 优化播放器卡顿 89 | #### 定制化load control 90 | #### seek优化 91 | #### 多码率支持 92 | #### HTTP2.0或者QUIC协议支持 93 | 94 | ### 本地代理 95 | #### 媒体类型解析模块 96 | #### MP4文件结构识别模块 97 | #### MP4 video-range处理模块 98 | #### HLS-TS适配模块 99 | #### local server seek优化模块 100 | #### load control控制技术 101 | 102 | ### 直播优化 103 | #### 直播首帧优化 104 | #### 直播成功率优化 105 | #### 直播卡顿优化 106 | 107 | ### 音视频同步技术点 108 | #### MediaPlayer音视频同步 109 | #### ExoPlayer音视频同步 110 | #### ijkplayer音视频同步 -------------------------------------------------------------------------------- /player/播放器首帧速度优化.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/player/播放器首帧速度优化.md -------------------------------------------------------------------------------- /音视频平凡之路/01-音视频全链路技术栈.md: -------------------------------------------------------------------------------- 1 | # 第一章 音视频全链路技术栈 2 | ## 1.概要 3 | 长久以来,在互联网圈一直流传着这样的一段话:1G和2G开启了通话和短信的时代,3G开启了图片和文字的娱乐时代,4G带来了视频娱乐的方式,如今5G到来,什么样优越的娱乐形态会萌生呢?未来不得而知,但是我们应该能想象得到,是沿着音视频技术革新的方向继续演进。会在音视频领域发生几个比较大的变化: 4 | > * 音质更加优越; 5 | > * 视频画面更加清晰和细腻,帧率更高; 6 | > * 交互形态更加多元化,从语音、视频聊天很可能发展到触摸传递等等。 7 | 8 | 我们当然希望未来能发展到这么先进的程度。2016年抖音横空出世,同年快手也推出短视频,真正的短视频时代来临,腾讯在2020年之前推出了十几款短视频,不过都死得很惨,这个行业已经竞争的非常白热化,市面上非常急缺音视频相关的技术开发,相关的技术人才比较隗帆的原因可能有几个原因: 9 | > * 之前音视频岗位招聘比较少,导致市场上相关的人才供应不多; 10 | > * 音视频开发细分比较多,每个细分领域仔细分拆都需要较深的研究,非一朝一夕能完成; 11 | > * 音视频开发有一定的开发门槛,学习难度比较大,一般小公司不一定需要非常专业的相关人才,都是大厂之间的互相流动; 12 | 13 | 但是现在情况正在悄然变化,音视频娱乐化已经成为各个互联网产品的主要手段,基本上每个互联网产品都有音视频相关的应用,或多或少罢了,所以这一块的人才需要只会越来越大,会经历一个人才供不应求--->火热--->趋于平稳--->供大于求,当然不是说最后还是完了,任何一种技术栈都不应该只停留在比较基础的层次,都需要经过一个由浅入深、由表及里的过程,我们程序猿要有这样的技术觉悟,不应该放弃学习、放弃进步。
14 | 但是学习是一个过程,不是一头栽进去出不来了。现在我们生在一个海量信息资源共享的时代,如果不在甄别的汲取知识,就掉进了知识的海洋中无法自拔,学习音视频也是一样的道理,最好能列出一个学习的技术路图或者学习脉络,这样我们在学习的时候能够有章法、有进展、有反馈,日拱一卒,日子久了,收获自然大。
15 | 想学好音视频,C++是基础,不会C++,单靠Android或者iOS的原生API,是翻不起大浪的,还请各位读者要认清现实,从现在开始,从我做起,认真学习C++,基础的C++书籍大家可以找找,《C++ Primer》这是入门书籍,深入一点的推荐《C++ 并发编程实战》,但是中文翻译实在不敢恭维,不过还是有真正的勇士的,推荐一个博客:https://www.zhihu.com/column/c_1307735602880331776 不是我的博客,只是觉得写得不错,介绍一下。
16 | 还是比较佩服这些同学的,纯粹兴趣爱好,要拿出自己很多的闲暇时间去做这样的事情,完全的开源精神,我们还有什么理由不好好学习,其实现在的人学习并不缺少信息,缺少了是过滤信息的能力,怎么从海量的信息中分拣出比较重要的信息,我们关注当前阶段的重点,而不是我全都要,人的精力有限,全都要的结果往往是都不精通,浪费了时间,到头来找工作,什么都接触过,什么都会一些,但是说不出精通的点,确实让人惋惜。还是要着重抓住一万小时定理,认真花一万小时的时间在一件事情上,钻研下去,时间和精力花够了,量变会促成质变的。 17 | 18 | ## 2.音视频技术栈 19 | 之前生产一个视频,各种剪辑需要非常专门的软件来完成,还需要一个非常专业的视频剪辑工程师,抖音快手我们观察短视频从产生到用户浏览到的完整链路,可以窥见音视频全链路的技术栈,我们力图通过一个章节阐释一下音视频的完整链路,当然所有的知识都是来源于对已有技术的分解。不会单纯的阐释理论,还是尽可能结合实际例子。 20 | ### 2.1 音视频生产 21 | ### 2.2 音视频传输 22 | ### 2.3 音视频消费 23 | ### 2.4 音视频算法 24 | 25 | ## 3.音视频工程师学习路图 26 | ### 3.1 交叉编译 27 | ### 3.2 JNI如何学 28 | #### 3.1.1 静态与动态注册 29 | #### 3.1.2 方法签名 30 | #### 3.1.3 如何实现java与native互相调用 31 | #### 3.1.4 本地引用、弱引用、全局引用 32 | #### 3.1.5 JNI中如何实现多线程 33 | ### 3.3 Android自带的音视频工具 34 | #### 3.3.1 AudioRecord和AudioTrack 35 | #### 3.3.2 MediaCodec 36 | #### 3.3.3 MediaExtractor和MediaMuxer 37 | #### 3.3.4 Camera和Camera2 38 | ### 3.4 播放器知多少 39 | #### 3.4.1 40 | 41 | ## 3.怎么做一个合格的音视频工程师 42 | ### 3.1 需要掌握的知识 43 | ### 3.2 音视频工程师需要做得事情 44 | ### 3.3 音视频工程师最重要的素质 45 | -------------------------------------------------------------------------------- /音视频平凡之路/02-files/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/02-files/01.png -------------------------------------------------------------------------------- /音视频平凡之路/02-files/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/02-files/02.png -------------------------------------------------------------------------------- /音视频平凡之路/02-files/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/02-files/03.png -------------------------------------------------------------------------------- /音视频平凡之路/02-files/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/02-files/04.png -------------------------------------------------------------------------------- /音视频平凡之路/02-files/05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/02-files/05.png -------------------------------------------------------------------------------- /音视频平凡之路/02-files/06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/02-files/06.png -------------------------------------------------------------------------------- /音视频平凡之路/02-files/07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/02-files/07.png -------------------------------------------------------------------------------- /音视频平凡之路/02-files/08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/02-files/08.png -------------------------------------------------------------------------------- /音视频平凡之路/02-files/09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/02-files/09.png -------------------------------------------------------------------------------- /音视频平凡之路/02-files/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/02-files/10.png -------------------------------------------------------------------------------- /音视频平凡之路/02-files/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/02-files/11.png -------------------------------------------------------------------------------- /音视频平凡之路/02-files/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/02-files/12.png -------------------------------------------------------------------------------- /音视频平凡之路/02-files/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/02-files/13.png -------------------------------------------------------------------------------- /音视频平凡之路/02-files/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/02-files/14.png -------------------------------------------------------------------------------- /音视频平凡之路/02-files/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/02-files/15.png -------------------------------------------------------------------------------- /音视频平凡之路/02-files/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/02-files/16.png -------------------------------------------------------------------------------- /音视频平凡之路/02-files/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/02-files/17.png -------------------------------------------------------------------------------- /音视频平凡之路/02-files/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/02-files/18.png -------------------------------------------------------------------------------- /音视频平凡之路/02-files/hello: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/02-files/hello -------------------------------------------------------------------------------- /音视频平凡之路/02-files/hello-android: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/02-files/hello-android -------------------------------------------------------------------------------- /音视频平凡之路/02-files/hello.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char** argv) { 4 | printf("Hello, jeffmony\n"); 5 | return 0; 6 | } -------------------------------------------------------------------------------- /音视频平凡之路/03-files/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/01.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/02.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/03.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/04.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/05.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/06.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/07.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/08.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/09.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/10.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/11.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/12.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/13.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/14.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/15.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/16.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/17.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/18.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/18.jpeg -------------------------------------------------------------------------------- /音视频平凡之路/03-files/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/19.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/20.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/20.jpeg -------------------------------------------------------------------------------- /音视频平凡之路/03-files/21.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/21.jpeg -------------------------------------------------------------------------------- /音视频平凡之路/03-files/22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/22.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/23.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/24.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/25.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/26.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/27.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/28.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/29.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/30.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/31.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/32.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/33.png -------------------------------------------------------------------------------- /音视频平凡之路/03-files/34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/03-files/34.png -------------------------------------------------------------------------------- /音视频平凡之路/03-音视频格式剖析.md: -------------------------------------------------------------------------------- 1 | # 第三章 音视频格式剖析 2 | ## 1.概要 3 | 作为一名音视频工程师,常见的音视频知识是必须要知道的,封装格式、编码格式、视频的YUV排列、H264帧中的SPS与PPS,还有一些传输协议,这些都是流媒体开发中比较常见的技术方案,作为一个合格的音视频工程师,不需要你样样精通,但是你一定要心中有数,知道原理。
4 | 不掌握核心原理去优化,是没法达到好的效果的,所以我们学习东西还是要深入到内部,了解原理细节。本文主要阐释音视频开发中经常会用到的一些格式和格式中的重要概念。 5 | ## 2.常见音视频封装格式 6 | 封装格式就是视频最外层的一套衣服,视频的内部数据都在这个封装格式内部,视频有音频轨道信息、视频轨道信息,甚至还有字幕轨道信息,这个信息或者数据流都被集成在封装格式中,所以解剖这些小麻雀是我们音视频进阶的第一步,开始脱衣服了,兄弟们。 7 | ### 2.1 MP4 8 | MP4应用有广泛,现在最火的短视频都是采用MP4的封装格式,还是首先抛出一个问题,MP4的全程是什么?
9 | 我去,全程是什么有那么重要吗?我之前在学校的时候被导师说过一次,他当时问我FFmpeg的全程是什么?我一脸懵逼,当时他就说我研究一个东西难道不应该先搞清楚这个东西来龙去脉吗?当时觉得委屈,现在想想看看,还是有一定的道理,稍微花点时间,可以搞清楚的事情有时候却不愿意去搞明白,还是学习地不彻底。好了,言归正传。
10 | MP4就是MPEG-4,MPEG的全程是Moving Picture Experts Group,即动态影像专家组,MPEG第一代在1998年就提出来了,目前最新的版本是MPEG-4,MPEG-4相对其前辈而言有高度的压缩比和高清的画质以及较高的灵活性。大家感兴趣可以看看MPEG的发展历史:https://zh.wikipedia.org/wiki/MPEG-4
11 | 12 | 下面我们通过分析MP4的结构来解剖这只小麻雀。
13 | MP4本质是一个BOX的嵌套结构,就是像套娃一样,主要包含四个一级文件索引:
14 | ![MP4一级文件索引](./03-files/01.png)
15 | **ftyp :**
16 | File Type Box,该Box有且只有一个,通常放在文件开始的位置,指示该MP4文件的相关信息,依次包括32位的major brand(4个字符),1个32位的minor version和1个32位的compatible brands,见下图。
17 | ![ftyp信息图](./03-files/02.png)
18 | ``` 19 | public class FileTypeBox extends AbstractBox { 20 | public static final String TYPE = "ftyp"; 21 | 22 | private String majorBrand; 23 | private long minorVersion; 24 | private List compatibleBrands = Collections.emptyList(); 25 | 26 | public FileTypeBox() { 27 | super(TYPE); 28 | } 29 | 30 | public FileTypeBox(String majorBrand, long minorVersion, List compatibleBrands) { 31 | super(TYPE); 32 | this.majorBrand = majorBrand; 33 | this.minorVersion = minorVersion; 34 | this.compatibleBrands = compatibleBrands; 35 | } 36 | //...... 37 | 38 | } 39 | 40 | ``` 41 | major_brand就是mp42,minor_version是0,compatible_brands是isom,mp42 42 | **free :**
43 | free box里面的数据无关紧要,基本上是可有可无的,它被删除了不会对播放产生任何影响,主要是最初的版本就有free box,后面为了向前兼容,所以没有删除。 44 | ``` 45 | public class FreeSpaceBox extends AbstractBox { 46 | public static final String TYPE = "skip"; 47 | 48 | byte[] data; 49 | 50 | public FreeSpaceBox() { 51 | super(TYPE); 52 | } 53 | 54 | protected long getContentSize() { 55 | return data.length; 56 | } 57 | 58 | public byte[] getData() { 59 | return data; 60 | } 61 | 62 | public void setData(byte[] data) { 63 | this.data = data; 64 | } 65 | //...... 66 | } 67 | 68 | ``` 69 | 它的type通常是FREE或者是SKIP的。大家了解即可。 70 | 71 | **mdat :**
72 | mdat存储是具体的音视频数据,当然是解码之前的数据,由于MP4知识封装格式,所以这部分原始数据我们不需要过多深究其细节,只要知道多大就行了,指引解析mdat的地方才是比较重要的地方,就是下面我们要讲的moov box结构。 73 | 74 | **moov :**
75 | moov是MP4文件中非常重要的一个BOX,其中存放媒体的metadata信息,MP4文件的所有属性信息都会在moov中显示,下面是moov的结构示意图:
76 | ![moov结构示意图](./03-files/03.png)
77 | ![trak结构示意图](./03-files/04.png)
78 | 如上图所示,moov的完整结构非常复杂。其一级目录中主要包括mvhd、meta、trak、udta
79 | 80 | mvhd又称为 movie header atom info,存放视频的基本信息,例如时长、码率、音量等等。
81 | ![mvhd基本信息](./03-files/05.png)
82 | > * version : box版本号 83 | > * creation_time : 创建时间,相对UTC-1904-01-01的时间 84 | > * modification_time : 最后的修改时间 85 | > * time_scale : 1s时间的刻度值 86 | > * duration : 时间长度, 和time_scale一起可以计算总时长, duraion / time_scale,例如上面的视频就是12s多一点。 87 | > * rate : 推荐的播放速率 88 | > * volume : 推荐的音量大小 89 | > * matrix : 指定的视频变换矩阵 90 | > * next_track_id : 指定下一个轨道的索引,这儿是3,说明已经有2个轨道了。 91 | 92 | ``` 93 | public class MovieBox extends AbstractContainerBox { 94 | public static final String TYPE = "moov"; 95 | 96 | public MovieBox() { 97 | super(TYPE); 98 | } 99 | 100 | public int getTrackCount() { 101 | return getBoxes(TrackBox.class).size(); 102 | } 103 | 104 | 105 | /** 106 | * Returns the track numbers associated with this MovieBox. 107 | * 108 | * @return the tracknumbers (IDs) of the tracks in their order of appearance in the file 109 | */ 110 | public long[] getTrackNumbers() { 111 | 112 | List trackBoxes = this.getBoxes(TrackBox.class); 113 | long[] trackNumbers = new long[trackBoxes.size()]; 114 | for (int trackCounter = 0; trackCounter < trackBoxes.size(); trackCounter++) { 115 | trackNumbers[trackCounter] = trackBoxes.get(trackCounter).getTrackHeaderBox().getTrackId(); 116 | } 117 | return trackNumbers; 118 | } 119 | 120 | public MovieHeaderBox getMovieHeaderBox() { 121 | return Path.getPath(this, "mvhd"); 122 | } 123 | 124 | } 125 | 126 | ``` 127 | 128 | 这已经是可视化的分析数据了,其实分析文件结构就是读位运算,还挺枯燥的。

129 | 130 | trak一听就是存储轨道信息,像上面的示意图,有两个轨道信息,我们拿出其中一个来分析一下。一个视频中多个track信息是互相独立的,分别有自己的时间和空间信息,trak信息包含一个tkhd和mdia box结构,下面是trak的代码逻辑: 131 | ``` 132 | public class TrackBox extends AbstractContainerBox { 133 | public static final String TYPE = "trak"; 134 | private SampleTableBox sampleTableBox; 135 | 136 | public TrackBox() { 137 | super(TYPE); 138 | } 139 | 140 | public TrackHeaderBox getTrackHeaderBox() { 141 | return Path.getPath(this, "tkhd[0]"); 142 | } 143 | 144 | /** 145 | * Gets the SampleTableBox at mdia/minf/stbl if existing. 146 | * 147 | * @return the SampleTableBox or null 148 | */ 149 | public SampleTableBox getSampleTableBox() { 150 | if (sampleTableBox != null) { 151 | return sampleTableBox; 152 | } 153 | MediaBox mdia = getMediaBox(); 154 | if (mdia != null) { 155 | MediaInformationBox minf = mdia.getMediaInformationBox(); 156 | if (minf != null) { 157 | sampleTableBox = minf.getSampleTableBox(); 158 | return sampleTableBox; 159 | } 160 | } 161 | return null; 162 | 163 | } 164 | 165 | 166 | public MediaBox getMediaBox() { 167 | return Path.getPath(this, "mdia[0]"); 168 | } 169 | 170 | @Override 171 | public void setBoxes(List boxes) { 172 | super.setBoxes(boxes); 173 | sampleTableBox = null; 174 | } 175 | 176 | } 177 | 178 | ``` 179 | 其中tkhd是trak的属性信息的集合,相当月trak的头文件:
180 | ![tkhd信息](./03-files/06.png)
181 | > * version : 版本号,通常为0或者1 182 | > * track_id : 轨道索引号 183 | > * layer : 指定的视频层次,默认为0,越小越在上面 184 | > * width : 视频的宽 185 | > * height : 视频的高,音频一般没有这个信息的。 186 | 187 | ![音频的trak信息](./03-files/07.png)

188 | 189 | mdia 则是整个track的媒体相关的信息,其中mdhd是其头部信息,mdhd和tkhd内容大致一样。如下图,不做过多的介绍了。
190 | ![mdhd信息](./03-files/08.png)
191 | 192 | stbl 存储音视频的chunk数据,像一个链表一样,存储着一个个sample块结构,
193 | stts中有块个数,stsz有sample size, 可以通过sample size到mdat中读取对应的数据。
194 | 195 | 简单分析了MP4的结构,我们会有一个疑问,moov是MP4的是头文件,那为什么moov在文件末尾了?会不会有问题,我要播放MP4文件,肯定要先读moov数据,才能通过moov信息到mdat找对应的原始数据。
196 | 其实标准的MP4文件中moov应该在mdat之前的,上面的视频一般是手机录制的视频,我们在短视频应用中需要在后台将moov处理放到mdat之前,因为边下边播的必要条件就是要moov一定要在mdat之前,不然双IO请求处理非常复杂。
197 | 这也间接说明了MP4文件并不是流式文件,它的诞生有很强的时代背景,在如今流式文件满天飞的时代,MP4是不能作为直播的封装格式存在的。
198 | 199 | 下面是指令可以将MP4文件的moov放到mdat之前。 200 | ``` 201 | ffmpeg -i input.mp4 -movflags faststart output.mp4 202 | 203 | ``` 204 | 但是凡事无绝对,moov在mdat后面确实会对MP4的首帧速度造成一些影响,但是不是绝对不能播放,只要把握这种规律,在起始请求的时候发起双端IO请求会解决不能播放的问题,但是性能肯定会收到影响了,这在后面分析播放器方面源码的时候会详细分析一下。 205 |
206 | 虽然MPEG-4文件的标准后缀名是.mp4,但是还有一些其他的后缀名,例如仅有音频的文件会使用.m4a作为后缀名,苹果中的音频加密文件会使用.m4p作为后缀名,早期的移动电话会使用.3qp作为后缀名。
207 | 208 | 上面分析MP4 box结构的工具是在线的工具,现在附上具体的链接:https://gpac.github.io/mp4box.js/test/filereader.html 209 | ### 2.2 FLV 210 | 上面提到了MP4不能作为直播的流式文件,因为MP4的BOX结构的限制,那可以作为直播的格式是什么?FLV开始登上历史的舞台。
211 | 上面之所以用是否是流式结果来区分FLV和MP4,并不是说FLV比MP4好,只是它们应用的场景不一样,所以我们关注它们特性的重点也不同,两种格式在不同的领域应用都非常广泛,所以大家要根据实际的应用情况来选择具体的格式。
212 | FLV全称是Flash Video,是Adobe公司推出的一种媒体封装格式,我们先分析一下FLV格式的具体内容。 213 | #### 2.2.1 FLV分析工具 214 | 计算机上面的数据,最终写入就是bytes数据,二进制信息,还是需要可视化的工具来查看。FLV查看的工具是FlvAnalyzer,回复FLV得到多媒体查看分析的一整套工具。下面使用FlvAnalyzer来分析FLV文件。 215 | ![FLV格式内容](./03-files/09.png)
216 | FlvAnalyzer解析出来的FLV文件是一种树状的排列,分为两大部分: 217 | > * FLV Header:包含版本信息和音频、视频的排位信息; 218 | > * FLV Body:这里能看到交替的Video Tag和Audio Tag信息; 219 | 220 | 里面可以直接看到二进制信息,当然这些二进制数据都有具体的含义,下面会重点阐述。
221 | 222 | 除了FlvAnalyzer,还有一个工具推荐一下,是雷神开发的一个工具,是SpecialFFlv工具,真的相当赞的工具,大大优化了可视化的功能,界面交互更加优化。它还提供了分离视频流和音频流的功能。
223 | ![FLV格式分析工具](./03-files/10.png)
224 | ![FLV分离工具](./03-files/11.png)
225 | test.flv分离出来的视频test_0.flv 和音频 test_0.mp3 226 | ``` 227 | ffprobe test.flv 228 | 229 | Input #0, flv, from 'test.flv': 230 | Metadata: 231 | major_brand : isom 232 | minor_version : 512 233 | compatible_brands: isomiso2avc1mp41 234 | encoder : Lavf57.83.100 235 | Duration: 00:01:19.69, start: 0.084000, bitrate: 950 kb/s 236 | Stream #0:0: Video: h264 (High), yuv420p(progressive), 960x540, 23.98 fps, 23.98 tbr, 1k tbn, 47.95 tbc 237 | Stream #0:1: Audio: mp3, 44100 Hz, stereo, s16p, 128 kb/s 238 | 239 | ``` 240 | test_0.flv解析如下 241 | ``` 242 | ffprobe test_0.flv 243 | 244 | Input #0, flv, from 'test_0.flv': 245 | Duration: 00:01:19.54, start: 0.084000, bitrate: 819 kb/s 246 | Stream #0:0: Video: h264 (High), yuv420p(progressive), 960x540, 24.42 fps, 23.98 tbr, 1k tbn, 47.95 tbc 247 | 248 | ``` 249 | #### 2.2.2 FLV格式分析 250 | 上面使用工具解析出FLV文件的具体结构,下面我们要分析一下FLV格式的各个字段代表什么意思。
251 | FLV包括文件头(File Header)和文件体(File Body)两部分,其中文件体由一系列的Tag组成。FLV文件的结构如下图:
252 | ![FLV文件格式](./03-files/12.png)
253 | ##### 2.2.2.1 FLV Header 254 | Header 部分记录了FLV的类型、版本等信息,是FLV的开头。一般差不多占9个字节。具体格式如下:
255 | ![FLV header具体信息1](./03-files/13.png)
256 | 可以看出来,其中包含,签名信息,版本信息,保留位,音频和视频信息,还有header size;
257 | 文件标识:占3位,总是FLV,0x46 0x4c 0x56
258 | ![FLV header具体信息2](./03-files/14.png)
259 | 版本号:占1位,目前默认为0x01
260 | ![FLV header具体信息3](./03-files/15.png)
261 | 流信息:占1位,文件的标志说明,前5位保留,必须为0;第6位为音频Tag:1 表示有音频;第七位保留,为0;第8位为视频Tag:1 表示有视频。
262 | ![FLV header具体信息4](./03-files/16.png)
263 | Header长度:占4位,整个Header的长度,一般为9(版本为0x01时);大于9表示下面还有扩展信息。即0x00000009。 264 | ##### 2.2.2.2 FLV Body 265 | 文件体由一系列的Tag组成。其中,每个Tag前面还包含了Previous Tag Size字段,表示前面一个Tag的大小。Tag的类型可以是视频、音频和Script,每个Tag只能包含以上三种类型的数据中的一种。
266 | 每个Tag由也是由两部分组成的:Tag Header和Tag Data。Tag Header里存放的是当前Tag的类型、数据区(Tag Data)长度等信息,具体如下:
267 | Tag类型:占1位,0x08:音频; 0x09:视频; 0x12:脚本; 其他:保留。
268 | 数据区长度:占3位,表示数据区的长度。
269 | 时间戳:占3位,整数,单位是毫秒。对于脚本型的tag总是0 (CTS)
270 | 时间戳扩展:占1位,加上之前的时间戳3位,将时间戳扩展为4bytes,代表高8位。很少用到。
271 | StreamsID:占1位,总是0
272 | 数据区(由数据区长度决定):数据实体
273 | 下面是三个Tag类型说明:
274 | > * Audio Tag Data结构(音频类型) :音频Tag Data区域开始的第一个字节包含了音频数据的参数信息,从第二个字节开始为音频流数据。 275 | > * Video Tag Data结构(视频类型):视频Tag Data开始的第一个字节包含视频数据的参数信息,从第二个字节开始为视频流数据。 276 | > * Script Tag Data结构(脚本类型、帧类型):该类型Tag又被称为MetaData Tag,存放一些关于FLV视频和音频的元信息,比如:duration、width、height等。通常该类型Tag会作为FLV文件的第一个tag,并且只有一个,跟在File Header后。
277 | ![FLV Script具体信息](./03-files/17.png)
278 | 上面是Script Tag 的结构信息。 279 | 280 | 其实从FLV的结构信息来看,比较清晰的看出来FLV就是流式格式,因为音频和视频分别对应出现,不会出现视频和音频完全分离,播放的时候再次同步拉取很麻烦。 281 | #### 2.2.3 FLV相关实践 282 | 将一个MP4文件转化为FLV文件,这在ffmpeg指令操作中非常简单: 283 | ``` 284 | ffmpeg -i test.mp4 -c:v libx264 -crf 24 test.flv 285 | 286 | ``` 287 | 如何将FLV格式中设置关键帧索引?FLV标准格式中并没有关键帧索引这一说法,但是我们在实际应用中,特别是现在直播的应用中,我们往往需要向FLV格式中写入关键帧索引,并将这些索引文件写在Metadata 中,这些我们再次播放的时候,可以很快通过这些关键帧索引站到对应的位置,然后准确快速渲染播放。 288 | ``` 289 | ffmpeg -i test.mp4 -c copy -f flv -flvflags add_keyframe_index key.flv 290 | 291 | ``` 292 | 查看具体的key.flv: 293 | ``` 294 | ffprobe -v trace -i key.flv 295 | 296 | ``` 297 | ![FLV关键帧索引信息](./03-files/18.jpeg)
298 | 关键帧位置对播放的seek操作非常重要,通过计算关键帧的位置,可以很快seek到具体的位置,不会耗时,具有非常好的用户体验效果,当然这是播放器的具体知识了。后面讲解播放器seek操作的时候可以展开分析一下。 299 | ### 2.3 HLS 300 | HLS全称为Http Live Streaming,是Apple主推的一种流媒体协议,外部的开发熟知的还是M3U8格式。
301 | M3U8格式,早期称为M3U格式,一种使用UTF-8编码的文本格式,当然文本中记录的链接是具体的视频链接。M3U8文件是M3U文件的一种,只不过它的编码格式是UTF-8。M3U使用Latin-1字符集编码。M3U的全称是Moving Picture Experts Group Audio Layer 3 Uniform Resource Locator,即mp3 URL。M3U是纯文本文件;所以UTF-8编码的M3U文件也简称为 M3U8。
302 | HLS 的工作原理是把整个流分成一个个小的基于 HTTP 的文件来下载,每次只下载一些。当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,允许流媒体会话适应不同的数据速率。在开始一个流媒体会话时,客户端会下载一个包含元数据的 extended M3U (m3u8) playlist文件,用于寻找可用的媒体流。
303 | HLS 只请求基本的 HTTP 报文,与实时传输协议(RTP)不同,HLS 可以穿过任何允许 HTTP 数据通过的防火墙或者代理服务器。它也很容易使用内容分发网络来传输媒体流。这是HLS应用在直播上的一大优势。
304 | 如果在直播中使用HLS技术,那么执行流程如下:图片来源于苹果官网;
305 | ![HLS直播执行流程](./03-files/19.png)
306 | 我们播放一个HLS,首先要对HLS流对应的M3U8文件进行解析,解析M3U8文件,首先要搞清楚M3U8的封装格式; 307 | #### 2.3.1 HLS格式解析 308 | HLS流可以用于直播,也可以用于点播;
309 | M3U8 文件实质是一个播放列表(playlist),其可能是一个媒体播放列表(Media Playlist),或者是一个主列表(Master Playlist)。 310 | ##### 2.3.1.1 HLS文件类型 311 | 当 M3U8 文件作为媒体播放列表(Media Playlist)时,其内部信息记录的是一系列媒体片段资源,顺序播放该片段资源,即可完整展示多媒体资源。其格式如下所示: 312 | ``` 313 | #EXTM3U 314 | #EXT-X-TARGETDURATION:10 315 | 316 | #EXTINF:9.009, 317 | http://media.example.com/first.ts 318 | #EXTINF:9.009, 319 | http://media.example.com/second.ts 320 | #EXTINF:3.003, 321 | http://media.example.com/third.ts 322 | #EXT-X-ENDLIST 323 | 324 | ``` 325 | 当 M3U8 作为主播放列表(Master Playlist)时,其内部提供的是同一份媒体资源的多份流列表资源。其格式如下所示: 326 | ``` 327 | #EXTM3U 328 | #EXT-X-STREAM-INF:BANDWIDTH=150000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2" 329 | http://example.com/low/index.m3u8 330 | #EXT-X-STREAM-INF:BANDWIDTH=240000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2" 331 | http://example.com/lo_mid/index.m3u8 332 | #EXT-X-STREAM-INF:BANDWIDTH=440000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2" 333 | http://example.com/hi_mid/index.m3u8 334 | #EXT-X-STREAM-INF:BANDWIDTH=640000,RESOLUTION=640x360,CODECS="avc1.42e00a,mp4a.40.2" 335 | http://example.com/high/index.m3u8 336 | #EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS="mp4a.40.5" 337 | http://example.com/audio/index.m3u8 338 | #EXT-X-ENDLIST 339 | 340 | ``` 341 | ##### 2.3.1.2 HLS基本字段 342 | ``` 343 | 344 | #EXTM3U M3U8文件头,必须放在第一行; 345 | #EXT-X-MEDIA-SEQUENCE 第一个TS分片的序列号,一般情况下是0,但是在直播场景下,这个序列号标识直播段的起始位置; #EXT-X-MEDIA-SEQUENCE:0 346 | #EXT-X-TARGETDURATION 每个分片TS的最大的时长; #EXT-X-TARGETDURATION:10 每个分片的最大时长是 10s 347 | #EXT-X-ALLOW-CACHE 是否允许cache; #EXT-X-ALLOW-CACHE:YES #EXT-X-ALLOW-CACHE:NO 默认情况下是YES 348 | #EXT-X-ENDLIST M3U8文件结束符; 349 | #EXTINF extra info,分片TS的信息,如时长,带宽等;一般情况下是 #EXTINF:,[] 后面可以跟着其他的信息,逗号之前是当前分片的ts时长,分片时长 移动要小于 #EXT-X-TARGETDURATION 定义的值; 350 | #EXT-X-VERSION M3U8版本号 351 | #EXT-X-DISCONTINUITY 该标签表明其前一个切片与下一个切片之间存在中断。下面会详解 352 | #EXT-X-PLAYLIST-TYPE 表明流媒体类型; 353 | #EXT-X-KEY 是否加密解析, #EXT-X-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=52" 加密方式是AES-128,秘钥需要请求 https://priv.example.com/key.php?r=52 ,请求回来存储在本地; 354 | 355 | ``` 356 | ##### 2.3.1.3 HLS高级字段 357 | 要想深入了解HLS, 光了解基本字段是不够,本文带你了解一下HLS的高级字段,让你对HLS有更深的理解。<br> 358 | **EXT-X-BYTERANGE**<br> 359 | 我们知道M3U8需要切片,但是有时候我们不想切片(为什么? 我比较懒), 我只想用一个ts来构建一个类似M3U8的分片索引, 这时候EXT-X-BYTERANGE就派上用场了. 360 | 表达的格式如下: 361 | ``` 362 | #EXT-X-BYTERANGE:<length>[@<offset>] 363 | 364 | ``` 365 | 其中length表示range的长度, offset表示这个range从分片的什么位置开始读.<br> 366 | 据一个例子吧: 367 | ``` 368 | #EXTM3U 369 | #EXT-X-TARGETDURATION:11 370 | #EXT-X-MEDIA-SEQUENCE:0 371 | #EXT-X-VERSION:4 372 | #EXTINF:9.009, 373 | #EXT-X-BYTERANGE:12345@0 374 | media.ts 375 | #EXTINF:9.009, 376 | #EXT-X-BYTERANGE:82112@12345 377 | media.ts 378 | #EXTINF:3.003, 379 | #EXT-X-BYTERANGE:69864 380 | media.ts 381 | #EXT-X-ENDLIST 382 | 383 | ``` 384 | 这个M3U8索引文件中,只有media.ts一个分片文件,这个文件可能很大, 第一个指定的分片是从media.ts的0位置开始读12345长度的数据; 第二个指定的分片是从media.ts的12345位置开始读82112长度的数据;第三个没有指定offset, 那么默认从上一个指定分为的结束处开始读. 385 | 当然只有VERSION版本不低于4才可以应用这个属性。<br><br> 386 | 387 | **EXT-X-KEY**<br> 388 | M3U8索引文件中有了#EXT-X-KEY 字段,说明当前的M3U8视频片段可能被加密了.这一行是告诉你应该怎么解密这些视频分片.<br> 389 | 表达的格式如下: 390 | ``` 391 | #EXT-X-KEY:<attribute-list> 392 | 393 | ``` 394 | 这个属性列表中有如下几个字段: 395 | > * METHOD : 这个是一个枚举值,可以为NONE, AES-128, SAMPLE-AES, NONE表述分片并没有被加密,其他的属性集就没有必要出现了,后面介绍如何解密 396 | > * URI : 密钥的地址存放的地方,也是一个url 397 | > * IV : 该值是一个十六进制序列, 它指定要与密钥一起使用的128位无符号整数初始化向量 398 | > * KEYFORMAT : 这个密钥的格式, 可选字段, 默认情况下是"identity", 主要是加强数字证书校验安全性设定的. 399 | 400 | M3U8加密和解密非常重要, 是M3U8的优势之一, 毕竟现代社会越来越注重安全,版权意识也非常重要。<br><br> 401 | 402 | **EXT-X-MAP**<br> 403 | 这个字段是视频的初始化片段, 简而言之,有了这个字段,说明后续的每一个分片文件必须和通过这个初始化片段才能完整解读,缺少这个初始化片段, M3U8视频根本播放不了. 404 | 表达的格式如下: 405 | ``` 406 | #EXT-X-MAP:<attribute-list> 407 | 408 | ``` 409 | > * URI : 初始化片段的地址, 这个信息是必须的. 410 | > * BYTERANGE : 这个可以参考 #EXT-X-BYTERANGE 字段 411 | 412 | 给大家据一个例子: <br> 413 | https://europe.olemovienews.com/hlstimeofffmp4/20210226/fICqcpqr/mp4/fICqcpqr.mp4/master.m3u8 <br> 414 | 其中有一行: <br> 415 | ``` 416 | #EXT-X-MAP:URI="init-v1-a1.mp4" 417 | 418 | ``` 419 | 说明后续的每一个分片都需要这个init-v1-a1.mp4才能真正解码播放出来,取出第一个分片地址如下: 420 | https://europe.olemovienews.com/hlstimeofffmp4/20210226/fICqcpqr/mp4/fICqcpqr.mp4/seg-1-v1-a1.m4s<br> 421 | ``` 422 | ffprobe version 4.2.4-1ubuntu0.1 Copyright (c) 2007-2020 the FFmpeg developers 423 | built with gcc 9 (Ubuntu 9.3.0-10ubuntu2) 424 | configuration: --prefix=/usr --extra-version=1ubuntu0.1 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --arch=amd64 --enable-gpl --disable-stripping --enable-avresample --disable-filter=resample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librsvg --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-nvenc --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared 425 | libavutil 56. 31.100 / 56. 31.100 426 | libavcodec 58. 54.100 / 58. 54.100 427 | libavformat 58. 29.100 / 58. 29.100 428 | libavdevice 58. 8.100 / 58. 8.100 429 | libavfilter 7. 57.100 / 7. 57.100 430 | libavresample 4. 0. 0 / 4. 0. 0 431 | libswscale 5. 5.100 / 5. 5.100 432 | libswresample 3. 5.100 / 3. 5.100 433 | libpostproc 55. 5.100 / 55. 5.100 434 | [mov,mp4,m4a,3gp,3g2,mj2 @ 0x55fd8bd5ff00] could not find corresponding trex (id 1) 435 | [mov,mp4,m4a,3gp,3g2,mj2 @ 0x55fd8bd5ff00] could not find corresponding track id 0 436 | [mov,mp4,m4a,3gp,3g2,mj2 @ 0x55fd8bd5ff00] trun track id unknown, no tfhd was found 437 | [mov,mp4,m4a,3gp,3g2,mj2 @ 0x55fd8bd5ff00] error reading header 438 | https://europe.olemovienews.com/hlstimeofffmp4/20210226/fICqcpqr/mp4/fICqcpqr.mp4/seg-1-v1-a1.m4s: Invalid data found when processing input 439 | 440 | ``` 441 | ![m4s解析错误](./03-files/20.jpeg)<br> 442 | 这是因为光解析第一个分片是不行的,因为真正的视频头部信息在init-v1-a1.mp4中,就是moov信息在init-v1-a1.mp4文件中,所以想要解析完整的MP4视频,还需要moov才可以真正的解析成功。<br><br> 443 | 444 | **EXT-X-I-FRAMES-ONLY**<br> 445 | 这个字段表示每个片段只有一个I帧, I帧是什么大家应该很清楚, 音音视频的兄弟们, 为什么会出现这个字段?<br> 446 | 如果做过视频特效处理的同学应该很清楚, 处理视频的时候,如果都是I帧,那么特效/反转/快进/快退非常方便.<br> 447 | 448 | 当然实际上M3U8上用到 #EXT-X-I-FRAMES-ONLY并不多, 大家了解即可.<br><br> 449 | 450 | **EXT-X-MEDIA**<br> 451 | 这个字段表示针对同一个内容的不同角度的演绎, 例如统一段视频有视频/音频/字幕, 甚至还不止一个音频轨道, 很有很多种语言的字幕, 一段视频有这么多信息, #EXT-X-MEDIA 就是干这个的.<br> 452 | 表达的格式如下: 453 | ``` 454 | #EXT-X-MEDIA:<attribute-list> 455 | 456 | ``` 457 | 属性集有下面的字段: 458 | > * TYPE : 这是一个枚举值, 可以是AUDIO, VIDEO, SUBTITLES, CLOSED-CAPTIONS, 这个字段是必须的. 459 | > * URI : 数据源的url, 如果TYPE是CLOSED-CAPTIONS, URI就没有了 460 | > * GROUP-ID : 表示源属于的组ID, 这只是一个标识 461 | > * DEFAULT : YES 或者 NO 462 | > * AUTOSELECT : YES 或者 NO 463 | 464 | 举个EXT-X-MEDIA应用的例子: 465 | ``` 466 | #EXTM3U 467 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="test_audio",LANGUAGE="eng",NAME="Test Audio",AUTOSELECT=NO,DEFAULT=NO,URI="test_audio_aac/index.m3u8" 468 | 469 | #EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="English",DEFAULT=YES,AUTOSELECT=YES,FORCED=NO,LANGUAGE="en",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog, public.accessibility.describes-music-and-sound",URI="subtitles/eng/index.m3u8" 470 | 471 | #EXT-X-STREAM-INF:BANDWIDTH=263851,CODECS="mp4a.40.2, avc1.4d400d",RESOLUTION=416x234,AUDIO="test_audio",SUBTITLES="subs" 472 | high_test/index.m3u8 473 | #EXT-X-STREAM-INF:BANDWIDTH=41457,CODECS="mp4a.40.2",AUDIO="test_audio",SUBTITLES="subs" 474 | middle_test/index.m3u8 475 | 476 | ``` 477 | ##### 2.3.1.3 HLS的具体例子 478 | (1)HLS基础例子:<br> 479 | ``` 480 | #EXTM3U 481 | #EXT-X-TARGETDURATION:10 482 | #EXT-X-VERSION:3 483 | #EXTINF:9.009, 484 | http://media.example.com/first.ts 485 | #EXTINF:9.009, 486 | http://media.example.com/second.ts 487 | #EXTINF:3.003, 488 | http://media.example.com/third.ts 489 | #EXT-X-ENDLIST 490 | 491 | ``` 492 | 493 | <br> 494 | (2)HLS直播例子:<br> 495 | 496 | ``` 497 | #EXTM3U 498 | #EXT-X-VERSION:3 499 | #EXT-X-TARGETDURATION:8 500 | #EXT-X-MEDIA-SEQUENCE:2680 501 | #EXTINF:7.975, 502 | https://priv.example.com/fileSequence2680.ts 503 | #EXTINF:7.941, 504 | https://priv.example.com/fileSequence2681.ts 505 | #EXTINF:7.975, 506 | https://priv.example.com/fileSequence2682.ts 507 | 508 | ``` 509 | 510 | <br> 511 | (3)master HLS例子:<br> 512 | 513 | ``` 514 | #EXTM3U 515 | #EXT-X-STREAM-INF:BANDWIDTH=1280000,AVERAGE-BANDWIDTH=1000000 516 | http://example.com/low.m3u8 517 | #EXT-X-STREAM-INF:BANDWIDTH=2560000,AVERAGE-BANDWIDTH=2000000 518 | http://example.com/mid.m3u8 519 | #EXT-X-STREAM-INF:BANDWIDTH=7680000,AVERAGE-BANDWIDTH=6000000 520 | http://example.com/hi.m3u8 521 | #EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS="mp4a.40.5" 522 | http://example.com/audio-only.m3u8 523 | 524 | ``` 525 | 526 | <br> 527 | (4)EXT-X-MEDIA例子:<br> 528 | 529 | ``` 530 | #EXTM3U 531 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",NAME="English", \ 532 | DEFAULT=YES,AUTOSELECT=YES,LANGUAGE="en", \ 533 | URI="main/english-audio.m3u8" 534 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",NAME="Deutsch", \ 535 | DEFAULT=NO,AUTOSELECT=YES,LANGUAGE="de", \ 536 | URI="main/german-audio.m3u8" 537 | #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",NAME="Commentary", \ 538 | DEFAULT=NO,AUTOSELECT=NO,LANGUAGE="en", \ 539 | URI="commentary/audio-only.m3u8" 540 | #EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS="...",AUDIO="aac" 541 | low/video-only.m3u8 542 | #EXT-X-STREAM-INF:BANDWIDTH=2560000,CODECS="...",AUDIO="aac" 543 | mid/video-only.m3u8 544 | #EXT-X-STREAM-INF:BANDWIDTH=7680000,CODECS="...",AUDIO="aac" 545 | hi/video-only.m3u8 546 | #EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS="mp4a.40.5",AUDIO="aac" 547 | main/english-audio.m3u8 548 | ``` 549 | 550 | #### 2.3.2 如何判断HLS是否是直播 551 | (1)判断是否存在 #EXT-X-ENDLIST<br> 552 | 对于一个M3U8文件,如果结尾不存在 #EXT-X-ENDLIST,那么一定是 直播,不是点播;<br><br> 553 | 554 | (2)判断 #EXT-X-PLAYLIST-TYPE 类型<br> 555 | 556 | '#EXT-X-PLAYLIST-TYPE' 有两种类型,<br> 557 | 558 | VOD 即 Video on Demand,表示该视频流为点播源,因此服务器不能更改该 M3U8 文件;<br> 559 | 560 | EVENT 表示该视频流为直播源,因此服务器不能更改或删除该文件任意部分内容(但是可以在文件末尾添加新内容)(注:VOD 文件通常带有 EXT-X-ENDLIST 标签,因为其为点播源,不会改变;而 EVEVT 文件初始化时一般不会有 EXT-X-ENDLIST 标签,暗示有新的文件会添加到播放列表末尾,因此也需要客户端定时获取该 M3U8 文件,以获取新的媒体片段资源,直到访问到 EXT-X-ENDLIST 标签才停止)。 561 | 562 | #### 2.3.3 HLS如何提供多码率 563 | 上面提出了有一种Master Playlist 的HLS类型,就是会提供 多份码率的列表资源,如下: 564 | ``` 565 | 566 | #EXTM3U 567 | #EXT-X-STREAM-INF:BANDWIDTH=150000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2" 568 | http://example.com/low/index.m3u8 569 | #EXT-X-STREAM-INF:BANDWIDTH=240000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2" 570 | http://example.com/lo_mid/index.m3u8 571 | #EXT-X-STREAM-INF:BANDWIDTH=440000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2" 572 | http://example.com/hi_mid/index.m3u8 573 | #EXT-X-STREAM-INF:BANDWIDTH=640000,RESOLUTION=640x360,CODECS="avc1.42e00a,mp4a.40.2" 574 | http://example.com/high/index.m3u8 575 | #EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS="mp4a.40.5" 576 | http://example.com/audio/index.m3u8 577 | #EXT-X-ENDLIST 578 | 579 | ``` 580 | '#EXT-X-STREAM-INF' 字段后面有:<br> 581 | BANDWIDTH 指定码率<br> 582 | RESOLUTION 分辨率<br> 583 | PROGRAM-ID 唯一ID<br> 584 | CODECS 指定流的编码类型<br><br> 585 | 586 | 码率、码流是同一个概念,是数据传输时单位时间传送的数据量,一般用单位kbps表示。<br> 587 | 588 | 视频码率就是指视频文件在单位时间内使用的数据量。简单理解就是要播放一秒的视频需要多少数据,从这个角度就不难理解通常码率越高视频质量也越好,相应的文件体积也会越大。码率、视频质量、文件体积是正相关的。但当码率超过一定数值后,对图像的质量影响就不大了。几乎所有的编码算法都在追求用最低的码率达到最少的失真(最好的清晰度); 589 | #### 2.3.4 如何在HLS中插入广告 590 | M3U8文件中插入广告,要想灵活的控制广告,则广告可以插入任何视频中,那么无法保证广告的编码格式和码率等信息和原视频的编码格式等信息保持一致,就必须告知播放器,在插入广告的地方,ts片段发生的信息变更,需要播放器适配处理。<br><br> 591 | 592 | '#EXT-X-DISCONTINUITY' 该标签表明其前一个切片与下一个切片之间存在中断。说明有不连续的视频出现,这个视频绝大多数情况下就是广告;<br> 593 | '#EXT-X-DISCONTINUITY' 这个字段就是来做这个事情的;<br> 594 | 当出现以下情况时,必须使用该标签: 595 | > * file format 596 | > * encoding parameters 597 | 598 | 下面展示一个插入广告的例子: 599 | ``` 600 | #EXTM3U 601 | #EXT-X-TARGETDURATION:10 602 | #EXT-X-VERSION:4 603 | #EXT-X-MEDIA-SEQUENCE:0 604 | #EXTINF:10.0, 605 | movieA.ts 606 | #EXTINF:10.0, 607 | movieB.ts 608 | ... 609 | #EXT-X-ENDLIST 610 | 611 | ``` 612 | 想在开头插入广告: 613 | ``` 614 | #EXTM3U 615 | #EXT-X-TARGETDURATION:10 616 | #EXT-X-VERSION:4 617 | #EXT-X-MEDIA-SEQUENCE:0 618 | #EXTINF:10.0, 619 | ad0.ts 620 | #EXTINF:8.0, 621 | ad1.ts 622 | #EXT-X-DISCONTINUITY 623 | #EXTINF:10.0, 624 | movieA.ts 625 | #EXTINF:10.0, 626 | movieB.ts 627 | ... 628 | #EXT-X-ENDLIST 629 | 630 | ``` 631 | 当然你可以在任意位置插入广告。<br> 632 | 633 | HLS协议草案:HLS协议中还有很多字段,但是有些字段其实就是协议,在实际应用中并不大;大家可以参考看看;https://tools.ietf.org/html/rfc8216 634 | 635 | #### 2.3.5 EXT-X-MAP工作原理 636 | 从上面的格式剖析来看,'#EXT-X-MAP'是视频的初始化片段, 简而言之,有了这个字段,说明后续的每一个分片文件必须和通过这个初始化片段才能完整解读,缺少这个初始化片段,M3U8视频根本播放不了。<br> 637 | 表达的格式如下: 638 | ``` 639 | #EXT-X-MAP:<attribute-list> 640 | 641 | ``` 642 | 这个属性集有下面这些字段: 643 | > * URI : 初始化片段的地址, 这个信息是必须的. 644 | > * BYTERANGE : 这个可以参考 #EXT-X-BYTERANGE 字段 645 | 646 | 上面已经举了一个例子:https://europe.olemovienews.com/hlstimeofffmp4/20210226/fICqcpqr/mp4/fICqcpqr.mp4/master.m3u8 中的其中一行是: 647 | ``` 648 | #EXT-X-MAP:URI="init-v1-a1.mp4" 649 | 650 | ``` 651 | 这是初始化片段,后续的每一个分片都需要这个初始化片段才能真正解析出来。<br> 652 | '#EXT-X-MAP'的字段主要目的有3个: 653 | > * 为了安全 654 | > * 为了节省存储空间 655 | > * 为了减少切片, 减少切片服务器的工作量 656 | 657 | ##### 2.3.5.1 安全作用 658 | '#EXT-X-MAP' 将一个完整视频的头单独存放, 将其他部分存在另一个视频文件中, 这样就是你们抓到了其中一个视频, 也播放不了, 这是防止盗链的一种手段, "将鸡蛋放在不同的篮子里" 659 | 对于https://europe.olemovienews.com/hlstimeofffmp4/20210226/fICqcpqr/mp4/fICqcpqr.mp4/master.m3u8 下载完成的文件如下:<br> 660 | ![M3U8下载完成的截图](./03-files/21.jpeg)<br> 661 | 其中#EXT-X-MAP中视频片段是: 662 | ``` 663 | #EXT-X-MAP:URI="https://europe.olemovienews.com/hlstimeofffmp4/20210226/fICqcpqr/mp4/fICqcpqr.mp4/init-v1-a1.mp4" 664 | 665 | ``` 666 | 可以看出来, #EXT-X-MAP中的视频片段中没有实质内容, 只有视频的头部, 了解MP4视频构造的都知道moov是MP4的头部, mdat才是MP4的实质内容,<br> 667 | ![init-mp4头部](./03-files/22.png)<br> 668 | 对比看一下第一个片段: 669 | https://europe.olemovienews.com/hlstimeofffmp4/20210226/fICqcpqr/mp4/fICqcpqr.mp4/seg-1-v1-a1.m4s 670 | 这是一个fMP4片段, 里面没有moov头部, 单单播放这个视频片段是无法播放成功的, 还需要和#EXT-X-MAP结合起来播放才可以真正播放成功的.<br> 671 | ![fMP4内容](./03-files/23.png)<br> 672 | MP4和fMP4的区别如下:<br> 673 | ![MP4和fMP4区别](./03-files/24.png)<br> 674 | 现在大家知道了'#EXT-X-MAP'的安全就表现在这里=====> 我可以将'#EXT-X-MAP'中的片段和其他的片段放在不同的服务器上, 只要两个片段不被同时拿到, 还是安全的. 675 | 当然安全并不是'#EXT-X-MAP'的主要目的, 毕竟如果为了安全, 还是使用'#EXT-X-KEY'更方便一点. 676 | 677 | ##### 2.3.5.2 减少存储空间 678 | 虽然MP4的moov头部占比不太大, 但是对于一个动不动有成百上千个的M3U8文件, 加起来的大小还是比较可观的, 一个moov头部一般有100K, 如果有1000个fMP4片段, 那么就可以节省10M大小, 海量的视频算算要节省多少数据. 679 | 680 | ##### 2.3.5.3 减少切片工作 681 | 我们上面都是谈 #EXT-X-MAP中的URI属性, 但是别忘了#EXT-X-MAP还有一个BYTERANGE属性, 这个属性是例子一般是 682 | ``` 683 | #EXT-X-MAP:URI="init-v1-a1.mp4",BYTERANGE="1000@2000" 684 | 685 | ``` 686 | 这就表示init-v1-a1.mp4中的1000和2000之间的数据被采用, 其他的不被采用.<br> 687 | 这样做的好处是将一个完整的视频切片是需要工作量的, 如果服务端不想做这个工作, 直接在M3U8索引文件中标识一下采用哪一段的数据, 就不用切片这么复杂的工作了. 688 | 689 | #### 2.3.6 HLS中URL生成规则 690 | M3U8中的URL的表示无处不在, 不管是基础的HLS片段还是Master的HLS类型, 都需要了解URL的计算规则: <br> 691 | 下面介绍四种URL的表示: 692 | > * 直接给出URL 693 | > * 单文件名相对位置 694 | > * 带文件路径的相对位置 695 | > * 双斜杠相对位置 696 | 697 | ##### 2.3.6.1 直接给出URL 698 | ``` 699 | #EXTM3U 700 | #EXT-X-TARGETDURATION:10 701 | #EXT-X-VERSION:3 702 | #EXTINF:9.009, 703 | http://media.example.com/first.ts 704 | #EXTINF:9.009, 705 | http://media.example.com/second.ts 706 | #EXTINF:3.003, 707 | http://media.example.com/third.ts 708 | #EXT-X-ENDLIST 709 | 710 | ``` 711 | 上面的每一个片段都直接指出了片段的具体url是什么,就是完整的url请求,我们在解析的时候就不需要对片段的具体为值进行拼接了,直接请求url即可 712 | 713 | ##### 2.3.6.2 单文件名相对位置 714 | ``` 715 | #EXTM3U 716 | #EXT-X-TARGETDURATION:10 717 | #EXT-X-VERSION:3 718 | #EXTINF:9.009, 719 | first.ts 720 | #EXTINF:9.009, 721 | second.ts 722 | #EXTINF:3.003, 723 | third.ts 724 | #EXT-X-ENDLIST 725 | 726 | ``` 727 | 这儿没有任何url, 只有片段的名字, 例如我们请求的视频url是 http://media.example.com/index.m3u8 728 | 这样我们请求http://media.example.com/index.m3u8 的时候,解析到first.ts的时候,我们会默认接上http://media.example.com/ 变成http://media.example.com/first.ts 729 | 730 | ##### 2.3.6.3 带文件路径的相对位置 731 | 例如 https://douban.donghongzuida.com/20210109/15467_73a719b2/index.m3u8 732 | 解析出来如下: 733 | ``` 734 | #EXTM3U 735 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=800000,RESOLUTION=1080x608 736 | 1000k/hls/index.m3u8 737 | 738 | ``` 739 | 这个1000k/hls/index.m3u8 就不是文件名这个简单了,是一个相对路径, 完整的路径是: 740 | https://douban.donghongzuida.com/20210109/15467_73a719b2/1000k/hls/index.m3u8 741 | 742 | 有时候也可以写成如下: 743 | ``` 744 | #EXTM3U 745 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=800000,RESOLUTION=1080x608 746 | /1000k/hls/index.m3u8 747 | 748 | ``` 749 | 甚至可以写成如下: 750 | ``` 751 | #EXTM3U 752 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=800000,RESOLUTION=1080x608 753 | /15467_73a719b2/1000k/hls/index.m3u8 754 | 755 | ``` 756 | 这时候你会生成https://douban.donghongzuida.com/20210109/15467_73a719b2/15467_73a719b2/1000k/hls/index.m3u8吧, 757 | 显然是不对的.<br> 758 | 759 | 这时候应该找 https://douban.donghongzuida.com/20210109/15467_73a719b2/和/15467_73a719b2/1000k/hls/index.m3u8 共同的那部分,然后将共同的部分抹掉, 这样就能得到新的url了。 760 | ##### 2.3.6.4 双斜杠相对位置 761 | 双斜杠后面一般直接就是域名了, 例如下面 762 | ``` 763 | #EXTM3U 764 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=800000,RESOLUTION=1080x608 765 | //douban.donghongzuida.com/20210109/15467_73a719b2/1000k/hls/index.m3u8 766 | 767 | ``` 768 | 这时候直接增加一个协议就可以的。 769 | 770 | #### 2.3.7 低延时HLS--->LL-HLS 771 | HLS====> HTTP LIVE STREAMING 是苹果公司2009推出的一种流媒体协议, 从推出到现在, 得到了非常广泛的应用, 不管在点播还是直播中, 使用的公司非常多, 点播的情况下不必多说. 我们都知道直播场景下, RTMP使用的显然比HLS更加广泛, 肯定是HLS存在某些缺陷才会导致在直播场景下应用不太广泛。<br> 772 | 下面是几种协议的优劣比较<br> 773 | |协议比较|RTMP|HTT-FLV|HLS| 774 | |:-|:-|:-|:-| 775 | |全称|Real Time Message Protocol|RTMP over HTTP|HTTP Live Streaming| 776 | |所在层|传输层|网络层|网络层| 777 | |是否长链接|是|是|否| 778 | |延时|1 ~ 3s|1 ~ 3s|10s| 779 | |兼容性|部分平台不一定支持|全平台支持|全平台支持| 780 | |扩展性|差,Adobe已经不维护|差,Adobe已经不维护了|Apple全力支持,扩展性好| 781 | 782 | 显而易见, RTMP在传输时延方面确实有很大的优势, 这是目前直播用RTMP的主要原因, 但是苹果公司也不是吃素的, 他们也在积极努力, 改进HLS的时延, 降低直播的耗时, 改善直播观看体验。<br> 783 | 经过多年的努力, 2019年苹果公司推出LL-HLS====> Low Latency HLS来着重解决这类问题。 784 | ##### 2.3.7.1 为什么HLS这么慢 785 | 首先看看HLS 标准协议文档中是怎么介绍的? ====> https://tools.ietf.org/html/rfc8216#section-6.3.3 <br> 786 | ![HLS官方文档1](./03-files/25.png)<br> 787 | ![HLS官方文档2](./03-files/26.png)<br> 788 | ![HLS加载逻辑](./03-files/27.png)<br> 789 | 简而言之, 必须至少加载3个分片视频, 当前的分片才能被启动播放, HLS标准的分片时长是10s, 加载3个分片, 也就说标准的时延要达到30s, 这在正常直播场景中是无法忍受的。 790 | ##### 2.3.7.2 LL-HLS做了什么改进 791 | **(1)生成分片的一部分**<br> 792 | LL-HLS将大的分片且分为一个个较小的分片, 这种切分方式不是简单的将源分片等分, 而是结合fMP4封装和#EXT-X-MAP规则, 将整视频的头部和内容分开, 而且内容源被划分的很细, 例如原来一个分片6s左右, 可能被切分为30个200ms的fMP4分片, 这些分片使用#EXT-X-PART来标注: 793 | ``` 794 | #EXTINF:6.003, 795 | LLHLS_Video1_67750710.mp4 796 | #EXT-X-PROGRAM-DATE-TIME:2021-03-18T09:20:29.482Z 797 | #EXT-X-PART:DURATION=1.000,URI="LLHLS_Video1_67750711.0.mp4",INDEPENDENT=YES 798 | #EXT-X-PART:DURATION=1.000,URI="LLHLS_Video1_67750711.1.mp4",INDEPENDENT=YES 799 | #EXT-X-PART:DURATION=1.000,URI="LLHLS_Video1_67750711.2.mp4",INDEPENDENT=YES 800 | #EXT-X-PART:DURATION=1.000,URI="LLHLS_Video1_67750711.3.mp4",INDEPENDENT=YES 801 | #EXT-X-PART:DURATION=1.000,URI="LLHLS_Video1_67750711.4.mp4",INDEPENDENT=YES 802 | #EXT-X-PART:DURATION=1.000,URI="LLHLS_Video1_67750711.5.mp4",INDEPENDENT=YES 803 | 804 | ``` 805 | 一个整分片LLHLS_Video1_67750710.mp4被切分为6个小分片, 每一个小分片用 '#EXT-X-PART' 标准 806 | 这样的好处是原来要把一整个分片请求下来才能播放, 现在不需要了, 我只要请求一两个小分片就可以播放可, 时间上肯定是大大减少了. <br> 807 | 808 | **(2)播放列表增量更新**<br> 809 | 直播过程中, M3U8索引文件是不断更新的, M3U8索引中会有每一分片的时间戳和真实的时间戳, 这样我们明确知道当前播放到什么问题, 这段分片视频是什么时候下发的, 直播过程中如果出现网络不好, 累积的时延会越来越大, 但是有了时间戳的校验, 网络差的情况下我们也会实时追上最新的播放点. 810 | ====> #EXT-X-SERVER-CONTROL 会告诉你那些分片会被丢弃调. 811 | ``` 812 | #EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=3.150,CAN-SKIP-UNTIL=36.000 813 | 814 | ``` 815 | CAN-SKIP-UNTIL=36.000 说明之前36的视频都是可以舍弃的.<br> 816 | 例如紧接着的文件描述为: 817 | ``` 818 | #EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=3.150,CAN-SKIP-UNTIL=36.000 819 | #EXT-X-MEDIA-SEQUENCE:67750702 820 | #EXT-X-PROGRAM-DATE-TIME:2021-03-18T09:19:35.479Z 821 | #EXTINF:6.000, 822 | LLHLS_Video1_67750702.mp4 823 | #EXT-X-PROGRAM-DATE-TIME:2021-03-18T09:19:41.479Z 824 | #EXTINF:6.000, 825 | LLHLS_Video1_67750703.mp4 826 | #EXT-X-PROGRAM-DATE-TIME:2021-03-18T09:19:47.479Z 827 | #EXTINF:6.000, 828 | LLHLS_Video1_67750704.mp4 829 | #EXT-X-PROGRAM-DATE-TIME:2021-03-18T09:19:53.479Z 830 | #EXTINF:6.000, 831 | LLHLS_Video1_67750705.mp4 832 | #EXT-X-PROGRAM-DATE-TIME:2021-03-18T09:19:59.479Z 833 | #EXTINF:6.000, 834 | LLHLS_Video1_67750706.mp4 835 | #EXT-X-PROGRAM-DATE-TIME:2021-03-18T09:20:05.479Z 836 | #EXTINF:6.000, 837 | LLHLS_Video1_67750707.mp4 838 | #EXT-X-PROGRAM-DATE-TIME:2021-03-18T09:20:11.479Z 839 | #EXTINF:6.000, 840 | LLHLS_Video1_67750708.mp4 841 | #EXT-X-PROGRAM-DATE-TIME:2021-03-18T09:20:17.479Z 842 | #EXT-X-PART:DURATION=1.000,URI="LLHLS_Video1_67750709.0.mp4",INDEPENDENT=YES 843 | #EXT-X-PART:DURATION=1.000,URI="LLHLS_Video1_67750709.1.mp4",INDEPENDENT=YES 844 | #EXT-X-PART:DURATION=1.000,URI="LLHLS_Video1_67750709.2.mp4",INDEPENDENT=YES 845 | #EXT-X-PART:DURATION=1.000,URI="LLHLS_Video1_67750709.3.mp4",INDEPENDENT=YES 846 | #EXT-X-PART:DURATION=1.000,URI="LLHLS_Video1_67750709.4.mp4",INDEPENDENT=YES 847 | #EXT-X-PART:DURATION=1.000,URI="LLHLS_Video1_67750709.5.mp4",INDEPENDENT=YES 848 | 849 | ``` 850 | 服务器明确告知我们, 36s之前的内容都是可以SKIP的, 36s之后开始再切片, 之前之所以不切片因为之前的内容可能比较老了, 没有比较再切片.<br> 851 | 36s之后如果发现'#EXT-X-SKIP' 说明也是可以丢弃的, 这些都是为了解决直播的实时性问题. <br> 852 | 853 | **(3)阻止播放列表重新加载**<br> 854 | 阻止播放列表重新加载, 直播中M3U8索引文件不断更新, 每隔一段时间重新请求以获取最新的M3U8索引列表, 但是重新请求可能浪费的时间更多,现在采用的方式是在LL-HLS中加入一些设置指定未来要请求的特定片段.<br> 855 | 我们在M3U8中会加入一个MSN===> Media Sequence Number来表示即将请求的MSN是哪一个, 这样可以不用重新加载M3U8索引文件, 就提前知道要请求哪一个分片, 哪一个索引文件.<br> 856 | 857 | **(4)预加载支持**<br> 858 | 预加载的支持是通过#EXT-X-PRELOAD-HINT来表示: 859 | ``` 860 | #EXT-X-PRELOAD-HINT:TYPE=PART,URI="LLHLS_Video1_67750712.5.mp4" 861 | 862 | ``` 863 | 通常在加载完一个分片之后, 即将要加载某一个分片之前, 标注一下未来要请求哪一个分片, 这种在索引文件中提前预告的行为确实能为我们省下很多时间.<br> 864 | 865 | **(5)多渲染报告支持**<br> 866 | LL-HLS播放过程中有时候会遇到#EXT-X-RENDITION-REPORT, 这说明接下来需要加载不一样的类型的视频了, 可能是分辨率/码率/格式发生了变化,LAST-MSN表示是在哪一个MSN结束之后开始加载这个新的索引文件. 867 | ``` 868 | #EXT-X-RENDITION-REPORT:URI="LLHLS_Video2.m3u8",LAST-MSN=67750884,LAST-PART=3 869 | 870 | ``` 871 | 举一个LL-HLS的例子: 872 | https://d18lkalz24uryj.cloudfront.net/LLHLS_Video1.m3u8 <br> 873 | 874 | <br> 875 | 876 | **小结:**<br> 877 | > * LL-HLS在直播中的延时大大降低, 可以降低值3s内, 但是即使这样, 还是不如RTMP, 不过Apple还会努力的, 我觉得LL-HLS还是可以优化的, 例如多服务器控制源 878 | > * LL-HLS的控制粒度更细了, 对预加载/H2 push的利用效率更好, 核心原理还是要减少RTT和HLS的原有耗时点. 879 | > * 国内使用LL-HLS并不多, 主要是目前RTMP并没有什么大的瓶颈, 而且RTC也在发展, 选择比较多, 不过LL-HLS很简单, 接入简单, 成本小, 需要维护的成本也小, 也不失为一种选择. 880 | > * Android 平台上ExoPlayer 2.13.0版本已经支持了LL-HLS, 可以体验实测下。 881 | 882 | #### 2.3.8 M3U8合并为MP4视频 883 | 我们知道M3U8文件如果下载到本地的话,是一个个分散的TS文件和一个索引文件,类似如下的情况:<br> 884 | https://europe.olemovienews.com/hlstimeofffmp4/20210226/fICqcpqr/mp4/fICqcpqr.mp4/master.m3u8 解析出来的索引文件如下: 885 | ``` 886 | #EXTM3U 887 | #EXT-X-VERSION:3 888 | #EXT-X-MEDIA-SEQUENCE:0 889 | #EXT-X-TARGETDURATION:6.0 890 | #EXT-X-KEY:METHOD=AES-128,URI="https://www.qzamfz.com/20190710/BjhH5Ffw/key.key" 891 | #EXTINF:5.110089, 892 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw000.ts 893 | #EXTINF:5.005333, 894 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw001.ts 895 | #EXTINF:5.005333, 896 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw002.ts 897 | #EXTINF:5.005333, 898 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw003.ts 899 | #EXTINF:5.005333, 900 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw004.ts 901 | #EXTINF:5.005333, 902 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw005.ts 903 | #EXTINF:5.005333, 904 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw006.ts 905 | #EXTINF:5.005333, 906 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw007.ts 907 | #EXTINF:5.005333, 908 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw008.ts 909 | #EXTINF:5.005333, 910 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw009.ts 911 | #EXTINF:5.005333, 912 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw010.ts 913 | #EXTINF:5.005333, 914 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw011.ts 915 | #EXTINF:5.005333, 916 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw012.ts 917 | #EXTINF:5.005333, 918 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw013.ts 919 | #EXTINF:5.005333, 920 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw014.ts 921 | #EXTINF:5.005333, 922 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw015.ts 923 | #EXTINF:5.005333, 924 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw016.ts 925 | #EXTINF:5.005333, 926 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw017.ts 927 | #EXTINF:5.005333, 928 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw018.ts 929 | #EXTINF:5.005333, 930 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw019.ts 931 | #EXTINF:5.005333, 932 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw020.ts 933 | #EXTINF:5.005333, 934 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw021.ts 935 | #EXTINF:5.005333, 936 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw022.ts 937 | #EXTINF:5.005333, 938 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw023.ts 939 | #EXTINF:5.005333, 940 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw024.ts 941 | #EXTINF:5.005333, 942 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw025.ts 943 | #EXTINF:5.005333, 944 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw026.ts 945 | #EXTINF:5.005333, 946 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw027.ts 947 | #EXTINF:5.005333, 948 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw028.ts 949 | #EXTINF:5.005333, 950 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw029.ts 951 | #EXTINF:5.005333, 952 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw030.ts 953 | #EXTINF:5.005333, 954 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw031.ts 955 | #EXTINF:5.005333, 956 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw032.ts 957 | #EXTINF:5.005333, 958 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw033.ts 959 | #EXTINF:5.005333, 960 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw034.ts 961 | #EXTINF:5.005333, 962 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw035.ts 963 | #EXTINF:5.005333, 964 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw036.ts 965 | #EXTINF:5.005333, 966 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw037.ts 967 | #EXTINF:5.005333, 968 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw038.ts 969 | #EXTINF:5.005333, 970 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw039.ts 971 | #EXTINF:5.005333, 972 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw040.ts 973 | #EXTINF:4.755067, 974 | https://www.qzamfz.com/20190710/BjhH5Ffw/BjhH5Ffw041.ts 975 | #EXT-X-ENDLIST 976 | 977 | ``` 978 | 如果下载到本地之后,本地的文件夹下就会出现很多个ts文件: 979 | ``` 980 | 130|PD1824:/sdcard/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec # ls 981 | e546f6e1e7a649e93be7cf705a9af9ec_local.m3u8 video_1.ts video_13.ts video_17.ts video_20.ts video_24.ts video_28.ts video_31.ts video_35.ts video_39.ts video_5.ts video_9.ts 982 | local.key video_10.ts video_14.ts video_18.ts video_21.ts video_25.ts video_29.ts video_32.ts video_36.ts video_4.ts video_6.ts 983 | remote.m3u8 video_11.ts video_15.ts video_19.ts video_22.ts video_26.ts video_3.ts video_33.ts video_37.ts video_40.ts video_7.ts 984 | video_0.ts video_12.ts video_16.ts video_2.ts video_23.ts video_27.ts video_30.ts video_34.ts video_38.ts video_41.ts video_8.ts 985 | 986 | ``` 987 | 有一个.m3u8的索引文件和很多个ts文件,其中本地的.m3u8索引文件如下:直接存储的是每一个ts文件的地址,这样解析这个.m3u8文件就可以找到本地的ts文件。 988 | ``` 989 | #EXTM3U 990 | #EXT-X-VERSION:3 991 | #EXT-X-MEDIA-SEQUENCE:0 992 | #EXT-X-TARGETDURATION:6.0 993 | #EXT-X-KEY:METHOD=AES-128,URI="/storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/local.key" 994 | #EXTINF:5.110089, 995 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_0.ts 996 | #EXTINF:5.005333, 997 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_1.ts 998 | #EXTINF:5.005333, 999 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_2.ts 1000 | #EXTINF:5.005333, 1001 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_3.ts 1002 | #EXTINF:5.005333, 1003 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_4.ts 1004 | #EXTINF:5.005333, 1005 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_5.ts 1006 | #EXTINF:5.005333, 1007 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_6.ts 1008 | #EXTINF:5.005333, 1009 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_7.ts 1010 | #EXTINF:5.005333, 1011 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_8.ts 1012 | #EXTINF:5.005333, 1013 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_9.ts 1014 | #EXTINF:5.005333, 1015 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_10.ts 1016 | #EXTINF:5.005333, 1017 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_11.ts 1018 | #EXTINF:5.005333, 1019 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_12.ts 1020 | #EXTINF:5.005333, 1021 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_13.ts 1022 | #EXTINF:5.005333, 1023 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_14.ts 1024 | #EXTINF:5.005333, 1025 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_15.ts 1026 | #EXTINF:5.005333, 1027 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_16.ts 1028 | #EXTINF:5.005333, 1029 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_17.ts 1030 | #EXTINF:5.005333, 1031 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_18.ts 1032 | #EXTINF:5.005333, 1033 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_19.ts 1034 | #EXTINF:5.005333, 1035 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_20.ts 1036 | #EXTINF:5.005333, 1037 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_21.ts 1038 | #EXTINF:5.005333, 1039 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_22.ts 1040 | #EXTINF:5.005333, 1041 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_23.ts 1042 | #EXTINF:5.005333, 1043 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_24.ts 1044 | #EXTINF:5.005333, 1045 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_25.ts 1046 | #EXTINF:5.005333, 1047 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_26.ts 1048 | #EXTINF:5.005333, 1049 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_27.ts 1050 | #EXTINF:5.005333, 1051 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_28.ts 1052 | #EXTINF:5.005333, 1053 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_29.ts 1054 | #EXTINF:5.005333, 1055 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_30.ts 1056 | #EXTINF:5.005333, 1057 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_31.ts 1058 | #EXTINF:5.005333, 1059 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_32.ts 1060 | #EXTINF:5.005333, 1061 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_33.ts 1062 | #EXTINF:5.005333, 1063 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_34.ts 1064 | #EXTINF:5.005333, 1065 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_35.ts 1066 | #EXTINF:5.005333, 1067 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_36.ts 1068 | #EXTINF:5.005333, 1069 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_37.ts 1070 | #EXTINF:5.005333, 1071 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_38.ts 1072 | #EXTINF:5.005333, 1073 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_39.ts 1074 | #EXTINF:5.005333, 1075 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_40.ts 1076 | #EXTINF:4.755067, 1077 | /storage/emulated/0/Android/data/com.jeffmony.videodemo/files/Video/Download/e546f6e1e7a649e93be7cf705a9af9ec/video_41.ts 1078 | #EXT-X-ENDLIST 1079 | 1080 | ``` 1081 | 从上面的描述可以看出来,为什么要做M3U8文件的合并? 1082 | > * 如果想将文件移动一下,需要完整拷贝.m3u8索引文件和ts文件。 1083 | > * 如果想将文件分享给别人,抱歉,M3U8文件不支持分享。 1084 | 1085 | 这就是痛点,无法分享文件,因为M3U8文件始终无法看成一个个体,那怎么合并成一个MP4呢?<br> 1086 | 合并使用的是ffmpeg,嵌入到android中的ffmpeg逻辑,具体可以参考开源项目:https://github.com/JeffMony/VideoDownloader <br> 1087 | 这个VideoDownloader支持的主要功能如下: 1088 | > * 1.支持下载HLS视频/非HLS视频 1089 | > * 2.支持并发任务下载 1090 | > * 3.支持线程池动态调整策略 1091 | > * 4.支持HLS众多特性: #EXT-X-KEY / #EXT-X-MAP等 1092 | > * 5.支持下载百分比/下载速度/下载大小等提示 1093 | > * 6.支持数据库同步 1094 | > * 7.支持一系列下载成功率的优化措施 1095 | > * 8.支持HLS下载成功之后合并为MP4视频 1096 | > * 9.支持下载完成之后播放视频 1097 | 1098 | M3U8合并为MP4中,一个比较大的问题就是可能会存在时间戳不连续导致的合成失败的问题: 1099 | ``` 1100 | Application provided invalid, non monotonically increasing dts to muxer in stream 1: 11264 >= 0 1101 | 1102 | ``` 1103 | 要想解决这个问题,首先还是要回到源码, ffmpeg源码中的mux.c文件======>compute_muxer_pkt_fields函数,我截取了最核心的判断逻辑。 1104 | ``` 1105 | if (st->cur_dts && st->cur_dts != AV_NOPTS_VALUE && 1106 | ((!(s->oformat->flags & AVFMT_TS_NONSTRICT) && 1107 | st->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE && 1108 | st->codecpar->codec_type != AVMEDIA_TYPE_DATA && 1109 | st->cur_dts >= pkt->dts) || st->cur_dts > pkt->dts)) { 1110 | av_log(s, AV_LOG_ERROR, 1111 | "Application provided invalid, non monotonically increasing dts to muxer in stream %d: %s >= %s\n", 1112 | st->index, av_ts2str(st->cur_dts), av_ts2str(pkt->dts)); 1113 | return AVERROR(EINVAL); 1114 | } 1115 | 1116 | ``` 1117 | 现在我们一个一个拆解一下判断逻辑: <br> 1118 | **条件一:**<br> 1119 | st->cur_dts 说明当前的dts要存在,而且要大于0<br> 1120 | 1121 | **条件二:**<br> 1122 | st->cur_dts != AV_NOPTS_VALUE 说明当前dts一定不能未知, 不能等于AV_NOPTS_VALUE<br> 1123 | #define AV_NOPTS_VALUE ((int64_t)UINT64_C(0x8000000000000000))<br> 1124 | 1125 | **条件三:**<br> 1126 | ``` 1127 | ((!(s->oformat->flags & AVFMT_TS_NONSTRICT) && 1128 | st->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE && 1129 | st->codecpar->codec_type != AVMEDIA_TYPE_DATA && 1130 | st->cur_dts >= pkt->dts) 1131 | ``` 1132 | 条件三比较复杂, 并且都是&&, 说明只要有一个条件不成立, 整个条件都不成立.<br> 1133 | 1134 | **条件四:**<br> 1135 | st->cur_dts > pkt->dts 当前的dts一定要大于avpacket中的dts, 这样的条件未免有点苛刻了.<br><br> 1136 | 1137 | 条件三是有必要拿出来讲讲的,!(s->oformat->flags & AVFMT_TS_NONSTRICT), s->oformat->flags是一个枚举的标记, 表示当前重新封装的视频遵守什么规则 1138 | ``` 1139 | /// Demuxer will use avio_open, no opened file should be provided by the caller. 1140 | #define AVFMT_NOFILE 0x0001 1141 | #define AVFMT_NEEDNUMBER 0x0002 /**< Needs '%d' in filename. */ 1142 | #define AVFMT_SHOW_IDS 0x0008 /**< Show format stream IDs numbers. */ 1143 | #define AVFMT_GLOBALHEADER 0x0040 /**< Format wants global header. */ 1144 | #define AVFMT_NOTIMESTAMPS 0x0080 /**< Format does not need / have any timestamps. */ 1145 | #define AVFMT_GENERIC_INDEX 0x0100 /**< Use generic index building code. */ 1146 | #define AVFMT_TS_DISCONT 0x0200 /**< Format allows timestamp discontinuities. Note, muxers always require valid (monotone) timestamps */ 1147 | #define AVFMT_VARIABLE_FPS 0x0400 /**< Format allows variable fps. */ 1148 | #define AVFMT_NODIMENSIONS 0x0800 /**< Format does not need width/height */ 1149 | #define AVFMT_NOSTREAMS 0x1000 /**< Format does not require any streams */ 1150 | #define AVFMT_NOBINSEARCH 0x2000 /**< Format does not allow to fall back on binary search via read_timestamp */ 1151 | #define AVFMT_NOGENSEARCH 0x4000 /**< Format does not allow to fall back on generic search */ 1152 | #define AVFMT_NO_BYTE_SEEK 0x8000 /**< Format does not allow seeking by bytes */ 1153 | #define AVFMT_ALLOW_FLUSH 0x10000 /**< Format allows flushing. If not set, the muxer will not receive a NULL packet in the write_packet function. */ 1154 | #define AVFMT_TS_NONSTRICT 0x20000 /**< Format does not require strictly 1155 | increasing timestamps, but they must 1156 | still be monotonic */ 1157 | #define AVFMT_TS_NEGATIVE 0x40000 /**< Format allows muxing negative 1158 | timestamps. If not set the timestamp 1159 | will be shifted in av_write_frame and 1160 | av_interleaved_write_frame so they 1161 | start from 0. 1162 | The user or muxer can override this through 1163 | AVFormatContext.avoid_negative_ts 1164 | */ 1165 | 1166 | #define AVFMT_SEEK_TO_PTS 0x4000000 /**< Seeking is based on PTS */ 1167 | 1168 | ``` 1169 | s->oformat->flags & AVFMT_TS_NONSTRICT 表示输出的视频format格式已经设置了非严格递增的时间戳, 这一点很重要, 我们在HLS合成MP4之前, 需要设置这一参数, 然后s->oformat->flags & AVFMT_TS_NONSTRICT 结果为true, !(s->oformat->flags & AVFMT_TS_NONSTRICT)为false, 条件三自然就不成立.<br> 1170 | 1171 | 条件四的判断我觉得有点鸡肋, 因为已经不要dts是严格递增了, 为什么还要加这个判断了, 我的策略是直接去掉这个判断. <br>所以我将ffmpeg中源码的最终判断条件该成如下: 1172 | ``` 1173 | if (st->cur_dts && st->cur_dts != AV_NOPTS_VALUE && 1174 | ((!(s->oformat->flags & AVFMT_TS_NONSTRICT) && 1175 | st->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE && 1176 | st->codecpar->codec_type != AVMEDIA_TYPE_DATA && 1177 | st->cur_dts >= pkt->dts))) { 1178 | av_log(s, AV_LOG_ERROR, 1179 | "Application provided invalid, non monotonically increasing dts to muxer in stream %d: %s >= %s\n", 1180 | st->index, av_ts2str(st->cur_dts), av_ts2str(pkt->dts)); 1181 | return AVERROR(EINVAL); 1182 | } 1183 | 1184 | ``` 1185 | 而且我在demo中还加上dts和pts的严格校验代码, 核心思想就是保证pts或者dts不能出现AV_NOPTS_VALUE 1186 | ``` 1187 | if (pkt.pts == AV_NOPTS_VALUE) { 1188 | if (pkt.dts != AV_NOPTS_VALUE) { 1189 | pkt.pts = pkt.dts; 1190 | last_dts = pkt.dts; 1191 | } else { 1192 | pkt.pts = last_dts + 1; 1193 | pkt.dts = pkt.pts; 1194 | last_dts = pkt.pts; 1195 | } 1196 | } else { 1197 | if (pkt.dts != AV_NOPTS_VALUE) { 1198 | last_dts = pkt.dts; 1199 | } else { 1200 | pkt.dts = pkt.pts; 1201 | last_dts = pkt.dts; 1202 | } 1203 | } 1204 | 1205 | if (pkt.pts < pkt.dts) { 1206 | pkt.pts = pkt.dts; 1207 | } 1208 | 1209 | ``` 1210 | 最终合并出来的视频是正常的。具体大家还是参照一下我上面列的开源项目VideoDownloader<br> 1211 | 1212 | 1213 | ### 2.4 DASH 1214 | https://mpeg.chiariglione.org/standards/mpeg-dash/ 1215 | 1216 | ## 3.常见的音视频编码格式 1217 | 1218 | 为什么需要编码?<br> 1219 | 我们知道视频是一张张图片组成的,假如一张图片时1280 x 720的像素,如果视频的帧率是30,一秒有30张图片,那一秒钟的大小是:1280 x 720 x 30 / 8 / 1024 / 1024 = 3.30MB,一秒的视频大小是3.3MB,那1个小时的视频占多大呢?<br> 1220 | 3.3 MB x 60 x 60 = 11880 MB = 11.6 GB<br> 1221 | 1个小时的视频就要11.6GB,大家应该知道这是什么概念吧。如果不加压缩,那这种级别的大小会压垮互联网的,我们想看视频就是奢望了。现在的带宽完全hold不住啊。<br> 1222 | 视频编码就是这种压缩方式。现在比较常用的编码解码主要是H264和H265(HEVC),当然还有google提倡的VP9和AV1等等,其思想基本上类似,只不过编码算法更优。<br><br> 1223 | 1224 | 推荐几款常用的H264码流分析工具。 1225 | > * StreamEye : https://www.jiangyu.org/streameye-4-0/ 1226 | > * H264/H265 BSAnalyzer : https://github.com/latelee/H264BSAnalyzer 1227 | > * H264bitstream : https://github.com/aizvorski/h264bitstream 1228 | 1229 | ### 3.1 H264 1230 | #### 3.1.1 H264结构 1231 | 用下面一张图了解一下H264的结构:<br> 1232 | ![H264帧结构](./03-files/28.png)<br> 1233 | H264包含一个序列帧,就是各种视频帧,视频帧中包含图像,图像中包含很多Slice(片),每个Slice分为N多个宏块,一个宏块由若干个子块组成。<br> 1234 | Slice是H264定义的一个概念,Slice主要是为数据传输而定义的,每个Slice都是独立发送的,其中包含的信息可以确定片内预测和片间预测的数据<br> 1235 | ![Slice结构图](./03-files/29.png)<br> 1236 | > * 分片包含头和数据两部分。 1237 | > * 分片头中包含分片类型、分片中的宏块类型、分片帧的数量、分片属于哪个像素以及对应的帧的设置和参数信息。 1238 | > * 分片中存储的具体信息就是宏块,宏块中存储着具体的像素数据。 1239 | 1240 | <br> 1241 | 宏块包含每一个像素的亮度和色度信息,一个宏块由一个16 x 16亮度像素和附加的一个8 x 8 Cb 和一个 8 x 8 Cr 彩色像素块组成,若干个宏块排列成Slice<br> 1242 | ![宏块结构图](./03-files/30.png)<br> 1243 | 从上图中可以看到宏块中包含了宏块类型、预测类型、Coded Block Pattern、Quantization Parameter、像素的亮度和色度数据集信息等等。<br><br> 1244 | 1245 | 那NALU处于什么位置呢?了解过H264编码的同学可能听说过NALU概念。<br> 1246 | ![NALU结构组装图](./03-files/31.png)<br> 1247 | 视频帧的会被切分为若干个Slice切片,每个切片会和一个NALU头部组装成一个NALU单元,这就是NALU单元的由来,为什么要这样组装,因为NALU组装起来便于发送数据。NALU是用来组成切片数据的,但是也可以装载其他的数据,例如PPS和SPS等描述视频信息的数据,这个后面会提到。<br> 1248 | H264规定编码器吐出来的数据需要在每个NALU添加起始码0x000001或者0x00000001,用来指示一个NALU的起始和终止位置,所以H264编码器输出的码流中每个帧开头的3---4字节的start code起始码是0x000001(0x 00 00 01)或者0x00000001(0x 00 00 00 01)<br> 1249 | 现在有一个问题,如果在Slice具体数据中如果发现了0x000001(0x 00 00 01)或者0x00000001(0x 00 00 00 01)怎么办?<br> 1250 | 如发现这种问题,需要在0x01之前加上0x03,在解析NALU具体数据的时候如果遇到0x03 01 会将前面的03 去掉,就是脱敏处理。 1251 | 1252 | 1253 | NALU,下面统一用NALU来代替宏块,NALU结构分为两层:视频编码层(VCL)和网络适配层(NAL): 1254 | > * VCL: Video Coding Layer,负责高效的视频内容表示,核心算法引擎,其中对宏块和片的处理都在这个层级上。 1255 | > * NAL: Network Abstraction Layer,以网络所要求的的方式对数据进行打包和发送。 1256 | 1257 | **为什么要分层处理?**<br> 1258 | 这样做的目的:VCL只负责视频的信号处理,包含压缩,量化等处理,NAL解决编码后数据的网络传输,这样可以将VCL和NAL的处理放到不同平台来处理,可以减少因为网络环境不同对VCL的比特流进行重构和重编码。<br><br> 1259 | 1260 | ![H264数据结构方面的分层](./03-files/32.png)<br> 1261 | 1262 | 1263 | **NALU类型**<br> 1264 | |NALU type|含义|备注| 1265 | |:-|:-|:-| 1266 | |0|未使用|| 1267 | |1|非IDR图像编码的SLICE|比如普通I、P、B帧| 1268 | |2|编码SLICE数据划分A|只传递片中最重要的信息,如片头,片中宏块的预测模式等;一般不会用到| 1269 | |3|编码SLICE数据划分B|只传输残差;一般不会用到| 1270 | |4|编码SLICE数据划分C|只传输残差中的AC系数;一般不会用到| 1271 | |5|IDR图像中的编码SLICE|IDR一定是I帧但是I帧不一定是IDR帧| 1272 | |6|SEI补充增强信息单元|可以存一些私有数据等;直播一般使用SEI来存储播放时间戳| 1273 | |7|SPS 序列参数集|SPS对如标识符、帧数以及参考帧数目、解码图像尺寸和帧场模式等解码参数进行标识记录| 1274 | |8|PPS 图像参数集|PPS对如熵编码类型、有效参考图像的数目和初始化等解码参数进行标志记录| 1275 | |9|单元定界符|视频图像的边界| 1276 | |10|序列结束|表明下一图像为IDR图像| 1277 | |11|码流结束|表示该码流中已经没有图像| 1278 | |12|填充数据|哑元数据,用于填充字节| 1279 | |13 ~ 23|保留|| 1280 | |24 ~ 31|未使用|| 1281 | 1282 | 其中比较重要的数据是SPS和PPS,这个下面会展开分析。 1283 | 1284 | #### 3.1.2 三种帧 1285 | I/P/B帧是H264中三种帧类型。 1286 | |帧类型|含义|备注| 1287 | |:-|:-|:-| 1288 | |I帧|帧内编码帧,又称intra frame|I 帧通常是每个 GOP(MPEG 所使用的一种视频压缩技术)的第一个帧,经过适度地压缩,做为随机访问的参考点,可以当成图象。I帧可以看成是一个图像经过压缩后的产物| 1289 | |P帧|前向预测编码帧,又称predictive-frame|通过充分将低于图像序列中前面已编码帧的时间冗余信息来压缩传输数据量的编码图像,也叫预测帧| 1290 | |B帧|双向预测帧,又称bi-directional interpolated prediction frame|既考虑与源图像序列前面已编码帧,也顾及源图像序列后面已编码帧之间的时间冗余信息来压缩传输数据量的编码图像,也叫双向预测帧| 1291 | 1292 | <br><br> 1293 | 1294 | > * I frame : 自身可以通过视频解压算法解压成一张单独的完整的图片。 1295 | > * P frame : 需要参考其前面的一个I frame 或者B frame来生成一张完整的图片。 1296 | > * B frame : 则要参考其前一个I或者P帧及其后面的一个P帧来生成一张完整的图片 1297 | 1298 | 上面也提到了IDR帧,这和I帧有什么区别,下面有分析。 1299 | 1300 | #### 3.1.3 IDR帧 1301 | IDR全程是Instantaneous Decoding Refresh,即时解码刷新,IDR和I帧都是使用帧内预测,对于IDR帧而言,IDR帧之后的所有帧都不能引用任何IDR帧之前的帧内容,但是普通的I帧是可以引用的,这就是区别。<br> 1302 | 一段视频的第0帧,就是首帧肯定是IDR帧,IDR帧是一个GOP(Group of Pictures)中的首个I帧,即从IDR帧开始,重新开始一个新的序列编码,它的作用是使解码器立即刷新,从而使预测错误不致传播,并提供随机访问的能力。一个GOP中可以有很多的I帧,但只能有一个IDR帧。IDR帧一定是I帧,但I帧不一定是IDR帧。对IDR帧的编码处理与I帧的处理相同: 1303 | > * (1)进行帧内预测,决定所采用的帧内预测模式。 1304 | > * (2)像素值减去预测值,得到残差。 1305 | > * (3)对残差进行变换和量化。 1306 | > * (4)变长编码和算术编码。 1307 | > * (5)重构图像并滤波,得到的图像作为其它帧的参考帧。 1308 | 1309 | #### 3.1.4 SPS和PPS 1310 | SPS即Sequence Paramater Set,又称作序列参数集。SPS中保存了一组编码视频序列(Coded video sequence)的全局参数。所谓的编码视频序列即原始视频的一帧一帧的像素数据经过编码之后的结构组成的序列。而每一帧的编码后数据所依赖的参数保存于图像参数集中。一般情况SPS和PPS的NALUnit通常位于整个码流的起始位置。但在某些特殊情况下,在码流中间也可能出现这两种结构,主要原因可能为: 1311 | > * 解码器需要在码流中间开始解码 1312 | > * 编码器在编码的过程中改变了码流的参数(如图像分辨率等) 1313 | 1314 | 在H.264的各种语法元素中,SPS中的信息至关重要。如果其中的数据丢失或出现错误,那么解码过程很可能会失败。SPS及后续将要讲述的图像参数集PPS在某些平台的视频处理框架(比如iOS的VideoToolBox等)还通常作为解码器实例的初始化信息使用。<br> 1315 | 1316 | H.264中另一重要的参数集合为图像参数集Picture Paramater Set(PPS)。通常情况下,PPS类似于SPS,在H.264的裸码流中单独保存在一个NAL Unit中,只是PPS NALUnit的nal_unit_type值为8;而在封装格式中,PPS通常与SPS一起,保存在视频文件的文件头中。<br><br> 1317 | 1318 | #### 3.1.5 GOP 1319 | 一段时间内图像变化不大的图像集我们就可以称之为一个序列,这里的图像又在H264里面称为帧,所以就是一组视频帧,其中第一个我们称为是IDR帧。<br> 1320 | GOP是画面组,一个GOP是一组连续的画面。<br> 1321 | GOP一般有两个数字,如M=3,N=12.M制定I帧与P帧之间的距离,N指定两个I帧之间的距离。那么现在的GOP结构是I BBP BBP BBP BB I<br> 1322 | 增大图片组能有效的减少编码后的视频体积,但是也会降低视频质量,至于怎么取舍,得看需求了。<br> 1323 | 1324 | #### 3.1.6 帧内预测和帧间预测 1325 | 帧内预测主要是针对同一个帧的,如下图:<br> 1326 | ![示例图片](./03-files/34.png)<br> 1327 | 图片中红框包含的信息都差不多,其实可以压缩到同一个宏块的,这就是帧间压缩。<br> 1328 | 1329 | 帧间预测就是帧间压缩,视频是一连串的视频帧组成的,是一组连续动作组成的序列,正常情况下,前后帧的图片差异不会太大,我们会通过计算前后帧的差异,然后通过运动补偿来解决帧间压缩的问题。这就是帧间压缩。 1330 | 1331 | 1332 | #### 3.1.7 DTS与PTS 1333 | PTS(Presentation Time Stamp) : PTS主要用于度量解码后的视频帧什么时候被显示出来。<br> 1334 | DTS(Decode Time Stamp) : DTS主要是标识内存中的bit流再什么时候开始送入解码器中进行解码。<br> 1335 | DTS与PTS的不同:<br> 1336 | DTS主要用户视频的解码,在解码阶段使用。PTS主要用于视频的同步和输出,在display的时候使用。再没有B frame的时候DTS和PTS是一样的。 1337 | 1338 | #### 3.1.8 H264封装分类 1339 | **annexb模式**<br> 1340 | 传统模式,有startcode,SPS和PPS是在ES中,vlc里打开编码器信息中显示h264。 1341 | 1342 | <br><br> 1343 | 1344 | **mp4模式**<br> 1345 | 一般mp4 ,mkv会有,没有startcode,SPS和PPS以及其它信息被封装在container中,每一个frame前面是这个frame的长度,vlc里打开编码器信息中显示avc1。 1346 | 1347 | <br><br> 1348 | ![mp4_annexb模式](./03-files/33.png)<br> 1349 | 1350 | ### 3.2 HEVC 1351 | ### 3.3 AAC 1352 | ### 3.4 YUV 1353 | YUV并不是编解码的格式,不过暂且放在这个小节里面讲讲吧。 1354 | ## 3.常见的音视频传输协议 1355 | ### 3.1 RTMP 1356 | ### 3.2 QUIC 1357 | -------------------------------------------------------------------------------- /音视频平凡之路/04-播放器全面剖析.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/04-播放器全面剖析.md -------------------------------------------------------------------------------- /音视频平凡之路/05-FFmpeg全面剖析.md: -------------------------------------------------------------------------------- 1 | # 第五章 FFmpeg全面剖析 2 | 3 | ## 编译so 4 | Android开发中需要编译ffmpeg相关的so,一般需要外接openssl、libx264、libfdk-aac、hevc等库,现在学习一下如何将这些库都链接到ffmpeg中。<br> 5 | ![openssl初始化调整](./05-files/01.png) -------------------------------------------------------------------------------- /音视频平凡之路/05-files/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/05-files/01.png -------------------------------------------------------------------------------- /音视频平凡之路/06-OpenGL知多少.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/06-OpenGL知多少.md -------------------------------------------------------------------------------- /音视频平凡之路/07-音视频SDK怎么做.md: -------------------------------------------------------------------------------- 1 | # 第七章 音视频SDK怎么做 2 | ## 日志平台建设 3 | 建议的日志平台是腾讯的开源软件:mars----> https://github.com/Tencent/mars -------------------------------------------------------------------------------- /音视频平凡之路/audioplayer/JeffAudioPlayer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/audioplayer/JeffAudioPlayer.jpg -------------------------------------------------------------------------------- /音视频平凡之路/audioplayer/audioplayer_mem_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/audioplayer/audioplayer_mem_1.png -------------------------------------------------------------------------------- /音视频平凡之路/audioplayer/audioplayer_mem_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/audioplayer/audioplayer_mem_2.png -------------------------------------------------------------------------------- /音视频平凡之路/audioplayer/audioplayer_mem_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/audioplayer/audioplayer_mem_3.png -------------------------------------------------------------------------------- /音视频平凡之路/audioplayer/音频播放器完整流程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/audioplayer/音频播放器完整流程.png -------------------------------------------------------------------------------- /音视频平凡之路/opengl/OpenGL-native-api1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/opengl/OpenGL-native-api1.png -------------------------------------------------------------------------------- /音视频平凡之路/opengl/OpenGL-native-so.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/opengl/OpenGL-native-so.png -------------------------------------------------------------------------------- /音视频平凡之路/opengl/OpenGL坐标系.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/opengl/OpenGL坐标系.png -------------------------------------------------------------------------------- /音视频平凡之路/opengl/光栅化流程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JeffMony/AV_Knowledge/5845d451f02b76e76485cbe0009f71da781ec192/音视频平凡之路/opengl/光栅化流程.png -------------------------------------------------------------------------------- /音视频平凡之路/从0开始搭建一个视频播放器.md: -------------------------------------------------------------------------------- 1 | # 从0开始搭建一个视频播放器 -------------------------------------------------------------------------------- /音视频平凡之路/从0开始搭建一个音频播放器.md: -------------------------------------------------------------------------------- 1 | # 从0开始搭建一个音频播放器 2 | ## 1.音频播放完整流程 3 | 日常生活中,音频无处不在,从电话录音、音乐等都是音频的一种形式,当然视频中的声音也是音频,只不过和视频画面合成在一起,本文不讨论视频的问题,留在下一章讨论。<br> 4 | 音频播放器的完整流程如下:<br> 5 | ![音频播放器流程](./audioplayer/音频播放器完整流程.png)<br> 6 | 从上面的流程可以看出完整的播放流程。从中归纳出几个重要的点,本文也是围绕这几个点展开来讲的。 7 | - 解封、解码 8 | - 重采样 9 | - OpenSLES启动 10 | - 音频队列 11 | - 音频后期处理 12 | 13 | 下面是我写的一个音频播放器的主界面截图(ps : 我不是很擅长写界面,界面不太友好,不过该有的功能都有了,大家将就看吧,笑cry)<br> 14 | ![音频播放器截图](./audioplayer/JeffAudioPlayer.jpg)<br> 15 | 音频播放器的几个核心功能:<br> 16 | - 正常播放、暂停、停止、释放资源 17 | - 拖动进度条 18 | - 倍速 19 | - 控制音量 20 | - 循环播放 21 | - 类播放器的接口 22 | 23 | 本文会一一介绍音频播放的核心流程,希望大家看完了本文,可以照着开源项目,自己消化理解一下,真正掌握音频播放器的核心原理。<br> 24 | ## 2.播放器用到的库 25 | 音频播放器需要解封、解码、重采样,这些都是ffmpeg支持的;如果需要访问网络,甚至支持https,那需要支持支持openssl;还需要倍速的功能,那可供选择的有sonic库和soundtouch库,当然两种库也是各有优劣,sonic对单纯说话的音频支持程度比较好,soundtouch采用WSOLA算法对音乐支持较好,但是大名鼎鼎的ExoPlayer也是采用的sonic算法,再加上soundtouch比sonic要大多了,所以此项目采用sonic支持倍速的功能。 26 | - ffmpeg 27 | - openssl 28 | - sonic 29 | 30 | ### 2.1 编译库 31 | ffmpeg库是一个很大的多媒体框架,音频播放器中只用到其中的一部分,所以需要裁剪,对于新手而言,编译ffmpeg库还是有一些门槛的, 32 | -------------------------------------------------------------------------------- /音视频平凡之路/如何在工程中引入Asan解决内存问题.md: -------------------------------------------------------------------------------- 1 | # 如何在工程中引入Asan解决内存问题 2 | ## 1.背景 3 | 我们做音视频开发的,平时主要写native代码,native crash中比较难缠的是内存问题,内存溢出、野指针、内存拷贝失败等等问题特别难缠,首先当然需要一个优秀的大脑,在写native程序的时候一定要小心加仔细,当然内存问题并不能完全避免,遇到此类问题,特别难查。这时候我们特别需要这个内存检测工具,Asan就是这样一个优秀的工具。 4 | 5 | ## 2.Asan介绍 6 | Asan全称是AddressSanitizer是一种基于编译器的快速检测的工具,用于检测原生代码中的内存错误问题,Asan可以解决如下四种核心问题: 7 | - 堆栈和堆缓冲区上溢、下溢 8 | - 释放之后堆重新使用问题 9 | - 超过范围的堆栈使用情况 10 | - 重复释放、错误释放问题 11 | Asan可以在32位和64位的arm平台上已经x84和x86-64上运行,但是运行的内存开销比较大,所以还是在调试代码的过程中使用asan,方便排查问题。当然还有一种内存检测工具HWAsan内存开销小一点,性能高一点。大家有空可以尝试下,本文我们主要介绍Asan工具。 12 | 13 | ## 3.Asan接入 14 | ### 3.1 build.gradle文件 15 | 首先在native项目的build.gradle中增加如下的配置: 16 | ``` 17 | android { 18 | 19 | defaultConfig { 20 | 21 | externalNativeBuild { 22 | cmake { 23 | arguments "-DANDROID_ARM_MODE=arm", "-DANDROID_STL=c++_shared" 24 | abiFilters 'armeabi-v7a', 'arm64-v8a' 25 | } 26 | } 27 | } 28 | } 29 | 30 | ``` 31 | 加入arguments `"-DANDROID_ARM_MODE=arm", "-DANDROID_STL=c++_shared"`这一行,Asan需要这些配置属性 32 | 33 | ### 3.2 CMakeLists.txt 34 | 现在native代码都通过引入CMakeList.txt来完成编译,需要加上需要检测的native库的名称。 35 | ``` 36 | target_link_libraries( 37 | ltpaudio 38 | android 39 | OpenSLES 40 | log 41 | z 42 | ltpffmpeg 43 | ) 44 | 45 | target_compile_options(ltpaudio PUBLIC -fsanitize=address -fno-omit-frame-pointer) 46 | set_target_properties(ltpaudio PROPERTIES LINK_FLAGS -fsanitize=address) 47 | 48 | ``` 49 | 添加最后两行,其中第一个参数都是你需要检测的库的名称,例如我本次需要检测是ltpaudio这个库。 50 | 51 | ### 3.3 导入Asan库 52 | 使用Asan的话,需要保证你当前使用的ndk版本不低于21版本,Asan库的具体位置在toolchains/llvm/prebuilt/darwin-x86_64/lib64/clang/9.0.8/lib/linux目录下: 53 | ``` 54 | jeffli@admindeMacBook-Pro linux % pwd 55 | /Users/jeffli/Library/Android/sdk/ndk/21.1.6352462/toolchains/llvm/prebuilt/darwin-x86_64/lib64/clang/9.0.8/lib/linux 56 | jeffli@admindeMacBook-Pro linux % ls -al *asan*.so 57 | -rw-r--r-- 1 jeffli staff 1904936 7 19 17:32 libclang_rt.asan-aarch64-android.so 58 | -rw-r--r-- 1 jeffli staff 1797956 7 19 17:32 libclang_rt.asan-arm-android.so 59 | -rw-r--r-- 1 jeffli staff 1666512 7 19 17:32 libclang_rt.asan-i686-android.so 60 | -rw-r--r-- 1 jeffli staff 1808008 7 19 17:32 libclang_rt.asan-x86_64-android.so 61 | -rw-r--r-- 1 jeffli staff 855376 7 19 17:32 libclang_rt.hwasan-aarch64-android.so 62 | -rw-r--r-- 1 jeffli staff 796264 7 19 17:32 libclang_rt.hwasan-x86_64-android.so 63 | 64 | ``` 65 | 这儿把hwasan列出来了,其实我们只需要asan库就行了,asan库有两个,32位和64位的。x86的库也是类似的,因为我们项目中只用到arm的32位和64位,所以只要这两个库 66 | ``` 67 | libclang_rt.asan-aarch64-android.so 68 | libclang_rt.asan-arm-android.so 69 | 70 | ``` 71 | 分别将这两个库拷贝你工程目录下对应的jniLibs下面,如下: 72 | ``` 73 | jeffli@admindeMacBook-Pro jniLibs % pwd 74 | /Users/jeffli/sources/JeffAudioPlayer/audiolibrary/src/main/jniLibs 75 | jeffli@admindeMacBook-Pro jniLibs % tree -L 2 76 | . 77 | ├── arm64-v8a 78 | │ └── libclang_rt.asan-aarch64-android.so 79 | └── armeabi-v7a 80 | └── libclang_rt.asan-arm-android.so 81 | 82 | ``` 83 | 84 | ### 3.4 导入wrap.sh文件 85 | 导入了对应的Asan库,需要执行脚本来链接Asan库,这时候需要在对应的目录下添加wrap.sh脚本。wrap.sh如下: 86 | ``` 87 | #!/system/bin/sh 88 | HERE="$(cd "$(dirname "$0")" && pwd)" 89 | export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1 90 | ASAN_LIB=$(ls $HERE/libclang_rt.asan-*-android.so) 91 | if [ -f "$HERE/libc++_shared.so" ]; then 92 | # Workaround for https://github.com/android-ndk/ndk/issues/988. 93 | export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so" 94 | else 95 | export LD_PRELOAD="$ASAN_LIB" 96 | fi 97 | "$@" 98 | 99 | ``` 100 | 将wrap.sh拷贝到main/resource/lib下面对应的库文件夹下,目录结构如下: 101 | ``` 102 | jeffli@admindeMacBook-Pro main % pwd 103 | /Users/jeffli/sources/JeffAudioPlayer/audiolibrary/src/main 104 | jeffli@admindeMacBook-Pro main % ls 105 | AndroidManifest.xml cpp java jniLibs resources 106 | 107 | ``` 108 | 创建resources文件夹: 109 | ``` 110 | jeffli@admindeMacBook-Pro resources % tree -L 3 111 | . 112 | └── lib 113 | ├── arm64-v8a 114 | │ └── wrap.sh 115 | └── armeabi-v7a 116 | └── wrap.sh 117 | 118 | ``` 119 | 目前所有的Asan相关的接入都完成了。 120 | 121 | ## 4.检测例子 122 | 我自己业余时间搞了一个音乐播放器,核心逻辑都在native层,有时候会发生一些native crash问题,指向性不明确,问题不好查,正好可以用一下Asan看看是否有效。<br> 123 | 下面是我写的一个音频播放器。<br> 124 | ![音频播放器截图](./audioplayer/JeffAudioPlayer.jpg)<br> 125 | 点击播放的时候发生了crash: 126 | ``` 127 | 2021-09-17 00:32:31.156 20042-20042/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 128 | 2021-09-17 00:32:31.156 20042-20042/? A/DEBUG: Build fingerprint: 'samsung/t2qzcx/t2q:11/RP1A.200720.012/G9960ZCU2AUGE:user/release-keys' 129 | 2021-09-17 00:32:31.156 20042-20042/? A/DEBUG: Revision: '13' 130 | 2021-09-17 00:32:31.156 20042-20042/? A/DEBUG: ABI: 'arm64' 131 | 2021-09-17 00:32:31.156 20042-20042/? A/DEBUG: Timestamp: 2021-09-17 00:32:31+0800 132 | 2021-09-17 00:32:31.156 20042-20042/? A/DEBUG: pid: 19946, tid: 20011, name: AudioTrack >>> com.jeffmony.audioplayer <<< 133 | 2021-09-17 00:32:31.157 20042-20042/? A/DEBUG: uid: 10350 134 | 2021-09-17 00:32:31.157 20042-20042/? A/DEBUG: signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr -------- 135 | 2021-09-17 00:32:31.157 20042-20042/? A/DEBUG: Abort message: '================================================================= 136 | ==19946==ERROR: AddressSanitizer: heap-use-after-free on address 0x004ac1e41080 at pc 0x007157f69580 bp 0x00705c0bb350 sp 0x00705c0bab08 137 | READ of size 1792 at 0x004ac1e41080 thread T32 (AudioTrack) 138 | #0 0x7157f6957c (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libclang_rt.asan-aarch64-android.so+0x9f57c) 139 | #1 0x706549c228 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x14228) 140 | #2 0x706549bcd4 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x13cd4) 141 | #3 0x70654994f0 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x114f0) 142 | #4 0x70654a9cbc (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x21cbc) 143 | #5 0x70654a91d4 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x211d4) 144 | #6 0x715af9d188 (/system/lib64/libwilhelm.so+0x1c188) 145 | #7 0x71570ea290 (/system/lib64/libaudioclient.so+0x8b290) 146 | #8 0x71570e9480 (/system/lib64/libaudioclient.so+0x8a480) 147 | #9 0x7156b664d4 (/system/lib64/libutils.so+0x154d4) 148 | #10 0x71593e9974 (/system/lib64/libandroid_runtime.so+0xa5974) 149 | #11 0x7156b65db0 (/system/lib64/libutils.so+0x14db0) 150 | #12 0x7156ace234 (/apex/com.android.runtime/lib64/bionic/libc.so+0xb6234) 151 | #13 0x7156a68e64 (/apex/com.android.runtime/lib64/bionic/libc.so+0x50e64) 152 | 153 | 154 | 0x004ac1e41080 is located 0 bytes inside of 1792-byte region [0x004ac1e41080,0x004ac1e41780) 155 | freed by thread T32 (AudioTrack) here: 156 | #0 0x7157f74c64 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libclang_rt.asan-aarch64-android.so+0xaac64) 157 | #1 0x70654a6d2c (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x1ed2c) 158 | #2 0x70654a6af0 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x1eaf0) 159 | #3 0x706549bf4c (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x13f4c) 160 | #4 0x706549bcd4 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x13cd4) 161 | #5 0x70654994f0 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x114f0) 162 | #6 0x70654a9cbc (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x21cbc) 163 | #7 0x70654a91d4 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x211d4) 164 | #8 0x715af9d188 (/system/lib64/libwilhelm.so+0x1c188) 165 | #9 0x71570ea290 (/system/lib64/libaudioclient.so+0x8b290) 166 | 167 | ``` 168 | 显示message是:heap-use-after-free on address 0x004ac1e41080<br> 169 | 说明是使用了已经释放掉的内存了,再继续看,这个内存具体在什么地方被释放的?<br> 170 | 0x004ac1e41080 is located 0 bytes inside of 1792-byte region [0x004ac1e41080,0x004ac1e41780)<br> 171 | 先解栈看看具体的代码行:<br> 172 | ``` 173 | #1 0x706549c228 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x14228) 174 | #2 0x706549bcd4 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x13cd4) 175 | #3 0x70654994f0 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x114f0) 176 | #4 0x70654a9cbc (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x21cbc) 177 | #5 0x70654a91d4 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x211d4) 178 | 179 | ``` 180 | 解栈结果是: 181 | ``` 182 | _ZN5media11AudioPlayer18ProcessNormalAudioEv 183 | /Users/jeffli/sources/JeffAudioPlayer/audiolibrary/src/main/cpp/audio_player.cc:344 184 | 185 | _ZN5media11AudioPlayer17GetAudioFrameDataEv 186 | /Users/jeffli/sources/JeffAudioPlayer/audiolibrary/src/main/cpp/audio_player.cc:318 187 | 188 | _ZN5media11AudioPlayer17AudioDataCallbackEPPhPiPv 189 | /Users/jeffli/sources/JeffAudioPlayer/audiolibrary/src/main/cpp/audio_player.cc:299 190 | 191 | _ZN5media13AudioRenderer13ProducePacketEv 192 | /Users/jeffli/sources/JeffAudioPlayer/audiolibrary/src/main/cpp/opensl/audio_renderer.cc:214 193 | 194 | _ZN5media13AudioRenderer19AudioPlayerCallbackEPKPK30SLAndroidSimpleBufferQueueItf_Pv 195 | /Users/jeffli/sources/JeffAudioPlayer/audiolibrary/src/main/cpp/opensl/audio_renderer.cc:207 196 | 197 | ``` 198 | ![内存问题图1](./audioplayer/audioplayer_mem_1.png)<br> 199 | 上图中红框的位置,audio_buffer应该被释放过,但是还想给audio_buffer_赋值,但是我们也不知道在什么地方被释放的,所以先把释放的地方解栈一下: 200 | ``` 201 | #1 0x70654a6d2c (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x1ed2c) 202 | #2 0x70654a6af0 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x1eaf0) 203 | #3 0x706549bf4c (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x13f4c) 204 | #4 0x706549bcd4 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x13cd4) 205 | #5 0x70654994f0 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x114f0) 206 | #6 0x70654a9cbc (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x21cbc) 207 | #7 0x70654a91d4 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x211d4) 208 | 209 | ``` 210 | 211 | ``` 212 | ~AvBuffer 213 | /Users/jeffli/sources/JeffAudioPlayer/audiolibrary/src/main/cpp/queue/buffer_queue.h:40 214 | 215 | _ZN5media12AudioDecoder12GetFrameDataEPPhPiPl 216 | /Users/jeffli/sources/JeffAudioPlayer/audiolibrary/src/main/cpp/decode/audio_decoder.cc:302 217 | 218 | _ZN5media11AudioPlayer18ProcessNormalAudioEv 219 | /Users/jeffli/sources/JeffAudioPlayer/audiolibrary/src/main/cpp/audio_player.cc:329 220 | 221 | _ZN5media11AudioPlayer17GetAudioFrameDataEv 222 | /Users/jeffli/sources/JeffAudioPlayer/audiolibrary/src/main/cpp/audio_player.cc:318 223 | 224 | _ZN5media11AudioPlayer17AudioDataCallbackEPPhPiPv 225 | /Users/jeffli/sources/JeffAudioPlayer/audiolibrary/src/main/cpp/audio_player.cc:299 226 | 227 | _ZN5media13AudioRenderer13ProducePacketEv 228 | /Users/jeffli/sources/JeffAudioPlayer/audiolibrary/src/main/cpp/opensl/audio_renderer.cc:214 229 | 230 | _ZN5media13AudioRenderer19AudioPlayerCallbackEPKPK30SLAndroidSimpleBufferQueueItf_Pv 231 | /Users/jeffli/sources/JeffAudioPlayer/audiolibrary/src/main/cpp/opensl/audio_renderer.cc:207 232 | 233 | ``` 234 | 235 | ![内存问题图2](./audioplayer/audioplayer_mem_2.png)<br> 236 | 237 | ![内存问题图3](./audioplayer/audioplayer_mem_3.png)<br> 238 | 239 | 我们在这个地方释放了这个内存,我们犯了一个错误,就是分配了一块内存地址,即使经过了赋值,如果没有使用完,是不能释放的,有点小心过头了。如果没有Asan帮我们分析这个过程,靠一步步排查,那要付出很大的成本,这个过程耗费的时间太长了。<br> 240 | 所以还是工欲善其事必先利其器,希望大家在开发native项目的时候可以多多使用Asan工具,帮助大家节省大量的时间。 -------------------------------------------------------------------------------- /音视频平凡之路/如何编译FFmpeg库.md: -------------------------------------------------------------------------------- 1 | # 如何编译FFmpeg库 2 | FFmpeg是一个开源的多媒体框架,音视频开发中最常见的库就是FFmpeg,学会裁剪和编译FFmpeg库是音视频工程师的入门课程,本文主要介绍怎么编译和裁剪FFmpeg,希望大家看完本文之后,能够掌握FFmpeg的编译原理。<br> 3 | 本文会从几个角度聊聊FFmpeg的相关知识: 4 | - FFmpeg代码架构 5 | - FFmpeg中关键数据结构 6 | - FFmpeg编译选项 7 | - PC上编译以及运行FFmpeg 8 | - 交叉编译FFmpeg 9 | - FFmpeg链接openssl/libx264/fdk-aac库 10 | 11 | ## 1.FFmpeg代码架构 12 | 第一步大家要下载FFmpeg源码,下载地址是:https://github.com/FFmpeg/FFmpeg <br> 13 | 下载完成后,发现FFmpeg源码目录下有一系列的libavXXX的模块,通过这些模块的命名,可以很好区分各个模块的分工以及主要的功能: 14 | - libavformat,format,用于格式封装和解封装,场景的parser功能就在这个模块 15 | - libavcodec,codec,音视频的编码和解码模块,也可以支持外接其他的编码和解码接口 16 | - libavutil,util,通用音视频工具,像素计算、IO、时间等工具 17 | - libavfilter,filter,过滤器,可以用作音视频特效处理 18 | - libavdevice,device,设备(摄像头、拾音器) 19 | - libswscale,scale,视频图像缩放,像素格式互换 20 | - libavresample,resample,重采样,这个模块现在已经废弃了 21 | - libswresample,也是重采样,类似图像缩放 22 | - libpostproc,后期处理模块 23 | 24 | 其中核心的四个模块是libavformat、libavcodec、libavfilter、libavutil 25 | ### 1.1 libavformat模块 26 | libavformat中主要功能有三个: 27 | - 协议解析:解析file/http/https/tcp等协议 28 | - 解封装:对mp4/mkv/avi/mp3/aac等音视频封装协议进行解析 29 | - 封装:将数据封装成mp4/mkv/avi/mp3/aac等文件,是解封装的逆过程 30 | 31 | libavformat中协议协议的索引文件是protocols.c,其中可以看到很多具体协议的的tag-name: 32 | ``` 33 | extern const URLProtocol ff_file_protocol; 34 | extern const URLProtocol ff_ftp_protocol; 35 | extern const URLProtocol ff_gopher_protocol; 36 | extern const URLProtocol ff_hls_protocol; 37 | extern const URLProtocol ff_http_protocol; 38 | extern const URLProtocol ff_httpproxy_protocol; 39 | extern const URLProtocol ff_https_protocol; 40 | extern const URLProtocol ff_icecast_protocol; 41 | extern const URLProtocol ff_mmsh_protocol; 42 | extern const URLProtocol ff_mmst_protocol; 43 | extern const URLProtocol ff_md5_protocol; 44 | extern const URLProtocol ff_pipe_protocol; 45 | extern const URLProtocol ff_prompeg_protocol; 46 | extern const URLProtocol ff_rtmp_protocol; 47 | extern const URLProtocol ff_tee_protocol; 48 | extern const URLProtocol ff_tcp_protocol; 49 | ...... 50 | 51 | ``` 52 | 我们要分析某一个协议的解析代码时,可以根据这个协议的tag-name找到对应的文件,例如ff_http_protocol对应的具体文件是http.c,可以在其中找到ff_http_protocol的定义: 53 | ``` 54 | const URLProtocol ff_http_protocol = { 55 | .name = "http", 56 | .url_open2 = http_open, 57 | .url_accept = http_accept, 58 | .url_handshake = http_handshake, 59 | .url_read = http_read, 60 | .url_write = http_write, 61 | .url_seek = http_seek, 62 | .url_close = http_close, 63 | .url_get_file_handle = http_get_file_handle, 64 | .url_get_short_seek = http_get_short_seek, 65 | .url_shutdown = http_shutdown, 66 | .priv_data_size = sizeof(HTTPContext), 67 | .priv_data_class = &http_context_class, 68 | .flags = URL_PROTOCOL_FLAG_NETWORK, 69 | .default_whitelist = "http,https,tls,rtp,tcp,udp,crypto,httpproxy" 70 | }; 71 | 72 | ``` 73 | 每一个链接的指针函数都指向一个新的目标执行方法,这是FFmpeg中的特色用法。如果我们做优化需要了解并掌握底层的网络请求代码,这样可以更好地提升我们程序的执行效率。大家在平时工作中也能明白一个道理:就是把一件事情做出来不是很难,但是做好或者做全面就比较难了,那是因为我们对原理的掌握还不够深入,导致一些点没有吃透,没有吃透的前提下,更好地优化就无从谈起。<br><br> 74 | 75 | libavformat中的解封装和封装模块是注册在同一个索引文件allformats.c: 76 | ``` 77 | /* (de)muxers */ 78 | extern AVInputFormat ff_matroska_demuxer; 79 | extern AVOutputFormat ff_matroska_muxer; 80 | extern AVOutputFormat ff_matroska_audio_muxer; 81 | extern AVInputFormat ff_mgsts_demuxer; 82 | extern AVInputFormat ff_microdvd_demuxer; 83 | extern AVOutputFormat ff_microdvd_muxer; 84 | extern AVInputFormat ff_mjpeg_demuxer; 85 | extern AVOutputFormat ff_mjpeg_muxer; 86 | extern AVInputFormat ff_mjpeg_2000_demuxer; 87 | extern AVInputFormat ff_mlp_demuxer; 88 | extern AVOutputFormat ff_mlp_muxer; 89 | extern AVInputFormat ff_mlv_demuxer; 90 | extern AVInputFormat ff_mm_demuxer; 91 | extern AVInputFormat ff_mmf_demuxer; 92 | extern AVOutputFormat ff_mmf_muxer; 93 | extern AVInputFormat ff_mov_demuxer; 94 | extern AVOutputFormat ff_mov_muxer; 95 | ...... 96 | 97 | ``` 98 | 这里面可以看出FFmpeg支持的解封装和封装协议真的非常多,几乎你能想到的所有协议这儿都能支持,我们平时场景的mp4协议封装和解封转可以简单谈谈,mp4也成为mov,所有mp4的解封装是ff_mov_demuxer,在mov.c文件中;mp4的封装是ff_mov_muxer,在movenc.c文件中。 99 | ``` 100 | mov.c 101 | 102 | AVInputFormat ff_mov_demuxer = { 103 | .name = "mov,mp4,m4a,3gp,3g2,mj2", 104 | .long_name = NULL_IF_CONFIG_SMALL("QuickTime / MOV"), 105 | .priv_class = &mov_class, 106 | .priv_data_size = sizeof(MOVContext), 107 | .extensions = "mov,mp4,m4a,3gp,3g2,mj2", 108 | .read_probe = mov_probe, 109 | .read_header = mov_read_header, 110 | .read_packet = mov_read_packet, 111 | .read_close = mov_read_close, 112 | .read_seek = mov_read_seek, 113 | .flags = AVFMT_NO_BYTE_SEEK, 114 | }; 115 | 116 | ``` 117 | 解封装的操作主要是: 118 | - 探测:探测当前是否符合封装协议的要求 119 | - 读取头部:安装封装格式的要求读取头部数据,mp4的头部就是moov信息 120 | - 读取body:安装moov信息读取对应的mdat数据 121 | - seek:拖动进度条的情况下如何快速seek数据 122 | 123 | ``` 124 | movenc.c 125 | 126 | AVOutputFormat ff_mov_muxer = { 127 | .name = "mov", 128 | .long_name = NULL_IF_CONFIG_SMALL("QuickTime / MOV"), 129 | .extensions = "mov", 130 | .priv_data_size = sizeof(MOVMuxContext), 131 | .audio_codec = AV_CODEC_ID_AAC, 132 | .video_codec = CONFIG_LIBX264_ENCODER ? 133 | AV_CODEC_ID_H264 : AV_CODEC_ID_MPEG4, 134 | .init = mov_init, 135 | .write_header = mov_write_header, 136 | .write_packet = mov_write_packet, 137 | .write_trailer = mov_write_trailer, 138 | .deinit = mov_free, 139 | .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE, 140 | .codec_tag = (const AVCodecTag* const []){ 141 | ff_codec_movvideo_tags, ff_codec_movaudio_tags, 0 142 | }, 143 | .check_bitstream = mov_check_bitstream, 144 | .priv_class = &mov_muxer_class, 145 | }; 146 | 147 | ``` 148 | 封装是解封装的逆过程,我们在开发音视频SDK的过程中经常会用到音视频的封装,这儿大家了解即可,后续还要展开分析。 149 | 150 | ### 1.2 libavcodec模块 151 | libavcodec的主要功能也有3个: 152 | - 比特流过滤器 : 解封装之后的原始数据过滤器 153 | - 解码 : 音视频解码流程,将h264/hevc/aac等音视频解码成原始数据的过程 154 | - 编码 : 解码的逆过程,将原始数据编码为h264/hevc/aac等过程 155 | 156 | 比特率过滤器是解码之前的重要步骤,我们解封装之后得到的数据离解码还有一小步,例如H264解析处最终的解码单元之前,还要读取SPS和PPS数据,这个比特率过滤器就是这样的一个过程,我们可以在bitstream_filters.c中看到每一个比特率过滤器的tag-name,然后再根据这个tag-name追踪到具体的执行文件: 157 | ``` 158 | extern const AVBitStreamFilter ff_aac_adtstoasc_bsf; 159 | extern const AVBitStreamFilter ff_chomp_bsf; 160 | extern const AVBitStreamFilter ff_dump_extradata_bsf; 161 | extern const AVBitStreamFilter ff_dca_core_bsf; 162 | extern const AVBitStreamFilter ff_eac3_core_bsf; 163 | extern const AVBitStreamFilter ff_extract_extradata_bsf; 164 | extern const AVBitStreamFilter ff_filter_units_bsf; 165 | extern const AVBitStreamFilter ff_h264_metadata_bsf; 166 | extern const AVBitStreamFilter ff_h264_mp4toannexb_bsf; 167 | extern const AVBitStreamFilter ff_h264_redundant_pps_bsf; 168 | extern const AVBitStreamFilter ff_hapqa_extract_bsf; 169 | extern const AVBitStreamFilter ff_hevc_metadata_bsf; 170 | extern const AVBitStreamFilter ff_hevc_mp4toannexb_bsf; 171 | extern const AVBitStreamFilter ff_imx_dump_header_bsf; 172 | extern const AVBitStreamFilter ff_mjpeg2jpeg_bsf; 173 | extern const AVBitStreamFilter ff_mjpega_dump_header_bsf; 174 | extern const AVBitStreamFilter ff_mp3_header_decompress_bsf; 175 | extern const AVBitStreamFilter ff_mpeg2_metadata_bsf; 176 | extern const AVBitStreamFilter ff_mpeg4_unpack_bframes_bsf; 177 | extern const AVBitStreamFilter ff_mov2textsub_bsf; 178 | extern const AVBitStreamFilter ff_noise_bsf; 179 | extern const AVBitStreamFilter ff_null_bsf; 180 | extern const AVBitStreamFilter ff_remove_extradata_bsf; 181 | extern const AVBitStreamFilter ff_text2movsub_bsf; 182 | extern const AVBitStreamFilter ff_trace_headers_bsf; 183 | extern const AVBitStreamFilter ff_vp9_raw_reorder_bsf; 184 | extern const AVBitStreamFilter ff_vp9_superframe_bsf; 185 | extern const AVBitStreamFilter ff_vp9_superframe_split_bsf; 186 | 187 | ``` 188 | 以我们熟知的ff_h264_metadata_bsf和ff_h264_mp4toannexb_bsf为例,ff_h264_mp4toannexb_bsf在h264_mp4toannexb_bsf.c中,ff_h264_metadata_bsf在h264_metadata_bsf.c中。 189 | ``` 190 | const AVBitStreamFilter ff_h264_mp4toannexb_bsf = { 191 | .name = "h264_mp4toannexb", 192 | .priv_data_size = sizeof(H264BSFContext), 193 | .init = h264_mp4toannexb_init, 194 | .filter = h264_mp4toannexb_filter, 195 | .codec_ids = codec_ids, 196 | }; 197 | 198 | 199 | const AVBitStreamFilter ff_h264_metadata_bsf = { 200 | .name = "h264_metadata", 201 | .priv_data_size = sizeof(H264MetadataContext), 202 | .priv_class = &h264_metadata_class, 203 | .init = &h264_metadata_init, 204 | .close = &h264_metadata_close, 205 | .filter = &h264_metadata_filter, 206 | .codec_ids = h264_metadata_codec_ids, 207 | }; 208 | ``` 209 | h264_mp4toannexb_filter中是解析SPS和PPS的过程,同样在音视频编辑SDK中会用到这部分的知识点。大家有想法的可以深入分析一下,在音视频SDK中会用到这部分的知识。<br><br> 210 | 211 | libavcodec中的解码和编码模块是非常核心的模块,其他的基本上围绕这两个模块开展,想了解编解码协议的同学,这一块可以下功夫去学习一下。目前编解码实在太多了,有上百种之多,可以在allcodecs.c中查找目前支持的所有codec: 212 | ``` 213 | extern AVCodec ff_bink_decoder; 214 | extern AVCodec ff_h261_encoder; 215 | extern AVCodec ff_h261_decoder; 216 | extern AVCodec ff_h263_encoder; 217 | extern AVCodec ff_h263_decoder; 218 | extern AVCodec ff_h263i_decoder; 219 | extern AVCodec ff_h263p_encoder; 220 | extern AVCodec ff_h263p_decoder; 221 | extern AVCodec ff_h263_v4l2m2m_decoder; 222 | extern AVCodec ff_h264_decoder; 223 | extern AVCodec ff_h264_crystalhd_decoder; 224 | extern AVCodec ff_h264_v4l2m2m_decoder; 225 | extern AVCodec ff_h264_mediacodec_decoder; 226 | extern AVCodec ff_h264_mmal_decoder; 227 | extern AVCodec ff_h264_qsv_decoder; 228 | extern AVCodec ff_h264_rkmpp_decoder; 229 | ...... 230 | 231 | ``` 232 | 对一个音视频工程师而言,要了解常见的音视频的编解码知识,从了解音视频编码的基础知识开始,例如H264的编码过程,H264是如何提升压缩效率的?当然这一张如何展开讲太多了,不过我会在其他章节重点分析一下场景的编码的H264/HEVC等等。 233 | 234 | ### 1.3 libavfilter模块 235 | libavfilter是处理数据的过滤器,不仅仅是我们理解的滤镜操作,甚至声音也有filter操作,混音、倍速都需要特定的filter处理,所有的filter都挂在allfilters.c文件中: 236 | ``` 237 | extern AVFilter ff_af_abench; 238 | extern AVFilter ff_af_acompressor; 239 | extern AVFilter ff_af_acontrast; 240 | extern AVFilter ff_af_acopy; 241 | extern AVFilter ff_af_acrossfade; 242 | extern AVFilter ff_af_acrusher; 243 | extern AVFilter ff_af_adelay; 244 | extern AVFilter ff_af_aecho; 245 | extern AVFilter ff_af_aemphasis; 246 | extern AVFilter ff_af_aeval; 247 | extern AVFilter ff_af_afade; 248 | extern AVFilter ff_af_afftfilt; 249 | extern AVFilter ff_af_afir; 250 | extern AVFilter ff_af_aformat; 251 | extern AVFilter ff_af_agate; 252 | extern AVFilter ff_af_aiir; 253 | extern AVFilter ff_af_ainterleave; 254 | extern AVFilter ff_af_alimiter; 255 | extern AVFilter ff_af_allpass; 256 | ...... 257 | 258 | ``` 259 | FFmpeg能够实现的非常丰富的filter操作,从加水印、去水印、加滤镜、加字幕、改变声音、混音等操作,从改变声音、图片、字幕等各个数据源的角度改变原始数据。 260 | 举个例子,ff_vf_delogo就是去水印的filter,可以看看具体的一些操作,在vf_delogo.c文件中: 261 | ``` 262 | AVFilter ff_vf_delogo = { 263 | .name = "delogo", 264 | .description = NULL_IF_CONFIG_SMALL("Remove logo from input video."), 265 | .priv_size = sizeof(DelogoContext), 266 | .priv_class = &delogo_class, 267 | .init = init, 268 | .query_formats = query_formats, 269 | .inputs = avfilter_vf_delogo_inputs, 270 | .outputs = avfilter_vf_delogo_outputs, 271 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, 272 | }; 273 | 274 | ``` 275 | 需要开发者指定祛除水印的坐标和大小,计算出当前位置的水印数据,而祛除数据。不过祛除完成后还是能看到一些马赛克。<br><br> 276 | 277 | ff_vf_amix就是混音操作,在af_amix.c文件中: 278 | ``` 279 | AVFilter ff_af_amix = { 280 | .name = "amix", 281 | .description = NULL_IF_CONFIG_SMALL("Audio mixing."), 282 | .priv_size = sizeof(MixContext), 283 | .priv_class = &amix_class, 284 | .init = init, 285 | .uninit = uninit, 286 | .activate = activate, 287 | .query_formats = query_formats, 288 | .inputs = NULL, 289 | .outputs = avfilter_af_amix_outputs, 290 | .flags = AVFILTER_FLAG_DYNAMIC_INPUTS, 291 | }; 292 | 293 | ``` 294 | 将多路音频流混合成一路流,这在音视频应用中还是非常常见的。混音操作也会是音视频SDK的重点。 295 | 296 | ### 1.4 libavutil模块 297 | libavutil是FFmpeg中一个通用方法管理模块,例如日志模块、时间处理模块、狂平台兼容模块等等。大家在FFmpeg开发中会用到很多相关的API。就拿我们常见的日志模块来讲,FFmpeg提供了独有的日志模块,av_log模块,在log.h文件中。我们在调用FFmpeg库的时候想打开FFmpeg中日志怎么做到了? 298 | - 设置log level : 什么级别的日志才能输出 299 | - 设置log回调处理 : log以什么形式输出 300 | 301 | 具体的执行代码如下: 302 | ``` 303 | av_log_set_level(AV_LOG_VERBOSE); 304 | av_log_set_callback(log_callback); 305 | 306 | ``` 307 | 308 | ``` 309 | void log_callback(void *ptr, int level, const char *fmt, va_list vl) { 310 | //ffmpeg中level越大打样的log优先级越低 311 | if (level > av_log_get_level()) 312 | return; 313 | TLogLevel android_level = kLevelAll; 314 | if (level > AV_LOG_DEBUG) { 315 | android_level = kLevelDebug; 316 | } else if (level > AV_LOG_VERBOSE) { 317 | android_level = kLevelVerbose; 318 | } else if (level > AV_LOG_INFO) { 319 | android_level = kLevelInfo; 320 | } else if (level > AV_LOG_WARNING) { 321 | android_level = kLevelWarn; 322 | } else if (level > AV_LOG_ERROR) { 323 | android_level = kLevelError; 324 | } else if (level > AV_LOG_FATAL) { 325 | android_level = kLevelFatal; 326 | } 327 | __LOGV__(android_level, FFMPEG_LOG_TAG, fmt, vl); 328 | } 329 | 330 | ``` 331 | 我们在Android平台上,可以将其转嫁到__android_log_print输出,在其他平台上也可以用其他的形式输出,最终的目的是方便我们查找问题。 332 | 333 | ## 2.FFmpeg中关键数据结构 334 | 开始学习FFmpeg源码,需要搞清楚有其内部有几个关键的数据结构,掌握这些数据结构对进一步学习有非常大的帮助: 335 | - AVFormatContext 336 | - AVIOContext 337 | - AVInputFormat 338 | - AVOutputFormat 339 | - AVStream 340 | - AVPacket和AVFrame 341 | 342 | ### 2.1 AVFormatContext 343 | AVFormatContext是打开文件必需的一个结构体,Format是音视频的一个核心概念,操作一个音视频文件,第一步就是将其读取打开转化为AVFormatContext结构体,其中音视频的关键信息已经被读到了AVFormatContext结构体中。AVFormatContext将音视频的信息解析出来,它不会直接处理,交给下面的子结构去处理,它负责全局调度。<br> 344 | 它都有哪些子结构呢? 345 | ``` 346 | /// 解封装专用 347 | struct AVInputFormat *iformat; 348 | 349 | /// 封装专用 350 | struct AVOutputFormat *oformat; 351 | 352 | /// 解封装中需要先打开文件,封装过程中需要写数据到文件,主要控制IO操作 353 | AVIOContext *pb; 354 | 355 | /// 文件流个数 356 | unsigned int nb_streams; 357 | 358 | /// 文件流具体的指针 359 | AVStream **streams; 360 | 361 | /// 文件时长 362 | int64_t duration; 363 | 364 | /// 视频编解码的id 365 | enum AVCodecID video_codec_id; 366 | 367 | /// 视频编解码的实例 368 | AVCodec *video_codec; 369 | 370 | /// 音频编解码的id 371 | enum AVCodecID audio_codec_id; 372 | 373 | /// 音频编解码的实例 374 | AVCodec *audio_codec; 375 | 376 | /// 字幕编解码的id 377 | enum AVCodecID subtitle_codec_id; 378 | 379 | /// 字幕编解码的实例 380 | AVCodec *subtitle_codec; 381 | 382 | /// 音视频的具体信息都存在metadata结构中,是一个key-value的结构,可以通过av_dump输出 383 | AVDictionary *metadata; 384 | 385 | ``` 386 | ### 2.2 AVIOContext 387 | AVIOContext结构体是音视频IO操作的时候用到的主要数据结构,例如我们需要输出一个mp4文件,首先肯定要打开这个文件,然后按照特定排列写入数据,这里面的主要是一些IO的执行方法: 388 | ``` 389 | /// 读数据包方法 390 | int (*read_packet)(void *opaque, uint8_t *buf, int buf_size); 391 | 392 | /// 写数据包方法 393 | int (*write_packet)(void *opaque, uint8_t *buf, int buf_size); 394 | 395 | /// seek情况下写操作 396 | int64_t (*seek)(void *opaque, int64_t offset, int whence); 397 | 398 | /// seek情况下读操作 399 | int64_t (*read_seek)(void *opaque, int stream_index, int64_t timestamp, int flags); 400 | ``` 401 | 402 | ### 2.3 AVInputFormat 403 | AVInputFormat用于解封装操作,其核心执行方法都是读操作,可以看下其结构体中指向方法: 404 | ``` 405 | /// 探测封装信息,决定操作哪种解封装格式 406 | int (*read_probe)(AVProbeData *); 407 | 408 | /// 读取数据的头部信息 409 | int (*read_header)(struct AVFormatContext *); 410 | 411 | /// 读取数据的body信息 412 | int (*read_packet)(struct AVFormatContext *, AVPacket *pkt); 413 | 414 | /// 关闭读取操作 415 | int (*read_close)(struct AVFormatContext *); 416 | 417 | /// seek情况下读取操作 418 | int (*read_seek)(struct AVFormatContext *, 419 | int stream_index, int64_t timestamp, int flags); 420 | 421 | /// 读取具体时间点的操作 422 | int64_t (*read_timestamp)(struct AVFormatContext *s, int stream_index, 423 | int64_t *pos, int64_t pos_limit); 424 | 425 | /// 读取RTSP协议信息 426 | int (*read_play)(struct AVFormatContext *); 427 | 428 | /// 暂停读取RTSP信息 429 | int (*read_pause)(struct AVFormatContext *); 430 | 431 | /// 读取TS信息 432 | int (*read_seek2)(struct AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags); 433 | 434 | ``` 435 | ### 2.4 AVOutputFormat 436 | AVOutputFormat是封装的过程,是AVInputFormat逆结构,都是写操作: 437 | ``` 438 | /// 写头部信息 439 | int (*write_header)(struct AVFormatContext *); 440 | 441 | /// 写数据body 442 | int (*write_packet)(struct AVFormatContext *, AVPacket *pkt); 443 | 444 | /// 写尾部信息 445 | int (*write_trailer)(struct AVFormatContext *); 446 | 447 | ``` 448 | ### 2.5 AVStream 449 | 解封装出来的具体流信息,有音频流、视频流、字幕流等等。AVFormatContext是整个文件的信息,那么AVStream就是具体流的信息。 450 | ``` 451 | /// 编解码的上下文 452 | AVCodecContext *codec; 453 | 454 | /// 时间基,单位时间内有多少的传输单元存在 455 | AVRational time_base; 456 | 457 | /// 时长 458 | int64_t duration; 459 | 460 | /// 帧数 461 | int64_t nb_frames; 462 | 463 | /// 编解码参数,里面包含编解码的基本信息,这是非常重要编解码信息key-value 464 | AVCodecParameters *codecpar; 465 | 466 | ``` 467 | 468 | 其中AVCodecParameters的结构如下: 469 | ``` 470 | /// 类型,可以视频音频、视频、字幕等等 471 | enum AVMediaType codec_type; 472 | 473 | /// 编解码id 474 | enum AVCodecID codec_id; 475 | 476 | /// format id,如果是图片,参考AVPixelFormat枚举;如果是音频,参考AVSampleFormat枚举 477 | int format; 478 | 479 | /// 比特率 480 | int64_t bit_rate; 481 | 482 | /// 宽和高 483 | int width; 484 | int height; 485 | 486 | /// channel 布局 487 | uint64_t channel_layout; 488 | 489 | /// 声道个数 490 | int channels; 491 | 492 | /// 采样率 493 | int sample_rate; 494 | 495 | /// 声音帧大小,aac是1024,mp3是1152 496 | int frame_size; 497 | ``` 498 | 特别要强调的一点是frame_size中aac的frame_size是1024,mp3的frame_size是1152,为什么特别强调一下,因为之前在做音视频SDK的时候遇到一个坑就是这个引起的,之前没有注意,后面谈到音视频SDK的时候会详细分析一下。 499 | ### 2.6 AVPacket和AVFrame 500 | AVPacket是未解码之前的音视频原始数据,将文件解封装之后,可以通过av_read_frame来得到对应的AVPacket,但是AVPacket还没有经过解码,经过解码之后的数据就是AVFrame。 501 | ``` 502 | typedef struct AVPacket { 503 | AVBufferRef *buf; 504 | int64_t pts; 505 | int64_t dts; 506 | uint8_t *data; 507 | int size; 508 | int stream_index; 509 | int flags; 510 | AVPacketSideData *side_data; 511 | int side_data_elems; 512 | int64_t duration; 513 | int64_t pos; ///< byte position in stream, -1 if unknown 514 | 515 | } AVPacket; 516 | 517 | ``` 518 | 主要是av_read_frame函数让大家误解,还以为AVPacket是帧数据,AVPacket是包数据,AVFrame才是真正的帧数据。<br><br> 519 | 520 | 521 | 操作文件的完整流程: 522 | - avformat_alloc_context() 创建输入媒体文件的AVFormatContext 523 | - avformat_alloc_output_context2() 创建输出媒体文件的AVFormatContext 524 | - av_dump_format() 打印format详情 525 | - avformat_open_input() 打开媒体文件,探知媒体文件的封装格式 526 | - avformat_close_input() 关闭媒体文件 527 | - avformat_find_stream_info() 探知媒体文件中的流信息,几条流,每条流的基本信息 528 | - av_read_frame() 读取媒体文件中每一个数据包,这是未解码之前的包 529 | - avformat_write_header() 写入输出文件的媒体头部信息 530 | - av_interleaved_write_frame() 写入输出文件的帧信息,此帧信息已经调整了帧与帧之间的关联了 531 | - av_write_uncoded_frame() 写入输出文件的未编码的帧信息 532 | - av_write_frame() 写入输出文件的已编码的帧信息 533 | - av_write_trailer() 写入输出文件的媒体尾部信息 534 | 535 | 536 | ## 3.FFmpeg编译选项 537 | FFmpeg的编译裁剪是非常重要的,它要求我们必须知道应该支持什么特性,不支持什么特性,那首先需要搞清楚,FFmpeg可以支持什么特性,查询FFmpeg是否支持这个特性是很重要的。下载了FFmpeg源码之后,在源码目录下有一个configure文件,这个文件非常庞大,里面包含了FFmpeg所有特性的配置选项。<br> 538 | - ./configure --help 539 | ``` 540 | --help print this message 541 | --quiet Suppress showing informative output 542 | --list-decoders show all available decoders 543 | --list-encoders show all available encoders 544 | --list-hwaccels show all available hardware accelerators 545 | --list-demuxers show all available demuxers 546 | --list-muxers show all available muxers 547 | --list-parsers show all available parsers 548 | --list-protocols show all available protocols 549 | --list-bsfs show all available bitstream filters 550 | --list-indevs show all available input devices 551 | --list-outdevs show all available output devices 552 | --list-filters show all available filters 553 | ``` 554 | 555 | - 支持哪些解码选项 ./configure --list-decoders 556 | 想支持这个解码,需要使用--enable-decoder=XXX;<br> 557 | 不想支持这个解码,需要使用--disable-decoder=XXX<br> 558 | - 支持哪些编码选项 ./configure --list-encoders 559 | 想支持这个编码,使用--enable-encoder=XXX;<br> 560 | 不想支持这个编码,使用--disable-encoder=XXX<br> 561 | - 支持哪些硬件编解码选项 ./configure --list-hwaccels 562 | ``` 563 | h263_vaapi hevc_dxva2 mpeg2_d3d11va mpeg4_videotoolbox vp9_dxva2 564 | h263_videotoolbox hevc_nvdec mpeg2_d3d11va2 vc1_d3d11va vp9_nvdec 565 | h264_d3d11va hevc_vaapi mpeg2_dxva2 vc1_d3d11va2 vp9_vaapi 566 | h264_d3d11va2 hevc_vdpau mpeg2_nvdec vc1_dxva2 wmv3_d3d11va 567 | h264_dxva2 hevc_videotoolbox mpeg2_vaapi vc1_nvdec wmv3_d3d11va2 568 | h264_nvdec mjpeg_nvdec mpeg2_vdpau vc1_vaapi wmv3_dxva2 569 | h264_vaapi mjpeg_vaapi mpeg2_videotoolbox vc1_vdpau wmv3_nvdec 570 | h264_vdpau mpeg1_nvdec mpeg2_xvmc vp8_nvdec wmv3_vaapi 571 | h264_videotoolbox mpeg1_vdpau mpeg4_nvdec vp8_vaapi wmv3_vdpau 572 | hevc_d3d11va mpeg1_videotoolbox mpeg4_vaapi vp9_d3d11va 573 | hevc_d3d11va2 mpeg1_xvmc mpeg4_vdpau vp9_d3d11va2 574 | 575 | ``` 576 | 上面列出的都是可以外接的硬件编解码模块。 577 | 578 | - 支持哪些解封装选项 ./configure --list-demuxers 579 | 想支持这个解封装,使用--enable-demuxer=XXX;<br> 580 | 不想支持这个解封装,使用--disable-demuxer=XXX<br> 581 | - 支持哪些封装选项 ./configure --list-muxers 582 | 想支持这个封装,使用--enable-muxer=XXX;<br> 583 | 不想支持这个封装,使用--disable-muxer=XXX<br> 584 | - 支持哪些parser选项 ./configure --list-parsers 585 | ``` 586 | aac dnxhd h261 opus vorbis 587 | aac_latm dpx h263 png vp3 588 | ac3 dvaudio h264 pnm vp8 589 | adx dvbsub hevc rv30 vp9 590 | bmp dvd_nav mjpeg rv40 xma 591 | cavsvideo dvdsub mlp sbc 592 | cook flac mpeg4video sipr 593 | dca g729 mpegaudio tak 594 | dirac gsm mpegvideo vc1 595 | 596 | ``` 597 | 想支持这个parser选项,使用--enable-parser=XXX;<br> 598 | 不想支持这个parser选项,使用--disable-parser=XXX<br> 599 | - 支持哪些协议 ./configure --list-protocols 600 | ``` 601 | async ftp librtmps pipe sctp 602 | bluray gopher librtmpt prompeg srtp 603 | cache hls librtmpte rtmp subfile 604 | concat http libsmbclient rtmpe tcp 605 | crypto httpproxy libsrt rtmps tee 606 | data https libssh rtmpt tls 607 | ffrtmpcrypt icecast md5 rtmpte udp 608 | ffrtmphttp librtmp mmsh rtmpts udplite 609 | file librtmpe mmst rtp unix 610 | ``` 611 | 想支持这个协议,使用--enable-protocol=XXX;<br> 612 | 不想支持这个协议,使用--disable-protocol=XXX<br> 613 | - 支持哪些比特流过滤器 ./configure --list-bsfs 614 | ``` 615 | aac_adtstoasc filter_units hevc_mp4toannexb mpeg2_metadata trace_headers 616 | chomp h264_metadata imx_dump_header mpeg4_unpack_bframes vp9_raw_reorder 617 | dca_core h264_mp4toannexb mjpeg2jpeg noise vp9_superframe 618 | dump_extradata h264_redundant_pps mjpega_dump_header null vp9_superframe_split 619 | eac3_core hapqa_extract mov2textsub remove_extradata 620 | extract_extradata hevc_metadata mp3_header_decompress text2movsub 621 | 622 | ``` 623 | 想支持这个比特流过滤器,使用--enable-bsf=XXX;<br> 624 | 不想支持这个比特流过滤器,使用--disable-bsf=XXX<br> 625 | - 支持哪些可用的输入设备 ./configure --list-indevs 626 | ``` 627 | alsa dshow kmsgrab openal vfwcap 628 | android_camera fbdev lavfi oss xcbgrab 629 | avfoundation gdigrab libcdio pulse 630 | bktr iec61883 libdc1394 sndio 631 | decklink jack libndi_newtek v4l2 632 | 633 | ``` 634 | 想支持这个输入设备,使用--enable-indev=XXX;<br> 635 | 不想支持这个输入设备,使用--disable-indev=XXX<br> 636 | - 支持哪些可用的输出设备 ./configure --list-outdevs 637 | ``` 638 | alsa fbdev oss sndio 639 | caca libndi_newtek pulse v4l2 640 | decklink opengl sdl2 xv 641 | 642 | ``` 643 | 想支持这个输出设备,使用--enable-outdev=XXX;<br> 644 | 不想支持这个输出设备,使用--disable-outdev=XXX<br> 645 | - 支持哪些filter选项 ./configure --list-filters 646 | 想支持这个filter选项,使用--enable-filter=XXX;<br> 647 | 不想支持这个filter选项,使用--disable-filter=XXX<br> 648 | FFmpeg中原生的filter非常多,也提供了扩张框架可以保证接入新的filter,如果你有好的filter,可以接入进来,这是FFmpeg强大的生命力保证。<br><br><br> 649 | 650 | 我们通过打开关闭这些编译选项,可以达到裁剪FFmpeg库的目的,要怎么裁剪,还要你根据实际情况来看,你的应用场景等等。 651 | ## 4.PC上编译以及运行FFmpeg 652 | ## 5.交叉编译FFmpeg 653 | ## 6.FFmpeg链接openssl/libx264/fdk-aac库 -------------------------------------------------------------------------------- /音视频平凡之路/深入浅出OpenGL.md: -------------------------------------------------------------------------------- 1 | # 深入浅出OpenGL 2 | 3 | ## 1.概述 4 | OpenGL是一种跨平台的图形API,用于3D图形处理硬件指定标准的软件接口,OpenGL ES是OpenGL规范的一种形式,用于嵌入式设备,目前Android支持的的OpenGL ES版本如下: 5 | - OpenGL ES 1.0 和 1.1 - 此 API 规范受 Android 1.0 及更高版本的支持。 6 | - OpenGL ES 2.0 - 此 API 规范受 Android 2.2(API 级别 8)及更高版本的支持。 7 | - OpenGL ES 3.0 - 此 API 规范受 Android 4.3(API 级别 18)及更高版本的支持。 8 | - OpenGL ES 3.1 - 此 API 规范受 Android 5.0(API 级别 21)及更高版本的支持。 9 | 10 | 如何选择OpenGL 版本?<br> 11 | OpenGL ES 1.0 API 版本(以及 1.1 扩展)、2.0 版本和 3.0 版本均可提供高性能图形界面,用于创建 3D 游戏、可视化图表和界面。OpenGL ES 2.0 和 3.0 的图形编程基本相似,不同之处在于版本 3.0 表示 2.0 API 与其他功能的超集。OpenGL ES 1.0/1.1 API 的编程明显不同于 OpenGL ES 2.0 和 3.0,因此开发者在开始借助这些 API 进行开发之前应仔细考虑以下因素:<br> 12 | - 性能 : 通常,OpenGL ES 2.0 和 3.0 可提供比 ES 1.0/1.1 API 更快的图形性能。不过,具体的性能差异可能因运行 OpenGL 应用的 Android 设备而异,这是因为硬件制造商对 OpenGL ES 图形管道的实现存在不同。 13 | - 设备兼容性 : 开发者应考虑他们的客户可用的设备类型、Android 版本和 OpenGL ES 版本。如需详细了解各种设备的 OpenGL 兼容性。 14 | - 编码便利性 : OpenGL ES 1.0/1.1 API 提供了一个固定函数管道和多个便捷函数,这些是 OpenGL ES 2.0 或 3.0 API 不具备的。刚接触 OpenGL ES 的开发者可能会发现针对版本 1.0/1.1 进行编码更快且更便捷。 15 | - 图形控制力 : OpenGL ES 2.0 和 3.0 API 通过使用着色程序提供完全可编程的管道,因而能够提供更强的控制力。通过更直接地控制图形处理管道,开发者可以打造使用 1.0/1.1 API 难以生成的效果。 16 | - 纹理支持 : OpenGL ES 3.0 API 能够最好地支持纹理压缩,因为它保证了支持透明度的 ETC2 压缩格式的可用性。1.x 和 2.0 API 实现通常包含对 ETC1 的支持,但这种纹理格式不支持透明度,因此您通常必须采用目标设备支持的其他压缩格式来提供资源。 17 | 18 | <br> 19 | <br> 20 | Android版本中OpenGL特性的支持度?<br> 21 | - OpenGL ES 1.0/1.1 : Android 1.0就支持 22 | - OpenGL ES 2.0 : Android 2.2 (API 8) 开始支持 23 | - OpenGL ES 3.0 : Android 4.3 (API 18) 开始支持 24 | - OpenGL ES 3.1 : Android 5.0 (API 21) 开始支持 25 | 26 | 在Android平台上,OpenGL ES有两套可供选择的API,java层和native层都有对应的API : 27 | - Java层的在android.opengl包下面 28 | - native层的在${NDK-ROOT}/platforms/android-API/arch-arm/usr/include下面,如下图: 29 | ![native调用](./opengl/OpenGL-native-api1.png)<br> 30 | ![native-so调用](./opengl/OpenGL-native-so.png)<br> 31 | 两种API中的用法后面都会有涉及,这人就不做过多讨论了。接下来我们了解一下OpenGL中用到的基本概念。 32 | 33 | ## 2.基本概念 34 | 35 | ### 2.1 坐标系 36 | - OpenGL坐标系 37 | - 纹理坐标系 38 | 39 | ![两种坐标系](./opengl/OpenGL坐标系.png) <br> 40 | 上图可以看出两种坐标系之间是有映射关系的,在开发过程中如果想要保证位置的准确,必须要确保对应的坐标位置和顺序是按照正常的位置排序的。 41 | 纹理坐标比较奇怪,因为纹理坐标是右上角是原点,就是(0, 0)点,X坐标向右是正方向,Y坐标向下是正方向。这点确实和正常的不一样,但是和Android上Bitmap和Canvas的坐标体系是一样的。<br> 42 | 两种坐标系是不同的,所以在绘制纹理的时候,需要做好两个坐标系之间的对应关系。<br> 43 | 有几个注意点 : 44 | - 纹理的每个维度必须是2次幂 45 | - 纹理有最大上限,一般是2048 * 2048 46 | 47 | ### 2.2 着色器 48 | 着色器又称为Shader,是运行在GPU上的小程序,这些小程序为图像渲染管线的某个特定部分而运行,基本而言,着色器只是一种将输入转化为输出的程序,着色器之间沟通必须要要通过输入和输出,不能直接调用函数进行沟通。<br> 49 | GLSL是着色器语言,全程是OpenGL Shader Language,GLSL专门为图形计算量身定制的,包含一些针对向量和矩阵操作的特性。<br> 50 | OpenGL中有两种着色器 : 51 | - 顶点着色器 52 | - 片元着色器 53 | 54 | 下面给出顶点着色器和片元着色器的例子:<br> 55 | 顶点着色器 : 56 | ``` 57 | #version 330 core 58 | layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为0 59 | 60 | out vec4 vertexColor; // 为片段着色器指定一个颜色输出 61 | 62 | void main() 63 | { 64 | gl_Position = vec4(aPos, 1.0); // 注意我们如何把一个vec3作为vec4的构造器的参数 65 | vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // 把输出变量设置为暗红色 66 | } 67 | ``` 68 | 片元着色器 : 69 | ``` 70 | #version 330 core 71 | out vec4 FragColor; 72 | 73 | in vec4 vertexColor; // 从顶点着色器传来的输入变量(名称相同、类型相同) 74 | 75 | void main() 76 | { 77 | FragColor = vertexColor; 78 | } 79 | ``` 80 | 这儿大家先简单了解一下,后续会结合具体的例子分析一下着色器的例子。 81 | 82 | ### 2.3 渲染流程 83 | 一个很简单的例子,我想利用OpenGL生成一个红色的三角形,那我需要3个顶点表示这个三角形的位置,然后连线将三角形的图元转配起来,然后进行光栅化,将三角形分为若干个像素点,最后利用片段着色器开始着色。<br> 84 | ![光栅化流程](./opengl/光栅化流程.png)<br> 85 | - 确定顶点的位置 86 | - 根据OpenGL中的规则,根据这些顶点绘制特定的图形 87 | - 光栅化图元,将图形分为一个个小的图元片段 88 | - 执行片段着色器 89 | - 将上述图形加载到帧缓冲区FBO(Frame Buffer Object),再展示到屏幕上 90 | 91 | <br> 92 | <br> 93 | 94 | 其实上面的过程我们只需要关注两点: 95 | - 顶点着色器 96 | - 片元着色器或者片段着色器 97 | 98 | <br> 99 | <br> 100 | - 顶点着色器: 每个顶点都会执行一次,例如绘制一个三角形,包含3个顶点,执行3次顶点着色器,而我们传递给顶点着色器的数据包含了顶点的位置、颜色。 101 | - 组装图元: 将顶点连成线,根据需求绘制线段、三角形、顶点等图元 102 | - 光栅化图元: 这个非常关键,在光栅化图元的时候,图形切分为若干个小片元,varying数据或者out数据在这个过程中计算生成,记录在每个片段中。 103 | - 片段着色器: 每个片段都计算一次。 104 | <br> 105 | **顶点位置、顶点颜色--->顶点着色器--->光栅化--->计算每个片段的具体颜色值--->片段着色器** 106 | 107 | ### 2.4 GLSL数据类型 108 | GLSL包含C和其他语言默认的基础数据类型: int/float/double/uint/bool等。但是GLSL也额外支持两种容器类型 : 向量(Vector)和矩阵(Matrix)<br> 109 | 向量可以包含1、2、3、4个分量的容器,分量的类型可以是前面提到的默认基础类型的任意一个,形式如下: 110 | |类型|含义| 111 | |:-|:-| 112 | |vecn|包含n个float分量的默认向量| 113 | |bvecn|包含n个bool分量的向量| 114 | |ivecn|包含n个int分量的向量| 115 | |uvecn|包含n个unsigned int分量的向量| 116 | |dvecn|包含n个double分量的向量| 117 | 118 | 大多数场景下使用的是vecn,因为float已经可以满足要求,可以使用vec.x、vec.y、vec.z、vec.w来获取vec的1、2、3、4个分量,例如下面的应用: 119 | ``` 120 | vec2 vect = vec2(0.5, 0.7); 121 | vec4 result = vec4(vect, 0.0, 0.0); 122 | vec4 otherResult = vec4(result.xyz, 1.0); 123 | ``` 124 | result.xyz表示result的前3个分量。 125 | 126 | ### 2.5 GLSL输入与输出 127 | 着色器程序是独立的小程序,上面介绍,其语法类似C语言,主函数入库也是main函数,那肯定也有对应的输入域输出,这样才能进行数据交换。GLSL中定义了in和out来表示输入与输出,还是例举上面的例子:<br> 128 | 顶点着色器: 129 | ``` 130 | #version 330 core 131 | layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为0 132 | 133 | out vec4 vertexColor; // 为片段着色器指定一个颜色输出 134 | 135 | void main() 136 | { 137 | gl_Position = vec4(aPos, 1.0); // 注意我们如何把一个vec3作为vec4的构造器的参数 138 | vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // 把输出变量设置为暗红色 139 | } 140 | 141 | ``` 142 | 片段着色器: 143 | ``` 144 | 145 | #version 330 core 146 | out vec4 FragColor; 147 | 148 | in vec4 vertexColor; // 从顶点着色器传来的输入变量(名称相同、类型相同) 149 | 150 | void main() 151 | { 152 | FragColor = vertexColor; 153 | } 154 | ``` 155 | 从上面看出来,顶点着色器中aPos是输入,vertexColor是输出,片段着色器中FragColor是输出,vertexColor是输入;vertexColor在顶点着色器中是输出,在片段着色器中就是输入,输入输出是相对当前的着色器而言的。<br> 156 | 157 | 这儿我想到了OpenGL ES2.0中的attribute和varying参数,这两个参数已经被废弃了,attribute就是输入,varying就是输出。其实in和out显然比attribute和varying更容易理解。 158 | 159 | ### 2.6 GLSL中的uniform 160 | uniform也是输入的一种,但是它和一般的顶点输入不同,uniform是全局的,全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问,无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。<br> 161 | uniform一般用在光源位置、统一变换矩阵、颜色等变量上。<br> 162 | 更新一下上面片段着色器的程序: 163 | ``` 164 | #version 330 core 165 | out vec4 FragColor; 166 | 167 | uniform vec4 ourColor; // 在OpenGL程序代码中设定这个变量 168 | 169 | void main() 170 | { 171 | FragColor = ourColor; 172 | } 173 | 174 | ``` 175 | 因为顶点着色器中并没有用到这个color变量,所以没有必要在顶点着色器中定义,然后通过顶点着色器传进来,直接在片段着色器中定义就行了。<br><br> 176 | 如何设置uniform的值,OpenGL中提供了几个方法: 177 | |函数|含义| 178 | |:-|:-| 179 | |glUniformf|函数需要一个float作为它的值| 180 | |glUniformi|函数需要一个int作为它的值| 181 | |glUniformui|函数需要一个unsigned int作为它的值| 182 | |glUniform3f|函数需要3个float作为它的值| 183 | |glUniformfv|函数需要一个float向量、数组作为它的值| 184 | 185 | 这些在我们接下来的实践中都会用到的。 186 | 187 | ## 3.创建EGL环境 188 | EGL是OpenGL ES和本地窗口系统的接口,不同平台上的EGL配置是不一样的,但是调用OpenGL的方式是一样的,就是说OpenGL能够跨平台就是依赖EGL接口。<br> 189 | 在Android中使用OpenGL ES,有相当一部分同学是使用GLSurfaceView结合GLSurfaceView.Render实现类来完成的,不需要我们手动创建EGL环境,因为GLSurfaceView中有一个GLThread线程已经帮我们做好了,使用GLSurfaceView的方式可定制化不是特别好,而且跨平台使用的话肯定不能只调用Android相关的API,我们还需要研究一下如何创建EGL环境。<br> 190 | 当我们需要把同一个场景渲染到不同的Surface上时,此时系统的GLSurfaceView就不能满足要求了,所以我们需要自己创建EGL环境来实现渲染的操作。一般较为复杂的OpenGL渲染操作都需要自定义GLSurfaceView来实现渲染功能。<br> 191 | OpenGL整体是一个状态机,通过改变状态就能改变后续的渲染方式,而EGLContext(EGL上下文)就保存所有的状态,因此可以通过共享EGLContext来实现同一场景渲染到不同的Surface上。<br> 192 | 193 | - 创建EGL实例 194 | - 得到默认的显示设备,就是显示窗口 195 | - 初始化默认的显示设备 196 | - 设置显示设备的属性 197 | - 从系统中获取对应属性的配置 198 | - 创建EGLContext实例 199 | - 创建渲染的Surface 200 | - 绑定EGLContext和Surface到显示设备中 201 | - 刷新数据,显示渲染场景 202 | 203 | 204 | 下面是native层创建EGL环境的代码: 205 | ``` 206 | EGLCore::EGLCore() : 207 | egl_display_(EGL_NO_DISPLAY), 208 | egl_config_(nullptr), 209 | egl_context_(EGL_NO_CONTEXT) { 210 | 211 | } 212 | 213 | EGLCore::~EGLCore() = default; 214 | 215 | int EGLCore::Init() { 216 | if ((egl_display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY)) == EGL_NO_DISPLAY) { 217 | LOGE("%s %s eglGetDisplay failed, error=%d", FILE_NAME, __func__ , eglGetError()); 218 | return -1; 219 | } 220 | if (!eglInitialize(egl_display_, 0, 0)) { 221 | LOGE("%s %s eglInitialize failedm error=%d", FILE_NAME, __func__ , eglGetError()); 222 | return -2; 223 | } 224 | 225 | EGLint num_configs; 226 | const EGLint attributes[] = { 227 | EGL_BUFFER_SIZE, 32, 228 | EGL_ALPHA_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, 229 | EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, 230 | EGL_SURFACE_TYPE, EGL_WINDOW_BIT, 231 | EGL_NONE 232 | }; 233 | 234 | if (!eglChooseConfig(egl_display_, attributes, &egl_config_, 1, &num_configs)) { 235 | LOGE("%s %s eglChooseConfig failed, error=%d", FILE_NAME, __func__ , eglGetError()); 236 | Release(); 237 | return -3; 238 | } 239 | 240 | EGLint eglContextAttributes[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; 241 | egl_context_ = eglCreateContext(egl_display_, egl_config_, EGL_NO_CONTEXT, eglContextAttributes); 242 | if (egl_context_ == nullptr) { 243 | LOGE("%s %s eglCreateContext failed, error=%d", FILE_NAME, __func__ , eglGetError()); 244 | Release(); 245 | return -4; 246 | } 247 | return 0; 248 | } 249 | 250 | EGLSurface EGLCore::CreateWindowSurface(ANativeWindow *window) { 251 | EGLSurface surface = EGL_NO_SURFACE; 252 | EGLint format; 253 | if (window == nullptr) { 254 | LOGE("%s %s window is null", FILE_NAME, __func__ ); 255 | return surface; 256 | } 257 | 258 | if (!eglGetConfigAttrib(egl_display_, egl_config_, EGL_NATIVE_VISUAL_ID, &format)) { 259 | LOGE("%s %s eglGetConfigAttrib failed, error=%d", FILE_NAME, __func__ , eglGetError()); 260 | Release(); 261 | return surface; 262 | } 263 | ANativeWindow_setBuffersGeometry(window, 0, 0, format); 264 | if (!(surface = eglCreateWindowSurface(egl_display_, egl_config_, window, nullptr))) { 265 | LOGE("%s %s eglCreateWindowSurface failed, error=%d", FILE_NAME, __func__ , eglGetError()); 266 | } 267 | return surface; 268 | } 269 | 270 | int EGLCore::MakeCurrent(EGLSurface surface) { 271 | return eglMakeCurrent(egl_display_, surface, surface, egl_context_); 272 | } 273 | 274 | int EGLCore::SwapBuffers(EGLSurface surface) { 275 | return eglSwapBuffers(egl_display_, surface); 276 | } 277 | 278 | void EGLCore::ReleaseSurface(EGLSurface surface) { 279 | eglDestroySurface(egl_display_, surface); 280 | } 281 | 282 | void EGLCore::Release() { 283 | if (egl_display_ != EGL_NO_DISPLAY && egl_context_ != EGL_NO_CONTEXT) { 284 | eglMakeCurrent(egl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); 285 | eglDestroyContext(egl_display_, egl_context_); 286 | eglTerminate(egl_display_); 287 | eglReleaseThread(); 288 | } 289 | egl_display_ = EGL_NO_DISPLAY; 290 | egl_context_ = EGL_NO_CONTEXT; 291 | } 292 | 293 | ``` 294 | 下面是Java层创建EGL环境的代码: 295 | ``` 296 | /** 297 | * EGL环境创建的类 298 | */ 299 | public class EglHelper { 300 | private EGL10 mEgl10; 301 | private EGLContext mEglContext; 302 | private EGLDisplay mEglDisplay; 303 | private EGLSurface mEglSurface; 304 | 305 | public void initEgl(Surface surface, EGLContext context) { 306 | //1. 307 | mEgl10 = (EGL10) EGLContext.getEGL(); 308 | 309 | //2. 310 | mEglDisplay = mEgl10.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); 311 | if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { 312 | throw new RuntimeException("eglGetDisplay failed"); 313 | } 314 | 315 | //3. 316 | int[] version = new int[2]; 317 | if (!mEgl10.eglInitialize(mEglDisplay, version)) { 318 | throw new RuntimeException("eglInitialize failed"); 319 | } 320 | 321 | //4.设置属性 322 | int[] attributes = new int[]{ 323 | EGL10.EGL_RED_SIZE, 8, 324 | EGL10.EGL_GREEN_SIZE, 8, 325 | EGL10.EGL_BLUE_SIZE, 8, 326 | EGL10.EGL_ALPHA_SIZE, 8, 327 | EGL10.EGL_DEPTH_SIZE, 8, 328 | EGL10.EGL_STENCIL_SIZE, 8, 329 | EGL10.EGL_RENDERABLE_TYPE, 4, 330 | EGL10.EGL_NONE 331 | }; 332 | 333 | int[] numConfigs = new int[1]; 334 | if (!mEgl10.eglChooseConfig(mEglDisplay, attributes, null, 1, numConfigs)) { 335 | throw new RuntimeException("eglChooseConfig failed"); 336 | } 337 | 338 | int numConfig = numConfigs[0]; 339 | if (numConfig <= 0) { 340 | throw new RuntimeException("No match configs"); 341 | } 342 | 343 | //5. 344 | EGLConfig[] configs = new EGLConfig[numConfig]; 345 | if (!mEgl10.eglChooseConfig(mEglDisplay, attributes, configs, numConfig, numConfigs)) { 346 | throw new RuntimeException("eglChooseConfig failed final"); 347 | } 348 | 349 | //6. 350 | //这句代码很重要,因为没有这个支持,并不能加载shader 351 | int[] attrib_list = { 352 | EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, 353 | EGL10.EGL_NONE 354 | }; 355 | if (context != null) { 356 | mEglContext = mEgl10.eglCreateContext(mEglDisplay, configs[0], context, attrib_list); 357 | } else { 358 | mEglContext = mEgl10.eglCreateContext(mEglDisplay, configs[0], EGL10.EGL_NO_CONTEXT, attrib_list); 359 | } 360 | 361 | //7. 362 | mEglSurface = mEgl10.eglCreateWindowSurface(mEglDisplay, configs[0], surface, null); 363 | 364 | //8. 365 | if (!mEgl10.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { 366 | throw new RuntimeException("eglMakeCurrent failed"); 367 | } 368 | } 369 | 370 | //刷新数据 371 | public boolean swapBuffers() { 372 | if (mEgl10 != null) { 373 | return mEgl10.eglSwapBuffers(mEglDisplay, mEglSurface); 374 | } else { 375 | throw new RuntimeException("EGL is empty"); 376 | } 377 | } 378 | 379 | public EGLContext getEglContext() { 380 | return mEglContext; 381 | } 382 | 383 | public void destoryEgl() { 384 | if (mEgl10 != null) { 385 | mEgl10.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); 386 | mEgl10.eglDestroySurface(mEglDisplay, mEglSurface); 387 | mEglSurface = null; 388 | 389 | mEgl10.eglDestroyContext(mEglDisplay, mEglContext); 390 | mEglContext = null; 391 | 392 | mEgl10.eglTerminate(mEglDisplay); 393 | mEglDisplay = null; 394 | mEgl10 = null; 395 | } 396 | } 397 | } 398 | 399 | ``` 400 | ## 4.shader加载流程 401 | 创建EGL环境之后,需要加载shader,获取对应的变量,进行相应的设置,实现绘制的需求。<br> 402 | shader加载流程: 403 | - 创建shader着色器,顶点和片元着色器 404 | ``` 405 | int shader = GLES20.glCreateShader(shaderType); 406 | 407 | ``` 408 | - 加载shader源码并编译shader 409 | ``` 410 | GLES20.glShaderSource(shader, source); 411 | GLES20.glCompileShader(shader); 412 | 413 | ``` 414 | - 检查是否编译成功 415 | ``` 416 | GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); 417 | 418 | ``` 419 | - 创建一个渲染程序 420 | ``` 421 | int program = GLES20.glCreateProgram(); 422 | 423 | ``` 424 | - 将着色器程序添加到渲染程序中 425 | ``` 426 | GLES20.glAttachShader(program, vertexShader); 427 | 428 | ``` 429 | - 链接源程序 430 | ``` 431 | GLES20.glLinkProgram(program); 432 | 433 | ``` 434 | - 检查链接源程序是否成功 435 | ``` 436 | GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); 437 | 438 | ``` 439 | - 得到着色器中的属性 440 | ``` 441 | int vPosition = GLES20.glGetAttribLocation(program, "v_Postion"); 442 | 443 | ``` 444 | - 使用源程序 445 | ``` 446 | GLES20.glUseProgram(program); 447 | 448 | ``` 449 | - 使顶点属性数组有效 450 | ``` 451 | GLES20.glEnableVertexAttribArray(vPosition); 452 | 453 | ``` 454 | - 为顶点属性赋值 455 | ``` 456 | GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer); 457 | 458 | ``` 459 | - 绘制图形 460 | ``` 461 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); 462 | 463 | ``` 464 | <br> 465 | OpenGL ES加载shader的代码流程: 加载shader和链接program的完整代码流程 466 | ``` 467 | private static int loadShader(int shaderType, String source) { 468 | int shader = GLES20.glCreateShader(shaderType); 469 | if(shader != 0) { 470 | GLES20.glShaderSource(shader, source); 471 | GLES20.glCompileShader(shader); 472 | 473 | int[] compile = new int[1]; 474 | GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compile, 0); 475 | if(compile[0] != GLES20.GL_TRUE) { 476 | Log.d(TAG, "shader compile error"); 477 | GLES20.glDeleteShader(shader); 478 | shader = 0; 479 | } 480 | return shader; 481 | } 482 | else { 483 | return 0; 484 | } 485 | } 486 | 487 | public static int createProgram(String vertexSource, String fragmentSoruce) { 488 | int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); 489 | int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSoruce); 490 | 491 | if(vertexShader != 0 && fragmentShader != 0) { 492 | int program = GLES20.glCreateProgram(); 493 | 494 | GLES20.glAttachShader(program, vertexShader); 495 | GLES20.glAttachShader(program, fragmentShader); 496 | 497 | GLES20.glLinkProgram(program); 498 | return program; 499 | } 500 | return 0; 501 | } 502 | ``` 503 | 504 | 505 | 506 | 507 | 508 | --------------------------------------------------------------------------------