ios 뽀개기/objective-c
AVCaptureVideoDataOutput을 이용해서 카메라 만들기 3 - 녹화
인생여희
2019. 4. 17. 20:44
반응형
AVCaptureVideoDataOutput을 이용해서 카메라 만들기 3 - 녹화
로직 작성 순서는 아래와 같다
//녹화 step1
//IDCaptureSessionPipelineViewController클래스 - 녹화,정지 토글 버튼 기능
- (IBAction)toggleRecording:(id)sender
{
NSLog(@"IDCaptureSessionPipelineViewController - toggleRecording 버튼");
if (_recording) {
[_captureSessionCoordinator stopRecording];
} else {
//잠자기 모드 비활성화
[UIApplication sharedApplication].idleTimerDisabled = YES;
//아직 세션이 완전히 시작된것이 아니기 때문에 일단 비활성화.
//IDCaptureSessionCoordinatorDelegate methods 인 - (void)coordinatorDidBeginRecording 에서 다시 활성화 해준다.
self.recordButton.enabled = NO;
self.recordButton.title = @"정지";
[self.captureSessionCoordinator startRecording];
_recording = YES;
}
}
//녹화 step2
//IDCaptureSessionPipelineViewController클래스 - 녹화가 시작되면 버튼 활성화
#pragma mark = IDCaptureSessionCoordinatorDelegate methods
- (void)coordinatorDidBeginRecording:(IDCaptureSessionCoordinator *)coordinator
{
NSLog(@"IDCaptureSessionPipelineViewController coordinatorDidBeginRecording 호출");
_recordButton.enabled = YES;
}
//녹화 step3
//IDCaptureSessionAssetWriterCoordinator 클래스
#pragma mark - Recording State Machine
// call under @synchonized( self ) // 레코딩 상태 변경해주기!
- (void)transitionToRecordingStatus:(RecordingStatus)newStatus error:(NSError *)error
{
RecordingStatus oldStatus = _recordingStatus; //제일 처음에는 0
_recordingStatus = newStatus; // 처음에 1을 넘겨 받는다.
NSLog(@"IDCaptureSessionAssetWriterCoordinator - transitionToRecordingStatus 진입 : oldStatus : %ld , newStatus : %ld" , (long)oldStatus , (long)newStatus);
//녹화버튼 클릭
// oldStatus : 0, newStatus: 1
//oldStatus : 1, newStatus: 2 // 녹화실행
//정지 버튼 클릭
//oldStatus : 2, newStatus: 3
//oldStatus : 3, newStatus: 0
if(newStatus != oldStatus){
//newStatus 가 0, 멈춤 상태일때
if (error && (newStatus == RecordingStatusIdle)) {
NSLog(@"IDCaptureSessionAssetWriterCoordinator - 녹화 멈춤 상태");
} else {
//멈춤상태가 아니면
error = nil;
//RecordingStatusStartingRecording = 1, RecordingStatusRecording = 2
if (oldStatus == RecordingStatusStartingRecording && newStatus == RecordingStatusRecording) {
//녹화 버튼을 -> 정지 한 뒤 -> 활성화 시켜준다.
//delegateCallbackQueue는 부모 클래스인 IDCaptureSessionCoordinator에서 가지고 있고
//IDCaptureSessionPipelineViewController 클래스에서 dispatch_get_main_queue()를 이미 넣어주었다.
dispatch_async(self.delegateCallbackQueue, ^{
@autoreleasepool{
[self.delegate coordinatorDidBeginRecording:self];
}
});
} else if(oldStatus == RecordingStatusStoppingRecording && newStatus == RecordingStatusIdle){
NSLog(@"IDCaptureSessionAssetWriterCoordinator - 녹화 정지 상태");
}
}
}
}
//녹화 step4
//IDAssetWriterCoordinator 클래스
-(instancetype)initWithURL:(NSURL *)URL
{
if (!URL) {
return nil;
}
self = [super init];
if (self) {
NSLog(@"IDAssetWriterCoordinator - initWithURL 진입");
_writingQueue = dispatch_queue_create("com.example.assetwriter.writing", DISPATCH_QUEUE_SERIAL);
_videoTrackTransform = CGAffineTransformMakeRotation(M_PI_2); // 세로 모드
_URL = URL;
}
return self;
}
//녹화 step5
//IDAssetWriterCoordinator 클래스
//IDCaptureSessionAssetWriterCoordinator 클래스에서 넘겨준 오디오 관련 CMFormatDescriptionRef(output), NSDictionary 값 변수에 셋팅
- (void)addAudioTrackWithSourceFormatDescription:(CMFormatDescriptionRef)formatDescription settings:(NSDictionary *)audioSettings
{
NSLog(@"IDAssetWriterCoordinator - addAudioTrackWithSourceFormatDescription 진입");
//formatDescription 가 없다면
if ( formatDescription == NULL ) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"NULL format description" userInfo:nil];
return;
}
@synchronized(self)
{
//WriterStatusIdle = 0
if (_status != WriterStatusIdle) {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Cannot add tracks while not idle" userInfo:nil];
return;
}
if ( _audioTrackSourceFormatDescription ) {
NSLog(@"IDAssetWriterCoordinator - Cannot add more than one audio track");
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Cannot add more than one audio track" userInfo:nil];
return;
}
_audioTrackSourceFormatDescription = (CMFormatDescriptionRef)CFRetain( formatDescription );
_audioTrackSettings = [audioSettings copy];
}
}
//녹화 step6
//IDAssetWriterCoordinator 클래스
//IDCaptureSessionAssetWriterCoordinator 클래스에서 넘겨준 오디오 관련 CMFormatDescriptionRef(output), NSDictionary 값 변수에 셋팅
- (void)addVideoTrackWithSourceFormatDescription:(CMFormatDescriptionRef)formatDescription settings:(NSDictionary *)videoSettings
{
NSLog(@"IDAssetWriterCoordinator - addVideoTrackWithSourceFormatDescription 진입");
if ( formatDescription == NULL ){
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"NULL format description" userInfo:nil];
return;
}
@synchronized( self )
{
if (_status != WriterStatusIdle){
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Cannot add tracks while not idle" userInfo:nil];
return;
}
if(_videoTrackSourceFormatDescription ){
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Cannot add more than one video track" userInfo:nil];
return;
}
_videoTrackSourceFormatDescription = (CMFormatDescriptionRef)CFRetain( formatDescription );
_videoTrackSettings = [videoSettings copy];
}
}
//녹화 step7
//IDAssetWriterCoordinator 클래스
- (void)setDelegate:(id<IDAssetWriterCoordinatorDelegate>)delegate callbackQueue:(dispatch_queue_t)delegateCallbackQueue
{
NSLog(@"IDAssetWriterCoordinator - setDelegate <IDAssetWriterCoordinatorDelegate> 진입");
NSLog(@"IDAssetWriterCoordinator - setDelegate 진입");
if ( delegate && ( delegateCallbackQueue == NULL ) ) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Caller must provide a delegateCallbackQueue" userInfo:nil];
}
@synchronized( self )
{
_delegate = delegate;
if ( delegateCallbackQueue != _delegateCallbackQueue ) {
_delegateCallbackQueue = delegateCallbackQueue;
}
}
}
//녹화 step8
//IDCaptureSessionAssetWriterCoordinator 클래스
#pragma mark - IDAssetWriterCoordinatorDelegate methods
//녹화 시작 버튼 클릭했을때 - 쓰기 준비!!
- (void)writerCoordinatorDidFinishPreparing:(IDAssetWriterCoordinator *)coordinator
{
NSLog(@"IDCaptureSessionAssetWriterCoordinator - writerCoordinatorDidFinishPreparing 진입 :");
@synchronized(self)
{
if(_recordingStatus != RecordingStatusStartingRecording){
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Expected to be in StartingRecording state" userInfo:nil];
return;
}
// 2
[self transitionToRecordingStatus:RecordingStatusRecording error:nil];
}
}
//녹화 step9
//IDCaptureSessionAssetWriterCoordinator 클래스
//녹화 시작 후 쓰기중에 오류발생 했을때!!
- (void)writerCoordinator:(IDAssetWriterCoordinator *)recorder didFailWithError:(NSError *)error
{
NSLog(@"IDCaptureSessionAssetWriterCoordinator - didFailWithError 진입 :");
@synchronized( self ) {
self.assetWriterCoordinator = nil;
[self transitionToRecordingStatus:RecordingStatusIdle error:error];
}
}
//녹화 step10
//IDAssetWriterCoordinator 클래스
// call under @synchonized( self )
- (void)transitionToStatus:(WriterStatus)newStatus error:(NSError *)error
{
NSLog(@"IDAssetWriterCoordinator - transitionToStatus 진입");
BOOL shouldNotifyDelegate = NO;
// 처음에 준비할때 는 1
if (newStatus != _status) {
if((newStatus == WriterStatusFinished) || (newStatus == WriterStatusFailed)){
shouldNotifyDelegate = YES;
//asset writer과 인풋을 해체하기 전에 , 더 이상 샘플 버퍼가 없는지 확인!
dispatch_async(_writingQueue, ^{
self->_assetWriter = nil;
self->_videoInput = nil;
self -> _audioInput = nil;
//상태가 실패면 임시 저장 파일에서 삭제
if (newStatus == WriterStatusFailed) {
[[NSFileManager defaultManager] removeItemAtURL:self->_URL error:NULL];
}
});//dispatch_async end -
}else if(newStatus == WriterStatusRecording){ //WriterStatusRecording = 2
//녹화상태
shouldNotifyDelegate = YES;
}
_status = newStatus;
}
if (shouldNotifyDelegate && self.delegate) { // delegate = IDAssetWriterCoordinatorDelegate
//IDCaptureSessionAssetWriterCoordinator 클래스에서 콜백큐 넣어준다.
dispatch_async(_delegateCallbackQueue, ^{
@autoreleasepool
{
switch (newStatus) {
case WriterStatusRecording: //2 - 쓰기 준비
[self.delegate writerCoordinatorDidFinishPreparing:self];
break;
case WriterStatusFinished: //5 - 쓰기 종료
[self.delegate writerCoordinatorDidFinishRecording:self];
break;
case WriterStatusFailed: //6
[self.delegate writerCoordinator:self didFailWithError:error];
break;
default:
break;
}
}
});
}
}
//녹화 step11 - (NSDictionary)videoSettings 이 null 일때 호출됨
//IDAssetWriterCoordinator 클래스
- (NSDictionary *)fallbackVideoSettingsForSourceFormatDescription:(CMFormatDescriptionRef)videoFormatDescription
{
NSLog(@"IDAssetWriterCoordinator - fallbackVideoSettingsForSourceFormatDescription 진입");
float bitsPerPixel;
CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(videoFormatDescription);
int numPixels = dimensions.width * dimensions.height;
int bitsPerSecond;
NSLog(@"비디오 셋팅이 제공되지 않아, 기본값으로 셋팅합니다.....");
//SD보다 낮은 해상도는 스트리밍을위한 것이고 낮은 비트 전송률을 사용한다고 가정한다.
if (numPixels < (640 * 480)) {
bitsPerPixel = 4.05; //이 비트율은 AVCaptureSessionPresetMedium 또는 Low로 생성되는 퀄리티와 거의 일치한다.
}
else{
bitsPerPixel = 10.1; //이 비트 전송률은 AVCaptureSessionPresetHigh에서 생성되는 품질과 거의 일치한다.
}
bitsPerSecond = numPixels * bitsPerPixel;
NSDictionary *compressionProperties = @{AVVideoAverageBitRateKey : @(bitsPerSecond),
AVVideoExpectedSourceFrameRateKey : @(30),
AVVideoMaxKeyFrameIntervalKey : @(30) };
return @{
AVVideoCodecKey : AVVideoCodecTypeH264 ,
AVVideoWidthKey : @(dimensions.width),
AVVideoHeightKey : @(dimensions.height),
AVVideoCompressionPropertiesKey : compressionProperties
};
}
//녹화 step11 - 2 (NSDictionary)videoSettings 이 null 일때 호출됨
//IDAssetWriterCoordinator 클래스
- (NSError *)cannotSetupInputError
{
NSLog(@"IDAssetWriterCoordinator - cannotSetupInputError 진입");
NSString *localizedDescription = NSLocalizedString( @"녹화가 시작되지 못했습니다.", nil );
NSString *localizedFailureReason = NSLocalizedString( @"asset writer input 을 셋팅할 수 없습니다.....", nil );
NSDictionary *errorDict = @{ NSLocalizedDescriptionKey : localizedDescription,
NSLocalizedFailureReasonErrorKey : localizedFailureReason };
return [NSError errorWithDomain:@"com.example" code:0 userInfo:errorDict];
}
//녹화 step12
//video input(AVAssetWriterInput) 생성 , assetWriter에 videoInput 추가
//IDAssetWriterCoordinator 클래스
- (BOOL)setupAssetWriterVideoInputWithSourceFormatDescription:(CMFormatDescriptionRef)videoFormatDescription transform:(CGAffineTransform)transform settings:(NSDictionary *)videoSettings error:(NSError **)errorOut
{
NSLog(@"IDAssetWriterCoordinator - setupAssetWriterVideoInputWithSourceFormatDescription 진입");
//비디오 셋팅이 존재하지 않으면 defalt 값을 만들어서 할당해 준다.
if (!videoSettings) {
videoSettings = [self fallbackVideoSettingsForSourceFormatDescription:videoFormatDescription];
}
//AVAssetWriter가 [비디오 셋팅값]을 지원한다면
if ([_assetWriter canApplyOutputSettings:videoSettings forMediaType:AVMediaTypeVideo]) {
//AVAssetWriterInput 에 [비디오 셋팅]과 [비디오 포멧 설명]을 넣고 초기화, 생성 해준다.
_videoInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo outputSettings:videoSettings sourceFormatHint:videoFormatDescription];
_videoInput.expectsMediaDataInRealTime = YES;
_videoInput.transform = transform;
//AVAssetWriter 에 AVAssetWriterInput을 넣어준다.
if ([_assetWriter canAddInput:_videoInput]) {
[_assetWriter addInput:_videoInput];
}else{
if (errorOut) {
*errorOut = [self cannotSetupInputError];
}
return NO;
}
}else{
if (errorOut) {
*errorOut = [self cannotSetupInputError];
}
return NO;
}
return YES;
}
//녹화 step12 - 2
//audio input(AVAssetWriterInput) 생성 , assetWriter에 videoInput 추가
//IDAssetWriterCoordinator 클래스
- (BOOL)setupAssetWriterAudioInputWithSourceFormatDescription:(CMFormatDescriptionRef)audioFormatDescription settings:(NSDictionary *)audioSettings error:(NSError **)errorOut
{
NSLog(@"IDAssetWriterCoordinator - setupAssetWriterAudioInputWithSourceFormatDescription 진입");
//audioSettings이 존재하지 않다면..
if (!audioSettings) {
audioSettings = @{AVFormatIDKey : @(kAudioFormatMPEG4AAC)};
}
//assetWriter 가 [오디오 셋팅]을 지원하는지 확인
if ([_assetWriter canApplyOutputSettings:audioSettings forMediaType:AVMediaTypeAudio]) {
_audioInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:audioSettings sourceFormatHint:audioFormatDescription];
_audioInput.expectsMediaDataInRealTime = YES;
//assetWriter 에 [오디오 인풋] 삽입
if ([_assetWriter canAddInput:_audioInput]) {
[_assetWriter addInput:_audioInput];
}else {
if (errorOut ) {
*errorOut = [self cannotSetupInputError];
}
return NO;
}
}else
{
if (errorOut) {
*errorOut = [self cannotSetupInputError];
}
return NO;
}
return YES;
}
//녹화 step13
- (void)prepareToRecord
{
NSLog(@"IDAssetWriterCoordinator - prepareToRecord 진입");
@synchronized( self )
{
if (_status != WriterStatusIdle){ //WriterStatusIdle = 0
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"예외: 이미 준비가 완료되었습니다, 다시 준비 할 수 없습니다." userInfo:nil];
return;
}
// 1
[self transitionToStatus:WriterStatusPreparingToRecord error:nil];
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
NSError *error = nil;
// AVAssetWriter 은 이미 존재하고 있는 파일 경로에 덮어쓰기 할 수 없다..
[[NSFileManager defaultManager] removeItemAtURL:self->_URL error:NULL];
//assetWriter 초기화
self->_assetWriter = [[AVAssetWriter alloc] initWithURL:self->_URL fileType:AVFileTypeQuickTimeMovie error:&error];
// assetWriter 생성하고 Writeinputs 넣기
if (!error && self->_videoTrackSourceFormatDescription ) {
[self setupAssetWriterVideoInputWithSourceFormatDescription:self->_videoTrackSourceFormatDescription transform:self->_videoTrackTransform settings:self->_videoTrackSettings error:&error];
}
// assetWriter 생성하고 Writeinputs 넣기
if (!error && self->_audioTrackSourceFormatDescription ) {
[self setupAssetWriterAudioInputWithSourceFormatDescription:self->_audioTrackSourceFormatDescription settings:self->_audioTrackSettings error:&error];
}
//AVAssetWriter 시작!
if (!error) {
BOOL success = [self -> _assetWriter startWriting];
if (!success) {
error = self -> _assetWriter.error;
}
}
@synchronized(self)
{
if (error) {
//6번
[self transitionToStatus:WriterStatusFailed error:error];
}else{
// 2 = 레코딩 시작 상태로 변경
[self transitionToStatus:WriterStatusRecording error:nil];
}
}
});
}
//녹화 step14
#pragma mark - Recording
//IDCaptureSessionAssetWriterCoordinator 클래스 (자식)
- (void)startRecording
{
@synchronized(self)
{
// 처음 상태 값 : _recordingStatus : 0 , RecordingStatusIdle = 0
NSLog(@"IDCaptureSessionAssetWriterCoordinator - startRecording 진입 : _recordingStatus : %ld , RecordingStatusIdle : %ld" , (long)_recordingStatus , (long)RecordingStatusIdle);
//값이 다르면 이미 녹화가 되고 있다
if (_recordingStatus != RecordingStatusIdle) {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"이미 녹화되고 있습니다." userInfo:nil];
return;
}
// //레코딩 상태 변경해주기, RecordingStatusStartingRecording = 1 값을 가지고 있다.
[self transitionToRecordingStatus:RecordingStatusStartingRecording error:nil];
}//@synchronized end
//임시 저장 경로 얻기
IDFileManager *fm = [IDFileManager new];
_recordingURL = [fm tempFileURL];
//레코딩 임시저장 경로 셋팅
self.assetWriterCoordinator = [[IDAssetWriterCoordinator alloc] initWithURL:_recordingURL];
// outputAudioFormatDescription 값이 존재한다면 , 참고 : didOutputSampleBuffer 에서 할당 해줬음
if (_outputAudioFormatDescription != nil) {
[_assetWriterCoordinator addAudioTrackWithSourceFormatDescription:self.outputAudioFormatDescription settings:_audioCompressionSettings];
}
[_assetWriterCoordinator addVideoTrackWithSourceFormatDescription:self.outputVideoFormatDescription settings:_videoCompressionSettings];
//writercallback 큐 생성
dispatch_queue_t callbackQueue = dispatch_queue_create("com.example.capturesession.writercallback", DISPATCH_QUEUE_SERIAL);
[_assetWriterCoordinator setDelegate:self callbackQueue:callbackQueue];
//비동기식이면 recorderDidFinishPreparing : 또는 recorder : didFailWithError :를 호출하면 다시 호출됨.
[_assetWriterCoordinator prepareToRecord];
}
//녹화 step15 -2
//IDAssetWriterCoordinator 클래스
//최종적으로 버퍼 더하기
- (void)appendSampleBuffer:(CMSampleBufferRef)sampleBuffer ofMediaType:(NSString *)mediaType
{
NSLog(@"IDAssetWriterCoordinator - appendSampleBuffer 진입");
//버퍼 값 유무 체크
if (sampleBuffer == NULL) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"sample 버퍼가 비었어요.." userInfo:nil];
return;
}
// 녹화 준비 체크
@synchronized(self){
if (_status < WriterStatusRecording) {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"녹화 준비가 안됐습니다.." userInfo:nil];
return;
}
}
CFRetain(sampleBuffer);
dispatch_async(_writingQueue, ^{
@autoreleasepool
{
@synchronized(self)
{
// From the client's perspective the movie recorder can asynchronously transition to an error state as the result of an append.
// Because of this we are lenient when samples are appended and we are no longer recording.
// Instead of throwing an exception we just release the sample buffers and return.
//클라이언트 관점에서 보면, 무비 레코더는 버퍼 추가의 결과로 비동기 적으로 오류 상태로 전환 될 수 있다..
//이 때문에 샘플이 추가되고 더 이상 녹음하지 않을 때 오류 관리에 허술해질 수 있다.
// 예외를 던지는 대신 샘플 버퍼를 해제하고 반환한다.
if (self->_status > WriterStatusFinishingRecordingPart1) { //버퍼가 추가되기를 기다리는 중 3번 보다 클때
CFRelease(sampleBuffer);
return ;
}
} //@synchronized end /
if (!self->_haveStartedSession && mediaType == AVMediaTypeVideo) {
//기록!
[self->_assetWriter startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
self -> _haveStartedSession = YES;
}
AVAssetWriterInput *input = (mediaType == AVMediaTypeVideo) ? self-> _videoInput : self -> _audioInput;
if (input.readyForMoreMediaData) {
//************input 준비하고 데이터 버퍼 더하기****************!!!
BOOL success = [input appendSampleBuffer:sampleBuffer];
if (!success) {
NSError *error = self->_assetWriter.error;
@synchronized(self){
NSLog(@"버퍼 기록 중 WriterStatusFailed");
[self transitionToStatus:WriterStatusFailed error:error];
}
}
}else{
NSLog( @"%@ 미디어 데이터를 위한 인풋이 준비되지 않았습니다.., dropping buffer", mediaType );
}
//버퍼 비우기?
CFRelease(sampleBuffer);
}//@autoreleasepool end /
});
}
//녹화 step16 -1
//IDAssetWriterCoordinator 클래스
- (void)appendVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer
{
NSLog(@"IDAssetWriterCoordinator - appendVideoSampleBuffer 진입");
[self appendSampleBuffer:sampleBuffer ofMediaType:AVMediaTypeVideo];
}
//녹화 step16 -2
//IDAssetWriterCoordinator 클래스
- (void)appendAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer
{
NSLog(@"IDAssetWriterCoordinator - appendAudioSampleBuffer 진입");
[self appendSampleBuffer:sampleBuffer ofMediaType:AVMediaTypeAudio];
}
- (void)finishRecording
{
NSLog(@"IDAssetWriterCoordinator - finishRecording 진입");
}
#pragma mark - SampleBufferDelegate methods
////녹화 step17
//IDCaptureSessionAssetWriterCoordinator 클래스
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
NSLog(@"IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :");
CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer);
if (connection == _videoConnection) {
NSLog(@"IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection = videoConnection");
if (self.outputVideoFormatDescription == nil) {
NSLog(@"IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : outputVideoFormatDescription == nil");
// Don't render the first sample buffer.
// This gives us one frame interval (33ms at 30fps) for setupVideoPipelineWithInputFormatDescription: to complete.
// Ideally this would be done asynchronously to ensure frames don't back up on slower devices.
//TODO: outputVideoFormatDescription should be updated whenever video configuration is changed (frame rate, etc.)
//Currently we don't use the outputVideoFormatDescription in IDAssetWriterRecoredSession
/*
첫 번째 샘플 버퍼를 렌더링하지 마십시오.
이 작업을 완료하려면 setupVideoPipelineWithInputFormatDescription :에 대해 하나의 프레임 간격 (30fps에서 33ms)이 필요합니다.
이상적으로 이것은 느린 장치에서 프레임이 백업되지 않도록 비동기 적으로 수행됩니다.
TODO : 비디오 구성이 변경 될 때마다 outputVideoFormatDescription을 업데이트해야합니다 (프레임 속도 등)
현재 우리는 IDAssetWriterRecoredSession에서 outputVideoFormatDescription을 사용하지 않습니다
*/
[self setupVideoPipelineWithInputFormatDescription:formatDescription];
}else{
NSLog(@"IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : outputVideoFormatDescription != nil");
self.outputVideoFormatDescription = formatDescription;
@synchronized(self){
//2 이면
if(_recordingStatus == RecordingStatusRecording){
NSLog(@"IDCaptureSessionAssetWriterCoordinator - [_assetWriterCoordinator appendVideoSampleBuffer:sampleBuffer] 호출 :");
//버퍼 붙이기 *****
[_assetWriterCoordinator appendVideoSampleBuffer:sampleBuffer];
}
}
}
}else if(connection == _audioConnection){
NSLog(@"IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection = audioConnection");
self.outputAudioFormatDescription = formatDescription;
@synchronized( self ) {
//2 면
if(_recordingStatus == RecordingStatusRecording){
//버퍼 붙이기 *****
[_assetWriterCoordinator appendAudioSampleBuffer:sampleBuffer];
}
}
}
}
////녹화 step18
//IDCaptureSessionAssetWriterCoordinator 클래스
//자식
- (void)startRecording
{
@synchronized(self)
{
// 처음 상태 값 : _recordingStatus : 0 , RecordingStatusIdle = 0
NSLog(@"IDCaptureSessionAssetWriterCoordinator - startRecording 진입 : _recordingStatus : %ld , RecordingStatusIdle : %ld" , (long)_recordingStatus , (long)RecordingStatusIdle);
//값이 다르면 이미 녹화가 되고 있다
if (_recordingStatus != RecordingStatusIdle) {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"이미 녹화되고 있습니다." userInfo:nil];
return;
}
// //레코딩 상태 변경해주기, RecordingStatusStartingRecording = 1 값을 가지고 있다.
[self transitionToRecordingStatus:RecordingStatusStartingRecording error:nil];
}//@synchronized end
//임시 저장 경로 얻기
IDFileManager *fm = [IDFileManager new];
_recordingURL = [fm tempFileURL];
//레코딩 임시저장 경로 셋팅
self.assetWriterCoordinator = [[IDAssetWriterCoordinator alloc] initWithURL:_recordingURL];
// outputAudioFormatDescription 값이 존재한다면 , 참고 : didOutputSampleBuffer 에서 할당 해줬음
if (_outputAudioFormatDescription != nil) {
[_assetWriterCoordinator addAudioTrackWithSourceFormatDescription:self.outputAudioFormatDescription settings:_audioCompressionSettings];
}
[_assetWriterCoordinator addVideoTrackWithSourceFormatDescription:self.outputVideoFormatDescription settings:_videoCompressionSettings];
//writercallback 큐 생성
dispatch_queue_t callbackQueue = dispatch_queue_create("com.example.capturesession.writercallback", DISPATCH_QUEUE_SERIAL);
[_assetWriterCoordinator setDelegate:self callbackQueue:callbackQueue];
//비동기식이면 recorderDidFinishPreparing : 또는 recorder : didFailWithError :를 호출하면 다시 호출됨.
[_assetWriterCoordinator prepareToRecord];
}
////녹화 step19
//IDCaptureSessionPipelineViewController 클래스
- (IBAction)toggleRecording:(id)sender
{
NSLog(@"IDCaptureSessionPipelineViewController - toggleRecording 버튼");
if (_recording) {
[_captureSessionCoordinator stopRecording];
} else {
//잠자기 모드 비활성화
[UIApplication sharedApplication].idleTimerDisabled = YES;
//아직 세션이 완전히 시작된것이 아니기 때문에 일단 비활성화.
//IDCaptureSessionCoordinatorDelegate methods 인 - (void)coordinatorDidBeginRecording 에서 다시 활성화 해준다.
self.recordButton.enabled = NO;
self.recordButton.title = @"정지";
[self.captureSessionCoordinator startRecording];
_recording = YES;
}
}
반응형