AVAsset MP3 PCM 格式 音频 采样 AVAssetReader
AVAssetWriter 输出 转换

需求是想在appStore上显示公司的名字,之前我们申请的是个人开发者账号,所以显示的是个人的名字。这时就需要将个人开发者账号升级为公司开发者账号。首先需要明确几个概念,什么是个人开发者账号、公司开发者账号、企业开发者账号,以及它们之间的区别。

AsyncDisplayKit 是 Facebook 开源的用于保持 iOS 界面流畅的库。

澳门微尼斯人手机版 1

开发者账号分为个人(individual),公司,企业(enterprise)三种类型。个人账号只能有一个开发者,公司账号可以允许多个开发者协作开发(比如可以共享开发平台等,这个对于需要多人协作开发的好处很多)。企业账号,其app只能用于内部员工使用,是无法对外公开的,所以,普通情况下大家都是选择个人或者公司账号。对于我们来说,选择99刀的公司账号就可以了。

  1. ASDK 的基本原理

    澳门微尼斯人手机版 2

    ASDK
    认为,阻塞主线程的任务,主要分为以上三大类,文本和布局的计算、渲染、解码、绘制都可以通过各种方式异步执行,但
    UIKit 和 CoreAnimation 相关操作必须在主线程执行。ASDK
    的主要任务,就是将这些任务从主线程挪走,而挪不走的,就尽量封装优化。

本文所有示例代码或Demo可以在此获取:

对比:

为了达成这一目标,ASDK 尝试对 UIKit 组件进行封装

如果本文对你有所帮助,请给个Star👍

1、个人(Individual):

澳门微尼斯人手机版 3

本文仅讲解所用技术的基本概念以及将MP3转成PCM格式的实际应用,其他格式的相互转换可以修改示例代码实现。关于AVAsset的其他使用场景可以参考这里,音频相关的内容可以参考这里。

费用:99美元一年

这是常见的 UIView 和 CALayer 的关系:UIView 持有 Layer 用于显示,View
中的大部分显示属性实际上是从 Layer 映射而来的;Layer 的 delegate 是
UIView,当其属性改变、动画产生时,View 能够得到通知。UIView 和 CALayer
不是线程安全的,并且只能在主线程创建、访问和销毁。

首先了解一些概念:

App Store上架:是

![](http://upload-images.jianshu.io/upload_images/4653622-ace8647c111ee2d1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/500)
AVAsset

它包含于AVFoundation,是一个不可变的抽象类,用来代表一个音视频媒体。一个AVAsset实例可能包含着一个或多个用来播放或处理的轨道,包含但不限于音频、视频、文本以及相关说明。但它并不是媒体资源本身,可以将它理解为时基媒体的容器。

最大uuid支持数:100

ASDK 为此创建了 ASDisplayNode 类,包括了常见属性(比如
frame/bounds/alpha/transform/backgroundColor/superNode/subNodes)等,然后它用
UIView->CALayer 的方式,实现了 ASNode->UIView 这样的一个关系。

AVAssetReader

我们可以使用一个AVAssetReader实例从一个AVAsset的实例中获取媒体数据。

协作人数:1人

澳门微尼斯人手机版 4

AVAssetReaderAudioMixOutput

它是AVAssetReaderOutput的一个子类,我们可以将一个AVAssetReaderAudioMixOutput的实例绑定到一个AVAssetReader实例上,从而得到这个AVAssetReader实例的asset的音频采样数据。

说明:“个人”开发者可以申请升级“公司”,可以通过拨打苹果公司客服电话(400
6701 855)来咨询和办理。

当不需要响应触摸事件时,ASDisplayNode 可以被设置为 layer backed,即
ASDisplayNode 充当了原来的 View 的功能,节省了更多资源。与 UIView 和
CALayer 不同,ASDisplayNode
是线程安全的,它可以在后台线程创建和修改。Node
刚创建的时候,并不会在内部新建 UIView 和 CALayer,直到第一次在主线程访问
UIView 和 CALayer
属性时,它才会在内部生成相应对象。当它的属性(frame/transform)改变后,它并不会立即同步到它持有的
View 或者 Layer
上,而是把改变的属性保存到内部的一个中间变量,稍后需要的时候再通过某个机制一次性设置到内部的
View 和 Layer。

AVAssetWriter

我们可以使用一个AVAssetWriter实例将媒体数据写入一个新的文件,并为其指定类型。

2、公司:

  1. ASDK 的图层预合成有时候一个 Layer 会包含许多 sub-Layer,而这些
    sub-Layer 并不需要响应触摸事件,也不需要进行动画和位置调整。ASDK
    为此实现了一个叫做 pre-composing 的技术,可以把这些 sub-layer
    合成渲染为一张图片。开发时,ASNode 已经替代了 UIView 和
    CALayer;直接使用各种 Node 并设置为 layer backed 后,ASNode
    甚至可以使用预合成来避免创建内部的 UIView 和
    CALayer。通过这种方式,把一个大的层级,通过一个大的绘制方法绘制到一张图上,性能会获得很大提升。CPU
    也避免了创建 UIKit 对象的资源消耗,GPU 避免了多张 Texture
    合成和渲染的消耗,更少的 bitmap 也意味着更少的内存占用。

  2. ASDK 异步并行开发自4S 开始,苹果移动设备都已经是双核 CPU 以上
    ,充分利用多核的优势、并发执行任务对保持界面流畅有很大作用。ASDK
    把布局计算、文本排版、图片/文本/图形渲染等操作都封装成较小的任务,并利用
    GCD 异步并发执行。如果开发者使用了 ASNode
    相关的控件,那么这些并发操作会自动在后台进行,无需进行过多配置。

  3. Runloop 任务分发Runloop Work Distribution 是 ASDK
    一个比较核心的技术。ASDK
    的介绍视频和文档中都没有详细介绍,但是网上关于 Runloop
    的博客很多,在这里无需赘述。

    澳门微尼斯人手机版 5

AVAssetWriterInput

我们可以将一个AVAssetWriterInput的实例绑定到一个AVAssetWriter实例上,从而将媒体采样包装成CMSampleBuffer对象或者元数据集合,然后添加到输出文件的单一通道上。

费用:99美元一年

iOS 的显示系统是由 VSync 信号驱动的,VSync
由硬件时钟生成,每秒发出60次(这个值取决于设备,iPhone 上通常是 59.97
次)。iOS 图形服务收到 VSync 信号后,会通过 IPC 通知的 App 内,App 的
Runloop 在启动后会注册对应的 CFRunloopSource 通过 math_port
传过来的时钟信号通知,随后 Source 的回调会驱动整个 App
的动画与现实。Core Animation 在 Runloop 中注册了一个 Observer,监听了
BeforeWaiting 和 Exit 事件,这个 Observer 的优先级是 200
0000,低于其他常见的 Observer。当一个触发事件到来时,Runloop 被唤醒,App
中的代码会执行一些操作,比如创建和调整视图层级设置 UIView 的 frame、修改
CALayer 的透明度、为视图添加一些动画;这些操作最终会被 CALayer
捕获,并通过 CATransaction 提交到一个中间状态去(CATransaction
的文档中有提到这写内容,但并不完整)。当上面的所有操作结束后,Runloop
即将进入休眠或退出时,关注该事件的 Observer 都会得到通知,这时 CA
注册的那个 Observer 就会在回调中把所有的中间状态合并提交到 GPU
去显示;如果此处有动画,CA 会通过 DisplayLink
等机制多次触发相关流程。ASDK 在此处模拟了 CoreAnimation
的这个机制,所有针对 ASNode
的修改和提交,总有些任务必须放到主线程中去执行的。当出现这种任务的时候,ASNode
会把任务用 ASASyncTransaction封装并提交到一个全新容器中去。ASDK 也在
Runloop 中注册了一个Observer,监听的事件和CA一样,但是优先级比 CA
要低。在 Runloop 进入休眠前,CA 处理完事件后,ASDK 就会执行该 loop
内提交的所有任务。具体代码见ASAsyncTransactionGroup。通过这种机制,ASDK
可以在合适的机会把同步、异步的操作同步到主线程中去,并且能获得不错的性能。

PCM

模拟音频信号经模数转换直接形成的二进制序列,PCM就是录制声音时保存的最原始的声音数据格式。WAV格式的音频其实就是给PCM数据流加上一段header数据。而WAV格式有时候之所以被称为无损格式,就是因为它保存的是原始PCM数据(也跟采样率比特率有关)。常见音频格式比如MP3AAC等等,为了节约占用空间都进行有损压缩。

这里列举两种应用场景:

  1. PCM数据写入磁盘保存成文件。
  2. PCM数据转成NSDate保存在内存中。

这两种场景都需要先读取MP3的数据,然后创建AVAssetReaderAVAssetReaderAudioMixOutput实例,所以前半部分的处理逻辑的一样的。

App Store上架:是

  1. 其他ASDK 中还封装了许多高级的功能,比如滑动列表的预加载、v2.0
    添加新的布局模式等。ASDK 是一个很庞大的库,它本身并不推荐你将整个
    App 全部改为 ASDK 驱动,把最需要提升交互性能的地方用 ASDK
    进行优化就足够了。
通用逻辑

0.导入头文件

import AVFoundation

1.创建AVAsset实例

func readMp3File() -> AVAsset? { guard let filePath = Bundle.main.path(forResource: "trust you", ofType: "mp3") else { return nil } let fileURL = URL(fileURLWithPath: filePath) let asset = AVAsset(url: fileURL) return asset}

2.创建AVAssetReader实例

func initAssetReader(asset: AVAsset) -> AVAssetReader? { let assetReader: AVAssetReader do { assetReader = try AVAssetReader(asset: asset) } catch { print return nil } return assetReader}

3.配置转码参数

var channelLayout = AudioChannelLayout()memset(&channelLayout, 0, MemoryLayout<AudioChannelLayout>.size)channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereolet outputSettings = [ AVFormatIDKey : kAudioFormatLinearPCM, // 音频格式 AVSampleRateKey : 44100.0, // 采样率 AVNumberOfChannelsKey : 2, // 通道数 1 || 2 AVChannelLayoutKey : Data.init(bytes: &channelLayout, count: MemoryLayout<AudioChannelLayout>.size), // 声音效果 AVLinearPCMBitDepthKey : 16, // 音频的每个样点的位数 AVLinearPCMIsNonInterleaved : false, // 音频采样是否非交错 AVLinearPCMIsFloatKey : false, // 采样信号是否浮点数 AVLinearPCMIsBigEndianKey : false // 音频采用高位优先的记录格式 ] as [String : Any]

4.创建AVAssetReaderAudioMixOutput实例并绑定到assetReader上

let readerAudioMixOutput = AVAssetReaderAudioMixOutput(audioTracks: asset.tracks, audioSettings: nil)if !assetReader.canAdd(readerAudioMixOutput) { print("can't add readerAudioMixOutput") return}assetReader.add(readerAudioMixOutput)

接来下两种场景的处理逻辑就不一样了,请注意区分。

最大uuid支持数:100

保存成文件

5.创建一个AVAssetWriter实例

func initAssetWriter() -> AVAssetWriter? { let assetWriter: AVAssetWriter guard let outPutPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else { return nil } // 这里的扩展名'.wav'只是标记了文件的打开方式,实际的编码封装格式由assetWriter的fileType决定 let fullPath = outPutPath + "outPut.wav" let outPutURL = URL(fileURLWithPath: fullPath) do { assetWriter = try AVAssetWriter(outputURL: outPutURL, fileType: AVFileTypeWAVE) } catch { print return nil } return assetWriter}

6.创建AVAssetWriterInput实例并绑定到assetWriter上

if !assetWriter.canApply(outputSettings: outputSettings, forMediaType: AVMediaTypeAudio) { print("can't apply outputSettings") return}let writerInput = AVAssetWriterInput(mediaType: AVMediaTypeAudio, outputSettings: outputSettings)// 是否让媒体数据保持实时。在此不需要开启writerInput.expectsMediaDataInRealTime = falseif !assetWriter.canAdd(writerInput) { print("can't add writerInput") return}assetWriter.add(writerInput)

7.启动转码

assetReader.startReading()assetWriter.startWriting()// 开启sessionguard let track = asset.tracks.first else { return }let startTime = CMTime(seconds: 0, preferredTimescale: track.naturalTimeScale)assetWriter.startSession(atSourceTime: startTime)let mediaInputQueue = DispatchQueue(label: "mediaInputQueue")writerInput.requestMediaDataWhenReady(on: mediaInputQueue, using: { while writerInput.isReadyForMoreMediaData { if let nextBuffer = readerAudioMixOutput.copyNextSampleBuffer() { writerInput.append(nextBuffer) } else { writerInput.markAsFinished() assetReader.cancelReading() assetWriter.finishWriting(completionHandler: { print("write complete") }) break } }})

协作人数:多人

转成NSDate

5.启动转码

assetReader.startReading()var PCMData = Data()while let nextBuffer = readerAudioMixOutput.copyNextSampleBuffer() { var audioBufferList = AudioBufferList() var blockBuffer: CMBlockBuffer? // CMSampleBuffer 转 Data CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(nextBuffer, nil, &audioBufferList, MemoryLayout<AudioBufferList>.size, nil, nil, 0, &blockBuffer) let audioBuffer = audioBufferList.mBuffers guard let frame = audioBuffer.mData else { continue } PCMData.append(frame.assumingMemoryBound(to: UInt8.self), count: Int(audioBuffer.mDataByteSize)) blockBuffer = nil}print("write complete")

性能问题

转码是个很占用CPU资源的计算过程。具体完成一个转码过程的时间取决于文件时长、转码配置、设备性能等多个条件。这是一个典型的耗时操作,务必要做好线程优化。另外,可以根据业务逻辑间歇调用readerAudioMixOutput.copyNextSampleBuffer()及后续操作,降低CPU开销峰值。

内存管理

以本文将MP3转成PCM的代码为例,一个时长4分半左右的MP3对应的PCM数据在55MB左右,这些数据占用了大量的内存或磁盘空间,注意释放。你可以通过改变转码配置参数outputSettings来调整输出数据的大小。在转码过程中,CMSampleBufferRefCMBlockBufferRef的对象在使用后需要调用CFRelease销毁,以防内存泄漏。

其他格式的转换

逻辑是一样的,你可以修改读取和输出的参数实现。注意处理的格式必须是AVFoundation所包含的,可以参考AudioFormatID这个类以及AVMediaFormat.hFile format UTIs。更多音频处理请参考Apple
Developer Library :AVFoundation或第三方框架。

在macOS上转换格式

macOS上可以使用一个强大的音视频库FFmpeg,它可以帮助你快速转码出需要的音频格式作为调试素材。macOS上编译FFmpeg请看这里。将MP3转换成PCM的命令:

ffmpeg mp3 => pcm ffmpeg -i xxx.mp3 -f s16le -ar 44100 -ac 2 xxx.pcm

本文提供了将MP3转成PCM的一种实现,中间涉及了一些音频AVFoundationCoreMedia的知识,这里就不展开了,有问题的同学可以在文章下留言讨论。

本文所有示例代码或Demo可以在此获取:

如果本文对你有所帮助,请给个Star👍

参考资料:Apple Developer Library
:AVFoundation

允许多个开发者进行协作开发,比个人多一些帐号管理的设置,可设置多个Apple
ID,分4种管理级别的权限。

说明:申请时需要填写公司的邓白氏编码(DUNS Number)。

3、企业 (Enterprise)

费用:299美元一年

App Store上架:否

即该账号开发应用不能发布到App Store,只能企业内部应用。

最大uuid支持数:不限制

协作人数:多人

费用:299美元一年

说明:需要注意的是,企业账号开发的应用不能上线App
Store,适合那些不希望公开发布应用的企业。同样,申请时也需要公司的邓白氏编码(DUNS
Number)。

链接:

发表评论

电子邮件地址不会被公开。 必填项已用*标注