FFmpeg音视频开发知识点(二)

系列文章目录

FFmpeg音视频开发知识点(一)



前言

该篇讲解一下,音频编解码中的难点,以及开发过程中遇到问题,有不对的地方,欢迎大佬指正


一、AAC音频编码

在开发音频编解码AAC,我使用QAudioInput进行采样,但是采样格式只有S16(有符号16位)最接近AAC的采样,我看了下安卓采样的样本长度也是16(PS:需要和安卓终端通话),于是查找并打开编码器

	AVCodec* pCodec = avcodec_find_encoder(AV_CODEC_ID_AAC);
	if (pCodec == nullptr)
    {
        //...省略
        return;
    }

    AVCodecContext* pCodecCtx = avcodec_alloc_context3(pCodec);
    if(pCodecCtx == NULL)
    {
        //...省略
        return;
    }
    pCodecCtx->codec_id = pCodec->id;
    pCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO;
    //...省略
    int iRet = avcodec_open2(pCodecCtx, pCodec, NULL);
    if (iRet < 0)
    {
        //...省略
        return;
    }

但是会报错(忘了是查找还是打开编码器报错了🤣),后面一查,ffmpeg本身自带的aac并不支持AV_SAMPLE_FMT_S16的,有两种方式可以编码S16音频采样

1. ffmpeg编译第三方的libfdk_aac

编译libfdk_aac可以参考我这篇文章:Linux部分开源库编译,附上我的ffmepg编译的configure配置命令,具体如下:

sudo ./configure \
--prefix=/home/lzy/Project/new_project/libH323Stack_src_1.2.0/bin4 \
--extra-cflags="-I/home/lzy/Project/bin/include -Wall -fPIC" \	#第三方库的头文件路径
--extra-ldflags="-L/home/lzy/Project/bin/lib -ldl" \			#第三方库的所在路径
--disable-static \
--enable-shared \
--disable-debug \
--disable-doc \
--disable-ffplay \
--disable-ffprobe \
--disable-symver \
--enable-small \
--enable-gpl \
--enable-nonfree \
--enable-libfdk-aac \
--enable-libx264 \
--enable-libx265 \
--enable-openssl \
--enable-hardcoded-tables \
--enable-avresample \
--enable-decoder=h264 \
--enable-decoder=hevc \
--enable-decoder=mjpeg \
--enable-decoder=aac \
--enable-encoder=libx264 \
--enable-encoder=libx265 \
--enable-encoder=libfdk_aac \
--enable-encoder=mjpeg \
--enable-encoder=pcm_s16le \
--enable-decoder=pcm_s16le \
--enable-protocol=file \
--enable-protocol=rtp \
--enable-protocol=tcp \
--enable-protocol=udp \
--enable-demuxer=mp3 \
--enable-demuxer=wav \
--enable-demuxer=mpegts \
--enable-demuxer=mov \
--enable-demuxer=flv \
--enable-bsf=h264_mp4toannexb \
--enable-bsf=hevc_mp4toannexb \
--enable-bsf=aac_adtstoasc

编译之后,就可以打开AV_SAMPLE_FMT_S16采样格式的编码器了,具体如下:

    AVCodec* pCodec = avcodec_find_encoder_by_name("libfdk_aac");
    if (pCodec == nullptr)
    {
        //...省略
        return;
    }
    
    //...省略

最后,附上一个比较关键的部分,就是将S16的音频采样数据,赋值给AVFrame,之前参数不对也折腾了很久

	// 创建输入帧
    AVFrame* pS16AudioFrame = av_frame_alloc();
    if (pS16AudioFrame == nullptr)
    {
        //...省略
        return;
    }
    // frame缓冲区中的样本帧数量(由ctx->frame_size决定)
    pS16AudioFrame->nb_samples = pCodecCtx->frame_size;
    // 音频采样格式
    pS16AudioFrame->format = pCodecCtx->sample_fmt;
    // 声道布局
    pS16AudioFrame->channel_layout = pCodecCtx->channel_layout;
    pS16AudioFrame->channels = pCodecCtx->channels;
    // 采样率
    pS16AudioFrame->sample_rate = pCodecCtx->sample_rate;
    // 利用nb_samples、format、channel_layout创建frame的数据缓冲区
    int iRet = av_frame_get_buffer(pS16AudioFrame, 0);
    if (iRet < 0)
    {
        //...省略
        return;
    }
	
	//...省略
	
	// 将读取到的PCM数据填充到frame去,但要注意格式的匹配, 是planar还是packed都要区分清楚
    iRet = av_samples_fill_arrays(pS16AudioFrame->data, pS16AudioFrame->linesize,
                                     stFrame.pFrame, pS16AudioFrame->channels,
                                     pCodecCtx->frame_size, pCodecCtx->sample_fmt, 0);
  	if (iRet < 0)
    {
        //...省略
        return;
    }

2. S16重采样FLTP

	// 创建音频转换上下文
    SwrContext* pSwrCtx = swr_alloc_set_opts(NULL, pCodecCtx->channel_layout, AV_SAMPLE_FMT_FLTP, pCodecCtx->sample_rate,
                                             pCodecCtx->channel_layout, AV_SAMPLE_FMT_S16, pCodecCtx->sample_rate, 0, NULL);
    if (pSwrCtx == nullptr)
    {
        printf("无法分配音频转换上下文\n");
        return;
    }

    // 初始化音频转换上下文
    if (swr_init(pSwrCtx) < 0)
    {
        printf("音频转换上下文初始化失败\n");
        return;
    }

    // 进行音频转换
    AVFrame* pFltpAudioFrame = av_frame_alloc();
    if (pCodec == nullptr)
    {
        //...省略
        return;
    }
    pFltpAudioFrame->format = pCodecCtx->sample_fmt;
    pFltpAudioFrame->channel_layout = AV_CH_LAYOUT_STEREO;
    pFltpAudioFrame->sample_rate = pCodecCtx->sample_rate;
    pFltpAudioFrame->nb_samples = 1024; //一帧音频一通道的采样数量
    int iRet = av_frame_get_buffer(pFltpAudioFrame, 0); //给pcm分配存储空间
    if (iRet < 0)
    {
        //...省略
        return;
    }

 	//...省略PCM复制给AVFrame
 	
 	// 执行音频转换
    iRet = swr_convert_frame(pSwrCtx, pFltpAudioFrame, pS16AudioFrame);
    if(iRet < 0)
    {
        //...省略
        return;
    }

二、AAC音频解码

音频编码完成后,发送给安卓端,能够正常播放音频;现在开始解码安卓发过来的AAC音频,原本以为很快就能解决,结果发现调用avcodec_receive_frame函数一直返回-11,也就是说没有能获取到解码后的完整的一帧数据,我打印了一下返回值,发现一次都没成功;由于我发送S16的编码数据给安卓能够正常播放,且安卓采样也是S16(但是走的硬编解码);让我一度认为,安卓发过来的音频编码数据的采样格式是S16,直到我一次偶然的尝试,将

AVCodec* pCodec = avcodec_find_decoder_by_name("libfdk_aac");
// ...省略
AVCodecContext* pCodecCtx = avcodec_alloc_context3(pCodec);
// ...省略
pCodecCtx->request_sample_fmt = AV_SAMPLE_FMT_S16;

改为

AVCodec* pCodec = avcodec_find_decoder(AV_CODEC_ID_AAC);
// ...省略
AVCodecContext* pCodecCtx = avcodec_alloc_context3(pCodec);
// ...省略
pCodecCtx->request_sample_fmt = AV_SAMPLE_FMT_FLTP;

结果发现解码成功了,…,附上FLTP重采样S16代码,其实和S16重采样FLTP差不多

// 创建音频转换上下文
    SwrContext* pSwrCtx = swr_alloc_set_opts(NULL, pCodecCtx->channel_layout, AV_SAMPLE_FMT_S16, pCodecCtx->sample_rate,
                                             pCodecCtx->channel_layout, AV_SAMPLE_FMT_FLTP, pCodecCtx->sample_rate, 0, NULL);
    if (pSwrCtx == nullptr)
    {
        printf("无法分配音频转换上下文\n");
        return;
    }

    // 初始化音频转换上下文
    if (swr_init(pSwrCtx) < 0)
    {
        printf("音频转换上下文初始化失败\n");
        return;
    }

    // 进行音频转换
    AVFrame* pS16AudioFrame = av_frame_alloc();
    if (NULL == pS16AudioFrame )
    {
        printf("av_frame_alloc failed!\n");
        return ;
    }
    pS16AudioFrame->format = AV_SAMPLE_FMT_S16;
    pS16AudioFrame->channel_layout = AV_CH_LAYOUT_STEREO;
    pS16AudioFrame->sample_rate = pCodecCtx->sample_rate;
    pS16AudioFrame->nb_samples = 1024; //一帧音频一通道的采样数量
    iRet = av_frame_get_buffer(pS16AudioFrame, 0); //给pcm分配存储空间
	if(iRet < 0)
    {
        //...省略
        return;
    }
    
    // 分配一帧空间,存放解码后的一帧数据
    AVFrame* pAudioFrame = av_frame_alloc();

    //...省略
	
    // 执行音频转换
    iRet = swr_convert_frame(pSwrCtx, pS16AudioFrame, pAudioFrame);
   
    //...省略

总结

音频编解码相对来说比较简单,就AAC稍微复杂一点,如果编解码失败,大概分两种情况:
1)编解码上下文参数不对
2)传给编解码器的数据不对
另外,每个函数的返回值也要判断一下,这样出现异常,也能迅速定位所在位置

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Li_Zhi_Yao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值