FFmpeg4入门系列教程18:pcm编码为mp3

索引地址:系列教程索引地址

上一篇:FFmpeg4入门系列教程17:软件解码音频并使用QAudioOutput播放

同视频类似,pcm作为原始音频数据,文件体积太大。所以使用编码器将其在不失真的情况下减少体积。

测试音频信息:

1
2
3
Input #0, mp3, from 'sunny.mp3':
Duration: 00:02:20.04, start: 0.000000, bitrate: 112 kb/s
Stream #0:0: Audio: mp3, 44100 Hz, stereo, fltp, 112 kb/s

大小为1.9M,格式为MP3。

先用ffmpeg将其转换为pcm格式。

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

生成的s16le.pcm大小为23.6M。

编码

仿照FFmpeg4入门系列教程12:本地yuv文件编码为h264的代码写pcm2mp3的代码:

打开文件、查找编码器、设置编码器数据、打开编码器部分

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
//==========Output information============

if(avformat_alloc_output_context2(&fmtCtx,NULL,NULL,outFileName)<0){
printf("Cannot alloc output file context.\n");
return -1;
}
AVOutputFormat *outFmt = fmtCtx->oformat;

if(avio_open(&fmtCtx->pb,outFileName,AVIO_FLAG_READ_WRITE)<0){
printf("Cannot open output file.\n");
return -1;
}

AVStream *outStream = avformat_new_stream(fmtCtx,NULL);
if(!outStream){
printf("Cannot create a new stream to output file.\n");
return -1;
}

//设置参数
AVCodecParameters *codecPara = fmtCtx->streams[outStream->index]->codecpar;
codecPara->codec_type = AVMEDIA_TYPE_AUDIO;
codecPara->codec_id = outFmt->audio_codec;
codecPara->sample_rate=44100;
codecPara->channel_layout = AV_CH_LAYOUT_STEREO;
codecPara->bit_rate = 128000;
codecPara->format = AV_SAMPLE_FMT_FLTP;
codecPara->channels = av_get_channel_layout_nb_channels(codecPara->channel_layout);

//查找编码器
codec = avcodec_find_encoder(outFmt->audio_codec);
if(codec==NULL){
printf("Cannot find audio encoder.\n");
return -1;
}

codecCtx = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(codecCtx,codecPara);
if(codecCtx==NULL){
printf("Cannot alloc codec ctx from para.\n");
return -1;
}

//打开编码器
if(avcodec_open2(codecCtx,codec,NULL)<0){
printf("Cannot open encoder.\n");
return -1;
}

av_dump_format(fmtCtx,0,outFileName,1);

设置重采样参数

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
//===========
frame->nb_samples = codecCtx->frame_size;
frame->format = codecCtx->sample_fmt;
frame->channels = 2;

// PCM重采样
struct SwrContext *swrCtx = swr_alloc_set_opts(NULL,
av_get_default_channel_layout(codecCtx->channels),
codecCtx->sample_fmt,
codecCtx->sample_rate,
av_get_default_channel_layout(frame->channels),
AV_SAMPLE_FMT_S16,// PCM源文件的采样格式
44100,0,NULL);
swr_init(swrCtx);

/* 分配空间 */
uint8_t **convert_data = (uint8_t**)calloc(codecCtx->channels,sizeof(*convert_data));
av_samples_alloc(convert_data,NULL,codecCtx->channels,
codecCtx->frame_size,
codecCtx->sample_fmt,0);

int size = av_samples_get_buffer_size(NULL,codecCtx->channels,
codecCtx->frame_size,codecCtx->sample_fmt,1);
uint8_t *frameBuf = (uint8_t*)av_malloc(size);
avcodec_fill_audio_frame(frame,codecCtx->channels,codecCtx->sample_fmt,
(const uint8_t*)frameBuf,size,1);

最为关键的编码每一帧数据的部分:

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
for(int i=0;;i++){
//输入一帧数据的长度
int length = frame->nb_samples*av_get_bytes_per_sample(AV_SAMPLE_FMT_S16)*frame->channels;
//读PCM:特意注意读取的长度,否则可能出现转码之后声音变快或者变慢
if(fread(frameBuf,1,length,inFile)<=0){
printf("Cannot read raw data from file.\n");
return -1;
}else if(feof(inFile)){
break;
}

swr_convert(swrCtx,convert_data,codecCtx->frame_size,
(const uint8_t**)frame->data,
frame->nb_samples);

//输出一帧数据的长度
length = codecCtx->frame_size * av_get_bytes_per_sample(codecCtx->sample_fmt);
//双通道赋值(输出的AAC为双通道)
memcpy(frame->data[0],convert_data[0],length);
memcpy(frame->data[1],convert_data[1],length);

frame->pts = i*100;
if(avcodec_send_frame(codecCtx,frame)<0){
while(avcodec_receive_packet(codecCtx,pkt)>=0){
pkt->stream_index = outStream->index;
printf("write %4d frame, size=%d, length=%d\n",i,size,length);
av_write_frame(fmtCtx,pkt);
}
}
av_packet_unref(pkt);
}

编码之后的mp3文件大小为1M,这个过程中肯定丢失了一些数据。

比较一下结果文件和源文件:

pcm2mp3

根据波形来看基本上差不多。

如果需要编码为其他的格式,可以自己调整参数。

完整代码在ffmpeg_Beginner中的ffmpeg_audio_encode_pcm2mp3中。

下一篇:FFmpeg4入门系列教程19:实现简单音视频同步和简单视频播放器