0%

FFmpeg API(上)

FFmpeg 的功能非常强大,FFmpeg 不但有丰富的命令行工具来帮助我们处理音视频数据,而且 FFmpeg 提供了非常易用的 API,通过这些 API 就可以把 FFmpeg 集成到我们自己的程序中,以写代码的方式调用这些 API 来完成对媒体文件的操作。 另外在使用这些 API 的同时可以参考下面的图,该图展示了 FFmpeg 处理流数据基本流程,一定要想搞楚每一步操作在干什么,目的是什么。

FFmpeg 处理流数据基本概念

本文主要是尝试以代码的方式调用 FFmpeg 的 API 完成一些媒体文件的操作,在学习 FFmpeg 的 API 之前需要介绍下一些统一术语:

1、容器/文件(Conainer/File):即特定格式的多媒体文件,比如 Mp4、flv、mov 等。

2、媒体流(Stream):表示时间轴上的一段连续数据,如一段声音数据、一段视频数据或一段字幕数据,可以是压缩的,也可以是非压缩的,压缩的数据需要关联特定的编解码器。

3、数据帧/数据包(Frame/Packet):通常一个媒体流是由大量的数据帧组成的,对于压缩数据,帧对应着编解码器的最小处理单元,分属于不同媒体流的数据帧交错存储于容器之中。

4、编解码器:编解码器是以帧为单位实现压缩数据和原始数据之间的相互转换的。

前面所介绍的术语,其实就是 FFmpeg 中抽象出来的概念,在 FFmpeg 的代码中,存在几个重要的结构体:

1、AVFormatContext:就是对容器个抽象,媒体文件的构成和基本信息上下文

2、AVStream:流的抽象,在每一路流中都会描述这路流的编码格式。

3、AVCodecContext:编解码格式抽象。

4、AVCodec:编解码器抽象。

5、AVPacket:压缩数据抽象,AVPacket 作为解码码器的输入。

6、AVFrame:原始数据的抽象,AVFrame 作为编码器的输入。

7、AVFilter:对于音视频的处理肯定是针对于原始数据的处理,也就是针对于 AVFrame 的处理,使用的就是 AVFilter。

FFmpeg 模块说明

FFmpeg 模块 功能
libavcodec 提供了一系列编码器的实现。
libavformat 实现在流协议,容器格式及其本 IO 访问。
libavutil 包括了 hash 器,解码器和各种工具函数。
libavfilter 提供了各种音视频过滤器。
libavdevice 提供了访问捕获设备和回放设备的接口。
libavswresample 实现了混音和重采样。
libavswscale 实现了色彩转换和缩放工能。

FFmpeg API Demo

1、日志打印

主要的 API

1
2
3
4
5
#include <libavutil.h>

av_log_set_level (AV_LOG_INFO);

av_log (NULL, AV_LOG_INFO, "...% s\n", "")

CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
cmake_minimum_required(VERSION 3.19)
project(ffmpeg_log)

# C++11
set(CMAKE_CXX_STANDARD 11)

# 指定目标于源文件
add_executable(ffmpeg_log main.cpp)

# 导入头文件
include_directories("/usr/local/ffmpeg/include")

# Import 依赖库
set(libs "/usr/local/ffmpeg/lib")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${libs}")

# 目标依赖库
target_link_libraries(
ffmpeg_log
# ffmpeg_log 这个目标依赖于 libavutil.dylib
avutil
)

main.cpp

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
#include <iostream>

extern "C"
{
// 打印日志的头文件
#include <libavutil/log.h>
}

int main() {
std::cout << "Hello, World!" << std::endl;
// 设置最低打印的日志级别为 INFO,超过这个级别的日志将不会被打印
av_log_set_level(AV_LOG_INFO);

// VERBOSE 级别不打印
av_log(NULL, AV_LOG_VERBOSE, "Hello % s\n", "FFmpeg Log! AV_LOG_VERBOSE");

// DEBUG 级别不打印
av_log(NULL, AV_LOG_DEBUG, "Hello % s\n", "FFmpeg Log! AV_LOG_DEBUG");

// 剩下的全都打印
av_log(NULL, AV_LOG_INFO, "Hello % s\n", "FFmpeg Log! AV_LOG_INFO");
av_log(NULL, AV_LOG_WARNING, "Hello % s\n", "FFmpeg Log! AV_LOG_WARNING");
av_log(NULL, AV_LOG_ERROR, "Hello % s\n", "FFmpeg Log! AV_LOG_ERROR");

return 0;
}

2、文件删除与重命名

主要的 API

1
2
3
4
5
6
#include <libavutil/log.h>
#include <libavformat/avio.h>

avpriv_io_move("srcFilePath", "destFilePath");

avpriv_io_delete("targetFilePath");

CMakeLists.txt 同上

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
// 因为 ffmpeg 的日志打印也是标准 / 错误输出流 
#include <iostream>

extern "C" {
#include <libavutil/log.h>
#include <libavformat/avio.h>
}

int main() {
av_log_set_level(AV_LOG_DEBUG);
av_log(NULL, AV_LOG_DEBUG, "start....\n");
int ret;

// 移动或者重命名文件
ret = avpriv_io_move("../README.md", "../my.md");
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "move README.md failed\n");
return -1;
}
av_log(NULL, AV_LOG_DEBUG, "move README.md success\n");

// 删除文件
ret = avpriv_io_delete("../README.md");
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "delete README.md failed\n");
return -1;
}

av_log(NULL, AV_LOG_DEBUG, "delete README.md success\n");
return 0;
}

3、文件夹打开与读取

主要的 API

avio_open_dir 打开文件夹

avio_read_dir 读取文件夹

avio_close_dir 释放资源

avio_free_directory_entry 释放资源

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
35
36
37
38
39
40
41
42
43
44
// 因为 ffmpeg 的日志打印也是标准 / 错误输出流 
#include <iostream>

extern "C"
{
#include <libavutil/log.h>
#include <libavformat/avio.h>
}

int main(int argc, char const *argv [])
{
av_log_set_level(AV_LOG_INFO);
//avio_open_dir ();
//avio_read_dir ();
//avio_close_dir ();
// AVIODirContext 操作目录的上下文
// AVIODirEntry 目录项,存放文件名、文件大小等信息
AVIODirContext *context = NULL;
int ret = avio_open_dir(&context, "./", NULL);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR,"Open dir failed! ret = % d\n", ret);
avio_close_dir(&context); // 避免内存泄漏
return -1;
}

AVIODirEntry *entry = NULL;
for(;;){
int ret = avio_read_dir(context, &entry);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR,"Read dir failed! ret = % d\n", ret);
return -1;
}

// 到末尾说明应该退出
if(entry == NULL) break;

av_log(NULL, AV_LOG_INFO, "%12" PRId64" % s \n", entry -> size, entry -> name);
// 释放 AVIODirEntry
avio_free_directory_entry(&entry);
}

avio_close_dir(&context);
return 0;
}

4、音视频 Meta 信息

FFmpeg 处理的基本流程:

av_register_all

avformat_open_input

av_dump_format

avformat_close_input

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
#include <iostream>

extern "C" {
#include <libavutil/log.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
}

int main()
{
const char* inputFilePath = "C:\\Users\\changlin\\Desktop\\input.mp4";
av_log_set_level(AV_LOG_INFO);
av_register_all();
//av_register_input_format ();
AVFormatContext* avContext = NULL;


//fmt: 输入文件格式(默认根据后缀名解析) options: 命令行参数
int ret = avformat_open_input(&avContext, inputFilePath, NULL, NULL);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Open file failed,ret = % d\n", ret);
return -1;
}

// 打印关于输入或输出格式的详细信息
//param2 需要解析的流索引号,例如 0 是视频流,1 是音频流
//param4 指定的视音频上下文是输入流(0)还是输出流(1)
av_dump_format(avContext, 0, inputFilePath, 0);
avformat_close_input(&avContext);
return 0;
}

5、抽取 AAC 音频数据

ADTS 全称是 (Audio Data Transport Stream),是 AAC 的一种十分常见的传输格式。

记得第一次做 demux 的时候,把 AAC 音频的 ES 流从 FLV、Mp4 等封装格式中抽出来送给硬件解码器时,不能播;因为一般的 AAC 解码器都需要把 AAC 的 ES 流打包成 ADTS 的格式,一般是在 AAC ES 流前添加 7 个字节的 ADTS header,也就是说你可以把 ADTS 这个头看作是 AAC 的 frameheader。

ADTS 头中相对有用的信息采样率、声道数、帧长度。想想也是,我要是解码器的话,你给我一堆得 AAC 音频 ES 流我也解不出来。每一个带 ADTS 头信息的 AAC 流会清晰的告送解码器他需要的这些信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#pragma once
#include <cstdint>
/*
* szAdtsHeader ADTS 头缓冲区
* dataLength 数据长度,也就是 Package.size + 7
*/
void adts_header(char* szAdtsHeader, int dataLength) {
int profile = 2; // AAC LC
int freqIdx = 4; // 44.1KHz
int chanCfg = 2; // CPE

//fill in ADTS data
szAdtsHeader [0] = (char)0xFF;
szAdtsHeader [1] = (char)0xF9;
szAdtsHeader [2] = (char)(((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
szAdtsHeader [3] = (char)(((chanCfg & 3) << 6) + (dataLength >> 11));
szAdtsHeader [4] = (char)((dataLength & 0x7FF) >> 3);
szAdtsHeader [5] = (char)(((dataLength & 7) << 5) + 0x1F);
szAdtsHeader [6] = (char)0xFC;
}

接下来抽取音频,并给给每帧 AAC 数据都添加上 AAC 帧头部信息:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <iostream>
#include <stdio.h>

extern "C" {
#include <libavutil/log.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include <libavcodec/packet.h>
#include <libavcodec/avcodec.h>
#include "adts_header.h"
}

int main()
{
const char* inputFilePath = "C:\\Users\\changlin\\Desktop\\input.mp4";
const char* outputFilePath = "C:\\Users\\changlin\\Desktop\\output.aac";
av_log_set_level(AV_LOG_INFO);
av_register_all();
//av_register_input_format ();
AVFormatContext* avContext = NULL;

//fmt: 输入文件格式(默认根据后缀名解析) options: 命令行参数
int ret = avformat_open_input(&avContext, inputFilePath, NULL, NULL);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Open file failed,ret = % d\n", ret);
return -1;
}
// 打印出基本信息
av_dump_format(avContext, 0, inputFilePath, 0);

FILE* outputFile = fopen(outputFilePath, "wb+");
if(outputFile == NULL)
{
av_log(NULL, AV_LOG_ERROR, "Open outFile failed!\n");
avformat_close_input(&avContext);
return -1;
}

int audio_index = 0;
// 寻找最佳音频流
audio_index = av_find_best_stream(avContext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if(audio_index < 0)
{
av_log(NULL, AV_LOG_ERROR, "Can't find the best stream!\n");
fclose(outputFile);
return -1;
}
AVPacket av_packet;
// 初始化包
av_init_packet(&av_packet);
size_t writeLength;
while(av_read_frame(avContext, &av_packet) >= 0)
{
if(audio_index == av_packet.stream_index)
{
// 添加 ADTS 头信息
char adts_header_buf [7];
adts_header(adts_header_buf, av_packet.size + 7);
fwrite(adts_header_buf, 1, 7, outputFile);

// 写入 AAC 帧
writeLength = fwrite(av_packet.data, 1, av_packet.size, outputFile);
if(writeLength != av_packet.size)
{
av_log(NULL, AV_LOG_WARNING, "write length not equal packet.size\n");
}
}
// 释放上次使用的包
av_packet_unref(&av_packet);
}
avformat_close_input(&avContext);
if(outputFile)
{
fclose(outputFile);
}
return 0;
}

6、抽取 H264 视频数据

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#include <iostream>

extern "C"
{
#include <libavutil/log.h>
#include <libavformat/avio.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavfilter/avfilter.h>
#include <stdio.h>
#include "extr_video.h"
}

/**
* 概念一:StartCode
* 帧与帧之间如何区分?
* - 1、特征码,比如解析到某个特征码的时候就知道了这一帧数据结束了。
* - 2、头部添加帧长度,知道了此帧有多长,就按照长度解析即可。
* H264 采用的就是第一种,StartCode 这个概念就是特征码。
*
* 概念二:SPS/PPS
* 解码用到的视频参数,视频帧的宽高、帧率等等参数。
* 如果是相同的参数,比如宽高、帧率一直不变,那么只需要一份 SPS/PPS 就可以了。
* 但是如果是变化的场景,比如直播流,参数会发生变化,每次发生变化的时候都会更新 SPS/PPS,
* 解码器在参数发生变化的时候就会重新初始化解码器,从而继续正确的解码数据。另外在直播流等
* 通过网络传输的场景下容易发生丢包,往往会在关键帧设置 SPS/PPS,避免错误解码等情况的出现。
* 但是这样会导致网络数据传输量增加,增加网络负载吗?其实不会,SPS/PPS 只有几个字节或者十
* 几个字节,占用空间非常小,对于整个视频流来说基本上可以忽略不计。
*
* 通过 FFmpeg 如何获取 SPS/PPS 呢?
* codec —> extratata
*/

/**
* @brief 抽取 H264 数据
*
* @param argc
* @param argv
* @return int
*/
int main(int argc, char const *argv [])
{
const char *intputPath = "/Users/zchanglin/Downloads/input.mp4";
const char *outputPath = "/Users/zchanglin/Downloads/output.h264";

av_register_all ();

AVFormatContext *avContext;
int open_ret = avformat_open_input (&avContext, intputPath, NULL, NULL);

if (open_ret < 0)
{
av_log (NULL, AV_LOG_ERROR, "Open input file failed!\n");
return -1;
}
// 打印视频信息
av_dump_format (avContext, NULL, intputPath, NULL);

// 打开输出文件
FILE *outputFile = fopen (outputPath, "wb");
if (outputPath == NULL)
{
av_log (NULL, AV_LOG_ERROR, "Open outputFile failed!\n");
avformat_close_input (&avContext);
return -1;
}

AVPacket av_package;
av_init_packet (&av_package);
av_package.size = 0;
av_package.data = NULL;

int bestStreamIndex = av_find_best_stream (avContext,
AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (bestStreamIndex < 0)
{
av_log (NULL, AV_LOG_ERROR, "Find best stream failed!\n");
avformat_close_input (&avContext);
fclose (outputFile);
return -1;
}

// 拿到视频流的索引号
while(av_read_frame (avContext, &av_package) >= 0){
// 添加 SPS/PPS 数据
if(av_package.stream_index == bestStreamIndex){
h264_mp4toannexb (avContext, &av_package, outputFile);
}
av_packet_unref (&av_package);
}

avformat_close_input (&avContext);
if (outputFile != NULL)
{
fclose (outputFile);
}

av_log (NULL, AV_LOG_INFO, "==========================\n");
av_log (NULL, AV_LOG_INFO, "Detach video data success!\n");
av_log (NULL, AV_LOG_INFO, "==========================\n");

return 0;
}

extr_video.h

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
35
36
#ifndef __EXTR_VIDEO_H__
#define __EXTR_VIDEO_H__
#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avio.h>
#include <libavformat/avformat.h>

#ifndef AV_WB32
#define AV_WB32 (p, val) \
do \
{ \
uint32_t d = (val); \
((uint8_t *)(p))[3] = (d); \
((uint8_t *)(p))[2] = (d) >> 8; \
((uint8_t *)(p))[1] = (d) >> 16; \
((uint8_t *)(p))[0] = (d) >> 24; \
} while (0)
#endif

#ifndef AV_RB16
#define AV_RB16 (x) \
((((const uint8_t *)(x))[0] << 8) | \
((const uint8_t *)(x))[1])
#endif

static int alloc_and_copy(AVPacket *out,
const uint8_t *sps_pps, uint32_t sps_pps_size,
const uint8_t *in, uint32_t in_size);

int h264_extradata_to_annexb(const uint8_t *codec_extradata,
const int codec_extradata_size,
AVPacket *out_extradata, int padding);

int h264_mp4toannexb(AVFormatContext *fmt_ctx, AVPacket *in, FILE *dst_fd);

#endif //!__EXTR_VIDEO_H__

extr_video.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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#include "extr_video.h"

static int alloc_and_copy(AVPacket *out,
const uint8_t *sps_pps, uint32_t sps_pps_size,
const uint8_t *in, uint32_t in_size)
{
uint32_t offset = out->size;
uint8_t nal_header_size = offset ? 3 : 4;
int err;

err = av_grow_packet (out, sps_pps_size + in_size + nal_header_size);
if (err < 0)
return err;

if (sps_pps)
memcpy(out->data + offset, sps_pps, sps_pps_size);
memcpy(out->data + sps_pps_size + nal_header_size + offset, in, in_size);
if (!offset)
{
AV_WB32 (out->data + sps_pps_size, 1);
}
else
{
(out->data + offset + sps_pps_size)[0] =
(out->data + offset + sps_pps_size)[1] = 0;
(out->data + offset + sps_pps_size)[2] = 1;
}

return 0;
}

int h264_extradata_to_annexb(const uint8_t *codec_extradata, const int codec_extradata_size, AVPacket *out_extradata, int padding)
{
uint16_t unit_size;
uint64_t total_size = 0;
uint8_t *out = NULL, unit_nb, sps_done = 0,
sps_seen = 0, pps_seen = 0, sps_offset = 0, pps_offset = 0;
const uint8_t *extradata = codec_extradata + 4;
static const uint8_t nalu_header [4] = {0, 0, 0, 1};
int length_size = (*extradata++ & 0x3) + 1; //retrieve length coded size, 用于指示表示编码数据长度所需字节数

sps_offset = pps_offset = -1;

/* retrieve sps and pps unit (s) */
unit_nb = *extradata++ & 0x1f; /* number of sps unit (s) */
if (!unit_nb)
{
goto pps;
}
else
{
sps_offset = 0;
sps_seen = 1;
}

while (unit_nb--)
{
int err;

unit_size = AV_RB16 (extradata);
total_size += unit_size + 4;
if (total_size > INT_MAX - padding)
{
av_log (NULL, AV_LOG_ERROR,
"Too big extradata size, corrupted stream or invalid MP4/AVCC bitstream\n");
av_free (out);
return AVERROR (EINVAL);
}
if (extradata + 2 + unit_size > codec_extradata + codec_extradata_size)
{
av_log (NULL, AV_LOG_ERROR, "Packet header is not contained in global extradata, "
"corrupted stream or invalid MP4/AVCC bitstream\n");
av_free (out);
return AVERROR (EINVAL);
}
if ((err = av_reallocp (&out, total_size + padding)) < 0)
return err;
memcpy(out + total_size - unit_size - 4, nalu_header, 4);
memcpy(out + total_size - unit_size, extradata + 2, unit_size);
extradata += 2 + unit_size;
pps:
if (!unit_nb && !sps_done++)
{
unit_nb = *extradata++; /* number of pps unit (s) */
if (unit_nb)
{
pps_offset = total_size;
pps_seen = 1;
}
}
}

if (out)
memset(out + total_size, 0, padding);

if (!sps_seen)
av_log (NULL, AV_LOG_WARNING,
"Warning: SPS NALU missing or invalid. "
"The resulting stream may not play.\n");

if (!pps_seen)
av_log (NULL, AV_LOG_WARNING,
"Warning: PPS NALU missing or invalid. "
"The resulting stream may not play.\n");

out_extradata->data = out;
out_extradata->size = total_size;

return length_size;
}

/**
* @brief 设置 SPS/PPS
*
* @param fmt_ctx
* @param in
* @param dst_fd
* @return int
*/
int h264_mp4toannexb(AVFormatContext *fmt_ctx, AVPacket *in, FILE *dst_fd)
{

AVPacket *out = NULL;
AVPacket spspps_pkt;

int len;
uint8_t unit_type;
int32_t nal_size;
uint32_t cumul_size = 0;
const uint8_t *buf;
const uint8_t *buf_end;
int buf_size;
int ret = 0, i;

out = av_packet_alloc ();

buf = in->data;
buf_size = in->size;
buf_end = in->data + in->size;

do
{
ret = AVERROR (EINVAL);
if (buf + 4 > buf_end)
goto fail;

for (nal_size = 0, i = 0; i < 4; i++)
nal_size = (nal_size << 8) | buf [i];

buf += 4;
unit_type = *buf & 0x1f;

if (nal_size > buf_end - buf || nal_size < 0)
goto fail;

if (unit_type == 5)
{

h264_extradata_to_annexb (fmt_ctx->streams [in->stream_index]->codec->extradata,
fmt_ctx->streams [in->stream_index]->codec->extradata_size,
&spspps_pkt,
AV_INPUT_BUFFER_PADDING_SIZE);

if ((ret = alloc_and_copy (out,
spspps_pkt.data, spspps_pkt.size,
buf, nal_size)) < 0)
goto fail;

}else {
if ((ret = alloc_and_copy (out, NULL, 0, buf, nal_size)) < 0)
goto fail;
}

len = fwrite (out->data, 1, out->size, dst_fd);
if (len != out->size)
{
av_log (NULL, AV_LOG_DEBUG, "warning, length of writed data isn't equal pkt.size (% d, % d)\n",
len,
out->size);
}
fflush (dst_fd);

next_nal:
buf += nal_size;
cumul_size += nal_size + 4; //s->length_size;
} while (cumul_size < buf_size);
fail:
av_packet_free (&out);

return ret;
}

7、mp4 格式转 flv

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#include <libavutil/log.h>
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>

int main(int argc, char **argv)
{
// 创建输出的 format
AVOutputFormat *ofmt = NULL;
// 创建两个上下文
AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
// 创建 packet
AVPacket pkt; v
const char *in_filename, *out_filename;
int ret, i;
int stream_index = 0;
int *stream_mapping = NULL;
int stream_mapping_size = 0;

in_filename = "/Users/zchanglin/Downloads/input.mp4";
out_filename = "/Users/zchanglin/Downloads/output.flv";


if ((ret = avformat_open_input (&ifmt_ctx, in_filename, 0, 0)) < 0) {
fprintf(stderr, "Could not open input file '% s'", in_filename);
goto end;
}
// 如果当前输入文件没有任何流的信息直接返回
if ((ret = avformat_find_stream_info (ifmt_ctx, 0)) < 0) {
fprintf(stderr, "Failed to retrieve input stream information");
goto end;
}
// 输出输出文件基本信息
av_dump_format (ifmt_ctx, 0, in_filename, 0);

// 创建输出文件上下文,是 ffpmeg 根据 out_filename 自主生成对应的
avformat_alloc_output_context2 (&ofmt_ctx, NULL, NULL, out_filename);
if (!ofmt_ctx) {
fprintf(stderr, "Could not create output context\n");
ret = AVERROR_UNKNOWN;
goto end;
}
// 获取输入文件流个数和大小并申请空间
stream_mapping_size = ifmt_ctx->nb_streams;
stream_mapping = av_mallocz_array (stream_mapping_size, sizeof(*stream_mapping));
if (!stream_mapping) {
ret = AVERROR (ENOMEM);
goto end;
}

ofmt = ofmt_ctx->oformat;

for (i = 0; i < ifmt_ctx->nb_streams; i++) {
AVStream *out_stream;
AVStream *in_stream = ifmt_ctx->streams [i];
AVCodecParameters *in_codecpar = in_stream->codecpar;
// 只要音频流 A、视频流 V、和字幕流 S
if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
stream_mapping [i] = -1;
continue;
}
// 记录流个数
stream_mapping [i] = stream_index++;
// 创建输出的流文件
out_stream = avformat_new_stream (ofmt_ctx, NULL);
if (!out_stream) {
fprintf(stderr, "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}
// 从输入流中拷贝参数,如 sps、pps、dts 一类到输出流中
ret = avcodec_parameters_copy (out_stream->codecpar, in_codecpar);
if (ret < 0) {
fprintf(stderr, "Failed to copy codec parameters\n");
goto end;
}
out_stream->codecpar->codec_tag = 0;
}
// 打印输出流的基本信息
av_dump_format (ofmt_ctx, 0, out_filename, 1);

if (!(ofmt->flags & AVFMT_NOFILE)) {

ret = avio_open (&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
if (ret < 0) {
fprintf(stderr, "Could not open output file '% s'", out_filename);
goto end;
}
}

// 写入头信息
ret = avformat_write_header (ofmt_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "Error occurred when opening output file\n");
goto end;
}

while (1) {
AVStream *in_stream, *out_stream;

ret = av_read_frame (ifmt_ctx, &pkt);
if (ret < 0)
break;

in_stream = ifmt_ctx->streams [pkt.stream_index];
// 查看流信息是否正确 前面也已经标注了
if (pkt.stream_index >= stream_mapping_size ||
stream_mapping [pkt.stream_index] < 0) {
av_packet_unref (&pkt);
continue;
}
// 对应好输入流和输出流
pkt.stream_index = stream_mapping [pkt.stream_index];
out_stream = ofmt_ctx->streams [pkt.stream_index];

// 统一采样率刻度
pkt.pts = av_rescale_q_rnd (pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt.dts = av_rescale_q_rnd (pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt.duration = av_rescale_q (pkt.duration, in_stream->time_base, out_stream->time_base);
pkt.pos = -1;

// 写入数据
ret = av_interleaved_write_frame (ofmt_ctx, &pkt);
if (ret < 0) {
fprintf(stderr, "Error muxing packet\n");
break;
}
// 释放包资源
av_packet_unref (&pkt);
}

av_write_trailer (ofmt_ctx);

av_log (NULL, AV_LOG_INFO, "==========================\n");
av_log (NULL, AV_LOG_INFO, "Mp4 to flv success!\n");
av_log (NULL, AV_LOG_INFO, "==========================\n");
end:

avformat_close_input (&ifmt_ctx);

/* close output */
if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
avio_closep (&ofmt_ctx->pb);
avformat_free_context (ofmt_ctx);

av_freep (&stream_mapping);

if (ret < 0 && ret != AVERROR_EOF) {
fprintf(stderr, "Error occurred: % s\n", av_err2str (ret));
return 1;
}
return 0;
}

8、裁剪 mp4 文件

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#include <stdlib.h>
#include <libavutil/log.h>
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>

static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt, const char *tag)
{
AVRational *time_base = &fmt_ctx->streams [pkt->stream_index]->time_base;

printf("% s: pts:% s pts_time:% s dts:% s dts_time:% s duration:% s duration_time:% s stream_index:% d\n",
tag,
av_ts2str (pkt->pts), av_ts2timestr (pkt->pts, time_base),
av_ts2str (pkt->dts), av_ts2timestr (pkt->dts, time_base),
av_ts2str (pkt->duration), av_ts2timestr (pkt->duration, time_base),
pkt->stream_index);
}

int cut_video(double from_seconds, double end_seconds, const char* in_filename, const char* out_filename) {
AVOutputFormat *ofmt = NULL;
AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
AVPacket pkt;
int ret, i;

av_register_all ();

if ((ret = avformat_open_input (&ifmt_ctx, in_filename, 0, 0)) < 0) {
fprintf(stderr, "Could not open input file '% s'", in_filename);
goto end;
}

if ((ret = avformat_find_stream_info (ifmt_ctx, 0)) < 0) {
fprintf(stderr, "Failed to retrieve input stream information");
goto end;
}

av_dump_format (ifmt_ctx, 0, in_filename, 0);

avformat_alloc_output_context2 (&ofmt_ctx, NULL, NULL, out_filename);
if (!ofmt_ctx) {
fprintf(stderr, "Could not create output context\n");
ret = AVERROR_UNKNOWN;
goto end;
}

ofmt = ofmt_ctx->oformat;

for (i = 0; i < ifmt_ctx->nb_streams; i++) {
AVStream *in_stream = ifmt_ctx->streams [i];
AVStream *out_stream = avformat_new_stream (ofmt_ctx, in_stream->codec->codec);
if (!out_stream) {
fprintf(stderr, "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}

ret = avcodec_copy_context (out_stream->codec, in_stream->codec);
if (ret < 0) {
fprintf(stderr, "Failed to copy context from input to output stream codec context\n");
goto end;
}
out_stream->codec->codec_tag = 0;
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
av_dump_format (ofmt_ctx, 0, out_filename, 1);

if (!(ofmt->flags & AVFMT_NOFILE)) {
ret = avio_open (&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
if (ret < 0) {
fprintf(stderr, "Could not open output file '% s'", out_filename);
goto end;
}
}

ret = avformat_write_header (ofmt_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "Error occurred when opening output file\n");
goto end;
}

//int64_t start_from = 8*AV_TIME_BASE;
ret = av_seek_frame (ifmt_ctx, -1, from_seconds*AV_TIME_BASE, AVSEEK_FLAG_ANY);
if (ret < 0) {
fprintf(stderr, "Error seek\n");
goto end;
}

int64_t *dts_start_from = malloc(sizeof(int64_t) * ifmt_ctx->nb_streams);
memset(dts_start_from, 0, sizeof(int64_t) * ifmt_ctx->nb_streams);
int64_t *pts_start_from = malloc(sizeof(int64_t) * ifmt_ctx->nb_streams);
memset(pts_start_from, 0, sizeof(int64_t) * ifmt_ctx->nb_streams);

while (1) {
AVStream *in_stream, *out_stream;

ret = av_read_frame (ifmt_ctx, &pkt);
if (ret < 0)
break;

in_stream = ifmt_ctx->streams [pkt.stream_index];
out_stream = ofmt_ctx->streams [pkt.stream_index];

log_packet (ifmt_ctx, &pkt, "in");

if (av_q2d (in_stream->time_base) * pkt.pts > end_seconds) {
av_free_packet (&pkt);
break;
}

if (dts_start_from [pkt.stream_index] == 0) {
dts_start_from [pkt.stream_index] = pkt.dts;
printf("dts_start_from: % s\n", av_ts2str (dts_start_from [pkt.stream_index]));
}
if (pts_start_from [pkt.stream_index] == 0) {
pts_start_from [pkt.stream_index] = pkt.pts;
printf("pts_start_from: % s\n", av_ts2str (pts_start_from [pkt.stream_index]));
}

// Copy 过程
pkt.pts = av_rescale_q_rnd (pkt.pts - pts_start_from [pkt.stream_index], in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
pkt.dts = av_rescale_q_rnd (pkt.dts - dts_start_from [pkt.stream_index], in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
if (pkt.pts < 0) {
pkt.pts = 0;
}
if (pkt.dts < 0) {
pkt.dts = 0;
}
pkt.duration = (int) av_rescale_q ((int64_t) pkt.duration, in_stream->time_base, out_stream->time_base);
pkt.pos = -1;
log_packet (ofmt_ctx, &pkt, "out");
printf("\n");

ret = av_interleaved_write_frame (ofmt_ctx, &pkt);
if (ret < 0) {
fprintf(stderr, "Error muxing packet\n");
break;
}
av_free_packet (&pkt);
}
free(dts_start_from);
free(pts_start_from);

av_write_trailer (ofmt_ctx);
end:

avformat_close_input (&ifmt_ctx);

// 释放资源
if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
avio_closep (&ofmt_ctx->pb);
avformat_free_context (ofmt_ctx);

if (ret < 0 && ret != AVERROR_EOF) {
fprintf(stderr, "Error occurred: % s\n", av_err2str (ret));
return 1;
}

return 0;
}

int main(int argc, char *argv []) {
const char* inputFile = "C:\\Users\\changlin\\Desktop\\input.mp4";
const char* outputFile = "C:\\Users\\changlin\\Desktop\\output.mp4";
cut_video (6, 10, inputFile, outputFile);
return 0;
}

Visual Studio 下如何引入 FFmpeg

如果是 Visual Studio 开发环境,首先可以确定的是 Visual Studio2017 已经支持 CMake 了,通过编写 CMakeLists.txt 照样可以达到和 Mac 或者 Linux 一样的效果,不过常规方式引入也是 OK 的。

如果下载的 64 位的库,那么生成的目标平台也必须是 64 位的:

然后点击项目属性,把 FFmpeg 包含的头文件路径配置进去:

添加链接库目录:

添加需要使用的库,以使用静态库为例:

上述配置好以后,main.c 测试一下,OK

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <libavutil/avutil.h>

int main()
{
av_log_set_level (AV_LOG_INFO);
av_log (NULL, AV_LOG_INFO, "Hello FFmpeg!\n");
return 0;
}

CMake 如何引入 FFmpeg

虽然前面用到了 CMake 去引入 FFmpeg,但是是通过设置 C_FLAG/C_XX_FALG 属性来设置的链接库,这种方式就等于在链接的时候使用了 -L 选项,其实这算是一种方式,但却不是一种优雅的方式,很多时候 CMake 在设置了 target_link_libraries 却没有生效,其实是因为设置顺序的问题,对于一个目标文件,在 add_executable 之前就应该设置 link_directories,下面是一个引入 FFmpeg 日志库的 Demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
cmake_minimum_required (VERSION 3.8)

# 设置工程名称
project ("FFmpegAPI")

# 设置 FFmpeg 的头文件目录和库目录
set(ffmpeg_include_dir "D:\\FFmpeg\\ffmpeg-4.4-full_build-shared\\include")
set(ffmpeg_lib_dir "D:\\FFmpeg\\ffmpeg-4.4-full_build-shared\\lib")

# 添加头文件目录
include_directories(ffmpeg_include_dir)

# 添加链接库目录
link_directories(ffmpeg_lib_dir)

# 将源代码添加到此项目的可执行文件
add_executable (FFmpegAPI "FFmpegAPI.c" )

# 目标依赖库
target_link_libraries(
FFmpegAPI
avutil
)

所以应该在 add_executable 之前就应该设置 link_directories,这一点是非常重要的。

欢迎关注我的其它发布渠道