【问题标题】:AVAssetWriter continuous segmentsAVAssetWriter 连续段
【发布时间】:2015-11-21 06:48:17
【问题描述】:

我想录制一系列剪辑,通过视频播放器或ffmpeg -f concat 一起播放时可以无缝播放。

在目前的任何一种情况下,我都会在每个分段连接点收到非常明显的音频打嗝。

我目前的策略是维护 2 个AssetWriter 实例。在每个截止点,我都会启动一个新的编写器,等到它准备好,然后开始给它样本。当视频和音频样本在特定时间点完成时,我会关闭最后一个编写器。

如何修改它以获得连续的剪辑记录?问题的根本原因是什么?

import Foundation
import UIKit
import AVFoundation

class StreamController: UIViewController, AVCaptureAudioDataOutputSampleBufferDelegate, AVCaptureVideoDataOutputSampleBufferDelegate {
    @IBOutlet weak var previewView: UIView!

    var closingVideoInput: AVAssetWriterInput?
    var closingAudioInput: AVAssetWriterInput?
    var closingAssetWriter: AVAssetWriter?

    var currentVideoInput: AVAssetWriterInput?
    var currentAudioInput: AVAssetWriterInput?
    var currentAssetWriter: AVAssetWriter?

    var nextVideoInput: AVAssetWriterInput?
    var nextAudioInput: AVAssetWriterInput?
    var nextAssetWriter: AVAssetWriter?

    var previewLayer: AVCaptureVideoPreviewLayer?
    var videoHelper: VideoHelper?

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

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

        let videoSettings: [String:AnyObject] = [AVVideoCodecKey: AVVideoCodecH264, AVVideoWidthKey: 960, AVVideoHeightKey: 540]
        nextVideoInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: videoSettings)
        nextVideoInput!.expectsMediaDataInRealTime = true
        nextAssetWriter?.addInput(nextVideoInput!)

        let audioSettings: [String:AnyObject] = [
                AVFormatIDKey: NSNumber(unsignedInt: kAudioFormatMPEG4AAC),
                AVSampleRateKey: 44100.0,
                AVNumberOfChannelsKey: 2,
        ]
        nextAudioInput = AVAssetWriterInput(mediaType: AVMediaTypeAudio, outputSettings: audioSettings)
        nextAudioInput!.expectsMediaDataInRealTime = true
        nextAssetWriter?.addInput(nextAudioInput!)

        nextAssetWriter!.startWriting()
    }

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        previewLayer = AVCaptureVideoPreviewLayer(session: videoHelper!.captureSession)
        previewLayer!.frame = self.previewView.bounds
        previewLayer!.videoGravity = AVLayerVideoGravityResizeAspectFill
        if ((previewLayer?.connection?.supportsVideoOrientation) != nil) {
            previewLayer?.connection?.videoOrientation = AVCaptureVideoOrientation.LandscapeRight
        }
        self.previewView.layer.addSublayer(previewLayer!)
    }

    func closeWriter() {
        if videoFinished && audioFinished {
            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.closingVideoInput = nil
            self.closingAssetWriter = nil
            audioFinished = false
            videoFinished = false
        }
    }

    func closingVideoFinished() {
        if closingVideoInput != nil {
            videoFinished = true
            closeWriter()
        }
    }

    func closingAudioFinished() {
        if closingAudioInput != nil {
            audioFinished = true
            closeWriter()
        }
    }

    var closingTime: CMTime = kCMTimeZero
    var audioFinished = false
    var videoFinished = false
    func captureOutput(captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBufferRef, fromConnection connection: AVCaptureConnection!) {
        let sampleTime: CMTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
        if let nextWriter = nextAssetWriter {
            if nextWriter.status.rawValue != 0 {
                print("Switching asset writers at t=\(NSDate().timeIntervalSince1970 - self.startTime)")

                closingAssetWriter = currentAssetWriter
                closingVideoInput = currentVideoInput
                closingAudioInput = currentAudioInput

                currentAssetWriter = nextAssetWriter
                currentVideoInput = nextVideoInput
                currentAudioInput = nextAudioInput

                nextAssetWriter = nil
                nextVideoInput = nil
                nextAudioInput = nil

                closingTime = sampleTime
                currentAssetWriter!.startSessionAtSourceTime(sampleTime)
            }
        }

        if currentAssetWriter != nil {
            if let _ = captureOutput as? AVCaptureVideoDataOutput {
                if (CMTimeCompare(sampleTime, closingTime) < 0) {
                    if closingVideoInput?.readyForMoreMediaData == true {
                        closingVideoInput?.appendSampleBuffer(sampleBuffer)
                    }
                } else {
                    closingVideoFinished()
                    if currentVideoInput?.readyForMoreMediaData == true {
                        currentVideoInput?.appendSampleBuffer(sampleBuffer)
                    }
                }

            } else if let _ = captureOutput as? AVCaptureAudioDataOutput {
                if (CMTimeCompare(sampleTime, closingTime) < 0) {
                    if currentAudioInput?.readyForMoreMediaData == true {
                        currentAudioInput?.appendSampleBuffer(sampleBuffer)
                    }
                } else {
                    closingAudioFinished()
                    if currentAudioInput?.readyForMoreMediaData == true {
                        currentAudioInput?.appendSampleBuffer(sampleBuffer)
                    }
                }
            }
        }
    }

    override func shouldAutorotate() -> Bool {
        return true
    }

    override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
        return [UIInterfaceOrientationMask.LandscapeRight]
    }
}

【问题讨论】:

标签: swift avfoundation avassetwriter


【解决方案1】:

我认为根本原因是视频和音频CMSampleBuffers 代表不同的时间间隔。您需要拆分并加入音频CMSampleBuffers,以使它们无缝地插入您的AVAssetWriter 时间线,这可能应该基于视频演示时间戳。

为什么要改变音频而不是视频?它似乎不对称,但我想这是因为音频具有更高的采样率。

附言实际上创建新的拆分样本缓冲区看起来很吓人。 CMSampleBufferCreate 有很多论点。 CMSampleBufferCopySampleBufferForRange 使用起来可能更简单、更高效。

【讨论】:

  • 是否可以使用 CMSampleBufferCopySampleBufferForRange 在一侧切割我需要的样本,并为下一个 AVAssetWriter 获取剩余部分?我想知道给定的样本本身是否会跨越时间边界,但正确划分样本可能就足够了。看起来样本缓冲区通常(总是?)包含单帧视频,但音频会获得很多样本,因此是不对称的。
  • CMSampleBufferCopySampleBufferForRange 看起来很有希望,视频总是 1 帧。你是对的,样本应该落在帧边界上是没有理由的。但也许 AVFoundation 会为你削减 - 如果你将它附加到当前和下一个作者中,一个悬垂的音频缓冲区可能“只是”工作。或者,也许您应该在音频缓冲区边界或音频样本边界拆分文件,并根据需要重复/剪切视频帧。由于音频的恒定采样率,这将为您提供恒定的文件持续时间。
  • 在音频边界分割可能更简单。我会试试的。实际上需要恒定的文件段长度 +- 秒,但无论如何它都会是一个不错的功能。
  • 听起来你在做一些有趣的事情。文件块让我想起了很久以前我正在开发的即时回放应用程序。当时的硬件还不能同时进行编码/解码。现在可能是一个不同的故事。
  • 分割缓冲区有什么运气吗?我面临着类似的问题stackoverflow.com/questions/43322157/…
猜你喜欢
  • 1970-01-01
  • 2018-07-04
  • 2021-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-23
  • 2017-07-04
  • 2011-07-16
相关资源
最近更新 更多