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
#include <libavutil.h>
av_log_set_level(AV_LOG_INFO);
av_log(NULL, AV_LOG_INFO, "...%s\n", "")
CMakeLists.txt
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
#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
#include <libavutil/log.h> #include <libavformat/avio.h> avpriv_io_move("srcFilePath", "destFilePath"); avpriv_io_delete("targetFilePath");
CMakeLists.txt同上
// 因为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 释放资源
// 因为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
#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流会清晰的告送解码器他需要的这些信息。
#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帧头部信息:
#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视频数据
#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
#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
#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
#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文件
#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
#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_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
,这一点是非常重要的。