如何将MP3或WAV文件解码成PCM文件

概要

本文介绍使用 FFmpeg,将MP3或WAV文件解码成PCM文件的方法。

整体架构流程

首先,使用的 FFmpeg 库要支持 MP3/WAV 解码功能,即编译的时候要加上(编译 FFmpeg 库可以参考:Windows编译和使用ffmpeg):

--enable-decoder=mp3float --enable-decoder=pcm_s16le --enable-demuxer=mp3 --enable-demuxer=wav

下面的函数就是利用 FFmpeg 接口,实现将 MP3 或 WAV 文件解码成PCM文件:

#ifdef __cplusplus
extern "C" {
#endif
#include "libavutil/imgutils.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswresample/swresample.h"
#ifndef __linux__
#include "libswscale/swscale.h"
#endif
#include "libavutil/opt.h"
#ifdef __cplusplus
}
#endif

#pragma comment(lib, "libavcodec.a")
#pragma comment(lib, "libavformat.a")
#pragma comment(lib, "libavutil.a")
#pragma comment(lib, "libswresample.a")

#define MAX_AUDIO_FRAME_SIZE 192000

int DecodedAudioFile(const char *pFileName) {
    FILE *pFile = fopen("tmp.pcm", "wb");  // 输出文件
    AVFormatContext *pFormatCtx;
    AVCodecContext *pCodecCtx;
    AVCodec *pCodec;
    AVPacket *packet;
    AVFrame *pFrame;
    struct SwrContext *au_convert_ctx = NULL;
    av_register_all();
    avformat_network_init();

    pFormatCtx = avformat_alloc_context();
    if (avformat_open_input(&pFormatCtx, pFileName, NULL, NULL) != 0) {
        OutputDebugStringA("Couldn't open input stream.\n");
        return -1;
    }
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        OutputDebugStringA("Couldn't find stream information.\n");
        return -1;
    }
    av_dump_format(pFormatCtx, 0, pFileName, false);
    // Find the first audio stream
    int audioStream = -1;
    for (int i = 0; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
            audioStream = i;
            break;
        }
    }
    if (audioStream == -1) {
        OutputDebugStringA("Didn't find a audio stream.\n");
        return -1;
    }
    // Get a pointer to the codec context for the audio stream
    pCodecCtx = pFormatCtx->streams[audioStream]->codec;
    // Find the decoder for the audio stream
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    if (pCodec == NULL) {
        OutputDebugStringA("Codec not found.\n");
        return -1;
    }
    // Open codec
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        OutputDebugStringA("Could not open codec.\n");
        return -1;
    }
    int64_t in_channel_layout = av_get_default_channel_layout(pCodecCtx->channels);
    packet = (AVPacket*)av_malloc(sizeof(AVPacket));
    av_init_packet(packet);

    // Out Audio Param
    uint64_t out_channel_layout = in_channel_layout;
    int out_nb_samples = pCodecCtx->frame_size;
    AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
    int out_sample_rate = pCodecCtx->sample_rate;
    int out_channels = pCodecCtx->channels;
    // Out Buffer Size
    int out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples, out_sample_fmt, 1);
    uint8_t *out_buffer = (uint8_t*)av_malloc(MAX_AUDIO_FRAME_SIZE * 2);
    pFrame = av_frame_alloc();

    // 如果输入文件格式不是AV_SAMPLE_FMT_S16才需要
    if (pCodecCtx->sample_fmt != AV_SAMPLE_FMT_S16) {
        au_convert_ctx = swr_alloc();
        au_convert_ctx = swr_alloc_set_opts(au_convert_ctx, out_channel_layout, out_sample_fmt, out_sample_rate,
            in_channel_layout, pCodecCtx->sample_fmt, pCodecCtx->sample_rate, 0, NULL);
        swr_init(au_convert_ctx);
    }
    while (av_read_frame(pFormatCtx, packet) >= 0) {
        if (packet->stream_index == audioStream) {
            int got_picture;
            int ret = avcodec_decode_audio4(pCodecCtx, pFrame, &got_picture, packet);
            if (ret < 0) {
                OutputDebugStringA("Error in decoding audio frame.\n");
                return -1;
            }
            if (got_picture > 0) {
                if (au_convert_ctx) {  // MP3文件格式通常是AV_SAMPLE_FMT_FLTP,要重采样、格式转换等
                    swr_convert(au_convert_ctx, &out_buffer, MAX_AUDIO_FRAME_SIZE, (const uint8_t**)pFrame->data, pFrame->nb_samples);
                    // Write PCM
                    fwrite(out_buffer, 1, out_buffer_size, pFile);
                } else {  // WAV文件格式通常是AV_SAMPLE_FMT_S16,与输出文件一致,直接保存
                    fwrite(pFrame->data[0], 1, pFrame->nb_samples * pCodecCtx->channels * 2, pFile);
                }
            }
        }
        av_free_packet(packet);
    }
    swr_free(&au_convert_ctx);
    fclose(pFile);
    av_free(out_buffer);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);
    return 0;
}

int main() {
    DecodedAudioFile("test.mp3");
    DecodedAudioFile("test.wav");
    return 0;
}

技术细节

WAV 文件通常是未压缩的 PCM 音频,解码的步骤与压缩格式(如 MP3)有所不同。在解码 WAV 文件时,解码器可能会直接输出 PCM 数据,而不是像 MP3 那样的压缩数据。对于 WAV 文件,如果格式是 AV_SAMPLE_FMT_S16,则不需要使用 swr_convert,因为音频数据已经是 PCM 格式,可以直接写入文件。例如上述代码中,对文件格式的检查:

// ...
// 如果输入文件格式不是AV_SAMPLE_FMT_S16才需要
if (pCodecCtx->sample_fmt != AV_SAMPLE_FMT_S16) {
    au_convert_ctx = swr_alloc();
    au_convert_ctx = swr_alloc_set_opts(au_convert_ctx, out_channel_layout, out_sample_fmt, out_sample_rate,
        in_channel_layout, pCodecCtx->sample_fmt, pCodecCtx->sample_rate, 0, NULL);
    swr_init(au_convert_ctx);
}
// ...
if (au_convert_ctx) {  // MP3文件格式通常是AV_SAMPLE_FMT_FLTP,要重采样、格式转换等
    swr_convert(au_convert_ctx, &out_buffer, MAX_AUDIO_FRAME_SIZE, (const uint8_t**)pFrame->data, pFrame->nb_samples);
    // Write PCM
    fwrite(out_buffer, 1, out_buffer_size, pFile);
} else {  // WAV文件格式通常是AV_SAMPLE_FMT_S16,与输出文件一致,直接保存
    fwrite(pFrame->data[0], 1, pFrame->nb_samples * pCodecCtx->channels * 2, pFile);
}
// ...

如何更改输出文件采样率和通道数

上文中,输出的 PCM 文件采样率默认是与输入文件一致的。如果要将输出文件的采样率统一设置为 8000 Hz,需要在音频格式转换过程中进行采样率转换,修改代码为:

int CBaseDecode::DecodedAudioFile(const char *pFileName) {
    FILE *pFile = fopen("tmp.pcm", "wb");  // 输出文件
    AVFormatContext *pFormatCtx;
    AVCodecContext *pCodecCtx;
    AVCodec *pCodec;
    AVPacket *packet;
    AVFrame *pFrame;
    struct SwrContext *au_convert_ctx = NULL;
    av_register_all();
    avformat_network_init();

    pFormatCtx = avformat_alloc_context();
    if (avformat_open_input(&pFormatCtx, pFileName, NULL, NULL) != 0) {
        OutputDebugStringA("Couldn't open input stream.\n");
        return -1;
    }
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        OutputDebugStringA("Couldn't find stream information.\n");
        return -1;
    }
    av_dump_format(pFormatCtx, 0, pFileName, false);
    // Find the first audio stream
    int audioStream = -1;
    for (int i = 0; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
            audioStream = i;
            break;
        }
    }
    if (audioStream == -1) {
        OutputDebugStringA("Didn't find an audio stream.\n");
        return -1;
    }
    // Get a pointer to the codec context for the audio stream
    pCodecCtx = pFormatCtx->streams[audioStream]->codec;
    // Find the decoder for the audio stream
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    if (pCodec == NULL) {
        OutputDebugStringA("Codec not found.\n");
        return -1;
    }
    // Open codec
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        OutputDebugStringA("Could not open codec.\n");
        return -1;
    }
    int64_t in_channel_layout = av_get_default_channel_layout(pCodecCtx->channels);
    packet = (AVPacket*)av_malloc(sizeof(AVPacket));
    av_init_packet(packet);

    // Out Audio Param
    uint64_t out_channel_layout = in_channel_layout;  // 保持原通道
    int out_nb_samples = pCodecCtx->frame_size;
    AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
    int out_sample_rate = 8000;  // 统一采样率为 8000 Hz
    int out_channels = pCodecCtx->channels;  // 保持原通道数
    // Out Buffer Size
    int out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples, out_sample_fmt, 1);
    uint8_t *out_buffer = (uint8_t*)av_malloc(MAX_AUDIO_FRAME_SIZE * 2);
    pFrame = av_frame_alloc();

    // Swr
    if (pCodecCtx->sample_fmt != out_sample_fmt || pCodecCtx->sample_rate != out_sample_rate) {
        au_convert_ctx = swr_alloc_set_opts(NULL, 
            out_channel_layout, out_sample_fmt, out_sample_rate,  // 输出参数
            in_channel_layout, pCodecCtx->sample_fmt, pCodecCtx->sample_rate,  // 输入参数
            0, NULL);
        swr_init(au_convert_ctx);
    }

    while (av_read_frame(pFormatCtx, packet) >= 0) {
        if (packet->stream_index == audioStream) {
            int got_picture;
            int ret = avcodec_decode_audio4(pCodecCtx, pFrame, &got_picture, packet);
            if (ret < 0) {
                OutputDebugStringA("Error in decoding audio frame.\n");
                return -1;
            }
            if (got_picture > 0) {
                int out_samples;
                if (au_convert_ctx) {
                    out_samples = swr_convert(au_convert_ctx, &out_buffer, MAX_AUDIO_FRAME_SIZE, (const uint8_t**)pFrame->data, pFrame->nb_samples);
                    if (out_samples < 0) {
                        OutputDebugStringA("Error in converting audio frame.\n");
                        return -1;
                    }
                } else {
                    out_samples = pFrame->nb_samples;
                    memcpy(out_buffer, pFrame->data[0], out_buffer_size);
                }
                int out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_samples, out_sample_fmt, 1);
                fwrite(out_buffer, 1, out_buffer_size, pFile);
            }
        }
        av_free_packet(packet);
    }
    if (au_convert_ctx) {
        swr_free(&au_convert_ctx);
    }
    fclose(pFile);
    av_free(out_buffer);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);
    return 0;
}

修改说明:

  • 统一输出采样率,将 out_sample_rate 设置为 8000 Hz
  • swr_alloc_set_opts 函数中,输出参数指定为 out_sample_rate,确保输出音频的采样率统一为 8000 Hz
  • 格式检查,添加了 if (pCodecCtx->sample_fmt != out_sample_fmt || pCodecCtx->sample_rate != out_sample_rate) 判断,仅在需要时进行格式转换

除了将输出文件采样率统一设置为 8000 Hz 之外,如果还想要统一为单声道,可以在音频格式转换过程中添加声道转换。具体来说,需要修改 SwrContext 的初始化参数,并确保输出缓冲区的相关参数也相应修改:

int CBaseDecode::DecodedAudioFile(const char *pFileName) {
    FILE *pFile = fopen("tmp.pcm", "wb");  // 输出文件
    AVFormatContext *pFormatCtx;
    AVCodecContext *pCodecCtx;
    AVCodec *pCodec;
    AVPacket *packet;
    AVFrame *pFrame;
    struct SwrContext *au_convert_ctx = NULL;
    av_register_all();
    avformat_network_init();

    pFormatCtx = avformat_alloc_context();
    if (avformat_open_input(&pFormatCtx, pFileName, NULL, NULL) != 0) {
        OutputDebugStringA("Couldn't open input stream.\n");
        return -1;
    }
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        OutputDebugStringA("Couldn't find stream information.\n");
        return -1;
    }
    av_dump_format(pFormatCtx, 0, pFileName, false);
    // Find the first audio stream
    int audioStream = -1;
    for (int i = 0; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
            audioStream = i;
            break;
        }
    }
    if (audioStream == -1) {
        OutputDebugStringA("Didn't find an audio stream.\n");
        return -1;
    }
    // Get a pointer to the codec context for the audio stream
    pCodecCtx = pFormatCtx->streams[audioStream]->codec;
    // Find the decoder for the audio stream
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    if (pCodec == NULL) {
        OutputDebugStringA("Codec not found.\n");
        return -1;
    }
    // Open codec
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        OutputDebugStringA("Could not open codec.\n");
        return -1;
    }
    int64_t in_channel_layout = av_get_default_channel_layout(pCodecCtx->channels);
    packet = (AVPacket*)av_malloc(sizeof(AVPacket));
    av_init_packet(packet);

    // Out Audio Param
    uint64_t out_channel_layout = AV_CH_LAYOUT_MONO;  // 输出为单声道
    int out_nb_samples = pCodecCtx->frame_size;
    AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
    int out_sample_rate = 8000;  // 统一采样率为 8000 Hz
    int out_channels = 1;  // 单声道
    // Out Buffer Size
    int out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples, out_sample_fmt, 1);
    uint8_t *out_buffer = (uint8_t*)av_malloc(MAX_AUDIO_FRAME_SIZE * 2);
    pFrame = av_frame_alloc();

    // Swr
    if (pCodecCtx->sample_fmt != out_sample_fmt || pCodecCtx->channels != out_channels || pCodecCtx->sample_rate != out_sample_rate) {
        au_convert_ctx = swr_alloc_set_opts(NULL, 
            out_channel_layout, out_sample_fmt, out_sample_rate,  // 输出参数
            in_channel_layout, pCodecCtx->sample_fmt, pCodecCtx->sample_rate,  // 输入参数
            0, NULL);
        swr_init(au_convert_ctx);
    }

    while (av_read_frame(pFormatCtx, packet) >= 0) {
        if (packet->stream_index == audioStream) {
            int got_picture;
            int ret = avcodec_decode_audio4(pCodecCtx, pFrame, &got_picture, packet);
            if (ret < 0) {
                OutputDebugStringA("Error in decoding audio frame.\n");
                return -1;
            }
            if (got_picture > 0) {
                int out_samples;
                if (au_convert_ctx) {
                    out_samples = swr_convert(au_convert_ctx, &out_buffer, MAX_AUDIO_FRAME_SIZE, (const uint8_t**)pFrame->data, pFrame->nb_samples);
                    if (out_samples < 0) {
                        OutputDebugStringA("Error in converting audio frame.\n");
                        return -1;
                    }
                } else {
                    out_samples = pFrame->nb_samples;
                    memcpy(out_buffer, pFrame->data[0], out_buffer_size);
                }
                int out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_samples, out_sample_fmt, 1);
                fwrite(out_buffer, 1, out_buffer_size, pFile);
            }
        }
        av_free_packet(packet);
    }
    if (au_convert_ctx) {
        swr_free(&au_convert_ctx);
    }
    fclose(pFile);
    av_free(out_buffer);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);
    return 0;
}

修改说明:

  • 统一输出声道,将 out_channel_layout 设置为 AV_CH_LAYOUT_MONO,并将 out_channels 设置为 1
  • swr_alloc_set_opts 函数中,输出参数指定为单声道(AV_CH_LAYOUT_MONO

通过这些修改,无论输入音频文件的采样率和声道数是多少,输出的 PCM 文件将统一为 8000 Hz 的单声道音频。

1、Java实现wav音频文件换为pcm音频文件(AudioUtils.java) 2、Java实现播放pcm音频文件PCMPlay.java) WAVwav是一种无损的音频文件格式,WAV符合 PIFF(Resource Interchange File Format)规范。所有的WAV都有一个文件头,这个文件头音频流的编码参数。WAV对音频流的编码没有硬性规定,除了PCM之外,还有几乎所有支持ACM规范的编码都可以为WAV的音频流进行编码。 PCM:PCM(Pulse Code Modulation----脉码调制录音)。所谓PCM录音就是将声音等模拟信号变符号化的脉冲列,再予以记录。PCM信号是由[1]、[0]等符号构的数字信号,而未经过任何编码和压缩处理。与模拟信号比,它不易受传送系统的杂波及失真的影响。动态范围宽,可得到音质相当好的影响效果。 简单来说:wav是一种无损的音频文件格式,pcm是没有压缩的编码方式。 WAVPCM的关系 WAV可以使用多种音频编码来压缩其音频流,不过我们常见的都是音频流被PCM编码处理的WAV,但这不表示WAV只能使用PCM编码,MP3编码同样也可以运用在WAV中,和AVI一样,只要安装好了相应的Decode,就可以欣赏这些WAV了。在Windows平台下,基于PCM编码的WAV是被支持得最好的音频格式,所有音频软件都能完美支持,由于本身可以达到较高的音质的要求,因此,WAV也是音乐编辑创作的首选格式,适合保存音乐素材。因此,基于PCM编码的WAV被作为了一种中介的格式,常常使用在其他编码的相互换之中,例如MP3WMA。 简单来说:pcm是无损wav文件中音频数据的一种编码方式,但wav还可以用其它方式编码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值