2015-11-24 4 views
3

Я пытаюсь записывать сегменты аудио и рекомбинировать их, не создавая разрыва в звуке.Запись бесщелевого звука с AVAssetWriter

Конечной целью является также есть видео, но я обнаружил, что сам аудио создает пробелы в сочетании с ffmpeg -f concat -i list.txt -c copy out.mp4

Если я поставил аудио в плейлист HLS, существуют и пробелы, так что я не» t думаю, что это уникально для ffmpeg.

Идея состоит в том, что образцы поступают непрерывно, и мой контроллер направляет образцы в надлежащее AVAssetWriter. Как устранить пробелы в аудио?

import Foundation 
import UIKit 
import AVFoundation 

class StreamController: UIViewController, AVCaptureAudioDataOutputSampleBufferDelegate, AVCaptureVideoDataOutputSampleBufferDelegate { 
    var closingAudioInput: AVAssetWriterInput? 
    var closingAssetWriter: AVAssetWriter? 

    var currentAudioInput: AVAssetWriterInput? 
    var currentAssetWriter: AVAssetWriter? 

    var nextAudioInput: AVAssetWriterInput? 
    var nextAssetWriter: AVAssetWriter? 

    var videoHelper: VideoHelper? 

    var startTime: NSTimeInterval = 0 
    let closeAssetQueue: dispatch_queue_t = dispatch_queue_create("closeAssetQueue", nil); 

    override func viewDidLoad() { 
     super.viewDidLoad() 
     startTime = NSDate().timeIntervalSince1970 
     createSegmentWriter() 
     videoHelper = VideoHelper() 
     videoHelper!.delegate = self 
     videoHelper!.startSession() 
     NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "createSegmentWriter", userInfo: nil, repeats: true) 
    } 

    func createSegmentWriter() { 
     print("Creating segment writer at t=\(NSDate().timeIntervalSince1970 - self.startTime)") 
     let outputPath = OutputFileNameHelper.instance.pathForOutput() 
     OutputFileNameHelper.instance.incrementSegmentIndex() 
     try? NSFileManager.defaultManager().removeItemAtPath(outputPath) 
     nextAssetWriter = try! AVAssetWriter(URL: NSURL(fileURLWithPath: outputPath), fileType: AVFileTypeMPEG4) 
     nextAssetWriter!.shouldOptimizeForNetworkUse = true 

     let audioSettings: [String:AnyObject] = EncodingSettings.AUDIO 
     nextAudioInput = AVAssetWriterInput(mediaType: AVMediaTypeAudio, outputSettings: audioSettings) 
     nextAudioInput!.expectsMediaDataInRealTime = true 
     nextAssetWriter?.addInput(nextAudioInput!) 

     nextAssetWriter!.startWriting() 
    } 

    func closeWriterIfNecessary() { 
     if closing && audioFinished { 
      closing = false 
      audioFinished = false 
      let outputFile = closingAssetWriter?.outputURL.pathComponents?.last 
      closingAssetWriter?.finishWritingWithCompletionHandler() { 
       let delta = NSDate().timeIntervalSince1970 - self.startTime 
       print("segment \(outputFile!) finished at t=\(delta)") 
      } 
      self.closingAudioInput = nil 
      self.closingAssetWriter = nil 
     } 
    } 

    var audioFinished = false 
    var closing = false 

    func captureOutput(captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBufferRef, fromConnection connection: AVCaptureConnection!) { 
     if let nextWriter = nextAssetWriter { 
      if nextWriter.status.rawValue != 0 { 
       if (currentAssetWriter != nil) { 
        closing = true 
       } 

       var sampleTiming: CMSampleTimingInfo = kCMTimingInfoInvalid 
       CMSampleBufferGetSampleTimingInfo(sampleBuffer, 0, &sampleTiming) 

       print("Switching asset writers at t=\(NSDate().timeIntervalSince1970 - self.startTime)") 
       closingAssetWriter = currentAssetWriter 
       closingAudioInput = currentAudioInput 

       currentAssetWriter = nextAssetWriter 
       currentAudioInput = nextAudioInput 

       nextAssetWriter = nil 
       nextAudioInput = nil 

       currentAssetWriter?.startSessionAtSourceTime(sampleTiming.presentationTimeStamp) 
      } 
     } 

     if let _ = captureOutput as? AVCaptureVideoDataOutput { 
     } else if let _ = captureOutput as? AVCaptureAudioDataOutput { 
      captureAudioSample(sampleBuffer) 
     } 

     dispatch_async(closeAssetQueue) { 
      self.closeWriterIfNecessary() 
     } 
    } 

    func printTimingInfo(sampleBuffer: CMSampleBufferRef, prefix: String) { 
     var sampleTiming: CMSampleTimingInfo = kCMTimingInfoInvalid 
     CMSampleBufferGetSampleTimingInfo(sampleBuffer, 0, &sampleTiming) 
     let presentationTime = Double(sampleTiming.presentationTimeStamp.value)/Double(sampleTiming.presentationTimeStamp.timescale) 
     print("\(prefix):\(presentationTime)") 
    } 

    func captureAudioSample(sampleBuffer: CMSampleBufferRef) { 
     printTimingInfo(sampleBuffer, prefix: "A") 
     if (closing && !audioFinished) { 
      if closingAudioInput?.readyForMoreMediaData == true { 
       closingAudioInput?.appendSampleBuffer(sampleBuffer) 
      } 
      closingAudioInput?.markAsFinished() 
      audioFinished = true 
     } else { 
      if currentAudioInput?.readyForMoreMediaData == true { 
       currentAudioInput?.appendSampleBuffer(sampleBuffer) 
      } 
     } 
    } 
} 
+0

Каковы ваши 'audioSettings'? –

+0

статические пусть AUDIO = [ AVFormatIDKey: NSNumber (Целочисленный Беззнаковый: kAudioFormatMPEG4AAC), AVSampleRateKey: 44100,0, AVNumberOfChannelsKey: 2, ] –

ответ

5

с форматами пакетов, как AAC вы имеете молчащие заливку кадров (так называемый энкодер задержка) в начале и в остальных кадрах в конце (когда длина аудио не кратная размеру пакета). В вашем случае это 2112 из них в начале каждого файла. Рамки для прайминга и остатка ломают возможность объединения файлов без их перекодирования, поэтому вы не можете обвинять ffmpeg -c copy в том, что они не создают бесшовный выход.

Я не уверен, где это оставляет вас с видео - очевидно, звук синхронизируется с видео, даже в присутствии прайминга.

Все зависит от того, как вы собираетесь конкатенировать окончательный звук (и в конечном итоге видео). Если вы делаете это самостоятельно, используя AVFoundation, то вы можете обнаружить и объяснить грунтовочный/остаток кадров с использованием

CMGetAttachment(buffer, kCMSampleBufferAttachmentKey_TrimDurationAtStart, NULL) 
CMGetAttachment(audioBuffer, kCMSampleBufferAttachmentKey_TrimDurationAtEnd, NULL) 

в качестве краткосрочного решения, вы можете переключиться на не «пакеты из» получить бесщелевой, concatenatable (с файлами ffmpeg).

например.

AVFormatIDKey: kAudioFormatAppleIMA4, fileType: AVFileTypeAIFC, суффикс ".aifc" или AVFormatIDKey: kAudioFormatLinearPCM, fileType: AVFileTypeWAVE, суффикс ".wav"

P.S. вы можете увидеть заливку & остаточных кадров и размеров пакетов с помощью вездесущего инструмента afinfo.

afinfo chunk.mp4 

Формат данных: 2 ч, 44100 Гц, 'ААС' (0x00000000) 0 бит/канал, 0 байтов/пакетов, 1024 кадров/пакетов, 0 байт/кадр
...
аудио 39596 действительных кадров + 2112 + 276 грунтование остаток = 41984
...

+0

Что невероятно подробный ответ. 'afinfo chunk.mp4' действительно показывает начальные кадры. Я попробую AIFC и WAVE и посмотрю, как это происходит. –

+1

AIFC отлично работает.Мне пришлось переключить ассистента на AVFileTypeQuickTimeMovie, чтобы принять AIFC. –

+0

Прохладный. 'AVFileTypeAIFC' не работает? –

1

Не уверен, если это поможет, но если у вас есть куча MP4s вы можете использовать этот код, чтобы объединить их:

func mergeAudioFiles(audioFileUrls: NSArray, callback: (url: NSURL?, error: NSError?)->()) { 

    // Create the audio composition 
    let composition = AVMutableComposition() 

    // Merge 
    for (var i = 0; i < audioFileUrls.count; i++) { 

     let compositionAudioTrack :AVMutableCompositionTrack = composition.addMutableTrackWithMediaType(AVMediaTypeAudio, preferredTrackID: CMPersistentTrackID()) 

     let asset = AVURLAsset(URL: audioFileUrls[i] as! NSURL) 

     let track = asset.tracksWithMediaType(AVMediaTypeAudio)[0] 

     let timeRange = CMTimeRange(start: CMTimeMake(0, 600), duration: track.timeRange.duration) 

     try! compositionAudioTrack.insertTimeRange(timeRange, ofTrack: track, atTime: composition.duration) 
    } 

    // Create output url 
    let format = NSDateFormatter() 
    format.dateFormat="yyyy-MM-dd-HH-mm-ss" 
    let currentFileName = "recording-\(format.stringFromDate(NSDate()))-merge.m4a" 
    print(currentFileName) 

    let documentsDirectory = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] 
    let outputUrl = documentsDirectory.URLByAppendingPathComponent(currentFileName) 
    print(outputUrl.absoluteString) 

    // Export it 
    let assetExport = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetAppleM4A) 
    assetExport?.outputFileType = AVFileTypeAppleM4A 
    assetExport?.outputURL = outputUrl 

    assetExport?.exportAsynchronouslyWithCompletionHandler({() -> Void in 
     switch assetExport!.status { 
      case AVAssetExportSessionStatus.Failed: 
       callback(url: nil, error: assetExport?.error) 
      default: 
       callback(url: assetExport?.outputURL, error: nil) 
     } 
    }) 

}