目录

音频相关概念

PortAudio

核心概念与函数

回调函数

1. 音频缓冲区需要填充 / 读取(实时处理)

2. 流状态变化时的通知

回调函数的核心规范(必须遵守)

返回值规范(决定流的后续状态)

回调函数的调用特性

使用流程

1.编写回调函数

常见使用场景

麦克风录制场景

音频播放场景:

实时音效处理场景(以 “音量调节 + 简单混响” 为例)

全双工(同时录播)场景(麦克风输入→实时变调→播放)

2.初始化 PortAudio 库

3.配置音频流参数(PaStreamParameters)

4.打开音频流(Pa_OpenStream)

5.启动音频流(Pa_StartStream)

6.运行音频逻辑(如等待录制结束、处理数据)

7.释放资源(关闭流 + 终止 PortAudio)

8.完整Demo:


音频相关概念

声音是什么

声音是由物体振动产生的声波。声音作为一种机械波,频率在20 Hz~20 kHz之间的声音是可以被人耳识别的。

音频录制

最简单的音频录制流程为:

设备采集获取模拟信号 --->模数转换 --->存储(播放、传输等).

播放端流程相反:

音频文件 --->数模转换--->播放器播放

模数转换

模拟信号转化为数字信号的流程:

如上图:

  • 模拟数据:最原始的连续信号。
  • 采样:按固定时间间隔 “抓取” 模拟信号的瞬时值,将连续时间的信号转换为离散时间的信号。
  • 量化:把采样得到的连续幅度值,划分到有限个 “量化等级” 中,将连续幅度转换为离散幅度
  • 编码:将量化后的离散整数值,转换为二进制,或其他数字格式,最终生成计算机可存储、处理的数字信号

这些术语是数字信号处理(Digital Signal Processing, DSP) 领域的核心概念。

简单说:模拟信号 → 采样(时间离散)→ 量化(幅度离散)→ 编码(二进制化)→ 数字信号

PortAudio

PortAudio 是一个跨平台的开源音频 I/O 库,旨在为不同操作系统提供统一的音频设备访问接口,方便开发者进行音频采集、播放等操作。

其能够在 Windows、macOS、Linux、iOS、Android 等多种操作系统上使用,支持多种音频设备和多种常见的音频数据格式,并且提供了简洁且一致的 API,使得开发者可以相对轻松地实现音频设备的打开、关闭,音频流的启动、停止,以及音频数据的读写等操作。

核心概念与函数

  • 音频设备(Audio Device):在 PortAudio 中,每个音频输入或输出设备都被抽象为一个音频设备对象。通过Pa_GetDeviceCount()函数可以获取系统中音频设备的数量。使用Pa_GetDeviceInfo()函数可以获取指定设备的详细信息。
  • 音频流(Audio Stream):音频流用于管理音频数据的传输。要使用音频设备进行音频采集或播放,需要先打开一个音频流。通过Pa_OpenStream()函数来打开音频流,该函数需要指定输入设备参数、输出设备参数、采样率、帧大小、音频格式等信息。打开音频流后,使用Pa_StartStream()函数启动音频流,开始进行音频数据的传输;使用Pa_ReadStream()函数可以从输入音频流中读取音频数据(用于音频采集),使用Pa_WriteStream()函数可以向输出音频流中写入音频数据(用于音频播放) ;最后,使用Pa_CloseStream()函数关闭音频流,释放相关资源。
  • 回调函数(Callback Function):在音频流的操作中,回调函数起着重要作用。例如,在进行音频采集时,可以设置一个回调函数,当音频流有新的音频数据可用时,PortAudio 会自动调用该回调函数,开发者可以在回调函数中处理采集到的音频数据。回调函数的使用使得音频数据的处理可以以异步的方式进行,提高了程序的效率和响应性。

回调函数

 PortAudio 的回调函数是其异步音频处理模式的核心,它由 PortAudio 内部的音频驱动线程自动调用,而非用户代码直接触发。其调用时机严格与音频硬件的采样时钟和缓冲区处理逻辑绑定。

1. 音频缓冲区需要填充 / 读取(实时处理)

这是回调函数被调用的最主要、最频繁的原因,是其最核心场景,直接服务于音频的实时输入 / 输出。

PortAudio 采用 “缓冲区” 机制协调软件处理与硬件速度的差异:

  • 对于输出流(播放音频):当音频硬件的输出缓冲区中已有的数据即将播放完毕(或已空)时,PortAudio 会立即调用回调函数,要求用户代码填充新的音频数据到输出缓冲区,避免出现 “断音”(underflow)。
  • 对于输入流(录制音频):当音频硬件的输入缓冲区已收集到足够多的音频数据(达到预设的缓冲区大小)时,PortAudio 会调用回调函数,将缓冲区中录制好的原始数据传递给用户代码,供后续处理,避免数据溢出(overflow)。
  • 对于全双工流(同时输入 + 输出):回调函数会在 “输入缓冲区满” 或 “输出缓冲区空” 的较早触发条件下被调用,此时用户可同时读取输入数据、填充输出数据(例如实时语音处理、音效实时叠加等场景)。
2. 流状态变化时的通知

当音频流的状态发生改变(如启动、停止、出错)时,PortAudio 也会调用回调函数,并通过参数告知用户当前的状态,以便进行对应的处理。常见的状态触发包括:

  • 流启动时:部分系统中,音频流刚通过Pa_StartStream()启动后,会立即调用一次回调函数,用于初始化缓冲区或同步状态。
  • 流停止时:当调用Pa_StopStream()Pa_AbortStream()后,PortAudio 可能会再调用一次回调函数,传递paCompletepaAbort状态,通知用户流已终止,可进行资源清理。
  • 流出错时:若音频硬件发生错误(如设备被占用、采样率不支持、缓冲区溢出 / 下溢),回调函数会被调用,并通过paError状态传递具体错误码,用户可据此进行错误处理。
回调函数的核心规范(必须遵守)

PortAudio 回调函数的函数签名是固定的,用户不能自定义参数或返回值类型,否则会导致程序崩溃。其原型如下(C/C++ 通用):

typedef int PaStreamCallback(
    const void *inputBuffer,   // 输入音频数据缓冲区(录制场景有效)
    void *outputBuffer,        // 输出音频数据缓冲区(播放场景有效)
    unsigned long framesPerBuffer,  // 本次回调需处理的采样点数(帧大小)
    const PaStreamCallbackTimeInfo *timeInfo,  // 时间信息(可选)
    PaStreamCallbackFlags statusFlags,  // 状态标志(如缓冲区溢出/下溢)
    void *userData             // 用户自定义数据(传递上下文)
);

各参数含义详解:

inputBuffer 录制场景:指向麦克风等输入设备采集的原始音频数据(格式与流配置一致);无输入时为 NULL
outputBuffer 播放场景:指向需要填充的输出音频数据缓冲区;无输出时为 NULL
framesPerBuffer 本次回调需处理的采样点数(即 “帧大小”),由 Pa_OpenStream 配置时指定(或系统自动分配)。
timeInfo 包含音频处理的时间戳信息(如当前缓冲区的播放 / 采集时间),一般用于高精度同步场景(可选忽略)。
statusFlags 状态标志位,如 paInputOverflow(输入缓冲区溢出)、paOutputUnderflow(输出缓冲区下溢),需据此处理错误。
userData 用户自定义数据指针(通过 Pa_OpenStream 传入),用于传递上下文(如缓冲区、配置参数等),避免全局变量。
返回值规范(决定流的后续状态)

回调函数的返回值是 PortAudio 控制音频流生命周期的关键,必须返回以下枚举值之一:

  • paContinue:继续运行,PortAudio 会持续调用回调函数;
  • paComplete:正常终止流,PortAudio 后续不再调用回调,需配合 Pa_CloseStream 释放资源;
  • paAbort:立即终止流。
回调函数的调用特性

理解调用时机的同时,需明确其底层特性,调用本质是 “硬件驱动的事件通知”,避免误用:

  1. 线程独立性:回调函数运行在 PortAudio 的内部音频线程中,而非用户的主线程,因此回调函数内需避免阻塞操作(如文件 IO、sleep、锁等待),否则会导致音频卡顿。
  2. 实时性要求:调用时机由硬件时钟决定,具有严格的实时性,用户需确保回调函数内的代码执行速度足够快(耗时 < 缓冲区对应的时间)。
  3. 参数驱动:回调函数的输入参数(如输入数据指针、缓冲区大小)会明确告知当前需要处理的任务,用户无需主动查询流状态。

使用流程

1.编写回调函数

首先引入PA的头文件

#include "portaudio.h"
常见使用场景
场景 回调核心逻辑
麦克风录制 读取 inputBuffer → (可选预处理)→ 写入文件 / 传递给后续模块(如语音识别)
音频播放 从文件 / 内存读取数据 → 写入 outputBuffer → 读完返回 paComplete
实时音效处理 读取 inputBuffer → 音效算法处理(如混响、均衡)→ 写入 outputBuffer 播放
全双工(同时录播) 读取 inputBuffer 处理,同时填充 outputBuffer 播放,需协调两者的同步性
麦克风录制场景
#include <portaudio.h>
#include <iostream>
#include <fstream>
// 自定义上下文结构体,用于传递录制相关信息
struct RecordContext {
    std::ofstream outFile;  // 用于存储录制音频的文件流
    bool isRecording;  // 录制状态标志
};

// 麦克风录制回调函数
int RecordCallback(
    const void *inputBuffer,
    void *outputBuffer,
    unsigned long framesPerBuffer,
    const PaStreamCallbackTimeInfo *timeInfo,
    PaStreamCallbackFlags statusFlags,
    void *userData
) {
    RecordContext* ctx = reinterpret_cast<RecordContext*>(userData);
    // 检查状态标志,处理可能的错误
    if (statusFlags & paInputOverflow) {
        std::cerr << "Input buffer overflow occurred!" << std::endl;
    }
    if (statusFlags & paError) {
        std::cerr << "An error occurred in the audio stream!" << std::endl;
        return paAbort;
    }
    // 如果处于录制状态且有输入音频数据
    if (ctx->isRecording && inputBuffer != nullptr) {
        // 假设音频数据是32位浮点数格式,将其写入文件
        const float* audioData = reinterpret_cast<const float*>(inputBuffer);
        ctx->outFile.write(reinterpret_cast<const char*>(audioData), framesPerBuffer * sizeof(float));
    }
    // 如果停止录制,返回paComplete终止流
    return ctx->isRecording? paContinue : paComplete;
}
音频播放场景:
#include <portaudio.h>
#include <iostream>
#include <fstream>

// 自定义上下文结构体,用于传递播放相关信息
struct PlaybackContext {
    std::ifstream inFile;  // 要播放的音频文件流
    bool isPlaying;  // 播放状态标志
};

// 音频播放回调函数
int PlaybackCallback(
    const void *inputBuffer,
    void *outputBuffer,
    unsigned long framesPerBuffer,
    const PaStreamCallbackTimeInfo *timeInfo,
    PaStreamCallbackFlags statusFlags,
    void *userData
) {
    PlaybackContext* ctx = reinterpret_cast<PlaybackContext*>(userData);
    // 检查状态标志,处理可能的错误
    if (statusFlags & paOutputUnderflow) {
        std::cerr << "Output buffer underflow occurred!" << std::endl;
    }
    if (statusFlags & paError) {
        std::cerr << "An error occurred in the audio stream!" << std::endl;
        return paAbort;
    }
    // 如果处于播放状态且有输出缓冲区
    if (ctx->isPlaying && outputBuffer != nullptr) {
        float* outData = reinterpret_cast<float*>(outputBuffer);
        // 从文件读取音频数据到输出缓冲区
        ctx->inFile.read(reinterpret_cast<char*>(outData), framesPerBuffer * sizeof(float));
        // 如果读取的帧数少于预期,说明文件已读完,填充剩余部分为0(静音)
        if (ctx->inFile.gcount() < framesPerBuffer) {
            std::fill(outData + ctx->inFile.gcount(), outData + framesPerBuffer, 0.0f);
            ctx->isPlaying = false;
            return paComplete;
        }
    }
    return paContinue;
}
实时音效处理场景(以 “音量调节 + 简单混响” 为例)
#include <portaudio.h>
#include <iostream>
#include <vector>

// 简单混响效果器:使用延迟线实现回声
class ReverbEffect {
private:
    std::vector<float> delayLine;  // 延迟线缓冲区
    size_t delayIndex = 0;         // 延迟线当前索引
    float decayFactor = 0.5f;      // 回声衰减因子

public:
    ReverbEffect(size_t maxDelaySamples) : delayLine(maxDelaySamples, 0.0f) {}

    // 处理单帧音频,添加混响效果
    void process(float* audioData, unsigned long framesPerBuffer) {
        for (unsigned long i = 0; i < framesPerBuffer; ++i) {
            // 当前输入样本
            float inputSample = audioData[i];
            // 延迟线中取出历史样本(回声)
            float delayedSample = delayLine[delayIndex];
            // 新样本 = 原始样本 + 衰减后的回声
            audioData[i] = inputSample + (delayedSample * decayFactor);
            // 将新样本存入延迟线(覆盖旧样本,实现循环)
            delayLine[delayIndex] = inputSample;
            // 更新延迟线索引(循环)
            delayIndex = (delayIndex + 1) % delayLine.size();
        }
    }
};

// 自定义上下文结构体:存储音效参数和状态
struct EffectContext {
    ReverbEffect reverb;  // 混响效果器
    float volumeGain = 1.2f;  // 音量增益(1.2倍)
    bool isProcessing = true;  // 处理状态标志
};

// 实时音效处理回调函数
int EffectCallback(
    const void *inputBuffer,
    void *outputBuffer,
    unsigned long framesPerBuffer,
    const PaStreamCallbackTimeInfo *timeInfo,
    PaStreamCallbackFlags statusFlags,
    void *userData
) {
    EffectContext* ctx = reinterpret_cast<EffectContext*>(userData);

    // 1. 处理状态标志(输入/输出错误)
    if (statusFlags & (paInputOverflow | paOutputUnderflow)) {
        std::cerr << "警告:缓冲区溢出/下溢,可能导致音效失真!\n";
    }
    if (statusFlags & paError) {
        std::cerr << "错误:音频流异常,终止处理!\n";
        return paAbort;
    }

    // 2. 核心逻辑:仅在处理状态下执行
    if (ctx->isProcessing && inputBuffer != nullptr && outputBuffer != nullptr) {
        // 强转输入/输出数据为 float*(与流配置的 paFloat32 匹配)
        const float* inData = reinterpret_cast<const float*>(inputBuffer);
        float* outData = reinterpret_cast<float*>(outputBuffer);

        // 3. 逐帧处理:音量调节 → 混响效果
        for (unsigned long i = 0; i < framesPerBuffer; ++i) {
            // 步骤1:音量调节(原始数据 × 增益)
            float processedSample = inData[i] * ctx->volumeGain;
            // 步骤2:限制音量范围(防止溢出)
            if (processedSample > 1.0f) processedSample = 1.0f;
            if (processedSample < -1.0f) processedSample = -1.0f;
            // 步骤3:混响处理(延迟线回声)
            outData[i] = processedSample;  // 先暂存,混响会直接修改 outData
        }
        ctx->reverb.process(outData, framesPerBuffer);
    } else {
        // 若未处理,填充静音
        memset(outputBuffer, 0, framesPerBuffer * sizeof(float));
    }

    return ctx->isProcessing ? paContinue : paComplete;
}
全双工(同时录播)场景(麦克风输入→实时变调→播放)
#include <portaudio.h>
#include <iostream>
#include <cmath>

// 简单变调效果器:通过插值实现音高调整
class PitchShifter {
private:
    float pitchRatio = 1.5f;  // 变调比例(1.5倍即升高半音)
    float readIndex = 0.0f;   // 读取索引(模拟非整数采样)

public:
    void setPitchRatio(float ratio) { pitchRatio = ratio; }

    // 处理单帧音频,调整音高
    void process(float* audioData, unsigned long framesPerBuffer) {
        for (unsigned long i = 0; i < framesPerBuffer; ++i) {
            // 线性插值:模拟非整数采样点的取值
            size_t prevIndex = static_cast<size_t>(readIndex);
            size_t nextIndex = (prevIndex + 1) % framesPerBuffer;
            float fraction = readIndex - prevIndex;
            // 插值计算新样本
            audioData[i] = audioData[prevIndex] * (1.0f - fraction) + 
                           audioData[nextIndex] * fraction;
            // 更新读取索引(按变调比例步进)
            readIndex = fmod(readIndex + pitchRatio, framesPerBuffer);
        }
    }
};

// 自定义上下文结构体:存储录制和播放的状态
struct DuplexContext {
    PitchShifter shifter;  // 变调效果器
    float* tempBuffer;     // 临时缓冲区(存储原始录制数据)
    unsigned long bufferSize;  // 缓冲区大小
    bool isDuplexRunning = true;  // 全双工运行状态
};

// 全双工回调函数:同时录制和播放,实时变调
int DuplexCallback(
    const void *inputBuffer,
    void *outputBuffer,
    unsigned long framesPerBuffer,
    const PaStreamCallbackTimeInfo *timeInfo,
    PaStreamCallbackFlags statusFlags,
    void *userData
) {
    DuplexContext* ctx = reinterpret_cast<DuplexContext*>(userData);

    // 1. 处理状态标志(输入/输出错误)
    if (statusFlags & (paInputOverflow | paOutputUnderflow)) {
        std::cerr << "警告:全双工模式下缓冲区溢出/下溢!\n";
    }
    if (statusFlags & paError) {
        std::cerr << "错误:音频流异常,终止全双工!\n";
        return paAbort;
    }

    // 2. 核心逻辑:仅在运行状态下执行
    if (ctx->isDuplexRunning && inputBuffer != nullptr && outputBuffer != nullptr) {
        // 强转输入/输出数据为 float*
        const float* inData = reinterpret_cast<const float*>(inputBuffer);
        float* outData = reinterpret_cast<float*>(outputBuffer);

        // 3. 步骤1:录制原始数据到临时缓冲区
        memcpy(ctx->tempBuffer, inData, framesPerBuffer * sizeof(float));

        // 4. 步骤2:对临时缓冲区数据变调处理
        ctx->shifter.process(ctx->tempBuffer, framesPerBuffer);

        // 5. 步骤3:将处理后的数据写入输出缓冲区(播放)
        memcpy(outData, ctx->tempBuffer, framesPerBuffer * sizeof(float));
    } else {
        // 若停止,填充静音
        memset(outputBuffer, 0, framesPerBuffer * sizeof(float));
    }

    return ctx->isDuplexRunning ? paContinue : paComplete;
}
2.初始化 PortAudio 库

在使用任何 PortAudio 功能前,必须先初始化库,为后续的音频设备交互和流操作准备环境。

PaError err = Pa_Initialize();
if (err != paNoError) {
    std::cerr << "PortAudio 初始化失败: " << Pa_GetErrorText(err) << std::endl;
    return -1;  // 初始化失败,直接退出
}
3.配置音频流参数(PaStreamParameters

根据需求(录制 / 播放 / 全双工),配置输入 / 输出流的参数,包括:

  • 音频设备
  • 通道数
  • 采样格式
  • 延迟

示例:麦克风录制的输入参数配置

PaStreamParameters inputParams;
inputParams.device = Pa_GetDefaultInputDevice();  // 使用默认麦克风
inputParams.channelCount = 1;                    // 单声道
inputParams.sampleFormat = paFloat32;            // 32位浮点数格式
inputParams.suggestedLatency = Pa_GetDeviceInfo(inputParams.device)->defaultLowInputLatency;
inputParams.hostApiSpecificStreamInfo = NULL;    // 无特定宿主API信息
4.打开音频流(Pa_OpenStream

回调函数上下文结构体注册到音频流中,完成 “流 - 回调 - 数据” 的绑定。

PaStream* stream;
err = Pa_OpenStream(
    &stream,                // 输出:创建的音频流指针
    &inputParams,           // 输入参数(录制场景,如麦克风)
    NULL,                   // 输出参数(录制场景为 NULL)
    SAMPLE_RATE,            // 采样率(如 16000 Hz)
    FRAMES_PER_BUFFER,      // 每帧采样数(如 512)
    paClipOff,              // 禁用自动剪辑(避免数据失真)
    RecordCallback,         // 注册回调函数
    &recordCtx              // 传递上下文结构体(userData 参数)
);
if (err != paNoError) {
    std::cerr << "打开音频流失败: " << Pa_GetErrorText(err) << std::endl;
    Pa_Terminate();         // 初始化失败,终止 PortAudio
    return -1;
}
5.启动音频流(Pa_StartStream

启动后,PortAudio 会自动调用回调函数,开始音频的采集或播放。

err = Pa_StartStream(stream);
if (err != paNoError) {
    std::cerr << "启动音频流失败: " << Pa_GetErrorText(err) << std::endl;
    Pa_CloseStream(stream); // 关闭流
    Pa_Terminate();
    return -1;
}
6.运行音频逻辑(如等待录制结束、处理数据)

根据业务逻辑,让程序保持运行,直到音频任务完成。

示例:等待麦克风录制 5 秒后停止

std::this_thread::sleep_for(std::chrono::seconds(5));  // 等待 5 秒
recordCtx.isRecording = false;  // 修改上下文状态,让回调返回 paComplete
Pa_StopStream(stream);          // 停止流(等待回调结束)
7.释放资源(关闭流 + 终止 PortAudio)

音频任务结束后,必须释放资源,避免内存泄漏或设备占用。

Pa_CloseStream(stream);  // 关闭音频流
Pa_Terminate();          // 终止 PortAudio 库
8.完整Demo:
#include <portaudio.h>
#include <iostream>
#include <fstream>
#include <thread>

// 录制上下文结构体
struct RecordContext {
    std::ofstream outFile;
    bool isRecording = true;
};

// 回调函数(前文定义的 RecordCallback)
int RecordCallback(
    const void *inputBuffer,
    void *outputBuffer,
    unsigned long framesPerBuffer,
    const PaStreamCallbackTimeInfo *timeInfo,
    PaStreamCallbackFlags statusFlags,
    void *userData
);

int main() {
    // 1. 初始化 PortAudio
    PaError err = Pa_Initialize();
    if (err != paNoError) {
        std::cerr << "Pa_Initialize 失败: " << Pa_GetErrorText(err) << std::endl;
        return -1;
    }

    // 2. 初始化上下文
    RecordContext recordCtx;
    recordCtx.outFile.open("output.pcm", std::ios::binary);
    if (!recordCtx.outFile.is_open()) {
        std::cerr << "打开输出文件失败" << std::endl;
        Pa_Terminate();
        return -1;
    }

    // 3. 配置输入参数
    PaStreamParameters inputParams;
    inputParams.device = Pa_GetDefaultInputDevice();
    inputParams.channelCount = 1;
    inputParams.sampleFormat = paFloat32;
    inputParams.suggestedLatency = Pa_GetDeviceInfo(inputParams.device)->defaultLowInputLatency;
    inputParams.hostApiSpecificStreamInfo = NULL;

    // 4. 打开音频流
    PaStream* stream;
    err = Pa_OpenStream(
        &stream,
        &inputParams,
        NULL,
        16000,         // 采样率 16kHz
        512,           // 每帧 512 采样
        paClipOff,
        RecordCallback,
        &recordCtx
    );
    if (err != paNoError) {
        std::cerr << "Pa_OpenStream 失败: " << Pa_GetErrorText(err) << std::endl;
        recordCtx.outFile.close();
        Pa_Terminate();
        return -1;
    }

    // 5. 启动流
    err = Pa_StartStream(stream);
    if (err != paNoError) {
        std::cerr << "Pa_StartStream 失败: " << Pa_GetErrorText(err) << std::endl;
        Pa_CloseStream(stream);
        recordCtx.outFile.close();
        Pa_Terminate();
        return -1;
    }

    // 6. 等待 5 秒后停止录制
    std::cout << "录制中... 5 秒后停止" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(5));
    recordCtx.isRecording = false;
    Pa_StopStream(stream);

    // 7. 释放资源
    Pa_CloseStream(stream);
    recordCtx.outFile.close();
    Pa_Terminate();
    std::cout << "录制完成,文件已保存为 output.pcm" << std::endl;

    return 0;
}

// 回调函数实现(前文定义的 RecordCallback)
int RecordCallback(
    const void *inputBuffer,
    void *outputBuffer,
    unsigned long framesPerBuffer,
    const PaStreamCallbackTimeInfo *timeInfo,
    PaStreamCallbackFlags statusFlags,
    void *userData
) {
    RecordContext* ctx = reinterpret_cast<RecordContext*>(userData);
    if (statusFlags & paInputOverflow) {
        std::cerr << "输入缓冲区溢出!" << std::endl;
    }
    if (ctx->isRecording && inputBuffer != nullptr) {
        const float* audioData = reinterpret_cast<const float*>(inputBuffer);
        ctx->outFile.write(reinterpret_cast<const char*>(audioData), framesPerBuffer * sizeof(float));
    }
    return ctx->isRecording ? paContinue : paComplete;
}

总结:核心流程是 “编写回调函数→ 初始化 → 配置参数 → 打开流 → 启动流 → 运行逻辑 → 释放资源”

👾👾👾...

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐