新版的ffmpeg对音频编码处理已经有了很大的变化,记录在此,做个备忘。
早期ffmpeg编码音频,输入数据一般都是S16格式,解码输出一般也是S16,也就是说PCM数据是存储在连续的buffer中,对一个双声道(左右)音频来说,存储格式可能就为
LRLRLR.........(左声道在前还是右声道在前没有认真研究过)。所以以往编码部分的代码基本形如:

int sample_bytes = av_get_bytes_per_sample(pCodecCtx->sample_fmt);

int frame_bytes = pCodecCtx->frame_size * sample_bytes * pCodecCtx->channels;

// AVFifoBuffer* fifo;    存放pcm数据
    while(av_fifo_size(fifo) >= frame_bytes) {
        av_fifo_generic_read(fifo, inputBuf, frame_bytes, NULL);

AVPacket pkt = {0};
        av_init_packet(&pkt);
        pkt.data = encodeBuf;
        pkt.size = AVCODEC_MAX_AUDIO_FRAME_SIZE;
        int got_packet = 0;

audioframe->nb_samples = pCodecCtx->frame_size;
        int samples_size = av_samples_get_buffer_size(NULL, pCodecCtx->channels,
                                                  audioframe->nb_samples,
                                                  pCodecCtx->sample_fmt, 0);
        avcodec_fill_audio_frame(audioframe, pCodecCtx->channels, pCodecCtx->sample_fmt,
                inputBuf, samples_size, 0);
        audioframe->pts = audio_sync_opts;
        audio_sync_opts = audioframe->pts + audioframe->nb_samples;

avcodec_encode_audio2(pCodecCtx, &pkt, audioframe, &got_packet);
        if (got_packet ) {
            //处理pkt,封装存储、流输出或交由上层应用
        }

}
项目中需要对音视频流进行转码输出,音频处理部分一般是先解码(得到PCM S16数据),再交由编码(MP3、AAC)

ffmpeg升级到2.1后(具体哪个版本开始的没去查,可能早几个版本就已经这样做了),音频格式增加了plane概念(呃,不是灰机,是平面)
enum AVSampleFormat {
    AV_SAMPLE_FMT_NONE = -1,
    AV_SAMPLE_FMT_U8,          ///< unsigned 8 bits
    AV_SAMPLE_FMT_S16,         ///< signed 16 bits
    AV_SAMPLE_FMT_S32,         ///< signed 32 bits
    AV_SAMPLE_FMT_FLT,         ///< float
    AV_SAMPLE_FMT_DBL,         ///< double

// 以下都是带平面格式
    AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planar
    AV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planar
    AV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planar
    AV_SAMPLE_FMT_FLTP,        ///< float, planar
    AV_SAMPLE_FMT_DBLP,        ///< double, planar

AV_SAMPLE_FMT_NB           ///< Number of sample formats. DO NOT USE if linking dynamically
};
这就有点像视频部分的YUV数据,有的带P,有的是不带P的,同样对双声道音频PCM数据,以S16P为例,存储就可能是
plane 0: LLLLLLLLLLLLLLLLLLLLLLLLLL...
plane 1: RRRRRRRRRRRRRRRRRRRRRRRRRR...
而不再是以前的连续buffer。
如mp3编码就明确规定了只使用平面格式的数据
AVCodec ff_libmp3lame_encoder = {
.....
    .capabilities          = CODEC_CAP_DELAY | CODEC_CAP_SMALL_LAST_FRAME,
    .sample_fmts           = (const enum AVSampleFormat[]) { AV_SAMPLE_FMT_S32P,
                                                             AV_SAMPLE_FMT_FLTP,
                                                             AV_SAMPLE_FMT_S16P,
                                                             AV_SAMPLE_FMT_NONE },
....
};
而AAC编码依旧使用 AV_SAMPLE_FMT_S16格式
也就说,音频编码不能再像以前那样简单的处理,统一输入S16数据,而要根据具体的codec转化为其支持的格式,否则无论是编码还是解码输出的声音会莫名其妙,幸好,转换工作不用自己做,ffmpeg提供了相应的API,swr_convert(类似以前的audio_resample,只是audio_resample目前已不再推荐使用,因为swr_convert更强大)
基于此,对音频编码部分做了相应修改,主要用的数据结构为 struct SwrContext* m_SwrCtx;
step 1:判断是否需要进行convert,初始化阶段
if (pCodecCtx->channels != pInputCtx->channels
|| pCodecCtx->sample_rate != pInputCtx->sample_rate
|| pCodecCtx->sample_fmt != pInputCtx->sample_fmt)
{
u::Log::write(get_log_file(), "Audio need resample!");
if ( NULL == m_SwrCtx ) {
m_SwrCtx = swr_alloc();
}
#if LIBSWRESAMPLE_VERSION_MINOR >= 17 // 根据版本不同,选用适当函数
av_opt_set_int(m_SwrCtx, "ich", pInputCtx->channels, 0);
av_opt_set_int(m_SwrCtx, "och", pCodecCtx->channels, 0);
av_opt_set_int(m_SwrCtx, "in_sample_rate",  pInputCtx->sample_rate, 0);
av_opt_set_int(m_SwrCtx, "out_sample_rate",  pCodecCtx->sample_rate, 0);
av_opt_set_sample_fmt(m_SwrCtx, "in_sample_fmt", pInputCtx->sample_fmt, 0);
av_opt_set_sample_fmt(m_SwrCtx, "out_sample_fmt", pCodecCtx->sample_fmt, 0);

#else
m_SwrCtx = swr_alloc_set_opts(m_SwrCtx,
pInputCtx->channel_layout, AV_SAMPLE_FMT_S16, pInputCtx->sample_rate,
pInputCtx->channel_layout, pInputCtx->sample_fmt, pInputCtx->sample_rate,
0, NULL);
#endif
swr_init(m_SwrCtx);
if (av_sample_fmt_is_planar(pCodecCtx->sample_fmt)) {
//如果是分平面数据,为每一声道分配一个fifo,单独存储各平面数据
for (int i = 0; i < pCodecCtx->channels; i++){
m_fifo[i] = av_fifo_alloc(BUF_SIZE_20K);
}
} else {
//不分平面,所有的数据只要一个fifo就够了,其实用不用fifo完全看个人了,只是我觉得方便些
fifo = av_fifo_alloc(BUF_SIZE_20K);
}

}

step 2:进行转换
//以下代码部分抄自ffmpeg自带的例子
if (m_SwrCtx != NULL) {
if ( !m_audioOut ) {
ret = av_samples_alloc_array_and_samples(&m_audioOut,
&dst_samples_linesize, pCodecCtx->channels, max_dst_nb_samples, pCodecCtx->sample_fmt, 0);
if (ret < 0){
av_log(NULL, AV_LOG_WARNING, "[%s.%d %s() Could not allocate destination samples\n", __FILE__, __LINE__, __FUNCTION__);
return -1;
}
}

dst_nb_samples = av_rescale_rnd(swr_get_delay(m_SwrCtx, pCodecCtx->sample_rate) + src_nb_samples,
pCodecCtx->sample_rate, pCodecCtx->sample_rate, AV_ROUND_UP);
if (dst_nb_samples > max_dst_nb_samples) {
av_free(m_audioOut[0]);
ret = av_samples_alloc(m_audioOut, &dst_samples_linesize, pCodecCtx->channels, dst_nb_samples, pCodecCtx->sample_fmt, 0);
if (ret < 0){
av_log(NULL, AV_LOG_WARNING, "[%s.%d %s() Could not allocate samples Buffer\n", __FILE__, __LINE__, __FUNCTION__);
return -1;
}
max_dst_nb_samples = dst_nb_samples;
}

//输入也可能是分平面的,所以要做如下处理
uint8_t* m_ain[SWR_CH_MAX];
setup_array(m_ain, (uint8_t*)input_buf, data->ctx.sample_fmt, src_nb_samples);

len = swr_convert(m_SwrCtx, m_audioOut, dst_nb_samples, (const uint8_t**)m_ain, src_nb_samples);

if (len < 0) {
char errmsg[BUF_SIZE_1K];
av_strerror(len, errmsg, sizeof(errmsg));
av_log(NULL, AV_LOG_WARNING, "[%s:%d] swr_convert!(%d)(%s)", __FILE__, __LINE__, len, errmsg);
return -1;
}

paudiobuf = m_audioOut[0];
decode_size = len * pCodecCtx->channels * av_get_bytes_per_sample(pCodecCtx->sample_fmt);

} else {
paudiobuf = (uint8_t*)input_buf;
decode_size = input_size;
}

//存储PCM数据,注意:m_SwrCtx即使进行了转换,也要判断转换后的数据是否分平面
if (m_SwrCtx && av_sample_fmt_is_planar(pCodecCtx->sample_fmt) ) {
for (int i = 0; i < pCodecCtx->channels; i++){
if (av_fifo_realloc2(m_fifo[i], av_fifo_size(m_fifo[i]) + len*av_get_bytes_per_sample(pCodecCtx->sample_fmt)) < 0){
av_log(NULL, AV_LOG_FATAL, "av_fifo_realloc2() failed\n");
return -1;
}
av_fifo_generic_write(m_fifo[i], m_audioOut[0]+i*dst_samples_linesize, len*av_get_bytes_per_sample(pCodecCtx->sample_fmt), NULL);
}
} else {
if (av_fifo_realloc2(fifo, av_fifo_size(fifo) + decode_size) < 0) {
av_log(NULL, AV_LOG_FATAL, "av_fifo_realloc2() failed\n");
return -1;
}
av_fifo_generic_write(fifo, paudiobuf, decode_size, NULL);
}

setup_array函数摘自ffmpeg例程
static void setup_array(uint8_t* out[SWR_CH_MAX], uint8_t* in, int format, int samples){
if (av_sample_fmt_is_planar((AVSampleFormat)format)) {
int i;
int plane_size = av_get_bytes_per_sample((AVSampleFormat)(format & 0xFF)) * samples;
format &= 0xFF;
for (i = 0; i < SWR_CH_MAX; i++) {
out[i] = in + i*plane_size;
}
} else {
out[0] = in;
}
}

step 3:进行编码
//编码格式要求是分平面数据
if (m_SwrCtx && ( av_sample_fmt_is_planar(pCodecCtx->sample_fmt) )) {
  //这里为简单示例,只判断第一个声道(因为左右声道数据大小是一致的),实际应用中应考虑每个声道具体情况
while(av_fifo_size(m_fifo[0]) >= pCodecCtx->frame_size * sample_bytes){
for (int i = 0; i < pCodecCtx->channels; i++) {
  //inputBuf是一块连续内存
av_fifo_generic_read(m_fifo[i], inputBuf+i*pCodecCtx->frame_size * sample_bytes, pCodecCtx->frame_size * sample_bytes, NULL);
}
AVPacket pkt = {0};
av_init_packet(&pkt);
pkt.data = encodeBuf;
pkt.size = AVCODEC_MAX_AUDIO_FRAME_SIZE;
int got_packet = 0;

audioframe->nb_samples = pCodecCtx->frame_size;
int samples_size = av_samples_get_buffer_size(NULL, pCodecCtx->channels,
                                                  audioframe->nb_samples,
                                                  pCodecCtx->sample_fmt, 0);
avcodec_fill_audio_frame(audioframe, pCodecCtx->channels, pCodecCtx->sample_fmt,
inputBuf, samples_size, 0);

int ret = avcodec_encode_audio2(pCodecCtx, &pkt, audioframe, &got_packet);

if (got_packet ) {
//处理pkt
}

}

} else {
//不分平面
while(av_fifo_size(fifo) >= frame_bytes) {
av_fifo_generic_read(fifo, inputBuf, frame_bytes, NULL);

AVPacket pkt = {0};
av_init_packet(&pkt);
pkt.data = encodeBuf;
pkt.size = AVCODEC_MAX_AUDIO_FRAME_SIZE;
int got_packet = 0;

audioframe->nb_samples = pCodecCtx->frame_size;
int samples_size = av_samples_get_buffer_size(NULL, pCodecCtx->channels,
                                                  audioframe->nb_samples,
                                                  pCodecCtx->sample_fmt, 0);

avcodec_fill_audio_frame(audioframe, pCodecCtx->channels, pCodecCtx->sample_fmt,
inputBuf, samples_size, 0);

int ret = avcodec_encode_audio2(pCodecCtx, &pkt, audioframe, &got_packet);

if (got_packet ) {
//处理pkt
}

}
}

另:
对于解码也可能需要做swr_convert,比如做播放器,很多时候我们是将S16格式数据丢给声卡,而新版ffmpeg解码音频输出的格式可能不满足S16,如AAC解码后得到的是FLT(浮点型),AC3解码是FLTP(带平面)等,需要根据具体的情况决定是否需要convert,转换过程与上类似。

FFmpeg音频编解码处理相关推荐

  1. FFMPEG视音频编解码学习(1)

    ###开篇 看过了FFMPEG视音频编解码的基础知识,接着还有许多比如制作一个基于FFMPEG的视频播放器等,但是好像比较抽象,而且在公司的mac上很多软件也没有(重点是看的一头雾水), 还是老老实实 ...

  2. [总结]FFMPEG视音频编解码零基础学习方法--转

    ffmpeg编解码学习 目录(?)[-] ffmpeg程序的使用ffmpegexeffplayexeffprobeexe 1 ffmpegexe 2 ffplayexe 3 ffprobeexe ff ...

  3. 基于 FFMPEG 的音频编解码(三):音频编码

    音频编码 基于 FFMPEG 的音频编解码(一):Hello FFMPEG,安装与编译 基于 FFMPEG 的音频编解码(二):音频解码 基于 FFMPEG 的音频编解码(三):音频编码 在前面文章中 ...

  4. 基于 FFMPEG 的音频编解码(二):音频解码

    音频解码 基于 FFMPEG 的音频编解码(一):Hello FFMPEG,安装与编译 基于 FFMPEG 的音频编解码(二):音频解码 基于 FFMPEG 的音频编解码(三):音频编码 在 Hell ...

  5. 基于 FFMPEG 的音频编解码(一):Hello FFMPEG,安装与编译

    Hello FFMPEG 基于 FFMPEG 的音频编解码(一):Hello FFMPEG,安装与编译 基于 FFMPEG 的音频编解码(二):音频解码 基于 FFMPEG 的音频编解码(三):音频编 ...

  6. FFMPEG视音频编解码零基础学习方法

    总结]FFMPEG视音频编解码零基础学习方法 在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFMPEG进行视音频编解码的人,有的已经是有多年经验的"大神",有的是刚 ...

  7. 音视频开发(5)---FFMPEG视音频编解码零基础学习方法

    FFMPEG视音频编解码零基础学习方法 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/leixiaohua1020/article/details/ ...

  8. [总结]FFMPEG视音频编解码零基础学习方法

    在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFMPEG进行视音频编解码的人,有的已经是有多年经验的"大神",有的是刚开始学习的初学者.在和大家探讨的过程中,我忽然 ...

  9. [总结]FFMPEG视音频编解码零基础学习方法【转】

    本文转载自:http://blog.csdn.net/leixiaohua1020/article/details/15811977 在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFM ...

最新文章

  1. 项目总监批评程序员穿大裤衩上班情商低!程序员一气之下要离职!项目已到一半了,总监着急发帖求助!...
  2. asp.net 连接access数据库方法
  3. 安徽 职称 计算机 英语,【安徽省评职称不再考职称英语、职称计算机】- 环球网校...
  4. mysql 秀出两个相关联的表中满足条件的内容_这六个 MySQL 死锁案例,能让你理解死锁的原因!...
  5. python限定方法参数类型、返回值类型、变量类型
  6. Java黑皮书课后题第5章:5.13(求满足n的三次方<12000的n的最大值)使用while循环找出满足n的三次方<12000的n的最大值
  7. Linux之字符串截取
  8. 什么原因可能导致主备延迟?
  9. tomcat jar包_tomcat学习|tomcat中的类加载器
  10. SQLserver被js注入的全库替换SQL
  11. SharePoint 2013 关于自定义显示列表表单的bug
  12. 解决Python shell中Delete-Backspace键乱码问题
  13. 贾跃亭造车实验室首曝光:正积极推进FF91量产工作
  14. androidsdcard挂载目录_获取android手机的自带存储路径和sdcard存储路径
  15. Linux 命令(47)—— file 命令
  16. PO、VO、BO、DTO、POJO、DAO之间的关系
  17. Jquery获取表格tr对象,并循环获取表格内容
  18. 【C语言】通讯录制作
  19. pos共识机制_PoW与PoS共识机制的优缺点介绍
  20. 【图像分割】基于计算机视觉实现视网膜图像中的血管分割附matlab代码

热门文章

  1. 初二计算机辅导记录,(初中信息技术兴趣小组活动记录.doc
  2. 常量元素记忆口诀_人体中的常量元素与微量元素
  3. linux下如何使用有道词典
  4. 语言堆栈入门——堆和栈的区别
  5. Spring - Java/J2EE Application Framework 应用框架 第 5 章 Spring AOP: Spring之面向方面编程
  6. [MySQL] 索引与性能(3)- 覆盖索引
  7. opencv 中 快速傅里叶变换 FFT
  8. 【人脸识别】人脸验证算法Joint Bayesian详解及实现(Python版)
  9. 跟我一起写 Makefile(八)
  10. 观点 | 云原生时代来袭 下一代云数据库技术将走向何方?