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

1#include <libavutil/log.h>
2#include <libavformat/avio.h>
3
4avpriv_io_move("srcFilePath", "destFilePath");
5
6avpriv_io_delete("targetFilePath");

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,这一点是非常重要的。