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

本实验内容是 使用 ffmpeg 采集音频数据,并进行重采样,最后播放。

音频重采样: 将音频三元组(采样率,采样大小,通道数)的值转换为另一组值。如将 44100/16/2 装换成48000/16/2

为什么要进行音频重采样:

1 从设备采集的音频数据与编码器要求的数据格式不一致
2 扬声器要求的音频数据与要播放的音频数据格式不一致
3 更方便运算

需要注意的是 重采样的数据不能太小,否则无法进行重采样,我用的是Ubuntu 虚拟机,每次采集数据的 packet包大小只有64,而64数据太少了,无法重采样,所以需要先将数据存储到某个缓冲区,等积累到 1024 或者 2048个数据 后 再进行重采样。

由于我的虚拟机不支持 f32le 格式的数据,所以这里记录的实验 采样前和采用后设置的数据格式并没有变化,都是 s16le,只是为了实验而实验,重采了个寂寞。。。

Makefile

BINS := audiotest

CXX := gcc

#.h
INCDIRI :=
INCDIRI += -I /usr/local/ffmpeg/include

#LIB
INCDIRI += -L /usr/local/ffmpeg/lib

#SRCSALL
SRCSALL += ./

LIBS += -lavcodec -lavdevice -lavfilter -lavformat -lavutil -lswresample -lswscale


CFLAGS := $(INCDIRI)

$(BINS):
	$(CXX) -o $(BINS) main.c $(LIBS) $(CFLAGS) 

clean :
	rm $(BINS)

main.c

#include 
#include 
#include "libavutil/avutil.h"
#include "libavdevice/avdevice.h"
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswresample/swresample.h"


//创建,初始化 上下文
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;
}

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.pcm";
    FILE *outfile = fopen(out, "wb+");
    
	//创建上下文,并初始化
    SwrContext* swr_ctx = init_swr();
    
    //创建重采样数据输入缓冲区
	/*
	参数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);
        
        //进行内存拷贝,按字节拷贝的   放到 重采样输入缓冲区数组的第一个缓冲区
        //积攒到2048大小的数据 再重采样
		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);                       //输入单个通道的采样数
			
			//write file
			fwrite(dst_data[0], 1, dst_linesize, outfile);
			fflush(outfile);
			av_packet_unref(&pkt); //release pkt

		count = 0;
    }
    
    //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);
    
    //关闭设备 并 释放音频数据上下文
    avformat_close_input(&fmt_ctx);

    av_log(NULL, AV_LOG_DEBUG, "finish!\n");
    
    return;
}

int main(int argc, char *argv[])
{
    rec_audio();
    return 0;
}

播放 :ffplay -ar 48000 -ac 2 -f s16le audio.pcm

ffmpeg 采集pcm音频数据 重采样 后播放_第1张图片

你可能感兴趣的