关于从ffmpeg中分离mp4 muxer
找到mp4 muxer源文件
因为为了统一管理muxer的方便,每个的muxer都封装进一个数据结构AVOutputFormat。
即用AVOutputFormat这个结构体来管理muxer,到时候把这一系列的结构体放入一个链表,要找一个muxer就方便了。
在源代码中搜AVOutputFormat,即可找到mp4相关的编码器源文件。movenc.c/h
以下即为管理mp4 muxer的结构体。
AVOutputFormat ff_mp4_muxer = {
.name = "mp4",
.long_name = NULL_IF_CONFIG_SMALL("MP4 (MPEG-4 Part 14)"),
.mime_type = "application/mp4",
.extensions = "mp4",
.priv_data_size = sizeof(MOVMuxContext),
.audio_codec = AV_CODEC_ID_AAC,
.video_codec = CONFIG_LIBX264_ENCODER ?
AV_CODEC_ID_H264 : AV_CODEC_ID_MPEG4,
.write_header = mov_write_header,
.write_packet = mov_write_packet,
.write_trailer = mov_write_trailer,
.flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH,
.codec_tag = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 },
.priv_class = &mp4_muxer_class,
};
为统一接口的调用,这个结构体实现了三个方法
.write_header = mov_write_header,
.write_packet = mov_write_packet,
.write_trailer = mov_write_trailer,
也即每个muxer都要实现的三个函数。方便外部操作的时候,
通过avcodec_find_encoder (codec_id)寻找到muxer即可以调用这些函数了。
因此,mp4的muxer就是三个关键函数,
即mov_write_header,
mov_write_packet,
mov_write_trailer,
上层API 接口av_write_frame调用的即是mov_write_packet函数。
上层API 接口av_write_header调用的即是mov_write_ header函数。
上层API 接口av_write_ trailer调用的即是mov_write_ trailer函数。
av_new_stream 此函数实现在哪?
avcodec_alloc_context3
ffmpeg File Index [最全的文档。源码都可以在这里看,结构清晰]
http://cekirdek.pardus.org.tr/~ismail/ffmpeg-docs/files.html
可以迅速掌握整个库的框架结构。
或Google里搜ffmpeg AVCODEC可以搜到。看开源库时,Google尽量搜英文。如“which source file is ffmpeg mp4 muxer”。这样stackfollow,codeproject等一些网站就出来了。
create_asf()
add_asf_video_stream()
put_asf_video()
add_asf_audio_stream()
put_asf_audio()
end_asf()
close_asf()
get_asf_data()
在ffmpeg中用api写一个mp4文件的步骤
1、av_register_all()注册所有编解码器
2、分配一个AVOutFormat通过fmt = guess_format()
2s、/* allocate the output media context */
AVFormatContext *oc =av_alloc_format_context();[1]
oc->oformat =fmt;把猜到的格式信息放到这里。做初步的结构体初始化。
av_set_parameters进一步做结构体初始化
dump_format再进一步做结构体初始化。
3、add_audio_stream,add_video_stream向
即是av_new_stream的封装[1为音频流,0为视频流]。
4、写文件头
编码[包括音,视频],写数据帧
写文件尾
归纳需要的几个数据结构
AVFormatContext
AVOutputFormat
AVStream
几个重要的函数
从上而下地把它们抽取出来。
大致按照ffmpeg的结构方式去组织
能支持这个编码到mp4的例子了,就是成功了。
avformatcontext是最大的结构,它包含其他的。
它直接包含avoutputformat,avinputformat,avstream这几个结构。
avstream中包含avcodeccontext。
avcodeccontext包含avcodec和avframe。
avframe包含avpicture。
avpicture包含avpacket。
大概是这样的。不知道对不对。如果有错误,请指正。
AAC encoder之libfaac1.28接口
一、FAAC API
Open FAAC engine
faacEncHandlefaacEncOpen //返回一个FAAC的handle
(
unsignedlong nSampleRate, //采样率,单位是bps
unsignedlong nChannels, //声道,为单声道,为双声道
unsignedlong &nInputSamples, //传引用,得到每次调用编码时所应接收的原始数据长度
unsignedlong &nMaxOutputBytes //传引用,得到每次调用编码时生成的AAC数据的最大长度
);
Get/Set encoding configuration
获取编码器的配置:
faacEncConfigurationPtrfaacEncGetCurrentConfiguration//得到指向当前编码器配置的指针
(
faacEncHandlehEncoder // FAAC的handle
);
设定编码器的配置:
intFAACAPIfaacEncSetConfiguration
(
faacDecHandlehDecoder, //此前得到的FAAC的handle
faacEncConfigurationPtrconfig // FAAC编码器的配置
);
2.3 Encode
intfaacEncEncode
(
faacEncHandlehEncoder, // FAAC的handle
short *inputBuffer, // PCM原始数据
unsignedintsamplesInput, //调用faacEncOpen时得到的nInputSamples值
unsignedchar *outputBuffer,//至少具有调用faacEncOpen时得到的nMaxOutputBytes字节长度的缓冲区
unsignedintbufferSize // outputBuffer缓冲区的实际大小
);
Close FAAC engine
voidfaacEncClose
(
faacEncHandlehEncoder //此前得到的FAAC handle
);
二、流程
(一)做什么准备
采样率,声道数(双声道还是单声道?),还有你的PCM的单个样本是8位的还是16位的?
(二)开启FAAC编码器,做编码前的准备
调用faacEncOpen开启FAAC编码器后,得到了单次输入样本数nInputSamples和输出数据最大字节数nMaxOutputBytes;根据nInputSamples和nMaxOutputBytes,分别为PCM数据和将要得到的AAC数据创建缓冲区;调用faacEncGetCurrentConfiguration获取当前配置,修改完配置后,调用faacEncSetConfiguration设置新配置。
(三)开始编码
调用faacEncEncode,该准备的刚才都准备好了,很简单。
(四)善后
关闭编码器,另外别忘了释放缓冲区,如果使用了文件流,也别忘记了关闭。
三、测试程序
(一)完整代码
将PCM格式音频文件/home/michael/Development/testspace/in.pcm转至AAC格式文件/home/michael/Development/testspace/out.aac。
#include
#include
typedefunsignedlong ULONG;
typedefunsignedintUINT;
typedefunsignedcharBYTE;
typedefchar_TCHAR;
intmain(intargc,_TCHAR*argv[])
{
ULONG nSampleRate = 11025; // 采样率
UINT nChannels = 1; // 声道数
UINT nPCMBitSize = 16; // 单样本位数
ULONG nInputSamples = 0;
ULONG nMaxOutputBytes = 0;
int nRet;
faacEncHandle hEncoder;
faacEncConfigurationPtr pConfiguration;
int nBytesRead;
int nPCMBufferSize;
BYTE* pbPCMBuffer;
BYTE* pbAACBuffer;
FILE* fpIn; // PCM file for input
FILE* fpOut; // AAC file for output
fpIn = fopen("/home/michael/Development/testspace/in.pcm","rb");
fpOut = fopen("/home/michael/Development/testspace/out.aac","wb");
// (1) Open FAAC engine
hEncoder = faacEncOpen(nSampleRate,nChannels, &nInputSamples, &nMaxOutputBytes);
if(hEncoder == NULL)
{
printf("[ERROR] Failed to call faacEncOpen()\n");
return -1;
}
nPCMBufferSize = nInputSamples * nPCMBitSize / 8;
pbPCMBuffer = new BYTE [nPCMBufferSize];
pbAACBuffer = new BYTE [nMaxOutputBytes];
// (2.1) Get current encoding configuration
pConfiguration = faacEncGetCurrentConfiguration(hEncoder);
pConfiguration->inputFormat = FAAC_INPUT_16BIT;
// (2.2) Set encoding configuration
nRet = faacEncSetConfiguration(hEncoder,pConfiguration);
for(int i = 0; 1;i++)
{
//读入的实际字节数,最大不会超过nPCMBufferSize,一般只有读到文件尾时才不是这个值
nBytesRead =fread(pbPCMBuffer, 1,nPCMBufferSize,fpIn);
// 输入样本数,用实际读入字节数计算,一般只有读到文件尾时才不是nPCMBufferSize/(nPCMBitSize/8);
nInputSamples =nBytesRead / (nPCMBitSize / 8);
// (3) Encode
nRet =faacEncEncode(
hEncoder, (int*)pbPCMBuffer,nInputSamples,pbAACBuffer,nMaxOutputBytes);
fwrite(pbAACBuffer, 1,nRet,fpOut);
printf("%d: faacEncEncode returns %d\n",i,nRet);
if(nBytesRead <= 0)
{
break;
}
}
/*
while(1)
{
// (3) Flushing
nRet = faacEncEncode(hEncoder, (int*) pbPCMBuffer, 0, pbAACBuffer, nMaxOutputBytes);
if(nRet <= 0)
{
break;
}
}
*/
// (4) Close FAAC engine
nRet = faacEncClose(hEncoder);
delete[] pbPCMBuffer;
delete[] pbAACBuffer;
fclose(fpIn);
fclose(fpOut);
//getchar();
return 0;
}
(二)编译运行
将上述代码保存为“pcm2aac.cpp”文件,然后编译:
g++ pcm2aac.cpp -o pcm2aac -L/usr/local/lib -lfaac -I/usr/local/include
运行:
./pcm2aac
然后就生成了out.aac文件了,听听看吧!~
注意:
faacEncEncode为什么总是返回0?
其实是faac库在编码前,需要缓存3、4个样本(缓冲区?),continue过即可。
为0表示编码器没有输出.即:被缓存了.编码器会自己先缓存3帧之后才输出.
[1] av_alloc_format_context 是旧接口, 在最新版[2012-9-28版本]中已经成名为avformat_alloc_context 这个接口名了。你新下载的ffmpeg是1.0即最新版的,已经改成avformat_alloc_context接口了。av_alloc_format_context 当然找不到。