【PortAudio】PortAudio 音频处理库Demo

news/2024/4/26 12:12:28/文章来源:https://blog.csdn.net/qq_30340349/article/details/131509624

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 numberof sample frames that PortAudio willrequest from the callback. Many appsmay want to usepaFramesPerBufferUnspecified, whichtells PortAudio to pick the best,possibly changing, buffer size.*/patestCallback, /* this is your callback function */&data ); /*This is a pointer that will be passed toyour 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.luyixian.cn/news_show_325559.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从零开始 Spring Boot 57:JPA中的一对多关系

从零开始 Spring Boot 57&#xff1a;JPA中的一对多关系 图源&#xff1a;简书 (jianshu.com) 在上篇文章中我们介绍了如何在 JPA 中实现实体的一对一关系&#xff0c;在关系型数据库设计中&#xff0c;除了一对一关系&#xff0c;还存在一对多关系。本篇文章介绍如何在 JPA 中…

【Python】NLP参数控制模板

前言 学过AI的都知道训练一个模型需要调整很多参数&#xff0c;为了有效的管理这些参数、不至于让代码的参数写的乱七八糟&#xff0c;有必要写一套控制参数的模板。 argparser argparser是python当中的参数解析器&#xff0c;在NLP当中主要是用来接受和使用参数的。一个使用它…

QT学习笔记:TCP客户端的实现

QT一般用来做客户端&#xff0c;我这里就简单讲一下怎么开发基于QT的TCP客户端。 1、用QtCreator创建项目 2、界面 3、.pro文件添加network QT core gui network 4、mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include &l…

SpringBoot的缓存管理

缓存是分布式系统中的重要组件&#xff0c;主要解决数据库数据的高并发访问问题。在实际开发中&#xff0c;尤其是用户 访问量较大的网站&#xff0c;为了提高服务器访问性能、减少数据库的访问压力、提高用户体验&#xff0c;使用缓存显得 尤为重要。Spring Boot对缓存提供了良…

基于单片机的盲人导航智能拐杖老人防丢防摔倒发短息定位

功能介绍 以STM32单片机作为主控系统&#xff1b; OLED液晶当前实时距离&#xff0c;安全距离&#xff0c;当前经纬度信息&#xff1b;超声波检测小于设置的安全距离&#xff0c;蜂鸣器报警提示&#xff1a;低于安全距离&#xff01;超声波检测当前障碍物距离&#xff0c;GPS进…

从零开始备战数学建模国赛之线性规划1.1

从零开始备战数学建模国赛之线性规划1.1 现在距离2023年的数学建模国赛还有不足三个月的时间&#xff0c;想与大家共同备战国赛。 这是我自己总结的一些代码和资料&#xff08;本文中的代码以及参考书籍等&#xff09;&#xff0c;放在github上供大家参考&#xff1a;https://…

Redis学习(一)数据类型、Java中使用redis、缓存概念

文章目录 常用数据结构String类型Hash类型List类型Set类型SortedSet 类型 通用命令key的层级结构 Spring Data Redis快速入门RedisTemplate的序列化方式StringRedisTemplateRedisTemplate的Hash类型操作 实战操作短信登录发送验证码校验登录信息校验登录状态 商家查询缓存缓存更…

怎么学习PHP表单处理与验证? - 易智编译EaseEditing

要学习PHP表单处理与验证&#xff0c;可以按照以下步骤进行&#xff1a; 掌握PHP基础知识&#xff1a; 在学习PHP表单处理与验证之前&#xff0c;首先需要对PHP编程语言有基本的了解。学习PHP的语法、变量、数据类型、数组、函数等基础知识是必要的。 学习HTML表单&#xff1…

MySQL - 第13节 - MySQL用户管理

1.MySQL用户管理概念 MySQL用户管理概念&#xff1a; • 与Linux操作系统类似&#xff0c;MySQL中也有超级用户和普通用户之分。 • 如果一个用户只需要访问MySQL中的某一个数据库&#xff0c;甚至数据库中的某一个表&#xff0c;那么可以为其创建一个普通用户&#xff0c;并为…

交流220v转12v给单片机供电芯片

客户的应用需求&#xff1a;AD220V转DC12V 体积要非常小&#xff0c;单片机使用&#xff0c;单片机设备12V 电流很小不会超过100mA&#xff1f; 【AD220V转DC12V体积小的问题】 问题&#xff1a;我需要将交流电&#xff08;220V&#xff09;转换为直流电&#xff08;12V&…

【CSS】CSS使用变量与变量定义

如何定义可以在CSS中使用的变量 CSS变量&#xff08;也称为自定义属性&#xff09;的定义规则如下&#xff1a; 使用–作为前缀&#xff0c;后跟变量名。变量名可以由字母、数字、连字符和下划线组成&#xff0c;并且不能以连字符开头。变量名区分大小写。变量定义在选择器范…

Arrays类概述,Lambda表达式

数组操作工具类&#xff0c;专门用于操作数组元素 2&#xff1a;常用API Lambda概述 Lambda表达式是JDK开始后的一种新语法形式作用&#xff1a;简化匿名内部类的代码写法 格式&#xff1a; 注意&#xff1a;Lambda表达式只能简化函数式接口的匿名内部类的写法形式。 什么是…

django 使用channels 搭建websocket聊天程序

channels官方文档&#xff1a;Django Channels — Channels 4.0.0 documentation 效果如下&#xff1a; 主要实现功能 基于Django的认证的群聊 具体实现 当建立websocket的时候&#xff0c;建立之前是http消息&#xff0c;我们可以拿到http消息里面的cookie等信息进行认证&…

Elasticsearch实战(二十四)---ES数据建模一对多模型Nested结构

Elasticsearch实战—ES数据建模一对多模型Nested结构 文章目录 Elasticsearch实战---ES数据建模一对多模型Nested结构1.ES 一对多模型Nested 结构模型实战2.ES字段查询2.1 非Nested 错误结构及错误查询2.2 Nested结构&#xff0c;正确查询 3.Nested结构原理 我们如何把Mysql的模…

(0020) H5-Vue-router+Element-ui 搭建非常简单的dashboard

参考学习&#xff1a; Vue Vue-router Element-ui 搭建一个非常简单的dashboard demo demo参考&#xff1a;https://github.com/wangduanduan/vue-el-dashboard 在线预览 效果图&#xff1a; 使用到的技术&#xff1a; Vue Vue-router Element-ui webpack Normalize.css v…

Spring Boot 中的 @RefreshScope 注解是什么,原理,如何使用

Spring Boot 中的 RefreshScope 注解是什么&#xff0c;原理&#xff0c;如何使用 在 Spring Boot 中&#xff0c;RefreshScope 注解是一个非常有用的注解。它可以让 Spring Boot 应用程序在运行时重新加载配置。这意味着您可以在不停止和重新启动应用程序的情况下更改配置。在…

深入理解链表:一种动态的线性数据结构

文章目录 前言1. 概述2. 单向链表3. 单向链表&#xff08;带哨兵&#xff09;4. 双向链表&#xff08;带哨兵&#xff09;5. 环形链表&#xff08;带哨兵&#xff09;6. 结语 前言 链表是我们在日常编程中经常使用的一种数据结构&#xff0c;它相比于数组具有更好的动态性能。…

策略模式深度实践——通用的HTTP接口调用

个人主页&#xff1a;金鳞踏雨 个人简介&#xff1a;大家好&#xff0c;我是金鳞&#xff0c;一个初出茅庐的Java小白 目前状况&#xff1a;22届普通本科毕业生&#xff0c;几经波折了&#xff0c;现在任职于一家国内大型知名日化公司&#xff0c;从事Java开发工作 我的博客&am…

HNU-小学期工训-STC-B案例测试作业

对于一些案例&#xff0c;这里列举一些 流水灯 八位数码管动态扫描 八位数码管流水灯(有BSP版本) 八位数码管滚动显示(有BSP版本) 可变亮度的数码管显示(有BSP版本) 扫描频率可改变的电子钟 按键消抖计数(有BSP版本) 三按键测试(有BSP版本) 霍尔磁场检测(有BSP版本) 数…

kubectl详解之声明式管理方法

目录 一、声明式管理方法二、资源配置清单的管理2.1 查看资源配置清单2.1 修改资源配置清单并应用2.1.1 离线修改2.1.2 在线修改 一、声明式管理方法 适合于对资源的修改操作 声明式资源管理方法依赖于资源配置清单文件对资源进行管理 资源配置清单文件有两种格式&#xff1a;…