This code case implementation: iOS end to achieve the capture of H264 coding to write local documents under the document
Camera preparation, coding preparation
1. Start camera head capture
Self. CaptureSession = [[AVCaptureSession alloc]init]; - (void)startCapture {//1. self.captureSession.sessionPreset = AVCaptureSessionPreset640x480; cCaptureQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); cEncodeQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); AVCaptureDevice *inputCamera = nil; NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; for (AVCaptureDevice *device in devices) { if ([device position] == AVCaptureDevicePositionBack) { inputCamera = device; } } self.cCaptureDeviceInput = [[AVCaptureDeviceInput alloc]initWithDevice:inputCamera error:nil]; if ([self.captureSession canAddInput:self.cCaptureDeviceInput]) { [self.captureSession addInput:self.cCaptureDeviceInput]; } self.cCaptureDataOutput = [[AVCaptureVideoDataOutput alloc]init]; [self.cCaptureDataOutput setAlwaysDiscardsLateVideoFrames:NO]; [self.cCaptureDataOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange] forKey:(id)kCVPixelBufferPixelFormatTypeKey]]; [self.cCaptureDataOutput setSampleBufferDelegate:self queue:cCaptureQueue]; if ([self.captureSession canAddOutput:self.cCaptureDataOutput]) { [self.captureSession addOutput:self.cCaptureDataOutput]; } //3. Output connection AVCaptureConnection Captures the connection between a specific capture input pair and a capture output object in the session. AVCaptureConnection *connection = [self.cCaptureDataOutput connectionWithMediaType:AVMediaTypeVideo]; [connection setVideoOrientation:AVCaptureVideoOrientationPortrait]; / / 4. Preview footage self. CPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession: self. CaptureSession]; [self.cPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspect]; [self.cPreviewLayer setFrame:self.view.bounds]; [self.view.layer addSublayer:self.cPreviewLayer]; / / 5. Set the directory file is written to the nsstrings * filePath = [NSHomeDirectory () stringByAppendingPathComponent: @ "/ Documents/video. H264"]. [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; BOOL createFile = [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil]; if (! createFile) { NSLog(@"create file failed"); }else { NSLog(@"create file success"); } NSLog(@"filePaht = %@",filePath); fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath]; //6. Initialize videoToolbBox [self initVideoToolBox]; [self.captureSession startRunning]; } // stopCapture - (void)stopCapture {[self.captureSession stopRunning]; [self.cPreviewLayer removeFromSuperlayer]; [self endVideoToolBox]; [fileHandle closeFile]; fileHandle = NULL; }Copy the code
2. Initialize videoToolBox
-(void)initVideoToolBox { dispatch_sync(cEncodeQueue, ^{ frameID = 0; int width = 480, height = 640; NULL default allocator 2. Width 3. Height resolution in pixels 4. NULL encoding specification, automatically selected by videoToolBox 6. NULL source pixel cache 7. NULL default allocation of compressed data allocator 8. Callback: function pointer to function name didCompressH264 9. Bridging the self 10. Coding session variable * / OSStatus status = VTCompressionSessionCreate (NULL, width, height, kCMVideoCodecType_H264, NULL, NULL, NULL, didCompressH264, (__bridge void * _Nullable)(self), &cEncodeingSession); NSLog(@"H264:VTCompressionSessionCreate %d", (int)status); // 0 : noErr = 0 if (status ! = noErr) { NSLog(@"H264:unable to creat H264 Session"); return; } /* Step 2: Set the encoding parameters * / / / 1.1. Real-time encoding parameters setting, real-time encoding output (avoid delay) VTSessionSetProperty (cEncodeingSession kVTCompressionPropertyKey_RealTime, kCFBooleanTrue); / / 1.2. Set ProfileLevel VTSessionSetProperty (cEncodeingSession kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_AutoLevel); // set key int frameInterval = 10; CFNumberRef frameIntervalRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &frameInterval); / / type conversion int - > CFNumberRef VTSessionSetProperty (cEncodeingSession kVTCompressionPropertyKey_MaxKeyFrameInterval, frameIntervalRef); //1.4. set the expected frame rate, not the actual frame rate, set the key frame GOPSize interval, the gop is too small will blur int FPS = 10; CFNumberRef fpsRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &fps); VTSessionSetProperty(cEncodeingSession, kVTCompressionPropertyKey_ExpectedFrameRate, fpsRef); BPS int bitRate = width * height * 3 * 4 * 8; (width * height * 3) * 4, CFNumberRef bitRateRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &bitRate); VTSessionSetProperty(cEncodeingSession, kVTCompressionPropertyKey_AverageBitRate, bitRateRef); / / the second step Prepare coding VTCompressionSessionPrepareToEncodeFrames (cEncodeingSession); // //AVFoundation captures the data only when the encoding is in the output cmSampleBuffer}); } / / end VideoToolBox - (void) endVideoToolBox {VTCompressionSessionCompleteFrames (cEncodeingSession kCMTimeInvalid); VTCompressionSessionInvalidate(cEncodeingSession); CFRelease(cEncodeingSession); cEncodeingSession = NULL; }Copy the code
The above two steps of camera preparation and coding preparation are basically completed. The next step is to receive sampleBuffer and encode
3. Receive sampleBuffer for encoding
#pragma mark - AVCaptureVideoDataOutputSampleBufferDelegate -(void)captureOutput:(AVCaptureOutput *)captureOutput DidOutputSampleBuffer :(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {// audio/video // CaptureOutput dispatch_sync(cEncodeQueue, ^{[self encode:sampleBuffer]; }); CVImageBufferRef imageBuffer = CVImageBufferRef imageBuffer = CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); CMTime presentationTimeStamp = CMTimeMake(frameID++, 1000); VTEncodeInfoFlags flags; // // Step 3: Start encoding /* Parameters: 1. Encode session variable 2. Unencoded data 3. Display time stamp of obtained sample buffer data. Each timestamp passed to this session is greater than the previous presentation timestamp 4. The display time of this frame when the sample buffer data was retrieved. If there is no time information, you can set KCMTimeInvalid 5. FrameProperties Contains the properties of this frame. OurceFrameRefCom callback refers to the reference value you set for this frame. InfoFlagsOut points to a VTEncodeInfoFlags to accept an encoding operation. If run asynchronously, KVTEncodeInfo_Asynchronous is set; Run synchronously,KVTEncodeInfo_FrameDropped is set; Set the NULL to don't want to receive this information * / OSStatus statusCode = VTCompressionSessionEncodeFrame (cEncodeingSession imageBuffer, presentationTimeStamp, kCMTimeInvalid, NULL, NULL, &flags); if (statusCode ! = noErr) { //error NSLog(@"H264:VTCompressionSessionEncodeFrame fail with %d", statusCode); / / release VTCompressionSessionInvalidate (cEncodeingSession); CFRelease(cEncodeingSession); cEncodeingSession = NULL; return; } NSLog(@"H264:VTCompressionSessionEncodeFrame Success"); // Complete encoding}Copy the code
4. Write the code to the file
// H264 file format SPS + PPS + stream data
// Sequence Parameter Sets
// Picture Parameter Sets (PPS)
// 00 00 00 01
Void didCompressH264(void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer) { if (status ! = 0) { // 0 / noError return; } // Not ready if (! CMSampleBufferDataIsReady(sampleBuffer)) { NSLog(@"H264: CMSampleBufferDataIsReady is not"); return; } // encoder: bridge self ViewController *encoder = (__bridge ViewController *)(outputCallbackRefCon); // check whether the current frame is keyFrame bool keyFrame = CFDictionaryContainsKey(CFArrayGetValueAtIndex(CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true), 0), kCMSampleAttachmentKey_NotSync); // Get the SPS & PPS data only once, save it in the first frame of h264 file // SPS (sample per second times /s), // if (keyFrame) {// Image storage mode, Description of encoder, the (access to the original image storage format) CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription (sampleBuffer); //sps size_t sparameterSetSize, sparameterSetCount; Const Uint8_t *sparameterSet; / / pointer to the data content / / for SPS OSStatus statusCode = CMVideoFormatDescriptionGetH264ParameterSetAtIndex (format, 0, & sparameterSet, &sparameterSetSize, &sparameterSetCount, 0); if (statusCode == noErr) { //pps size_t pparameterSetSize, pparameterSetCount; Const Uint8_t *pparameterSet; / / pointer to the data content / / for SPS OSStatus pStatusCode = CMVideoFormatDescriptionGetH264ParameterSetAtIndex (format, 1, & pparameterSet, &pparameterSetSize, &pparameterSetCount, 0); If (pStatusCode = = noErr) {/ / C data into oc NSData / / for SPS data NSData * SPS = [NSData dataWithBytes: sparameterSet length:sparameterSetSize]; / / to get PPS data NSData * PPS = [NSData dataWithBytes: pparameterSet length: pparameterSetSize]; If (encoder) {// Write the SPS and PPS headers to file [encoder gotSpsPps: SPS: PPS]; }}}} front head has been written to / / / / then write other data CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer (sampleBuffer); // the totalLength of the data, the length of a single data, the storage address size_t length, totalLength; char *dataPointer; / / get the data information OSStatus bStatusCodeRet = CMBlockBufferGetDataPointer (dataBuffer, 0, & length, & totalLength, & dataPointer); // Switch from big-endian mode to small-endian mode /* Big-endian mode high-byte data in front, low-byte data in back 12 34 big-endian */ /* Small-endian mode low-byte data in front, If (bStatusCodeRet == noErr) {size_t bufferOffset = 0; Static const int AVCCHeaderLength = 4; The first 4 bytes of nALu data returned by // // are not 001 startCode, While (bufferOffset < Totallength-avCCheaderLength) {// Uint32_t NALUnitLength = 0; Memcpy (&NALUnitLength, dataPointer + bufferOffset, AVCCHeaderLength); NALUnitLength = CFSwapInt32BigToHost(NALUnitLength); NSData *data = [[NSData alloc] initWithBytes:(dataPointer + bufferOffset + AVCCHeaderLength) length:NALUnitLength]; // Write nALu to a file [encoder gotEncodedData:data isKeyFrame:keyFrame]; // Move to the next NAL unit in the block buffer bufferOffset += AVCCHeaderLength + NALUnitLength; } } } - (void)gotSpsPps:(NSData*)sps pps:(NSData*)pps { // NSLog(@"gotSpsPps length: %d pps: %d", (int)[sps length], (int)[pps length]); const char bytes[] = "\x00\x00\x00\x01"; // Why -1 ends with \0 in C, "\x00\x00\x00\x01\0"; // Why -1 ends with \0 in C, "\x00\x00\x00\x01\0"; -1 size_t length = sizeof(bytes) -1; // (sizeof bytes); NSData *ByteHeader = [NSData dataWithBytes:bytes length:length]; [fileHandle writeData:ByteHeader]; //4 [fileHandle writeData:sps]; [fileHandle writeData:ByteHeader]; // interval [fileHandle writeData: PPS]; } - (void)gotEncodedData:(NSData*)data isKeyFrame:(BOOL)isKeyFrame { NSLog(@"gotEncodedData length: %d ", (int)[data length]); const char bytes[] = "\x00\x00\x00\x01"; Size_t length = sizeof(bytes) -1; NSData *ByteHeader = [NSData dataWithBytes:bytes length:length]; FileHandle writeData:ByteHeader [fileHandle writeData:ByteHeader]; // write h264 data [fileHandle writeData:data]; }Copy the code