Я записываю экран и хочу объединить звук с микрофона и звуки из звука приложения в видео с ОДНОЙ стерео-звуковой дорожкой.С настройкой AVAssetWriter
, которая у меня есть, он создает видео файл с ДВУМИ отдельными звуковыми дорожками;одна стерео дорожка для аудио устройства и одна моно дорожка для аудио микрофона.Это бесполезно.
Я также попытался взять полученный видеофайл и восстановить НОВЫЙ видеофайл с отдельными аудио AVAssetTrack
s, объединенными в один, используя функцию AVMutableCompositionTrack
s insertTimeRange(
в качествеувидим ниже.Но это НЕ объединяет дорожки, независимо от того, что я пытаюсь, просто объединяет их (последовательно, не накладывая поверх одной).
Может кто-нибудь сказать мне, как я могу записать дорожки, объединенные в первойместо с 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)
})
}