關(guān)鍵詞
AVAsset MP3 PCM 格式 音頻 采樣 AVAssetReader AVAssetWriter 輸出 轉(zhuǎn)換
本文所有示例代碼或Demo可以在此獲确矍:https://github.com/WillieWangWei/SampleCode_MP3ToPCM
如果本文對你有所幫助,請給個Star??
概述
本文僅講解所用技術(shù)的基本概念以及將MP3
轉(zhuǎn)成PCM
格式的實際應(yīng)用谐丢,其他格式的相互轉(zhuǎn)換可以修改示例代碼實現(xiàn)。關(guān)于AVAsset
的其他使用場景可以參考這里港令,音頻相關(guān)的內(nèi)容可以參考這里熔号。
首先了解一些概念:
AVAsset
它包含于AVFoundation
,是一個不可變的抽象類避除,用來代表一個音視頻媒體。一個AVAsset
實例可能包含著一個或多個用來播放或處理的軌道胸嘁,包含但不限于音頻瓶摆、視頻、文本以及相關(guān)說明性宏。但它并不是媒體資源本身群井,可以將它理解為時基媒體的容器。
AVAssetReader
我們可以使用一個AVAssetReader
實例從一個AVAsset
的實例中獲取媒體數(shù)據(jù)毫胜。
AVAssetReaderAudioMixOutput
它是AVAssetReaderOutput
的一個子類书斜,我們可以將一個AVAssetReaderAudioMixOutput
的實例綁定到一個AVAssetReader
實例上,從而得到這個AVAssetReader
實例的asset
的音頻采樣數(shù)據(jù)酵使。
AVAssetWriter
我們可以使用一個AVAssetWriter
實例將媒體數(shù)據(jù)寫入一個新的文件荐吉,并為其指定類型。
AVAssetWriterInput
我們可以將一個AVAssetWriterInput
的實例綁定到一個AVAssetWriter
實例上口渔,從而將媒體采樣包裝成CMSampleBuffer
對象或者元數(shù)據(jù)集合样屠,然后添加到輸出文件的單一通道上。
PCM
模擬音頻信號經(jīng)模數(shù)轉(zhuǎn)換(A/D變換)直接形成的二進制序列,PCM
就是錄制聲音時保存的最原始的聲音數(shù)據(jù)格式痪欲。
WAV
格式的音頻其實就是給PCM
數(shù)據(jù)流加上一段header數(shù)據(jù)悦穿。而WAV
格式有時候之所以被稱為無損格式,就是因為它保存的是原始PCM
數(shù)據(jù)(也跟采樣率
和比特率
有關(guān))业踢。常見音頻格式比如MP3
栗柒,AAC
等等,為了節(jié)約占用空間都進行有損壓縮陨亡。
代碼
這里列舉兩種應(yīng)用場景:
- 將
PCM
數(shù)據(jù)寫入磁盤保存成文件傍衡。 - 將
PCM
數(shù)據(jù)轉(zhuǎn)成NSDate
保存在內(nèi)存中。
這兩種場景都需要先讀取MP3
的數(shù)據(jù)负蠕,然后創(chuàng)建AVAssetReader
和AVAssetReaderAudioMixOutput
實例,所以前半部分的處理邏輯的一樣的倦畅。
通用邏輯
0.導(dǎo)入頭文件
import AVFoundation
1.創(chuàng)建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.創(chuàng)建AVAssetReader實例
func initAssetReader(asset: AVAsset) -> AVAssetReader? {
let assetReader: AVAssetReader
do {
assetReader = try AVAssetReader(asset: asset)
} catch {
print(error)
return nil
}
return assetReader
}
3.配置轉(zhuǎn)碼參數(shù)
var channelLayout = AudioChannelLayout()
memset(&channelLayout, 0, MemoryLayout<AudioChannelLayout>.size)
channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo
let outputSettings = [
AVFormatIDKey : kAudioFormatLinearPCM, // 音頻格式
AVSampleRateKey : 44100.0, // 采樣率
AVNumberOfChannelsKey : 2, // 通道數(shù) 1 || 2
AVChannelLayoutKey : Data.init(bytes: &channelLayout, count: MemoryLayout<AudioChannelLayout>.size), // 聲音效果(立體聲)
AVLinearPCMBitDepthKey : 16, // 音頻的每個樣點的位數(shù)
AVLinearPCMIsNonInterleaved : false, // 音頻采樣是否非交錯
AVLinearPCMIsFloatKey : false, // 采樣信號是否浮點數(shù)
AVLinearPCMIsBigEndianKey : false // 音頻采用高位優(yōu)先的記錄格式
] as [String : Any]
4.創(chuàng)建AVAssetReaderAudioMixOutput實例并綁定到assetReader上
let readerAudioMixOutput = AVAssetReaderAudioMixOutput(audioTracks: asset.tracks, audioSettings: nil)
if !assetReader.canAdd(readerAudioMixOutput) {
print("can't add readerAudioMixOutput")
return
}
assetReader.add(readerAudioMixOutput)
接來下兩種場景的處理邏輯就不一樣了遮糖,請注意區(qū)分。
保存成文件
5.創(chuàng)建一個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(error)
return nil
}
return assetWriter
}
6.創(chuàng)建AVAssetWriterInput實例并綁定到assetWriter上
if !assetWriter.canApply(outputSettings: outputSettings, forMediaType: AVMediaTypeAudio) {
print("can't apply outputSettings")
return
}
let writerInput = AVAssetWriterInput(mediaType: AVMediaTypeAudio, outputSettings: outputSettings)
// 是否讓媒體數(shù)據(jù)保持實時欲账。在此不需要開啟
writerInput.expectsMediaDataInRealTime = false
if !assetWriter.canAdd(writerInput) {
print("can't add writerInput")
return
}
assetWriter.add(writerInput)
7.啟動轉(zhuǎn)碼
assetReader.startReading()
assetWriter.startWriting()
// 開啟session
guard 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
}
}
})
轉(zhuǎn)成NSDate
5.啟動轉(zhuǎn)碼
assetReader.startReading()
var PCMData = Data()
while let nextBuffer = readerAudioMixOutput.copyNextSampleBuffer() {
var audioBufferList = AudioBufferList()
var blockBuffer: CMBlockBuffer?
// CMSampleBuffer 轉(zhuǎn) 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")
注意問題
性能問題
轉(zhuǎn)碼是個很占用CPU資源的計算過程。
具體完成一個轉(zhuǎn)碼過程的時間取決于文件時長芭概、轉(zhuǎn)碼配置赛不、設(shè)備性能等多個條件。這是一個典型的耗時操作罢洲,務(wù)必要做好線程優(yōu)化踢故。另外,可以根據(jù)業(yè)務(wù)邏輯間歇調(diào)用readerAudioMixOutput.copyNextSampleBuffer()
及后續(xù)操作惹苗,降低CPU開銷峰值殿较。
內(nèi)存管理
以本文將MP3
轉(zhuǎn)成PCM
的代碼為例,一個時長4分半左右的MP3
對應(yīng)的PCM
數(shù)據(jù)在55MB左右桩蓉,這些數(shù)據(jù)占用了大量的內(nèi)存或磁盤空間淋纲,注意釋放。你可以通過改變轉(zhuǎn)碼配置參數(shù)outputSettings
來調(diào)整輸出數(shù)據(jù)的大小院究。
在轉(zhuǎn)碼過程中洽瞬,CMSampleBufferRef
、CMBlockBufferRef
的對象在使用后需要調(diào)用CFRelease
銷毀业汰,以防內(nèi)存泄漏伙窃。
其他格式的轉(zhuǎn)換
邏輯是一樣的,你可以修改讀取和輸出的參數(shù)實現(xiàn)蔬胯。注意處理的格式必須是AVFoundation
所包含的对供,可以參考AudioFormatID
這個類以及AVMediaFormat.h
的File format UTIs
。更多音頻處理請參考Apple Developer Library :AVFoundation或第三方框架。
在macOS上轉(zhuǎn)換格式
macOS上可以使用一個強大的音視頻庫FFmpeg产场,它可以幫助你快速轉(zhuǎn)碼出需要的音頻格式作為調(diào)試素材鹅髓。
macOS上編譯FFmpeg
請看這里。
將MP3
轉(zhuǎn)換成PCM
的命令:
ffmpeg mp3 => pcm ffmpeg -i xxx.mp3 -f s16le -ar 44100 -ac 2 xxx.pcm
總結(jié)
本文提供了將MP3
轉(zhuǎn)成PCM
的一種實現(xiàn)京景,中間涉及了一些音頻
窿冯、AVFoundation
和CoreMedia
的知識,這里就不展開了确徙,有問題的同學(xué)可以在文章下留言討論醒串。
本文所有示例代碼或Demo可以在此獲取:https://github.com/WillieWangWei/SampleCode_MP3ToPCM
如果本文對你有所幫助鄙皇,請給個Star??
參考資料:
Apple Developer Library :AVFoundation
http://msching.github.io/blog/2014/07/07/audio-in-ios/