Отметка времени кадра AVCaptureSession и AVCaptureMovieFileOutput

Я записываю фильм с помощью AVCaptureSession и AVCaptureMovieFileOutput. Я также записываю данные ускорения и пытаюсь согласовать данные ускорения с видео.

Я пытаюсь найти способ узнать время начала записи видеофайла. Я делаю следующее:

currentDate = [NSDate date];
[output startRecordingToOutputFileURL:fileUrl recordingDelegate:self];

Однако, согласно моим тестам, запись видео начинается за 0,12 секунды до вызова startRecordingToOutputFileURL. Я предполагаю, что это потому, что различные видео буферы уже полны данных, которые добавляются в файл.

Есть ли какой-нибудь фактический NSDate первого кадра видео?

2 ответа

У меня была такая же проблема, и я наконец нашел ответ. Я напишу весь код ниже, но недостающая часть, которую я искал, была:

      self.captureSession.masterClock!.time

MasterClock в CaptureSession — это часы, на которых основано относительное время каждого буфера ( presentationTimeStamp).


Полный код и объяснение

Первое, что вам нужно сделать, это преобразовать в AVCaptureVideoDataOutputа также AVCaptureAudioDataOutput. Поэтому убедитесь, что ваш класс реализует AVCaptureVideoDataOutputSampleBufferDelegateа также AVCaptureAudioDataOutputSampleBufferDelegate. У них одна и та же функция, поэтому добавьте ее в свой класс (о реализации я расскажу позже):

          let videoDataOutput = AVCaptureVideoDataOutput()
    let audioDataOutput = AVCaptureAudioDataOutput()

    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        // I will get to this
    }

В сеансе захвата, добавляющем вывод, мой код выглядит так (вы можете изменить видеоориентацию и другие вещи, если хотите)

                  if captureSession.canAddInput(cameraInput)
                && captureSession.canAddInput(micInput)
//                && captureSession.canAddOutput(self.movieFileOutput)
                && captureSession.canAddOutput(self.videoDataOutput)
                && captureSession.canAddOutput(self.audioDataOutput)
            {
                captureSession.beginConfiguration()
                captureSession.addInput(cameraInput)
                captureSession.addInput(micInput)
//                self.captureSession.addOutput(self.movieFileOutput)
                
                let videoAudioDataOutputQueue = DispatchQueue(label: "com.myapp.queue.video-audio-data-output") //Choose any label you want

                self.videoDataOutput.alwaysDiscardsLateVideoFrames = false
                self.videoDataOutput.setSampleBufferDelegate(self, queue: videoAudioDataOutputQueue)
                self.captureSession.addOutput(self.videoDataOutput)

                self.audioDataOutput.setSampleBufferDelegate(self, queue: videoAudioDataOutputQueue)
                self.captureSession.addOutput(self.audioDataOutput)

                if let connection = self.videoDataOutput.connection(with: .video) {
                    if connection.isVideoStabilizationSupported {
                        connection.preferredVideoStabilizationMode = .auto
                    }
                    if connection.isVideoOrientationSupported {
                        connection.videoOrientation = .portrait
                    }
                }
                
                self.captureSession.commitConfiguration()
                
                DispatchQueue.global(qos: .userInitiated).async {
                    self.captureSession.startRunning()
                }
            }

Чтобы записать видео, как вы бы с AVCaptureMovieFileOutput, вы можете использовать AVAssetWriter. Поэтому добавьте в свой класс следующее:

          var videoWriter: AVAssetWriter?
    var videoWriterInput: AVAssetWriterInput?
    var audioWriterInput: AVAssetWriterInput?

    private func setupWriter(url: URL) {
        self.videoWriter = try! AVAssetWriter(outputURL: url, fileType: AVFileType.mov)
        
        self.videoWriterInput = AVAssetWriterInput(mediaType: .video, outputSettings: self.videoDataOutput.recommendedVideoSettingsForAssetWriter(writingTo: AVFileType.mov))
        self.videoWriterInput!.expectsMediaDataInRealTime = true
        self.videoWriter!.add(self.videoWriterInput!)
        
        self.audioWriterInput = AVAssetWriterInput(mediaType: .audio, outputSettings: self.audioDataOutput.recommendedAudioSettingsForAssetWriter(writingTo: AVFileType.mov))
        self.audioWriterInput!.expectsMediaDataInRealTime = true
        self.videoWriter!.add(self.audioWriterInput!)
        
        self.videoWriter!.startWriting()
    }

Каждый раз, когда вы хотите записать, вам сначала нужно настроить записывающее устройство. startWritingФункция на самом деле не начинает запись в файл, а подготавливает модуль записи к тому, что скоро что-то будет записано.

Следующим кодом мы добавим код для запуска или остановки записи. Но обратите внимание, что мне все еще нужно исправить файл stopRecording. stopRecording на самом деле заканчивает запись слишком рано, потому что буфер всегда задерживается. Но, может быть, это не имеет значения для вас.

          var isRecording = false
    var recordFromTime: CMTime?
    var sessionAtSourceTime: CMTime?

    func startRecording(url: URL) {
        guard !self.isRecording else { return }
        self.isRecording = true
        self.sessionAtSourceTime = nil
        self.recordFromTime = self.captureSession.masterClock!.time //This is very important, because based on this time we will start recording appropriately
        self.setupWriter(url: url)
        //You can let a delegate or something know recording has started now
    }
    
    func stopRecording() {
        guard self.isRecording else { return }
        self.isRecording = false
        self.videoWriter?.finishWriting { [weak self] in
            self?.sessionAtSourceTime = nil
            guard let url = self?.videoWriter?.outputURL else { return }
            
            //Notify finished recording and pass url if needed
        }
    }

И, наконец, реализация функции, о которой мы упоминали в начале этого поста:

          private func canWrite() -> Bool {
        return self.isRecording && self.videoWriter != nil && self.videoWriter!.status == .writing
    }
    
    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        guard CMSampleBufferDataIsReady(sampleBuffer), self.canWrite() else { return }
        
        //sessionAtSourceTime is the first buffer we will write to the file
        if self.sessionAtSourceTime == nil {
            //Make sure we start by capturing the videoDataOutput (if we start with the audio the file gets corrupted)
            guard output == self.videoDataOutput else { return }
            //Make sure we don't start recording until the buffer reaches the correct time (buffer is always behind, this will fix the difference in time)
            guard sampleBuffer.presentationTimeStamp >= self.recordFromTime! else { return }
            self.sessionAtSourceTime = sampleBuffer.presentationTimeStamp
            self.videoWriter!.startSession(atSourceTime: sampleBuffer.presentationTimeStamp)
        }
        
        if output == self.videoDataOutput {
            if self.videoWriterInput!.isReadyForMoreMediaData {
                self.videoWriterInput!.append(sampleBuffer)
            }
        } else if output == self.audioDataOutput {
            if self.audioWriterInput!.isReadyForMoreMediaData {
                self.audioWriterInput!.append(sampleBuffer)
            }
        }
    }

Итак, самое главное, что исправляет разницу во времени начала записи и ваш собственный код, это self.captureSession.masterClock!.time. Мы смотрим на относительное время буфера, пока оно не достигнет времени, когда вы начали запись. Если вы хотите также исправить время окончания, просто добавьте переменную recordUntilTimeи проверьте, есть ли в методе didOutput sampleBuffer.

Если я правильно понял ваш вопрос, вы хотите знать отметку времени, когда был записан первый кадр. ты можешь попробовать

CMTime captureStartTime = nil;

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { 

      if !captureStartTime{ 
         captureStartTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
      }
  // do the other things you want
 }
Другие вопросы по тегам