写在前面的话

最近的6个月,自从“摄像机”“摄像头”的移动端开发以来,初步了解了移动端音视频的开发过程,以及从P2P链接,编解码,到渲染呈现以至错误处理的整个流程。
越来越觉得,视音频开发真是太它喵的难了。

它的难点在于:

  1. 内部变量繁多,一个类(结构体)可能蕴含包含下划线等的近百个变量;
  2. 需要小心的处理多线程问题,在并行运行中,你也不会知道哪里快了一步,哪里慢了一步;
  3. 需要小心的处理算法与数据结构,你也不知道在队列,链表等数据结构的混合下,哪里会出现错误;
  4. 一堆循环运行的代码总是比较难DEBUG的;
  5. ....等等。

为了攻克“移动端开发👑上的明珠”,在午休时候(笔者心情好的时候)会尝试学习一些关于音视频开发的技术。

在尝试了《音视频开发进阶指南》与《FFmpeg从入门到精通》两本书籍后,翻来倒去...,歪歪斜斜的每页上都写着“开发技巧”几个字,我横竖睡不着,仔细看了半夜,才从字缝里看出来,满本上都写着四个字:

总的来说,就算是具有“各平台编写程序经验 + 移动端开发经验 + 系统的计算机知识(指的是C语言与数据结构)”也很难驾驭理解书本中的内容,更不用说产出了。

接下来的内容

之所以笔者写下这篇音视频开发学习笔记鞭策自己,是因为发现了一个相对学习曲线较为平滑的教程:

debugly / FFmpegTutorial

作者详细的以编写一个播放器为例子,讲解了FFmpeg的内容,觉得目前还算OK。

后续还会查找一些视频教程(包括淘宝),进行一些围绕C语言的音视频开发基础的学习,

笔者会逐步写一个自己的系列文章《移动端音视频(x)》来记录一下学习过程与一些自己的理解,但是并不对内部的正确与否做出保证(因为本人也是一个初学者),如果发现以前的文章有错误,将会及时改正。

AVFrame与AVBuffer

本文将介绍AVFrame与AVBuffer的基础概念。

AVFrame代表了什么

AVFrame是FFmpeg中一种最基本的数据格式,你可以理解为视频帧音频帧

这是什么意思?

我们观看的视频,可能是25帧一秒,可能是30帧一秒,这里的一帧就是一个AVFrame。它里面的装的是一个一个像素,但是格式可能是YUV或者RGB的。
我们播放的音频,44.1KHZ,代表每秒采样44100次,这里的一帧按照编码格式是不同的,比如AAC中一帧包含1024个采样点,而MP3包含1152个采样点。

那么如果,播放1秒视频,这个视频的视频流(VideoStream)是30fps,音频流(AudioStream)是44.1KHZ并且以AAC形式编码。
每秒需要:

视频帧 = 30 x 1 = 30帧
音频帧 = 44100 / 1024 x 1 = 44帧

这样的数据。

主要注意的是,AVFrame是解码之后的数据,也就是“解码流程中的Output”,“编码流程中的Input”这个概念。

在教程中,使用了“队列”来保存AVFrame,这样的好处也显而易见,我们可以通过一个线程不停向队列末尾插入新的AVFrame,同时播放器不停读取队列头部的AVFrame进行播放,符合先进先出这一数据结构。

与AVFrame有关的函数

并且,作者罗列了关于AVFrame最基本的函数,与我观察到使用的也非常一致。

函数名 解释
AVFrame *av_frame_alloc(void) 用于分配 AVFrame 结构内存,初始化成员变量,仅仅是自身,不包括 data buffer,必须使用 av_frame_free 释放。
av_frame_free(AVFrame **frame) 释放内存空间,如果 frame 是引用计数的则先解除引用。释放后指针将被置空,因此多次调用也不会有问题。
int av_frame_ref(AVFrame *dst, const AVFrame *src) 为 src 里的各个 AVBufferRef 建立新的引用到 dst,复制 src 里的属性到 dst,如果 src 不支持引用计数,那么将重新申请内存空间,然后复制 data buffer 数据。注意:dst 的内存可能会发生泄漏,记得使用 av_frame_unref 释放之前引用的数据。(引用计数加1)
AVFrame *av_frame_clone(const AVFrame *src) 克隆一个新的 AVFrame,相当于 av_frame_alloc() 加 av_frame_ref() 。
void av_frame_unref(AVFrame *frame) 解除对引用 data buffer 的引用,并且重置 frame 的属性。原则上应该跟 av_frame_ref 配对,但是多调用也没有问题,引用计数减到 0 时,内存就被释放了,不会过度释放!(引用计数减1)
void av_frame_move_ref(AVFrame *dst, AVFrame *src) 将 src 的全部内容转移到 dst,并且重置 src;我的是理解是 av_frame_ref(dst,src) + av_frame_unref(src)。(dst引用计数加1,src引用计数减1,总体不变)

 

值得注意的是,这些是成对出现的:

AVFrame *av_frame_alloc(void)av_frame_free(AVFrame **frame)
int av_frame_ref(AVFrame *dst, const AVFrame *src) void av_frame_unref(AVFrame *frame)

AVBuffer

在教程中,提到了AVBuffer这个概念,并且笔者查阅了 FFmpeg数据结构AVFrame 这篇文章后发现 —— AVFrame中数据似乎是保存在AVBuffer中的。

查阅了FFmpeg的官方文档 AVFrame Struct 后,明确的说明了AVFrame中的buf变量,类型为AVBufferRef(一种引用类型,就像我们用的CGContextRef)。

我们不应该直接使用data(尽管它是原始数据),而应该使用buf,并且类型AVBufferRef是对AVBuffer的一种安全性保护。

具体的解释需要参照 FFMpeg, AVFrame and AVBuffer ,里面指出了:

Is referenced frame basically 'previous' AVFrame->data??

Well, sort of, but please do note that most modern codecs support multiple references, so the past N AVFrame->data[] are cached internally in the codec to be used as reference frame in inter prediction of subsequent frames.

基本上,buf就是data,至于AVBufferRef类型,其实就是代表了一种引用计数,当没有AVBufferRef去引用一个AVBuffer,那么它就会被内存释放,所以猜测有一种共享的AVBuffer?(我不确定),有许多AVBufferRef指向它来获取数据。

结尾

作为系列的第一篇,本文讲了AVFrame与AVBuffer的一些拓展知识,权当笔记使用。
除此之外,教程中的多线程,队列获取AVFrame等方法,我打算放在后续的实践环节中说明。
多谢各位看一些碎碎念~