每天一点点音视频_AVFrame

AVFrame 是一个数据结构,存放解码后的数据,也就是原始数据(Raw Data), 包括视频数据,音频数据。

(我发现我在这写的,根它的注释很吻合, 这样的话, 工作轻松很多呀)

使用 av_frame_alloc() 创建数据结构, 使用 av_frame_free() 释放, 内部的数据buffer,不会分配

AVFrame 通常被重复使用, 这时候通常使用 av_frame_unref() 来恢复初始状态

(感觉应付事儿了)

明天: 每天一点点音视频_音频文件格式概览

每天一点点音视频_AVPacket

之前已经提到了, 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编码过程

我们在之前的文章中知道, 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》- 那些法则

最近在看 《Atomic Habits》,英文版的, 读的很慢,每天睡前一小段, 在读的过程常常感叹,说的有道理,想要记录下来,但是却给自己找借口,太晚了,以后会专门记录下来的。甚至还想着能不能翻译整本书,出版呢。这样想起来,感觉有点不可能,完全没有路呀。要联系出版社啥的。

现在,看完了四大法则, 想先把四大法则的后面积累下来的方法记录下来,也算是暂时缓解我的那种渴望。

以下为翻译内容,并且添加了我所理解的内容:

如何创建一个好习惯

法则1 让它明显

1.1 填写习惯分数卡。写下自己当前习惯,并关注它们, 对它们是需要加强和减弱做上标记

1.2 具体到时间地点: “我会在 什么时候 什么地点 做什么。”

1.3 习惯栈: “在 当前习惯 后做 新习惯。”

1.4 设计环境, 让好习惯的提示信息更明显

法则2 让它更吸引人

2.1 诱惑绑定。将你想做的行为和你需要做的行为放在一块

2.2 加入一种文化, 在那里,你所想要做的行为是很普通的行为。 比如去国外环境学英语

2.3 创建动机仪式。在一个困难的习惯之前做一个自己喜欢的事情。 比如在开始写作之前喝杯奶昔

法则3 让它更容易

3.1 减少阻力。减少你和你的习惯之间的步骤。比如你可以选择在房间里原地踏步1万部,而不是出门去体育场跑圈

3.2 准备好环境。准备好环境让未来要做的习惯更容易,比如睡前倒一杯水放在床头第二天喝

3.3 掌握小的抉择时刻。优化那些会有很大影响的小的抉择。比如如果知道每次经过面包店闻到香味就想买个面包,可以提前就在选择回家的路时避开那家面包店

3.4 使用两分钟原则。 缩小你的习惯到两分钟就能完成

3.5 自动你的习惯。使用技术或者某些一次性购买来避免某个在未来要发生的行为。比如想跟踪自己的习惯,可以使用软件,而只在一周的时候总结

法则4 让它更满足

4.1 使用补给品。 当你完成你的习惯时,给自己一个及时的奖励

4.2 让 “不做事情” 变得有趣。当避免了一个坏习惯时, 设计一种方式可以看到好处。比如每当想要抽烟时,给自己10块钱,去买自己想买的东西(除了烟)

4.3 使用习惯追踪器。保持追踪你的习惯, “不要断了”

4.4 永远不要错过两次。当你忘记做某个习惯时,赶紧回来

如何戒掉坏习惯

反转法则1 让它不可见

1.5 在你的环境中减少坏习惯的提示

反转法则2 让它不吸引人

2.4 重组思维。强调避免坏习惯后的好处

反转法则3 让它更困难

3.6 增加阻力。增加你和你的坏习惯的步骤。比如不买烟,或者,把烟放在一个盒子里,然后所在箱子里,然后所在立橱里

3.7 使用一个委托设备。限制你的未来选择,从而只能选择有利的。比如手机可以设置在几点到几点静音,如果静音需要负责的操作,就更好了

反转规则4 让它不满足

4.5 找一个有责任的同伴。让它观察你的行为。我觉得作为up主,让粉丝监督就可以

4.6 创建习惯协议。让你的坏习惯的代价公开并且让你感到痛苦。比如坏习惯一旦出现就给粉丝发放福利

每天一点点音视频相关_第一周总结

不知不觉,这个系列已经一周了,虽然现在记录下来的都是写皮毛,蜻蜓点水的知识,但是还是很有成就感的。毕竟每天坚持一件事,并且跟踪进度,就是一件很有成就感的事情。

这周学习了软解码的过程, 特别讲解了 ffmpeg 解码的过程。

下周主要学习: ffmpeg 的编码过程, Andrid上的硬编解码

每天一点点音视频相关_ffmpeg_解包器和解码器连接

我们之前通过 AVFormatContext 创建并打开了一个音视频文件, 获取到特定的流, 然后通过 av_read_frame 从其中获取一个一个的包, 这些包里存储的是编码的数据。

然后,我们又通过 AVFormatContext 获取到流所需要的解码器 AVCodecContext, 这时候我们就可以将包数据喂给解码器,然后就得到了解码后的数据。

下面是解码的一帧的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
frame = av_frame_alloc();

ret = avcodec_send_packet(dec_ctx, pkt);
if (ret < 0) {
fprintf(stderr, "Error sending a packet for decoding\n");
exit(1);
}

while (ret >= 0) {
ret = avcodec_receive_frame(dec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return;
else if (ret < 0) {
fprintf(stderr, "Error during decoding\n");
exit(1);
}
// 获取到解码后的帧数据
}

首先,可以看到初始化了一个解码后的帧数据结构, 然后通过 avcodec_send_packet 给解码器喂食为解码的数据包, 然后它就会拉出解码后的数据, 可以看出,为给它一次,它可能会拉出几坨,当然,也可能一坨都没有, 那就再唯。

这种机制根 Android 的 MediaCodec 是相同的, 应该也是音视频文件的数据结构所决定的。也就是一个包的数据和一个帧的数据并不是一一对一个的,而更像是一种多对多的关系。这种方式,最好的操作方式还是流的操作方式,就是喂的只管喂,拉的只管拉。详细的后面将 MediaCodec的时候再说。

明天: 每天一点点音视频相关_第一周总结

每天一点点音视频相关_ffmpeg解码器

接上一节, 我们得到了一个一个的 packet

接下来,按照c程序的套路, 先把编码器的数据结构创建好:

1
2
3
4
5
6
7
8
9
10
11
12
AVCodecParameters *pParameters = format_context->streams[video_index]->codecpar;
video_codec = avcodec_find_decoder(fpParameters->codec_id);
video_codec_ctx = avcodec_alloc_context3(video_codec);
avcodec_parameters_to_context(video_codec_ctx,
pParameters);
AVDictionary *options = NULL;
av_dict_set(&options, "threads", "auto", 0);
av_dict_set(&options, "refcounted_frames", "1", 0);
avcodec_open2(video_codec_ctx, video_codec, &options)
AVFrame * decode_frame = av_frame_alloc();

avcodec_free_context(&video_codec_ctx);
  1. 创建编码器的数据结构 AVCodecContext
  2. 从流里拿到编码参数,根据编码参数里的编码器id获取到编码器
  3. 使用编码器参数初始化 AVCodecContext
  4. 初始化 AVCodecContext 来使用前面的到的编码器
  5. 用完记得释放

这里有两点疑问:

  1. 为什么方法名后有个数字
  2. 为什么创建数据结构和打开操作的时候都传 video_codec, 注释说两个必须一致

今天完成了编码器数据结构的初始化,还是比较复杂的

明天: 每天一点点音视频相关_ffmpeg_解包器和解码器连接

每天一点点音视频相关_ffmpeg解包数据示例_part2

昨天我们了解了 ffmpeg 里从视频文件里读取数据的数据结构和方法, 如下:

  1. avformat_alloc_context() 分配一个数据结构,用来存放打开的视频的信息,相当于是创建对象
  2. avformat_open_input(format_context, url, NULL, NULL) 打开 url 指定的视频文件, 读取头信息, 存入 format_context
  3. avformat_find_stream_info(format_context, NULL) 读取流的信息, 此后,就可以知道每个轨道的信息了
  4. av_find_best_stream(format_context, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0) 查找指定类型的轨道的 index, 包括音频,视频,字母等
  5. format_context->streams[video_index] 拿到了一个流, 就是轨道, 以后的解码操作都是基于轨道的。

下面详细说说流里包括那些信息:

  • AVCodecParameters *codecpar 编码器用到的一些参数, 比如帧的宽,高, 码率等等
  • av_dict_get(v_stream->metadata, “rotate”, m, AV_DICT_MATCH_CASE), 可以获取一些元数据, 这里可能类似与 Android 里的 MediaFormat 包含的数据
  • format_context->duration 视频时长

下面, 重点来说明一下, 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);

相同的套路:

  1. 分配一个数据结构, 有专门的方法
  2. 填充数据

至此, 解包完成, 获取到了一个一个的 packet

明天: 每天一点点音视频相关_ffmpeg解码器

每天一点点音视频相关_ffmpeg解包数据示例 part1

今天上午因为我个来,没有写,牵挂了一天,补上。

ffmpeg 可以说是音视频领域的明星, 使用 C 写的, 跨平台, 强大。

今天是演示 ffmpeg 的解包, 本来想在 Linux 上测试, 结果, 很痛苦。想用 gradle
构建, 但是 java 环境不对, java环境对了, 发现 gradle 对 c++ 的构建有点鸡肋, 没深入研究, 以后再说吧。

简单粗暴,直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

// 创建一个 avformat 的数据结构, 存储视频文件的信息
AVFormatContext *format_context = avformat_alloc_context();

// 打开文件, 读取头信息存入 format_context
if (avformat_open_input(format_context, url, NULL, NULL) != 0) {
LOGE("can not open url\n");
ret = 100;
goto fail;
}

// 以后就只用 format_context 了, 这里查找流信息
if (avformat_find_stream_info(format_context, NULL) < 0) {
LOGE("can not find stream\n");
ret = 101;
goto fail;
}

// 找到视频流
int video_index = av_find_best_stream(format_context, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (video_index != AVERROR_STREAM_NOT_FOUND) {
LOGI("video_index pd->av_track_flags:%d", i);
}

// 找到音频流
int audio_index = av_find_best_stream(pd->format_context, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if (audio_index != AVERROR_STREAM_NOT_FOUND) {
LOGI("audio_index pd->av_track_flags:%d", i);
}

AVStream *v_stream = format_context->streams[video_index];

avformat_close_input(&format_context)

注释基本白费, 基本的流程就是创建一个数据结构, 然后执行几个方法, 来初始化数据结构,将视频文件的信息存入其中, 以后所有的操作,只用这个数据机构就行了。

还可以知道, 一个视频文件, 里面是分视频流和音频流的, 实际上,一个视频可能会有多个视频轨道,音频轨道。

就这个小模块, 还没说完, 明天说: 每天一点点音视频相关_ffmpeg解包数据示例_part2。

值得强调的是,我发现C语言的模式了:

  1. 定义一个数据结构
  2. 调用方法, 填入数据
  3. 以后, 都用这个数据结构了, 对数据做各种操作

当然, 这是标准的面向对象的方式吧, 或者叫 “基于数据结构的算法”

每天一点点音视频相关_Android上的音视频编解码概述之软编

Android上的音视频编解码的软编的库主要是 ffmpeg, 实际上 ffmpeg 纯用软件实现视频编解码, 所以是跨平台的。

ffmpeg 的数据转换过程如下:

音视频文件 –demuxer–> 编码数据的包 –decoder–> 解码的帧 –encoder–> 编码的数据的包 –muxer–> 音视频文件

大的过程是一样的,因为音视频作为一种数据结构,一种数据结构,可能能够决定处理的流程。

音视频文件最外层是一个容器, 容器里可以有不同的数据,比如音频数据,视频数据,字幕数据等等,它只负责打包,便于将它们一块传输。而每一种数据,有自己的编码格式,这编码可能是为了压缩,也可能是为了加密。说道加密, 容器是不是也可以加密呢?

明天: 每天一点点音视频相关_ffmpeg解包数据示例