音视频开发是个非常复杂的, 庞大的开发话题, 初涉其中, 先看一下结合 OEIP(开源项目)新增例子.
可以打开 flv,mp4 类型文件, 以及 rtmp 协议音视频数据, 声音的播放使用 SDL.
把采集的麦 / 声卡数据混合并与采集的视频信息写入媒体文件或是 RMTP 协议中.
图片主要属性
包含长 / 宽 / 通道数 / 像素格式(U8/U16/F32), 以及排列格式 RGBA/YUV. 其中通道与像素格式, 如在 opencv 中, CV_8UC1/CV_8UC4, 表示 1 个通道与 4 个通道的 U8 格式. 而排列格式, 简单的分为 RGBA 类的, 如 BGRA,BGR,R 这些, 一般用于游戏里的纹理, RGBA/BGR/R 本身有表示通道格式的意思, 所以后面组合 RGBA32,RGBAF32 用来表示通道像素里数据格式. 而 YUV 类型, 一般用在媒体类型, 如采集设备, 音视频文件, 推拉流传输等, YUV 格式有二个组成部分. 一是 UV 对应像素个数, 如 YUV420,YUV422,YUV444, 他们的相同点就是一个像素点对应一个 Y, 不同点是, 如 YUV420 表示一个 U/V 对应 4 个像素点, 422 一个 UV 对应二个像素点, UV444 则表示一个 UV 对应一个像素点, YUV 像素点数据格式一般是 U8.
我们可以来比较一些常用格式最终占用字节大小, 如 1080P 下.
RGBA32 1920*1080*4=8,294,400 其中 4 是每个像素包含 RGBA 四个通道.
YUV420 1920*1080 + 980*540*2 前面是 Y 占用大小, 后面是 UV 占用大小.
YUV422 1920*1080 + 980*1080*2 前面是 Y 占用大小, 后面是 UV 占用大小
YUV444 1920*1080*3 每个像素一个 YUV 三通道.
第二是排序格式, 如 YUV420 可以细分 YUV420I,YUV420P,YUV420SP(NV12), 简单来说, 后缀是 P 是表示 YUV 这三个分开存放, 而 SP 是 Y 单独分开, UV 交织在一起, 而 I 就更简单了, YUV 交织在一起, 简单来说, 和 RGBA 格式差不多排列.
YUV420P/YUV422P 一般用来视频文件与推拉流传输上, 我想可能原因是 Y 用来表示亮度, 人眼最敏感部分, 早期黑白电视只有 Y 大家一样能看, 这种格式最方便兼容, 直接把 UV 丢弃就行, Y/U/V 可能在内存分布上不连续. 其中 YUV420I/YUV422I 这种交织的一般是采集设备所用, 内存上连续, 方便处理, YUV420SP(NV12) 这种一般也是采集设备所有, 这种格式简单来说, 一是方便只使用 Y, 二是 UV 可以和 Y 的宽度统一, 后面 align 的话, YUV 也是统一一个, 内存上也方便连续.
音频基本属性如下
采样率: 如人耳听到最高频率是 22kHz, 如要完全重现频率, 需要采样率是频率 * 2, 所以一般来说 44100 是很常见的采样率.
声道数: 常见单声道, 双声道, 也有不常见的更多声道.
数据格式: 一般是 S16(16 位有符号整合),F32(32 位浮点), 别的不算常用如 U8,D64, 这二个表示的分贝范围一个太小, 一个太大.
和图像类型, 多个声道的表示方法也有 P(平面),I(交织), 类似, 音频采集数据一般是 I, 传输 P, 在 OEIP 项目中, 相应格式一般都转成单声道 S16, 也就不需要管理是 P 还是 I.
音视频开发基本概念
有了上面基本认识, 我们再来认识一些概念(以下是本人的一些基本理解, 如果有错, 欢迎大家指出).
编码: 把原始音视频数据压缩, 简单来说, 1080PYUV420 的大小 1920*1080*3/2=3,110,400 Byte,1 秒 25 桢的话就有差不多 78M. 视频如 YUV->H264, 音频 PCM->ACC 过程就是编码. 在 FFMPEG 中, 类似过程就是 AVFrame->AVPacket
解码: 把压缩后的音视频转换成原始音视频数据, H264->YUV,ACC->PCM 过程. 在 FFMPEG 中, 就是 AVPacket->AVFrame.
在编码与解码中, 图像每桢原始大小取决原始图像大小根据长宽, 像素排序和格式组成, 而音频每桢对于特定编解码器是每个通道固定多少采样点, 如 AAC 是 1024(也有特殊情况 2048 的),MP3 是 1152, 相应字段在 AVFrame.nb_sampes,AVCodecContext.frame_size 里表示. 这样 AAC 中双通道 U16 每桢数据量就在 2*sizeof(U16)*1024. 而码率决定了编码质量, 一般情况下, 码率高质量好, 但是生成文件或是网络占用就好, 合适的码率网上有介绍, 1080P 下一般用 4M 的码率, 你用 1M 也行, 但是画面动的时候可能就糊了, 码率控制也有不同的控制策略, 根据需求选择自己的控制策略, 这部分网上有详细的讲解.
媒体文件: FLV/MP4 这些, 不同媒体格式把编码的信息用不同的方式保存, 不同媒体格式支持不同的编码格式, 大部媒体格式都支持 h264/acc 编码信息, 所以这二个编码格式比较常用.
多媒体协议: RTMP/RTSP 这些, 在媒体文件之上封装网络传输与控制的相关信息.
音视频流: 流分为音频流, 视频流, 字幕流等等这些, 其中 媒体文件里可能包含一个或多个音视频流, 而每个视频流是相同属性 (长宽, 像素格式等) 的原始视频数据编码成的信息流.
复用: 举个例子, 把一个音频流与一个视频流合成一个媒体文件, 就是复用.
解复用: 如上, 把一个媒体文件分解成相应的音频流与视频流.
FFMPEG 主要对象
AVFormatContext: 多媒体协议或是媒体文件, 如果是协议, 会解析出协议里包含的媒体文件信息, 这个类主要如今读 / 写压缩包, 读 / 写文件头与文件尾等方法. 你可以把这对象认为是一个媒体文件.
AVCodec: 编解码, 注意编码与解码或是用同一 codecId, 但是对象不同, 这个对象主要包含一些函数指针, 告诉如何把 frame->packet/packet->frame.
AVCodecContext: 编解码环境, 简单来说, AVCodec 是说如何编解码, 这个就是告诉他相应属性设置, 如对应视频来说, 长宽, 以及编码相应设置是否包含 B 桢, GOP 是多少都在这, 可以这么理解, 我们假设 AVCodec 与 AVCodecContext 如果是一个类, 那么 AVCodec 相当于里面的方法集合, AVCodecContext 相当于里面的变量集合.
AVStream: 媒体文件一般来说至少包含一个音频流或是视频流, 在复用 / 解复用到编解码之间是个承上启下的关系. 你可以理解 AVStream 包含音视频编码的信息列表. AVStream 也要包含相应的 AVCodecContext 包含的编解码信息, 后面会讲这二者信息在复用与解复用从那复制到那.
AVFrame: 音视频原始信息, 包含一个定长的数据信息.
AVPacket: 音视频编码信息, 包含一个不定长的数据信息.
FFMPEG 常见 API 分析
读一个媒体文件相应动作与 API 解析.
avformat_open_input 根据媒体文件 / 协议地址打开 AVFormatContext.
avformat_find_stream_info 查找 AVFormatContext 里对应的音视频流索引.
avcodec_find_decoder 根据索引打开对应音频与视频流解码器.
avcodec_alloc_context3 根据解码器生成解码器环境.
avcodec_parameters_to_context 把流的解码器参数 (图像长宽, 音频基本属性以及 frame_size) 复制到解码器环境.
avcodec_open2 打开解码器环境.
av_read_frame 从媒体文件 AVFormatContext 读每个 AVPacket.
avcodec_send_packet 根据对应 AVPacket 的索引, 发给对应流的解码器解码.
avcodec_receive_frame 得到解码器解码后的原始数据, 如在视频流中, 因 P 桢 B 桢关系, 一个 AVPacket 并不一定能得到一个 AVFrame, 比如 P 桢要考虑前后, 所以可能到后几个 Packet 的时候, 一下读出多桢数据, 所以 avcodec_send_packet/avcodec_receive_frame 的写法会是这样一个情况.
写入媒体文件相应动作与 API 解析(非 IO 模式):
avformat_alloc_output_context2 根据对应格式生成一个 AVFormatContext, 不同格式会固定一些数据, 比如上 FLV 格式, 音频流与视频流的时间基就是毫秒, 我试着改过这值, 后面也会在 avformat_write_header 之后重新改回来.
avcodec_find_encoder/avcodec_find_encoder_by_name 选择自己想要的编码器.
avcodec_alloc_context3 选择选择的编码器生成编码器环境, 不同与上面 的解码过程, 这里我们要自己填充相应信息, 如图像编码需要知道长宽, 码率, gop 等设置.
avcodec_open2 打开解码器环境.
avformat_new_stream 生成相应音视频流信息, 填充对应编码器到 AVFormatContext 里.
avcodec_parameters_from_context 把编码器设置的参数复制到流中.
avio_open 协议的解析以及协议的操作指针, 如何读写协议信息, 协议头, 协议内容等.
avformat_write_header 写入头信息.
avcodec_send_frame 把末压缩数据给编码器.
avcodec_receive_packet 拿到编码后的数据, 和解码类似, P 桢决定不可能一 Frame 一 packet, 可能要前后几个 Frame, 才能得到一系列的 packet.
av_interleaved_write_frame 把编码后的音视频数据交叉写入媒体文件中
av_write_trailer 结束写入, 根据写入的所有数据填充一部分需要计算的值.
还有一种 IO 模式, 可以利用关键桢图像与音频数据直接写入 IO 中, 然后直接从桢中读取相应音视频流的属性拿来直接用, 用来不确定视频流长宽等情况下使用.
从 API 可以看读写的差异, 读的媒体文件 AVFormatContext 里面的信息全有, 读到流, 从流里得到解码信息, 打开解码器, 从 AVFormatContext 读每个包, 用解码器解码包. 写入媒体文件就是生成一个空白的 AVFormatContext, 然后打开选择的编码器, 生成流, 然后写入流中每桢数据, 使用编码器编码后定稿文件.
图像上相关的坑, 媒体文件一般使用是 YUV 的 P 格式, 这个格式 YUV 分块保存, 还有相应 align 的概念, 举个例子, 假设你宽是 1080, 但是在 YUV 分块中, Y 宽度可能是 1088(假设当时使用 32 定齐), 其中每行数据索引处 1080-1087 以 0 填充, 相应的图像处理我全部提出来在 OEIP 处理, 在 OEIP 中图像数据全给 GPU 处理, 需要的是紧湊数据, 所以需要用 av_image_copy_to_buffer/av_image_fill_arrays 处理.
音频上相关的坑, 媒体文件多声道也是用的 P 格式, 而音频采集与播放设备一般用的 I 格式, 所以一般要用 swr_convert 转换, 音频播放使用 SDL 库, 就几个 API 调用就行, 在这就不说了, 可以查看相应 OEIP 里的代码处理, 音频采集 Winodws 用的是 WASAPI.
时间基的概念: 音视频流都有一个时间基的概念, 这个比较重要, flv 的音视频都是(1,1000), 如果是 mp4, 视频的时间基为(1,90000), 音频一般设为对应采样率. 时间基, 你可以简单理解为 1 秒内刻度, flv 的流对应就是毫秒, 而 mp4 视频流的时间基对应的是 1/90 毫秒, 什么意义了, 比如你视频对应的是 25 桢, 在 flv 里, 每桢相隔 40 个时间基, 而在 mp4 里, 相隔 360 个时间基, 在编码时, 我们需要把 frams 上的 pts/dts/duration 以对应时间基为单位, 注意转换, 在 OEIP 中, 我们把所有转出 / 转入与用户有关的时间全是毫秒, 其中转换我们内部自己处理.
故到此 OEIP 中, 可用的输入输出源新增媒体文件 / 协议, 同样, 这些功能在 Unity3D/UE4 里很方便展示, 比如把媒体文件 / 协议里的内容直接展示成对应 Unity3D/UE4 里的 Texture2D 显示, 或是把 Unity3D/UE4 里的 Texture2D/RTT 里的数据保存视频或是推送出去.
参考:
https://www.cnblogs.com/leisure_chn/category/1351812.html 叶余 FFMPEG 开发
来源: https://www.cnblogs.com/zhouxin/p/12651304.html