ffmpeg 采集pcm音频数据 重采样 编码 后播放

本实验 需要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

猜你喜欢

转载自blog.csdn.net/LinuxArmbiggod/article/details/121461780