今天我们来谈谈Android 环境下,使用FFmpeg录制视频的流程。
基本流程解析
使用FFmpeg录制视频的流程大体如下:
1、初始化FFmpeg
2、打开音频流、视频流
3、将PCM编码为AAC
4、将YUV编码为H264
5、写入文件
6、写入文件尾部信息
7、关闭媒体流
初始化FFmpeg
初始化FFmpeg,主要是有一下几个步骤:
1、注册所有的编码器
2、分配输出格式上下文(AVFormatContext)
3、打开视频流,并初始化视频编码器
4、打开音频流,并初始化音频编码器
5、打开视频编码器
6、打开音频编码器
7、写入文件头不信息
整个过程实现代码如下:
/**
* 初始化编码器
* @return
*/
bool CainEncoder::initEncoder() {
if (isInited) {
return true;
}
do {
AVDictionary *opt = NULL;
int ret;
av_log_set_callback(ffmpeg_log);
// 注册
av_register_all();
// 分配输出媒体上下文
avformat_alloc_output_context2(&fmt_ctx, NULL, NULL, mOutputFile);
if (fmt_ctx == NULL) {
ALOGE("fail to avformat_alloc_output_context2 for %s", mOutputFile);
break;
}
// 获取AVOutputFormat
fmt = fmt_ctx->oformat;
// 使用默认格式编码器视频流,并初始化编码器
if (fmt->video_codec != AV_CODEC_ID_NONE) {
addStream(&video_st, fmt_ctx, &video_codec, fmt->video_codec);
have_video = true;
}
// 使用默认格式编码器音频流,并初始化编码器
if (fmt->audio_codec != AV_CODEC_ID_NONE && enableAudio) {
addStream(&audio_st, fmt_ctx, &audio_codec, fmt->audio_codec);
have_audio = true;
}
if(!have_video && !have_audio) {
ALOGE("no audio or video codec found for the fmt!");
break;
}
// 打开视频编码器
if (have_video) {
openVideo(video_codec, &video_st, opt);
}
// 打开音频编码器
if (have_audio) {
openAudio(audio_codec, &audio_st, opt);
}
// 打开输出文件
ret = avio_open(&fmt_ctx->pb, mOutputFile, AVIO_FLAG_READ_WRITE);
if (ret < 0) {
ALOGE("Could not open '%s': %s", mOutputFile, av_err2str(ret));
break;
}
// 写入文件头部信息
ret = avformat_write_header(fmt_ctx, NULL);
if (ret < 0) {
ALOGE("Error occurred when opening output file: %s", av_err2str(ret));
break;
}
isInited = true;
} while (0);
// 判断是否初始化成功,如果不成功,则需要重置所有状态
if (!isInited) {
reset();
}
return isInited;
}
其中,打开媒体流的方法如下:
/**
* 添加码流
* @param ost
* @param oc
* @param codec
* @param codec_id
* @return
*/
bool CainEncoder::addStream(OutputStream *ost, AVFormatContext *oc, AVCodec **codec,
enum AVCodecID codec_id) {
AVCodecContext *context;
// 查找编码器
*codec = avcodec_find_encoder(codec_id);
if (!(*codec)) {
ALOGE("Could not find encoder for '%s'\n", avcodec_get_name(codec_id));
return false;
}
// 创建输出码流
ost->st = avformat_new_stream(oc, *codec);
if (!ost->st) {
ALOGE("Could not allocate stream\n");
return false;
}
// 绑定码流的ID
ost->st->id = oc->nb_streams - 1;
// 创建编码上下文
context = avcodec_alloc_context3(*codec);
if (!context) {
ALOGE("Could not alloc an encoding context\n");
return false;
}
// 绑定编码上下文
ost->enc = context;
// 判断编码器的类型
switch ((*codec)->type) {
// 如果创建的是音频码流,则设置音频编码器的参数
case AVMEDIA_TYPE_AUDIO:
context->sample_fmt = (*codec)->sample_fmts
? (AVSampleFormat) (*codec)->sample_fmts[0]
: AV_SAMPLE_FMT_S16;
context->bit_rate = mAudioBitRate;
context->