这是一个简单直观的 iOS 和 OSX 音频框架。
感谢大家在过去一年中的耐心 - 在过去几周里,我一直在疯狂地重写和扩展 EZAudio 的核心和界面组件,并修复了很多 bug。 finalmente,Το EZAudio 现已发布 1.0.0 版本,包含所有全新更新的组件、示例和文档。快乐的编码!
我很想开始创建一个使用 EZAudio 开发的项目的列表。如果您已经使用 EZAudio 开发了某个酷炫的东西,无论是应用、开源可视化或其他,请通过 syedhali07[at]gmail.com 发邮件给我,我将把它添加到我们的墙上!为了开始:
出色的组件
我设计了六个音频组件和两个界面组件,让您能够立即动手记录、播放和可视化音频数据。这些组件简单连接在一起,基于高性能、低延迟的 AudioUnits API,并为您提供一个易于使用的 Objective-C API,而不是纯 C。
这是一个有用的类,可以在任何 Apple 设备上获取所有当前和可用的输入/输出。EZMicrophone
和 EZOutput
使用此类将声音从不同的硬件组件中指导进出。
这是一个麦克风类,可以一键从默认设备麦克风接收音频数据。
这是一个输出类,可以回放其数据源提供的任何音频。
这是一个读取/定位音频文件的类,并提供有用的代理回调。
这是一个代替 AVAudioPlayer
的类,结合了 EZAudioFile
和 EZOutput
来实现任何硬件上任何文件的稳健播放。
这是一个记录类,提供了从任何数据源快速轻松地将音频文件写入的途径。
这是一个基于 Core Graphics 的音频波形图,可以将任何浮点数组视为缓冲区或滚动图。
一个基于OpenGL、GPU加速的音频波形图,能够将任何浮点数组可视化成缓冲区或滚动物形图。
跨平台
EZAudio
被设计为可在所有iOS和OSX设备上无缝工作。这意味着无论您是构建Mac还是iOS应用,都只需要一个通用的API。例如,在底层,EZAudioPlot
知道它将为iOS的UIView或OSX的NSView提供子类,而EZMicrophone
知道在iOS上应该在RemoteIO AudioUnit之上进行构建,但默认使用OSX的系统默认输入和输出。
在这个仓库中,您将发现iOS和OSX的示例,帮助您熟悉每个组件,并将它们组合在一起。只需几行代码,您就可以从麦克风录音、生成音频波形,以及像专业人士一样播放音频文件。更多信息请见完整的入门指南,这里有一个直观的每个组件的交互式介绍。
EZAudioCoreGraphicsWaveformExample
展示了如何使用EZMicrophone
和EZAudioPlot
实时可视化麦克风中的音频数据。波形可以被显示为缓冲区或滚动物形图(传统波形外观)。
EZAudioOpenGLWaveformExample
展示了如何使用EZMicrophone
和EZAudioPlotGL
实时可视化麦克风中的音频数据。因为使用OpenGL进行绘图,所以对于需要许多点的图表性能要好得多。
EZAudioPlayFileExample
展示了如何使用EZAudioPlayer
和EZAudioPlotGL
回放、暂停、查找并显示音频文件的波形,如图缓冲或滚动物形图。
EZAudioRecordWaveformExample
展示了如何使用EZMicrophone
、EZRecorder
和EZAudioPlotGL
将麦克风输入录音到文件,同时显示传入数据的音频波形。您可以使用AVFoundation回放新录制的音频文件,并将更多音频数据添加到文件的末尾。
EZAudioWaveformFromFileExample
展示了如何使用EZAudioFile
和EZAudioPlot
为整个音频文件播放一个音频波形动画。
EZAudioPassThroughExample
展示了如何使用EZMicrophone
、EZOutput
和EZAudioPlotGL
将麦克风输入传递给输出进行播放,同时实时显示音频波形(作为缓冲或滚动物形图)。
EZAudioFFTExample
展示了如何计算来自EZMicrophone
和Accelerate框架的音频数据的实时FFT。音频数据使用两个EZAudioPlots
来进行时间和频谱显示。
可以在这里找到EZAudio的官方文档:http://cocoadocs.org/docsets/EZAudio/1.1.4/
您也可以通过在EZAudio源文件夹上运行appledocs来自动生成docset。
开始使用EZAudio
之前,请确保您有适当的构建需求和框架。以下是对每个组件的解释以及代码片段,显示如何使用每个组件执行常见任务,例如获取麦克风数据、更新音频波形图、读取/查找音频文件以及进行播放。
iOS
OSX
iOS
OSX
您可以通过几种方式将 EZAudio 添加到您的项目中
1.) 使用 CocoaPods 是使用 EZAudio 的最简单方式。只需像这样将 EZAudio 添加到您的 Podfile:
pod 'EZAudio', '~> 1.1.4'
如果您还使用惊人的音频引擎,则可以使用 EZAudio/Core
子规范,如下所示:
pod 'EZAudio/Core', '~> 1.1.4'
2.) EZAudio 现在支持 Carthage(感谢 Andrew 和 Tommaso!)。您可以在 Carthage 的安装说明中找到如何操作的指南:https://github.com/Carthage/Carthage
3.) 或者,您可以查看 iOS/Mac 例子,了解如何设置一个项目,将 EZAudio 项目作为嵌入式项目使用,并利用框架。请确保将您的头文件搜索路径设置为包含 EZAudio 源的文件夹。
EZAudio
目前提供了六个音频组件,涵盖了广泛的功能。除了这些组件的职能方面,如拉取音频数据、读写文件、执行播放等功能外,它们还对接口组件进行了专门的链接,以便开发人员可以显示视觉反馈(请参阅下面的接口组件)。
提供了一个简单的接口,用于获取任何 Apple 设备的当前和所有可用的输入和输出。例如,iPhone 6 有三个可用的麦克风进行输入,而在 OSX 上可以选择内置麦克风或系统上可用的任何 HAL 设备。同样地,对于 iOS,您可以从连接的耳机或扬声器中进行选择,而在 OSX 上,您可以选择内置输出、任何可用的 HAL 设备或 Airplay。
要获取所有可用的输入设备,请使用 inputDevices
类方法
NSArray *inputDevices = [EZAudioDevice inputDevices];
或者只需获取当前选定的输入设备,请使用 currentInputDevice
方法
// On iOS this will default to the headset device or bottom microphone, while on OSX this will
// be your selected inpupt device from the Sound preferences
EZAudioDevice *currentInputDevice = [EZAudioDevice currentInputDevice];
同样地,要获取所有可用的输出设备,请使用 outputDevices
类方法
NSArray *outputDevices = [EZAudioDevice outputDevices];
或者只需获取当前选定的输出设备,请使用 currentInputDevice
方法
// On iOS this will default to the headset speaker, while on OSX this will be your selected
// output device from the Sound preferences
EZAudioDevice *currentOutputDevice = [EZAudioDevice currentOutputDevice];
可通过一行代码访问默认设备麦克风,并提供了委托回调以接收音频数据作为 AudioBufferList 和浮点数组。
相关示例项目
通过声明属性并按如下方式初始化来创建一个 EZMicrophone
实例
// Declare the EZMicrophone as a strong property
@property (nonatomic, strong) EZMicrophone *microphone;
...
// Initialize the microphone instance and assign it a delegate to receive the audio data
// callbacks
self.microphone = [EZMicrophone microphoneWithDelegate:self];
或者,您还可以使用共享的 EZMicrophone
实例并仅分配它的 EZMicrophoneDelegate
。
// Assign a delegate to the shared instance of the microphone to receive the audio data
// callbacks
[EZMicrophone sharedMicrophone].delegate = self;
EZMicrophone
使用一个 EZAudioDevice
实例来选择它将使用什么特定硬件目标来获取音频数据。如果您想更改输入设备,如 iOS 中的 EZAudioCoreGraphicsWaveformExample 或 OSX 中的 EZAudioCoreGraphicsWaveformExample,您将使用这个功能。您可以在任何时候通过设置设备属性来更改所使用的输入设备
NSArray *inputs = [EZAudioDevice inputDevices];
[self.microphone setDevice:[inputs lastObject]];
每次 EZMicrophone
更改其设备时,它都会触发 EZMicrophoneDelegate
事件
- (void)microphone:(EZMicrophone *)microphone changedDevice:(EZAudioDevice *)device
{
// This is not always guaranteed to occur on the main thread so make sure you
// wrap it in a GCD block
dispatch_async(dispatch_get_main_queue(), ^{
// Update UI here
NSLog(@"Changed input device: %@", device);
});
}
注意:对于 iOS,如果 AVAudioSession 改变当前设备,这可以自动发生。
要指示麦克风开始获取音频,请使用 startFetchingAudio
函数。
// Starts fetching audio from the default device microphone and sends data to EZMicrophoneDelegate
[self.microphone startFetchingAudio];
一旦 EZMicrophone
开始工作,它将以几种方式将音频发送回 EZMicrophoneDelegate
。一个浮点数数组的数组
/**
The microphone data represented as non-interleaved float arrays useful for:
- Creating real-time waveforms using EZAudioPlot or EZAudioPlotGL
- Creating any number of custom visualizations that utilize audio!
*/
-(void) microphone:(EZMicrophone *)microphone
hasAudioReceived:(float **)buffer
withBufferSize:(UInt32)bufferSize
withNumberOfChannels:(UInt32)numberOfChannels
{
__weak typeof (self) weakSelf = self;
// Getting audio data as an array of float buffer arrays that can be fed into the
// EZAudioPlot, EZAudioPlotGL, or whatever visualization you would like to do with
// the microphone data.
dispatch_async(dispatch_get_main_queue(),^{
// Visualize this data brah, buffer[0] = left channel, buffer[1] = right channel
[weakSelf.audioPlot updateBuffer:buffer[0] withBufferSize:bufferSize];
});
}
或者音频缓冲区列表表示
/**
The microphone data represented as CoreAudio's AudioBufferList useful for:
- Appending data to an audio file via the EZRecorder
- Playback via the EZOutput
*/
-(void) microphone:(EZMicrophone *)microphone
hasBufferList:(AudioBufferList *)bufferList
withBufferSize:(UInt32)bufferSize
withNumberOfChannels:(UInt32)numberOfChannels
{
// Getting audio data as an AudioBufferList that can be directly fed into the EZRecorder
// or EZOutput. Say whattt...
}
随时按如下方式暂停或恢复获取音频
// Stop fetching audio
[self.microphone stopFetchingAudio];
// Resume fetching audio
[self.microphone startFetchingAudio];
或者,您也可以切换 microphoneOn
属性(与 Cocoa 绑定兼容)
// Stop fetching audio
self.microphone.microphoneOn = NO;
// Start fetching audio
self.microphone.microphoneOn = YES;
通过请求 EZOutputDataSource
提供要播放的音频数据,以灵活的方式播放到默认输出设备。无论缓冲区从何而来(麦克风、音频文件、流式音频等),它都不关心。从 1.0.0 版本开始,EZOutputDataSource
已经简化为只有一个提供音频数据到您的 EZOutput
实例的方法。
// The EZOutputDataSource should fill out the audioBufferList with the given frame count.
// The timestamp is provided for sample accurate calculation, but for basic use cases can
// be ignored.
- (OSStatus) output:(EZOutput *)output
shouldFillAudioBufferList:(AudioBufferList *)audioBufferList
withNumberOfFrames:(UInt32)frames
timestamp:(const AudioTimeStamp *)timestamp;
相关示例项目
通过声明属性并按如下方式初始化来创建一个 EZOutput
。
// Declare the EZOutput as a strong property
@property (nonatomic, strong) EZOutput *output;
...
// Initialize the EZOutput instance and assign it a delegate to provide the output audio data
self.output = [EZOutput outputWithDataSource:self];
或者,您还可以使用共享的输出实例,并在只有一个 EZOutput
实例用于您的应用程序时为其分配一个 EZOutputDataSource
。
// Assign a delegate to the shared instance of the output to provide the output audio data
[EZOutput sharedOutput].delegate = self;
EZOutput
使用一个 EZAudioDevice
实例来选择它将输出音频到的特定硬件目标。如果您想更改输出设备,如 OSX 中的 EZAudioPlayFileExample,您将使用此功能。您可以通过设置 device
属性随时更改使用的输出设备
// By default the EZOutput uses the default output device, but you can change this at any time
EZAudioDevice *currentOutputDevice = [EZAudioDevice currentOutputDevice];
[self.output setDevice:currentOutputDevice];
每次 EZOutput
更改其设备时,它都会触发 EZOutputDelegate
事件
- (void)output:(EZOutput *)output changedDevice:(EZAudioDevice *)device
{
NSLog(@"Change output device to: %@", device);
}
当提供音频数据时,EZOutputDataSource
会期望您填满随 EZOutput
中设置的 inputFormat
提供的 AudioBufferList
。默认情况下,输入格式是立体声、非交织、浮点格式(详见 默认输入格式 了解更多信息)。如果您正在处理不同的输入格式(通常是这样),只需设置 inputFormat
属性。例如
// Set a mono, float format with a sample rate of 44.1 kHz
AudioStreamBasicDescription monoFloatFormat = [EZAudioUtilities monoFloatFormatWithSampleRate:44100.0f];
[self.output setInputFormat:monoFloatFormat];
EZOutputDataSource
的一个实现示例在内部使用 EZAudioPlayer
完成使用 在磁盘上的音频文件读取音频,如下所示
- (OSStatus) output:(EZOutput *)output
shouldFillAudioBufferList:(AudioBufferList *)audioBufferList
withNumberOfFrames:(UInt32)frames
timestamp:(const AudioTimeStamp *)timestamp
{
if (self.audioFile)
{
UInt32 bufferSize; // amount of frames actually read
BOOL eof; // end of file
[self.audioFile readFrames:frames
audioBufferList:audioBufferList
bufferSize:&bufferSize
eof:&eof];
if (eof && [self.delegate respondsToSelector:@selector(audioPlayer:reachedEndOfAudioFile:)])
{
[self.delegate audioPlayer:self reachedEndOfAudioFile:self.audioFile];
}
if (eof && self.shouldLoop)
{
[self seekToFrame:0];
}
else if (eof)
{
[self pause];
[self seekToFrame:0];
[[NSNotificationCenter defaultCenter] postNotificationName:EZAudioPlayerDidReachEndOfFileNotification
object:self];
}
}
return noErr;
}
我创建了一个示例项目,该项目使用 EZOutput
作为信号发生器来播放正弦波、方波、三角波、锯齿波和噪声波形。下面是生成正弦波的一段代码
...
double const SAMPLE_RATE = 44100.0;
- (void)awakeFromNib
{
//
// Create EZOutput to play audio data with mono format (EZOutput will convert
// this mono, float "inputFormat" to a clientFormat, i.e. the stereo output format).
//
AudioStreamBasicDescription inputFormat = [EZAudioUtilities monoFloatFormatWithSampleRate:SAMPLE_RATE];
self.output = [EZOutput outputWithDataSource:self inputFormat:inputFormat];
[self.output setDelegate:self];
self.frequency = 200.0;
self.sampleRate = SAMPLE_RATE;
self.amplitude = 0.80;
}
- (OSStatus) output:(EZOutput *)output
shouldFillAudioBufferList:(AudioBufferList *)audioBufferList
withNumberOfFrames:(UInt32)frames
timestamp:(const AudioTimeStamp *)timestamp
{
Float32 *buffer = (Float32 *)audioBufferList->mBuffers[0].mData;
size_t bufferByteSize = (size_t)audioBufferList->mBuffers[0].mDataByteSize;
double theta = self.theta;
double frequency = self.frequency;
double thetaIncrement = 2.0 * M_PI * frequency / SAMPLE_RATE;
if (self.type == GeneratorTypeSine)
{
for (UInt32 frame = 0; frame < frames; frame++)
{
buffer[frame] = self.amplitude * sin(theta);
theta += thetaIncrement;
if (theta > 2.0 * M_PI)
{
theta -= 2.0 * M_PI;
}
}
self.theta = theta;
}
else if (... other shapes in full source)
}
有关平方、三角、锯齿波和噪声函数的完整实现,请在此处查看:(https://github.com/syedhali/SineExample/blob/master/SineExample/GeneratorViewController.m#L220-L305)
一旦 EZOutput
已经开始,它将通过 EZOutputDelegate
将音频以浮点数组的形式发送回来以进行可视化。这些数组将在 EZOutput
组件内部从任何您可能提供的输入格式转换为浮点格式,例如,如果您为 inputFormat
属性提供交织的带符号整数 AudioStreamBasicDescription,则将在以下 playedAudio:...
方法发送时自动转换为立体声、非交织、浮点格式:一个浮点数组数组
/**
The output data represented as non-interleaved float arrays useful for:
- Creating real-time waveforms using EZAudioPlot or EZAudioPlotGL
- Creating any number of custom visualizations that utilize audio!
*/
- (void) output:(EZOutput *)output
playedAudio:(float **)buffer
withBufferSize:(UInt32)bufferSize
withNumberOfChannels:(UInt32)numberOfChannels
{
__weak typeof (self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
// Update plot, buffer[0] = left channel, buffer[1] = right channel
});
}
随时暂停或恢复输出组件
// Stop fetching audio
[self.output stopPlayback];
// Resume fetching audio
[self.output startPlayback];
内部,EZOutput
一直在使用 AUGraph
连接一起一个转换器、混音器和输出音频单元。您可以通过子类化 EZOutput
并实现方法来连接到这个图
// By default this method connects the AUNode representing the input format converter to
// the mixer node. In subclasses you can add effects in the chain between the converter
// and mixer by creating additional AUNodes, adding them to the AUGraph provided below,
// and then connecting them together.
- (OSStatus)connectOutputOfSourceNode:(AUNode)sourceNode
sourceNodeOutputBus:(UInt32)sourceNodeOutputBus
toDestinationNode:(AUNode)destinationNode
destinationNodeInputBus:(UInt32)destinationNodeInputBus
inGraph:(AUGraph)graph;
这受到了 CocoaLibSpotify 音频处理图的启发(Spotify 的 Daniel Kennett 有一个非常好的博客帖子,解释了如何在 CocoaLibSpotify AUGraph 中添加 EQ)。
下面是如何添加延迟音频单元(kAudioUnitSubType_Delay
)的示例
// In interface, declare delay node info property
@property (nonatomic, assign) EZAudioNodeInfo *delayNodeInfo;
// In implementation, overwrite the connection method
- (OSStatus)connectOutputOfSourceNode:(AUNode)sourceNode
sourceNodeOutputBus:(UInt32)sourceNodeOutputBus
toDestinationNode:(AUNode)destinationNode
destinationNodeInputBus:(UInt32)destinationNodeInputBus
inGraph:(AUGraph)graph
{
self.delayNodeInfo = (EZAudioNodeInfo *)malloc(sizeof(EZAudioNodeInfo));
// A description for the time/pitch shifter Device
AudioComponentDescription delayComponentDescription;
delayComponentDescription.componentType = kAudioUnitType_Effect;
delayComponentDescription.componentSubType = kAudioUnitSubType_Delay;
delayComponentDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
delayComponentDescription.componentFlags = 0;
delayComponentDescription.componentFlagsMask = 0;
[EZAudioUtilities checkResult:AUGraphAddNode(graph,
&delayComponentDescription,
&self.delayNodeInfo->node)
operation:"Failed to add node for time shift"];
// Get the time/pitch shifter Audio Unit from the node
[EZAudioUtilities checkResult:AUGraphNodeInfo(graph,
self.delayNodeInfo->node,
NULL,
&self.delayNodeInfo->audioUnit)
operation:"Failed to get audio unit for delay node"];
// connect the output of the input source node to the input of the time/pitch shifter node
[EZAudioUtilities checkResult:AUGraphConnectNodeInput(graph,
sourceNode,
sourceNodeOutputBus,
self.delayNodeInfo->node,
0)
operation:"Failed to connect source node into delay node"];
// connect the output of the time/pitch shifter node to the input of the destination node, thus completing the chain.
[EZAudioUtilities checkResult:AUGraphConnectNodeInput(graph,
self.delayNodeInfo->node,
0,
destinationNode,
destinationNodeInputBus)
operation:"Failed to connect delay to destination node"];
return noErr;
}
// Clean up
- (void)dealloc
{
free(self.delayNodeInfo);
}
提供简单的读取/查找操作,拉取波形振幅数据,并为 EZAudioFileDelegate
提供,以通知任何发生在 EZAudioFile
上的读取/查找动作。这可以被认为是音频世界的 NSImage/UIImage 的等效物。
相关示例项目
为了打开音频文件,创建 EZAudioFile
类的新实例。
// Declare the EZAudioFile as a strong property
@property (nonatomic, strong) EZAudioFile *audioFile;
...
// Initialize the EZAudioFile instance and assign it a delegate to receive the read/seek callbacks
self.audioFile = [EZAudioFile audioFileWithURL:[NSURL fileURLWithPath:@"/path/to/your/file"] delegate:self];
EZAudioFile
允许您快速从音频文件中获取波形数据,您希望有多少或多少细节。
__weak typeof (self) weakSelf = self;
// Get a waveform with 1024 points of data. We can adjust the number of points to whatever level
// of detail is needed by the application
[self.audioFile getWaveformDataWithNumberOfPoints:1024
completionBlock:^(float **waveformData,
int length)
{
[weakSelf.audioPlot updateBuffer:waveformData[0]
withBufferSize:length];
}];
从文件中读取音频数据需要您创建一个用于存储数据的AudioBufferList。EZAudio
实用函数audioBufferList
提供了一种方便的方式获取可使用的分配的AudioBufferList。还有一个实用函数freeBufferList:
,在您使用完该音频数据后用于释放(或释放)AudioBufferList。
注意:即使在自动引用计数(ARC)中,您也必须释放AudioBufferList。
// Allocate an AudioBufferList to hold the audio data (the client format is the non-compressed
// in-app format that is used for reading, it's different than the file format which is usually
// something compressed like an mp3 or m4a)
AudioStreamBasicDescription clientFormat = [self.audioFile clientFormat];
UInt32 numberOfFramesToRead = 512;
UInt32 channels = clientFormat.mChannelsPerFrame;
BOOL isInterleaved = [EZAudioUtilities isInterleaved:clientFormat];
AudioBufferList *bufferList = [EZAudioUtilities audioBufferListWithNumberOfFrames:numberOfFramesToRead
numberOfChannels:channels
interleaved:isInterleaved];
// Read the frames from the EZAudioFile into the AudioBufferList
UInt32 framesRead;
UInt32 isEndOfFile;
[self.audioFile readFrames:numberOfFramesToRead
audioBufferList:bufferList
bufferSize:&framesRead
eof:&isEndOfFile]
读取时,EZAudioFileDelegate
会收到两个事件。
一个事件通知代理已读取的音频数据作为浮点数组
-(void) audioFile:(EZAudioFile *)audioFile
readAudio:(float **)buffer
withBufferSize:(UInt32)bufferSize
withNumberOfChannels:(UInt32)numberOfChannels
{
__weak typeof (self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.audioPlot updateBuffer:buffer[0]
withBufferSize:bufferSize];
});
}
以及一个事件通知代理在EZAudioFile
中新的帧位置
-(void)audioFile:(EZAudioFile *)audioFile updatedPosition:(SInt64)framePosition
{
__weak typeof (self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
// Update UI
});
}
您可以使用EZAudioFile
的seekToFrame:
方法轻松地搜索音频文件。EZAudioFile
提供了一个totalFrames
方法,为您提供音频文件中帧的总数,以便您可以计算适当的偏移量。
// Get the total number of frames for the audio file
SInt64 totalFrames = [self.audioFile totalFrames];
// Seeks halfway through the audio file
[self.audioFile seekToFrame:(totalFrames/2)];
// Alternatively, you can seek using seconds
NSTimeInterval duration = [self.audioFile duration];
[self.audioFile setCurrentTime:duration/2.0];
寻找时,EZAudioFileDelegate
会收到寻找事件
-(void)audioFile:(EZAudioFile *)audioFile updatedPosition:(SInt64)framePosition
{
__weak typeof (self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
// Update UI
});
}
提供了一个类,该类结合了EZAudioFile
和EZOutput
,用于解码所有支持格式的音频文件并在任何硬件设备上播放。由于EZAudioPlayer
内部连接到EZAudioFileDelegate
和EZOutputDelegate
,因此您应该实现EZAudioPlayerDelegate
来接收playedAudio:...
和updatedPosition:
事件。EZAudioPlayFileExample项目为iOS和OSX展示了如何使用EZAudioPlayer
播放音频文件,使用音频波形可视化样本,调整音量,并使用EZAudioDevice
类更改输出设备。因为EZAudioPlayer
主要通过NSNotificationCenter
发布通知,所以通常有一个音频播放器和多个UI元素需要监听播放器事件以正确更新。
// Declare the EZAudioFile as a strong property
@property (nonatomic, strong) EZAudioFile *audioFile;
...
// Create an EZAudioPlayer with a delegate that conforms to EZAudioPlayerDelegate
self.player = [EZAudioPlayer audioPlayerWithDelegate:self];
EZAudioPlayer
使用内部的EZAudioFile
为其EZOutput
提供数据以通过EZOutputDataSource
输出。您只需设置EZAudioPlayer
上的audioFile
属性即可创建该文件路径URL上的EZAudioFile
的副本供自身使用。
// Set the EZAudioFile for playback by setting the `audioFile` property
EZAudioFile *audioFile = [EZAudioFile audioFileWithURL:[NSURL fileURLWithPath:@"/path/to/your/file"]];
[self.player setAudioFile:audioFile];
// This, however, will not pause playback if a current file is playing. Instead
// it's encouraged to use `playAudioFile:` instead if you're swapping in a new
// audio file while playback is already running
EZAudioFile *audioFile = [EZAudioFile audioFileWithURL:[NSURL fileURLWithPath:@"/path/to/your/file"]];
[self.player playAudioFile:audioFile];
随着音频的播放,EZAudioPlayerDelegate
将接收到playedAudio:...
、updatedPosition:...
(如果音频文件达到文件尾,将会有reachedEndOfAudioFile:
事件)。典型的EZAudioPlayerDelegate
实现可能如下:
- (void) audioPlayer:(EZAudioPlayer *)audioPlayer
playedAudio:(float **)buffer
withBufferSize:(UInt32)bufferSize
withNumberOfChannels:(UInt32)numberOfChannels
inAudioFile:(EZAudioFile *)audioFile
{
__weak typeof (self) weakSelf = self;
// Update an EZAudioPlot or EZAudioPlotGL to reflect the audio data coming out
// of the EZAudioPlayer (post volume and pan)
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.audioPlot updateBuffer:buffer[0]
withBufferSize:bufferSize];
});
}
//------------------------------------------------------------------------------
- (void)audioPlayer:(EZAudioPlayer *)audioPlayer
updatedPosition:(SInt64)framePosition
inAudioFile:(EZAudioFile *)audioFile
{
__weak typeof (self) weakSelf = self;
// Update any UI controls including sliders and labels
// display current time/duration
dispatch_async(dispatch_get_main_queue(), ^{
if (!weakSelf.positionSlider.highlighted)
{
weakSelf.positionSlider.floatValue = (float)framePosition;
weakSelf.positionLabel.integerValue = framePosition;
}
});
}
您可以使用与EZAudioFile
类似的方式来搜索音频文件。也就是说,使用seekToFrame:
或currentTime
属性。
// Get the total number of frames and seek halfway
SInt64 totalFrames = [self.player totalFrames];
[self.player seekToFrame:(totalFrames/2)];
// Alternatively, you can seek using seconds
NSTimeInterval duration = [self.player duration];
[self.player setCurrentTime:duration/2.0];
由于EZAudioPlayer
封装了EZOutput
,因此您可以调整播放的音量和平衡参数。
// Make it half as loud, 0 = silence, 1 = full volume. Default is 1.
[self.player setVolume:0.5];
// Make it only play on the left, -1 = left, 1 = right. Default is 0.0 (center)
[self.player setPan:-1.0];
EZAudioPlayer
对 EZAudioFile
进行封装,并提供了获取当前时间、持续时间、帧索引、总帧数等值的高级接口。
NSTimeInterval currentTime = [self.player currentTime];
NSTimeInterval duration = [self.player duration];
NSString *formattedCurrentTime = [self.player formattedCurrentTime]; // MM:SS formatted
NSString *formattedDuration = [self.player formattedDuration]; // MM:SS formatted
SInt64 frameIndex = [self.player frameIndex];
SInt64 totalFrames = [self.player totalFrames];
此外,EZOutput
属性也以高级形式提供。
EZAudioDevice *outputDevice = [self.player device];
BOOL isPlaying = [self.player isPlaying];
float pan = [self.player pan];
float volume = [self.player volume];
EZAudioPlayer
提供以下通知(截至 1.1.2 版本)。
/**
Notification that occurs whenever the EZAudioPlayer changes its `audioFile` property. Check the new value using the EZAudioPlayer's `audioFile` property.
*/
FOUNDATION_EXPORT NSString * const EZAudioPlayerDidChangeAudioFileNotification;
/**
Notification that occurs whenever the EZAudioPlayer changes its `device` property. Check the new value using the EZAudioPlayer's `device` property.
*/
FOUNDATION_EXPORT NSString * const EZAudioPlayerDidChangeOutputDeviceNotification;
/**
Notification that occurs whenever the EZAudioPlayer changes its `output` component's `pan` property. Check the new value using the EZAudioPlayer's `pan` property.
*/
FOUNDATION_EXPORT NSString * const EZAudioPlayerDidChangePanNotification;
/**
Notification that occurs whenever the EZAudioPlayer changes its `output` component's play state. Check the new value using the EZAudioPlayer's `isPlaying` property.
*/
FOUNDATION_EXPORT NSString * const EZAudioPlayerDidChangePlayStateNotification;
/**
Notification that occurs whenever the EZAudioPlayer changes its `output` component's `volume` property. Check the new value using the EZAudioPlayer's `volume` property.
*/
FOUNDATION_EXPORT NSString * const EZAudioPlayerDidChangeVolumeNotification;
/**
Notification that occurs whenever the EZAudioPlayer has reached the end of a file and its `shouldLoop` property has been set to NO.
*/
FOUNDATION_EXPORT NSString * const EZAudioPlayerDidReachEndOfFileNotification;
/**
Notification that occurs whenever the EZAudioPlayer performs a seek via the `seekToFrame` method or `setCurrentTime:` property setter. Check the new `currentTime` or `frameIndex` value using the EZAudioPlayer's `currentTime` or `frameIndex` property, respectively.
*/
FOUNDATION_EXPORT NSString * const EZAudioPlayerDidSeekNotification;
提供了一种将任何音频源录制到音频文件中的方法。这个功能与其他组件配合得很好,可以在录制过程中绘制音频波形,以提供视觉反馈,了解正在发生什么。通过 EZRecorderDelegate
提供的方法可以监听写事件和 EZRecorder
的最终关闭事件(详情见下文)。
相关示例项目
要创建 EZRecorder
,您必须提供以下至少三个事项:表示音频文件写入路径的 NSURL
(现有文件将被覆盖)、表示您将提供音频数据的格式的 clientFormat
和表示磁盘上音频数据文件格式的 EZRecorderFileType
或 AudioStreamBasicDescription
。
// Provide a file path url to write to, a client format (always linear PCM, this is the format
// coming from another component like the EZMicrophone's audioStreamBasicDescription property),
// and a EZRecorderFileType constant representing either a wav (EZRecorderFileTypeWAV),
// aiff (EZRecorderFileTypeAIFF), or m4a (EZRecorderFileTypeM4A) file format. The advantage of
// this is that the `fileFormat` property will be automatically filled out for you.
+ (instancetype)recorderWithURL:(NSURL *)url
clientFormat:(AudioStreamBasicDescription)clientFormat
fileType:(EZRecorderFileType)fileType;
// Alternatively, you can provide a file path url to write to, a client format (always linear
// PCM, this is the format coming from another component like the EZMicrophone's
// audioStreamBasicDescription property), a `fileFormat` representing your custom
// AudioStreamBasicDescription, and an AudioFileTypeID that corresponds with your `fileFormat`.
+ (instancetype)recorderWithURL:(NSURL *)url
clientFormat:(AudioStreamBasicDescription)clientFormat
fileFormat:(AudioStreamBasicDescription)fileFormat
audioFileTypeID:(AudioFileTypeID)audioFileTypeID;
首先,声明一个 EZRecorder 实例(您将为每个写入的音频文件有一个这样的实例)。
// Declare the EZRecorder as a strong property
@property (nonatomic, strong) EZRecorder *recorder;
然后使用上述两种初始化方法之一进行初始化。例如,使用 EZRecorderFileType
快捷初始化器可以如此创建实例
// Example using an EZMicrophone and a string called kAudioFilePath representing a file
// path location on your computer to write out a M4A file.
self.recorder = [EZRecorder recorderWithURL:[NSURL fileURLWithPath:@"/path/to/your/file.m4a"]
clientFormat:[self.microphone audioStreamBasicDescription]
fileType:EZRecorderFileTypeM4A];
或者要配置自己的自定义文件格式,例如写入 8000 Hz、iLBC 文件
// Example using an EZMicrophone, a string called kAudioFilePath representing a file
// path location on your computer, and an iLBC file format.
AudioStreamBasicDescription iLBCFormat = [EZAudioUtilities iLBCFormatWithSampleRate:8000];
self.recorder = [EZRecorder recorderWithURL:[NSURL fileURLWithPath:@"/path/to/your/file.caf"]
clientFormat:[self.microphone audioStreamBasicDescription]
fileFormat:iLBCFormat
audioFileTypeID:kAudioFileCAFType];
初始化 EZRecorder
后,可以通过传递 AudioBufferList 及其缓冲区大小来追加数据。
// Append the microphone data coming as a AudioBufferList with the specified buffer size
// to the recorder
-(void) microphone:(EZMicrophone *)microphone
hasBufferList:(AudioBufferList *)bufferList
withBufferSize:(UInt32)bufferSize
withNumberOfChannels:(UInt32)numberOfChannels
{
// Getting audio data as a buffer list that can be directly fed into the EZRecorder. This is
// happening on the audio thread - any UI updating needs a GCD main queue block.
if (self.isRecording)
{
// Since we set the recorder's client format to be that of the EZMicrophone instance,
// the audio data coming in represented by the AudioBufferList can directly be provided
// to the EZRecorder. The EZRecorder will internally convert the audio data from the
// `clientFormat` to `fileFormat`.
[self.recorder appendDataFromBufferList:bufferList
withBufferSize:bufferSize];
}
}
成功使用 EZRecorder
写入音频数据后,将通知 EZRecorderDelegate
事件,使其可以通过
// Triggers after the EZRecorder's `appendDataFromBufferList:withBufferSize:` method is called
// so you can update your interface accordingly.
- (void)recorderUpdatedCurrentTime:(EZRecorder *)recorder
{
__weak typeof (self) weakSelf = self;
// This will get triggerd on the thread that the write occured on so be sure to wrap your UI
// updates in a GCD main queue block! However, I highly recommend you first pull the values
// you'd like to update the interface with before entering the GCD block to avoid trying to
// fetch a value after the audio file has been closed.
NSString *formattedCurrentTime = [recorder formattedCurrentTime]; // MM:SS formatted
dispatch_async(dispatch_get_main_queue(), ^{
// Update label
weakSelf.currentTimeLabel.stringValue = formattedCurrentTime;
});
}
录制完成后,务必调用 closeAudioFile
方法,以确保在再次读取音频文件之前,写入磁盘上的音频文件已经正确关闭。
// Close the EZRecorder's audio file BEFORE reading
[self.recorder closeAudioFile];
这将触发 EZRecorder 的代理方法
- (void)recorderDidClose:(EZRecorder *)recorder
{
recorder.delegate = nil;
}
EZAudio
当前提供两个嵌入式音频波形组件,有助于简化音频可视化的过程。
提供了一个使用 CoreGraphics 进行绘制的音频波形图。在 iOS 中,它是 UIView 的子类,而在 OSX 中,它是 NSView 的子类。截至 1.0.0 版本,波形图使用 CALayers 绘制,排版在 GPU 上完成。因此,性能有了显著的提升,实时(即每秒 60 帧)绘图时的 CPU 使用率现在约为 2-3%,而此前为 20-30%。
相关示例项目
您可以在界面构建器中通过将UIView(iOS)或NSView(OSX)拖动到内容区域中来创建一个音频图表。然后更改UIView/NSView的自定义类为EZAudioPlot
。
或者,您也可以通过编程的方式创建音频图表
// Programmatically create an audio plot
EZAudioPlot *audioPlot = [[EZAudioPlot alloc] initWithFrame:self.view.frame];
[self.view addSubview:audioPlot];
所有图表都提供更改背景颜色、波形颜色、图表类型(缓冲或滚动)、切换填充和虚线、以及切换镜像和非镜像(关于x轴)的能力。iOS上的颜色类型为UIColor,而OSX上的颜色类型为NSColor。
// Background color (use UIColor for iOS)
audioPlot.backgroundColor = [NSColor colorWithCalibratedRed:0.816
green:0.349
blue:0.255
alpha:1];
// Waveform color (use UIColor for iOS)
audioPlot.color = [NSColor colorWithCalibratedRed:1.000
green:1.000
blue:1.000
alpha:1];
// Plot type
audioPlot.plotType = EZPlotTypeBuffer;
// Fill
audioPlot.shouldFill = YES;
// Mirror
audioPlot.shouldMirror = YES;
从iOS 8开始,您可以通过IBInspectable属性直接在界面构建器中调整背景颜色、颜色、增益、shouldFill和shouldMirror参数
所有图表只有一个更新函数,即updateBuffer:withBufferSize:
,该函数需要一个float数组及其长度。
// The microphone component provides audio data to its delegate as an array of float buffer arrays.
- (void) microphone:(EZMicrophone *)microphone
hasAudioReceived:(float **)buffer
withBufferSize:(UInt32)bufferSize
withNumberOfChannels:(UInt32)numberOfChannels
{
/**
Update the audio plot using the float array provided by the microphone:
buffer[0] = left channel
buffer[1] = right channel
Note: Audio updates happen asynchronously so we need to make sure
sure to update the plot on the main thread
*/
__weak typeof (self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.audioPlot updateBuffer:buffer[0] withBufferSize:bufferSize];
});
}
提供使用OpenGL进行绘图的音频波形图表。此类的API与上面提到的EZAudioPlot的完全相同。在iOS上,这是一个GLKView的子类,而在OSX上,这是一个NSOpenGLView的子类。在大多数情况下,这是您想要的图表,它是GPU加速的,可以处理大量点,而在每秒显示60帧时表现惊人(EZAudioPlot在1024以上时开始出现问题),并且可以在所有设备上表现出色。唯一的缺点是只能在屏幕上同时有一个OpenGL图表。然而,您可以在视图层次结构中将OpenGL图表与Core Graphics图表结合起来(请参阅EZAudioRecordExample以获得如何做到这一点的示例)。
相关示例项目
您可以在界面构建器中将UIView(iOS)或NSView(OSX)拖动到内容区域中来创建音频图表。然后,将UIView/NSView的自定义类更改为EZAudioPlotGL
。
或者,您可以通过编程的方式创建EZAudioPlotGL
// Programmatically create an audio plot
EZAudioPlotGL *audioPlotGL = [[EZAudioPlotGL alloc] initWithFrame:self.view.frame];
[self.view addSubview:audioPlotGL];
所有图表都提供更改背景颜色、波形颜色、图表类型(缓冲或滚动)、切换填充和虚线、以及切换镜像和非镜像(关于x轴)的能力。iOS上的颜色类型为UIColor,而OSX上的颜色类型为NSColor。
// Background color (use UIColor for iOS)
audioPlotGL.backgroundColor = [NSColor colorWithCalibratedRed:0.816
green:0.349
blue:0.255
alpha:1];
// Waveform color (use UIColor for iOS)
audioPlotGL.color = [NSColor colorWithCalibratedRed:1.000
green:1.000
blue:1.000
alpha:1];
// Plot type
audioPlotGL.plotType = EZPlotTypeBuffer;
// Fill
audioPlotGL.shouldFill = YES;
// Mirror
audioPlotGL.shouldMirror = YES;
从iOS 8开始,您可以通过IBInspectable属性直接在界面构建器中调整背景颜色、颜色、增益、shouldFill和shouldMirror参数
所有图表只有一个更新函数,即updateBuffer:withBufferSize:
,该函数需要一个float数组及其长度。
// The microphone component provides audio data to its delegate as an array of float buffer arrays.
- (void) microphone:(EZMicrophone *)microphone
hasAudioReceived:(float **)buffer
withBufferSize:(UInt32)bufferSize
withNumberOfChannels:(UInt32)numberOfChannels
{
/**
Update the audio plot using the float array provided by the microphone:
buffer[0] = left channel
buffer[1] = right channel
Note: Audio updates happen asynchronously so we need to make sure
sure to update the plot on the main thread
*/
__weak typeof (self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.audioPlotGL updateBuffer:buffer[0] withBufferSize:bufferSize];
});
}
EZAudio在MIT许可证下提供。有关更多信息,请参阅LICENSE文件。
Syed Haris Ali
www.syedharisali.com
syedhali07[at]gmail.com
以下人员很棒
EZOutput
的重写。