由于 FFmpeg 只支持H264+AAC的mp4封装格式的,并不支持H264+G711的mp4封装格式。所以需要将G711a转码成AAC格式的,然后封装成mp4文件,但网上有说 通过修改movenc.c文件, 重新编译ffmpeg,能支持H264+G711a的 ,但我尝试编译,没有成功,你们可以尝试编译一下,要是成功了,希望能一起分享一下,哈哈。
文章链接:https://blog.csdn.net/zhuyunier/article/details/80814227
1.首先是将G711a转码成AAC。基于该类库: https://github.com/EasyDarwin/EasyAACEncoder
InitParam initParam;
initParam.u32AudioSamplerate=8000;
initParam.ucAudioChannel=1;
initParam.u32PCMBitSize=16;
initParam.ucAudioCodec = Law_ALaw;
//initParam.ucAudioCodec = Law_ULaw;
EasyAACEncoder_Handle handle = Easy_AACEncoder_Init( initParam);
char* infilename = "src.g711a"; //标准
char* outAacname = "g711.aac";
FILE* fpIn = fopen(infilename, "rb");
if(NULL == fpIn)
{
printf("%s:[%d] open %s file failed\n",__FUNCTION__,__LINE__,infilename);
return -1;
}
FILE* fpOut = fopen(outAacname, "wb");
if(NULL == fpOut)
{
printf("%s:[%d] open %s file failed\n",__FUNCTION__,__LINE__,outAacname);
return -1;
}
int gBytesRead = 0;
int bG711ABufferSize = 500;
int bAACBufferSize = 4*bG711ABufferSize;//提供足够大的缓冲区
unsigned char *pbG711ABuffer = (unsigned char *)malloc(bG711ABufferSize *sizeof(unsigned char));
unsigned char *pbAACBuffer = (unsigned char*)malloc(bAACBufferSize * sizeof(unsigned char));
unsigned int out_len = 0;
while((gBytesRead = fread(pbG711ABuffer, 1, bG711ABufferSize, fpIn)) >0)
{
if(Easy_AACEncoder_Encode(handle, pbG711ABuffer, gBytesRead, pbAACBuffer, &out_len) > 0)
{
fwrite(pbAACBuffer, 1, out_len, fpOut);
}
else
{
}
}
Easy_AACEncoder_Release(handle);
free(pbG711ABuffer);
free(pbAACBuffer);
fclose(fpIn);
fclose(fpOut);
2. 将H264 + AAC保存到MP4文件 参考:https://www.jianshu.com/p/e62dc6928941
1)创建文件
int FFmpegTool::CreateMp4(const char* filename)
{
int ret; // 成功返回0,失败返回1
const char* pszFileName = filename;
AVOutputFormat *fmt;
AVCodec *video_codec;
AVStream *m_pVideoSt;
AVCodec *audio_codec;
AVStream *m_pAudioSt;
av_register_all();
avformat_alloc_output_context2(&m_pOc, NULL, NULL, pszFileName);
if (!m_pOc)
{
printf("Could not deduce output format from file extension: using MPEG. \n");
avformat_alloc_output_context2(&m_pOc, NULL, "mpeg", pszFileName);
}
if (!m_pOc)
{
return 1;
}
fmt = m_pOc->oformat;
if (fmt->video_codec != AV_CODEC_ID_NONE)
{
// 添加视频和音频流信息
m_pVideoSt = add_stream(m_pOc, &video_codec, AV_CODEC_ID_H264, 0);
m_pAudioSt = add_stream(m_pOc, &audio_codec, AV_CODEC_ID_AAC, 1);
}
// 打开视音频流
if (m_pAudioSt)
{
open_audio(m_pOc, audio_codec, m_pAudioSt);
}
if (m_pVideoSt)
{
open_video(m_pOc, video_codec, m_pVideoSt);
}
printf("==========Output Information==========\n");
av_dump_format(m_pOc, 0, pszFileName, 1);
printf("======================================\n");
/* open the output file, if needed */
if (!(fmt->flags & AVFMT_NOFILE))
{
// 打开输出流
ret = avio_open(&m_pOc->pb, pszFileName, AVIO_FLAG_WRITE);
if (ret < 0)
{
printf("could not open %s\n", pszFileName);
return 1;
}
}
/* Write the stream header, if any */
ret = avformat_write_header(m_pOc, NULL);
if (ret < 0)
{
printf("Error occurred when opening output file");
return 1;
}
}
bool isIdrFrame(uint8_t* buf, int len) {
switch (buf[0] & 0x1f) {
case 7: // SPS
return true;
case 8: // PPS
return true;
case 5:
return true;
case 1:
return false;
default:
return false;
break;
}
return false;
}
// 判断关键帧
bool FFmpegTool::judgeKeyFrame(uint8_t* buf, int size) {
//主要是解析idr前面的sps pps
int last = 0;
for (int i = 2; i <= size; ++i) {
if (i == size) {
if (last) {
bool ret = isIdrFrame(buf + last, i - last);
if (ret) {
return true;
}
}
}
else if (buf[i - 2] == 0x00 && buf[i - 1] == 0x00 && buf[i] == 0x01) {
if (last) {
int size = i - last - 3;
if (buf[i - 3]) ++size;
bool ret = isIdrFrame(buf + last, size);
if (ret) {
return true;
}
}
last = i + 1;
}
}
return false;
}
// 输出流中添加视音频流
AVStream * FFmpegTool::add_stream(AVFormatContext *oc, AVCodec **codec, enum AVCodecID codec_id, int type)
{
AVCodecContext *c;
AVStream *st;
/* find the encoder */
*codec = avcodec_find_encoder(codec_id);
if (!*codec)
{
printf("could not find encoder for '%s' \n", avcodec_get_name(codec_id));
exit(1);
}
st = avformat_new_stream(oc, *codec);
if (!st)
{
printf("could not allocate stream \n");
exit(1);
}
st->id = oc->nb_streams - 1;
c = st->codec;
if (type == 0)
m_vi_nstream = st->index;
else
m_ai_nstream = st->index;
AVRational time_base;
switch ((*codec)->type)
{
case AVMEDIA_TYPE_AUDIO:
c->sample_fmt = AV_SAMPLE_FMT_S16P;
//c->bit_rate = 128000;
c->sample_rate = m_sample_rate;
c->channels = m_channel;
c->codec_id = AV_CODEC_ID_AAC;
c->channel_layout = AV_CH_LAYOUT_STEREO;
time_base = { 1, c->sample_rate };
st->time_base = time_base;
break;
case AVMEDIA_TYPE_VIDEO:
c->codec_id = AV_CODEC_ID_H264;
c->bit_rate = m_bit_rate;
c->width = m_width;
c->height = m_height;
c->time_base.den = m_fps;
c->time_base.num = 1;
c->gop_size = 1;
c->pix_fmt = AV_PIX_FMT_YUV420P;
time_base = { 1, m_fps };
st->time_base = time_base;
if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO)
{
c->max_b_frames = 2;
}
if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO)
{
c->mb_decision = 2;
}
break;
default:
break;
}
if (oc->oformat->flags & AVFMT_GLOBALHEADER)
{
c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
return st;
}
// 打开视频编码器
void FFmpegTool::open_video(AVFormatContext *oc, AVCodec *codec, AVStream *st)
{
int ret;
AVCodecContext *c = st->codec;
/* open the codec */
ret = avcodec_open2(c, codec, NULL);
if (ret < 0)
{
printf("could not open video codec:%d", ret);
}
}
// 打开音频编码器
void FFmpegTool::open_audio(AVFormatContext *oc, AVCodec *codec, AVStream *st)
{
int ret;
AVCodecContext *c = st->codec;
/* open the codec */
ret = avcodec_open2(c, codec, NULL);
if (ret < 0)
{
printf("could not open audio codec:%d", ret);
//exit(1);
}
}
2) 写入音视频
// 写入视频
void FFmpegTool::WriteVideo(void* data, int nLen, int type)
{
AVStream *pst;
// 分为视频流和音频流
if (type == 0)
pst = m_pOc->streams[m_vi_nstream];
else
pst = m_pOc->streams[m_ai_nstream];
// Init packet
AVPacket pkt;
av_init_packet(&pkt);
int isI = judgeKeyFrame((uint8_t*)data, nLen);
pkt.flags |= isI ? AV_PKT_FLAG_KEY : 0;
pkt.stream_index = pst->index; // (int)packet在stream的index位置
pkt.data = (uint8_t*)data;
pkt.size = nLen;
// 第一帧为关键帧
if (m_waitkey) {
if (0 == (pkt.flags & AV_PKT_FLAG_KEY)) {
return;
}
else
m_waitkey = 0;
}
// 计算每一帧的长度
int64_t calc_duration = (double)AV_TIME_BASE / m_fps;
// 计算该帧的显示时间戳
pkt.pts = (double)(m_frame_index*calc_duration) / (double)(av_q2d(pst->time_base)*AV_TIME_BASE);
// 解码时间戳和显示时间戳相等 因为视频中没有b帧
pkt.dts = pkt.pts;
// 帧的时长
pkt.duration = (double)calc_duration / (double)(av_q2d(pst->time_base)*AV_TIME_BASE);
if (type == 0) {
// 一帧一帧计算
cur_pts_v = pkt.pts;
m_frame_index++;
}
else
{ // 音频帧和视频帧同步
cur_pts_a = pkt.pts;
}
// 换算时间戳 (换算成已输出流中的时间基为单位的显示时间戳)
pkt.pts = av_rescale_q_rnd(pkt.pts, pst->time_base, pst->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.dts = av_rescale_q_rnd(pkt.dts, pst->time_base, pst->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.duration = av_rescale_q(pkt.duration, pst->time_base, pst->time_base);
pkt.pos = -1;
pkt.stream_index = pst->index;
if (av_interleaved_write_frame(m_pOc, &pkt) < 0) {
printf("cannot write frame\n");
}
av_free_packet(&pkt);
}
3) 关闭视频
void FFmpegTool::CloseMp4()
{
m_waitkey = -1;
m_vi_nstream = -1;
m_ai_nstream = -1;
if (m_pOc)
av_write_trailer(m_pOc);
if (m_pOc && !(m_pOc->oformat->flags & AVFMT_NOFILE))
avio_close(m_pOc->pb);
if (m_pOc)
{
avformat_free_context(m_pOc);
m_pOc = NULL;
}
}
以上是一些主要的代码实现,大家可以参考下。里面一些参数还需要自己根据情况设置一下,比如音频的采样率 ,音频的通道,视频的帧率,分辨率等等。代码里我把我理解的地方基本都写上注释了,如果有不正确的地方,大家也多多指教,谢谢。
注: 由于公司项目,无法全部上传,所以只能上传主要实现代码。