【PortAudio】PortAudio 音频处理库Demo

news2024/11/13 15:35:21

1. 介绍

PortAudio是一个免费、跨平台、开源的音频I/O库。看到I/O可能就想到了文件,但是PortAudio操作的I/O不是文件,而是音频设备。它能够简化C/C++的音频程序的设计实现,能够运行在Windows、Macintosh OS X和UNIX之上(Linux的各种版本也不在话下)。使用PortAudio可以在不同的平台上迁移应用程序,比如你可以把你基于PortAudio的应用程序发展一个Android版本啊。

PortAudio的API非常简单,通过一个一个简单的回调函数或者阻塞的读/写接口来录制或者播放声音。PortAudio自带了很多示例程序,比如播放正弦波形的音频信号,处理音频输入,录制回放音频,列举音频设备。

本文以一个录音,播放的例子展示,PortAudio的使用流程。完整代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <memory>
#include "portaudio.h"
using namespace std;
// 回调函数定义
static int audioCallback(const void* inputBuffer, void* outputBuffer,
    unsigned long framesPerBuffer,
    const PaStreamCallbackTimeInfo* timeInfo,
    PaStreamCallbackFlags statusFlags,
    void* userData)
{
    // 在此处处理音频数据
    // 将输入缓冲区中的数据复制到输出缓冲区以回放声音
    if (inputBuffer != NULL)
        memcpy(outputBuffer, inputBuffer, framesPerBuffer * sizeof(float));

    return paContinue;
}

int main()
{
    PaStream* stream;
    PaError err;

    // 初始化PortAudio库
    err = Pa_Initialize();
    if (err != paNoError) {
        printf("初始化PortAudio失败: %s\n", Pa_GetErrorText(err));
        return 1;
    }

    // 打开默认的音频输入和输出设备
    err = Pa_OpenDefaultStream(&stream,
        1,      // 输入通道数
        1,      // 输出通道数
        paFloat32,  // 采样格式
        44100,  // 采样率
        256,    // 缓冲区大小(每个缓冲区的帧数)
        audioCallback,  // 回调函数
        NULL);  // 用户数据

    if (err != paNoError) {
        printf("打开音频流失败: %s\n", Pa_GetErrorText(err));
        return 1;
    }

    // 启动音频流
    err = Pa_StartStream(stream);
    if (err != paNoError) {
        printf("启动音频流失败: %s\n", Pa_GetErrorText(err));
        return 1;
    }

    printf("录音已开始,请按 Enter 键停止...\n");
    getchar();

    // 停止音频流
    err = Pa_StopStream(stream);
    if (err != paNoError) {
        printf("停止音频流失败: %s\n", Pa_GetErrorText(err));
        return 1;
    }

    // 关闭音频流和PortAudio库
    err = Pa_CloseStream(stream);
    if (err != paNoError) {
        printf("关闭音频流失败: %s\n", Pa_GetErrorText(err));
        return 1;
    }

    Pa_Terminate();
    printf("录音已停止。\n");

    return 0;
}

2. 下载安装

2.1 源码编译

源码下载路径为http://www.portaudio.com/download.html
VS上编译的步骤可以参考http://portaudio.com/docs/v19-doxydocs/compile_windows.html
ubuntu下直接使用apt-get install portaudio19-dev即可

2.2 Vcpkg 安装

使用 vcpkg c++ 包管理器安装

 vcpkg install portaudio:x64-windows 

3. 使用流程

编写一个PortAudio应用,只需要掌握回调函数即可:

  1. 编写一个回调函数,PortAudio在进行音频处理的时候自动调用
  2. 初始化PA库,并为I/O打开一个流
  3. 启动流,PA会在幕后调用回调函数
  4. 在回调函数中可以从inputBuffer读取音频数据,或者将音频数据写入到outputBuffer
  5. 回调函数返回1, 或者调用相应函数来停止流
  6. 关闭流,然后终止PA

除了回调函数,PA还支持阻塞I/O模型,但并不是所有的功能都得到支持。所以推荐使用回调函数。

流程图
在这里插入图片描述

3.1 编写回调函数

首先引入PA的头文件

#include "portaudio.h"

回调函数会在两种情况下被调用:PA获取音频数据时和PA需要音频数据作为输出时。

回调函数是一个神奇的地方,因为一些系统在一个特殊的线程中处理回调函数,甚至是通过中断来处理,这不同于程序中的其他代码。如果你想音频能够按时到达Speaker,就得保证回调函数能够快速地执行。不同的平台上,什么 样的操作是安全的,什么样的操作是不安全的,是不一样的。一个通用准则就是,不要做内存的分配释放操作、读写文件、printf,或者其他依赖于OS的不能在一定时间内返回的操作,也包括可能导致上下文切换的操作。

回调函数原型:

typedef int PaStreamCallback( 
	const void *input,
	void *output,
	unsigned long frameCount, 
	const PaStreamCallbackTimeInfo* timeInfo, 
	PaStreamCallbackFlags statusFlags, 
	void *userData );

比如我们想把录音的数据,再播放回来就可以这样写这个回调

// 回调函数定义
static int audioCallback(
	const void* inputBuffer, 
	void* outputBuffer,
    unsigned long framesPerBuffer,
    const PaStreamCallbackTimeInfo* timeInfo,
    PaStreamCallbackFlags statusFlags,
    void* userData)
{
    // 在此处处理音频数据
    // 将输入缓冲区中的数据复制到输出缓冲区以回放声音
    if (inputBuffer != NULL)
        memcpy(outputBuffer, inputBuffer, framesPerBuffer * sizeof(float));

    return paContinue;
}

如果我们想对声音数据做处理,可以这样写

typedef struct
{
    float left_phase;
    float right_phase;
}   
paTestData;
/* This routine will be called by the PortAudio engine when audio is needed.
 * It may called at interrupt level on some machines so don't do anything
 * that could mess up the system like calling malloc() or free().
*/ 
static int patestCallback( 
	const void  			*inputBuffer, 
	void 					*outputBuffer,
    unsigned long 			framesPerBuffer,
    const 					PaStreamCallbackTimeInfo* timeInfo,
    PaStreamCallbackFlags 	statusFlags,
    void 					*userData )
{
    /* Cast data passed through stream to our structure. */
    paTestData *data = (paTestData*)userData; 
    float *out = (float*)outputBuffer;
    unsigned int i;
    (void) inputBuffer; /* Prevent unused variable warning. */
    
    for( i=0; i<framesPerBuffer; i++ )
    {
         out++ = data->left_phase;  /* left */
         out++ = data->right_phase;  /* right */
        /* Generate simple sawtooth phaser that ranges between -1.0 and 1.0. */
        data->left_phase += 0.01f;
        /* When signal reaches top, drop back down. */
        if( data->left_phase >= 1.0f ) data->left_phase -= 2.0f;
        /* higher pitch so we can distinguish left and right. */
        data->right_phase += 0.03f;
        if( data->right_phase >= 1.0f ) data->right_phase -= 2.0f;
    }
    return 0;
}

3.2 Initializing PortAudio

调用 Pa_Initialize() 这将触发对可用设备的扫描,稍后可以查询这些设备。像大部分PA 函数,都将返回paError信息, 如果不是 paNoError 这个表示有错误产生。

auto err = Pa_Terminate();
if( err != paNoError )
   printf(  "PortAudio error: %s\n", Pa_GetErrorText( err ) );

3.3 Opeing Stream Using Defaults

在这一步将打开一个流,就和打开一个文件一样。你可以指定你想输入/输出音频,多少通道,数据格式,采样率等。打开一个“默认”流意味着打开默认的输入和输出设备,这样可以省去获取设备列表并从列表中选择一个的麻烦。(稍后我们将介绍如何做到这一点。)

#define SAMPLE_RATE (44100)
static paTestData data;
.....
    PaStream *stream;
    PaError err;
    /* Open an audio I/O stream. */
    err = Pa_OpenDefaultStream( &stream,
                                0,          /* no input channels */
                                2,          /* stereo output */
                                paFloat32,  /* 32 bit floating point output */
                                SAMPLE_RATE,
                                256,        /* frames per buffer, i.e. the number
                                                   of sample frames that PortAudio will
                                                   request from the callback. Many apps
                                                   may want to use
                                                   paFramesPerBufferUnspecified, which
                                                   tells PortAudio to pick the best,
                                                   possibly changing, buffer size.*/
                                patestCallback, /* this is your callback function */
                                &data ); /*This is a pointer that will be passed to
                                                   your callback*/
    if( err != paNoError ) goto error;

这里的的 data 对应了 Call back 里面的 userData 参数。

上面的这个示例展示了写的stream, 以满足播放的要求。也可以打开一个用于读取的流,进行录音,或者同时进行读取和写入,以实现同时录制和播放甚至实时音频处理。如果您计划同时进行播放和录制,请只打开一个具有有效输入和输出参数的流。
比如,在文章开头提到得录音,播放得初始化

err = Pa_OpenDefaultStream(&stream,
        1,      // 输入通道数
        1,      // 输出通道数
        paFloat32,  // 采样格式
        44100,  // 采样率
        256,    // 缓冲区大小(每个缓冲区的帧数)
        audioCallback,  // 回调函数
        NULL);  // 用户数据

    if (err != paNoError) {
        printf("打开音频流失败: %s\n", Pa_GetErrorText(err));
        return 1;
    }

Note:

  • 一些平台得设备可能只读或者只写
  • 尽管多流可以被打开,但是他们很难同步
  • 一些平台的设备不支持打开多流
  • 使用多个流可能没有经过与其他功能一样全面的测试。
  • PortAudio库的调用必须来自同一线程或由用户进行同步。

3.4 Starting, Stoping and Aborting a Stream

PortAudio 当你启动流的将开始播放音频, 当调用 Pa_StartStream() 时, PortAudio 将开始调用你的Callback 函数来执行音频处理。

err = Pa_StartStream( stream );
if( err != paNoError ) goto error;

你可以通过在打开调用时传递的数据结构、全局变量或使用其他进程间通信技术与回调函数进行通信,但请注意,当前台进程最不希望发生中断时可能会调用你的回调函数。因此,避免共享像双向链表这样容易损坏的复杂数据结构,并避免使用诸如互斥锁之类的锁,因为这可能导致你的回调函数阻塞并且丢失音频。这些技术甚至可能在某些平台上导致死锁。

PortAudio将继续调用您的回调函数并处理音频,直到您停止流。这可以通过多种方式完成,但在执行此操作之前,我们希望能看到一些我们的音频经过几秒钟的睡眠进行处理。使用Pa_Sleep()可以轻松实现这一点,许多patests/目录中的示例都是为了这个目的而使用它。请注意,出于各种原因,您不能依赖此函数进行准确的调度,因此您的流可能不会像您期望的那样运行相同的时间,但对于我们的示例来说,这已经足够了。

/* Sleep for several seconds. */
Pa_Sleep(NUM_SECONDS*1000);

现在我们需要停止播放。有几种方法可以做到这一点,其中最简单的方法是调用Pa_StopStream()函数:

err = Pa_StopStream( stream );
if( err != paNoError ) goto error;

Pa_StopStream()函数的设计目的是确保您在回调函数中处理的缓冲区都被播放,这可能会导致一些延迟。或者,您可以调用Pa_AbortStream()函数。在某些平台上,中止流程速度更快,可能会导致部分由回调函数处理的数据不被播放。

停止流的另一种方法是从回调函数返回paComplete或paAbort。paComplete确保最后一个缓冲区被播放,而paAbort尽快停止流。如果您使用此技术停止流程,则需要在再次启动流程之前调用Pa_StopStream()函数。

3.5 Closing a Stream and Terminating PortAudio

当您完成一个流程时,应该关闭它以释放资源:

err = Pa_CloseStream( stream );
if( err != paNoError ) goto error;

在初始化PortAudio时我们已经提到过这一点,但是以防您忘记了,在完成时请确保终止PortAudio:

err = Pa_Terminate( );
if( err != paNoError ) goto error;

4. 参考资料

https://blog.csdn.net/GG_SiMiDa/article/details/77185755
http://files.portaudio.com/docs/v19-doxydocs/terminating_portaudio.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/714336.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SAP从入门到放弃系列之生产车间相关单据打印

文章目录概览 一、前言二、系统相关设置2.1、配置:1&#xff1a;2.2、配置点2&#xff1a;2.3、配置点3 三、主数据准备四、测试场景准备五、小结 一、前言 通常在项目实施的时候&#xff0c;如果没有MES&#xff0c;那么生产调度相关岗位下达订单后&#xff08;订单下达感觉没…

K8s部署微服务(springboot+vue)

文章目录 前言一、使用到的K8s资源1.1 Deployment1.2 Service 二、Springboot基础服务部署2.1 网关gateway2.2 鉴权auth2.3 文件file2.4 流程flow2.5 消息message2.6 组织org2.7 系统通用system2.8 用户user2.9 Node 三、Vue前端部署3.1 项目前端nginx3.2 静态资源服务nginx 四…

迪杰斯特拉算法(求最短路径)

迪杰斯特拉算法&#xff08;求最短路径&#xff09; 迪杰斯特拉算法用于查找图中某个顶点到其它所有顶点的最短路径&#xff0c;该算法既适用于无向加权图&#xff0c;也适用于有向加权图。 注意&#xff0c;使用迪杰斯特拉算法查找最短路径时&#xff0c;必须保证图中所有边…

相对位置编码(二) Relative Positional Encodings - Transformer-XL

1. Motivation 在Transformer-XL中&#xff0c;由于设计了segments&#xff0c;如果仍采用transformer模型中的绝对位置编码的话&#xff0c;将不能区分处不同segments内同样相对位置的词的先后顺序。 比如对于segmenti&#xfffd;&#xfffd;&#xfffd;&#xfffd;&…

pycharm安装opencv-python报错

问题一 通过pycharm中的Terminal窗口安装opencv-python错误如下&#xff1a; 上图所示为部分错误&#xff0c;全部错误如下&#xff1a; Building wheel for opencv-contrib-python (PEP 517) ... errorERROR: Complete output from command D:\anzhuanglujing\Anaconda\python…

从零开始之PID控制

从零开始系列之PID控制&#xff0c;宗旨就是以说人话的方式讲述它&#xff0c;真正的做到从零开始&#xff0c;小白一看就会&#xff0c;一学就废。 一、什么是PID控制&#xff1f; PID控制&#xff08;比例-积分-微分控制&#xff09;由比例单元&#xff08;Proportional&…

玩耍的猫咪【 InsCode Stable Diffusion 美图活动一期】

1️⃣ 工具介绍 InsCode是一个集成了在线IDE、在线AI编程、在线算力租赁、在线项目部署以及在线SD 模型使用的综合代码开发平台。 Stable Diffusion是目前最火的AI绘画工具之一&#xff0c;它是一个免费开源的项目。通过Stable Diffusion&#xff0c;可以很轻松的通过文字描述…

上半年结束,下半年继续冲!

前言: 这周直播也把雷神写的Ffmpeg推流器讲解完了&#xff0c;而一同时&#xff0c;一转眼间&#xff0c;2023年已经过半&#xff0c;正式进入了下半年&#xff1a; 因为上半年已经开始在做解析Ffmpeg 最新版本的源码&#xff0c;所以下半年&#xff0c;我会继续坚持讲解Ffmpeg…

“GPT+健康医疗”赋能医疗行业“数智化”发展,景联文科技提供高质量医疗数据库

近日&#xff0c;ChatGPT这个代表着通用版的大型语言模型以其出色的表现在全球互联网上引人注目。它所使用的GPT技术基础为人工智能应用开启了全新的世界。 “大模型时代已经到来。它已变成基础设施&#xff0c;变成算力&#xff0c;变成生产力。大模型可能有通用技术&#xf…

C++杂谈-友元和操作符重载

1、友元- friend 我的理解&#xff1a;通过设置友元函数和友元类来让外部函数来访问私有成员&#xff0c;这样虽然破坏了类的封装型和隐藏性&#xff0c;但是提高了程序的运行效率&#xff08;减少了某些安全性检查的过程&#xff09;。 友元函数和友元类统称友元&#xff0c;…

Nginx+Tomcat(多实例)实现动静分离和负载均衡(四层、七层)

目录 一、Tomcat 多实例部署 二、反向代理的两种类型 三、NginxTomcat实现负载均衡和动静分离&#xff08;七层代理&#xff09; 1.动静分离和负载均衡原理 2.实现方法 3.部署实例 &#xff08;1&#xff09;部署Nginx负载均衡服务器 &#xff08;2&#xff09;配置Tom…

C++之GNU C的__attribute__常用属性(一百五十)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

使用 ZBrush、Ornatrix 和 Substance 3D Painter 重现哈利波特中的凤凰

今天瑞云渲染小编给大家带来了Ramn Tapia 分享 Phoenix 项目背后的工作流程&#xff0c;解释了如何在 Ornatrix 中完成修饰&#xff0c;并展示了纹理化过程。 介绍 你好&#xff0c;有创造力的读者朋友们 我的名字是Ramn&#xff0c;但在数字艺术领域&#xff0c;我的名字是ra…

【 Android11 无线热点开发 】无线AP开与关、无线AP信息获取

前言 前面四篇文章介绍完了有线网络、无线网络的开发过程&#xff0c;下面介绍下Android 11上网络的终结篇&#xff0c;无线热点的开发流程。 相关文章 1、【 Android11 WiFi开发 一 】WiFi列表获取与展示 2、【 Android11 WiFi开发 二 】WiFi连接、断开 3、【 Android11 Wi…

软件为什么要进行故障演练?主要为了什么?

随着现代社会的高度信息化和软件的广泛应用&#xff0c;软件的质量和可靠性对于保障用户体验和信息安全显得尤为重要。为了保证软件的稳定运行和即时响应&#xff0c;软件故障演练成为软件开发和运维过程中的重要环节&#xff0c;那软件为什么要进行故障演练&#xff1f;主要为…

IIC(I2C)协议

I2C&#xff08;Inter-Integrated Circuit&#xff09;:是一种串行通信协议&#xff0c;用于在集成电路之间进行数据传输。它由飞利浦公司开发&#xff0c;并广泛应用在各种电子设备和传感器之间进行通信。 I2C通信协议由两根线组成&#xff1a; 一个是用于数据传输的串行数据线…

JVM关键知识点整理,从入门到提高到实践

文章目录 基础篇一、了解JVM内存结构程序计数器&#xff08;线程私有&#xff09;Java虚拟机栈&#xff08;线程私有&#xff09;本地方法栈&#xff08;线程私有&#xff09;方法区&#xff08;线程共享&#xff09;堆&#xff08;线程共享&#xff09;运行时常量池直接内存 二…

Android12之解决-Werror,-Wunused类似问题万能公式(一百五十九)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

Unity3d跨平台原理是什么?

Unity3D的跨平台原理是通过提供统一的开发环境和工具、抽象化的底层接口、平台适配和优化&#xff0c;以及导出和发布功能来实现的。这让开发者们能够在一个平台上创建游戏&#xff0c;并轻松地将其移植和发布到其他支持的平台上&#xff0c;实现游戏的跨平台兼容性。 基于以下…

【大数据之Hadoop】三十七、Hadoop HA高可用

1、HA概述 实现高可用最关键的策略是消除单点故障。HA分成各个组件的HA机制&#xff1a;HDFS的HA和YARN的HA。   Hadoop2.0之前&#xff0c;在HDFS集群中NameNode存在单点故障&#xff08;SPOF&#xff09;。 NameNode主要在以下两个方面影响HDFS集群&#xff1a; &#xff…