本实验 需要AAC编码器,需要 libfdk-aac 包
大致数据流程:
PCM数据 -->
重采样数据输入缓冲区 --> 进行重采样 -->重采样数据输出缓冲区 -->
AVFrame编码器输入数据缓冲区 --->AAC编码器 ----> AVPacket码器输出数据缓冲区 --->AAC数据文件
.
#include <stdio.h>
#include <string.h>
#include "libavutil/avutil.h"
#include "libavdevice/avdevice.h"
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswresample/swresample.h"
/* 将音频帧数据 送给 编码器进行编码 ,将编码后的数据 写到目标文件中
参数1 AVCodecContext *ctx 编码器上下文
参数2 AVFrame *frame 编码前的音频帧数据输入给编码器
参数3 AVPacket *pkt 编码器编码后的音频帧数据
参数4 FILE *output 编码后的音频帧写到目标文件
*/
static void encode(AVCodecContext *ctx,
AVFrame *frame,
AVPacket *pkt,
FILE *output){
int ret = 0;
//将数据送给编码器 (将一帧的音频送给编码器处理)
/*
参数1:编码器上下文
参数2:需要编码的音频数据
*/
ret = avcodec_send_frame(ctx, frame);
//如果ret>=0说明数据送给编码器成功,此后 我们才能从编码器中去获取编码好的音频帧数据
while(ret >= 0){
//获取编码后的音频数据,如果成功,需要重复获取,直到失败为止
/*
我们向编码器送一帧音频帧数据的时候,编码器不一定就会马上返回一个AVPacket音频帧。
有可能是我们送了很多帧音频数据后,编码器才会返回一个编码好的AVPacket音频帧。也有
可能同时返回多个编码好的AVPacket音频帧。
参数1:编码器上下文
参数2:编码器编码后的音频帧数据
*/
ret = avcodec_receive_packet(ctx, pkt);
/*
ret > 0 表示本次获取成功,然后继续循环获取,因为有可能后面还有编码好的数据需要获取
ret == AVERROR(EAGAIN) : 表示当前编码没有任何问题,但是输入的音视频帧不够,所以当前没有packet输出,需要继续送入frame进行编码码
ret == AVERROR_EOF :内部缓冲区中数据全部编码完成,不再有编码后的数据包输出。编码到最后了 没有任何数据了
other ,ret < 0 && ret!= AVERROR(EAGAIN) && ret!=AVERROR_EOF :编码错误 直接退出
*/
if(ret == AVERROR(EAGAIN)){
printf("data is not enough\n");
return;
}
if(ret == AVERROR_EOF){
printf("encoding end\n");
return;
}else if( ret < 0){ // 编码错误直接退出
printf("Error, encoding audio frame\n");
exit(-1);
}
//write file 将编码后的音频帧数据 写到 output文件
fwrite(pkt->data, 1, pkt->size, output);
fflush(output);
}
return;
}
//创建,初始化 上下文
SwrContext* init_swr(){
//创建 重采样 上下文
SwrContext *swr_ctx = NULL;
/*创建 并 设置 重采样上下文实例(输入,输出格式信息),返回上下文信息
参数1: 重采样的上下文,可以是已经创建好的上下文,如果之前没有上下文 可以设置为NULL
参数2: 重采样后输出目标 通道的布局:立体声,如放几个喇叭。 AV_CH_LAYOUT_STEREO(左前方和右前方)
参数3:输出数据的采样格式
参数4:采样率 44100
参数5:输入的 通道的布局:立体声,AV_CH_LAYOUT_STEREO(左前方和右前方)
参数6:输入的采样格式 32位浮点型
参数7:输入数据的采样率
参数8 9 :log 相关,暂时不用
*/
swr_ctx = swr_alloc_set_opts(NULL, //ctx
AV_CH_LAYOUT_STEREO, //输出channel布局
AV_SAMPLE_FMT_S16, //输出的采样格式 AV_SAMPLE_FMT_S16
48000, //采样率
AV_CH_LAYOUT_STEREO, //输入channel布局
AV_SAMPLE_FMT_S16, //输入的采样格式 AV_SAMPLE_FMT_FLT
48000, //输入的采样率
0, NULL);
if(!swr_ctx){
//失败
}
/*
创建设置 重采样上下文实例后,初始化 重采样上下文实例
*/
if(swr_init(swr_ctx) < 0){
//失败
}
//返回 初始化好的 重采样上下文实例
return swr_ctx;
}
/* 打开编码器
获取编码器,创建编码器上下文,设置编码器上下文,打开编码器
返回编码器上下文
*/
static AVCodecContext* open_coder(){
//打开编码器
/*
通过 ID 查找获取编码器
或者
通过名字 查找获取编码器
*/
//avcodec_find_encoder(AV_CODEC_ID_AAC);
AVCodec *codec = avcodec_find_encoder_by_name("libfdk_aac");
if(codec == NULL){
printf("codec creat failed\n");
exit(1);
}
//创建 编码器codec 上下文
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
/*
设置编码器上下文
*/
codec_ctx->sample_fmt = AV_SAMPLE_FMT_S16; //输入音频数据的采样大小
codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO; //输入音频的通道布局 channel layout:立体声,AV_CH_LAYOUT_STEREO(左前方和右前方)
codec_ctx->channels = 2; //输入音频 channel 个数
codec_ctx->sample_rate = 44100; //输入音频的采样率
/*
需要注意的是,只有当 bit_rate属性为0的时候 ffmpeg才会查找 profile属性值,并确认是哪个编码器,并会设置他们各自对应的默认的码率。
*/
codec_ctx->bit_rate = 0; //码率大小 AAC_LC: 128K, AAC HE: 64K, AAC HE V2: 32K
codec_ctx->profile = FF_PROFILE_AAC_HE_V2; //选择 AAC HE V2 编码器
//打开编码器
/*
参数1: 编码器上下文
参数2: 获取的编码器
参数3:其他选项 暂时不用
*/
if(avcodec_open2(codec_ctx, codec, NULL)<0){
//
return NULL;
}
return codec_ctx;
}
void rec_audio() {
int ret = 0;
char errors[1024] = {0, };
int count = 0;
//重采样输入缓冲区 地址 大小
uint8_t **src_data = NULL;
int src_linesize = 0;
uint8_t **dst_data = NULL;
int dst_linesize = 0;
//音频数据上下文
AVFormatContext *fmt_ctx = NULL;
AVDictionary *options = NULL;
//pakcet
AVPacket pkt;
//[[video device]:[audio device]]
//音频输入设备 我的ubuntu系统下音频设备是 hw:0,0
char *devicename = "hw:0";
//set log level
av_log_set_level(AV_LOG_DEBUG);
//register audio device 向ffmpeg注册设备
avdevice_register_all();
//设置采集方式,对于不同的平台,采集数据的方式不同 linux系统是
/*
返回值:输入格式
*/
AVInputFormat *iformat = av_find_input_format("alsa");
//打开音频设备
/*
参数1 获得 音频数据上下文 AVFormatContext
参数2 网络地址/本地文件(设备名)
参数3 输入格式
参数4 其他参数 这里为NULL
*/
if((ret = avformat_open_input(&fmt_ctx, devicename, iformat, &options)) < 0 ){
av_strerror(ret, errors, 1024);
fprintf(stderr, "Failed to open audio device, [%d]%s\n", ret, errors);
return;
}
//创建输出的音频文件 将音频数据写到该文件
char *out = "/home/mhr/Desktop/video/audio_test/audio.aac";
FILE *outfile = fopen(out, "wb+");
//打开编码器
/*
获取编码器,创建编码器上下文,设置编码器上下文,打开编码器
返回编码器上下文
*/
AVCodecContext *c_ctx = open_coder();
if(!c_ctx){
printf("open coder failed\n");
exit(1);
}
//创建上下文,并初始化
SwrContext* swr_ctx = init_swr();
//AAC音频输入数据 空间初始化
//初始化 AVFrame 结构,它用于存放未编码的音频数据的空间
AVFrame *frame = av_frame_alloc();
if(!frame){
printf("Error, No Memory!\n");
goto __ERROR;
}
//设置 AVFrame 结构 信息
frame->nb_samples = 512; //单通道一个音频帧的采样数
frame->format = AV_SAMPLE_FMT_S16; //采样的大小
frame->channel_layout = AV_CH_LAYOUT_STEREO; //通道布局 立体声 channel layout
/* 为 AVFrame 结构 分配空间,用于存放未编码的音频数据的空间
他也是根据 前面三个参数 得知需要的缓冲区大小 = 采样大小*采样频率*通道数
参数1: AVFrame
参数2: 对齐方式
*/
av_frame_get_buffer(frame, 0); // 512 * 2 * 2= 2048 所以音频编码之前的数据就是2048
if(!frame->data[0]){
printf("Error, Failed to alloc buf in frame!\n");
//内存泄漏
goto __ERROR;
}
//AAC音频输出数据 空间初始化
//分配 编码后数据空间
AVPacket *newpkt = av_packet_alloc();
if(!newpkt){
printf("Error, Failed to alloc buf in frame!\n");
goto __ERROR;
}
//创建重采样数据输入缓冲区
/*
参数1:生成的缓冲区
参数2:生成的缓冲区大小
参数3:通道数
参数4:单通道采样个数 : 4096/4=1024/2=512 采集每一帧音频数据的数据量(字节单位)/采集格式32位(4字节)/通道数
参数5:采样格式 32位浮点型 AV_SAMPLE_FMT_FLT
参数6:对齐
*/
av_samples_alloc_array_and_samples(&src_data, //输入缓冲区地址
&src_linesize, //缓冲区的大小
2, //通道个数
512, //单通道采样个数
AV_SAMPLE_FMT_S16, //采样格式 AV_SAMPLE_FMT_FLT AV_SAMPLE_FMT_S32
0);
//创建重采样数据输出缓冲区
av_samples_alloc_array_and_samples(&dst_data, //输出缓冲区地址
&dst_linesize, //缓冲区的大小
2, //通道个数
512, //单通道采样个数
AV_SAMPLE_FMT_S16, //采样格式 AV_SAMPLE_FMT_S16
0);
/* read data from device 获取音频数据 到pkt
参数1: 音频数据上下文
参数2: 音频数据存放的目标地址
*/
while((ret = av_read_frame(fmt_ctx, &pkt)) == 0) {
av_log(NULL, AV_LOG_INFO,"packet size is %d(%p) count=%d\n", pkt.size, pkt.data,count);
if(count < 32){
//进行内存拷贝,按字节拷贝的 放到 重采样输入缓冲区数组的第一个缓冲区
memcpy((void*)(src_data[0]+count*pkt.size), (void*)pkt.data, pkt.size);
if(count < 31){
count++;
continue;
}
}
printf("store %d data\n",count*pkt.size);
//在写之前 对每一帧数据进行重采样,重采样之后 再将数据写入文件中。
/*
参数1:重采样上下文
参数2:重采样后 输出的位置
参数3:输出数据的每个通道的采样个数
参数4:输入数据 存储位置
参数5:输入单个通道的采样数
*/
swr_convert(swr_ctx, //重采样的上下文
dst_data, //重采样数据输出缓冲区
512, //每个通道的采样数
(const uint8_t **)src_data, //输入缓冲区
512); //输入单个通道的采样数
//将重采样的数据拷贝到 AAC音频输入数据 空间frame 中,交给编码器用于编码
memcpy((void *)frame->data[0], dst_data[0], dst_linesize);
/* 将音频帧数据 送给 编码器进行编码 ,然后将编码后的数据 写到目标文件中
参数1 AVCodecContext *ctx 编码器上下文
参数2 AVFrame *frame 编码前的音频帧数据输入给编码器
参数3 AVPacket *pkt 编码器编码后的音频帧数据
参数4 FILE *output 编码后的音频帧写到目标文件
*/
encode(c_ctx, frame, newpkt, outfile);
av_packet_unref(&pkt); //release pkt
count = 0;
}
/*再次调用编码器,强制让编码器编码缓存中剩下的音频帧数据 并吐出
此时不送数据到编码器,让编码器对缓冲区中剩下的数据进行编码并吐出
*/
encode(c_ctx, NULL, newpkt, outfile);
__ERROR:
//close file
fclose(outfile);
//释放输入输出缓冲区
if(src_data){
av_freep(&src_data[0]);
}
av_freep(src_data);
if(dst_data){
av_freep(&dst_data[0]);
}
av_freep(dst_data);
//释放重采样的上下文
swr_free(&swr_ctx);
//释放 AVFrame 和 AVPacket
if(frame){
av_frame_free(&frame);
}
if(newpkt){
av_packet_free(&newpkt);
}
//关闭设备 并 释放音频数据上下文
avformat_close_input(&fmt_ctx);
av_log(NULL, AV_LOG_DEBUG, "finish!\n");
return;
}
int main(int argc, char *argv[])
{
rec_audio();
return 0;
}
播放:ffmplay audio.aac
|