本实验内容是 使用 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