AVFrame 是一个数据结构,存放解码后的数据,也就是原始数据(Raw Data), 包括视频数据,音频数据。
(我发现我在这写的,根它的注释很吻合, 这样的话, 工作轻松很多呀)
使用 av_frame_alloc() 创建数据结构, 使用 av_frame_free() 释放, 内部的数据buffer,不会分配
AVFrame 通常被重复使用, 这时候通常使用 av_frame_unref() 来恢复初始状态
(感觉应付事儿了)
明天: 每天一点点音视频_音频文件格式概览
AVFrame 是一个数据结构,存放解码后的数据,也就是原始数据(Raw Data), 包括视频数据,音频数据。
(我发现我在这写的,根它的注释很吻合, 这样的话, 工作轻松很多呀)
使用 av_frame_alloc() 创建数据结构, 使用 av_frame_free() 释放, 内部的数据buffer,不会分配
AVFrame 通常被重复使用, 这时候通常使用 av_frame_unref() 来恢复初始状态
(感觉应付事儿了)
明天: 每天一点点音视频_音频文件格式概览
之前已经提到了, AVPacket 里存储着压缩后的视频帧数据。现在我们详细来说明以下它的结构, 创建,使用和释放。
它的结构主要包括属于那个流,数据, 数据的信息包括大小等。
AVPacket *pkt = av_packet_alloc();
av_packet_free(AVPacket **pkt);
这里需要注意的是, 传的是指针的指针, 这就避免了每次调用了这个方法后自己再置空, 这个方法内部自动做了置空。
AVPacket 是一个传递数据的结构, 所以没有专门的操作的方法, 它有一些方便复用的方法, 比如:
AVPacket *av_packet_clone(const AVPacket *src);
int av_copy_packet(AVPacket *dst, const AVPacket *src);
int av_packet_ref(AVPacket *dst, const AVPacket *src);
void av_packet_unref(AVPacket *pkt);
当然还是有一些其他的方法的, 比如对数据进行压缩的, 对额外数据进行的操作,知道就行。
明天: 每天一点点音视频_AVFrame
我们在之前的文章中知道, ffmpeg里, 解码后的数据是一个一个的 AVFrame, 我们要对它进行编码, 让它变成 AVPacket, 然后对 AVPacket 打包成一个最终的音视频文件。
我们首先需要一个编码器, 在之前创建解码器的时候,我们是根据 AVPacket 的数据来找特定的编码器的, 但是现在,我们是在编码,我们可以选择特定的编码器。
codec = avcodec_find_encoder(AV_CODEC_ID_MP2);
c = avcodec_alloc_context3(codec);
因为是在编码,所以我们不仅可以决定编码器,还可以选择不同的参数比如码率,采样率等等。
在设置好参数后,需要执行打开命令
avcodec_open2(c, codec, NULL)
此时, 编码器准备好了。
剩下的就是给它喂数据,然后等它拉出来了。
先准备输入和输出的数据结构
pkt = av_packet_alloc();
frame = av_frame_alloc();
av_frame_get_buffer(frame, 0);
av_frame_make_writable(frame);
对于 Frame, 需要填入数据和数据信息
额, 开始琐碎了, 不够抽象了, 花太多时间了。
明天: 每天一点点音视频_AVPacket
最近在看 《Atomic Habits》,英文版的, 读的很慢,每天睡前一小段, 在读的过程常常感叹,说的有道理,想要记录下来,但是却给自己找借口,太晚了,以后会专门记录下来的。甚至还想着能不能翻译整本书,出版呢。这样想起来,感觉有点不可能,完全没有路呀。要联系出版社啥的。
现在,看完了四大法则, 想先把四大法则的后面积累下来的方法记录下来,也算是暂时缓解我的那种渴望。
以下为翻译内容,并且添加了我所理解的内容:
不知不觉,这个系列已经一周了,虽然现在记录下来的都是写皮毛,蜻蜓点水的知识,但是还是很有成就感的。毕竟每天坚持一件事,并且跟踪进度,就是一件很有成就感的事情。
这周学习了软解码的过程, 特别讲解了 ffmpeg 解码的过程。
下周主要学习: ffmpeg 的编码过程, Andrid上的硬编解码
我们之前通过 AVFormatContext 创建并打开了一个音视频文件, 获取到特定的流, 然后通过 av_read_frame 从其中获取一个一个的包, 这些包里存储的是编码的数据。
然后,我们又通过 AVFormatContext 获取到流所需要的解码器 AVCodecContext, 这时候我们就可以将包数据喂给解码器,然后就得到了解码后的数据。
下面是解码的一帧的代码
1 | frame = av_frame_alloc(); |
首先,可以看到初始化了一个解码后的帧数据结构, 然后通过 avcodec_send_packet 给解码器喂食为解码的数据包, 然后它就会拉出解码后的数据, 可以看出,为给它一次,它可能会拉出几坨,当然,也可能一坨都没有, 那就再唯。
这种机制根 Android 的 MediaCodec 是相同的, 应该也是音视频文件的数据结构所决定的。也就是一个包的数据和一个帧的数据并不是一一对一个的,而更像是一种多对多的关系。这种方式,最好的操作方式还是流的操作方式,就是喂的只管喂,拉的只管拉。详细的后面将 MediaCodec的时候再说。
明天: 每天一点点音视频相关_第一周总结
接上一节, 我们得到了一个一个的 packet
接下来,按照c程序的套路, 先把编码器的数据结构创建好:
1 | AVCodecParameters *pParameters = format_context->streams[video_index]->codecpar; |
这里有两点疑问:
今天完成了编码器数据结构的初始化,还是比较复杂的
明天: 每天一点点音视频相关_ffmpeg_解包器和解码器连接
昨天我们了解了 ffmpeg 里从视频文件里读取数据的数据结构和方法, 如下:
下面详细说说流里包括那些信息:
下面, 重点来说明一下, seek 和 读包
我们在读取视频信息的时候, 可以选择制定在某个时间, 读取数据, 这样就可以不用从头一帧一帧的解码了, 实现进度的跳转。
av_seek_frame(format_context, -1, seek_to, AVSEEK_FLAG_BACKWARD)
第二个参数, 可以指定基于某个流进行seek, seek_to 是时间, AVSEEK_FLAG_BACKWARD 是 Seek的模式, 比如, 额, 这个不是我所认识的模式,之前我了解的 seek模式如相对于当前位置seek, 或者绝对时间的seek, 这里的模式却是, 基于 byte 的seek, 可以seek到任何帧, 还是seek 到关键帧等。
在指定好要读的位置, 下一步,就是开始读了
在读之前, 县准备好存贮一个 包 数据的结构
AVPacket * packet = av_packet_alloc()
av_read_frame(format_context, packet);
相同的套路:
至此, 解包完成, 获取到了一个一个的 packet
明天: 每天一点点音视频相关_ffmpeg解码器
今天上午因为我个来,没有写,牵挂了一天,补上。
ffmpeg 可以说是音视频领域的明星, 使用 C 写的, 跨平台, 强大。
今天是演示 ffmpeg 的解包, 本来想在 Linux 上测试, 结果, 很痛苦。想用 gradle
构建, 但是 java 环境不对, java环境对了, 发现 gradle 对 c++ 的构建有点鸡肋, 没深入研究, 以后再说吧。
简单粗暴,直接上代码:
1 |
|
注释基本白费, 基本的流程就是创建一个数据结构, 然后执行几个方法, 来初始化数据结构,将视频文件的信息存入其中, 以后所有的操作,只用这个数据机构就行了。
还可以知道, 一个视频文件, 里面是分视频流和音频流的, 实际上,一个视频可能会有多个视频轨道,音频轨道。
就这个小模块, 还没说完, 明天说: 每天一点点音视频相关_ffmpeg解包数据示例_part2。
值得强调的是,我发现C语言的模式了:
当然, 这是标准的面向对象的方式吧, 或者叫 “基于数据结构的算法”
Android上的音视频编解码的软编的库主要是 ffmpeg, 实际上 ffmpeg 纯用软件实现视频编解码, 所以是跨平台的。
ffmpeg 的数据转换过程如下:
音视频文件 –demuxer–> 编码数据的包 –decoder–> 解码的帧 –encoder–> 编码的数据的包 –muxer–> 音视频文件
大的过程是一样的,因为音视频作为一种数据结构,一种数据结构,可能能够决定处理的流程。
音视频文件最外层是一个容器, 容器里可以有不同的数据,比如音频数据,视频数据,字幕数据等等,它只负责打包,便于将它们一块传输。而每一种数据,有自己的编码格式,这编码可能是为了压缩,也可能是为了加密。说道加密, 容器是不是也可以加密呢?
明天: 每天一点点音视频相关_ffmpeg解包数据示例