Swift AVAssetWriter 将带有麦克风音频和设备音频的视频录制成带有一个音轨 AVAssetTrack 的视频
Posted
技术标签:
【中文标题】Swift AVAssetWriter 将带有麦克风音频和设备音频的视频录制成带有一个音轨 AVAssetTrack 的视频【英文标题】:Swift AVAssetWriter Record Video with Mic Audio AND Device Audio into a Video with ONE Audio Track AVAssetTrack 【发布时间】:2019-06-09 22:44:17 【问题描述】:我正在录制屏幕,我想将麦克风音频和来自应用程序音频的声音组合成一个带有 ONE 立体声音轨的视频。使用我拥有的AVAssetWriter
设置,它会创建一个包含两个单独音轨的视频文件;一个用于设备音频的立体声轨道和一个用于麦克风音频的单声道。这不好。
我还尝试使用 AVMutableCompositionTrack
s insertTimeRange(
函数将生成的视频文件合并为一个新的视频文件,并将单独的音频 AVAssetTrack
s 合并为一个,如下所示。但这不会合并轨道,无论我尝试什么,它只是将它们连接起来(按顺序,而不是相互重叠)。
请有人告诉我如何记录最初与 AVAssetWriter 合并的曲目。或者以后如何将它们合并。网上没有任何东西可以讨论并完成它。很多文章都提到了insertTimeRange(
的使用,但是这个函数连接了音轨。请帮忙。
我目前使用的代码:
func startRecording(withFileName fileName: String, recordingHandler: @escaping (Error?) -> Void)
let sharedRecorder = RPScreenRecorder.shared()
currentlyRecordingURL = URL(fileURLWithPath: CaptureArchiver.filePath(fileName))
guard currentlyRecordingURL != nil else return
desiredMicEnabled = RPScreenRecorder.shared().isMicrophoneEnabled
assetWriter = try! AVAssetWriter(outputURL: currentlyRecordingURL!, fileType: AVFileType.mp4)
let appAudioOutputSettings = [
AVFormatIDKey : kAudioFormatMPEG4AAC,
AVNumberOfChannelsKey : 2,
AVSampleRateKey : 44100.0,
AVEncoderBitRateKey: 192000
] as [String : Any]
let micAudioOutputSettings = [
AVFormatIDKey : kAudioFormatMPEG4AAC,
AVNumberOfChannelsKey : 1,
AVSampleRateKey : 44100.0,
AVEncoderBitRateKey: 192000
] as [String : Any]
let adjustedWidth = ceil(UIScreen.main.bounds.size.width/4)*4
let videoOutputSettings: Dictionary<String, Any> = [
AVVideoCodecKey : AVVideoCodecType.h264,
AVVideoWidthKey : adjustedWidth,
AVVideoHeightKey : UIScreen.main.bounds.size.height
]
let audioInput_app = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: appAudioOutputSettings)
audioInput_app.expectsMediaDataInRealTime = true
if assetWriter.canAdd(audioInput_app) assetWriter.add(audioInput_app)
self.audioInput_app = audioInput_app
let audioInput_mic = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: micAudioOutputSettings)
audioInput_mic.expectsMediaDataInRealTime = true
if assetWriter.canAdd(audioInput_mic) assetWriter.add(audioInput_mic)
self.audioInput_mic = audioInput_mic
let videoInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoOutputSettings)
videoInput.expectsMediaDataInRealTime = true
if assetWriter.canAdd(videoInput) assetWriter.add(videoInput)
self.videoInput = videoInput
RPScreenRecorder.shared().startCapture(handler: [unowned self] (sample, bufferType, error) in
if CMSampleBufferDataIsReady(sample)
DispatchQueue.main.async [unowned self] in
if self.assetWriter.status == AVAssetWriter.Status.unknown
self.assetWriter.startWriting()
#if DEBUG
let status = self.assetWriter.status
log(self, message: "LAUNCH assetWriter.status[\(status.rawValue)]:\(String(describing: self.readable(status)))")
#endif
self.assetWriter.startSession(atSourceTime: CMSampleBufferGetPresentationTimeStamp(sample))
else if self.assetWriter.status == AVAssetWriter.Status.failed
recordingHandler(error)
return
else
switch bufferType
case .audioApp:
if let audioInput_app = self.audioInput_app
if audioInput_app.isReadyForMoreMediaData audioInput_app.append(sample)
case .audioMic:
if let audioInput_mic = self.audioInput_mic
if audioInput_mic.isReadyForMoreMediaData audioInput_mic.append(sample)
case .video:
if let videoInput = self.videoInput
if videoInput.isReadyForMoreMediaData videoInput.append(sample)
@unknown default:
fatalError("Unknown RPSampleBufferType:\(bufferType)")
) [unowned self] (error) in
recordingHandler(error)
if error == nil && self.desiredMicEnabled == true && RPScreenRecorder.shared().isMicrophoneEnabled == false
self.viewController.mic_cap_denied = true
else
self.viewController.mic_cap_denied = false
func mergeAudioTracksInVideo(_ videoURL: URL, completion: @escaping ((Bool) -> Void))
let sourceAsset = AVURLAsset(url: videoURL)
let sourceVideoTrack: AVAssetTrack = sourceAsset.tracks(withMediaType: AVMediaType.video)[0]
let sourceAudioTrackApp: AVAssetTrack = sourceAsset.tracks(withMediaType: AVMediaType.audio)[0]
let sourceAudioTrackMic: AVAssetTrack = sourceAsset.tracks(withMediaType: AVMediaType.audio)[1]
let comp: AVMutableComposition = AVMutableComposition()
guard let newVideoTrack: AVMutableCompositionTrack = comp.addMutableTrack(withMediaType: AVMediaType.video,
preferredTrackID: kCMPersistentTrackID_Invalid) else
completion(false)
return
newVideoTrack.preferredTransform = sourceVideoTrack.preferredTransform
guard let newAudioTrack: AVMutableCompositionTrack = comp.addMutableTrack(withMediaType: AVMediaType.audio,
preferredTrackID: kCMPersistentTrackID_Invalid) else
completion(false)
return
//THE MIXING //THIS STILL RESULTS IN TWO SEPARATE AUDIO TRACKS //LOOKS LIKE THIS IS MORE ABOUT VOLUME LEVELS
let mix = AVMutableAudioMix()
let audioMixInputParamsMic = AVMutableAudioMixInputParameters()
audioMixInputParamsMic.trackID = sourceAudioTrackMic.trackID
audioMixInputParamsMic.setVolume(1.0, at: CMTime.zero)
let audioMixInputParamsApp = AVMutableAudioMixInputParameters()
audioMixInputParamsApp.trackID = sourceAudioTrackApp.trackID
audioMixInputParamsApp.setVolume(1.0, at: CMTime.zero)
mix.inputParameters.append(audioMixInputParamsMic)
mix.inputParameters.append(audioMixInputParamsApp)
///////
let timeRange: CMTimeRange = CMTimeRangeMake(start: CMTime.zero, duration: sourceAsset.duration)
do
try newVideoTrack.insertTimeRange(timeRange, of: sourceVideoTrack, at: CMTime.zero)
try newAudioTrack.insertTimeRange(timeRange, of: sourceAudioTrackMic, at: CMTime.zero)
try newAudioTrack.insertTimeRange(timeRange, of: sourceAudioTrackApp, at: CMTime.zero)
catch
completion(false)
return
let exporter: AVAssetExportSession = AVAssetExportSession(asset: comp, presetName: AVAssetExportPresetHighestQuality)!
exporter.audioMix = mix
exporter.outputFileType = AVFileType.mp4
exporter.outputURL = videoURL
removeFileAtURLIfExists(url: videoURL)
exporter.exportAsynchronously(completionHandler:
switch exporter.status
case AVAssetExportSession.Status.failed:
#if DEBUG
log(self, message: "1000000000failed \(String(describing: exporter.error))")
#endif
case AVAssetExportSession.Status.cancelled:
#if DEBUG
log(self, message: "1000000000cancelled \(String(describing: exporter.error))")
#endif
case AVAssetExportSession.Status.unknown:
#if DEBUG
log(self, message: "1000000000unknown\(String(describing: exporter.error))")
#endif
case AVAssetExportSession.Status.waiting:
#if DEBUG
log(self, message: "1000000000waiting\(String(describing: exporter.error))")
#endif
case AVAssetExportSession.Status.exporting:
#if DEBUG
log(self, message: "1000000000exporting\(String(describing: exporter.error))")
#endif
default:
#if DEBUG
log(self, message: "1000000000-----Mutable video exportation complete.")
#endif
completion(true)
)
【问题讨论】:
【参考方案1】:在该类中使用,并在以下函数中使用后用于记录开始和停止:
https://gist.github.com/mspvirajpatel/f7e1e258f3c1fff96917d82fa9c4c137
import AVFoundation
import ReplayKit
var rpScreenRecorder = RPScreenRecorder.shared()
var rpScreenWriter = RPScreenWriter()
func startRecord()
rpScreenRecorder.isMicrophoneEnabled = true
rpScreenRecorder.startCapture(handler: cmSampleBuffer, rpSampleBufferType, error in
if let error = error
else
self.rpScreenWriter.writeBuffer(cmSampleBuffer, rpSampleType: rpSampleBufferType)
) error in
func stopRecording()
rpScreenRecorder.stopCapture error in
if let error = error
else
self.rpScreenWriter.finishWriting(completionHandler: url, error in
if let url = url
print("\(url)")
)
【讨论】:
音频没有与屏幕一起录制。 您好@NamanVaishnav 您找到任何解决方案了吗?因为我面临同样的问题。录制屏幕时未录制音频。 是的@DikenShah,确保您的设备未处于静音模式。而且在 ios 12 中也无法使用。 非常感谢@NamanVaishnav 你让我开心。它运行良好。有时我收到“录制因多任务处理和内容调整大小而中断”错误,并且在不重新启动设备的情况下,此问题无法解决。你遇到过这样的问题吗? @DikenShah 是的,请确保在结束录制会话后,下一个后续会话应在 4 到 5 秒后开始。而且它应该记录至少 4-5 秒...避免立即采取行动...以上是关于Swift AVAssetWriter 将带有麦克风音频和设备音频的视频录制成带有一个音轨 AVAssetTrack 的视频的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 AVAssetWriter 在 ios 中写入 AAC 音频?
将关于录制麦克风信号的旧 swift 代码行翻译为 swift 4.2
Swift 3:如何在使用 AVFoundation 录制视频期间将麦克风静音/取消静音