音频重采样,主要遇到了两个问题,
1 写入的 audio.pcm 文件的路径,如果在 app 的沙盒路径之外,是不能打开和写入数据的, macOS app 新手在这个坑里面折腾了很久,
2 重采样的函数和旧版的不一样,
3 重采样的 audio.pcm 是原始数据,没有任何的头信息,想要播放必须给一些参数
ffplay -ar 44100 -ac 2 -f f32le audio.pcm
其中 -ac 是设置通道的,如果终端报错,提示参数不对,可以不传,去掉
上代码:
//
// testc.c
// myapp
//
// Created by mac on 2025/6/23.
//
#include "testc.h"
static int record_status = 0;
char* getSandboxHomeFromEnv(void) {
return getenv("HOME"); // 返回容器内的 Data 目录(如 /Users/username/Library/Containers/com.app/Data)
}
void set_record_status(int status) { record_status = status; }
void record_audio(void) {
char *home = getSandboxHomeFromEnv();
printf("sandbox home: %s\n", home);
// char *file_name = "/myapp/audio.pcm";
char *file_name = "/audio.pcm";
char sound_output_path[1024] = "";
strcat(sound_output_path, home);
printf("--0--sandbox out_path: %s\n", sound_output_path);
strcat(sound_output_path, file_name);
printf("--1--sandbox out_path: %s\n", sound_output_path);
int ret = 0;
char errors[1024] = {
0,
};
AVFormatContext *fmt_ctx = NULL;
FILE *out_file = NULL;
int count = 0;
// ffmpeg -f avfoundation -list_devices true -i ' '
// [[video device]:[audio device]]
// char *devicename = ":2";
// char *devicename = ":1";
char *devicename = ":0";
// 设置日志级别
av_log_set_level(AV_LOG_DEBUG);
// 开始录制
record_status = 1;
// 1 register audio device
avdevice_register_all();
// 2 get format
// const AVInputFormat *iformat = av_find_input_format("avfoundation");
const AVInputFormat *input_format = av_find_input_format("avfoundation");
AVDictionary *options = NULL;
av_dict_set(&options, "sample_rate", "44100", 0);
av_dict_set(&options, "channels", "2", 0);
// 3 open device
ret = avformat_open_input(&fmt_ctx, devicename, input_format, &options);
if (ret < 0) {
av_strerror(ret, errors, 1024);
fprintf(stderr, "Failed to open audio device [%d] %s\n", ret, errors);
return;
}
// create file
// char *out = sound_output_path;
out_file = fopen(sound_output_path, "wb+");
if (!out_file) {
perror("Failed to open output file\n");
avformat_close_input(&fmt_ctx);
return;
}
// 重采样上下文
SwrContext *swr_ctx = NULL;
AVChannelLayout out_ch_layout = {0};
AVChannelLayout in_ch_layout = {0};
// 初始化输出声道布局(例如:立体声)
av_channel_layout_default(&out_ch_layout, 2); // 2 声道
// 初始化输入声道布局(例如:单声道)
av_channel_layout_default(&in_ch_layout, 2); // 2 声道
// Allocate SwrContext
ret = swr_alloc_set_opts2(
&swr_ctx, // ctx
&out_ch_layout, // out_ch_layout
AV_SAMPLE_FMT_S16, // output sample format or bitdepth
44100, // output sample rate
&in_ch_layout, // in_ch_layout
AV_SAMPLE_FMT_FLT, // input sample format or bitdepth
44100, // input sample rate
0, //
NULL);
if (ret != 0) {
fprintf(stderr, "Could not allocate resampler context\n");
fclose(out_file);
avformat_close_input(&fmt_ctx);
return;
}
if (swr_init(swr_ctx) < 0) {
fprintf(stderr, "Could not initialize resampler context\n");
fclose(out_file);
avformat_close_input(&fmt_ctx);
swr_free(&swr_ctx);
return;
}
// AVPacket 使用之前要初始化
// av_init_packet(&pkt);
// 读出来的数据存储到这个 packet 里面
AVPacket *pkt = av_packet_alloc(); // 动态分配 AVPacket
if (!pkt) {
fprintf(stderr, "Failed to allocate packet\n");
goto end;
}
uint8_t **src_data = NULL;
int src_linesize = 0;
uint8_t **dst_data = NULL;
int dst_linesize = 0;
// 4096/4=1024/2=512
// 创建输入缓冲区
ret = av_samples_alloc_array_and_samples(&src_data, // 输出缓冲区地址
&src_linesize, // 缓冲区大小
2, // 通道个数
512, // 单通道采样个数
AV_SAMPLE_FMT_FLT, // 采样格式
0);
if (ret < 0) {
fprintf(stderr, "Failed to allocate src_data\n");
goto end;
}
// 创建输出缓冲区
ret = av_samples_alloc_array_and_samples(&dst_data, // 输出缓冲区地址
&dst_linesize, // 缓冲区大小
2, // 通道个数
512, // 单通道采样个数
AV_SAMPLE_FMT_S16, // 采样格式
0);
if (ret < 0) {
fprintf(stderr, "Failed to allocate dst_data\n");
goto end;
}
while (record_status) {
printf("record_status = %d\n", record_status);
// read data from device
if ((ret = av_read_frame(fmt_ctx, pkt)) == AVERROR(EAGAIN)){
// 等待或处理其他任务(如非阻塞模式)
usleep(1000); // 避免忙等待
continue;
}
if (ret < 0 && ret != AVERROR_EOF) {
// 真实错误
fprintf(stderr, "Error reading packet: %s\n", av_err2str(ret));
} else if (ret == AVERROR_EOF) {
// 正常结束
printf("Reached end of file.\n");
} else {
// 成功读取到数据包
// 处理 pkt...
av_log(NULL, AV_LOG_INFO, "count = %d, ret = %d, pkt.size = %d\n",
count, ret, pkt->size);
// 进行内存拷贝 按字节拷贝
memcpy(src_data[0], pkt->data, pkt->size);
int in_samples = pkt->size / av_get_bytes_per_sample(AV_SAMPLE_FMT_FLT);
// 重采样
int out_samples = swr_convert(swr_ctx, // 重采样的上下文
dst_data, // 输出结果缓冲区
in_samples, // 每个通道的采样数
(const uint8_t **)src_data, // 输入缓冲区
in_samples); // 输入单个通道的采样数
if (out_samples < 0) {
fprintf(stderr, "Resampling failed: %s\n", av_err2str(out_samples));
continue;
}
// write file
// fwrite(pkt.data, 1, pkt.size, out_file);
fwrite(dst_data[0], 1, out_samples * 2 * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16), out_file);
fflush(out_file);
// 每次使用完释放包
av_packet_unref(pkt);
}
}
end:
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);
if (pkt) {
av_packet_free(&pkt);
}
if (out_file) {
fclose(out_file);
}
if (fmt_ctx) {
avformat_close_input(&fmt_ctx);
}
}