Отметка времени кадра 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
}