ffmpeg入——安装
Fmpeg地址
FFmpeg源码地址:GitHub - FFmpeg/FFmpeg: Mirror of https://git.ffmpeg.org/ffmpeg.git
FFmpeg可执行文件地址:Download FFmpeg
Windows平台
Windows平台下载解压后如图所示(文件名称以-share结尾的是开发库)
FFmpeg库介绍
◆ ffmpeg.exe:用于音视频转码, 也可以从url/现场音频/视频源抓取输入源。
◆ ffplay.exe:一个非常简单和可移植的媒体播放器,使用FFmpeg库和SDL库。
◆ ffprobe.exe:查看多媒体文件的信息
将FFmpeg可执行文件加入系统环境变量,如下图所示:
Linux
ffmpeg官方 GitHub - FFmpeg/FFmpeg: Mirror of https://git.ffmpeg.org/ffmpeg.git
下载压缩包 或者 直接clone
- 直接clone:git clone https://github.com/FFmpeg/FFmpeg.git
- 下载完成,执行./configure
如果出现了错误 nasm/yasm not found or too old. Use --disable-x86asm for a crippled build.
。
这是因为 FFMPEG为了提高编译速度,使用了汇编指令,如MMX和SSE等。如果系统中没有yasm
指令的话,就会该错误。
1)下载:wget http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz
2)解压:tar zxvf yasm-1.3.0.tar.gz
3)切换路径: cd yasm-1.3.0
4)执行配置: ./configure
5)编译:make
6)安装:make install
3.make && sudo make install
测试代码:
#include<stdio.h>
#include<libavutil/log.h>int main()
{av_log_set_level(AV_LOG_DEBUG); //设置日志等级av_log(NULL, AV_LOG_INFO, "Hello~~~\n"); // INFO等级,输出helloreturn 0;
}
编译链接: gcc -o ff_log ff_log.c -lavutil
或者 gcc -o ff_log ff_log.c `pkg-config --cflags --libs libavutil`
(“pkg-config工具:1)会检查库的版本号。如果所需要的库的版本不满足要求,它会打印出错误信息,避免链接错误版本的库文件;2)会获得编译预处理参数,如宏定义,头文件的位置。3)还会获得链接参数,如库及依赖的其它库的位置,文件名及其它一些连接参数)
选项--cflags 它是用来指定程序在编译时所需要头文件所在的目录, 选项 --libs则是指定程序在链接时所需要的动态链接库的目录。
ffmpeg——log日志系统
log日志位于libavutil模块,log level的声明位于log.h
日志级别:
// 静默模式,不打印日志
#define AV_LOG_QUIET -8// 立即崩溃,退出程序
#define AV_LOG_PANIC 0// 严重出错,无法修复
#define AV_LOG_FATAL 8// 程序出错
#define AV_LOG_ERROR 16// 警告
#define AV_LOG_WARNING 24// 信息
#define AV_LOG_INFO 32// 详细信息
#define AV_LOG_VERBOSE 40// 调试日志
#define AV_LOG_DEBUG 48// 跟踪日志
#define AV_LOG_TRACE 56
设置日志等级的方法set_log_level()位于log.c,其中av_log_level是个静态全局变量,具体如下
static int av_log_level = AV_LOG_INFO;void av_log_set_level(int level)
{av_log_level = level;
}
常用的打印日志方法av_log(),声明位于log.h:
/** * 发送特定消息到小于等于当前等级的日志,默认全部发送到stderr** @param avcl 指向任意结构体的指针,结构体第一个变量为AVClass或NULL* @param level 日志等级* @param fmt 字符串格式*/
void av_log(void *avcl, int level, const char *fmt, ...) av_printf_format(3, 4);
ffmpeg——文件的删除与重命名
- avpriv_io_delete() //删除
- avpriv_io_move() //重命名
编译链接: gcc xxx.c -o xxx -lavformat -lavutil
#include <libavformat/avformat.h>int main(int argc,char *argv[])
{int ret;set_av_log_level(AV_LOG_DEBUG);ret = avpriv_io_move("111.txt","222.txt");if(ret < 0){av_log(NULL,AV_LOG_ERROR,"Failed to move file:%s\n","111.txt");return -1;}ret = avpriv_io_delete("./mytestfile.txt");if(ret < 0){av_log(NULL,AV_LOG_ERROR,"Failed to delete file:%s\n","mytestfile.txt");return -1;}av_log(NULL,AV_LOG_DEBUG,"Success to delete file:%s\n","mytestfile.txt");return 0;
}
ffmpeg——目录操作
重要结构体:
AVIODirContext 操作目录的上下文(记录avio_open_dir打开目录信息)
AVIODirEntry 目录项。用于存放文件名,文件大小等信息
操作目录API:
- avio_open_dir()
- avio_read_dir()//读取目录中每一个文件的属性
- avio_close_dir()
编译链接: gcc xxx.c -o xxx -lavformat -lavutil
// 实现简单的ls命令#include <libavutil/log.h>
#include <libavformat/avformat.h>int main(int argc,char* argv[])
{int ret;AVIODirContext *ctx = NULL;AVIODirEntry *entry = NULL;av_log_set_level(AV_LOG_INFO);ret = avio_open_dir(&ctx,"./",NULL);if(ret < 0){av_log(NULL,AV_LOG_ERROR,"Cant open dir:%s\n",av_err2str(ret));return -1;}while(1){ret = avio_read_dir(ctx,&entry);//malloc entryif(ret < 0){av_log(NULL,AV_LOG_ERROR,"Cant read dir:%s\n",av_err2str(ret));avio_close_dir(&ctx);return -1;}if(!entry){//the endbreak;}av_log(NULL,AV_LOG_INFO,"%12"PRId64" %s\n",entry->size,entry->name);avio_free_directory_entry(&entry);//free entry}avio_close_dir(&ctx);return 0;
}
ffmpeg——重要的结构体
- AVIOContext(I/O上下文) // 实现文件或者流的I/O操作
- AVFormatContext(封装格式上下文) // 管理媒体文件的格式和封装信息
- AVCodecContext(编解码器上下文) // 控制和配置音视频编解码器的行为
- AVCodecParameters (编解码器参数) // 保存音视频流的基本参数信息
- AVStream(音视频流) // 描述音视频流的属性
- AVPacket (数据包) // 用于在媒体文件、流之间传递数据
- AVFrame(帧) // 存储和传递音视频数据
- AVDictionary(字典) // 存储配置选项和元数据
- AVFilterContext(滤镜上下文) // 处理音视频数据,应用滤镜效果。
- SwsContext(图像转换上下文) // 进行图像处理和转换
- SwrContext(音频重采样上下文) // 进行音频处理和转换
AVIOContext结构体,用于管理媒体文件或者网络流的输入和输出操作
AVCodecContext结构体,包含了音频和视频编解码器的配置信息,如编码参数、解码参数、码率控制等。部分字段说明:
codec | 编解码器的AVCodec,比如指向AVCodec ff_aac_latm_decoder |
width, height | 图像的宽高(只针对视频) |
pix_fmt | 像素格式(只针对视频) |
sample_rate | 采样率(只针对音频) |
channels | 声道数(只针对音频) |
sample_fmt | 采样格式(只针对音频 |
AVFormatContext结构体,是与多媒体文件格式相关的结构体,用于打开、读取和写入媒体文件。它包含了文件的格式信息、音视频流、文件I/O操作等。部分字段说明:
struct AVInputFormat* iformat | 输入媒体的AVInputFormat(输入数据的封装格式),比如指向AVInputFormatff_flv_demuxer |
struct AVOutputFormat *oformat | 用于指定输出文件的格式以及文件写入的操作函数 |
AVIOContext *pb | 用于读取和写入媒体数据的 I/O 上下文 |
unsigned int nb_streams | 输入媒体的AVStream 个数 |
AVStream** streams | 输入媒体的AVStream []数组 |
int64_t start_time, duration | 媒体文件的起始时间戳和持续时间(以微秒为单位),计算方式可以参考av_dump_format()函数 |
int bit_rate | 输入媒体的码率 |
AVStream结构体,代表了音频或视频流,包括编解码参数、时间基准、时间戳等信息。部分字段说明
index | 标识该视频/音频流,流索引 |
time_base | 流的时间基准,用于将时间戳转换为实际时间,时间戳示例为“以timebase为单位每秒cnt个units”。 |
avg_frame_rate | 该流的帧率 |
pts、dts、duration | 该流中第一个样本和最后一个样本的时间戳及时长。 |
codecpar | 该流的编码参数,包含音频或视频编码器特定的参数信息,如分辨率、像素格式、采样率、声道数、编码类型等(该流对应的AVCodecContext) |
// AVStream 结构体主要成员int index: // 标识该视频/音频流
AVCodecContext *codec: // 指向该视频/音频流的AVCodecContext(它们是一一对应的关系)
AVRational time_base: // 时基。通过该值可以把PTS,DTS转化为真正的时间。FFMPEG其他结构体中也有这个字段,但是根据我的经验,只有AVStream中的time_base是可用的。PTS*time_base=真正的时间
int64_t duration: // 该视频/音频流长度
AVDictionary *metadata: // 元数据信息
AVRational avg_frame_rate:// 帧率(注:对视频来说,这个挺重要的)
AVPacket attached_pic: // 附带的图片。比如说一些MP3,AAC音频文件附带的专辑封面。
AVCodecParameters结构体,保存音视频流的基本参数信息。该结构体通常会在AVCodecContext中被填充并使用。
部分字段说明:
odec_type | 媒体类型,比如AVMEDIA_TYPE_VIDEO(视频) AVMEDIA_TYPE_AUDIO(音频)等 |
codec_id | 编解码器类型, 比如AV_CODEC_ID_H264(H264编码) AV_CODEC_ID_AAC(AAC编码) |
因为AVCodecContext结构体包含的参数太多,AVCodecParameters将编码器的参数从AVCodecContext分离出来,AVCodecParameters结构体中部分重要的参数如下:
enum AVMediaType codec_type; 编解码器的类型
const struct AVCodec *codec; 编解码器,初始化后不可更改
enum AVCodecID codec_id; 编解码器的id
int64_t bit_rate; 平均比特率
uint8_t *extradata; int extradata_size; 针对特定编码器包含的附加信息
AVRational time_base; 根据该参数可以将pts转化为实践
int width, height; 每一帧的宽和高
int gop_size; 一组图片的数量,编码时用户设置,解码时不使用
enum AVPixelFormat pix_fmt; 像素格式,编码时用户设置,解码时可由用户指定,但是在分析数据会被覆盖用户的设置
int refs; 参考帧的数量
enum AVColorSpace colorspace; YUV色彩空间类型
enum AVColorRange color_range; MPEG JPEG YUV范围
int sample_rate; 采样率 仅音频
int channels; 声道数(音频)
enum AVSampleFormat sample_fmt; //采样格式
int frame_size; 每个音频帧中每个声道的采样数量
int profile;配置类型
int level;级别
AVPacket结构体,用于存储音频或视频数据包,包括编码后的数据和时间戳。部分字段说明:
pts | 显示时间戳 |
dts | 解码时间戳 |
data | 压缩编码数据 |
size | 压缩编码数据大小 |
pos | 数据的偏移地址 |
stream_index | 所属的AVStream |
AVFrame结构体,用于存储音频或视频帧的数据。它包括像素数据、采样数据、时间戳等。部分字段说明:
data | 解码后的图像像素数据(音频采样数据) |
linesize | 对视频来说是图像中一行像素的大小;对音频来说是整个音频帧的大小 |
width, height | 图像的宽高(只针对视频) |
key_frame | 是否为关键帧(只针对视频) |
pict_type | 帧类型(只针对视频) 。例如I, P, B |
sample_rate | 音频采样率(只针对音频) |
nb_samples | 音频每通道采样数(只针对音频) |
pts | 显示时间戳 |
AVDictionary结构体,用于存储键值对,通常用于配置选项的传递和存储。
SwrContext结构体,用于音频重采样,支持不同采样率、通道数之间的转换
SwsContext结构体,用于图像转换,支持不同的像素格式和大小之间的转换
AVFilterContext结构体,用于配置和管理滤镜,包括音频滤镜和视频滤镜。
播放器框架
有关参考常用api:
- 官方api文档:FFmpeg: Main Page
- 常用api: https://www.cnblogs.com/linuxAndMcu/p/12041359.html
- 音视频八股文(6)-- ffmpeg大体介绍和内存模型 https://blog.51cto.com/moonfdd/6229017
ffmpeg——多媒体文件简单操作
多媒体文件是一个容器(也可以理解为包装盒,常见的mp4、flv、mkv都是不同的包装盒 ),可以存放很多数据,最常见的有字幕数据、视频数据、音频数据。
在容器中有很多流(Stream/Track),流媒体文件中不同的流之数据不会相交的,比如音频流和视频流就是各自独立的。
每种流是由不同的编码器编码的,每条流数据在多媒体文件中是经过压缩的,但是其使用压缩的编码器都是不一样的。比如音频有可能是MP3,有可能是AAC,视频常见的压缩器有可能是H264或者H265。
从流中读出的数据称为包,在一个包中包含着一个或者多个帧。
ffmpeg操作流数据的基本步骤:
从多媒体文件提取音、视频数据:
//提取多媒体文件的音频数据//编译链接:gcc -o extra_audio extra_audio.c `pkg-config --libs --cflags libavutil libavformat libavcodec`//执行 ./extra_audio test.mp4 1.aac#include<stdio.h>
#include<libavutil/log.h>
#include <libavformat/avformat.h>int main(int argc, char* argv[])
{int ret = -1;int idx = -1;// 1. 处理输入参数char* src, * dst;AVFormatContext* pFmtCtx = NULL; // 多媒体上下文AVFormatContext* oFmtCtx = NULL; // 目标文件上下文信息const AVOutputFormat* outFmt = NULL; // 输出文件格式信息AVStream* outStream = NULL; //输出文件的流AVStream* inStream = NULL; //输入文件的流AVPacket pkt; // 包av_log_set_level(AV_LOG_DEBUG);if (argc < 3) { //该可执行程序 源文件 目标文件av_log(NULL, AV_LOG_INFO, "Arguments must be more than 3.");exit(-1);}src = argv[1];dst = argv[2];// 2、打开多媒体文件(包含文件头和文件体)if ((ret = avformat_open_input(&pFmtCtx, src, NULL, NULL))){av_log(NULL, AV_LOG_ERROR, "%s\n", av_err2str(ret));exit(-1);}// 3、从多媒体文件中找到音频流idx = av_find_best_stream(pFmtCtx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); // 在文件中找到“最佳”流。AVMEDIA_TYPE_AUDIO 提取音频 //视频 AVMEDIA_TYPE_VIDEOif (idx < 0) {av_log(pFmtCtx, AV_LOG_ERROR, "Does not include audio stream\n");goto _ERROR;}// 4、打开目的文件的上下文oFmtCtx = avformat_alloc_context();if (!oFmtCtx) {av_log(NULL, AV_LOG_ERROR, "No Memory!\n");goto _ERROR;}//设置输出文件的参数outFmt = av_guess_format(NULL, dst, NULL); // 返回与所提供参数最匹配的已注册输出格式列表中的输出格式(NULL-系统匹配)oFmtCtx->oformat = outFmt;// 5、为目的文件创建一个新的音频流outStream = avformat_new_stream(oFmtCtx, NULL);// 6、设置输出音频参数inStream = pFmtCtx->streams[idx];avcodec_parameters_copy(outStream->codecpar, inStream->codecpar); //将源文件的内容复制到目的文件 outStream->codecpar->codec_tag = 0; // 根据多媒体文件自动识别编解码器//上下文信息与输出文件绑定ret = avio_open2(&oFmtCtx->pb, dst, AVIO_FLAG_WRITE, NULL,NULL);if (ret < 0) {av_log(NULL, AV_LOG_ERROR, "%s", av_err2str(ret));goto _ERROR;}// 7、写多媒体文件头(包含多媒体的类型、版本等信息)到目标文件ret = avformat_write_header(oFmtCtx, NULL);if (ret < 0) {av_log(oFmtCtx, AV_LOG_ERROR, "%s", av_err2str(ret));goto _ERROR;}// 8、从源多媒体文件中读到音频数据while (av_read_frame(pFmtCtx, &pkt) >= 0) { // 从多媒体文件读取到帧数据if (pkt.stream_index == idx) { // 判断是否是我们需要的流(之前找到的音频流)// 修改目标文件时间戳 pts dts 时长 durationpkt.pts = av_rescale_q_rnd(pkt.pts, inStream->time_base, outStream->time_base, (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));pkt.dts = pkt.pts; // 音频文件dts 和 pts 相等,视频需要通过av_rescale_q_rnd()计算dtspkt.duration = av_rescale_q(pkt.duration, inStream->time_base, outStream->time_base);pkt.stream_index = 0; // 流编号信息pkt.pos = -1; // 偏移位置av_interleaved_write_frame(oFmtCtx, &pkt); // 将音频帧写入目标文件中av_packet_unref(&pkt);}}// 9、写多媒体文件尾到文件中av_write_trailer(oFmtCtx);// 10、将申请的资源释放掉
_ERROR:if (pFmtCtx) {avformat_close_input(&pFmtCtx);pFmtCtx = NULL;}if (oFmtCtx->pb) {avio_close(oFmtCtx->pb);}if (oFmtCtx) {avformat_free_context(oFmtCtx);oFmtCtx = NULL;}return 0;
}
//提取多媒体文件的视频数据//编译链接:gcc -o extra_video extra_video.c `pkg-config --libs --cflags libavutil libavformat libavcodec`//执行 ./extra_video test.mp4 2.h264#include<stdio.h>
#include<libavutil/log.h>
#include <libavformat/avformat.h>int main(int argc, char* argv[])
{int ret = -1;int idx = -1;// 1. 处理输入参数char* src, * dst;AVFormatContext* pFmtCtx = NULL; // 多媒体上下文AVFormatContext* oFmtCtx = NULL; // 目标文件上下文信息const AVOutputFormat* outFmt = NULL; // 输出文件格式信息AVStream* outStream = NULL; //输出文件的流AVStream* inStream = NULL; //输入文件的流AVPacket pkt; // 包av_log_set_level(AV_LOG_DEBUG);if (argc < 3) { //该可执行程序 源文件 目标文件av_log(NULL, AV_LOG_INFO, "Arguments must be more than 3.");exit(-1);}src = argv[1];dst = argv[2];// 2、打开多媒体文件(包含文件头和文件体)if ((ret = avformat_open_input(&pFmtCtx, src, NULL, NULL))){av_log(NULL, AV_LOG_ERROR, "%s\n", av_err2str(ret));exit(-1);}// 3、从多媒体文件中找到视频流idx = av_find_best_stream(pFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); // 在文件中找到“最佳”流。if (idx < 0) {av_log(pFmtCtx, AV_LOG_ERROR, "Does not include audio stream\n");goto _ERROR;}// 4、打开目的文件的上下文oFmtCtx = avformat_alloc_context();if (!oFmtCtx) {av_log(NULL, AV_LOG_ERROR, "No Memory!\n");goto _ERROR;}//设置输出文件的参数outFmt = av_guess_format(NULL, dst, NULL); // 返回与所提供参数最匹配的已注册输出格式列表中的输出格式(NULL-系统匹配)oFmtCtx->oformat = outFmt;// 5、为目的文件创建一个新的视频流outStream = avformat_new_stream(oFmtCtx, NULL);// 6、设置输出视频参数inStream = pFmtCtx->streams[idx];avcodec_parameters_copy(outStream->codecpar, inStream->codecpar); //将源文件的内容复制到目的文件 outStream->codecpar->codec_tag = 0; // 根据多媒体文件自动识别编解码器//上下文信息与输出文件绑定ret = avio_open2(&oFmtCtx->pb, dst, AVIO_FLAG_WRITE, NULL, NULL);if (ret < 0) {av_log(NULL, AV_LOG_ERROR, "%s", av_err2str(ret));goto _ERROR;}// 7、写多媒体文件头(包含多媒体的类型、版本等信息)到目标文件ret = avformat_write_header(oFmtCtx, NULL);if (ret < 0) {av_log(oFmtCtx, AV_LOG_ERROR, "%s", av_err2str(ret));goto _ERROR;}// 8、从源多媒体文件中读到视频数据while (av_read_frame(pFmtCtx, &pkt) >= 0) { // 从多媒体文件读取到帧数据if (pkt.stream_index == idx) { // 判断是否是我们需要的流(之前找到的视频流)// 修改目标文件时间戳 pts dts 时长 durationpkt.pts = av_rescale_q_rnd(pkt.pts, inStream->time_base, outStream->time_base, (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));pkt.dts = av_rescale_q_rnd(pkt.dts, inStream->time_base, outStream->time_base, (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));pkt.duration = av_rescale_q(pkt.duration, inStream->time_base, outStream->time_base);pkt.stream_index = 0; // 流编号信息pkt.pos = -1; // 偏移位置av_interleaved_write_frame(oFmtCtx, &pkt); // 将视频帧写入目标文件中av_packet_unref(&pkt);}}// 9、写多媒体文件尾到文件中av_write_trailer(oFmtCtx);// 10、将申请的资源释放掉
_ERROR:if (pFmtCtx) {avformat_close_input(&pFmtCtx);pFmtCtx = NULL;}if (oFmtCtx->pb) {avio_close(oFmtCtx->pb);}if (oFmtCtx) {avformat_free_context(oFmtCtx);oFmtCtx = NULL;}return 0;
}
用到API:
/* 打开一个输入流并读取报头。编解码器没有打开。流必须用avformat_close_input()关闭。参数:@param ps指向用户提供的AVFormatContext(由avformat_alloc_context)。可能是指向NULL的指针,在这种情况下,AVFormatContext由this分配函数并写入ps。注意,用户提供的AVFormatContext将被释放失败。@param url打开的流的url。@param fmt如果非null,该参数强制使用特定的输入格式。否则格式自动检测。@param options一个由AVFormatContext和demuxer-private填充的字典选项。返回时,此参数将被销毁并替换为包含未找到的选项的字典。可能为NULL。成功时返回0,失败时返回负AVERROR。
*/int avformat open input(AVFormatContext *ps, const char *url, const AVinputFormat *fmt,AVDictionary **options)
//分配一个AVFormatContext。
// avformat_free_context()可以用来释放上下文和框架在其中分配的所有内容。AVFormatContext *avformat_alloc_context(void);
/*在文件中找到“最佳”流。最佳流是根据各种启发式确定的,因为最可能是用户期望的。如果decoder参数非null, av_find_best_stream将为流的编解码器找到默认的解码器;找不到解码器的流将被忽略。参数:* @param ic 媒体文件句柄* @param type 视频,音频,字幕等* @param wanted_stream_nb 用户请求的流编号或-1为自动选择* @param related_stream 尝试找到一个流相关(例如:在同一个程序中)到这个,如果没有,则为-1* @param decoder_ret 如果非null,返回所选流的解码器* @param flags 当前没有定义@return如果成功,返回非负的流号,如果没有找到请求类型的流,返回AVERROR_STREAM_NOT_FOUND,如果找到流但没有解码器,返回AVERROR_DECODER_NOT_FOUND@note
如果av_find_best_stream返回成功并且decoder_ret不是NULL,那么*decoder_ret保证被设置为有效的AVCodec。*/
int av_find_best_stream(AVFormatContext *ic,enum AVMediaType type,int wanted_stream_nb,int related_stream,const AVCodec **decoder_ret,int flags);
/*返回与所提供参数最匹配的已注册输出格式列表中的输出格式,如果没有匹配则返回NULL。@param short_name if non-NULL检查short_name是否与注册格式的名称匹配
@param filename if non-NULL检查filename是否以注册格式的扩展名结束
@param mime_type if non-NULL检查mime_type是否与注册格式的MIME类型匹配*/const AVOutputFormat *av_guess_format(const char *short_name,const char *filename,const char *mime_type);
/*向媒体文件添加新的流。在解模时,由read_header()中的解模器调用。如果标志AVFMTCTX_NOHEADER在s.ctx_flags中设置,那么它也可以在read_packet()中被调用当复用时,应该在avformat_write_header()之前由用户调用。用户需要调用avformat_free_context()来清理分配,由avformat_new_stream()。参数:@param s 媒体文件句柄@param c 未使用,不做任何事情@return 新创建的流,错误时返回NULL。*/AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);
/*
***创建并初始化一个AVIOContext来访问url表示的资源。参数:@param s 用于返回指向创建的AVIOContext的指针。在失败的情况下,指向的值被设置为NULL。@param url 要访问的资源@param flags 控制如何打开url所指示的资源的标志@param int_cb 在协议级别使用的中断回调@param options 私有选项的字典,返回时此参数将被销毁并替换为包含未找到选项的字典可能为NULL。@return >= 0如果成功,则为an对应的负值失败时的AVERROR代码注:当url所指示的资源以读+写方式打开时,AVIOContext只能用于写。*/
int avio_open2(AVIOContext **s, const char *url, int flags,const AVIOInterruptCB *int_cb, AVDictionary **options);
/*
*** 返回流的下一帧。此函数返回存储在文件中的内容,而不验证是否存在用于解码器的有效帧。它将把存储在文件中的内容分割成帧,并为每次调用返回一个帧。它不会省略有效帧之间的无效数据,以便为解码器提供可能用于解码的最大信息。如果成功,返回的数据包将被引用计数(pkt->但已设置)并无限期有效。当不再需要该数据包时,必须使用av_packet_unref()释放该数据包。对于视频,数据包只包含一帧。对于音频,如果每帧有一个已知的固定大小(例如PCM或ADPCM数据),它包含一个整数帧数。如果音频帧具有可变大小(例如MPEG音频),则它包含一个帧。pkt->pts, pkt->dts和pkt->duration在AVStream中总是设置为正确的值。Time_base单位(并猜测格式是否不能提供它们)。如果视频格式有b帧,pkt->pts可以是AV_NOPTS_VALUE,所以如果不解压缩有效载荷,最好依赖pkt->dts。return: 如果OK则返回0,错误或文件结束时返回< 0。如果出现错误,pkt将为空白(就好像它来自av_packet_alloc())。注:注意PKT将被初始化,所以它可能是未初始化的,但它不能包含需要释放的数据。
*/
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
多媒体文件格式转封装:
音视频解码流程:
转换多媒体文件的格式代码:
//转换多媒体文件的格式//编译链接:gcc -o remux remux.c `pkg-config --libs --cflags libavutil libavformat libavcodec`//执行 ./remux test.mp4 test.flv#include<stdio.h>
#include<libavutil/log.h>
#include <libavformat/avformat.h>int main(int argc, char* argv[])
{int ret = -1;int idx = -1;int i = 0;int stream_idx = 0;// 处理输入参数char* src, * dst;int* stream_map = NULL;AVFormatContext* pFmtCtx = NULL; // 多媒体上下文AVFormatContext* oFmtCtx = NULL; // 目标文件上下文信息const AVOutputFormat* outFmt = NULL; // 输出文件格式信息AVPacket pkt; // 包av_log_set_level(AV_LOG_DEBUG);if (argc < 3) { //该可执行程序 源文件 目标文件av_log(NULL, AV_LOG_INFO, "Arguments must be more than 3.");exit(-1);}src = argv[1];dst = argv[2];// 打开多媒体文件(包含文件头和文件体)if ((ret = avformat_open_input(&pFmtCtx, src, NULL, NULL))){av_log(NULL, AV_LOG_ERROR, "%s\n", av_err2str(ret));exit(-1);}// 打开目的文件的上下文avformat_alloc_output_context2(&oFmtCtx, NULL, NULL, dst);if (!oFmtCtx) {av_log(NULL, AV_LOG_ERROR, "NO Memory!\n");goto _ERROR;}stream_map = av_calloc(pFmtCtx->nb_streams, sizeof(int));if (!stream_map) {av_log(NULL, AV_LOG_ERROR, "NO Memory!\n");goto _ERROR;}// 遍历源文件每一条流for (i = 0; i < pFmtCtx->nb_streams; i++) {AVStream* outStream = NULL;AVStream* inStream = pFmtCtx->streams[i];AVCodecParameters* inCodecPar = inStream->codecpar;// 只处理音、视频、字幕数据if (inCodecPar->codec_type != AVMEDIA_TYPE_AUDIO &&inCodecPar->codec_type != AVMEDIA_TYPE_VIDEO &&inCodecPar->codec_type != AVMEDIA_TYPE_SUBTITLE) {stream_map[i] = -1;continue;}stream_map[i] = stream_idx++;// 为目的文件创建一个新的视频流outStream = avformat_new_stream(oFmtCtx, NULL);if (!outStream) {av_log(oFmtCtx, AV_LOG_ERROR, "NO Memory!\n");goto _ERROR;}avcodec_parameters_copy(outStream->codecpar, inStream->codecpar); //将源文件的内容复制到目的文件 outStream->codecpar->codec_tag = 0; // 根据多媒体文件自动识别编解码器}//上下文信息与输出文件绑定ret = avio_open2(&oFmtCtx->pb, dst, AVIO_FLAG_WRITE, NULL, NULL);if (ret < 0) {av_log(NULL, AV_LOG_ERROR, "%s", av_err2str(ret));goto _ERROR;}// 写多媒体文件头(包含多媒体的类型、版本等信息)到目标文件ret = avformat_write_header(oFmtCtx, NULL);if (ret < 0) {av_log(oFmtCtx, AV_LOG_ERROR, "%s", av_err2str(ret));goto _ERROR;}// 从源多媒体文件中读到音、视频、字幕数据while (av_read_frame(pFmtCtx, &pkt) >= 0) { // 从多媒体文件读取到帧数据,读取码流中的音频若干帧或者视频一帧AVStream* inStream, * outStream;inStream = pFmtCtx->streams[pkt.stream_index];if (stream_map[pkt.stream_index] < 0) { // 流编号为-1, 不是音、视频、字幕流数据av_packet_unref(&pkt); // 释放packetcontinue;}pkt.stream_index = stream_map[pkt.stream_index];outStream = oFmtCtx->streams[pkt.stream_index];av_packet_rescale_ts(&pkt, inStream->time_base, outStream->time_base); // 修改时间戳pkt.pos = -1; // 偏移位置av_interleaved_write_frame(oFmtCtx, &pkt); // 将视频帧写入目标文件中av_packet_unref(&pkt);}// 写多媒体文件尾到文件中av_write_trailer(oFmtCtx);// 将申请的资源释放掉
_ERROR:if (pFmtCtx) {avformat_close_input(&pFmtCtx);pFmtCtx = NULL;}if (oFmtCtx->pb) {avio_close(oFmtCtx->pb);}if (oFmtCtx) {avformat_free_context(oFmtCtx);oFmtCtx = NULL;}if (stream_map) {av_free(stream_map);}return 0;
}