2011-02-27 3 views
10

처음 여기에서 질문합니다. 게시물이 명확하고 샘플 코드가 올바르게 포맷되기를 바랍니다.AVFoundation - Retiming CMSampleBufferRef 비디오 출력

AVFoundation 및 시간 경과 사진을 실험하고 있습니다.

내 의도는 iOS 기기 (내 iPod touch, 버전 4)의 비디오 카메라에서 매 N 번째 프레임을 가져 와서 각 프레임을 파일로 작성하여 시간 경과를 만드는 것입니다. AVCaptureVideoDataOutput, AVAssetWriter 및 AVAssetWriterInput을 사용하고 있습니다.

captureOutput:idOutputSampleBuffer:fromConnection:
에 전달 된 CMSampleBufferRef를 사용하는 경우 문제는 각 프레임의 재생이 원래 입력 프레임 간의 시간 길이라는 것입니다. 1fps의 프레임 속도. 30fps를 얻으려고합니다.

CMSampleBufferCreateCopyWithNewTiming()
을 사용해 보았지만 13 개의 프레임이 파일에 기록 된 후에
captureOutput:idOutputSampleBuffer:fromConnection:
이 호출되는 것을 중지했습니다. 인터페이스가 활성화되어 있으며 캡처를 중지하고 사진 라이브러리에 저장하여 재생할 수있는 버튼을 누를 수 있습니다. 30fps로 원하는대로 재생되는 것처럼 보이지만 13 프레임 만 있습니다.

목표를 30fps로 재생하려면 어떻게해야합니까? 앱이 손실되는 위치와 그 이유를 어떻게 알 수 있습니까?

useNativeTime 플래그를 두어 두 경우 모두 테스트 할 수 있습니다. YES로 설정하면 콜백이 '잃어 버리지 않습니다.'와 같이 관심있는 모든 프레임을 얻습니다. 해당 플래그를 NO로 설정하면 13 프레임 만 처리되고 다시는 해당 메서드로 반환되지 않습니다. 위에서 언급 한 것처럼 두 경우 모두 비디오를 재생할 수 있습니다.

도움 주셔서 감사합니다.

여기서 리 타이밍을 수행하려고합니다.

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection 
{ 
    BOOL useNativeTime = NO; 
    BOOL appendSuccessFlag = NO; 

    //NSLog(@"in captureOutpput sample buffer method"); 
    if(!CMSampleBufferDataIsReady(sampleBuffer)) 
    { 
     NSLog(@"sample buffer is not ready. Skipping sample"); 
     //CMSampleBufferInvalidate(sampleBuffer); 
     return; 
    } 

    if (! [inputWriterBuffer isReadyForMoreMediaData]) 
    { 
     NSLog(@"Not ready for data."); 
    } 
    else { 
     // Write every first frame of n frames (30 native from camera). 
     intervalFrames++; 
     if (intervalFrames > 30) { 
      intervalFrames = 1; 
     } 
     else if (intervalFrames != 1) { 
      //CMSampleBufferInvalidate(sampleBuffer); 
      return; 
     } 

     // Need to initialize start session time. 
     if (writtenFrames < 1) { 
      if (useNativeTime) imageSourceTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); 
      else imageSourceTime = CMTimeMake(0 * 20 ,600); //CMTimeMake(1,30); 
      [outputWriter startSessionAtSourceTime: imageSourceTime]; 
      NSLog(@"Starting CMtime"); 
      CMTimeShow(imageSourceTime); 
     } 

     if (useNativeTime) { 
      imageSourceTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); 
      CMTimeShow(imageSourceTime); 
      // CMTime myTiming = CMTimeMake(writtenFrames * 20,600); 
      // CMSampleBufferSetOutputPresentationTimeStamp(sampleBuffer, myTiming); // Tried but has no affect. 
      appendSuccessFlag = [inputWriterBuffer appendSampleBuffer:sampleBuffer]; 
     } 
     else { 
      CMSampleBufferRef newSampleBuffer; 
      CMSampleTimingInfo sampleTimingInfo; 
      sampleTimingInfo.duration = CMTimeMake(20,600); 
      sampleTimingInfo.presentationTimeStamp = CMTimeMake((writtenFrames + 0) * 20,600); 
      sampleTimingInfo.decodeTimeStamp = kCMTimeInvalid; 
      OSStatus myStatus; 

      //NSLog(@"numSamples of sampleBuffer: %i", CMSampleBufferGetNumSamples(sampleBuffer)); 
      myStatus = CMSampleBufferCreateCopyWithNewTiming(kCFAllocatorDefault, 
                  sampleBuffer, 
                  1, 
                  &sampleTimingInfo, // maybe a little confused on this param. 
                  &newSampleBuffer); 
      // These confirm the good heath of our newSampleBuffer. 
      if (myStatus != 0) NSLog(@"CMSampleBufferCreateCopyWithNewTiming() myStatus: %i",myStatus); 
      if (! CMSampleBufferIsValid(newSampleBuffer)) NSLog(@"CMSampleBufferIsValid NOT!"); 

      // No affect. 
      //myStatus = CMSampleBufferMakeDataReady(newSampleBuffer); // How is this different; CMSampleBufferSetDataReady ? 
      //if (myStatus != 0) NSLog(@"CMSampleBufferMakeDataReady() myStatus: %i",myStatus); 

      imageSourceTime = CMSampleBufferGetPresentationTimeStamp(newSampleBuffer); 
      CMTimeShow(imageSourceTime); 
      appendSuccessFlag = [inputWriterBuffer appendSampleBuffer:newSampleBuffer]; 
      //CMSampleBufferInvalidate(sampleBuffer); // Docs don't describe action. WTF does it do? Doesn't seem to affect my problem. Used with CMSampleBufferSetInvalidateCallback maybe? 
      //CFRelease(sampleBuffer); // - Not surprisingly - “EXC_BAD_ACCESS” 
     } 

     if (!appendSuccessFlag) 
     { 
      NSLog(@"Failed to append pixel buffer"); 
     } 
     else { 
      writtenFrames++; 
      NSLog(@"writtenFrames: %i", writtenFrames); 
      } 
    } 

    //[self displayOuptutWritterStatus]; // Expect and see AVAssetWriterStatusWriting. 
} 

내 설정 루틴.

- (IBAction) recordingStartStop: (id) sender 
{ 
    NSError * error; 

    if (self.isRecording) { 
     NSLog(@"~~~~~~~~~ STOPPING RECORDING ~~~~~~~~~"); 
     self.isRecording = NO; 
     [recordingStarStop setTitle: @"Record" forState: UIControlStateNormal]; 

     //[self.captureSession stopRunning]; 
     [inputWriterBuffer markAsFinished]; 
     [outputWriter endSessionAtSourceTime:imageSourceTime]; 
     [outputWriter finishWriting]; // Blocks until file is completely written, or an error occurs. 
     NSLog(@"finished CMtime"); 
     CMTimeShow(imageSourceTime); 

     // Really, I should loop through the outputs and close all of them or target specific ones. 
     // Since I'm only recording video right now, I feel safe doing this. 
     [self.captureSession removeOutput: [[self.captureSession outputs] objectAtIndex: 0]]; 

     [videoOutput release]; 
     [inputWriterBuffer release]; 
     [outputWriter release]; 
     videoOutput = nil; 
     inputWriterBuffer = nil; 
     outputWriter = nil; 
     NSLog(@"~~~~~~~~~ STOPPED RECORDING ~~~~~~~~~"); 
     NSLog(@"Calling UIVideoAtPathIsCompatibleWithSavedPhotosAlbum."); 
     NSLog(@"filePath: %@", [projectPaths movieFilePath]); 
     if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum([projectPaths movieFilePath])) { 
      NSLog(@"Calling UISaveVideoAtPathToSavedPhotosAlbum."); 
      UISaveVideoAtPathToSavedPhotosAlbum ([projectPaths movieFilePath], self, @selector(video:didFinishSavingWithError: contextInfo:), nil); 
     } 
     NSLog(@"~~~~~~~~~ WROTE RECORDING to PhotosAlbum ~~~~~~~~~"); 
    } 
    else { 
     NSLog(@"~~~~~~~~~ STARTING RECORDING ~~~~~~~~~"); 
     projectPaths = [[ProjectPaths alloc] initWithProjectFolder: @"TestProject"]; 
     intervalFrames = 30; 

     videoOutput = [[AVCaptureVideoDataOutput alloc] init]; 
     NSMutableDictionary * cameraVideoSettings = [[[NSMutableDictionary alloc] init] autorelease]; 
     NSString* key = (NSString*)kCVPixelBufferPixelFormatTypeKey; 
     NSNumber* value = [NSNumber numberWithUnsignedInt: kCVPixelFormatType_32BGRA]; //kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange]; 
     [cameraVideoSettings setValue: value forKey: key]; 
     [videoOutput setVideoSettings: cameraVideoSettings]; 
     [videoOutput setMinFrameDuration: CMTimeMake(20, 600)]; //CMTimeMake(1, 30)]; // 30fps 
     [videoOutput setAlwaysDiscardsLateVideoFrames: YES]; 

     queue = dispatch_queue_create("cameraQueue", NULL); 
     [videoOutput setSampleBufferDelegate: self queue: queue]; 
     dispatch_release(queue); 

     NSMutableDictionary *outputSettings = [[[NSMutableDictionary alloc] init] autorelease]; 
     [outputSettings setValue: AVVideoCodecH264 forKey: AVVideoCodecKey]; 
     [outputSettings setValue: [NSNumber numberWithInt: 1280] forKey: AVVideoWidthKey]; // currently assuming 
     [outputSettings setValue: [NSNumber numberWithInt: 720] forKey: AVVideoHeightKey]; 

     NSMutableDictionary *compressionSettings = [[[NSMutableDictionary alloc] init] autorelease]; 
     [compressionSettings setValue: AVVideoProfileLevelH264Main30 forKey: AVVideoProfileLevelKey]; 
     //[compressionSettings setValue: [NSNumber numberWithDouble:1024.0*1024.0] forKey: AVVideoAverageBitRateKey]; 
     [outputSettings setValue: compressionSettings forKey: AVVideoCompressionPropertiesKey]; 

     inputWriterBuffer = [AVAssetWriterInput assetWriterInputWithMediaType: AVMediaTypeVideo outputSettings: outputSettings]; 
     [inputWriterBuffer retain]; 
     inputWriterBuffer.expectsMediaDataInRealTime = YES; 

     outputWriter = [AVAssetWriter assetWriterWithURL: [projectPaths movieURLPath] fileType: AVFileTypeQuickTimeMovie error: &error]; 
     [outputWriter retain]; 

     if (error) NSLog(@"error for outputWriter = [AVAssetWriter assetWriterWithURL:fileType:error:"); 
     if ([outputWriter canAddInput: inputWriterBuffer]) [outputWriter addInput: inputWriterBuffer]; 
     else NSLog(@"can not add input"); 

     if (![outputWriter canApplyOutputSettings: outputSettings forMediaType:AVMediaTypeVideo]) NSLog(@"ouptutSettings are NOT supported"); 

     if ([captureSession canAddOutput: videoOutput]) [self.captureSession addOutput: videoOutput]; 
     else NSLog(@"could not addOutput: videoOutput to captureSession"); 

     //[self.captureSession startRunning]; 
     self.isRecording = YES; 
     [recordingStarStop setTitle: @"Stop" forState: UIControlStateNormal]; 

     writtenFrames = 0; 
     imageSourceTime = kCMTimeZero; 
     [outputWriter startWriting]; 
     //[outputWriter startSessionAtSourceTime: imageSourceTime]; 
     NSLog(@"~~~~~~~~~ STARTED RECORDING ~~~~~~~~~"); 
     NSLog (@"recording to fileURL: %@", [projectPaths movieURLPath]); 
    } 

    NSLog(@"isRecording: %@", self.isRecording ? @"YES" : @"NO"); 

    [self displayOuptutWritterStatus]; 
} 

답변

3

좀 더 검색하고 읽을 때 나는 작동하는 해결책이 있습니다. 그것이 최선의 방법인지는 모르겠지만, 지금까지는 아주 좋습니다.

내 설정 영역에서 AVAssetWriterInputPixelBufferAdaptor를 설정했습니다. 코드 추가는 다음과 같습니다.

InputWriterBufferAdaptor = [AVAssetWriterInputPixelBufferAdaptor 
      assetWriterInputPixelBufferAdaptorWithAssetWriterInput: inputWriterBuffer 
      sourcePixelBufferAttributes: nil]; 
[inputWriterBufferAdaptor retain]; 

아래 코드를 완전히 이해하려면 설치 방법에이 세 줄이 있어야합니다.

fpsOutput = 30; //Some possible values: 30, 10, 15 24, 25, 30/1.001 or 29.97; 
cmTimeSecondsDenominatorTimescale = 600 * 100000; //To more precisely handle 29.97. 
cmTimeNumeratorValue = cmTimeSecondsDenominatorTimescale/fpsOutput; 

샘플 버퍼의 복사본에 리 타이밍을 적용하는 대신. 이제 다음과 같은 세 줄의 코드가 효율적으로 동일한 작업을 수행합니다. 어댑터의 withPresentationTime 매개 변수를 확인하십시오. 내 맞춤 가치를 전달함으로써, 나는 정확한 타이밍을 얻고 있습니다. AVAssetWriterInputPixelBufferAdaptor.pixelBufferPool 속성의

CVPixelBufferRef myImage = CMSampleBufferGetImageBuffer(sampleBuffer); 
imageSourceTime = CMTimeMake(writtenFrames * cmTimeNumeratorValue, cmTimeSecondsDenominatorTimescale); 
appendSuccessFlag = [inputWriterBufferAdaptor appendPixelBuffer: myImage withPresentationTime: imageSourceTime]; 

사용은 어떤 이득이있을 수 있습니다,하지만 난 그것을 파악하지 않았습니다.

10

좋아요, 첫 번째 게시물에서 버그를 발견했습니다.

myStatus = CMSampleBufferCreateCopyWithNewTiming(kCFAllocatorDefault, 
               sampleBuffer, 
               1, 
               &sampleTimingInfo, 
               &newSampleBuffer); 

를 사용하여 당신은 AVAssetWriterInputPixelBufferAdaptor 인스턴스의 piexBufferPool와 CVPixelBufferRef를 사용할 때 같은 생각이 진정한 보유하고 CFRelease(newSampleBuffer);

으로 그 균형을해야합니다. appendPixelBuffer: withPresentationTime: 메서드를 호출 한 후에 CVPixelBufferRelease(yourCVPixelBufferRef);을 사용합니다.

다른 사람에게 도움이되기를 바랍니다.

+0

감사합니다. 이로 인해 많은 고통을 덜어 줬습니다. –

+0

당신은 오신 것을 환영합니다. 좋은 소식을 듣고 도움이되었습니다. –

+0

감사합니다. 이것은 정말로 나의 날을 저장했다. 그걸 발견하기 위해 더 많은 땜질을했을 것입니다 ... 당신의 처음이지만 도움이되는 질문. – CipherCom