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

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

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

还是上一篇使用的音频文件,本篇主要是修改解码后的一些参数,比如数据格式等等。

44100Hz就是音频的采样数,一段连续音频编码时包含多个数据包,一个数据包里面有连续数据,编码时会丢掉部分。比如说1秒的原始音频,44100Hz的意思就是我在这1秒的音频里面取44100个点,其他的数据丢掉。那么重采样最好的方法就是向下重采样,44100->40000,向上重采样时原始数据已经丢掉肯定会出现噪音。

解码流程图为:

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.05:保存视频帧FFmpeg入门教程10.14:mp3音频解码为pcm,之前的代码理解了这个就容易多了:

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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#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"

#define MAX_AUDIO_FRAME_SIZE 192000

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");
break;
}
if(avformat_find_stream_info(fmtCtx,NULL)<0){
printf("Cannot find any stream in file.\n");
break;
}

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");
break;
}

AVCodecParameters *aCodecPara = fmtCtx->streams[aStreamIndex]->codecpar;
AVCodec *codec = avcodec_find_decoder(aCodecPara->codec_id);
if(!codec){
printf("Cannot find any codec for audio.\n");
break;
}
codecCtx = avcodec_alloc_context3(codec);
if(avcodec_parameters_to_context(codecCtx,aCodecPara)<0){
printf("Cannot alloc codec context.\n");
break;
}
codecCtx->pkt_timebase=fmtCtx->streams[aStreamIndex]->time_base;

if(avcodec_open2(codecCtx,codec,NULL)<0){
printf("Cannot open audio codec.\n");
break;
}

//设置转码参数
uint64_t out_channel_layout = codecCtx->channel_layout;
enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
int out_sample_rate = codecCtx->sample_rate;
int out_channels = av_get_channel_layout_nb_channels(out_channel_layout);

uint8_t *audio_out_buffer = (uint8_t*)av_malloc(MAX_AUDIO_FRAME_SIZE*2);

SwrContext *swr_ctx = swr_alloc_set_opts(NULL,
out_channel_layout,
out_sample_fmt,
out_sample_rate,
codecCtx->channel_layout,
codecCtx->sample_fmt,
codecCtx->sample_rate,
0,NULL);
swr_init(swr_ctx);

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 len = swr_convert(swr_ctx,
&audio_out_buffer,
MAX_AUDIO_FRAME_SIZE*2,
(const uint8_t**)frame->data,
frame->nb_samples);
if(len<=0){
continue;
}

int dst_bufsize = av_samples_get_buffer_size(0,
out_channels,
len,
out_sample_fmt,
1);

//int numBytes =av_get_bytes_per_sample(out_sample_fmt);
//printf("number bytes is: %d.\n",numBytes);

fwrite(audio_out_buffer,1,dst_bufsize,file);

//pcm播放时是LRLRLR格式,所以要交错保存数据
// for(int i=0;i<frame->nb_samples;i++){
// for(int ch=0;ch<2;ch++){
// fwrite((char*)audio_out_buffer[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;
}

解码后的音频大小为42.4MB。

使用以下命令测试一下播放是否正常:

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

也可以使用ffmpeg软件命令来将MP3文件解码为pcm文件。

1
ffmpeg -i test.mp3 -f s16le -ar 44100 -ac 2 -acodec pcm_s16le test.pcm

完整代码在ffmpeg_beginner中的10.15.ffmpeg_audio_decode_swr_mp32pcm

下一篇:FFmpeg入门教程10.16:软件解码音频并使用QAudioOutput播放


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