使用 AVCaptureSession 和 AVAssetWriter 在翻转相机时无缝录制音频
Posted
技术标签:
【中文标题】使用 AVCaptureSession 和 AVAssetWriter 在翻转相机时无缝录制音频【英文标题】:Seamless audio recording while flipping camera, using AVCaptureSession & AVAssetWriter 【发布时间】:2016-11-08 19:21:42 【问题描述】:我正在寻找一种在前后摄像头之间切换时保持无缝音轨的方法。市场上的许多应用程序都可以做到这一点,例如 SnapChat……
解决方案应使用 AVCaptureSession 和 AVAssetWriter。此外,它不应明确使用 AVMutableComposition,因为在 AVMutableComposition 和 AVCaptureSession ATM 之间存在bug。另外,我负担不起后期处理时间。
目前,当我更改视频输入时,录音会跳过并变得不同步。
我将包含可能相关的代码。
翻转相机
-(void) updateCameraDirection:(CamDirection)vCameraDirection
if(session)
AVCaptureDeviceInput* currentInput;
AVCaptureDeviceInput* newInput;
BOOL videoMirrored = NO;
switch (vCameraDirection)
case CamDirection_Front:
currentInput = input_Back;
newInput = input_Front;
videoMirrored = NO;
break;
case CamDirection_Back:
currentInput = input_Front;
newInput = input_Back;
videoMirrored = YES;
break;
default:
break;
[session beginConfiguration];
//disconnect old input
[session removeInput:currentInput];
//connect new input
[session addInput:newInput];
//get new data connection and config
dataOutputVideoConnection = [dataOutputVideo connectionWithMediaType:AVMediaTypeVideo];
dataOutputVideoConnection.videoOrientation = AVCaptureVideoOrientationPortrait;
dataOutputVideoConnection.videoMirrored = videoMirrored;
//finish
[session commitConfiguration];
样本缓冲区
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
//not active
if(!recordingVideo)
return;
//start session if not started
if(!startedSession)
startedSession = YES;
[assetWriter startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
//Process sample buffers
if (connection == dataOutputAudioConnection)
if([assetWriterInputAudio isReadyForMoreMediaData])
BOOL success = [assetWriterInputAudio appendSampleBuffer:sampleBuffer];
//…
else if (connection == dataOutputVideoConnection)
if([assetWriterInputVideo isReadyForMoreMediaData])
BOOL success = [assetWriterInputVideo appendSampleBuffer:sampleBuffer];
//…
也许调整音频采样时间戳?
【问题讨论】:
我相信即使您切换到前置摄像头,Snapchat 也会使用后置摄像头音频。尝试继续使用后置摄像头的音频? 我想我确实尝试过,但不能肯定地说。好主意,谢谢。 是的,值得一试,我知道我正在修理一部使用前置麦克风的 Siri 无法工作的 iPhone。有趣的是,Snapchat 会录制带有音频的前置视频。祝你好运,让我知道你想出什么我想听的! 谢谢,当我回到这里并寻找可靠的解决方案时会在这里发布。我最终只是重新定时音频,所以有一点差距 atm.. Tinypop 应用程序。 【参考方案1】:嘿,我遇到了同样的问题,发现在切换相机后,下一帧被推得太远了。这似乎在之后的每一帧都发生了变化,从而导致视频和音频不同步。我的解决方案是在切换相机后将每个错位的帧移到正确的位置。
抱歉,我的答案将在 Swift 4.2 中
您必须使用AVAssetWriterInputPixelBufferAdaptor
才能将样本缓冲区附加到指定的演示时间戳。
previousPresentationTimeStamp
是前一帧的演示时间戳,currentPresentationTimestamp
是你猜到的当前帧的演示时间戳。 maxFrameDistance
在测试时运行良好,但您可以根据自己的喜好进行更改。
let currentFramePosition = (Double(self.frameRate) * Double(currentPresentationTimestamp.value)) / Double(currentPresentationTimestamp.timescale)
let previousFramePosition = (Double(self.frameRate) * Double(previousPresentationTimeStamp.value)) / Double(previousPresentationTimeStamp.timescale)
var presentationTimeStamp = currentPresentationTimestamp
let maxFrameDistance = 1.1
let frameDistance = currentFramePosition - previousFramePosition
if frameDistance > maxFrameDistance
let expectedFramePosition = previousFramePosition + 1.0
//print("[mwCamera]: Frame at incorrect position moving from \(currentFramePosition) to \(expectedFramePosition)")
let newFramePosition = ((expectedFramePosition) * Double(currentPresentationTimestamp.timescale)) / Double(self.frameRate)
let newPresentationTimeStamp = CMTime.init(value: CMTimeValue(newFramePosition), timescale: currentPresentationTimestamp.timescale)
presentationTimeStamp = newPresentationTimeStamp
let success = assetWriterInputPixelBufferAdator.append(pixelBuffer, withPresentationTime: presentationTimeStamp)
if !success, let error = assetWriter.error
fatalError(error.localizedDescription)
另外请注意 - 这很有效,因为我保持帧速率一致,因此请确保您在整个过程中完全控制捕获设备的帧速率。
I have a repo using this logic here
【讨论】:
好简洁的回应!感谢分享。如您所知,这是一个非常古老的帖子。我目前不在这方面工作。但如果解决方案有效,我相信它会对看到它的人有所帮助。【参考方案2】:我确实设法找到了一个中间解决方案来解决我在 Woody Jean-louis 解决方案中使用 is repo 找到的同步问题。
结果与 instagram 的结果相似,但效果似乎更好一些。基本上我所做的是防止 assetWriterAudioInput 在切换相机时附加新样本。无法确切知道何时发生这种情况,所以我发现在切换之前和之后,captureOutput 方法每 0.02 秒 +-(最多 0.04 秒)发送视频样本。
知道了这一点,我创建了一个 self.lastVideoSampleDate,每次将视频样本附加到 assetWriterInputPixelBufferAdator 时都会更新它,并且我只允许将音频样本附加到 >assetWriterAudioInput 是日期低于 0.05。
if let assetWriterAudioInput = self.assetWriterAudioInput,
output == self.audioOutput, assetWriterAudioInput.isReadyForMoreMediaData
let since = Date().timeIntervalSince(self.lastVideoSampleDate)
if since < 0.05
let success = assetWriterAudioInput.append(sampleBuffer)
if !success, let error = assetWriter.error
print(error)
fatalError(error.localizedDescription)
let success = assetWriterInputPixelBufferAdator.append(pixelBuffer, withPresentationTime: presentationTimeStamp)
if !success, let error = assetWriter.error
print(error)
fatalError(error.localizedDescription)
self.lastVideoSampleDate = Date()
【讨论】:
【参考方案3】:解决此问题最“稳定”的方法是在切换源时“暂停”录制。
但您也可以使用空白视频和无声音频帧来“填补空白”。 这是我在项目中实现的。
因此,创建布尔值以阻止在切换摄像头/麦克风时附加新 CMSampleBuffer 的功能,并在延迟一段时间后将其重置:
let idleTime = 1.0
self.recordingPaused = true
DispatchQueue.main.asyncAfter(deadline: .now() + idleTime)
self.recordingPaused = false
writeAllIdleFrames()
在writeAllIdleFrames方法中你需要计算你需要写多少帧:
func writeAllIdleFrames()
let framesPerSecond = 1.0 / self.videoConfig.fps
let samplesPerSecond = 1024 / self.audioConfig.sampleRate
let videoFramesCount = Int(ceil(self.switchInputDelay / framesPerSecond))
let audioFramesCount = Int(ceil(self.switchInputDelay / samplesPerSecond))
for index in 0..<max(videoFramesCount, audioFramesCount)
// creation synthetic buffers
recordingQueue.async
if index < videoFramesCount
let pts = self.nextVideoPTS()
self.writeBlankVideo(pts: pts)
if index < audioFramesCount
let pts = self.nextAudioPTS()
self.writeSilentAudio(pts: pts)
如何计算下一个PTS?
func nextVideoPTS() -> CMTime
guard var pts = self.lastVideoRawPTS else return CMTime.invalid
let framesPerSecond = 1.0 / self.videoConfig.fps
let delta = CMTime(value: Int64(framesPerSecond * Double(pts.timescale)),
timescale: pts.timescale, flags: pts.flags, epoch: pts.epoch)
pts = CMTimeAdd(pts, delta)
return pts
如果您还需要创建空白/静音视频/音频缓冲区的代码,请告诉我:)
【讨论】:
嘿,迈克,是的,很想看看创建空白/静音视频/音频缓冲区的代码。真的很挣扎 嘿克里斯蒂安。当然。我有一个包含其中一些实用程序的仓库。看看:github.com/MikeSoftZP/swift-utilities/blob/master/…以上是关于使用 AVCaptureSession 和 AVAssetWriter 在翻转相机时无缝录制音频的主要内容,如果未能解决你的问题,请参考以下文章
如何在 AVCaptureSession 上设置音频采样率?
在使用 AVCaptureMovieFileOutput 保存之前修改 AVCaptureSession
如何记录使用 AVCaptureSession 捕获的第一个和最后一个电影帧的确切时间?