音视频开发9. 使用ffmpeg 将pcm转码mp3实践(C++)
Posted 编程圈子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了音视频开发9. 使用ffmpeg 将pcm转码mp3实践(C++)相关的知识,希望对你有一定的参考价值。
@[TOC](音视频开发9. 使用ffmpeg 将pcm转码mp3实践(C++))
一、准备环境
- CentOS环境
- 安装 ffmpeg 库,并有必要的库(主要是lame:mp3解码库)
- ffmpeg库安装可参考之前文档
二、一些预备知识
1. 音频格式说明
如前文所述,pcm是音频裸数据,要转成mp3需要进行重采样、编码步骤。常见的PCM格式有8位和16位两种。
- 8位每一个PCM数据的值由一个字节即8位来表示(0-255)
- 16位是指每一个PCM数据的值由两个字节即16位来表示,分为高8位和第8位(-32767~32767)
2. 采样频率
采样频率指每秒钟对音频的采样点数,单位为Hz(赫兹)。
如采样频率为44100hz是指每秒钟采集44100个样本点。
3. 声道数
常见的声道数有:
- 单声道:mono
- 双声道:stereo,包含左右两声道
- 2.1声道:在双声道基础上增加了一个低音声道
- 5.1声道:分别为正面、左前方、右前方、左环绕、右环绕声道、一个低音声道
- 7.1声道:在5.1声道的基础上,把左右的环绕声道拆分为左右环绕声道以及左右后置声道,主要应用于BD以及现代的电影院
4. 样本大小
例:
- 1024个16位单声道PCM样本,它的样本大小为102421=2048字节;
- 1024个16位双声道PCM样本,它的样本大小为102422=4096字节
5. 一帧样本数
- PCM 一般为1024;
- MP3 一般为 1152。
6. 参考命令行
ffmpeg -y -ac 1 -ar 16000 -f s16le -i /data/ffmpeg/test/input.pcm -c:a libmp3lame -q:a 2 /data/ffmpeg/test/output.mp3
三、几个重要函数
1. 重采样参数设置两个函数
3.1.1 swr_alloc_set_opts
示例:
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);
3.1.2 av_opt_set_int
示例:
SwrContext *swrContext = swr_alloc();
// 通道布局:立体声
av_opt_set_int(swrContext, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0);
// 采样率:44100
av_opt_set_int(swrContext, "in_sample_rate", OSR, 0);
// 样本格式 s16交错存储
av_opt_set_sample_fmt(swrContext, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0);
在设置完参数后,要调用swr_init进行初始化。
2. 分配样本数据内存空间
3.2.1 av_samples_alloc_array_and_samples
根据音频格式分配相应大小的内存空间,函数内部会调用 av_samples_alloc
,示例代码:
result = av_samples_alloc_array_and_samples(&input_data, &input_linesize, 2, avFrame->nb_samples, AV_SAMPLE_FMT_S16, 0);
3.2.2 av_samples_alloc
根据音频格式分配相应大小的内存空间。用于转换过程中对输出内存大小进行调整。
3. 整体流程
四、实现代码
1. CMakeLists.txt
cmake_minimum_required(VERSION 3.17)
project(ffmpeg_demo)
# 设置ffmpeg依赖库及头文件所在目录,并存进指定变量
set(ffmpeg_libs_DIR /home/xundh/ffmpeg_sources/ffmpeg-4.2.2)
set(ffmpeg_headers_DIR /home/xundh/ffmpeg_sources/ffmpeg-4.2.2)
#对于find_package找不到的外部依赖库,可以用add_library添加
# SHARED表示添加的是动态库
# IMPORTED表示是引入已经存在的动态库
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 main.cpp)
add_executable(ffmpeg_demo pcm_to_mp3.cpp)
target_link_libraries($PROJECT_NAME avcodec avformat avutil swresample swscale swscale avfilter )
2. 主文件
#include <iostream>
#ifdef __cplusplus
extern "C"
#endif
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/imgutils.h"
#include "libavutil/log.h"
#include "libswresample/swresample.h"
#include "libavutil/avutil.h"
#include "libavutil/opt.h"
#ifdef __cplusplus
#endif
using namespace std;
#define CHANNEL 2
#define OSR 44100
/**
* pcm 转 mp3格式,输入文件路径
*/
int pcm_to_mp3(const char *pcm_file_path, const char *mp3_file_path)
FILE *pcm_file = NULL;
FILE *mp3_file = NULL;
int result;
// 获取mp3编码器
cout << "获取mp3编码器" << endl;
const AVCodec *avCodec = avcodec_find_encoder(AV_CODEC_ID_MP3);
if (!avCodec)
cout << "初始化mp3 编码器失败" << endl;
return -1;
// 创建编码器上下文
AVCodecContext *avCodecContext = avcodec_alloc_context3(avCodec);
if (!avCodecContext)
cout << "avcodec_alloc_context3 失败" << avCodecContext << endl;
return -1;
avCodecContext->bit_rate = 64000;
avCodecContext->channels = CHANNEL;
avCodecContext->channel_layout = AV_CH_LAYOUT_STEREO;
avCodecContext->sample_rate = OSR;
avCodecContext->sample_fmt = AV_SAMPLE_FMT_S16P;
avCodecContext->time_base = av_get_time_base_q();
// 打开编码器
cout << "打开mp3编码器" << endl;
result = avcodec_open2(avCodecContext, avCodec, NULL);
if (result < 0)
cout << "avcodec_open2失败: " << result << endl;
return result;
cout << "打开mp3文件" << mp3_file_path << endl;
// 打开输出文件
mp3_file = fopen(mp3_file_path, "wb");
if (!mp3_file)
cout << "打开mp3文件失败" << endl;
return -1;
// AVFrame 接受重采样的每一帧的音频数据 每帧的样本大小为1152
AVFrame *avFrame = av_frame_alloc();
if (!avFrame)
cout << "分配avFrame帧失败" << endl;
return -1;
// mp3一帧的样本数为1152
avFrame->nb_samples = 1152;
avFrame->channels = CHANNEL;
avFrame->channel_layout = AV_CH_LAYOUT_STEREO;
avFrame->format = AV_SAMPLE_FMT_S16P;
// 给帧分配内存空间
result = av_frame_get_buffer(avFrame, 0);
if (result < 0)
cout << "分配帧内存失败" << endl;
return result;
// 重采样 创建音频重采样上下文
cout << "配置重采样器上下文" << endl;
SwrContext *swrContext = swr_alloc();
if (!swrContext)
cout << "配置重采样上下文失败" << endl;
return -1;
// 设置重采样输入pcm参数:通道布局:立体声 采样率:44100 样本格式 s16交错存储
av_opt_set_int(swrContext, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(swrContext, "in_sample_rate", OSR, 0);
av_opt_set_sample_fmt(swrContext, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0);
// 设置重采样输出mp3参数:通道布局:立体声 采样率:44100 样本格式 s16平面存储
av_opt_set_int(swrContext, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(swrContext, "out_sample_rate", OSR, 0);
av_opt_set_sample_fmt(swrContext, "out_sample_fmt", AV_SAMPLE_FMT_S16P, 0);
// 重采样初始化
result = swr_init(swrContext);
if (result < 0)
cout << "重采样器初始化失败,error=" << result << endl;
return result;
uint8_t **input_data = NULL;
uint8_t **output_data = NULL;
int input_linesize, output_linesize;
// 打开pcm文件
cout << "打开源 pcm 文件 " << pcm_file_path << endl;
pcm_file = fopen(pcm_file_path, "rb");
if (!pcm_file)
cout << "打开 pcm 文件失败" << endl;
return -1;
cout << "开始编码转换" << endl;
// 给pcm文件数据分配空间
result = av_samples_alloc_array_and_samples(&input_data, &input_linesize, 2, avFrame->nb_samples, AV_SAMPLE_FMT_S16, 0);
if (result < 0)
cout << "给pcm文件分配空间失败, result = " << result << endl;
return result;
// 缓存重采样数据的空间分配
result = av_samples_alloc_array_and_samples(&output_data, &output_linesize, 2, avFrame->nb_samples, AV_SAMPLE_FMT_S16P, 0);
if (result < 0)
cout << "获取mp3 重采样数据失败, result=" << result << endl;
return result;
// 存放编码后的数据
AVPacket *avPacket = av_packet_alloc();
if (!avPacket)
cout << "分配 avPacket 内存失败" << endl;
return -1;
cout << "==========循环读入帧==========" << endl;
long total_size = 0;
while (!feof(pcm_file))
long read_size = (long)fread(input_data[0], 1, avFrame->nb_samples * 4, pcm_file);
total_size += read_size;
if ((total_size / read_size) % 50 == 0)
cout << "读取数据:" << read_size << "字节; 累计:" << total_size << " 字节 " << endl;
if (read_size <= 0)
break;
// 重采样
result = swr_convert(swrContext, output_data, avFrame->nb_samples, (const uint8_t **)input_data, avFrame->nb_samples);
if (result < 0)
cout << "音频编码失败,错误信息" << result << endl;
return result;
// 将重采样后的数据存入frame,MP3是s16p 先存放左声道的数据 后存放右声道的数据, data[0]是左声道,1是右声道
avFrame->data[0] = output_data[0];
avFrame->data[1] = output_data[1];
// 编码,写入mp3文件,实际上是对frame这个结构体里面的数据进行编码操作,发送到编码线程:使用编码器 和 存储数据的frame
result = avcodec_send_frame(avCodecContext, avFrame);
if (result < 0)
cout << "mp3编码失败,错误信息:" << result << endl;
return result;
while (result >= 0)
// 接收编码后的数据,使用编码器 和 存储编码数据的pkt, 有可能需要多次才能接收完成
result = avcodec_receive_packet(avCodecContext, avPacket);
// AVERROR_EOF表示没有数据了 这两个错误不影响继续接收数据
if (result == AVERROR_EOF || result == AVERROR(EAGAIN))
continue;
else if (result < 0)
break;
fwrite(avPacket->data, 1, avPacket->size, mp3_file);
av_packet_unref(avPacket);
// 告诉解码器没有帧了,如果没有这几行的逻辑,在关闭 avCodecContext 可能会提示 * fames left in the queu on closing
avcodec_send_frame(avCodecContext, __null);
while(avcodec_receive_packet(avCodecContext, avPacket)!=AVERROR_EOF);
// 关闭缓存
if (input_data)
av_free(input_data);
if (output_data)
av_free(output_data);
cout << "关闭文件" << endl;
fclose(pcm_file);
fclose(mp3_file);
cout << "释放资源" << endl;
// s释放 frame pkt
av_frame_free(&avFrame);
av_packet_free(&avPacket);
// 释放重采样上下文
swr_free(&swrContext);
// 释放编码器上下文
avcodec_free_context(&avCodecContext);
cout << "转码完成" << endl;
return 0;
int main(int argc, char *argv[])
const char *input = "input.pcm";
const char *output = "out.mp3";
pcm_to_mp3(input, output);
return 1;
程序执行结果:
./ffmpeg_demo
获取mp3编码器
打开mp3编码器
打开mp3文件out.mp3
配置重采样器上下文
打开源 pcm 文件 input.pcm
开始编码转换
==========循环读入帧==========
读取数据:4608字节; 累计:230400 字节
读取数据:4608字节; 累计:460800 字节
读取数据:4608字节; 累计:691200 字节
读取数据:4608字节; 累计:921600 字节
关闭文件
释放资源
转码完成
以上是关于音视频开发9. 使用ffmpeg 将pcm转码mp3实践(C++)的主要内容,如果未能解决你的问题,请参考以下文章
音视频开发8. 使用ffmpeg 将pcm转码aac实践(C++)