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#include <libavutil.h>
2
3av_log_set_level(AV_LOG_INFO);
4
5av_log(NULL, AV_LOG_INFO, "...%s\n", "")
CMakeLists.txt
1cmake_minimum_required(VERSION 3.19)
2project(ffmpeg_log)
3
4# C++11
5set(CMAKE_CXX_STANDARD 11)
6
7# 指定目标于源文件
8add_executable(ffmpeg_log main.cpp)
9
10# 导入头文件
11include_directories("/usr/local/ffmpeg/include")
12
13# Import依赖库
14set(libs "/usr/local/ffmpeg/lib")
15set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${libs}")
16
17# 目标依赖库
18target_link_libraries(
19 ffmpeg_log
20 # ffmpeg_log这个目标依赖于libavutil.dylib
21 avutil
22)
main.cpp
1#include <iostream>
2
3extern "C"
4{
5 // 打印日志的头文件
6 #include <libavutil/log.h>
7}
8
9int main() {
10 std::cout << "Hello, World!" << std::endl;
11 // 设置最低打印的日志级别为INFO,超过这个级别的日志将不会被打印
12 av_log_set_level(AV_LOG_INFO);
13
14 // VERBOSE级别不打印
15 av_log(NULL, AV_LOG_VERBOSE, "Hello %s\n", "FFmpeg Log! AV_LOG_VERBOSE");
16
17 // DEBUG级别不打印
18 av_log(NULL, AV_LOG_DEBUG, "Hello %s\n", "FFmpeg Log! AV_LOG_DEBUG");
19
20 // 剩下的全都打印
21 av_log(NULL, AV_LOG_INFO, "Hello %s\n", "FFmpeg Log! AV_LOG_INFO");
22 av_log(NULL, AV_LOG_WARNING, "Hello %s\n", "FFmpeg Log! AV_LOG_WARNING");
23 av_log(NULL, AV_LOG_ERROR, "Hello %s\n", "FFmpeg Log! AV_LOG_ERROR");
24
25 return 0;
26}
2、文件删除与重命名
主要的API
CMakeLists.txt同上
1// 因为ffmpeg的日志打印也是标准/错误输出流
2#include <iostream>
3
4extern "C" {
5#include <libavutil/log.h>
6#include <libavformat/avio.h>
7}
8
9int main() {
10 av_log_set_level(AV_LOG_DEBUG);
11 av_log(NULL, AV_LOG_DEBUG, "start....\n");
12 int ret;
13
14 // 移动或者重命名文件
15 ret = avpriv_io_move("../README.md", "../my.md");
16 if (ret < 0) {
17 av_log(NULL, AV_LOG_ERROR, "move README.md failed\n");
18 return -1;
19 }
20 av_log(NULL, AV_LOG_DEBUG, "move README.md success\n");
21
22 // 删除文件
23 ret = avpriv_io_delete("../README.md");
24 if (ret < 0) {
25 av_log(NULL, AV_LOG_ERROR, "delete README.md failed\n");
26 return -1;
27 }
28
29 av_log(NULL, AV_LOG_DEBUG, "delete README.md success\n");
30 return 0;
31}
3、文件夹打开与读取
主要的API
avio_open_dir 打开文件夹
avio_read_dir 读取文件夹
avio_close_dir 释放资源
avio_free_directory_entry 释放资源
1// 因为ffmpeg的日志打印也是标准/错误输出流
2#include <iostream>
3
4extern "C"
5{
6#include <libavutil/log.h>
7#include <libavformat/avio.h>
8}
9
10int main(int argc, char const *argv[])
11{
12 av_log_set_level(AV_LOG_INFO);
13 // avio_open_dir();
14 // avio_read_dir();
15 // avio_close_dir();
16 // AVIODirContext 操作目录的上下文
17 // AVIODirEntry 目录项,存放文件名、文件大小等信息
18 AVIODirContext *context = NULL;
19 int ret = avio_open_dir(&context, "./", NULL);
20 if(ret < 0) {
21 av_log(NULL, AV_LOG_ERROR,"Open dir failed! ret = %d\n", ret);
22 avio_close_dir(&context); // 避免内存泄漏
23 return -1;
24 }
25
26 AVIODirEntry *entry = NULL;
27 for(;;){
28 int ret = avio_read_dir(context, &entry);
29 if(ret < 0) {
30 av_log(NULL, AV_LOG_ERROR,"Read dir failed! ret = %d\n", ret);
31 return -1;
32 }
33
34 // 到末尾说明应该退出
35 if(entry == NULL) break;
36
37 av_log(NULL, AV_LOG_INFO, "%12" PRId64" %s \n", entry -> size, entry -> name);
38 // 释放AVIODirEntry
39 avio_free_directory_entry(&entry);
40 }
41
42 avio_close_dir(&context);
43 return 0;
44}
4、音视频 Meta 信息
FFmpeg 处理的基本流程:
av_register_all
avformat_open_input
av_dump_format
avformat_close_input
1#include <iostream>
2
3extern "C" {
4#include <libavutil/log.h>
5#include <libavformat/avformat.h>
6#include <libavformat/avio.h>
7}
8
9int main()
10{
11 const char* inputFilePath = "C:\\Users\\changlin\\Desktop\\input.mp4";
12 av_log_set_level(AV_LOG_INFO);
13 av_register_all();
14 //av_register_input_format();
15 AVFormatContext* avContext = NULL;
16
17
18 // fmt:输入文件格式(默认根据后缀名解析) options: 命令行参数
19 int ret = avformat_open_input(&avContext, inputFilePath, NULL, NULL);
20 if (ret < 0)
21 {
22 av_log(NULL, AV_LOG_ERROR, "Open file failed,ret = %d\n", ret);
23 return -1;
24 }
25
26 // 打印关于输入或输出格式的详细信息
27 // param2 需要解析的流索引号,例如0是视频流,1是音频流
28 // param4 指定的视音频上下文是输入流(0)还是输出流(1)
29 av_dump_format(avContext, 0, inputFilePath, 0);
30 avformat_close_input(&avContext);
31 return 0;
32}
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#pragma once
2#include <cstdint>
3/*
4 * szAdtsHeader ADTS头缓冲区
5 * dataLength 数据长度,也就是Package.size + 7
6 */
7void adts_header(char* szAdtsHeader, int dataLength) {
8 int profile = 2; // AAC LC
9 int freqIdx = 4; // 44.1KHz
10 int chanCfg = 2; // CPE
11
12 // fill in ADTS data
13 szAdtsHeader[0] = (char)0xFF;
14 szAdtsHeader[1] = (char)0xF9;
15 szAdtsHeader[2] = (char)(((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
16 szAdtsHeader[3] = (char)(((chanCfg & 3) << 6) + (dataLength >> 11));
17 szAdtsHeader[4] = (char)((dataLength & 0x7FF) >> 3);
18 szAdtsHeader[5] = (char)(((dataLength & 7) << 5) + 0x1F);
19 szAdtsHeader[6] = (char)0xFC;
20}
接下来抽取音频,并给给每帧AAC数据都添加上AAC帧头部信息:
1#include <iostream>
2#include <stdio.h>
3
4extern "C" {
5#include <libavutil/log.h>
6#include <libavformat/avformat.h>
7#include <libavformat/avio.h>
8#include <libavcodec/packet.h>
9#include <libavcodec/avcodec.h>
10#include "adts_header.h"
11}
12
13int main()
14{
15 const char* inputFilePath = "C:\\Users\\changlin\\Desktop\\input.mp4";
16 const char* outputFilePath = "C:\\Users\\changlin\\Desktop\\output.aac";
17 av_log_set_level(AV_LOG_INFO);
18 av_register_all();
19 //av_register_input_format();
20 AVFormatContext* avContext = NULL;
21
22 // fmt:输入文件格式(默认根据后缀名解析) options: 命令行参数
23 int ret = avformat_open_input(&avContext, inputFilePath, NULL, NULL);
24 if (ret < 0)
25 {
26 av_log(NULL, AV_LOG_ERROR, "Open file failed,ret = %d\n", ret);
27 return -1;
28 }
29 // 打印出基本信息
30 av_dump_format(avContext, 0, inputFilePath, 0);
31
32 FILE* outputFile = fopen(outputFilePath, "wb+");
33 if(outputFile == NULL)
34 {
35 av_log(NULL, AV_LOG_ERROR, "Open outFile failed!\n");
36 avformat_close_input(&avContext);
37 return -1;
38 }
39
40 int audio_index = 0;
41 // 寻找最佳音频流
42 audio_index = av_find_best_stream(avContext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
43 if(audio_index < 0)
44 {
45 av_log(NULL, AV_LOG_ERROR, "Can't find the best stream!\n");
46 fclose(outputFile);
47 return -1;
48 }
49 AVPacket av_packet;
50 // 初始化包
51 av_init_packet(&av_packet);
52 size_t writeLength;
53 while(av_read_frame(avContext, &av_packet) >= 0)
54 {
55 if(audio_index == av_packet.stream_index)
56 {
57 // 添加ADTS头信息
58 char adts_header_buf[7];
59 adts_header(adts_header_buf, av_packet.size + 7);
60 fwrite(adts_header_buf, 1, 7, outputFile);
61
62 // 写入AAC帧
63 writeLength = fwrite(av_packet.data, 1, av_packet.size, outputFile);
64 if(writeLength != av_packet.size)
65 {
66 av_log(NULL, AV_LOG_WARNING, "write length not equal packet.size\n");
67 }
68 }
69 // 释放上次使用的包
70 av_packet_unref(&av_packet);
71 }
72 avformat_close_input(&avContext);
73 if(outputFile)
74 {
75 fclose(outputFile);
76 }
77 return 0;
78}
6、抽取H264视频数据
1#include <iostream>
2
3extern "C"
4{
5#include <libavutil/log.h>
6#include <libavformat/avio.h>
7#include <libavformat/avformat.h>
8#include <libavcodec/avcodec.h>
9#include <libavfilter/avfilter.h>
10#include <stdio.h>
11#include "extr_video.h"
12}
13
14/**
15 * 概念一:StartCode
16 * 帧与帧之间如何区分?
17 * - 1、特征码,比如解析到某个特征码的时候就知道了这一帧数据结束了。
18 * - 2、头部添加帧长度,知道了此帧有多长,就按照长度解析即可。
19 * H264采用的就是第一种,StartCode这个概念就是特征码。
20 *
21 * 概念二:SPS/PPS
22 * 解码用到的视频参数,视频帧的宽高、帧率等等参数。
23 * 如果是相同的参数,比如宽高、帧率一直不变,那么只需要一份SPS/PPS就可以了。
24 * 但是如果是变化的场景,比如直播流,参数会发生变化,每次发生变化的时候都会更新SPS/PPS,
25 * 解码器在参数发生变化的时候就会重新初始化解码器,从而继续正确的解码数据。另外在直播流等
26 * 通过网络传输的场景下容易发生丢包,往往会在关键帧设置SPS/PPS,避免错误解码等情况的出现。
27 * 但是这样会导致网络数据传输量增加,增加网络负载吗?其实不会,SPS/PPS只有几个字节或者十
28 * 几个字节,占用空间非常小,对于整个视频流来说基本上可以忽略不计。
29 *
30 * 通过FFmpeg如何获取SPS/PPS呢?
31 * codec —> extratata
32 */
33
34/**
35 * @brief 抽取H264数据
36 *
37 * @param argc
38 * @param argv
39 * @return int
40 */
41int main(int argc, char const *argv[])
42{
43 const char *intputPath = "/Users/zchanglin/Downloads/input.mp4";
44 const char *outputPath = "/Users/zchanglin/Downloads/output.h264";
45
46 av_register_all();
47
48 AVFormatContext *avContext;
49 int open_ret = avformat_open_input(&avContext, intputPath, NULL, NULL);
50
51 if (open_ret < 0)
52 {
53 av_log(NULL, AV_LOG_ERROR, "Open input file failed!\n");
54 return -1;
55 }
56 // 打印视频信息
57 av_dump_format(avContext, NULL, intputPath, NULL);
58
59 // 打开输出文件
60 FILE *outputFile = fopen(outputPath, "wb");
61 if (outputPath == NULL)
62 {
63 av_log(NULL, AV_LOG_ERROR, "Open outputFile failed!\n");
64 avformat_close_input(&avContext);
65 return -1;
66 }
67
68 AVPacket av_package;
69 av_init_packet(&av_package);
70 av_package.size = 0;
71 av_package.data = NULL;
72
73 int bestStreamIndex = av_find_best_stream(avContext,
74 AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
75 if (bestStreamIndex < 0)
76 {
77 av_log(NULL, AV_LOG_ERROR, "Find best stream failed!\n");
78 avformat_close_input(&avContext);
79 fclose(outputFile);
80 return -1;
81 }
82
83 // 拿到视频流的索引号
84 while(av_read_frame(avContext, &av_package) >= 0){
85 // 添加SPS/PPS数据
86 if(av_package.stream_index == bestStreamIndex){
87 h264_mp4toannexb(avContext, &av_package, outputFile);
88 }
89 av_packet_unref(&av_package);
90 }
91
92 avformat_close_input(&avContext);
93 if (outputFile != NULL)
94 {
95 fclose(outputFile);
96 }
97
98 av_log(NULL, AV_LOG_INFO, "==========================\n");
99 av_log(NULL, AV_LOG_INFO, "Detach video data success!\n");
100 av_log(NULL, AV_LOG_INFO, "==========================\n");
101
102 return 0;
103}
extr_video.h
1#ifndef __EXTR_VIDEO_H__
2#define __EXTR_VIDEO_H__
3#include <stdio.h>
4#include <libavutil/log.h>
5#include <libavformat/avio.h>
6#include <libavformat/avformat.h>
7
8#ifndef AV_WB32
9#define AV_WB32(p, val) \
10 do \
11 { \
12 uint32_t d = (val); \
13 ((uint8_t *)(p))[3] = (d); \
14 ((uint8_t *)(p))[2] = (d) >> 8; \
15 ((uint8_t *)(p))[1] = (d) >> 16; \
16 ((uint8_t *)(p))[0] = (d) >> 24; \
17 } while (0)
18#endif
19
20#ifndef AV_RB16
21#define AV_RB16(x) \
22 ((((const uint8_t *)(x))[0] << 8) | \
23 ((const uint8_t *)(x))[1])
24#endif
25
26static int alloc_and_copy(AVPacket *out,
27 const uint8_t *sps_pps, uint32_t sps_pps_size,
28 const uint8_t *in, uint32_t in_size);
29
30int h264_extradata_to_annexb(const uint8_t *codec_extradata,
31 const int codec_extradata_size,
32 AVPacket *out_extradata, int padding);
33
34int h264_mp4toannexb(AVFormatContext *fmt_ctx, AVPacket *in, FILE *dst_fd);
35
36#endif //!__EXTR_VIDEO_H__
extr_video.c
1#include "extr_video.h"
2
3static int alloc_and_copy(AVPacket *out,
4 const uint8_t *sps_pps, uint32_t sps_pps_size,
5 const uint8_t *in, uint32_t in_size)
6{
7 uint32_t offset = out->size;
8 uint8_t nal_header_size = offset ? 3 : 4;
9 int err;
10
11 err = av_grow_packet(out, sps_pps_size + in_size + nal_header_size);
12 if (err < 0)
13 return err;
14
15 if (sps_pps)
16 memcpy(out->data + offset, sps_pps, sps_pps_size);
17 memcpy(out->data + sps_pps_size + nal_header_size + offset, in, in_size);
18 if (!offset)
19 {
20 AV_WB32(out->data + sps_pps_size, 1);
21 }
22 else
23 {
24 (out->data + offset + sps_pps_size)[0] =
25 (out->data + offset + sps_pps_size)[1] = 0;
26 (out->data + offset + sps_pps_size)[2] = 1;
27 }
28
29 return 0;
30}
31
32int h264_extradata_to_annexb(const uint8_t *codec_extradata, const int codec_extradata_size, AVPacket *out_extradata, int padding)
33{
34 uint16_t unit_size;
35 uint64_t total_size = 0;
36 uint8_t *out = NULL, unit_nb, sps_done = 0,
37 sps_seen = 0, pps_seen = 0, sps_offset = 0, pps_offset = 0;
38 const uint8_t *extradata = codec_extradata + 4;
39 static const uint8_t nalu_header[4] = {0, 0, 0, 1};
40 int length_size = (*extradata++ & 0x3) + 1; // retrieve length coded size, 用于指示表示编码数据长度所需字节数
41
42 sps_offset = pps_offset = -1;
43
44 /* retrieve sps and pps unit(s) */
45 unit_nb = *extradata++ & 0x1f; /* number of sps unit(s) */
46 if (!unit_nb)
47 {
48 goto pps;
49 }
50 else
51 {
52 sps_offset = 0;
53 sps_seen = 1;
54 }
55
56 while (unit_nb--)
57 {
58 int err;
59
60 unit_size = AV_RB16(extradata);
61 total_size += unit_size + 4;
62 if (total_size > INT_MAX - padding)
63 {
64 av_log(NULL, AV_LOG_ERROR,
65 "Too big extradata size, corrupted stream or invalid MP4/AVCC bitstream\n");
66 av_free(out);
67 return AVERROR(EINVAL);
68 }
69 if (extradata + 2 + unit_size > codec_extradata + codec_extradata_size)
70 {
71 av_log(NULL, AV_LOG_ERROR, "Packet header is not contained in global extradata, "
72 "corrupted stream or invalid MP4/AVCC bitstream\n");
73 av_free(out);
74 return AVERROR(EINVAL);
75 }
76 if ((err = av_reallocp(&out, total_size + padding)) < 0)
77 return err;
78 memcpy(out + total_size - unit_size - 4, nalu_header, 4);
79 memcpy(out + total_size - unit_size, extradata + 2, unit_size);
80 extradata += 2 + unit_size;
81 pps:
82 if (!unit_nb && !sps_done++)
83 {
84 unit_nb = *extradata++; /* number of pps unit(s) */
85 if (unit_nb)
86 {
87 pps_offset = total_size;
88 pps_seen = 1;
89 }
90 }
91 }
92
93 if (out)
94 memset(out + total_size, 0, padding);
95
96 if (!sps_seen)
97 av_log(NULL, AV_LOG_WARNING,
98 "Warning: SPS NALU missing or invalid. "
99 "The resulting stream may not play.\n");
100
101 if (!pps_seen)
102 av_log(NULL, AV_LOG_WARNING,
103 "Warning: PPS NALU missing or invalid. "
104 "The resulting stream may not play.\n");
105
106 out_extradata->data = out;
107 out_extradata->size = total_size;
108
109 return length_size;
110}
111
112/**
113 * @brief 设置SPS/PPS
114 *
115 * @param fmt_ctx
116 * @param in
117 * @param dst_fd
118 * @return int
119 */
120int h264_mp4toannexb(AVFormatContext *fmt_ctx, AVPacket *in, FILE *dst_fd)
121{
122
123 AVPacket *out = NULL;
124 AVPacket spspps_pkt;
125
126 int len;
127 uint8_t unit_type;
128 int32_t nal_size;
129 uint32_t cumul_size = 0;
130 const uint8_t *buf;
131 const uint8_t *buf_end;
132 int buf_size;
133 int ret = 0, i;
134
135 out = av_packet_alloc();
136
137 buf = in->data;
138 buf_size = in->size;
139 buf_end = in->data + in->size;
140
141 do
142 {
143 ret = AVERROR(EINVAL);
144 if (buf + 4 > buf_end)
145 goto fail;
146
147 for (nal_size = 0, i = 0; i < 4; i++)
148 nal_size = (nal_size << 8) | buf[i];
149
150 buf += 4;
151 unit_type = *buf & 0x1f;
152
153 if (nal_size > buf_end - buf || nal_size < 0)
154 goto fail;
155
156 if (unit_type == 5)
157 {
158
159 h264_extradata_to_annexb(fmt_ctx->streams[in->stream_index]->codec->extradata,
160 fmt_ctx->streams[in->stream_index]->codec->extradata_size,
161 &spspps_pkt,
162 AV_INPUT_BUFFER_PADDING_SIZE);
163
164 if ((ret = alloc_and_copy(out,
165 spspps_pkt.data, spspps_pkt.size,
166 buf, nal_size)) < 0)
167 goto fail;
168
169 }else {
170 if ((ret = alloc_and_copy(out, NULL, 0, buf, nal_size)) < 0)
171 goto fail;
172 }
173
174 len = fwrite(out->data, 1, out->size, dst_fd);
175 if (len != out->size)
176 {
177 av_log(NULL, AV_LOG_DEBUG, "warning, length of writed data isn't equal pkt.size(%d, %d)\n",
178 len,
179 out->size);
180 }
181 fflush(dst_fd);
182
183 next_nal:
184 buf += nal_size;
185 cumul_size += nal_size + 4; //s->length_size;
186 } while (cumul_size < buf_size);
187fail:
188 av_packet_free(&out);
189
190 return ret;
191}
7、mp4格式转flv
1#include <libavutil/log.h>
2#include <libavutil/timestamp.h>
3#include <libavformat/avformat.h>
4
5int main(int argc, char **argv)
6{
7 // 创建输出的format
8 AVOutputFormat *ofmt = NULL;
9 // 创建两个上下文
10 AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
11 // 创建packet
12 AVPacket pkt; v
13 const char *in_filename, *out_filename;
14 int ret, i;
15 int stream_index = 0;
16 int *stream_mapping = NULL;
17 int stream_mapping_size = 0;
18
19 in_filename = "/Users/zchanglin/Downloads/input.mp4";
20 out_filename = "/Users/zchanglin/Downloads/output.flv";
21
22
23 if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
24 fprintf(stderr, "Could not open input file '%s'", in_filename);
25 goto end;
26 }
27 // 如果当前输入文件没有任何流的信息直接返回
28 if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
29 fprintf(stderr, "Failed to retrieve input stream information");
30 goto end;
31 }
32 // 输出输出文件基本信息
33 av_dump_format(ifmt_ctx, 0, in_filename, 0);
34
35 // 创建输出文件上下文,是ffpmeg根据out_filename自主生成对应的
36 avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
37 if (!ofmt_ctx) {
38 fprintf(stderr, "Could not create output context\n");
39 ret = AVERROR_UNKNOWN;
40 goto end;
41 }
42 // 获取输入文件流个数和大小并申请空间
43 stream_mapping_size = ifmt_ctx->nb_streams;
44 stream_mapping = av_mallocz_array(stream_mapping_size, sizeof(*stream_mapping));
45 if (!stream_mapping) {
46 ret = AVERROR(ENOMEM);
47 goto end;
48 }
49
50 ofmt = ofmt_ctx->oformat;
51
52 for (i = 0; i < ifmt_ctx->nb_streams; i++) {
53 AVStream *out_stream;
54 AVStream *in_stream = ifmt_ctx->streams[i];
55 AVCodecParameters *in_codecpar = in_stream->codecpar;
56 // 只要音频流A、视频流V、和字幕流S
57 if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
58 in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
59 in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
60 stream_mapping[i] = -1;
61 continue;
62 }
63 // 记录流个数
64 stream_mapping[i] = stream_index++;
65 // 创建输出的流文件
66 out_stream = avformat_new_stream(ofmt_ctx, NULL);
67 if (!out_stream) {
68 fprintf(stderr, "Failed allocating output stream\n");
69 ret = AVERROR_UNKNOWN;
70 goto end;
71 }
72 // 从输入流中拷贝参数,如sps、pps、dts 一类到输出流中
73 ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
74 if (ret < 0) {
75 fprintf(stderr, "Failed to copy codec parameters\n");
76 goto end;
77 }
78 out_stream->codecpar->codec_tag = 0;
79 }
80 // 打印输出流的基本信息
81 av_dump_format(ofmt_ctx, 0, out_filename, 1);
82
83 if (!(ofmt->flags & AVFMT_NOFILE)) {
84
85 ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
86 if (ret < 0) {
87 fprintf(stderr, "Could not open output file '%s'", out_filename);
88 goto end;
89 }
90 }
91
92 // 写入头信息
93 ret = avformat_write_header(ofmt_ctx, NULL);
94 if (ret < 0) {
95 fprintf(stderr, "Error occurred when opening output file\n");
96 goto end;
97 }
98
99 while (1) {
100 AVStream *in_stream, *out_stream;
101
102 ret = av_read_frame(ifmt_ctx, &pkt);
103 if (ret < 0)
104 break;
105
106 in_stream = ifmt_ctx->streams[pkt.stream_index];
107 // 查看流信息是否正确 前面也已经标注了
108 if (pkt.stream_index >= stream_mapping_size ||
109 stream_mapping[pkt.stream_index] < 0) {
110 av_packet_unref(&pkt);
111 continue;
112 }
113 // 对应好输入流和输出流
114 pkt.stream_index = stream_mapping[pkt.stream_index];
115 out_stream = ofmt_ctx->streams[pkt.stream_index];
116
117 // 统一采样率刻度
118 pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
119 pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
120 pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
121 pkt.pos = -1;
122
123 // 写入数据
124 ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
125 if (ret < 0) {
126 fprintf(stderr, "Error muxing packet\n");
127 break;
128 }
129 // 释放包资源
130 av_packet_unref(&pkt);
131 }
132
133 av_write_trailer(ofmt_ctx);
134
135 av_log(NULL, AV_LOG_INFO, "==========================\n");
136 av_log(NULL, AV_LOG_INFO, "Mp4 to flv success!\n");
137 av_log(NULL, AV_LOG_INFO, "==========================\n");
138end:
139
140 avformat_close_input(&ifmt_ctx);
141
142 /* close output */
143 if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
144 avio_closep(&ofmt_ctx->pb);
145 avformat_free_context(ofmt_ctx);
146
147 av_freep(&stream_mapping);
148
149 if (ret < 0 && ret != AVERROR_EOF) {
150 fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
151 return 1;
152 }
153 return 0;
154}
8、裁剪mp4文件
1#include <stdlib.h>
2#include <libavutil/log.h>
3#include <libavutil/timestamp.h>
4#include <libavformat/avformat.h>
5
6static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt, const char *tag)
7{
8 AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;
9
10 printf("%s: pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",
11 tag,
12 av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base),
13 av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base),
14 av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base),
15 pkt->stream_index);
16}
17
18int cut_video(double from_seconds, double end_seconds, const char* in_filename, const char* out_filename) {
19 AVOutputFormat *ofmt = NULL;
20 AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
21 AVPacket pkt;
22 int ret, i;
23
24 av_register_all();
25
26 if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
27 fprintf(stderr, "Could not open input file '%s'", in_filename);
28 goto end;
29 }
30
31 if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
32 fprintf(stderr, "Failed to retrieve input stream information");
33 goto end;
34 }
35
36 av_dump_format(ifmt_ctx, 0, in_filename, 0);
37
38 avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
39 if (!ofmt_ctx) {
40 fprintf(stderr, "Could not create output context\n");
41 ret = AVERROR_UNKNOWN;
42 goto end;
43 }
44
45 ofmt = ofmt_ctx->oformat;
46
47 for (i = 0; i < ifmt_ctx->nb_streams; i++) {
48 AVStream *in_stream = ifmt_ctx->streams[i];
49 AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);
50 if (!out_stream) {
51 fprintf(stderr, "Failed allocating output stream\n");
52 ret = AVERROR_UNKNOWN;
53 goto end;
54 }
55
56 ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
57 if (ret < 0) {
58 fprintf(stderr, "Failed to copy context from input to output stream codec context\n");
59 goto end;
60 }
61 out_stream->codec->codec_tag = 0;
62 if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
63 out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
64 }
65 av_dump_format(ofmt_ctx, 0, out_filename, 1);
66
67 if (!(ofmt->flags & AVFMT_NOFILE)) {
68 ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
69 if (ret < 0) {
70 fprintf(stderr, "Could not open output file '%s'", out_filename);
71 goto end;
72 }
73 }
74
75 ret = avformat_write_header(ofmt_ctx, NULL);
76 if (ret < 0) {
77 fprintf(stderr, "Error occurred when opening output file\n");
78 goto end;
79 }
80
81 // int64_t start_from = 8*AV_TIME_BASE;
82 ret = av_seek_frame(ifmt_ctx, -1, from_seconds*AV_TIME_BASE, AVSEEK_FLAG_ANY);
83 if (ret < 0) {
84 fprintf(stderr, "Error seek\n");
85 goto end;
86 }
87
88 int64_t *dts_start_from = malloc(sizeof(int64_t) * ifmt_ctx->nb_streams);
89 memset(dts_start_from, 0, sizeof(int64_t) * ifmt_ctx->nb_streams);
90 int64_t *pts_start_from = malloc(sizeof(int64_t) * ifmt_ctx->nb_streams);
91 memset(pts_start_from, 0, sizeof(int64_t) * ifmt_ctx->nb_streams);
92
93 while (1) {
94 AVStream *in_stream, *out_stream;
95
96 ret = av_read_frame(ifmt_ctx, &pkt);
97 if (ret < 0)
98 break;
99
100 in_stream = ifmt_ctx->streams[pkt.stream_index];
101 out_stream = ofmt_ctx->streams[pkt.stream_index];
102
103 log_packet(ifmt_ctx, &pkt, "in");
104
105 if (av_q2d(in_stream->time_base) * pkt.pts > end_seconds) {
106 av_free_packet(&pkt);
107 break;
108 }
109
110 if (dts_start_from[pkt.stream_index] == 0) {
111 dts_start_from[pkt.stream_index] = pkt.dts;
112 printf("dts_start_from: %s\n", av_ts2str(dts_start_from[pkt.stream_index]));
113 }
114 if (pts_start_from[pkt.stream_index] == 0) {
115 pts_start_from[pkt.stream_index] = pkt.pts;
116 printf("pts_start_from: %s\n", av_ts2str(pts_start_from[pkt.stream_index]));
117 }
118
119 // Copy过程
120 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);
121 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);
122 if (pkt.pts < 0) {
123 pkt.pts = 0;
124 }
125 if (pkt.dts < 0) {
126 pkt.dts = 0;
127 }
128 pkt.duration = (int)av_rescale_q((int64_t)pkt.duration, in_stream->time_base, out_stream->time_base);
129 pkt.pos = -1;
130 log_packet(ofmt_ctx, &pkt, "out");
131 printf("\n");
132
133 ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
134 if (ret < 0) {
135 fprintf(stderr, "Error muxing packet\n");
136 break;
137 }
138 av_free_packet(&pkt);
139 }
140 free(dts_start_from);
141 free(pts_start_from);
142
143 av_write_trailer(ofmt_ctx);
144end:
145
146 avformat_close_input(&ifmt_ctx);
147
148 // 释放资源
149 if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
150 avio_closep(&ofmt_ctx->pb);
151 avformat_free_context(ofmt_ctx);
152
153 if (ret < 0 && ret != AVERROR_EOF) {
154 fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
155 return 1;
156 }
157
158 return 0;
159}
160
161int main(int argc, char *argv[]) {
162 const char* inputFile = "C:\\Users\\changlin\\Desktop\\input.mp4";
163 const char* outputFile = "C:\\Users\\changlin\\Desktop\\output.mp4";
164 cut_video(6, 10, inputFile, outputFile);
165 return 0;
166}
Visual Studio下如何引入FFmpeg
如果是 Visual Studio 开发环境,首先可以确定的是 Visual Studio2017 已经支持 CMake 了,通过编写CMakeLists.txt 照样可以达到和 Mac 或者 Linux 一样的效果,不过常规方式引入也是OK的。
如果下载的64位的库,那么生成的目标平台也必须是64位的:
然后点击项目属性,把FFmpeg包含的头文件路径配置进去:
添加链接库目录:
添加需要使用的库,以使用静态库为例:
上述配置好以后,main.c测试一下,OK
1#include <stdio.h>
2#include <libavutil/avutil.h>
3
4int main()
5{
6 av_log_set_level(AV_LOG_INFO);
7 av_log(NULL, AV_LOG_INFO, "Hello FFmpeg!\n");
8 return 0;
9}
CMake如何引入FFmpeg
虽然前面用到了CMake去引入FFmpeg,但是是通过设置 C_FLAG/C_XX_FALG
属性来设置的链接库,这种方式就等于在链接的时候使用了-L
选项,其实这算是一种方式,但却不是一种优雅的方式,很多时候CMake在设置了 target_link_libraries
却没有生效,其实是因为设置顺序的问题,对于一个目标文件,在 add_executable
之前就应该设置link_directories
,下面是一个引入FFmpeg日志库的 Demo:
1cmake_minimum_required (VERSION 3.8)
2
3# 设置工程名称
4project ("FFmpegAPI")
5
6# 设置FFmpeg的头文件目录和库目录
7set(ffmpeg_include_dir "D:\\FFmpeg\\ffmpeg-4.4-full_build-shared\\include")
8set(ffmpeg_lib_dir "D:\\FFmpeg\\ffmpeg-4.4-full_build-shared\\lib")
9
10# 添加头文件目录
11include_directories(ffmpeg_include_dir)
12
13# 添加链接库目录
14link_directories(ffmpeg_lib_dir)
15
16# 将源代码添加到此项目的可执行文件
17add_executable (FFmpegAPI "FFmpegAPI.c" )
18
19# 目标依赖库
20target_link_libraries(
21 FFmpegAPI
22 avutil
23)
所以应该在
add_executable
之前就应该设置link_directories
,这一点是非常重要的。