文章目录

  • 1. 获取编码器和创建解码器上下文
  • 2. 创建音频流和输出封装上下文
  • 3. 编码原始数据写入到文件中

WAV音频封装格式可以存储无编码的PCM数据,而MP3封装格式中不能直接存储PCM数据,需要对数据进行编码;使用FFMpeg将原始的PCM数据生成WAV和MP3文件的步骤如下:

  1. 创建编码器AVCodec和编码码器上下文AVCodecContext
  2. 创建音频流AVStream和输出封装上下文AVFormatContext
  3. 将原始的PCM数据的AVFrame转换为便那后的AVPacket
  4. 将AVPacket使用封装上下文写入到文件中。

1. 获取编码器和创建解码器上下文

  1. 使用函数avcodec_find_encoder() 获取编码器 AVCodec
  2. 使用函数 avcodec_alloc_context3() 创建编码器上下文AVCodecContext
  3. 为编码器设置参数,音频采样率、采样格式、通道数等信息。
  4. 打开编码器

整体的头文件定义如下:

#ifndef AUIDO_GENERATER_H
#define AUIDO_GENERATER_H#include <QObject>
extern "C"
{
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswresample/swresample.h"
}class AudioGenerater : public QObject
{Q_OBJECTpublic:enum AudioFormatType{AudioFormat_WAV,AudioFormat_MP3};public:AudioGenerater(QObject *parent = nullptr);~AudioGenerater();// 生成音频文件void generateAudioFile(AudioFormatType formatType, QString fileName, QByteArray pcmData);private:AVCodec *m_AudioCodec = nullptr;AVCodecContext *m_AudioCodecContext = nullptr;AVFormatContext *m_FormatContext = nullptr;AVOutputFormat *m_OutputFormat = nullptr;// init Formatbool initFormat(QString audioFileName);
};
#endif

获取编码器和创建解码器上下文代码如下:

AVCodecID codecID = AV_CODEC_ID_PCM_S16LE;
if (formatType == AudioFormat_MP3)codecID = AV_CODEC_ID_MP3;// 查找Codec
m_AudioCodec = avcodec_find_encoder(codecID);
if (m_AudioCodec == nullptr)return;// 创建编码器上下文
m_AudioCodecContext = avcodec_alloc_context3(m_AudioCodec);
if (m_AudioCodecContext == nullptr)return;// 设置参数
m_AudioCodecContext->bit_rate = 64000;
m_AudioCodecContext->sample_rate = 44100;
if (formatType == AudioFormat_WAV)m_AudioCodecContext->sample_fmt = AV_SAMPLE_FMT_S16;
elsem_AudioCodecContext->sample_fmt = AV_SAMPLE_FMT_S16P;
m_AudioCodecContext->channels = 2;
m_AudioCodecContext->channel_layout = av_get_default_channel_layout(2);
m_AudioCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;// 打开编码器
int result = avcodec_open2(m_AudioCodecContext, m_AudioCodec, nullptr);
if (result < 0)  goto end;

这里对于生成WAV格式的文件采样格式为AV_SAMPLE_FMT_S16, 生成MP3格式的文件采样格式为AV_SAMPLE_FMT_S16P。S16和S16P的区别在于左右声道数据的存储顺序
S16:LRLRLRLRLRLRLRLRLR…
S16P:LLLLLLLL…RRRRRRR…

2. 创建音频流和输出封装上下文

创建音频流和封装上下文的步骤如下:

  1. 使用函数avformat_alloc_output_context2() 创建Format上下文
  2. 使用函数**avformat_new_stream()**创建一个流
  3. 设置流中的参数
  4. 使用函数avio_open打开文件

示例代码如下:

bool AudioGenerater::initFormat(QString audioFileName)
{// 创建输出 Format 上下文int result = avformat_alloc_output_context2(&m_FormatContext, nullptr, nullptr, audioFileName.toLocal8Bit().data());if (result < 0)return false;m_OutputFormat = m_FormatContext->oformat;// 创建音频流AVStream *audioStream = avformat_new_stream(m_FormatContext, m_AudioCodec);if (audioStream == nullptr){avformat_free_context(m_FormatContext);return false;}// 设置流中的参数audioStream->id = m_FormatContext->nb_streams - 1;audioStream->time_base = { 1, 44100 };result = avcodec_parameters_from_context(audioStream->codecpar, m_AudioCodecContext);if (result < 0){avformat_free_context(m_FormatContext);return false;}// 打印FormatContext信息av_dump_format(m_FormatContext, 0, audioFileName.toLocal8Bit().data(), 1);// 打开文件IOif (!(m_OutputFormat->flags & AVFMT_NOFILE)){result = avio_open(&m_FormatContext->pb, audioFileName.toLocal8Bit().data(), AVIO_FLAG_WRITE);if (result < 0){avformat_free_context(m_FormatContext);return false;}}return true;
}

3. 编码原始数据写入到文件中

代码如下:

// 写入文件头
result = avformat_write_header(m_FormatContext, nullptr);
if (result < 0)goto end;// 创建Frame
AVFrame *frame = av_frame_alloc();
if (frame == nullptr)goto end;int nb_samples = 0;
if (m_AudioCodecContext->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)nb_samples = 10000;
elsenb_samples = m_AudioCodecContext->frame_size;// 设置Frame的参数
frame->nb_samples = nb_samples;
frame->format = m_AudioCodecContext->sample_fmt;
frame->channel_layout = m_AudioCodecContext->channel_layout;// 申请数据内存
result = av_frame_get_buffer(frame, 0);
if (result < 0)
{av_frame_free(&frame);goto end;
}// 设置Frame为可写
result = av_frame_make_writable(frame);
if (result < 0)
{av_frame_free(&frame);goto end;
}int perFrameDataSize = frame->linesize[0];
if (formatType == AudioFormat_MP3)perFrameDataSize *= 2;
int count = pcmData.size() / perFrameDataSize;
bool needAddOne = false;
if (pcmData.size() % perFrameDataSize != 0)
{count++;needAddOne = true;
}int frameCount = 0;
for (int i = 0; i < count; ++i)
{// 创建PacketAVPacket *pkt = av_packet_alloc();if (pkt == nullptr)goto end;av_init_packet(pkt);if (i == count - 1)perFrameDataSize = pcmData.size() % perFrameDataSize;// 设置数据if (formatType == AudioFormat_WAV){// 合成WAV文件memset(frame->data[0], 0, perFrameDataSize);memcpy(frame->data[0], &(pcmData.data()[perFrameDataSize * i]), perFrameDataSize);}else{memset(frame->data[0], 0, frame->linesize[0]);memset(frame->data[1], 0, frame->linesize[0]);
#if 1// 合成MP3文件int channelLayout = av_get_default_channel_layout(2);// 格式转换 S16->S16PSwrContext *swrContext = swr_alloc_set_opts(nullptr, channelLayout, AV_SAMPLE_FMT_S16P, 44100, \channelLayout, AV_SAMPLE_FMT_S16, 44100, 0, nullptr);swr_init(swrContext);uchar *pSrcData[1] = {0};pSrcData[0] = (uchar*)&(pcmData.data()[perFrameDataSize * i]);swr_convert(swrContext, frame->data, frame->nb_samples, \(const uint8_t **)pSrcData, frame->nb_samples);swr_free(&swrContext);AVRational rational;rational.den = m_AudioCodecContext->sample_rate;rational.num = 1;//frame->pts = av_rescale_q(0, rational, m_AudioCodecContext->time_base);
#endif}frame->pts = frameCount++;// 发送Frameresult = avcodec_send_frame(m_AudioCodecContext, frame);if (result < 0)continue;// 接收编码后的Packetresult = avcodec_receive_packet(m_AudioCodecContext, pkt);if (result < 0){av_packet_free(&pkt);continue;}// 写入文件av_packet_rescale_ts(pkt, m_AudioCodecContext->time_base, m_FormatContext->streams[0]->time_base);pkt->stream_index = 0;result = av_interleaved_write_frame(m_FormatContext, pkt);if (result < 0)continue;av_packet_free(&pkt);
}// 写入文件尾
av_write_trailer(m_FormatContext);
// 关闭文件IO
avio_closep(&m_FormatContext->pb);
// 释放Frame内存
av_frame_free(&frame);
end:
avcodec_free_context(&m_AudioCodecContext);
if (m_FormatContext != nullptr)avformat_free_context(m_FormatContext);
  1. 函数avformat_write_header()av_write_trailer() 表示写入文件头和文件尾,写入音频数据前写入头,完成音频写入后,写入文件尾。
  2. 函数av_frame_alloc() 创建AVFrame数据,函数 av_frame_free() 释放AVFrame数据。
  3. 函数av_frame_get_buffer() 为AVFrame申请数据的存储空间。其中AVFrame中的成员linesize存储每个通道的数据字节大小,data中存储数据对于planes类型的数据每个通道是分别存储的。
  4. 函数av_packet_alloc() 为AVPacket分配内存,函数av_packet_free() 为AVPacket释放内存。
  5. 函数avcodec_send_frame() 发送原始的AVFrame,函数avcodec_receive_packet() 接收编码后的AVPacket。
  6. 函数av_interleaved_write_frame() 将编码后的AVPacket写入到文件中。
  7. 函数avio_closep() 关闭文件。

完整代码如下:
.CPP文件

#include "AudioGenerater.h"
#include <QDebug>AudioGenerater::AudioGenerater(QObject *parent):QObject(parent)
{av_register_all();avcodec_register_all();
}AudioGenerater::~AudioGenerater()
{}void AudioGenerater::generateAudioFile(AudioFormatType formatType, QString fileName, QByteArray pcmData)
{AVCodecID codecID = AV_CODEC_ID_PCM_S16LE;if (formatType == AudioFormat_MP3)codecID = AV_CODEC_ID_MP3;// 查找Codecm_AudioCodec = avcodec_find_encoder(codecID);if (m_AudioCodec == nullptr)return;// 创建编码器上下文m_AudioCodecContext = avcodec_alloc_context3(m_AudioCodec);if (m_AudioCodecContext == nullptr)return;// 设置参数m_AudioCodecContext->bit_rate = 64000;m_AudioCodecContext->sample_rate = 44100;if (formatType == AudioFormat_WAV)m_AudioCodecContext->sample_fmt = AV_SAMPLE_FMT_S16;elsem_AudioCodecContext->sample_fmt = AV_SAMPLE_FMT_S16P;m_AudioCodecContext->channels = 2;m_AudioCodecContext->channel_layout = av_get_default_channel_layout(2);m_AudioCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;// 打开编码器int result = avcodec_open2(m_AudioCodecContext, m_AudioCodec, nullptr);if (result < 0) goto end;// 创建封装if (!initFormat(fileName))goto end;// 写入文件头result = avformat_write_header(m_FormatContext, nullptr);if (result < 0)goto end;// 创建FrameAVFrame *frame = av_frame_alloc();if (frame == nullptr)goto end;int nb_samples = 0;if (m_AudioCodecContext->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)nb_samples = 10000;elsenb_samples = m_AudioCodecContext->frame_size;// 设置Frame的参数frame->nb_samples = nb_samples;frame->format = m_AudioCodecContext->sample_fmt;frame->channel_layout = m_AudioCodecContext->channel_layout;// 申请数据内存result = av_frame_get_buffer(frame, 0);if (result < 0){av_frame_free(&frame);goto end;}// 设置Frame为可写result = av_frame_make_writable(frame);if (result < 0){av_frame_free(&frame);goto end;}int perFrameDataSize = frame->linesize[0];if (formatType == AudioFormat_MP3)perFrameDataSize *= 2;int count = pcmData.size() / perFrameDataSize;bool needAddOne = false;if (pcmData.size() % perFrameDataSize != 0){count++;needAddOne = true;}int frameCount = 0;for (int i = 0; i < count; ++i){// 创建PacketAVPacket *pkt = av_packet_alloc();if (pkt == nullptr)goto end;av_init_packet(pkt);if (i == count - 1)perFrameDataSize = pcmData.size() % perFrameDataSize;// 设置数据if (formatType == AudioFormat_WAV){// 合成WAV文件memset(frame->data[0], 0, perFrameDataSize);memcpy(frame->data[0], &(pcmData.data()[perFrameDataSize * i]), perFrameDataSize);}else{memset(frame->data[0], 0, frame->linesize[0]);memset(frame->data[1], 0, frame->linesize[0]);
#if 1// 合成MP3文件int channelLayout = av_get_default_channel_layout(2);// 格式转换 S16->S16PSwrContext *swrContext = swr_alloc_set_opts(nullptr, channelLayout, AV_SAMPLE_FMT_S16P, 44100, \channelLayout, AV_SAMPLE_FMT_S16, 44100, 0, nullptr);swr_init(swrContext);uchar *pSrcData[1] = {0};pSrcData[0] = (uchar*)&(pcmData.data()[perFrameDataSize * i]);swr_convert(swrContext, frame->data, frame->nb_samples, \(const uint8_t **)pSrcData, frame->nb_samples);swr_free(&swrContext);AVRational rational;rational.den = m_AudioCodecContext->sample_rate;rational.num = 1;//frame->pts = av_rescale_q(0, rational, m_AudioCodecContext->time_base);
#endif}frame->pts = frameCount++;// 发送Frameresult = avcodec_send_frame(m_AudioCodecContext, frame);if (result < 0)continue;// 接收编码后的Packetresult = avcodec_receive_packet(m_AudioCodecContext, pkt);if (result < 0){av_packet_free(&pkt);continue;}// 写入文件av_packet_rescale_ts(pkt, m_AudioCodecContext->time_base, m_FormatContext->streams[0]->time_base);pkt->stream_index = 0;result = av_interleaved_write_frame(m_FormatContext, pkt);if (result < 0)continue;av_packet_free(&pkt);}// 写入文件尾av_write_trailer(m_FormatContext);// 关闭文件IOavio_closep(&m_FormatContext->pb);// 释放Frame内存av_frame_free(&frame);
end:avcodec_free_context(&m_AudioCodecContext);if (m_FormatContext != nullptr)avformat_free_context(m_FormatContext);
}bool AudioGenerater::initFormat(QString audioFileName)
{// 创建输出 Format 上下文int result = avformat_alloc_output_context2(&m_FormatContext, nullptr, nullptr, audioFileName.toLocal8Bit().data());if (result < 0)return false;m_OutputFormat = m_FormatContext->oformat;// 创建音频流AVStream *audioStream = avformat_new_stream(m_FormatContext, m_AudioCodec);if (audioStream == nullptr){avformat_free_context(m_FormatContext);return false;}// 设置流中的参数audioStream->id = m_FormatContext->nb_streams - 1;audioStream->time_base = { 1, 44100 };result = avcodec_parameters_from_context(audioStream->codecpar, m_AudioCodecContext);if (result < 0){avformat_free_context(m_FormatContext);return false;}// 打印FormatContext信息av_dump_format(m_FormatContext, 0, audioFileName.toLocal8Bit().data(), 1);// 打开文件IOif (!(m_OutputFormat->flags & AVFMT_NOFILE)){result = avio_open(&m_FormatContext->pb, audioFileName.toLocal8Bit().data(), AVIO_FLAG_WRITE);if (result < 0){avformat_free_context(m_FormatContext);return false;}}return true;
}

使用FFMpeg将音频PCM数据生成WAV和MP3文件相关推荐

  1. ffmpeg进行混音,将两路音频pcm数据合成一路输出

    ffmpeg进行混音,将两路音频pcm数据合成一路输出 audiomixer.h #ifndef AUDIOMIXER_H #define AUDIOMIXER_H#include <map&g ...

  2. 音视频从入门到精通——FFmpeg分离出PCM数据实战

    什么是PCM? PCM(Pulse Code Modulation,脉冲编码调制)音频数据是未经压缩的音频采样数据裸流,它是由模拟信号经过采样.量化.编码转换成的标准数字音频数据. 描述PCM数据的6 ...

  3. js实现音频PCM数据合并、拼接、裁剪、调节音量等功能

    关于音频的内容,我边学习,边实践也总结了一些,从最开始实现一个简单的web音乐播放器的自定义工具栏,到后来的实现简单的音频频谱图.直到今天的对音频数据进行的进一步操作,我也是一点点的在进步.虽然很多地 ...

  4. WIN32下使用DirectSound接口的简单音频播放器(支持wav和mp3)

    刚好最近接触了一些DirectSound,就写了一个小程序练练手,可以用来添加播放基本的wav和mp3音频文件的播放器.界面只是简单的GDI,dxsdk只使用了DirectSound8相关的接口. D ...

  5. Android AudioRecord录制PCM以及转换为wav和mp3

    Android AudioRecord录制PCM以及转换为wav和mp3 1.录制pcm pcm介绍 pcm是指音频裸数据是脉冲编码调制数据.描述一段PCM数据通常以下几个概念: 量化格式(Sampl ...

  6. Python 使用netCDF4读写nc文件以及截取指定经纬度范围内的数据生成新的nc文件

    Python 使用netCDF4读写nc文件以及截取nc文件经纬度范围内的数据 简单介绍nc文件的读写操作,以及实现输入nc文件和坐标范围,输出一个新的nc文件的功能 环境 python3.8.13 ...

  7. [Android] [音视频系列]在 Android 平台使用 AudioRecord 和 AudioTrack API 完成音频 PCM 数据的采集和播放,并实现读写音频 wav 文件

    参考 官方文档地址:https://developer.android.google.cn/reference/android/media/AudioRecord GitHub 地址:https:// ...

  8. AudioRecord 采集音频PCM数据

    AudioRecord 可以用来采集音频原始数据(PCM)格式,使用起来非常简单. 主要就是构造函数的定义 AudioRecord(int audioSource, int sampleRateInH ...

  9. 关于音频PCM数据2字节(16位)byte与64位double之间的转换

    1 致谢 感谢kimmking网友提供的资料 原文链接如下:http://blog.csdn.net/kimmking/article/details/8752737 2 问题描述 今天遇到一个问题 ...

最新文章

  1. SQL 主键 自动编号 主键自增
  2. 深度学习笔记 第四门课 卷积神经网络 第四周 特殊应用:人脸识别和神经风格转换...
  3. weak_ptr指针编程实验
  4. Focus 焦点定位
  5. elasticsearch搜素关键字自动补全(suggest)
  6. 10分钟了解一致性hash算法
  7. 第一百一十二期:96秒100亿!如何抗住双11高并发流量?
  8. 麒麟810处理器_何刚口中第二颗7nm处理器麒麟810曝光:将搭载自研NPU
  9. Java 解析 XML
  10. 【JAVA SE】第十一章 正则表达式、包装类和BigDecimal
  11. android camera (2) ---高通平台camera开发
  12. 为提升在线语音识别效率,他创造了两种升级版算法模型
  13. sqlyog的快捷键
  14. android twitter第三方登录,android中接入twitter进行第三方登录
  15. 茜在人名可以读xi吗_带茜字的女孩名字
  16. C++ 线性表的结构体定义(顺序表和链式表)
  17. iPhone 移除描述文件详细步骤(Apple Configurator 2)
  18. Centos7 网卡做 bond 以及 team
  19. 国外网络需要验证中国手机号码的格式(获取手机的验证码时)(kaggle 收不到手机验证码)
  20. CouchDB与CouchBase的比较

热门文章

  1. 港科资讯|沈向洋教授获委任为香港科大校董会主席
  2. Vue项目的登录和注册界面
  3. 吴军《数学之美》第二版阅读整理
  4. FATE联邦学习初探(二)
  5. 【长难句分析精讲】并列结构
  6. TimeZone-时间戳测试
  7. 【阿里云仓库 可用 2022】IDEA MAVEN setings.xml 配置
  8. Android 进阶——Framework 核心ANR( Applicatipon No Response)机制设计思想详解
  9. 如何实现web系统检测浏览器类型的功能
  10. 画仓鼠大赛 评比开始!