使用 AVAssetWriter 录制无缝音频

2024-04-26

我正在尝试录制音频片段并重新组合它们,而不产生音频间隙。

最终的目标是也有视频,但我发现音频本身在与ffmpeg -f concat -i list.txt -c copy out.mp4

如果我将音频放入 HLS 播放列表中,也会有间隙,所以我认为这不是 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)
            }
        }
    }
}

对于像 AAC 这样的数据包格式,您在开始处有静默启动帧(也称为编码器延迟),在结尾处有剩余帧(当您的音频长度不是数据包大小的倍数时)。在你的例子中,每个文件的开头有 2112 个。启动帧和剩余帧破坏了在不转码的情况下连接文件的可能性,因此您不能真正责怪ffmpeg -c copy因为不产生无缝输出。

我不确定这会给您带来视频的什么结果 - 显然,即使存在启动帧,音频也会与视频同步。

这完全取决于您打算如何连接最终的音频(以及最终的视频)。如果您自己使用AVFoundation,那么您可以使用以下方法检测并解释启动/剩余帧

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

作为短期解决方案,您可以切换到非“打包”以获得无缝、可连接(使用 ffmpeg)文件。

e.g.

AVFormatIDKey: kAudioFormatAppleIMA4, fileType: AVFileTypeAIFC、后缀“.aifc”或AVFormatIDKey: kAudioFormatLinearPCM, fileType: AVFileTypeWAVE,后缀“.wav”

附注您可以使用无处不在的命令查看启动和剩余帧以及数据包大小afinfo tool.

afinfo chunk.mp4

数据格式:2通道,44100 Hz,'aac'(0x00000000)0位/通道,0字节/包,1024帧/包,0字节/帧
...
音频 39596 个有效帧 + 2112 个启动帧 + 276 个剩余帧 = 41984
...

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 AVAssetWriter 录制无缝音频 的相关文章

  • 将 NSDecimalNumber 转换为 NSString

    我正在从如下所示的对象中检索密钥 po obj TypeID 3 TypeName Asset 键值的检索方式如下 NSString typeId NSString obj objectForKey TypeID typeId 不是 NSS
  • 返回 Self 的协议函数

    我有一个返回对象副本的协议 P protocol P func copy gt Self 和一个实现 P 的 C 类 class C P func copy gt Self return C 但是 我是否将返回值设置为Self我收到以下错误
  • 在 iPhone 上使用 Xcode Auto Layout 处理不同的图像尺寸

    通过纵向 iPhone 的自动布局 我不希望 iPhone 6 上的游戏角色的 UIImageView 尺寸与 iPhone 4S 中的完全相同 4S 的角色需要更小一些 否则看起来会太大 使用自动布局如何更改 iPhone 之间的图像尺寸
  • Swift 完成处理程序语法

    此代码用于回答此处的问题 如何在 Swift 中发出 HTTP 请求 https stackoverflow com questions 24016142 how to make an http request in swift let u
  • 清空 Firebase DatabaseReference 不会停止观察,这绝对正确吗?

    In the Firebase 太棒了 你做这个 var r1 DatabaseReference nil 然后这个 r1 Database database reference withPath score bucks r1 observ
  • 循环缓冲区录音 iOS:可能吗?

    我的一个客户想要连续录制音频 当他单击 提交 时 他只想提交最后 10 秒的内容 所以他想要连续记录并且只保留最后 x 秒 我认为这需要类似循环缓冲区的东西 但是 作为 iOS 的新手 它看起来像AVAudioRecorder只能写入文件
  • AVPlayerLayer获取图像到UIImageView缓冲区

    我尝试 playerLayer renderInContext UIGraphicsGetCurrentContext 它将显示黑色背景 所以我得到当前播放器项目作为连续的重击图像 它看起来不像视频播放 只是静态图像连续流动 那么还有其他选
  • 进入/退出编辑模式时重绘 UITableViewCell

    我有一个表格视图 其中根据表格是否正在编辑 单元格的构建方式有所不同 具体来说 处于编辑模式时选择样式为无 非编辑模式时选择样式为蓝色 当我从一个单元转换到另一个单元时 我注意到某些单元格没有更新 快速的日志记录告诉我 即使单元格的外观发生
  • 恢复从未付款过的用户的应用内购买

    我正在尝试在我的应用程序中测试应用程序内购买 当我与购买了应用程序内购买的测试用户恢复应用程序内购买时 一切正常 但是当我尝试与用户恢复应用内购买时didn t在我期望框架调用以下方法之前进行应用内购买 paymentQueue resto
  • #include 在 iOS 的 C++ 文件中找不到文件

    我在 iOS 下有一个目标 c c 项目 将其从 OS X 移动 然后出现 文件未找到 错误 include
  • CloudKit 通过 cron 作业发送推送通知?

    我正在创建一个大学餐饮菜单应用程序 在其中我需要根据每日菜单发送推送通知 最初 我计划通过 Heroku 将用户数据存储在数据库中 并使用 cron 作业将数据库中的数据与每日菜单进行比较 并向用户发送适当的通知 然而 在 Cloudkit
  • 如何从 GCD (DispatchQueue) 转换为 Swift async/await?

    我正在关注斯坦福大学的 CS193p 开发 iOS 应用程序在线课程 它使用 Grand Central Dispatch GCD API 来演示多线程 但他们指出 自 WWDC 2021 起 GCD 已大部分被 Swift 新的内置异步
  • BUG - 在 IOS 中没有选择标签的完成按钮

    我正在使用最新的离子并有一个简单的选择标签
  • iOS 8 支持动态链接吗?

    直到 iOS7 之前 出于安全考虑 Apple 都不支持动态链接 开发人员之间的代码重用通常依赖于静态库 这些静态库是作为应用程序可执行文件的一部分构建的 在 iOS8 中引入扩展似乎稍微改变了这一点 因为扩展是单独的可执行文件 扩展及其包
  • 对于 Swift 中的计算器

    只是要警告你 我是 Swift 的新手 我仍在适应它的工作原理 我一直在尝试在课堂上完成这个计算器项目 问题是 我需要练习简化代码 现在 当按下数字按钮时 我将其保存在这样的数组中 IBAction func buttonPressed s
  • TDD iOS 教程 [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 您好 我正在寻找非常好的 iOS TDD 教程 请您帮助我 什么是最好的 iOS TDD 书籍 博客
  • 删除 UICollectionView 中的最后一个单元格会导致崩溃

    您好 我正在使用自定义 UICollectionView https github com SureCase WaterfallCollectionView https github com SureCase WaterfallCollec
  • iOS 中如何清除特定域的 cookie?

    我已经搜索了 StackOverflow 上的几乎所有问题来寻找我的问题的答案 我还没有找到任何有用的链接或教程来说明哪种方式最好清除特定域的 cookie 如果有人可以帮助我 请 我自己找到了解决方案 如果您想删除 UIWebView 中
  • UITextView 动画更改框架不会动画文本重新分配

    我有一个 UITextView 我试图在用户点击按钮时为框架的变化设置动画 基本上 文本视图会变大以适应屏幕 以便可以显示更多文本 然后当用户再次点击按钮时 它会缩小到原始框架 我使用块执行动画 如下所示 if isDisplayingDe
  • UITableview 中的水平和垂直滚动[关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 I want to make a lineup for a festival You can see what I want to a

随机推荐