文件音频编辑

音频编辑功能结构

一个音频编辑的基本功能是要包含上面图片中的这些功能的。
解释顺序如下:编辑操作、音频播放、音频保存

编辑操作

1.ActionModel

对于编辑,首先要记录每次编辑所对应的起始点和结束点即beginFrame和endFrame,还要记录一个播放的位置 seekFrame。创建一个model用来记录每次的操作

class WQAudioEditModel: NSObject {
    //开始
    var beginFrame:Int64! = 0
    //结束
    var endFrame:Int64! = 0
    //从哪个位置开始播放
    var seekFrame:Int64! = 0
    
    var fileUrl:URL!
    
    var audioFile:AVAudioFile!

    var editAction:AudioEditActionEnum! = AudioEditActionEnum.none

class func model(bgFrame:Int64,edFrame:Int64,editAction:AudioEditActionEnum,fileUrl:URL) -> WQAudioEditModel {
        let model : WQAudioEditModel = WQAudioEditModel.init()
        model.beginFrame    = bgFrame
        model.endFrame      = edFrame
        model.seekFrame     = bgFrame
        model.editAction    = editAction
        model.fileUrl       = fileUrl
        do {
            try model.audioFile     = AVAudioFile.init(forReading: fileUrl)
        } catch  {
            
        }
        return model
    }
}

beginFrame和endFrame以及seekFrame均为记录原文件音频流位置

2.Action

默认原音频为第一个存入数组

 private func config() {
        self.editModelArray = NSMutableArray.init()
        let firstModel = WQAudioEditModel.model(bgFrame: 0, edFrame: 0, editAction: .none,fileUrl: self.defaultUrl)
        firstModel.endFrame = firstModel.audioFile.length
        self.editModelArray.add(firstModel)
        
    }

拷贝
要创建一个copyArray用来存储拷贝的model,如果音频只有一段,则直接根据beginFrame和endFrame进行读取,多段的情况下要考虑不同情况,这里只说多片的情况,其他类似

这是拷贝之后,存储到copyArray中是三个model,即第1片的后半部分,完整的第2片,第3片的前半部分。

func copyAction(firstFrame:Int64,endFrame:Int64) {
        self.copyArray = NSMutableArray.init()
        let cpLength = endFrame - firstFrame
        
        var realLoc:Int64 = 0
        
        for model in self.editModelArray {
            let aeModel = model as! WQAudioEditModel
            let modelLength = aeModel.endFrame - aeModel.beginFrame
            
            if realLoc + modelLength > firstFrame{//firstFrame在此model中
                if realLoc + modelLength >= endFrame{//endFrame也在model中
                    let cpFirstFrame:Int64 = firstFrame - realLoc + aeModel.beginFrame
                    let cpEndFrame:Int64 = cpFirstFrame + cpLength
                    let cpModel = WQAudioEditModel.model(bgFrame: cpFirstFrame, edFrame: cpEndFrame, editAction: .copy,fileUrl: aeModel.fileUrl)
                    self.copyArray.add(cpModel.reinitModel())
                    break
                }else if(realLoc + modelLength < endFrame){//endframe在下一个model中
                    let cpFirstFrame:Int64 = firstFrame - realLoc + aeModel.beginFrame
                    //先添加firstFrame所在的model的部分
                    self.copyArray.add(WQAudioEditModel.model(bgFrame: cpFirstFrame, edFrame: aeModel.endFrame, editAction: .copy,fileUrl: aeModel.fileUrl))
                    //获取endframe
                    let index = self.editModelArray.index(of: model)
                    let lastArray = self.editModelArray.subarray(with: NSMakeRange(index + 1, self.editModelArray.count - index - 1))
                    
                    for lastModel in lastArray{
                        let lastM = lastModel as! WQAudioEditModel
                        let lastMLength = lastM.endFrame - lastM.beginFrame
                        //找endFrame的所在model
                        if realLoc + lastMLength > endFrame{
                            self.copyArray.add(WQAudioEditModel.model(bgFrame: lastM.beginFrame, edFrame: endFrame - realLoc + lastM.beginFrame, editAction: .copy,fileUrl: aeModel.fileUrl))
                            break;
                        }else{
                            realLoc = realLoc + lastMLength
                            self.copyArray.add(lastM.reinitModel())
                        }
                        
                        
                    }
                    break
                    
                }
                
            }else{
                realLoc = modelLength + realLoc
            }

            
        }
        
    }

粘贴
方式和复制类似,情况主要体现在其实位置在某一段中间,要将该段分为两段,然后插入到中间

即将片段1分为前段和后段两个model

func pasteAction(locFrame:Int64) {
        if self.copyArray.count > 0{
            var realLoc:Int64 = 0
            let forModel = NSArray.init(array: self.editModelArray)
            for model in forModel {
                let aeModel = model as! WQAudioEditModel
                let modelLength = aeModel.endFrame - aeModel.beginFrame
                
                if realLoc + modelLength > locFrame{//寻找位置,位置在model的中部,分割model
                    let index = self.editModelArray.index(of: model)
                    if realLoc == locFrame{//起始点相同,则直接插入到前面
                        let lastarray = self.editModelArray.subarray(with: NSMakeRange(index, self.editModelArray.count - index))
                        self.editModelArray.removeObjects(in: lastarray)
                        self.editModelArray.addObjects(from: self.copyArray as! [Any])
                        self.editModelArray.addObjects(from: lastarray)
                    }else{
                        let newModel1 = WQAudioEditModel.model(bgFrame: aeModel.beginFrame, edFrame: locFrame - realLoc + aeModel.beginFrame, editAction: .none,fileUrl: aeModel.fileUrl)
                        self.editModelArray.replaceObject(at: index, with: newModel1)
                        let lastarray = self.editModelArray.subarray(with: NSMakeRange(index + 1, self.editModelArray.count - index - 1))
                        self.editModelArray.removeObjects(in: lastarray)
                        let newModel2 = WQAudioEditModel.model(bgFrame: locFrame - realLoc + aeModel.beginFrame, edFrame: aeModel.endFrame, editAction: .none,fileUrl: aeModel.fileUrl)
                        self.editModelArray.addObjects(from: self.copyArray as! [Any])
                        self.editModelArray.add(newModel2)
                        self.editModelArray.addObjects(from: lastarray)
                    }
                    
                    
                    break
                }else if realLoc + modelLength == locFrame{//寻找位置,位置在model的尾部,从其后插入位置
                    let index = self.editModelArray.index(of: model)
                    let lastarray = self.editModelArray.subarray(with: NSMakeRange(index + 1, self.editModelArray.count - index - 1))
                    self.editModelArray.removeObjects(in:lastarray)
                    self.editModelArray.addObjects(from: self.copyArray as! [Any])
                    self.editModelArray.addObjects(from: lastarray)
                    
                    break
                }else{
                    realLoc = realLoc + modelLength
                }
                
            }
            
        }
    }

剪切
剪切就是将复制和粘贴的方式进行结合了,只不过是把选中的部分去掉了而已,直接贴代码了哦

func cutAction(firstFrame:Int64,endFrame:Int64) {
        self.copyAction(firstFrame: firstFrame, endFrame: endFrame)
        var realLoc:Int64 = 0
        let forModel = NSArray.init(array: self.editModelArray)
        for model in forModel {
            let aeModel = model as! WQAudioEditModel
            let modelLength = aeModel.endFrame - aeModel.beginFrame
            
            if realLoc + modelLength > firstFrame{//firstFrame在此model中
                let index = self.editModelArray.index(of: model)
                if realLoc + modelLength >= endFrame{//endFrame也在model中
                    if realLoc == firstFrame{//起点相同,直接剪切
                        if realLoc + modelLength != endFrame{
                            let newModel2 = WQAudioEditModel.model(bgFrame: endFrame - realLoc, edFrame: aeModel.endFrame, editAction: .none,fileUrl: aeModel.fileUrl)
                            self.editModelArray.replaceObject(at: index, with: newModel2)
                        }
                    }else{
                        let newModel1 = WQAudioEditModel.model(bgFrame: aeModel.beginFrame, edFrame: firstFrame - realLoc, editAction: .none,fileUrl: aeModel.fileUrl)
                        self.editModelArray.replaceObject(at: index, with: newModel1)
                        if realLoc + modelLength != endFrame{
                            let newModel2 = WQAudioEditModel.model(bgFrame: endFrame - realLoc, edFrame: aeModel.endFrame, editAction: .none,fileUrl: aeModel.fileUrl)
                            self.editModelArray.insert(newModel2, at: index + 1)
                        }
                    }
                    break
                }else if(realLoc + modelLength < endFrame){//endframe在下一个model中
                    let cutFirstFrame:Int64 = firstFrame - realLoc + aeModel.beginFrame
                    let newFirstModel = WQAudioEditModel.model(bgFrame: aeModel.beginFrame, edFrame: cutFirstFrame, editAction: .none,fileUrl: aeModel.fileUrl)
                    self.editModelArray.replaceObject(at: index, with: newFirstModel)
                
                    //获取endframe
                    let index = self.editModelArray.index(of: model)
                    let lastArray = self.editModelArray.subarray(with: NSMakeRange(index+1, self.editModelArray.count - index - 1))
                    for lastModel in lastArray{
                        let lastM = lastModel as! WQAudioEditModel
                        let lastMLength = lastM.endFrame - lastM.beginFrame
                        //找endFrame的所在model
                        if realLoc + lastMLength > endFrame{
                            //创建新的model,去掉截取的部分
                            let newEndModel = WQAudioEditModel.model(bgFrame: endFrame - realLoc + lastM.beginFrame, edFrame: lastM.endFrame, editAction: .none,fileUrl: aeModel.fileUrl)
                            let cutIndex = self.editModelArray.index(of: lastM)
                            self.editModelArray.replaceObject(at: cutIndex, with: newEndModel)
                            break;
                        }else{
                            realLoc = realLoc + lastMLength
                            self.editModelArray.remove(lastModel)
                        }
                        
                        
                    }
                    break
                    
                }
                
            }else{
                realLoc = modelLength + realLoc
            }

            
        }
        
    }

这个时候所有操作过的音频段均保存在editArray数组中,开始准备播放了。

播放

这个时候需要把audioEngine请出来了,引擎主要管理整个过程的流以及流的操作,具体播放我们使用AVAudioPlayerNode进行播放,文件的读取是使用AVAudioFile。
对于播放,我目前未找到能够体现播放进度的属性或者方法,所以选择了个折中的方法,每次播放1024的长度,然后就可以监听进度了(如果有小伙伴知道怎么获得播放进度,请告知我,谢谢)
playnode播放的位置和长度是如下方法

open func scheduleSegment(_ file: AVAudioFile, startingFrame startFrame: AVAudioFramePosition, frameCount numberFrames: AVAudioFrameCount, at when: AVAudioTime?, completionHandler: AVAudioNodeCompletionHandler? = nil)

当这一片播放完之后completionHandler会调用,这个时候就可以去读取下一片继续播放,此时需要判断要播放我们editArray数组中的哪个片段,当数组中的所有片段均播放完毕,则为播放结束

@objc private func playNoti(noti:Notification) {//播放完毕再填充下一部分
        let model = noti.object as! WQAudioEditModel
        if self.playerNode.isPlaying == false{
            model.seekFrame = model.beginFrame
            NotificationCenter.default.removeObserver(self)
            if self.playFinishBlock != nil{
                self.playFinishBlock()
            }
            return
        }
        if(model.seekFrame >= model.endFrame){//结束
            //最后一片结束,查询下一个
            let index =  self.editActionTool.editModelArray.index(of: model)
            if index < self.editActionTool.editModelArray.count - 1{
                model.seekFrame = model.beginFrame
                let nextModel = self.editActionTool.editModelArray.object(at: index + 1) as! WQAudioEditModel
                self.realScheduleSegment(fromFrame: nextModel.seekFrame, length: self.segLength, model: nextModel)
            }else{
                print("nam")
                model.seekFrame = model.beginFrame
                NotificationCenter.default.removeObserver(self)
                if self.playFinishBlock != nil{
                    self.playFinishBlock()
                }
            }
        }else if (model.seekFrame + self.segLength  < model.endFrame){
            
        
            print("\(String(describing: model.seekFrame)),\(String(describing: model.endFrame))")
            self.realScheduleSegment(fromFrame: model.seekFrame, length: self.segLength, model: model)
            
        }else if (model.seekFrame + self.segLength >= model.endFrame ){//最后一片

            self.realScheduleSegment(fromFrame: model.seekFrame, length: model.endFrame - model.seekFrame, model: model)
        }
    }

存储

存储是使用AVAudioFile进行存储

func beginSave() {
        var writeFile:AVAudioFile! = nil
        do {
            writeFile = try AVAudioFile.init(forWriting: URL.init(string: self.fileUrl)!, settings: [AVFormatIDKey:NSNumber.init(value: kAudioFormatLinearPCM),AVNumberOfChannelsKey:NSNumber.init(value: 2),AVSampleRateKey:NSNumber.init(value: self.actionTool.getSampleRate())])
        } catch  {
            
        }
        if writeFile == nil{
            return
        }

        
//
        self.editPlay.stop()
        self.editPlay.audioEngine.pause()
        for model:WQAudioEditModel in self.actionTool.editModelArray as! [WQAudioEditModel]{
            let buffer:AVAudioPCMBuffer = AVAudioPCMBuffer.init(pcmFormat: model.audioFile.processingFormat, frameCapacity: AVAudioFrameCount(model.endFrame - model.beginFrame))!
            model.audioFile.framePosition = model.beginFrame
            do {
                try model.audioFile.read(into: buffer, frameCount: AVAudioFrameCount(model.endFrame - model.beginFrame))
            } catch  {

            }
            do {
                try writeFile.write(from: buffer)
            } catch  {

            }
        }
        print("\(String(describing: self.fileUrl))")
        do {
            try self.editPlay.audioEngine.start()
        } catch  {

        }
    }

至此,保存也结束了。是不是也挺简单的

对于AVAudioEngine,其底层是audiounit,我也是从audiounit看起,其较为c语言化,这一块的内容可以看下EZAudio的源码,内容很全面,存储这一块也可以看下AVAudioEngineOfflineRender,使用的exaudioUnit进行存储,比较简洁。 后来查到苹果推荐使用audioEngine,且功能上同audiounit相差无二,编写也少了很多代码,所以用audiounit编写了一半又跑来搞了下这个,学习过程还是很有趣

猜你喜欢

转载自blog.csdn.net/qq_21743659/article/details/112003354