FFmpeg入门教程10.14:mp3音频解码为pcm

系列索引:FFmpeg入门系列索引

上一篇:FFmpeg入门教程10.13:Linux下摄像头捕获并编码为h264(无音频)

本系列的之前文章介绍了视频的编解码相关,接下来介绍音频的编解码,本文将mp3音频文件解码为pcm。

使用的mp3音频文件为从网易云音乐上下载的排骨教主的牵丝戏,文件大小为9.6MB。

先看一下文件信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$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)。

解码流程图为:

flowchart TB
    F --Yes--> I
    K --下一帧--> F
    I --No--> F
    subgraph init
        direction TB
        A(开始) --> B[打开文件]
        B --> C[查找流信息]
        C --> D[查找对应解码器]
        D --> E[打开解码器]
        E --> F{读取帧}
        F --No--> G(结束)
    end
    subgraph decode
        direction TB
        I{是视频帧?} --Yes--> J[发送帧给解码器]
        J --> K[从解码器获取结果]
    end
flowchart TB
    F --Yes--> I
    K --Next Frame--> F
    I --No--> F
    subgraph init
        direction TB
        A(Start) --> B[avformat_open_input]
        B --> C[av_find_stream_info]
        C --> D[avcodec_find_decoder]
        D --> E[avcodec_open]
        E --> F{av_read_frame}
        F --No--> G(End)
    end
    subgraph decode
        direction TB
        I{Video Packet?} --Yes--> J[avcodec_send_packet]
        J --> K[avcodec_receive_frame]
    end

可以看到和FFmpeg入门教程10.04:解码视频流过程的基本流程是一样的。

解码代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#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播放一下看看效果:

1
ffplay -ar 44100 -ac 2 -f f32le -i test.pcm 

ar为audio rate,ac为audio channel ,f32le为float 32位小端数据格式。

显示为:

result

没发现什么大问题。

完整代码在ffmpeg_beginner中的10.14.ffmpeg_audio_decode_mp32pcm

下一篇:FFmpeg入门教程10.15:音频重采样解码为pcm


FFmpeg入门教程10.14:mp3音频解码为pcm
https://blog.jackeylea.com/ffmpeg/ffmpeg-audio-decode-mp3-to-pcm-with-cpu/
作者
JackeyLea
发布于
2020年11月3日
许可协议