上一节,我们使用alsa库编写了音频的采集和播放的程序。这一节,我们将在采集到的pcm格式的音频数据的基础上,进一步将其编码为aac格式。
pcm是最原始的音频编码格式,这种编码是无损的。同时意味着存储这种数据的文件将会很庞大,因此必须进行压缩。pcm是音频的编码格式,它不是文件的封装格式,上一节我们录制的声音存储在一个.pcm为后缀的文件中,这只是我们愿意这么做而已,你完全可以不这么做,这没有关系。
aac既是一种文件的封装格式,又是音频的编码格式。一aac为封装格式的文件,以.aac为后缀。aac封装格式一般内部的音频数据编码格式也为aac。
音频编码和视频编码的流程基本一致,而视频编码我们在前面已经做过了。因此,关于程序的流程就没有太多需要废话的了。下面介绍几个音频相关的参数,这几个参数是编码器进行编码所必需的。
我们总共需要设置四个参数即可:
1.sample_rate
codecContext->sample_rate = frame->sample_rate;
sample_rate指的是采样率。也就是我们一秒钟采集多少次声音样本。
2.frame->channels
codecContext->channels = frame->channels;
frame->channels之的是通道的数目。音频一般有双通道或者单通道之分,一般都是双通道吧,我们的程序里面也是设置为双通道的。也就是frame->channels=2.
3.frame->format
codecContext->sample_fmt = frame->format;
frame->format指的是样本的格式。一个音频的样本一般用两个字节来描述,分为大小端。我们的程序中使用的是16bit的小端格式。
4.channel_layout
codecContext->channel_layout = AV_CH_LAYOUT_STEREO;
channel_layout 用来设置输出通道布局。这个参数不太理解!!!
下面结合程序流程图和程序中的注释,来看程序:
//created by Jinwei Liu
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv){
AVFrame *frame;
AVCodec *codec = NULL;
AVPacket packet;
AVCodecContext *codecContext;
int readSize=0;
int ret=0,getPacket;
FILE * fileIn,*fileOut;
int frameCount=0;
/* register all the codecs */
av_register_all();
if(argc!=3){
fprintf(stdout,"usage:./a.out xxx.pcm xxx.aac\n");
return -1;
}
//1.我们需要读一帧一帧的数据,所以需要AVFrame结构
//读出的一帧数据保存在AVFrame中。
frame = av_frame_alloc();
frame->format = AV_SAMPLE_FMT_S16;
frame->nb_samples = 1024;
frame->sample_rate = 11025;
frame->channels = 2;
fileIn =fopen(argv[1],"r+");
frame->data[0] = av_malloc(1024*4);
//2.读出来的数据保存在AVPacket中,因此,我们还需要AVPacket结构体
//初始化packet
memset(&packet, 0, sizeof(AVPacket));
av_init_packet(&packet);
//3.读出来的数据,我们需要编码,因此需要编码器
//下面的函数找到h.264类型的编码器
/* find the mpeg1 video encoder */
codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
if (!codec){
fprintf(stderr, "Codec not found\n");
exit(1);
}
//有了编码器,我们还需要编码器的上下文环境,用来控制编码的过程
codecContext = avcodec_alloc_context3(codec);//分配AVCodecContext实例
if (!codecContext){
fprintf(stderr, "Could not allocate video codec context\n");
return -1;
}
/* put sample parameters */
codecContext->sample_rate = frame->sample_rate;
codecContext->channels = frame->channels;
codecContext->sample_fmt = frame->format;
/* select other audio parameters supported by the encoder */
codecContext->channel_layout = AV_CH_LAYOUT_STEREO;
//准备好了编码器和编码器上下文环境,现在可以打开编码器了
//根据编码器上下文打开编码器
if (avcodec_open2(codecContext, codec, NULL) < 0){
fprintf(stderr, "Could not open codec\n");
return -1;
}
//4.准备输出文件
fileOut= fopen(argv[2],"w+");
//下面开始编码
while(1){
//读一帧数据出来
readSize = fread(frame->data[0],1,1024*4,fileIn);
if(readSize == 0){
fprintf(stdout,"end of file\n");
frameCount++;
break;
}
//初始化packet
av_init_packet(&packet);
/* encode the image */
frame->pts = frameCount;
ret = avcodec_encode_audio2(codecContext, &packet, frame, &getPacket); //将AVFrame中的像素信息编码为AVPacket中的码流
if (ret < 0) {
fprintf(stderr, "Error encoding frame\n");
goto out;
}
if (getPacket) {
frameCount++;
//获得一个完整的编码帧
printf("Write frame %3d (size=%5d)\n", frameCount, packet.size);
fwrite(packet.data, 1,packet.size, fileOut);
av_packet_unref(&packet);
}
}
/* flush buffer */
for ( getPacket= 1; getPacket; frameCount++){
frame->pts = frameCount;
ret = avcodec_encode_audio2(codecContext, &packet, NULL, &getPacket); //输出编码器中剩余的码流
if (ret < 0){
fprintf(stderr, "Error encoding frame\n");
goto out;
}
if (getPacket){
printf("flush buffer Write frame %3d (size=%5d)\n", frameCount, packet.size);
fwrite(packet.data, 1, packet.size, fileOut);
av_packet_unref(&packet);
}
} //for (got_output = 1; got_output; frameIdx++)
out:
fclose(fileIn);
fclose(fileOut);
av_frame_free(&frame);
avcodec_close(codecContext);
av_free(codecContext);
return 0;
}
现在,我们还不能运行这个程序,因为我们在便宜ffmpeg工程的时候没有使能aac编码器,所以我们需要重新编译ffmpeg,是的aac编码器可用。
libfdk_aac:
官网关于使能aac的介绍
-enable-nonfree –enable-libfdk-aac
同时我们需要安装libfdk-aac库:
sudo apt-get install libfdk-aac-dev
然后我们需要重新配置和便宜ffmpeg工程:
1.执行
./configure --enable-libx264 --enable-gpl --enable-decoder=h264 --enable-encoder=libx264 --enable-shared --enable-static --disable-yasm --enable-nonfree --enable-libfdk-aac --enable-shared --prefix=tmp
2.make
3.make install
有了aac编码器以后,再次运行这个程序,就可以将pcm格式的音频数据编码为aac格式了,例如,执行如下命令:
./out.bin recorder.pcm recorder.aac
使用ffplay recorder.aac即可听到声音。
Makefile
VAR_INCLUDE := -I /home/mlinux/work/ffmpeg-2.7/ffmpeg-2.7.6/tmp/include
VAR_SHARED_LIB_DIR = -L /home/mlinux/work/ffmpeg-2.7/ffmpeg-2.7.6/tmp/lib
VAR_SHARED_LIBS = -l avcodec -l avformat -l avdevice -l avutil -l swresample -l avfilter -l swscale
out.bin:pcm_2_aac.o
gcc -o $@ $^ $(VAR_INCLUDE) $(VAR_SHARED_LIB_DIR) $(VAR_SHARED_LIBS)
%.o:%.c
gcc -c $< $(VAR_INCLUDE) $(VAR_SHARED_LIB_DIR) $(VAR_SHARED_LIBS)
clean:
rm -rf *.o *.bin
init.sh
export LD_LIBRARY_PATH=/home/mlinux/work/ffmpeg-2.7/ffmpeg-2.7.6/tmp/lib
看过前面文章的reader就应该知道,init.sh就是到处共享库的位置,让我们的可执行程序知道动态链接库的位置。