FFmpeg是一套开源的计算机程序,主要用于记录、转换数字音频、视频,并能将其转化为流。它提供了录制、转换以及流化音视频的完整解决方案,被誉为多媒体业界的“瑞士军刀”。
1.使用ffmpeg命令实现音频流数据提取
[wbyq@wbyq ffmpeg]$ ffmpeg -i 1.mp4 -acodec copy -vn test.aac
- acodec: 指定音频编码器,copy 指明只拷贝,不做编解码。
- vn: v 代表视频,n 代表 no 也就是无视频的意思。
2.调用ffmpeg库使用音频流数据提取
从一个视频中抽取音频流数据操作步骤:
(1)打开音视频原文件avformat_open_input()
(2)读取音视频流数据包,获取流信息avformat_find_stream_info()
该函数不会影响正常文件信息,一些媒体文件在没有标头信息必须要调用该函数;
若要输出当前媒体流信息,可调到av_dump_format() ;输出流信息,若不需要查看,则可不调用该函数;
从媒体文件中查找要解析的流数据av_find_best_stream() ;调用成功返回解析的流下标;
(3)创建一个输出媒体上下文件指针avformat_alloc_context()
(4)根据媒体文件类型注册格式av_guess_format()
(5)创建一个流数据avformat_new_stream()
(6)将源媒体文件流数据拷贝到目的媒体文件中avcodec_parameters_copy()
(7)打开目的媒体文件avio_open(),将多媒体头数据写入到目标文件avformat_write_header()
(8)循环从源媒体文件中读取流数据包av_read_frame() ;判断读取的流数据是否和查找解析的流数据一致,若一致的话将数据包写入大目标媒体文件中av_interleaved_write_frame() ,该函数可以按dts顺序写入,写入完后释放pack包av_packet_unref() ;在写入数据包之前,需要设置dts、pts、duration、stream_index、pos 参数;
(9)写入多媒体尾数据av_write_trailer
(10)释放资源,自此,流数据抽取成功;
avformat_free_context()
avforamt_close_input()
整体流程图如下所示:
3.整体代码实现
#include <stdio.h>
#include <libavutil/log.h>//日志文件处理
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
int main(int argc,char **argv)
{
/*
函数功能:抽取音频流数据
*/
//1.设置日志信息等级
av_log_set_level(AV_LOG_INFO);
//请求输入参数:./app <源文件> <目标文件>
if(argc!=3)
{
av_log(NULL,AV_LOG_ERROR,"运行格式:./app <源文件> <目标文件>\n");
return 0;
}
const char *src=argv[1];
const char *dest=argv[2];
//2.打开文件
AVFormatContext *pfmt_ctx=NULL;//上下文指针
int ret;
ret=avformat_open_input(&pfmt_ctx,src,NULL, NULL);
if(ret!=0)
{
av_log(NULL,AV_LOG_ERROR,"打开媒体文件失败,err=%d,%s\n",ret,av_err2str(ret));
return 0;
}
//读取流数据包,获取流信息
avformat_find_stream_info(pfmt_ctx, NULL);
//输出流信息
av_dump_format(pfmt_ctx,0,src,0);
/*
3.查找音频流数据
ic --上下文指针
type --媒体文件类型 AVMEDIA_TYPE_VIDEO(视频) AVMEDIA_TYPE_AUDIO(音频)
wanted_stream_nb --流编号,-1表示自动选择
related_stream --相关流,一般填-1
decoder_ret --流解码器,不清楚则填NULL
flags --暂未定义,填0即可
返回值:成功返回一个非负整数,表示对应流编号
失败返回负数
*/
int index=-1;
index=av_find_best_stream(pfmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1,NULL,0);
if(index<0)
{
av_log(pfmt_ctx,AV_LOG_ERROR,"查找音频流数据失败err=%d,%s\n",index,av_err2str(index));
goto _fil;
}
av_log(pfmt_ctx,AV_LOG_INFO,"index=%d\n",index);
/*4.创建输出流上下文指针*/
AVFormatContext *ofmatctx=avformat_alloc_context();
if(ofmatctx==NULL)
{
av_log(NULL,AV_LOG_ERROR,"创建输出流上下文件指针失败\n");
goto _fil;
}
/*
5.根据类型注册输出格式
short_name --格式短参名,不清楚则填NULL
filename --输出格式的目标名字
mime_type --MIME类型,不清楚则填NULL
该函数可以根据short_name、filename、mime_type的任意一个实现格式注册,成功返回AVOutputFormat
失败返回NULL
*/
AVOutputFormat *ofmt=av_guess_format(NULL,dest,NULL);
if(ofmt==NULL)
{
av_log(NULL,AV_LOG_ERROR,"注册输出格式失败\n");
goto _fil2;
}
//6.将输出流信息写入到输出流上下文指针
ofmatctx->oformat=ofmt;
//7.创建一个新的流数据
AVStream *ostream=avformat_new_stream(ofmatctx, NULL);
if(ostream==NULL)
{
av_log(ofmatctx,AV_LOG_ERROR,"创建数据流失败\n");
goto _fil2;
}
//8.将输入媒体文件中的音频流数据拷贝到输出媒体文件中
AVStream *istream=pfmt_ctx->streams[index];//输入流数据
avcodec_parameters_copy(ostream->codecpar, istream->codecpar);
ostream->codecpar->codec_tag=0;//填0,表示编解码器有系统选择
//将输出媒体文件打开,便于后续写数据
ret=avio_open(&ofmatctx->pb, dest, AVIO_FLAG_WRITE);
if(ret<0)
{
av_log(ofmatctx,AV_LOG_ERROR,"输出媒体文件打开失败err=%s\n",av_err2str(ret));
goto _fil2;
}
//9.写多媒体文件头到目标文件
ret=avformat_write_header(ofmatctx,NULL);
if(ret<0)
{
av_log(ofmatctx,AV_LOG_ERROR,"写入多媒体头数据失败,ret=%s\n",av_err2str(ret));
}
//循环写入音频流数据
AVPacket pkt;
while(av_read_frame(pfmt_ctx, &pkt)==0)//从源文件中读取流数据
{
if(pkt.stream_index==index)//判断读取的流数据是否为需要的音频流
{
/*
PTS:(Presentation Time Stamp)显示时间戳 PTS主要用于度量解码后的视频帧什么时候被显示出来。
DTS:(Decode Time Stamp)解码时间戳 DTS主要是标识读入内存中的数据流在什么时候开始送入解码器中进行解码。
音频流数据,没有视频,所以PTS和DTS一致
av_rescale_q_rnd 函数:实现时间戳pts从输入流基准时间时间戳bq 转换 为输出流基准时间时间戳cq
av_rescale_q_rnd(int64_t a, AVRational bq, AVRational cq,enum AVRounding rnd)
计算方式:a*bq/cq
rnd --表示计算处理方式,AV_ROUND_PASS_MINMAX表示限制最大最小值;AV_ROUND_NEAR_INF表示四舍五入处理方式
*/
pkt.pts=av_rescale_q_rnd(pkt.pts,istream->time_base,ostream->time_base, AV_ROUND_PASS_MINMAX|AV_ROUND_NEAR_INF);
pkt.dts=pkt.pts;
//计算音频时长
pkt.duration=av_rescale_q(pkt.duration,istream->time_base,ostream->time_base);
pkt.stream_index=0;//流下标
pkt.pos=-1;//相对位置
//根据dts递增顺序正确交织写入数据到到媒体文件
av_interleaved_write_frame(ofmatctx,&pkt);
av_packet_unref(&pkt);//释放pkt包
}
}
//写入多媒体文件尾数据
av_write_trailer(ofmatctx);
_fil2:
if(!ofmatctx)
{
avformat_free_context(ofmatctx);//释放流指针
}
_fil:
if(!pfmt_ctx)
{
avformat_close_input(&pfmt_ctx);//关闭媒体文件
}
}
4.程序编译Makefile
obj=getaudio.o
CC=gcc
#CFLAGS=-I/home/wbyq/src_pack/ffmpeg-5.1.4/_install/include
#CFLAGS+=-L/home/wbyq/src_pack/ffmpeg-5.1.4/_install/lib
#CFLAGS+=-lavutil
CFLAGS=`pkg-config --libs libavformat libavutil libavcodec --cflags`
app:$(obj)
$(CC) $^ -o $@ $(CFLAGS)
5.相关函数介绍
5.1日志信息输出
ffmpeg中提供了日志打印函av_log,在输出调试信息时,可以先通过设置日志等级来决定输出的调试信息。
/*
leve可填写的参数:AV_LOG_DEBUG、AV_LOG_INFO、AV_LOG_WARNING、AV_LOG_ERROR等
*/
void av_log_set_level(int level);
/*
打印日志信息
形参:avcl --上下文关联指针,不用可直接填NULL
level --输出类型:AV_LOG_DEBUG、AV_LOG_INFO、AV_LOG_WARNING、AV_LOG_ERROR等
后需参数和printf一致
*/
void av_log(void* avcl, int level, const char *fmt, ...);
5.2 打开媒体文件
/*
形参:ps --打开成功返回一个AVFormatContext 上下文指针
filename --要打开的文件名
fmt --格式,填NULL可有系统根据文件名自行判断
options --可选参数,一般填NULL
返回值:成功返回0,失败返回<0
*/
int avformat_open_input(AVFormatContext **ps, const char *filename,
const AVInputFormat *fmt, AVDictionary **options)
5.3 获取流信息
/*
形参:ic --上下文指针
options --可选参数,一般填NULL
返回值:成功>=0,失败返回<0
*/
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options)
5.4 输出流信息
/*
形参:ic --上下文指针
index --流下标,一般填0
url --媒体文件名
is_output --0表示输入媒体文件,1表示输出媒体文件
*/
void av_dump_format(AVFormatContext *ic, int index,
const char *url, int is_output)
5.6 查找音视频流数据
/*
形参:
ic --上下文指针
type --媒体文件类型 AVMEDIA_TYPE_VIDEO(视频) AVMEDIA_TYPE_AUDIO(音频)
wanted_stream_nb --流编号,-1表示自动选择
related_stream --相关流,一般填-1
decoder_ret --流解码器,不清楚则填NULL
flags --暂未定义,填0即可
返回值:成功返回一个非负整数,表示对应流编号
失败返回负数
*/
int av_find_best_stream(AVFormatContext *ic, enum AVMediaType type,
int wanted_stream_nb, int related_stream,
const AVCodec **decoder_ret, int flags)