本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10584948.html

FFmpeg 编解码处理系列笔记:

[0]. FFmpeg时间戳详解

[1]. FFmpeg编解码处理1-转码全流程简介

[2]. FFmpeg编解码处理2-编解码API详解

[3]. FFmpeg编解码处理3-视频编码

[4]. FFmpeg编解码处理4-音频编码

基于 FFmpeg 4.1 版本。

6. 音频编码

编码使用 avcodec_send_frame() 和 avcodec_receive_packet() 两个函数。

音频编码的步骤:

[1] 初始化打开输出文件时构建编码器上下文

[2] 音频帧编码

[2.1] 将滤镜输出的音频帧写入音频 FIFO

[2.2] 按音频编码器中要求的音频帧尺寸从音频 FIFO 中取出音频帧

[2.3] 为音频帧生成 pts

[2.4] 将音频帧送入编码器,从编码器取出编码帧

[2.5] 更新编码帧流索引

[2.6] 将帧中时间参数按输出封装格式的时间基进行转换

6.1 打开视频编码器

完整源码在 open_output_file() 函数中,下面摘出关键部分:

    // 3. 构建AVCodecContext
if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO ||
dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) // 音频流或视频流
{
// 3.1 查找编码器AVCodec,本例使用与解码器相同的编码器
AVCodec *encoder = NULL;
if ((dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) && (strcmp(v_enc_name, "copy") != 0))
{
encoder = avcodec_find_encoder_by_name(v_enc_name);
}
else if ((dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) && (strcmp(a_enc_name, "copy") != 0))
{
encoder = avcodec_find_encoder_by_name(a_enc_name);
}
else
{
encoder = avcodec_find_encoder(dec_ctx->codec_id);
} if (!encoder)
{
av_log(NULL, AV_LOG_FATAL, "Necessary encoder not found\n");
return AVERROR_INVALIDDATA;
}
// 3.2 AVCodecContext初始化:分配结构体,使用AVCodec初始化AVCodecContext相应成员为默认值
AVCodecContext *enc_ctx = avcodec_alloc_context3(encoder);
if (!enc_ctx)
{
av_log(NULL, AV_LOG_FATAL, "Failed to allocate the encoder context\n");
return AVERROR(ENOMEM);
} // 3.3 AVCodecContext初始化:配置图像/声音相关属性
/* In this example, we transcode to same properties (picture size,
* sample rate etc.). These properties can be changed for output
* streams easily using filters */
if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO)
{
enc_ctx->height = dec_ctx->height; // 图像高
enc_ctx->width = dec_ctx->width; // 图像宽
enc_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio; // 采样宽高比:像素宽/像素高
/* take first format from list of supported formats */
if (encoder->pix_fmts) // 编码器支持的像素格式列表
{
enc_ctx->pix_fmt = encoder->pix_fmts[0]; // 编码器采用所支持的第一种像素格式
}
else
{
enc_ctx->pix_fmt = dec_ctx->pix_fmt; // 编码器采用解码器的像素格式
}
/* video time_base can be set to whatever is handy and supported by encoder */
enc_ctx->time_base = av_inv_q(dec_ctx->framerate); // 时基:解码器帧率取倒数
enc_ctx->framerate = dec_ctx->framerate;
//enc_ctx->bit_rate = dec_ctx->bit_rate; /* emit one intra frame every ten frames
* check frame pict_type before passing frame
* to encoder, if frame->pict_type is AV_PICTURE_TYPE_I
* then gop_size is ignored and the output of encoder
* will always be I frame irrespective to gop_size
*/
//enc_ctx->gop_size = 10;
//enc_ctx->max_b_frames = 1;
}
else
{
enc_ctx->sample_rate = dec_ctx->sample_rate; // 采样率
enc_ctx->channel_layout = dec_ctx->channel_layout; // 声道布局
enc_ctx->channels = av_get_channel_layout_nb_channels(enc_ctx->channel_layout); // 声道数量
/* take first format from list of supported formats */
enc_ctx->sample_fmt = encoder->sample_fmts[0]; // 编码器采用所支持的第一种采样格式
enc_ctx->time_base = (AVRational){1, enc_ctx->sample_rate}; // 时基:编码器采样率取倒数
// enc_ctx->codec->capabilities |= AV_CODEC_CAP_VARIABLE_FRAME_SIZE; // 只读标志 // 初始化一个FIFO用于存储待编码的音频帧,初始化FIFO大小的1个采样点
// av_audio_fifo_alloc()第二个参数是声道数,第三个参数是单个声道的采样点数
// 采样格式及声道数在初始化FIFO时已设置,各处涉及FIFO大小的地方都是用的单个声道的采样点数
pp_audio_fifo[i] = av_audio_fifo_alloc(enc_ctx->sample_fmt, enc_ctx->channels, 1);
if (pp_audio_fifo == NULL)
{
av_log(NULL, AV_LOG_ERROR, "Could not allocate FIFO\n");
return AVERROR(ENOMEM);
}
} if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
{
enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
} // 3.4 AVCodecContext初始化:使用AVCodec初始化AVCodecContext,初始化完成
/* Third parameter can be used to pass settings to encoder */
ret = avcodec_open2(enc_ctx, encoder, NULL);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot open video encoder for stream #%u\n", i);
return ret;
}
// 3.5 设置输出流codecpar
ret = avcodec_parameters_from_context(out_stream->codecpar, enc_ctx);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Failed to copy encoder parameters to output stream #%u\n", i);
return ret;
} // 3.6 保存输出流contex
pp_enc_ctx[i] = enc_ctx;
}

6.2 判断是否需要音频 FIFO

完整源码在 main() 函数中,下面摘出关键部分:

    if (codec_type == AVMEDIA_TYPE_AUDIO) {
if (((stream.o_codec_ctx->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE) == 0) &&
(stream.i_codec_ctx->frame_size != stream.o_codec_ctx->frame_size))
{
stream.aud_fifo = oafifo[stream_index];
ret = transcode_audio_with_afifo(&stream, &ipacket);
}
else
{
ret = transcode_audio(&stream, &ipacket);
}
}

解码过程中的音频帧尺寸:

AVCodecContext.frame_size 表示音频帧中每个声道包含的采样点数。当编码器 AV_CODEC_CAP_VARIABLE_FRAME_SIZE 标志有效时,音频帧尺寸是可变的,AVCodecContext.frame_size 值可能为 0;否则,解码器的 AVCodecContext.frame_size 等于解码帧中的 AVFrame.nb_samples。

编码过程中的音频帧尺寸:

上述代码中第一个判断条件是 "(stream.o_codec_ctx->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE) == 0)", 第二个判断条件是 "(stream.i_codec_ctx->frame_size != stream.o_codec_ctx->frame_size)"。如果编码器不支持可变尺寸音频帧(第一个判断条件生效),而原始音频帧的尺寸又和编码器帧尺寸不一样(第二个判断条件生效),则需要引入音频帧 FIFO,以保证每次从 FIFO 中取出的音频帧尺寸和编码器帧尺寸一样。音频 FIFO 输出的音频帧不含时间戳信息,因此需要重新生成时间戳。

引入音频FIFO的原因:

如果编码器不支持可变长度帧,而编码器输入音频帧尺寸和编码器要求的音频帧尺寸不一样,就会编码失败。比如,AAC 音频格式转 MP2 音频格式,AAC 格式音频帧尺寸为 1024,而 MP2 音频编码器要求音频帧尺寸为 1152,编码会失败;再比如 AAC 格式转码 AAC 格式,某些 AAC 音频帧为 2048,而此时若 AAC 音频编码器要求音频帧尺寸为 1024,编码就会失败。解决这个问题的方法有两个,一是进行音频重采样,使音频帧转换为编码器支持的格式;另一个是引入音频 FIFO,一端写一端读,每次从读端取出编码器要求的帧尺寸即可。

AAC 音频帧尺寸可能是 1024,也可能是 2048,参考“FFmpeg关于nb_smples,frame_size以及profile的解释”

6.3 音频 FIFO 接口函数

本节代码参考 "https://github.com/FFmpeg/FFmpeg/blob/n4.1/doc/examples/transcode_aac.c" 实现

/**
* Initialize one input frame for writing to the output file.
* The frame will be exactly frame_size samples large.
* @param[out] frame Frame to be initialized
* @param output_codec_context Codec context of the output file
* @param frame_size Size of the frame
* @return Error code (0 if successful)
*/
static int init_audio_output_frame(AVFrame **frame,
AVCodecContext *occtx,
int frame_size)
{
int error; /* Create a new frame to store the audio samples. */
if (!(*frame = av_frame_alloc()))
{
fprintf(stderr, "Could not allocate output frame\n");
return AVERROR_EXIT;
} /* Set the frame's parameters, especially its size and format.
* av_frame_get_buffer needs this to allocate memory for the
* audio samples of the frame.
* Default channel layouts based on the number of channels
* are assumed for simplicity. */
(*frame)->nb_samples = frame_size;
(*frame)->channel_layout = occtx->channel_layout;
(*frame)->format = occtx->sample_fmt;
(*frame)->sample_rate = occtx->sample_rate; /* Allocate the samples of the created frame. This call will make
* sure that the audio frame can hold as many samples as specified. */
// 为AVFrame分配缓冲区,此函数会填充AVFrame.data和AVFrame.buf,若有需要,也会填充
// AVFrame.extended_data和AVFrame.extended_buf,对于planar格式音频,会为每个plane
// 分配一个缓冲区
if ((error = av_frame_get_buffer(*frame, 0)) < 0)
{
fprintf(stderr, "Could not allocate output frame samples (error '%s')\n",
av_err2str(error));
av_frame_free(frame);
return error;
} return 0;
} // FIFO中可读数据小于编码器帧尺寸,则继续往FIFO中写数据
static int write_frame_to_audio_fifo(AVAudioFifo *fifo,
uint8_t **new_data,
int new_size)
{
int ret = av_audio_fifo_realloc(fifo, av_audio_fifo_size(fifo) + new_size);
if (ret < 0)
{
fprintf(stderr, "Could not reallocate FIFO\n");
return ret;
} /* Store the new samples in the FIFO buffer. */
ret = av_audio_fifo_write(fifo, (void **)new_data, new_size);
if (ret < new_size)
{
fprintf(stderr, "Could not write data to FIFO\n");
return AVERROR_EXIT;
} return 0;
} static int read_frame_from_audio_fifo(AVAudioFifo *fifo,
AVCodecContext *occtx,
AVFrame **frame)
{
AVFrame *output_frame;
// 如果FIFO中可读数据多于编码器帧大小,则只读取编码器帧大小的数据出来
// 否则将FIFO中数据读完。frame_size是帧中单个声道的采样点数
const int frame_size = FFMIN(av_audio_fifo_size(fifo), occtx->frame_size); /* Initialize temporary storage for one output frame. */
// 分配AVFrame及AVFrame数据缓冲区
int ret = init_audio_output_frame(&output_frame, occtx, frame_size);
if (ret < 0)
{
return AVERROR_EXIT;
} // 从FIFO从读取数据填充到output_frame->data中
ret = av_audio_fifo_read(fifo, (void **)output_frame->data, frame_size);
if (ret < frame_size)
{
fprintf(stderr, "Could not read data from FIFO\n");
av_frame_free(&output_frame);
return AVERROR_EXIT;
} *frame = output_frame; return ret;
}

6.4 编码音频帧

完整源码在 transcode_audio_with_afifo() 函数中,下面摘出关键部分:

    // 2. 滤镜处理
ret = filtering_frame(sctx->flt_ctx, frame_dec, frame_flt);
if (ret == AVERROR_EOF) // 滤镜已冲洗
{
flt_finished = true;
av_log(NULL, AV_LOG_INFO, "filtering aframe EOF\n");
frame_flt = NULL;
}
else if (ret < 0)
{
av_log(NULL, AV_LOG_INFO, "filtering aframe error %d\n", ret);
goto end;
} // 3. 使用音频fifo,从而保证每次送入编码器的音频帧尺寸满足编码器要求
// 3.1 将音频帧写入fifo,音频帧尺寸是解码格式中音频帧尺寸
if (!dec_finished)
{
uint8_t** new_data = frame_flt->extended_data; // 本帧中多个声道音频数据
int new_size = frame_flt->nb_samples; // 本帧中单个声道的采样点数 // FIFO中可读数据小于编码器帧尺寸,则继续往FIFO中写数据
ret = write_frame_to_audio_fifo(p_fifo, new_data, new_size);
if (ret < 0)
{
av_log(NULL, AV_LOG_INFO, "write aframe to fifo error\n");
goto end;
}
} // 3.2 从fifo中取出音频帧,音频帧尺寸是编码格式中音频帧尺寸
// FIFO中可读数据大于编码器帧尺寸,则从FIFO中读走数据进行处理
while ((av_audio_fifo_size(p_fifo) >= enc_frame_size) || dec_finished)
{
bool flushing = dec_finished && (av_audio_fifo_size(p_fifo) == 0); // 已取空,刷洗编码器 if (frame_enc != NULL)
{
av_frame_free(&frame_enc);
} if (!flushing)
{
// 从FIFO中读取数据,编码,写入输出文件
ret = read_frame_from_audio_fifo(p_fifo, sctx->o_codec_ctx, &frame_enc);
if (ret < 0)
{
av_log(NULL, AV_LOG_INFO, "read aframe from fifo error\n");
goto end;
} // 4. fifo中读取的音频帧没有时间戳信息,重新生成pts
frame_enc->pts = s_pts;
s_pts += ret;
} flush_encoder:
// 5. 编码
ret = av_encode_frame(sctx->o_codec_ctx, frame_enc, &opacket);
if (ret == AVERROR(EAGAIN)) // 需要获取新的frame喂给编码器
{
//av_log(NULL, AV_LOG_INFO, "encode aframe need more packet\n");
if (frame_enc != NULL)
{
av_frame_free(&frame_enc);
}
continue;
}
else if (ret == AVERROR_EOF)
{
av_log(NULL, AV_LOG_INFO, "encode aframe EOF\n");
enc_finished = true;
goto end;
} // 5.1 更新编码帧中流序号,并进行时间基转换
// AVPacket.pts和AVPacket.dts的单位是AVStream.time_base,不同的封装格式其AVStream.time_base不同
// 所以输出文件中,每个packet需要根据输出封装格式重新计算pts和dts
opacket.stream_index = sctx->stream_idx;
av_packet_rescale_ts(&opacket, sctx->o_codec_ctx->time_base, sctx->o_stream->time_base); av_log(NULL, AV_LOG_DEBUG, "Muxing frame\n"); // 6. 将编码后的packet写入输出媒体文件
ret = av_interleaved_write_frame(sctx->o_fmt_ctx, &opacket);
if (ret < 0)
{
av_log(NULL, AV_LOG_INFO, "write aframe error %d\n", ret);
goto end;
} if (flushing)
{
goto flush_encoder;
}
}

FFmpeg编解码处理4-音频编码的更多相关文章

  1. FFmpeg编解码处理3-视频编码

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10584937.html FFmpeg编解码处理系列笔记: [0]. FFmpeg时间戳详 ...

  2. FFmpeg编解码处理2-编解码API详解

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10584925.html FFmpeg编解码处理系列笔记: [0]. FFmpeg时间戳详 ...

  3. FFmpeg编解码处理1-转码全流程简介

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10584901.html FFmpeg编解码处理系列笔记: [0]. FFmpeg时间戳详 ...

  4. ffmpeg 编解码详细过程

    ffmpeg编解码详细过程     bobbypollo 转:ffmpeg编解码详细过程 原文地址:ffmpeg编解码详细过程(转)作者:心在飞翔原文出处: http://www.360doc.com ...

  5. 最近在研究FFmpeg编解码

    好几年没上CNBLOGS了, 最近在研究FFmpeg编解码,一个人研究感到很寂寞,所以想通过博客来和大家分享和交流,呵呵. 最近研究的主题是: ANDROID手机同屏技术: 需要用到ANDROID截屏 ...

  6. ffmpeg编解码音频AAC

    本次项目的需求:手机端和PC端共享同一个音视频网络源. 所以编解码需要满足手机上编码和解码原来PC端的音视频流. 这里先封装安卓手机端音频的编解码. 编译工作依然是在linux下 ubuntu 12. ...

  7. ffmpeg编解码视频导致噪声增大的一种解决方法

    一.前言 ffmpeg在视音频编解码领域算是一个比较成熟的解决方案了.公司的一款视频编辑软件正是基于ffmpeg做了二次封装,并在此基础上进行音视频的编解码处理.然而,在观察编码后的视频质量时,发现图 ...

  8. 流媒体技术学习笔记之(六)FFmpeg官方文档先进音频编码(AAC)

    先进音频编码(AAC)的后继格式到MP3,和以MPEG-4部分3(ISO / IEC 14496-3)被定义.它通常用于MP4容器格式; 对于音乐,通常使用.m4a扩展名.第二最常见的用途是在MKV( ...

  9. FFmpeg 学习(五):FFmpeg 编解码 API 分析

    在上一篇文章 FFmpeg学习(四):FFmpeg API 介绍与通用 API 分析 中,我们简单的讲解了一下FFmpeg 的API基本概念,并分析了一下通用API,本文我们将分析 FFmpeg 在编 ...

随机推荐

  1. Pivot For和UNPivot For

    一.使用PIVOT和UNPIVOT命令的SQL Server版本要求 1.数据库的最低版本要求为SQL Server 2005 或更高. 2.必须将数据库的兼容级别设置为90 或更高. 3.查看我的数 ...

  2. ibatis (六) dynamic的用法

    view plain copy print? dynamic可以去除第一个prepend="and"中的字符(这里为and),从而可以帮助你实现一些很实用的功能.具体情况如下: 1 ...

  3. MD5加密算法的Java版本

    网上搜索Java实现MD5的资料很多,错误的也很多. 之前编写的一个阿里云直播鉴权原理算法需要用到MD5算法,网上找了几个,都是不行,浪费了时间,现在贴一个,做备用. import java.secu ...

  4. python发送邮箱

    一.Python发送邮件 import smtplib from email.mime.text import MIMEText def send(email_to,title,content): m ...

  5. tensorflow学习之(二)Seesion的两种打开模式

    #Seesion的两种打开模式 import tensorflow as tf matrix1 = tf.constant([[3,3]])#一行两列的一个矩阵 matrix2 = tf.consta ...

  6. OpenCV图像分割2

    1.GrubCut算法 2.K-means聚类算法 3.分水岭算法

  7. 完整的SOPC开发流程体验

    课程目标:学习并掌握完整的SOPC开发流程. 开发环境:Quartus15.1 学习内容:1.使用QSYS工具建立能够运行流水灯项目的NIOS II处理器系统 2.在quartus ii中添加NIOS ...

  8. 8.AOP全自动

    CGLIB字节码增强 l没有接口,只有实现类. 采用字节码增强框架 cglib,在运行时 创建目标类的子类,从而对目标类进行增强. 导入jar包: 自己导包(了解): 核心:hibernate-dis ...

  9. 一些有价值的Blog推荐

    待看的一些文章 1. 性能调优攻略 http://coolshell.cn/articles/7490.html 2. 内存的存储管理--段式和页式管理的区别 http://blog.sina.com ...

  10. Centos 执行top命令详细解读

    1.作用:top命令用来显示执行中的程序进程,使用权限是所有用户. 2.格式:top [-] [d delay] [q] [c] [S] [s] [i] [n] 3.主要参数: d:指定更新的间隔,以 ...