命令行方式采集:
1
| ffmpeg -f avfoundation -i :0 output/out.wav
|
准备
这里创建的是Mac App,以及引入的是动态库。由于动态库存放在一个共享位置,编译时就固定了它所在的位置,所以不需要拷贝到目录中。
- 引入并链接动态库文件(General/Frameworks, Libraries, and Embedded Content))。
- 添加头文件搜索目录(Build Settings/User Header Search Paths)。
- 创建C语言头文件以及实现文件,并创建Bridging-Header。
采集音频
打开设备
步骤:
- 注册设备。
- 设置采集方式(avfouncdation✔️/dshow/alsa)。
- 打开音频设备。
打开之后就可以录制音频流。
必要头文件:
1 2 3
| #include "libavutil/avutil.h" #include "libavdevice/avdevice.h" #include "libavformat/avformat.h"
|
记得要引入的是动态库啊,静态库会有一堆符号找不到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| int ret = 0; AVFormatContext *context = NULL;
char *deviceName = ":0"; AVDictionary *options = NULL; size_t errorBufferLength = 1024; char errorBuffer[errorBufferLength];
avdevice_register_all();
AVInputFormat *inputFormat = av_find_input_format("avfoundation");
ret = avformat_open_input(&context, deviceName, inputFormat, &options); if (ret < 0) { av_strerror(ret, errorBuffer, errorBufferLength); fprintf(stderr, "Failed to open audio device, [%d]%s\n", ret, errorBuffer); return; }
|
注意要先获取麦克风权限。
上下文创建后,记得要对应进行释放。
读取音频数据
av_read_frame
:该方法既可以读取音频数据,也可以读取视频数据。
AVFormatContext
:格式上下文,上面打开设备也用到。上下文是后续处理的基础,在打开设备的时候就可以获取上下文。
AVPacket,音视频数据包结构体。
返回0则表示成功。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| AVPacket pkt; av_init_packet(&pkt);
int count = 0; int ret = 0; while ((ret == 0 || ret == -35) && count < 5) { ret = av_read_frame(context, &pkt); if (ret != 0) { av_strerror(ret, error_buffer, kErrorLength); fprintf(stderr, "Failed to reading, [%d]%s\n", ret, error_buffer); continue; } printf("pkt size is %d\n", pkt.size); count++; }
av_packet_unref(&pkt);
|
要注意,采集时有时会出现-35返回,是设备临时不可用,需要忽略,并重试。
记得av_read_frame
后要释放对应资源,避免内存泄漏。
AVPacket
头文件:libavcodec/avcodec.h
重要成员
data
:音视频具体数据。
size
:缓冲区数据大小。
相关API(成对使用):
如果只在栈空间使用AVPacket,则可以只用以下两个方法。
av_init_packet
:AVPacket初始化方法,但不会填充data
、size
。
av_packet_unref
:释放AVPacket资源。内部会释放buffer
的内存占用。
堆空间分配:
av_packet_alloc
:分配空间并初始化AVPacket,同样不会填充data、size。即包含了av_init_packet的调用。
av_packet_free
:释放AVPacket对应的资源,同样也包含了av_packet_unref调用。
写入到文件
基本步骤:
- 创建文件;
- 把音频写入到文件中;
- 关闭文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| char *output_path = "/Users/bq/Workspace/test/audio.pcm"; FILE *output_file = fopen(output_path, "wb+");
while ((ret == 0 || ret == -35) && count < 500) { ret = av_read_frame(context, &pkt);
printf("[%d] pkt size is %d\n", count, pkt.size); count++;
fwrite(pkt.data, pkt.size, 1, output_file); fflush(output_file);
av_packet_unref(&pkt); }
fclose(output_file); printf("完成写入");
|
播放测试:
1
| ffplay -ar 44100 -ac 2 -f f32le audio.pcm
|
编码音频
FFmpeg编码基本过程:
- 创建编码器;
- 创建上下文;
- 打开编码器;
- 送数据给编码器;编码器一般是要缓冲一部分帧,才能编码输出帧。
- 编码;
- 释放资源。
打开编码器API:
avcodec_find_encoder
:查找编码器。通过id或名字查找。
avcodec_alloc_context3
:创建上下文。
avcodec_open2
:打开编码器。
fdk_aac,支持的采样大小是16位的,不能设置为FLT。设置了profile后,需要bit_rate置0,否则profile设置不生效。
传输数据API:
avcodec_send_frame
,把帧输入到编码器。顾名思义,其传入的是AVFrame。会先缓冲一部分数据。
avcodec_receive_packet
,获取编码后的数据。顾名思义,其输出的是AVPacket。
AVFrame与AVPacket,从命名上看,似乎frame是解压后的帧、packet是压缩后的数据包。之前打开设备并从中av_read_frame
出来的却是个packet,这是因为FFmpeg把设备视为媒体文件处理。而从媒体文件读取的就是packet数据。即按照正规流程,从设备读取帧获得packet后,还需要走解码的步骤,最后得出AVFrame。我们是知道从设备读取的帧就是未压缩的帧,所以就直接从packet里面拿数据了。这其实也是种投机取巧的方式。
重采样音频
基本步骤:
- 创建重采样上下文;
- 设置参数;
- 初始化重采样;
对应API:
swr_alloc_set_opts
:创建了重采样的上下文,并进行了初始化。
swr_init
swr_convert
swr_free
需要头文件:
libswresample/swresample.h
channel layout:指扬声器的布局,用它来表示声道信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| char *output_path = "/Users/bq/Workspace/test/audio.pcm"; FILE *output_file = fopen(output_path, "wb+");
SwrContext *swr_ctx = swr_alloc_set_opts(NULL, AV_CH_LAYOUT_MONO, AV_SAMPLE_FMT_S16, 44100, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_FLT, 44100, 0, NULL );
if (!swr_ctx || swr_init(swr_ctx) < 0) { printf("重采样上下文创建失败"); }
const int ch_length = 4096 / 4 / 2; uint8_t **src_data = NULL; int src_data_length = 0;
av_samples_alloc_array_and_samples(&src_data, &src_data_length, 2, ch_length, AV_SAMPLE_FMT_FLT, 0);
uint8_t **dst_data = NULL; int dst_data_length = 0;
av_samples_alloc_array_and_samples(&dst_data, &dst_data_length, 1, ch_length, AV_SAMPLE_FMT_S16, 0);
while ((ret == 0 || ret == -35) && count < 500) { ret = av_read_frame(context, &pkt);
if (ret == -35) continue; printf("[%d] pkt size is %d\n", count, pkt.size); count++;
memcpy(src_data[0], pkt.data, pkt.size);
swr_convert(swr_ctx, dst_data, 512, (const uint8_t **)src_data, 512 );
fwrite(dst_data[0], 1, dst_data_length, output_file); fflush(output_file);
av_packet_unref(&pkt); }
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);
fclose(output_file); printf("完成写入");
|
播放测试:
1
| ffplay -ar 44100 -ac 1 -f s16le audio.pcm
|