本系列的之前文章介绍了视频的编解码相关,接下来介绍音频的编解码,本文将mp3音频文件解码为pcm。
使用的mp3音频文件为从网易云音乐上下载的排骨教主的牵丝戏,文件大小为9.6MB。
先看一下文件信息:
$ffprobe test.mp3
Input #0, mp3, from 'test.mp3':
Metadata:
encoder : Lavf57.25.100
album : 排骨翻唱合集
artist : 排骨教主
title : 牵丝戏
comment : 163 key(Don't modify):L64FU3W4YxX3ZFTmbZ+8/VtyyFZpYaEZwBWeBYUoIhTD9n+9XApvnDI33SQMX5/hovsUgti9hVA1nVRCnV2p/JFk/KogWWpzJQE7UDv7jwhDvQEpzKhh5cV1ribc34A73au+8wyCBqJDRJP2g7PSBHwGuxUsoS2O64gLvAdVfhfjd9p5aDHjskwNs6ZjhoQ45q6BlPJRf+bTmH0STg/SHgWMyYdN6EeHUVkFQTY06
track : 12
Duration: 00:03:59.44, start: 0.025056, bitrate: 321 kb/s
Stream #0:0: Audio: mp3, 44100 Hz, stereo, fltp, 320 kb/s
Metadata:
encoder : Lavc57.24
Stream #0:1: Video: mjpeg (Baseline), yuvj420p(pc, bt470bg/unknown/unknown), 640x640 [SAR 256:256 DAR 1:1], 90k tbr, 90k tbn, 90k tbc (attached pic)
Metadata:
comment : Cover (front)
注意音频流Stream #0:0
这行信息,格式为mp3,采样率为44.1kHz,stereo立体声(即双声道),fltp表示数据格式为浮点型(float)。
解码流程图为:
可以看到和FFmpeg5入门教程05:解码视频流过程的基本流程是一样的。
解码代码为:
#include <stdio.h>
#include "libavcodec/avcodec.h"
#include "libavfilter/avfilter.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libavutil/ffversion.h"
#include "libswresample/swresample.h"
#include "libswscale/swscale.h"
#include "libpostproc/postprocess.h"
int main()
{
const char inFileName[] = "/home/jackey/Music/test.mp3";
const char outFileName[] = "test.pcm";
FILE *file=fopen(outFileName,"w+b");
if(!file){
printf("Cannot open output file.\n");
return -1;
}
AVFormatContext *fmtCtx =avformat_alloc_context();
AVCodecContext *codecCtx = NULL;
AVPacket *pkt=av_packet_alloc();
AVFrame *frame = av_frame_alloc();
int aStreamIndex = -1;
do{
if(avformat_open_input(&fmtCtx,inFileName,NULL,NULL)<0){
printf("Cannot open input file.\n");
return -1;
}
if(avformat_find_stream_info(fmtCtx,NULL)<0){
printf("Cannot find any stream in file.\n");
return -1;
}
av_dump_format(fmtCtx,0,inFileName,0);
for(size_t i=0;i<fmtCtx->nb_streams;i++){
if(fmtCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_AUDIO){
aStreamIndex=(int)i;
break;
}
}
if(aStreamIndex==-1){
printf("Cannot find audio stream.\n");
return -1;
}
AVCodecParameters *aCodecPara = fmtCtx->streams[aStreamIndex]->codecpar;
AVCodec *codec = avcodec_find_decoder(aCodecPara->codec_id);
if(!codec){
printf("Cannot find any codec for audio.\n");
return -1;
}
codecCtx = avcodec_alloc_context3(codec);
if(avcodec_parameters_to_context(codecCtx,aCodecPara)<0){
printf("Cannot alloc codec context.\n");
return -1;
}
codecCtx->pkt_timebase = fmtCtx->streams[aStreamIndex]->time_base;
if(avcodec_open2(codecCtx,codec,NULL)<0){
printf("Cannot open audio codec.\n");
return -1;
}
while(av_read_frame(fmtCtx,pkt)>=0){
if(pkt->stream_index==aStreamIndex){
if(avcodec_send_packet(codecCtx,pkt)>=0){
while(avcodec_receive_frame(codecCtx,frame)>=0){
/*
Planar(平面),其数据格式排列方式为 (特别记住,该处是以点nb_samples采样点来交错,不是以字节交错):
LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL...(每个LLLLLLRRRRRR为一个音频帧)
而不带P的数据格式(即交错排列)排列方式为:
LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRL...(每个LR为一个音频样本)
*/
if(av_sample_fmt_is_planar(codecCtx->sample_fmt)){
int numBytes =av_get_bytes_per_sample(codecCtx->sample_fmt);
//pcm播放时是LRLRLR格式,所以要交错保存数据
for(int i=0;i<frame->nb_samples;i++){
for(int ch=0;ch<codecCtx->channels;ch++){
fwrite((char*)frame->data[ch]+numBytes*i,1,numBytes,file);
}
}
}
}
}
}
av_packet_unref(pkt);
}
}while(0);
av_frame_free(&frame);
av_packet_free(&pkt);
avcodec_close(codecCtx);
avcodec_free_context(&codecCtx);
avformat_free_context(fmtCtx);
fclose(file);
return 0;
}
和解码视频的部分类似。解码结果为84.5MB。
我们使用ffplay播放一下看看效果:
ffplay -ar 44100 -ac 2 -f f32le -i test.pcm
ar为audio rate,ac为audio channel ,f32le为float 32位小端数据格式。
显示为:
没发现什么大问题。
完整代码在github中的15.ffmpeg_audio_decode_mp32pcm