文章目录
概要
本文介绍使用 FFmpeg,将MP3或WAV文件解码成PCM文件的方法。
整体架构流程
首先,使用的 FFmpeg 库要支持 MP3/WAV 解码功能,即编译的时候要加上(编译 FFmpeg 库可以参考:Windows编译和使用ffmpeg):
bash
--enable-decoder=mp3float --enable-decoder=pcm_s16le --enable-demuxer=mp3 --enable-demuxer=wav
下面的函数就是利用 FFmpeg 接口,实现将 MP3 或 WAV 文件解码成PCM文件:
cpp
#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")
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 格式,可以直接写入文件。例如上述代码中,对文件格式的检查:
cpp
// ...
// 如果输入文件格式不是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);
}
// ...