@[TOC](音视频开发8. 使用ffmpeg 将pcm转码aac实践(C++))
一、 准备环境
- CentOS 已安装ffmpeg库
- 本地使用vscode,安装 Remote developement 远程开发插件 。
- 实现目标: 将pcm文件转码aac格式文件。
PCM全称Pulse-Code Modulation,即脉冲调制编码。PCM音频数据是未经压缩的音频采样数据裸流,一般播放器无法直接播放。如果要播放PCM还需要描述其采样率、声道、采样格式、码率等信息。
本文示例需要准备一段PCM样本文件,本文使用的PCM:
- 采样率:44100
- 双声道
- 码率:64000
- 样本格式:float, planar
- 立体声
二、 项目结构
三、 audio.cpp 完整代码
1. 引用ffmpeg头
extern "C"
{
}
using namespace std;
2. 主体代码
int main(){
av_register_all();
avcodec_register_all();
char inputfile[] = "input.pcm";
char outputfile[] = "audio.aac";
AVCodec* avCodec = avcodec_find_encoder(AV_CODEC_ID_AAC);
if(!avCodec){
cout << "avcodec_find_encoder failed" << endl;
return -1;
}
// 编码器上下文配置
AVCodecContext* avCodecContext = avcodec_alloc_context3(avCodec);
if(!avCodecContext){
cout << "avcodec_alloc_context3 failed" << endl;
return -1;
}
int ret=0;
// 采样率设置
avCodecContext->sample_rate = 44100;
// 双通道
avCodecContext->channels = 2;
// 32位样本格式
avCodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP;
// 码率
avCodecContext->bit_rate = 64000;
// 声道类型
avCodecContext->channel_layout = AV_CH_LAYOUT_STEREO;
// 音频帧使用公共头部
avCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
// 打开音频的编码器
ret = avcodec_open2(avCodecContext, avCodec,NULL);
if(ret<0){
cout << "avcodec_open2 failed" << endl;
return -1;
}
// 创建输出的上下文
AVFormatContext* avFormatContext = NULL;
// 初始化
avformat_alloc_output_context2(&avFormatContext, NULL, NULL, outputfile);
if(!avFormatContext){
cout << "avformat_alloc_output_context2 failed" << endl;
return -1;
}
cout << "创建音频流,然后打印输出流信息" << endl;
// 创建音频流
AVStream* st = avformat_new_stream(avFormatContext, NULL);
// 音频流参数设置
st->codecpar->codec_tag = 0;
// 编码器参数复制到音频流上,省得再设置一遍
avcodec_parameters_from_context(st->codecpar, avCodecContext);
// 第三个参数,0音频、1视频
av_dump_format(avFormatContext, 0, outputfile, 1);
cout << "打开输出文件 " << endl;
// 打开输出文件
ret = avio_open(&avFormatContext->pb, outputfile, AVIO_FLAG_WRITE);
if(ret<0){
cout << "avio_open failed" << endl;
return -1;
}
cout << "写文件头部,创建输出文件" << endl;
// 写头部信息,创建输出文件
avformat_write_header(avFormatContext, NULL);
cout << "重采样上下文" << endl;
// 重采样上下文
SwrContext* swrContext = NULL;
// 设置参数, 1. 上下文 2.输出声道布局 4.输出采样率, 5.输入声道布局 6.输入样本格式 7.输入采样率 8.配音 9.日志
swrContext = swr_alloc_set_opts(swrContext, avCodecContext->channel_layout, avCodecContext->sample_fmt, avCodecContext->sample_rate,
AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, 44100,
0, 0);
if(!swrContext){
cout << "swr_alloc_set_opts failed" << endl;
return -1;
}
// 初始化
ret = swr_init(swrContext);
if(ret<0){
cout << "swr_init failed" << endl;
return -1;
}
// 一帧帧读取 PCM 数据、重采样、编码、写入.1帧是1024个样本。
AVFrame* avFrame = av_frame_alloc();
// 样本格式 float4字节
avFrame->format = AV_SAMPLE_FMT_FLTP;
// 通道数
avFrame->channels=2;
// 通道布局
avFrame->channel_layout = AV_CH_LAYOUT_STEREO;
// 每一帧音频的样本数量
avFrame->nb_samples=1024;
ret = av_frame_get_buffer(avFrame, 0);
if(ret<0){
cout << "av_frame_get_buffer failed" << endl;
return -1;
}
// 准备缓存空间存放数据
int readSize = avFrame->nb_samples*2*2; //双声道,float
char* pcms = new char[readSize];
cout << "打开输入文件 每次读取(双声道,float):readsize=" << readSize << endl;
// 打开输入文件
FILE* fp = fopen(inputfile, "rb");
cout << "==========循环读入帧==========" << endl;
int sum=0;
int index=0;
for(;;){
// 存放编码后的数据
AVPacket pkt;
// 包初始化
av_init_packet(&pkt);
int len = fread(pcms,1,readSize, fp);
if(len <=0){
// 文件读完了
cout << "文件读取结束" << endl;
// 告诉解码器没有帧了
avcodec_send_frame(avCodecContext, NULL);
while(avcodec_receive_packet(avCodecContext, &pkt)!=AVERROR_EOF);
break;
}else{
index ++;
sum += len;
// 强制数据类型转换,重采样之前的数据
const uint8_t* data[1];
data[0] = (uint8_t*)pcms;
// 重采样 1.重采样上下文 2.重采样后的数据 3.样本数量不变 4.重采样之前的数据
len = swr_convert(swrContext, avFrame->data, avFrame->nb_samples, data, avFrame->nb_samples);
if(len <=0){
cout << "重采样输出异常" << len << endl;
break;
}
// ------------对重采样的数据重新编码--------
// 编码重采样后的数据
// 重采样的数据发送到编码线程
// 1. 编码器上下文 2.要发送的数据
ret = avcodec_send_frame(avCodecContext, avFrame);
if(ret < 0){
// 完成发送了
cout << "发编码器线程异常" << ret << endl;
continue;
}
// 接收编码和解码后的数据
// 取出编码后的数据,TODO: 这里要加循环多次读取以确保读取成功
ret = avcodec_receive_packet(avCodecContext, &pkt);
if(ret == -EAGAIN){
cout << "缓冲区未满 等待新数据" << endl;
} else if(ret != 0){
cout << "取编码结果异常" << ret << endl;
continue;
}
// 指定是音频流。 1为视频流
pkt.stream_index = 0;
// 解码时间、显示时间置为0
pkt.dts = 0;
pkt.pts = 0;
// 编码后的写入ac文件 1.输出上下文
av_interleaved_write_frame(avFormatContext, &pkt);
if(index%50==0){
cout << "第" << index << "帧数据:" << len << "..." << "已处理 " << sum << " 字节" << endl ;
}
}
}
cout << endl << "处理总字节数:" << sum << endl;
cout << endl << "释放资源" <<endl;
// 释放
delete pcms;
pcms = NULL;
// 写入索引
av_write_trailer(avFormatContext);
fclose(fp);
cout << "关闭输出文件" << endl;
avio_close(avFormatContext->pb);
cout << "关闭编码器" << endl;
avcodec_close(avCodecContext);
cout << "清理编码器参数" << endl;
avcodec_free_context(&avCodecContext);
cout << "清理输出上下文" << endl;
avformat_free_context(avFormatContext);
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.17)
project(ffmpeg_demo)
set(ffmpeg_libs_DIR /home/xundh/ffmpeg_sources/ffmpeg-4.2.2)
set(ffmpeg_headers_DIR /home/xundh/ffmpeg_sources/ffmpeg-4.2.2)
add_library( avcodec SHARED IMPORTED)
add_library( avfilter SHARED IMPORTED )
add_library( swresample SHARED IMPORTED )
add_library( swscale SHARED IMPORTED )
add_library( avformat SHARED IMPORTED )
add_library( avutil SHARED IMPORTED )
set_target_properties( avcodec PROPERTIES IMPORTED_LOCATION ${ffmpeg_libs_DIR}/libavcodec/libavcodec.so )
set_target_properties( avfilter PROPERTIES IMPORTED_LOCATION ${ffmpeg_libs_DIR}/libavfilter/libavfilter.so )
set_target_properties( swresample PROPERTIES IMPORTED_LOCATION ${ffmpeg_libs_DIR}/libswresample/libswresample.so )
set_target_properties( swscale PROPERTIES IMPORTED_LOCATION ${ffmpeg_libs_DIR}/libswscale/libswscale.so )
set_target_properties( avformat PROPERTIES IMPORTED_LOCATION ${ffmpeg_libs_DIR}/libavformat/libavformat.so )
set_target_properties( avutil PROPERTIES IMPORTED_LOCATION ${ffmpeg_libs_DIR}/libavutil/libavutil.so )
include_directories( ${ffmpeg_headers_DIR} )
link_directories(${ffmpeg_libs_DIR} )
link_directories(/usr/lib)
set(CMAKE_CXX_STANDARD 14)
add_executable(ffmpeg_demo audio.cpp)
target_link_libraries(${PROJECT_NAME} avcodec avformat avutil swresample swscale swscale avfilter )
运行效果: