技术在于交流、沟通,转载请注明出处并保持作品的完整性。

原文:https://blog.csdn.net/hiwubihe/article/details/81260882

[音频编解码系列文章]

  1. 音频编解码基础
  2. FFMPEG实现音频重采样
  3. FFMPEG实现PCM编码(采用封装格式实现)
  4. FFMPEG实现PCM编码(不采用封装格式实现)
  5. FAAC库实现PCM编码
  6. FAAD库实现RAW格式AAC解码
  7. FAAD库实现RAW格式AAC封装成ADTS格式
  8. FAAD库实现ADTS格式解码
  9. FFMPEG实现对AAC解码(采用封装格式实现)
  10. FFMPEG实现对AAC解码(不采用封装格式实现)

本篇基于FFMPEG实现把PCM编码成AAC或者MP3格式的视频文件,编码的比特率都是64kbps,代码中AAC格式编码不需要音频重采样,而MP3格式编码只支持样本平行存储的方式。在编码中,首先PCM文件的格式是一种编码方式,这种是波形编码,而各种压缩算法实现本身会支持一个格式,如对通道,样本格式的要求,所以当PCM文件格式与编码实现不一致时,就需要对PCM数据重采样,然后编码。MP3格式据说编码比特率在128Kbps的情况下,高频部分损失比较严重,后面可以分析一下。本篇编码是把AAC或者MP3当做一种封装格式如MP4这种封装格式进行的,下篇将介绍一种只打开编码器,不打开封装格式,获取原始数据直接送入编码器的方式。ffmpeg编码的AAC是ADTS格式。

PCM编码AAC或者MP3代码:

/*******************************************************************************
Copyright (c) wubihe Tech. Co., Ltd. All rights reserved.
--------------------------------------------------------------------------------Date Created:   2014-10-25
Author:         wubihe QQ:1269122125 Email:1269122125@qq.com
Description:    代码实现PCM编码AAC,MP3格式
--------------------------------------------------------------------------------
Modification History
DATE          AUTHOR          DESCRIPTION
--------------------------------------------------------------------------------********************************************************************************/
#include <stdio.h>#define __STDC_CONSTANT_MACROS#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswresample/swresample.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#ifdef __cplusplus
};
#endif
#endif#define INPUT_FILE_NAME           ("huangdun_r48000_FMT_S16_c2.pcm")
//输出文件前缀
#define OUTPUT_FILE_NAME_PREFIX ("huangdun")
//输出文件后缀
//#define OUTPUT_FILE_NAME_SUFFIX   ("aac")
//输出文件后缀
#define OUTPUT_FILE_NAME_SUFFIX ("mp3")
//输出文件比特率 该值越大 音频质量越好 音质损失越小
#define OUTPUT_FILE_BIT_RATE    (64000)int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index)
{int ret;int got_frame;AVPacket enc_pkt;if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &CODEC_CAP_DELAY))return 0;while (1) {enc_pkt.data = NULL;enc_pkt.size = 0;av_init_packet(&enc_pkt);//输入视频帧为NULLret = avcodec_encode_audio2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,NULL, &got_frame);av_frame_free(NULL);if (ret < 0)break;if (!got_frame){ret=0;break;}printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n",enc_pkt.size);/* mux encoded frame */ret = av_write_frame(fmt_ctx, &enc_pkt);if (ret < 0)break;}return ret;
}int main()
{static char*pFormatName[]={"FMT_U8","FMT_S16","FMT_S32","FMT_FLT","FMT_DBL","FMT_U8P","FMT_S16P","FMT_S32P","FMT_FLTP","FMT_DBLP"};//各种不同格式对应字节数static int mapSampleBytes[AV_SAMPLE_FMT_NB]={1,2,4,4,8,1,2,4,4,8};//PCM原始数据格式uint64_t iInputLayout             = AV_CH_LAYOUT_STEREO;int      iInputChans              = av_get_channel_layout_nb_channels(iInputLayout);AVSampleFormat eInputSampleFormat   = AV_SAMPLE_FMT_S16;int        iInputSampleRate           = 48000;//不同样本格式长度int iInputSampleBytes             = mapSampleBytes[eInputSampleFormat];//PCM需要重采样的格式 部分编码器不支持原始PCM的数据格式如MP3uint64_t iOutputLayout             = AV_CH_LAYOUT_STEREO;int      iOutputChans             = av_get_channel_layout_nb_channels(iOutputLayout);AVSampleFormat eOutputSampleFormat ;int       iOutputSampleRate          = 48000;if(strcmp(OUTPUT_FILE_NAME_SUFFIX,"aac") == 0){eOutputSampleFormat    = AV_SAMPLE_FMT_S16;}else if(strcmp(OUTPUT_FILE_NAME_SUFFIX,"mp3") == 0){//MP3不支持AV_SAMPLE_FMT_S16这种格式eOutputSampleFormat   = AV_SAMPLE_FMT_S16P;}else{}//编码样本长度int iOutputSampleBytes    = mapSampleBytes[eOutputSampleFormat];//是否需要重采样bool bNeedResample = false;if(eInputSampleFormat != eOutputSampleFormat){bNeedResample = true;}//是否平面存储结构bool bPlanner      = false;if((eOutputSampleFormat>=AV_SAMPLE_FMT_U8P) &&(eOutputSampleFormat<=AV_SAMPLE_FMT_DBLP)){bPlanner      = true;}//打开输入文件FILE *pInputFile = fopen("huangdun_r48000_FMT_S16_c2.pcm", "rb");if(pInputFile == NULL){}//打开输出文件char szOutFileName[256]={0};sprintf(szOutFileName,"%s_br%d_sr%d.%s",OUTPUT_FILE_NAME_PREFIX,OUTPUT_FILE_BIT_RATE,iOutputSampleRate,OUTPUT_FILE_NAME_SUFFIX);FILE *pOutputFile = fopen(szOutFileName, "wb");//打开中间测试文件char szTempFileName[256]={0};sprintf(szTempFileName,"%s_sr%d_c1.pcm",OUTPUT_FILE_NAME_PREFIX,iOutputSampleRate);FILE *pTempFile = fopen(szTempFileName, "wb");int iReturn;///编码器操作//注册所有编解码器av_register_all();    //封装格式上下文 AVFormatContext中有AVInputFormat和AVOutputFormat//解复用时avformat_open_input()初始化AVInputFormat,复用时用户自己初始化AVOutputFormatAVFormatContext* pFormatCtx;AVOutputFormat * fmt;//Method 1.分配一个封装格式pFormatCtx = avformat_alloc_context();//根据后缀名 填充 输出格式上下文fmt = av_guess_format(NULL, szOutFileName, NULL);pFormatCtx->oformat = fmt;//Method 2.//avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_file);//fmt = pFormatCtx->oformat;//添加一个流AVStream  *audio_st = avformat_new_stream(pFormatCtx, 0);if (audio_st==NULL){return -1;}//添加一个输出路径if (avio_open(&pFormatCtx->pb,szOutFileName, AVIO_FLAG_READ_WRITE) < 0){printf("Failed to open output file!\n");return -1;}//Show some information 日志信息av_dump_format(pFormatCtx, 0, szOutFileName, 1);//初始化编码器相关结构体 获取输出流中的编码上下文AVCodecContext* pCodecCtx = audio_st->codec;pCodecCtx->codec_id       = fmt->audio_codec ;pCodecCtx->codec_type     = AVMEDIA_TYPE_AUDIO  ;//立体声pCodecCtx->channel_layout = iOutputLayout  ;pCodecCtx->channels       = iOutputChans    ;//编码比特率 AAC支持多种比特率 一般比特率越高 视频质量越好 需要传输带宽越大pCodecCtx->bit_rate         = OUTPUT_FILE_BIT_RATE; pCodecCtx->sample_rate    = iOutputSampleRate;//PCM样本深度为AV_SAMPLE_FMT_S16 但不是所有格式的编码都支持这种格式pCodecCtx->sample_fmt      = eOutputSampleFormat;//编码器AVCodec* pCodec = avcodec_find_encoder(pCodecCtx->codec_id);if (!pCodec){printf("Can not find encoder!\n");return -1;}//打开解码器 有可能失败 -22 错误,原因不同的编码格式支持的样本格式不一样//如封装AAC格式样本格式是AV_SAMPLE_FMT_FLT,打开就出错if ((iReturn = avcodec_open2(pCodecCtx, pCodec,NULL)) < 0){printf("Failed to open encoder :[%d]!\n",iReturn);return -1;}//重采用上下文SwrContext *pSwrCtx = NULL;//原始数据帧AVFrame* pRawframe  = NULL;//原始帧一Planer的大小 非平面分布的情况就是缓存总大小int iRawLineSize = 0;//原始帧缓存大小int iRawBuffSize = 0;//原始帧缓存uint8_t *pRawBuff= NULL;//重采样后数据帧AVFrame* pConvertframe = NULL;//重采样后一Planer的大小int iConvertLineSize  = 0;//重采样后缓存大小int iConvertBuffSize  = 0;//重采样后帧缓存uint8_t *pConvertBuff = NULL;//1帧数据样本数int iFrameSamples = pCodecCtx->frame_size;// 存储原始数据 iRawLineSize = 0;iRawBuffSize  = av_samples_get_buffer_size(&iRawLineSize, iInputChans, iFrameSamples, eInputSampleFormat, 0);pRawBuff = (uint8_t *)av_malloc(iRawBuffSize);//原始数据保存在AVFrame结构体中pRawframe = av_frame_alloc();pRawframe->nb_samples = iFrameSamples;pRawframe->format        = eInputSampleFormat;pRawframe->channels     = iInputChans;iReturn = avcodec_fill_audio_frame(pRawframe, iInputChans, eInputSampleFormat, (const uint8_t*)pRawBuff, iRawBuffSize, 0);if(iReturn<0){return -1;}if(bNeedResample){pSwrCtx = swr_alloc_set_opts(NULL,iOutputLayout, eOutputSampleFormat, iOutputSampleRate,iInputLayout,eInputSampleFormat , iInputSampleRate,0, NULL);swr_init(pSwrCtx);// 存储转换后数据 iConvertLineSize  = 0;iConvertBuffSize  = av_samples_get_buffer_size(&iConvertLineSize, iOutputChans, iFrameSamples, eOutputSampleFormat, 0);pConvertBuff      = (uint8_t *)av_malloc(iConvertBuffSize);//转换后数据保存在AVFrame结构体中pConvertframe = av_frame_alloc();pConvertframe->nb_samples   = iFrameSamples;pConvertframe->format        = eOutputSampleFormat;pConvertframe->channels     = iOutputChans;iReturn = avcodec_fill_audio_frame(pConvertframe, iOutputChans, eOutputSampleFormat, (const uint8_t*)pConvertBuff, iConvertBuffSize, 0);if(iReturn<0){return -1;}}//编码以后的数据是AVPacketAVPacket pkt;if(!bNeedResample){av_new_packet(&pkt,iRawBuffSize);}else{av_new_packet(&pkt,iConvertBuffSize);}//Write Headeravformat_write_header(pFormatCtx,NULL);//统计读取样本数long long lReadTotalSamples = 0;//每次读取样本数int iReadSamples;//统计所有的帧数int iFrameNum =0;//是否编码成功int got_frame =0;//临时AVFrame* pTempFrame=NULL;//读取数据 保存在pConvertframe->dataint iRealRead = fread(pRawBuff, 1, iRawBuffSize, pInputFile);while(iRealRead>0){iReadSamples = iRealRead/(iInputSampleBytes*iInputChans);if(bNeedResample){swr_convert(pSwrCtx, (uint8_t**)pConvertframe->data, iFrameSamples ,(const uint8_t**)pRawframe->data, iFrameSamples );if(bPlanner){ //只保存一个通道 因为保存多个通道测试工具 audacity看不了fwrite(pConvertframe->data[0],pConvertframe->linesize[0],1,pTempFile);}printf("Convert Frame :%d\n",++iFrameNum);pTempFrame = pConvertframe;}else{pTempFrame = pRawframe;}pTempFrame->pts            = lReadTotalSamples;got_frame               = 0;//Encodeif(avcodec_encode_audio2(pCodecCtx, &pkt,pTempFrame, &got_frame)<0){printf("Failed to encode!\n");return -1;}if (got_frame==1){printf("Succeed to encode 1 frame! \tsize:%5d\n",pkt.size);pkt.stream_index = audio_st->index;av_write_frame(pFormatCtx, &pkt);av_free_packet(&pkt);}//统计样本数以转换前为准 转换前后样本数是一样的lReadTotalSamples += (iReadSamples);iRealRead = fread(pRawBuff, 1, iRawBuffSize, pInputFile);}//刷新编码器if(flush_encoder(pFormatCtx,0)<0){printf("Flushing encoder failed\n");return -1;}fclose(pInputFile);fclose(pOutputFile);fclose(pTempFile);av_free(pRawBuff);if(bNeedResample){av_free(pConvertBuff);swr_free(&pSwrCtx);}printf("Convert Success!!\n");getchar();return 0;}

编码AAC生成文件 huangdun_br64000_sr48000.aac,可以直接用普通播放器播放如VLC

编码mp3生成文件 huangdun_br64000_sr48000.mp3,可以直接用普通播放器播放如VLC

MP3格式频谱分析

原始PCM格式频谱图

编码MP3后的频谱图

从上面频谱图可以看出MP3格式编码对高频部分衰减确实很厉害,图中11000HZ---15000HZ部分全部衰减掉了。现在把程序中编码比特率提高到128Kbps,来看看效果。

可以看到高频部分15000HZ衰减有明显减少,由此得出结论MP3编码格式优点压缩率高,能够在低码率的情况下提高较好的音质,适合网络传输,但是缺点是比特率低于128kbps的情况,高频部分有很高的衰减。

DEMO编译环境:   Win7_64bit+VS2008

DEMO下载地址:https://download.csdn.net/download/hiwubihe/10569791

FFMPEG实现PCM编码(采用封装格式实现)相关推荐

  1. FFMPEG实现对AAC解码(采用封装格式实现)

    技术在于交流.沟通,转载请注明出处并保持作品的完整性. 原文:https://blog.csdn.net/hiwubihe/article/details/81261022 [音频编解码系列文章] 音 ...

  2. 视频基础知识:告别菜鸟 高清视频编码及封装格式解析

    告别菜鸟 高清视频编码及封装格式解析 在如今的互联网时代,对于全高清视频文件来说,非常容易获取.不像以前在拨号上网的年代,可谓是资源匮乏,平时电脑看个VCD,就已经觉得是比较出色的画质了.时代在发展, ...

  3. Android FFmpeg视频播放器三 音频封装格式解码播放

    Android FFmpeg视频播放器一解封装 Android Android FFmpeg视频播放器二 视频封装格式解码播放 视频解封装之后就会得到音频流和视频流,解封状得到的数据是AVPackag ...

  4. 【FFmpeg】PCM编码成AAC

    使用FFmpeg把PCM裸数据编码成AAC音频流,具体步骤跟YUV编码成H264差不多. 1.命令行 ffmpeg -f s16le -ar 44100 -ac 2 -i bb1.pcm output ...

  5. 视频编码中封装格式RMVB,AVI,264

    常规理解 封装格式(也叫容器),就是将已经编码压缩好的视频轨和音频轨按照一定的格式放到一个文件中,也就是说仅仅是一个外壳,或者大家把它当成一个放视频轨和音频轨的文件夹也可以.说得通俗点,视频轨相当于饭 ...

  6. 常见音视频文件的编码和封装格式详解

    常见的AVI.RMVB.MKV.ASF.WMV.MP4.3GP.FLV等⽂件其实只能算是⼀种封装标准. ⼀个完整的视频⽂件是由⾳频和视频2部分组成的.H264.Xvid等就是视频编码格式,MP3.AA ...

  7. ffmpeg将视频编码为H264格式

    ffmpeg视频编解码课程教学视频:https://edu.csdn.net/course/detail/27795 课件里面提供源码资料 一.ffmpeg初始化 av_register_all(); ...

  8. 【开源项目】使用FFMPEG解析H264编码为YUV格式

    头文件 #pragma once#ifndef _VIDEO_DECODING_HEADER_ #define _VIDEO_DECODING_HEADER_#define INBUF_SIZE 40 ...

  9. ffmpeg中的ts/mp4封装格式支持哪些编码格式

    ts libavformat/mpegtsenc.c中 static int mpegts_write_pmt(AVFormatContext *s, MpegTSService *service) ...

最新文章

  1. 华为「硬」生生把AI搞出暴力美学
  2. 结构体序列为JSON
  3. 27. Leetcode 92. 反转链表 II (链表-反转链表)
  4. linux cp指令报错:cp: omitting directory ‘xxx‘(需要加-r递归拷贝)
  5. Spring MVC 基于URL的拦截和映射规则
  6. MapReduce算法–了解数据联接第1部分
  7. python 数组中取出最小值_Python 数组中的冒号使用
  8. js window.onload 加载多个函数的两种方法
  9. springboot event线程池总结
  10. 几个危险的扩展存储过程
  11. 转载,对于已用encryption加密的存储过程,的解密方法
  12. LINUX开机,直接进入终端,如何加载硬盘
  13. python调用通达信公式_通达信公式-主力雷达Python化
  14. 《Python语言程序设计基础》:第2章:Python程序实例解析:程序练习题
  15. 驱动开发入门 - 之二:Win7-x64 + VMWare (Win7-x64) + WinDbg 双机调试环境搭建
  16. 联想服务器远程管理模块,联想慧眼远程管理模块-Lenovo服务网站.PDF
  17. Simscape Multibody 多体动力学仿真教程(一)
  18. 软考-架构师-第六章-开发方法 第二节 软件开发模型(读书笔记)
  19. 功能全面的开源小程序商城-CRMEB
  20. Javascript基础——函数

热门文章

  1. seata-server
  2. 小睿睿的等式 (思维dp)
  3. algorithms sort
  4. 如何迁移单台或多台服务器?
  5. WGS84坐标 火星坐标 BD09坐标转换
  6. 一次聊个透彻:满二叉树、完全二叉树、二叉搜索树,二叉平衡树
  7. xadsafe做暗刷_手把手教你如何去掉网吧广告之万象OL篇_XADSAFE
  8. 前端实习生应该掌握什么技能?
  9. c语言约会,初次约会的十个话题 这么和女生聊天保准不冷场
  10. 可以把对方卡掉线的代码_2015最新卡死安卓QQ代码 卡到让对方手机QQ无响应及代码分享...