使用到的ffmpeg API
av_log_set_level()
设置日志打印的标准, 高于这个标准的将不会打印
av_log()
打印ffmpeg中的日志,当做printf用
av_dump_format()
打印流的各种信息
av_register_all()
初始化所有组件
int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options)
打开一个文件并解析,传入文件地址,输入文件的上下文会保存在 ps
主要是完成了对输入的AVFormatContext中的AVInputFormat的初始化,对AVIOContext打开输入控制,以及创建AVStream。
avformat_close_input()
传入上下文,关闭文件
av_find_best_stream()
输入流的上下文和要找的流的类型,返回最好流的索引
av_init_packet()
初始化一个包,保存了解复用(demuxer)之后,解码(decode)之前的数据(仍然是压缩后的数据)和关于这些数据的一些附加的信息
av_read_frame()
返回流的下一帧,当不再需要数据包时,必须使用av_packet_unref释放该数据包,当到文件结尾时返回负数
av_packet_unref()
取消引用数据包引用的缓冲区,并将剩余的数据包字段重置为其默认值。
avformat_alloc_context()
用来申请AVFormatContext类型变量空间并初始化默认参数,返回AVFormatContext类型上下文
av_guess_format()
根据输入的文件名生成最适合的输出容器,也可以指定文件类型,返回容器的上下文
avformat_new_stream()
创建流通道,返回AVStream
首先调用av_mallocz()为AVStream分配内存。
接着给新分配的AVStream的各个字段赋上默认值。
然后调用了另一个函数avcodec_alloc_context3()初始化AVStream中的AVCodecContext。
avcodec_parameters_copy()
拷贝avstream中的codecpar的信息,这里将输入流的参数信息拷贝到输出流中
*int av_strerror(int errnum, char errbuf, size_t errbuf_size)
errnum指错误码,errbuf返回错误的buf,错误buf的size,给错误信息赋值,为打印做准备
avformat_write_header
写输出文件流的头
av_rescale_q_rnd
计算 “a * b / c” 的值并分五种方式来取整.
av_rescale_q
av_rescale_q_rnd的特殊情况
av_interleaved_write_frame
此函数将根据需要在内部缓冲数据包,以确保输出文件中的数据包按照增加dts的顺序正确地交织在一起
av_write_trailer
将流尾随写入输出媒体文件并释放文件私有数据。只能在成功调用avformat_write_header后调用。
avio_open
创建并初始化一个AVIOContext,用于访问由url指示的资源。
avio_close
关闭
使用到的ffmpeg的数据结构
AVFormatContext(统领全局,主要存储视音频封装格式中包含的信息)
|------AVInputFormat(存储输入视音频使用的封装格式)
|------AVOutputFormat(存储输出音视频使用的封装格式)
|------AVIOContext(处在协议层)
| |---------URLContext(存储视音频使用的协议的类型以及状态)
| |--------- URLProtocol(存储输入视音频使用的封装格式)
|------AVStream(存储一个视频/音频流的相关数据)
|---------AVCondecContext(存储该视频/音频流使用解码方式的相关数据)
|---------AVCodec(视频/音频对应的解码器)
AVOutputFormat
对流(Stream)的封装和抽象,描述了视频、音频等流的编码格式等基本流信息。此外也是音频、视频、字母数据流的重要载体。
AVStream
存储每一个视频/音频流信息的结构体
AVCodecContext *codec // 已过时,使用另一个 codecpar 结构体代替。
AVRational time_base // 时间基数。
int64_t duration // 总时长。流的总时长,该参数不可靠。
AVRational avg_frame_rate // 帧率。
AVCodecParameters *codecpar; // 包含音视频参数的结构体。很重要,可以用来获取音视频参数中的宽度、高度、采样率、编码格式等信息。
AVPacket
保存了解复用(demuxer)之后,解码(decode)之前的数据(仍然是压缩后的数据)和关于这些数据的一些附加的信息
关于数据的属性有以下字段:
pts: (int64_t)显示时间,结合AVStream->time_base转换成时间戳
dts: (int64_t)解码时间,结合AVStream->time_base转换成时间戳
size: (int)data的大小
stream_index: (int)packet在stream的index位置
flags: (int)标示,结合AV_PKT_FLAG使用,其中最低为1表示该数据是一个关键帧。
#define AV_PKT_FLAG_KEY 0x0001 //关键帧
#define AV_PKT_FLAG_CORRUPT 0x0002 //损坏的数据
#define AV_PKT_FLAG_DISCARD 0x0004 /丢弃的数据
side_data_elems: (int)边缘数据元数个数
duration: (int64_t)数据的时长,以所属媒体流的时间基准为单位,未知则值为默认值0
pos: (int64_t )数据在流媒体中的位置,未知则值为默认值-1
convergence_duration:该字段已deprecated,不在使用
关于数据缓存,AVPacket本身只是个容器,不直接的包含数据,而是通过数据缓存的指针引用数据。AVPacket包含两种数据
uint8_t *data:指向保存压缩数据的指针,这就是AVPacket的实际数据。
AVPacketSideData *side_data:容器提供的一些附加数据
AVBufferRef *buf:用来管理data指针引用的数据缓存,其使用在后面介绍
第一种方式
先取出一个个音频包,然后在每个音频包前边手动的加 ADTS Header写成一个文件。
#include <stdio.h>
#define __STDC_CONSTANT_MACROS
#ifdef __cplusplus
extern "C" {
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>
#include <libavutil/log.h>
#ifdef __cplusplus
};
#endif
void adts_header(char *szAdtsHeader, int dataLen)
{
int audio_object_type = 2;
int sampling_frequency_index = 7;
int channel_config = 2;
int adtsLen = dataLen + 7;
szAdtsHeader[0] = 0xff; //syncword:0xfff 高8bits
szAdtsHeader[1] = 0xf0; //syncword:0xfff 低4bits
szAdtsHeader[1] |= (0 << 3); //MPEG Version:0 for MPEG-4,1 for MPEG-2 1bit
szAdtsHeader[1] |= (0 << 1); //Layer:0 2bits
szAdtsHeader[1] |= 1; //protection absent:1 1bit
szAdtsHeader[2] = (audio_object_type - 1) << 6; //profile:audio_object_type - 1 2bits
szAdtsHeader[2] |= (sampling_frequency_index & 0x0f) << 2; //sampling frequency index:sampling_frequency_index 4bits
szAdtsHeader[2] |= (0 << 1); //private bit:0 1bit
szAdtsHeader[2] |= (channel_config & 0x04) >> 2; //channel configuration:channel_config 高1bit
szAdtsHeader[3] = (channel_config & 0x03) << 6; //channel configuration:channel_config 低2bits
szAdtsHeader[3] |= (0 << 5); //original:0 1bit
szAdtsHeader[3] |= (0 << 4); //home:0 1bit
szAdtsHeader[3] |= (0 << 3); //copyright id bit:0 1bit
szAdtsHeader[3] |= (0 << 2); //copyright id start:0 1bit
szAdtsHeader[3] |= ((adtsLen & 0x1800) >> 11); //frame length:value 高2bits
szAdtsHeader[4] = (uint8_t)((adtsLen & 0x7f8) >> 3); //frame length:value 中间8bits
szAdtsHeader[5] = (uint8_t)((adtsLen & 0x7) << 5); //frame length:value 低3bits
szAdtsHeader[5] |= 0x1f; //buffer fullness:0x7ff 高5bits
szAdtsHeader[6] = 0xfc;
}
int main(int argc,char * argv[])//argc为参数的个数,argv[0]为程序的名字,之后为输入的参数
{
char* src=NULL;
char* dst=NULL;
int ret;
int len;
int audio_index;
AVFormatContext *fmt_ctx=NULL;
AVPacket pkt;
av_log_set_level(AV_LOG_INFO);
av_register_all();//注册,必须要
//拿到输入参数
if(argc<3)
{
av_log(NULL, AV_LOG_ERROR, "can't read dir!\n");
return -1;
}
src=argv[1];
dst=argv[2];
if(!src||!dst)
{
av_log(NULL, AV_LOG_ERROR, "src or dst is null\n");
return -1;
}
//打开src文件
ret=avformat_open_input(&fmt_ctx,src,NULL,NULL);//输出fmt_ctx
if(ret<0)
{
av_log(NULL, AV_LOG_ERROR, "can't read dir:%s\n",av_err2str(ret));//将错误码转化为字符串打印
avformat_close_input(&fmt_ctx);
return -1;
}
//打印mata信息
av_dump_format(fmt_ctx,0,src,0);//第四个参数0为输入,1为输出 打印mata信息api
//打开输出文件
FILE* dst_fd=fopen(dst,"wb");
if(dst_fd<0)
{
av_log(NULL, AV_LOG_ERROR, "can't open out file \n");
return -1;
}
//2.get stream
ret=av_find_best_stream(fmt_ctx,AVMEDIA_TYPE_AUDIO,-1,-1,NULL,0);
if(ret<0)
{
av_log(NULL, AV_LOG_ERROR, "can't find the best stream\n");
fclose(dst_fd);
avformat_close_input(&fmt_ctx);
return -1;
}
audio_index=ret;
//初始化包
av_init_packet(&pkt);
while(av_read_frame(fmt_ctx,&pkt)>=0)//从媒体流中读取帧填充到填充到Packet的数据缓存空间,到文件末尾返回<0退出while
{
if(pkt.stream_index==audio_index)//判断该AVPacket所属的视频/音频流
{
//每一帧音频数据前加入adts_header
char adts_header_buf[7];
adts_header(adts_header_buf,pkt.size);
fwrite(adts_header_buf,1,7,dst_fd);
//3.write audio data to acc file
len=fwrite(pkt.data,1,pkt.size,dst_fd);
if(len!=pkt.size)
{
av_log(NULL, AV_LOG_WARNING, "wanning ,length of data is no equal \n");
}
}
av_packet_unref(&pkt);//将缓存空间的引用计数-1,并将Packet中的其他字段设为初始值。如果引用计数为0,自动的释放缓存空间
}
avformat_close_input(&fmt_ctx);
if(dst_fd)
{
fclose(dst_fd);
}
//输出一下视频信息
/* avformat_open_input(&fmt_ctx,"/home/ky/ffmpeg_demo/new/test.aac",NULL,NULL);
av_dump_format(fmt_ctx,0,src,0);
avformat_close_input(&fmt_ctx);
return 0;
}
*/
第一种方式只对HE-AAC有效,而对于 LC-AAC,建立使用通用的第二种方式
第二种方法
使用 ffmpeg API 直接创建一个 AAC 文件,在 ffmpeg库内部会自己查找到对应的多媒体格式帮你做好 ADTS Header,并最终写好AAC 文件。
核心流程:
调用 av_guess_format 让ffmpeg帮你找到一个合适的文件格式。
调用 avformat_new_stream 为输出文件创建一个新流。
调用 avio_open 打开新创建的文件。
调用 avformat_write_header 写文件头。
调用 av_interleaved_write_frame 写文件内容。
调用 av_write_trailer 写文件尾。
调用 avio_close 关闭文件。
#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avformat.h>
int main(int argc,char * argv[])
{
int err_code;
char errors[1024];
char *src_fileName = NULL;
char *dst_fileName = NULL;
//上下文
AVFormatContext *fmt_ctx = NULL;
AVFormatContext *ofmt_ctx = NULL;
//支持各种各样的输出文件格式,MP4,FLV,3GP等等
AVOutputFormat *output_fmt = NULL;
//输入流
AVStream *in_stream = NULL;
//输出流
AVStream *out_stream = NULL;
//存储压缩数据
AVPacket packet;
//要拷贝的流
int audio_stream_index = -1;
//注册
av_register_all();
//拿到输入参数
if(argc < 3)
{
av_log(NULL, AV_LOG_DEBUG, "the count of parameters should be more than three!\n");
return -1;
}
src_fileName = argv[1];
dst_fileName = argv[2];
//打开输入文件,关于输入文件的所有就保存到fmt_ctx中了
err_code = avformat_open_input(&fmt_ctx, src_fileName, NULL, NULL);
if (err_code < 0)
{
av_log(NULL, AV_LOG_ERROR, "cant open 1 file:%s\n", av_err2str(err_code));
return -1;
}
if(fmt_ctx->nb_streams<2)
{
//流数小于2,说明这个文件音频、视频流这两条都不能保证,输入文件有错误
av_log(NULL, AV_LOG_ERROR, "输入文件错误,流不足2条\n");
exit(1);
}
//拿到文件中音频流
in_stream = fmt_ctx->streams[1];
//aac参数信息
AVCodecParameters *in_codecpar = in_stream->codecpar;
//找到最好的音频流
audio_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if(audio_stream_index < 0)
{
av_log(NULL, AV_LOG_DEBUG, "寻找最好音频流失败,请检查输入文件!\n");
return AVERROR(EINVAL);
}
//2.准备 output file and stream
// 输出上下文
ofmt_ctx = avformat_alloc_context();
//根据目标文件名生成最适合的输出容器
output_fmt = av_guess_format(NULL,dst_fileName,NULL);
if(!output_fmt)
{
av_log(NULL, AV_LOG_DEBUG, "根据目标生成输出容器失败!\n");
exit(1);
}
ofmt_ctx->oformat = output_fmt;
//新建输出流
out_stream = avformat_new_stream(ofmt_ctx, NULL);
if(!out_stream)
{
av_log(NULL, AV_LOG_DEBUG, "创建输出流失败!\n");
exit(1);
}
//3.data copy
// 将参数信息拷贝到输出流中,我们只是抽取音频流,并不做音频处理,所以这里只是Copy
if((err_code = avcodec_parameters_copy(out_stream->codecpar, in_codecpar)) < 0 )
{
av_strerror(err_code, errors, 1024);
av_log(NULL, AV_LOG_ERROR,"拷贝编码参数失败!, %d(%s)\n",
err_code, errors);
}
//初始化AVIOContext,文件操作由它完成
if((err_code = avio_open(&ofmt_ctx->pb, dst_fileName, AVIO_FLAG_WRITE)) < 0)//open new file
{
av_strerror(err_code, errors, 1024);
av_log(NULL, AV_LOG_DEBUG, "Could not open file %s, %d(%s)\n",
dst_fileName,
err_code,
errors);
exit(1);
}
//初始化 AVPacket, 我们从文件中读出的数据会暂存在其中
av_init_packet(&packet);
packet.data = NULL;
packet.size = 0;
// 写头部信息
if (avformat_write_header(ofmt_ctx, NULL) < 0)
{
av_log(NULL, AV_LOG_DEBUG, "Error occurred when opening output file");
exit(1);
}
//每读出一帧数据
while(av_read_frame(fmt_ctx, &packet) >=0 )
{
if(packet.stream_index == audio_stream_index)
{
//时间基计算,音频pts和dts一致
packet.pts = av_rescale_q_rnd(packet.pts, in_stream->time_base, out_stream->time_base, (AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
packet.dts = packet.pts;
packet.duration = av_rescale_q(packet.duration, in_stream->time_base, out_stream->time_base);
packet.pos = -1;
packet.stream_index = 0;
//将包写到输出媒体文件
av_interleaved_write_frame(ofmt_ctx, &packet);
//减少引用计数,避免内存泄漏
av_packet_unref(&packet);
}
}
//写尾部信息
av_write_trailer(ofmt_ctx);
//最后别忘了释放内存
avformat_close_input(&fmt_ctx);
avio_close(ofmt_ctx->pb);
return 0;
}
参考链接
https://blog.csdn.net/weixin_33980459/article/details/88010202?utm_medium=distribute.pc_relevant.none-task-blog-title-3&spm=1001.2101.3001.4242
https://www.imooc.com/article/254733