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

原创
2022-11-30
4812
13

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_OBJECT

public:
    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 Format
    bool 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;
else
    m_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);

    // 打开文件IO
    if (!(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;
else
    nb_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)
{
    // 创建Packet
    AVPacket *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->S16P
        SwrContext *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++;
    // 发送Frame
    result = avcodec_send_frame(m_AudioCodecContext, frame);
    if (result < 0)
        continue;

    // 接收编码后的Packet
    result = 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;

    // 查找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;
    else
        m_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;
    
    // 创建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;
    else
        nb_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)
    {
        // 创建Packet
        AVPacket *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->S16P
            SwrContext *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++;
        // 发送Frame
        result = avcodec_send_frame(m_AudioCodecContext, frame);
        if (result < 0)
            continue;

        // 接收编码后的Packet
        result = 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);
}

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);

    // 打开文件IO
    if (!(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 API 获取摄像头的图像数据