使用 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 录制无缝音频 的相关文章

  • 如何观察UserDefaults的变化?

    我有一个 ObservedObject在我看来 struct HomeView View ObservedObject var station Station var body some View Text self station sta
  • 如何在 Firebase Analytics 事件中报告参数

    我用过Fabric with iOS在此之前 在同一分析事件中报告自定义参数非常容易 如下所示 Answers logCustomEvent withName saved border customAttributes image inde
  • 如何检测 swiftui 中是否存在键盘

    我想知道按下按钮时键盘是否存在 我该怎么做 我已经尝试过 但我没有任何运气 谢谢 使用该协议 KeyboardReadable 你可以符合任何View并从中获取键盘更新 KeyboardReadable协议 import Combine i
  • 如何在 swiftUI (macOS) 中检测按键按下和释放

    除了标题之外没什么可说的 我希望能够在按下按键和释放按键时 在 macOS 上 在 swiftUI 视图中执行操作 在 swiftUI 中是否有任何好的方法可以做到这一点 如果没有 有什么解决方法吗 不幸的是 键盘事件处理是其中一个令人痛苦
  • WKWebView 未打开自定义 URL 方案(js 在新窗口中打开自定义方案链接)

    我有一个WKWebView在我的应用程序中 我不使用UIWeb视图 因为由于某种奇怪的原因 它无法正确打开包含大量 JS 代码的网页 当我点击链接时自定义 url 方案 scm 它确实nothing My code void viewDid
  • 使 iOS 应用程序与 iPhone 6 和 iPhone 6 尺寸兼容 [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我创建了一个应用程序 其中使用 xib 进行布局 目前我使用两种不同的 xib 一种用于iPhone4 320 480 一种用于iPh
  • SwiftUI:动态“列表”中的“切换”在重用时会破坏其布局?

    我试图展现一种动态List行包含Toggle元素 这Toggle最初布局正确 但是当它们滚动进和滚出视图时 即单元格重用时 它们的布局会中断 最小示例代码 import SwiftUI struct SwitchList View var
  • HTML 分页

    有没有html分页的开源项目 我正在为 iPhone 开发一个应用程序 我想在 UIWebView 上显示 HTML 文件 并且不希望用户向下滚动以查看屏幕上未显示的剩余内容 我想在第二个 UIWebView 上显示剩余的内容 我怎样才能做
  • 使用 AVFoundation 裁剪 AVAsset 视频

    我在用AVCaptureMovieFileOutput录制一些视频 我使用显示预览层AVLayerVideoGravityResizeAspectFill稍微放大 我遇到的问题是最终的视频较大 包含预览期间不适合屏幕的额外图像 这是预览和生
  • 对成员“buildBlock()”的引用不明确

    我一直在尝试使用 Swift UI 为 iOS 13 制作一个应用程序 但我不断收到这个奇怪的错误 对成员 buildBlock 的引用不明确 无论我做什么 错误都不会消失 我尝试一次对代码段进行注释 以查看哪一部分可能导致了问题 但唯一有
  • 如何在iOS的Delphi程序中使用IPv6协议

    我尝试在我的移动程序中使用 IPv6 协议 我的服务器位于 NAT 后面的 LAN 内 在服务器上我使用IP端口3000 我已经组织了从路由器端口 45500 到服务器端口 3000 的虚拟服务器 端口转发 在服务器上 我运行 ipconf
  • 从 Mac 命令行访问 iOS 应用程序目录(沙箱)

    我需要使用 Mac 或 Linux 上的命令行 非 GUI 访问 iOS 设备上安装的应用程序的沙箱目录 这有助于开发和测试自动化 将 json 文件放入沙箱中可以让我设置参数 例如额外的调试消息和更小的刷新间隔 像 iFunBox 这样的
  • ios 在后台处理推送通知

    我想保存应用程序处于后台状态时到达的推送通知 我知道关于 void application UIApplication application didReceiveRemoteNotification NSDictionary userIn
  • XCode 4.5 给我“SenTestingKit/SenTestKit.h”文件未找到,但适用于 4.4.1

    我刚刚安装了 XCode 4 5 它在我现有的项目之一上给了我一个 SenTestingKit SenTestingKit h 文件未找到错误 此错误仅发生在 XCode 4 5 中 但它在 4 4 1 上编译正常 我已经检查过SenTes
  • AWS S3 公共对象与私有对象?

    回到 S3 我的存储桶中有图像的 URL 我将在我的应用程序中呈现这些图像 但它们被设置为私有 当我尝试单击该链接时 它显示 访问被拒绝 当我将链接的设置更改为公共时 它会通过 但是我读到公共访问并不是最安全的事情 所以这本质上是一个由两部
  • 将 UIButton 中的图像缩放到 AspectFit?

    我想将图像添加到 UIButton 并且还想缩放图像以适合 UIButton 使图像变小 请告诉我该怎么做 这是我尝试过的 但它不起作用 将图像添加到按钮并使用setContentMode self itemImageButton setI
  • ios 导航 堆栈操作

    我在尝试从 iOS 应用程序操作导航堆栈时遇到问题 或者至少是由于这种操纵而产生的行为 我的情况 我有 3 个 ViewController 控制器a显示多个级别 控制器 b 是游戏视图 控制器 c 是某种分数 显然 我将在控制器 a 中选
  • 企业发行版在 Swift 应用程序中与 iOS8 配合不佳

    我在使用 swift 应用程序在 iOS 8 设备上运行 Enterprise 版本时遇到问题 如果我使用非企业帐户进行代码签名 它似乎工作正常 有人遇到这个问题吗 以下是我在尝试使用企业帐户运行构建以进行协同设计时在 iOS 设备上收到的
  • 在 HTML5 iOS 7 / iOS 8 中显示十进制键盘

    经过几个小时的搜索后 我只是有一个简单的问题 是否有可能在网络浏览器输入字段中显示小数键盘 input type number 只显示数字 但我需要在左下角使用逗号或点 我尝试过任何事情 pattern step等等 但没有显示十进制键盘
  • 设置/覆盖 UICollectionView 中单元格之间的填充

    我有一个 UICollectionView 但在获取单元格之间的填充时遇到了问题 理论上 我应该能够将屏幕除以 4 并且我可以获得包含 4 个图像的单元格大小 完美地占据屏幕宽度 但是 它选择不这样做 相反 它会创建 3 个具有巨大填充的图

随机推荐