编辑
2021-08-03
客户端技术
00
请注意,本文编写于 782 天前,最后修改于 105 天前,其中某些信息可能已经过时。

目录

FFmpeg 处理流数据基本概念
FFmpeg 模块说明
FFmpeg API Demo
1、日志打印
2、文件删除与重命名
3、文件夹打开与读取
4、音视频 Meta 信息
5、抽取AAC音频数据
6、抽取H264视频数据
7、mp4格式转flv
8、裁剪mp4文件
Visual Studio下如何引入FFmpeg
CMake如何引入FFmpeg

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

c
#include <libavutil.h> av_log_set_level(AV_LOG_INFO); av_log(NULL, AV_LOG_INFO, "...%s\n", "")

CMakeLists.txt

cmake
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

cpp
#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

cpp
#include <libavutil/log.h> #include <libavformat/avio.h> avpriv_io_move("srcFilePath", "destFilePath"); avpriv_io_delete("targetFilePath");

CMakeLists.txt同上

cpp
// 因为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 释放资源

cpp
// 因为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

cpp
#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流会清晰的告送解码器他需要的这些信息。

c
#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帧头部信息:

cpp
#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视频数据

c
#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

c
#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

c
#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

c
#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文件

c
#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

c
#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:

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

本文作者:Tim

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!