Быстро записывайте видео AVAssetWriter с помощью микрофона Audio & Device Audio в видео с помощью одной звуковой дорожки AVAssetTrack - PullRequest
1 голос
/ 10 июня 2019

Я записываю экран и хочу объединить звук с микрофона и звуки из звука приложения в видео с ОДНОЙ стерео-звуковой дорожкой.С настройкой 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)

    })

}

1 Ответ

1 голос
/ 10 июня 2019

Использование В этом классе и после использования в функции ниже для начала и остановки записи:

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)")
                }
            })
        }
    }
}
...