音视频学习笔记 05 重采样

94 阅读4分钟

音频重采样,主要遇到了两个问题,

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);
    }
}