原文:[UWP] 用 AudioGraph 来增强 UWP 的音频处理能力——AudioFrameInputNode

上一篇心得记录中提到了 AudioGraph, 描述了一下 什么是 AudioGraph 以及其中涉及到的各种类型的 节点(Node)。

这一篇就其中比较有意思的 AudioFrameInputNode 来详细展开一下。

借用 AudioFrameInputNode, 实现简单的音频左右声道互换

什么是 AudioFrameInputNode?

在微软的文档中这么介绍

An audio frame input node allows you to push audio data that you generate in your own code into the audio graph. This enables scenarios like creating a custom software synthesizer.

按照我个人的理解,AudioFrameInputNode 可以让我们自由的访问音频数据,音频数据是 PCM 格式,我们可以对音频数据做一些魔改,具体怎么魔改,就需要一些音频处理的算法知识了。

如何使用 AudioFrameInputNode?

1.创建 AudioFrameInputNode

AudioEncodingProperties nodeEncodingProperties = audioGraph.EncodingProperties;
nodeEncodingProperties.ChannelCount = 2;
nodeEncodingProperties.Subtype = "float";
nodeEncodingProperties.SampleRate = 44100;
nodeEncodingProperties.BitsPerSample = 32;AudioFrameInputNode frameInputNode = audioGraph.CreateFrameInputNode(nodeEncodingProperties);
frameInputNode.QuantumStarted += FrameInputNode_QuantumStarted;

所有的音频输入节点,都必须通过 AudioGragh 的实例方法来创建,AudioFrameInputNode 也不例外,在创建时,需要传入一个 AudioEncodingProperties,来描述 AudioFrameInputNode 需要处理的音频的一些属性。

在创建完成一个 AudioFrameInputNode 的对象实例后,需要订阅其 QuantumStarted 事件,这个事件会在 AudioGraph 开始处理音频数据时调用,在该事件方法内部,可以完成对音频数据的添加和修改。

2.访问 AudioFrame

AudioFrameInputNode 是基于 AudioFrame, 需要对其数据进行读取和写入。

所以在事件的订阅方法 FrameInputNode_QuantumStarted 内部,需要对 AudioFrame 填充 PCM 音频数据。

首先需要创建一个 AudioFrame 对象,在构造函数中,需要传入缓冲区的大小。

在这个示例中,每一个 采样点(Sample) 都是 Float 类型,采用立体声,也就是双通道,所以计算缓冲区大小的代码如下:

var bufferSize = args.RequiredSamples * sizeof(float) * 2;
AudioFrame audioFrame = new AudioFrame((uint)bufferSize);

在 AudioFrame 内部是一个 AudioBuffer,它代表存储 PCM 数据的缓冲区,所以接下来需要获取对该缓冲区的访问权,需要如下方法:

AudioBuffer audioBuffer = audioFrame.LockBuffer(AudioBufferAccessMode.Write);
IMemoryBufferReference bufferReference = audioBuffer.CreateReference();

通过 AudioBuffer 的实例方法 CreateReference,得到 IMemoryBufferReference 的对象,它实际上是一个 COM 接口,通过如下方法强制转换,可以获取 native 的缓冲区指针和缓冲区长度:

((IMemoryBufferByteAccess)bufferReference).GetBuffer(out byte* dataInBytes, out uint capacityInBytes);

其中 IMemoryBufferByteAccess 接口定义如下:

[ComImport]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe interface IMemoryBufferByteAccess
{void GetBuffer(out byte* buffer, out uint capacity);
}

注意,因为用到了指针,所以需要在工程配置文件中 允许unsafe code 选项打开, 并且在该方法签名中指明 unsafe 关键字。

至此,就得到了音频数据缓冲区的指针,但是此时整个缓冲区都是空的,需要填充 PCM 音频数据。

此处便是 AudioFrame 的便利之处,因为我们可以任意填充我们想要的音频数据,无论是处理过的还是没有处理过的。而获取 PCM 原始音频数据的途径很多,可以代码生成,也可以从文件读取,对于我这种对音频处理技术几乎白痴的人,我选择从一个 PCM 文件导入。

此处可以借用 Adobe Audition 等工具转换生成 PCM。

3.PCM 音频数据填充

打开一个 PCM 格式的文件流 fileStream, 其中 PCM 采样率是44100,32位浮点型,立体声。这些格式很重要,需要和初始化 AudioFrameInputNode 对象实例时设定的一样,才能保证数据填充过程正确。

在构造 AudioFrame 时传入了代表缓冲区长度的值 bufferSize,所以此处需要从文件流 fileStream 读取对应长度的数据到内存中,

var managedBuffer = new byte[capacityInBytes];var lastLength = fileStream.Length - fileStream.Position;
int readLength = (int)(lastLength < capacityInBytes ? lastLength : capacityInBytes);
if (readLength <= 0)
{fileStream.Close();fileStream = null;return;
}
fileStream.Read(managedBuffer, 0, readLength);

为了稍微体现一下 AudioFrameInputNode 的价值,这儿对要填充的数据做一项最简单的处理,即交换左右声道的内容。

在 PCM 中,每一个 Sample 是四个字节,具体排布是:

左声道,右声道,左声道,右声道,左声道,右声道,左声道,右声道........

所以交换声道就很简单了,代码如下:

for (int i = 0; i < readLength; i+=8)
{dataInBytes[i+4] = managedBuffer[i+0];dataInBytes[i+5] = managedBuffer[i+1];dataInBytes[i+6] = managedBuffer[i+2];dataInBytes[i+7] = managedBuffer[i+3];dataInBytes[i+0] = managedBuffer[i+4];dataInBytes[i+1] = managedBuffer[i+5];dataInBytes[i+2] = managedBuffer[i+6];dataInBytes[i+3] = managedBuffer[i+7];
}

因为 dataInBytes 是缓冲区的指针,所以对缓冲区赋值就是填充缓冲区的过程。在填充完后,需要释放 audioBuffer 和 bufferReference 对象,避免内存泄漏。

踩到的坑

  1. 大小端问题

    借用百度百科内容:

    大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。

    小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。

    二进制内容在内存里面存储,是存在大小端问题的,对于PCM格式,也存在大小端问题,所以如果对数据想进一步处理,大小端的问题一定要注意。

    在C#中调用 native 内容时,我的机器上实测时小端模式。

    也可以通过如下 unsafe 代码来判断:

    int temp = 0x01;
    int* pTempInt = &temp;
    byte* pTempByte = (byte*)pTempInt;
    if(0x01== *pTempByte)
    {//小端
    }
    else
    {//大端
    }
  2. float 在内存中如何排布?

    对于 int 类型,将其转换为二进制后,求补码,即是它在内存中的实际值,但是对于浮点型,就有一套自己的计算方法了,可以参考如下博客(大学计算机课本里的内容,忘得差不多了)

    float & double 内存布局

附件

Github AudioFrameInputNode Demo

附上我测试用的 PCM 数据,44100,32位 浮点型,小端模式

听说最近杭州下雪了,这歌现在很火!

许嵩-断桥残雪 片段 PCM

下图是该 PCM 的原始波形图,

所以听的时候听到的顺序应该是:先右声道,再立体声,最后左声道,和波形图里相反。

记得耳机别戴反!

[UWP] 用 AudioGraph 来增强 UWP 的音频处理能力——AudioFrameInputNode相关推荐

  1. uwp 能否运行于Linux,UWP,实现跨平台的关键

    02UWP,实现跨平台的关键 UWP,实现跨平台的关键 UWP是Win10中Universal Windows Platform的简称,是一个Windows通用应用平台.UWP通用应用不同于传统Win ...

  2. c# uwp html源码,C#UWP使用Microsoft Edge打开Web URL

    Process.Start是.NET Framework中使用的传统方法,不能直接在UWP应用程序中使用.要在UWP中使用Microsoft Edge打开Web URI,我们可以使用 Launcher ...

  3. c# uwp html源码,调试 UWP 应用中的 HTML 和 CSS - Visual Studio | Microsoft Docs

    在 Visual Studio 中调试 UWP 应用中的 HTML 和 CSS 07/17/2018 本文内容 Visual Studio 针对 JavaScript 应用提供全面的调试体验,其中包括 ...

  4. 【译】Visual Studio 2019 中 WPF UWP 的 XAML 开发工具新特性

    原文 | Dmitry 翻译 | 郑子铭 自Visual Studio 2019推出以来,我们为使用WPF或UWP桌面应用程序的XAML开发人员发布了许多新功能.在本周的 Visual Studio ...

  5. win10 UWP Controls by function

    Windows的 XAML UI 框架提供了很多控件,支持用户界面开发库. 我现在做的一个中文版的,很多都是照着微软写,除了注释 我们先学微软做一个简单的frame,新建Page,里面放title和跳 ...

  6. win10 uwp 线程池

    win10 uwp 线程池 原文:win10 uwp 线程池 如果大家有开发 WPF 或以前的程序,大概知道线程池不是 UWP 创造的,实际上在很多技术都用到线程池. 为什么需要线程池,他是什么?如何 ...

  7. [UWP]本地化入门

    [UWP]本地化入门 原文:[UWP]本地化入门 1. 前言 上一篇文章介绍了各种WPF本地化的入门知识,这篇文章介绍UWP本地化的入门知识. 2. 使用resw资源文件实现本地化 在以前的XAML平 ...

  8. [UWP]做个调皮的BusyIndicator

    1. 前言 最近突然想要个BusyIndicator.做过WPF开发的程序员对BusyIndicator应该不陌生,Extended WPF Toolkit 提供了BusyIndicator的开源实现 ...

  9. New UWP Community Toolkit - DeveloperTools

    概述 UWP Community Toolkit  中有一个开发者工具集 DeveloperTools,可以帮助开发者在开发过程中进行 UI 和功能的调试,本篇我们结合代码详细讲解  Develope ...

最新文章

  1. 玩转spring boot——结合阿里云持续交付
  2. Ubuntu 设置Android adb 环境变量
  3. html5斐波那契数列,经典的斐波那契数列与arguments.callee
  4. map中只有一个值 获取_小学数学,为什么一个三角形中最多只有一个直角或一个钝角...
  5. Spring Cloud综合实战 - 基于TCC补偿模式的分布式事务
  6. HTML5本地图片裁剪并上传
  7. 从程序员到项目经理(六):程序员加油站 -- 懂电脑更要懂人脑
  8. php 添加 redis 扩展模块
  9. redhat linux启动mysql_redhatlinux下mysql启动不了
  10. iPhone 13 的十大爆料:“十三”到底“香不香”?
  11. 超详细 | 接口自动化测试总结与分享入门篇
  12. UVA10023 Square root【大数】
  13. Testbench编写方法
  14. VS自带工具:dumpbin的使用查看Lib,dll等
  15. Translation 翻译插件google
  16. 最实用的Linux命令大全
  17. peU盘ud区和efi区如何共用wim文件
  18. matlab相机标定工具箱下载,matlab相机标定工具箱
  19. 使用 Zipkin 和 Brave 实现分布式系统追踪(基础篇
  20. python 猜字游戏外挂

热门文章

  1. Spring 实践 -IoC
  2. adodb.RecordSet的属性和方法
  3. 天堂Lineage(單機版)從零開始架設教學
  4. SWFTools PDF转换为SWF
  5. MFC-4简单的窗口重绘(非部分重绘)
  6. 淘宝网7年变化图--建议非美工UED人员也看看
  7. C#精髓【月儿原创】第二讲 WMI完美秀出CPU编号厂商主频百分比等全部信息
  8. 通过WMI获得硬盘和CPU的物理序列号(VB.net)
  9. 使用 .NET 框架轻松开发完美的 Web 窗体控件
  10. C++中MessageBox的常见用法