preface
The last article briefly introduced the use of AVFoundation. This article will look at how to use VideoToolbox and AudioToolbox to achieve audio and video codec. The video stream format studied is H264 and the audio stream format is AAC. If you are not familiar with these two formats, click here for a quick understanding.
VideoToolbox
VideoToolbox is a low-level framework that provides direct access to hardware codecs. Can provide video compression and decompression services, as well as provide format conversion of images stored in the CoreVideo pixel buffer. First, let’s take a look at the CMSampleBuffer data structure. In the process of video collection, there are the following two data structures
As you can see, the difference is the CMBlockBuffer and CVPixelBuffer.
CVPixelBuffer is unencoded original data, while CMBlockBuffer is encoded H264 raw stream data, as shown below:Let’s implement this process in code
The coding part
Initialize the
#pragma mark - Code
- (void)encodeInit{
OSStatus status = VTCompressionSessionCreate(kCFAllocatorDefault, (uint32_t)_width, (uint32_t)_height, kCMVideoCodecType_H264, NULL, NULL, NULL, encodeCallBack, (__bridge void * _Nullable)(self), &_encoderSession);
if(status ! = noErr) {return; } / / set the real-time encoding status = VTSessionSetProperty (_encoderSession kVTCompressionPropertyKey_RealTime, kCFBooleanTrue); / / set the filter frames status = B VTSessionSetProperty (_encoderSession kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_AutoLevel); CFNumberRef bitRate = (__bridge CFNumberRef)(@(_height*1000)); status = VTSessionSetProperty(_encoderSession, kVTCompressionPropertyKey_AverageBitRate, bitRate); CFArrayRef = (__bridge CFArrayRef)(@[@(_height*1000/4),@(_height*1000*4)]); status = VTSessionSetProperty(_encoderSession, kVTCompressionPropertyKey_DataRateLimits, limits); // set FPS CFNumberRef FPS = (__bridge CFNumberRef)(@(25)); status = VTSessionSetProperty(_encoderSession, kVTCompressionPropertyKey_ExpectedFrameRate, fps); CFNumberRef maxKeyFrameInterval = (__bridge CFNumberRef)(@(25*2)); status = VTSessionSetProperty(_encoderSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, maxKeyFrameInterval); / / ready to code status = VTCompressionSessionPrepareToEncodeFrames (_encoderSession); }Copy the code
VTCompressionSessionCreate argument parsing
/* * allocator: memory allocator: NULL * * width, height: video frame pixel width, if the encoder does not support this width may change * * codecType: encoding type, enumeration * encoderSpecification: Specify a specific encoder. If NULL is filled, the VideoToolBox will automatically select *sourceImageBufferAttributes: Attributes of the source pixel buffer used to create a pixel buffer pool for the source frame. If this parameter has a value, VideoToolBox will create a buffer pool, no buffer pool can be set to NULL. Using VideoToolbox with unallocated pixel buffers may increase the chances of copying image data. * compressedDataAllocator: a memory allocator for compressed data, NULL using the default allocator * outputCallback: a callback function for compressed video output data. This function was on the call VTCompressionSessionEncodeFrame thread asynchronous calls. NULL, call when VTCompressionSessionEncodeFrameWithOutputHandler calls for coding frame. * compressionSessionOut: param outputCallbackRefCon: a custom pointer to a callback function, in which we normally pass self, to retrieve the current class's methods and properties. Encoder handle, Incoming encoder pointer * / VT_EXPORT OSStatus VTCompressionSessionCreate (CM_NULLABLE CFAllocatorRef allocator, int32_t width, int32_t height, CMVideoCodecType codecType, CM_NULLABLE CFDictionaryRef encoderSpecification, CM_NULLABLE CFDictionaryRefsourceImageBufferAttributes, CM_NULLABLE CFAllocatorRef compressedDataAllocator, CM_NULLABLE VTCompressionOutputCallback outputCallback, void * CM_NULLABLE outputCallbackRefCon, CM_RETURNS_RETAINED_PARAMETER CM_NULLABLE VTCompressionSessionRef * CM_NONNULL compressionSessionOut) API_AVAILABLE (macosx (10.8), the ios (8.0), tvos (10.2));Copy the code
Data input
- (void)encodeSampleBuffer:(CMSampleBufferRef)sampleBuffer{
if(! self.encoderSession) { [self encodeInit]; } CFRetain(sampleBuffer); dispatch_async(self.encoderQueue, ^ {/ / original data acquisition CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer (sampleBuffer); TimeStamp = CMTimeMake(self->frameID, 1000); self->frameID++; CMTime duration = kCMTimeInvalid; VTEncodeInfoFlags infoFlagsOut; / / code OSStatus status = VTCompressionSessionEncodeFrame (self. EncoderSession imageBuffer, TimeStamp, duration, NULL, NULL, &infoFlagsOut);if(status ! = noErr) { NSLog(@"error");
}
CFRelease(sampleBuffer);
});
}
Copy the code
Code the callback method
void encodeCallBack (
void * CM_NULLABLE outputCallbackRefCon,
void * CM_NULLABLE sourceFrameRefCon,
OSStatus status,
VTEncodeInfoFlags infoFlags,
CM_NULLABLE CMSampleBufferRef sampleBuffer ){
if(status ! = noErr) { NSLog(@"encodeVideoCallBack: encode error, status = %d",(int)status);
return;
}
if(! CMSampleBufferDataIsReady(sampleBuffer)) { NSLog(@"encodeVideoCallBack: data is not ready");
return; } Demo2ViewController *VC = (__bridge Demo2ViewController *)(outputCallbackRefCon); BOOL isKeyFrame = NO; CFArrayRef attachArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer,true); isKeyFrame = ! CFDictionaryContainsKey(CFArrayGetValueAtIndex(attachArray, 0), kCMSampleAttachmentKey_NotSync); //(note the inverse sign)if(isKeyFrame && ! VC->hasSpsPps) { size_t spsSize, spsCount; size_t ppsSize, ppsCount; const uint8_t *spsData, *ppsData; / / get the image source format CMFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription (sampleBuffer); OSStatus status1 = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDesc, 0, &spsData, &spsSize, &spsCount, 0); OSStatus status2 = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDesc, 1, &ppsData, &ppsSize, &ppsCount, 0);if (status1 == noErr && status2 == noErr) {
VC->hasSpsPps = true; //sps data VC->sps = [NSMutableData dataWithCapacity:4 + spsSize]; [VC->sps appendBytes:StartCode length:4]; [VC->sps appendBytes:spsData length:spsSize]; //pps data VC->pps = [NSMutableData dataWithCapacity:4 + ppsSize]; [VC->pps appendBytes:StartCode length:4]; [VC->pps appendBytes:ppsData length:ppsSize]; [VC decodeH264Data:VC->sps]; [VC decodeH264Data:VC->pps]; } // get NALU data size_t lengthAtOffset, totalLength; char *dataPoint; / / copy the data to the former dataPoint CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer (sampleBuffer); OSStatus error = CMBlockBufferGetDataPointer(blockBuffer, 0, &lengthAtOffset, &totalLength, &dataPoint);if(error ! = kCMBlockBufferNoErr) { NSLog(@"VideoEncodeCallback: get datapoint failed, status = %d", (int)error);
return; } // loop to get nalu data size_t offet = 0; Const int lengthInfoSize = 4;while(offet < totalLength - lengthInfoSize) { uint32_t naluLength = 0; // Obtain the length of nALU data memcpy(&naluLength, dataPoint + offet, lengthInfoSize); NaluLength = CFSwapInt32BigToHost(naluLength); NSMutableData *data = [NSMutableData dataWithCapacity:4 + naluLength]; [data appendBytes:StartCode length:4]; [data appendBytes:dataPoint + offet + lengthInfoSize length:naluLength]; dispatch_async(VC.encoderCallbackQueue, ^{ [VC decodeH264Data:data]; }); Offet += lengthInfoSize + naluLength; }}Copy the code
VTCompressionOutputCallback argument parsing
/* * outputCallbackRefCon: reference value of the callback function. *sourceFrameRefCon: Frame reference value fromsourceCopy to VTCompressionSessionEncodeFrame FrameRefCon parameters. * status: Returns noErr if compression succeeds; If compression is unsuccessful, an error code is issued. * infoFlags: Contains information about encoding operations. If the encoding is running asynchronously, set kVTEncodeInfo_Asynchronous. If frames are dropped, set kVTEncodeInfo_FrameDropped. * sampleBuffer: contains the compressed frame if the compression succeeds and the frame is not deleted; Otherwise, empty. */ typedef void (*VTCompressionOutputCallback)( void *outputCallbackRefCon, void *sourceFrameRefCon,
OSStatus status,
VTEncodeInfoFlags infoFlags,
CMSampleBufferRef sampleBuffer
);
Copy the code
Decoding part
Initialize the
- (void)decodeVideoInit { const uint8_t * const parameterSetPointers[2] = {_sps, _pps}; const size_t parameterSetSizes[2] = {_spsSize, _ppsSize}; int naluHeaderLen = 4; / / decoding parameters according to the SPS PPS configuration OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets (kCFAllocatorDefault, 2, parameterSetPointers, parameterSetSizes, naluHeaderLen, &_decodeDesc);if(status ! = noErr) { NSLog(@"Video hard DecodeSession create H264ParameterSets(sps, pps) failed status= %d", (int)status);
return; } / / configuration video output parameters NSDictionary * destinationPixBufferAttrs = @ {kCVPixelBufferPixelFormatTypeKey (id) : [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange], // On iOS nv12(UVUV configuration) instead of NV21 (VUVU configuration) (ID) [NSNumber numberWithInteger:_width], (id)kCVPixelBufferHeightKey: [NSNumber numberWithInteger:_height], (id)kCVPixelBufferOpenGLCompatibilityKey: [NSNumber numberWithBool:true]}; VTDecompressionOutputCallbackRecord callbackRecord; callbackRecord.decompressionOutputCallback = videoDecompressionOutputCallback; callbackRecord.decompressionOutputRefCon = (__bridge void * _Nullable)(self); / / create the decoder status = VTDecompressionSessionCreate (kCFAllocatorDefault _decodeDesc, NULL, (__bridge CFDictionaryRef _Nullable)(destinationPixBufferAttrs), &callbackRecord, &_decoderSession);if(status ! = noErr) { NSLog(@"Video hard DecodeSession create failed status= %d", (int)status);
return; } / / set the decoding real-time VTSessionSetProperty (_decoderSession kVTDecompressionPropertyKey_RealTime, kCFBooleanTrue); }Copy the code
CMVideoFormatDescriptionCreateFromH264ParameterSets argument parsing
/* * allocator: allocator * parameterSetCount: number of parameters * parameterSetPointers: parameter set pointer * parameterSetSizes: Parameter set size * NALUnitHeaderLength: Start code length 4 * formatDescriptionOut: Decoder description * / CM_EXPORT OSStatus CMVideoFormatDescriptionCreateFromH264ParameterSets (CFAllocatorRef CM_NULLABLE allocator, size_t parameterSetCount, const uint8_t * CM_NONNULL const * CM_NONNULL parameterSetPointers, const size_t * CM_NONNULL parameterSetSizes, int NALUnitHeaderLength, CM_RETURNS_RETAINED_PARAMETER CMFormatDescriptionRef CM_NULLABLE * CM_NONNULL formatDescriptionOut )Copy the code
VTDecompressionOutputCallback callback argument parsing
/ * * decompressionOutputRefCon: callback references *sourceFrameRefCon: reference to frames * status: a status flag (containing undefined code) * infoFlags: flag indicating synchronous/asynchronous decoding, or whether the decoder intends to drop frames * imageBuffer: Buffer of the actual image * presentationTimeStamp: the timestamp that appears * presentationDuration: The duration of a * / typedef void VTDecompressionOutputCallback (*) (void * CM_NULLABLE decompressionOutputRefCon, void * CM_NULLABLEsourceFrameRefCon,
OSStatus status,
VTDecodeInfoFlags infoFlags,
CM_NULLABLE CVImageBufferRef imageBuffer,
CMTime presentationTimeStamp,
CMTime presentationDuration );
Copy the code
VTDecompressionSessionCreate argument parsing
/ * * allocator: memory session * videoFormatDescription: describe the source video frame * videoDecoderSpecification: Specify must use a specific video decoder * destinationImageBufferAttributes: describe the source pixel buffer requirements * outputCallback: use the decompression frame to invoke the callback * decompressionSessionOut: Points to a variable to receive the new decompression session * / VT_EXPORT OSStatus VTDecompressionSessionCreate (CM_NULLABLE CFAllocatorRef allocator, CM_NONNULL CMVideoFormatDescriptionRef videoFormatDescription, CM_NULLABLE CFDictionaryRef videoDecoderSpecification, CM_NULLABLE CFDictionaryRef destinationImageBufferAttributes, const VTDecompressionOutputCallbackRecord * CM_NULLABLE outputCallback, CM_RETURNS_RETAINED_PARAMETER CM_NULLABLE VTDecompressionSessionRef * CM_NONNULL decompressionSessionOut) API_AVAILABLE (macosx (10.8), the ios (8.0), tvos (10.2));Copy the code
H264 data input
- (void)decodeH264Data:(NSData *)h264Data {
if(! self.decoderSession) { [self decodeVideoInit]; } uint8_t *frame = (uint8_t *)h264Data.bytes; uint32_t size = (uint32_t)h264Data.length; inttype = (frame[4] & 0x1F);
// 将NALU的开始码转为4字节大端NALU的长度信息
uint32_t naluSize = size - 4;
uint8_t *pNaluSize = (uint8_t *)(&naluSize);
frame[0] = *(pNaluSize + 3);
frame[1] = *(pNaluSize + 2);
frame[2] = *(pNaluSize + 1);
frame[3] = *(pNaluSize);
switch (type) {
case0x05: // keyframe [self decode:frame withSize:size];break;
case 0x06:
//NSLog(@"SEI"); // Enhance informationbreak;
case 0x07: //sps
_spsSize = naluSize;
_sps = malloc(_spsSize);
memcpy(_sps, &frame[4], _spsSize);
break;
case 0x08: //pps
_ppsSize = naluSize;
_pps = malloc(_ppsSize);
memcpy(_pps, &frame[4], _ppsSize);
break; Default: // Other frames (1-5) [self decode:frame withSize:size];break; } // decode function - (void)decode:(uint8_t *)frame withSize:(uint32_t)frameSize {CVPixelBufferRef outputPixelBuffer = NULL; CMBlockBufferRef blockBuffer = NULL; CMBlockBufferFlags flag0 = 0; OSStatus status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, frame, frameSize, kCFAllocatorNull, NULL, 0, frameSize, flag0, &blockBuffer);if(status ! = kCMBlockBufferNoErr) { NSLog(@"Video hard decode create blockBuffer error code=%d", (int)status);
return;
}
CMSampleBufferRef sampleBuffer = NULL;
const size_t sampleSizeArray[] = {frameSize};
status = CMSampleBufferCreateReady(kCFAllocatorDefault, blockBuffer, _decodeDesc, 1, 0, NULL, 1, sampleSizeArray, &sampleBuffer);
if(status ! = noErr || ! sampleBuffer) { NSLog(@"Video hard decode create sampleBuffer failed status=%d", (int)status);
CFRelease(blockBuffer);
return; VTDecodeFrameFlags flag1 = kVTDecodeFrame_1xRealTimePlayback; // VTDecodeInfoFlags infoFlag = kVTDecodeInfo_Asynchronous; // Decoded data /* Parameters 1: decoded session parameters 2: CMsampleBuffer parameters that contain one or more video frames 3: decoded flag parameters 4: outputPixelBuffer parameters 5: Synchronous/asynchronous identifier * / status = VTDecompressionSessionDecodeFrame (_decoderSession sampleBuffer, flag1, & outputPixelBuffer, &infoFlag);if (status == kVTInvalidSessionErr) {
NSLog(@"Video hard decode InvalidSessionErr status =%d", (int)status);
} else if (status == kVTVideoDecoderBadDataErr) {
NSLog(@"Video hard decode BadData status =%d", (int)status);
} else if(status ! = noErr) { NSLog(@"Video hard decode failed status =%d", (int)status);
}
CFRelease(sampleBuffer);
CFRelease(blockBuffer);
}
Copy the code
Decode the callback method
void videoDecompressionOutputCallback(void * CM_NULLABLE decompressionOutputRefCon,
void * CM_NULLABLE sourceFrameRefCon,
OSStatus status,
VTDecodeInfoFlags infoFlags,
CM_NULLABLE CVImageBufferRef imageBuffer,
CMTime presentationTimeStamp,
CMTime presentationDuration ) {
if(status ! = noErr) { NSLog(@"Video hard decode callback error status=%d", (int)status);
return; } // Decoded datasourceFrameRefCon -> CVPixelBufferRef
CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon; *outputPixelBuffer = CVPixelBufferRetain(imageBuffer); / / get the self Demo2ViewController * decoder = (__bridge Demo2ViewController *) (decompressionOutputRefCon); / / call the callback queue dispatch_async (decoder. DecoderCallbackQueue, CVPixelBufferRelease(imageBuffer); }); }Copy the code
VTDecompressionOutputCallback argument parsing
/ * * decompressionOutputRefCon: callback references *sourceFrameRefCon: reference to frames * status: a status flag (containing undefined code) * infoFlags: flag indicating synchronous/asynchronous decoding, or whether the decoder intends to drop frames * imageBuffer: Buffer of the actual image * presentationTimeStamp: the timestamp that appears * presentationDuration: The duration of a * / typedef void VTDecompressionOutputCallback (*) (void * CM_NULLABLE decompressionOutputRefCon, void * CM_NULLABLEsourceFrameRefCon,
OSStatus status,
VTDecodeInfoFlags infoFlags,
CM_NULLABLE CVImageBufferRef imageBuffer,
CMTime presentationTimeStamp,
CMTime presentationDuration );
Copy the code
The complete code
//
// Demo2ViewController.m
// Demo
//
// Created by Noah on 2020/3/19.
// Copyright © 2020 Noah. All rights reserved.
//
#import "Demo2ViewController.h"
#import <AVFoundation/AVFoundation.h>
#import <VideoToolbox/VideoToolbox.h>@interface Demo2ViewController ()<AVCaptureVideoDataOutputSampleBufferDelegate> { long frameID; BOOL hasSpsPps; // Check whether the PPS and SPS NSMutableData * SPS have been obtained; NSMutableData *pps; uint8_t *_sps; NSUInteger _spsSize; uint8_t *_pps; NSUInteger _ppsSize; CMVideoFormatDescriptionRef _decodeDesc; } @property (nonatomic, strong) AVCaptureSession *captureSession; @property (nonatomic, strong) dispatch_queue_t captureQueue; @property (nonatomic, strong) AVCaptureDeviceInput *videoDataInput; @property (nonatomic, strong) AVCaptureDeviceInput *frontCamera; @property (nonatomic, strong) AVCaptureDeviceInput *backCamera; @property (nonatomic, strong) AVCaptureVideoDataOutput *videoDataOutput; @property (nonatomic, strong) AVCaptureConnection *videoConnection; @property (nonatomic, strong) AVCaptureVideoPreviewLayer *videoPreviewLayer; @property (nonatomic) VTCompressionSessionRef encoderSession; @property (nonatomic, strong) dispatch_queue_t encoderQueue; @property (nonatomic, strong) dispatch_queue_t encoderCallbackQueue; @property (nonatomic) VTDecompressionSessionRef decoderSession; @property (nonatomic, strong) dispatch_queue_t decoderQueue; @property (nonatomic, strong) dispatch_queue_t decoderCallbackQueue; // Capture video width @property (nonatomic, assign,readonly) NSUInteger width; // Capture video high @property (nonatomic, assign,readonly) NSUInteger height;
@end
const Byte StartCode[] = "\x00\x00\x00\x01";
@implementation Demo2ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self setupVideo];
[self.captureSession startRunning];
}
#pragma Mark - Video initialization
- (void)setupVideo{
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice *device in devices) {
if (device.position == AVCaptureDevicePositionBack) {
self.backCamera = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
}else{
self.frontCamera = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
}
}
self.videoDataInput = self.backCamera;
self.videoDataOutput = [[AVCaptureVideoDataOutput alloc]init];
[self.videoDataOutput setSampleBufferDelegate:self queue:self.captureQueue];
[self.videoDataOutput setAlwaysDiscardsLateVideoFrames:YES];
[self.videoDataOutput setVideoSettings:@{(__bridge NSString *)kCVPixelBufferPixelFormatTypeKey:@(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)}];
[self.captureSession beginConfiguration];
if ([self.captureSession canAddInput:self.videoDataInput]) {
[self.captureSession addInput:self.videoDataInput];
}
if ([self.captureSession canAddOutput:self.videoDataOutput]) {
[self.captureSession addOutput:self.videoDataOutput];
}
[self setVideoPreset];
[self.captureSession commitConfiguration];
self.videoConnection = [self.videoDataOutput connectionWithMediaType:AVMediaTypeVideo];
self.videoConnection.videoOrientation = AVCaptureVideoOrientationPortrait;
[self updateFps:25];
self.videoPreviewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];
self.videoPreviewLayer.frame = self.view.bounds;
self.videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
[self.view.layer addSublayer:self.videoPreviewLayer];
}
- (void)setVideoPreset {
if ([self.captureSession canSetSessionPreset:AVCaptureSessionPreset1920x1080]) {
[self.captureSession setSessionPreset:AVCaptureSessionPreset1920x1080];
_width = 1080; _height = 1920;
} else if ([self.captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
[self.captureSession setSessionPreset:AVCaptureSessionPreset1280x720];
_width = 720; _height = 1280;
} else if ([self.captureSession canSetSessionPreset:AVCaptureSessionPreset640x480]) {
[self.captureSession setSessionPreset:AVCaptureSessionPreset640x480]; _width = 480; _height = 640; }} -(void)updateFps:(NSInteger) FPS {// obtain the current capture device NSArray *videoDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; // Traverse all devices (front and rear cameras)for (AVCaptureDevice *vDevice inVideoDevices) {// Get the maximum FPS currently supportedfloatmaxRate = [(AVFrameRateRange *)[vDevice.activeFormat.videoSupportedFrameRateRanges objectAtIndex:0] maxFrameRate]; // If you want the FPS to be less than or equal to a larger FPS, change itif(maxRate >= FPS) {// Actually modify the FPS codeif ([vDevice lockForConfiguration:NULL]) {
vDevice.activeVideoMinFrameDuration = CMTimeMake(10, (int)(fps * 10));
vDevice.activeVideoMaxFrameDuration = vDevice.activeVideoMinFrameDuration; [vDevice unlockForConfiguration]; }}}}#pragma mark - Lazy loading
- (AVCaptureSession *)captureSession{
if(! _captureSession) { _captureSession = [[AVCaptureSession alloc]init]; }return _captureSession;
}
- (dispatch_queue_t)captureQueue{
if(! _captureQueue) { _captureQueue = dispatch_queue_create("capture queue", NULL);
}
return _captureQueue;
}
- (dispatch_queue_t)encoderQueue{
if(! _captureQueue) { _captureQueue = dispatch_queue_create("encoder queue", NULL);
}
return _captureQueue;
}
- (dispatch_queue_t)encoderCallbackQueue{
if(! _encoderCallbackQueue) { _encoderCallbackQueue = dispatch_queue_create("encoder callback queue", NULL);
}
return _encoderCallbackQueue;
}
- (dispatch_queue_t)decoderQueue{
if(! _decoderQueue) { _decoderQueue = dispatch_queue_create("decoder queue", NULL);
}
return _decoderQueue;
}
- (dispatch_queue_t)decoderCallbackQueue{
if(! _decoderCallbackQueue) { _decoderCallbackQueue = dispatch_queue_create("decoder callback queue", NULL);
}
return _decoderCallbackQueue;
}
#pragma mark - Code
- (void)encodeInit{
OSStatus status = VTCompressionSessionCreate(kCFAllocatorDefault, (uint32_t)_width, (uint32_t)_height, kCMVideoCodecType_H264, NULL, NULL, NULL, encodeCallBack, (__bridge void * _Nullable)(self), &_encoderSession);
if(status ! = noErr) {return; } / / set the real-time encoding status = VTSessionSetProperty (_encoderSession kVTCompressionPropertyKey_RealTime, kCFBooleanTrue); / / set does not need to B frames status = VTSessionSetProperty (_encoderSession kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_AutoLevel); CFNumberRef bitRate = (__bridge CFNumberRef)(@(_height*1000)); status = VTSessionSetProperty(_encoderSession, kVTCompressionPropertyKey_AverageBitRate, bitRate); CFArrayRef = (__bridge CFArrayRef)(@[@(_height*1000/4),@(_height*1000*4)]); status = VTSessionSetProperty(_encoderSession, kVTCompressionPropertyKey_DataRateLimits, limits); // set FPS CFNumberRef FPS = (__bridge CFNumberRef)(@(25)); status = VTSessionSetProperty(_encoderSession, kVTCompressionPropertyKey_ExpectedFrameRate, fps); CFNumberRef maxKeyFrameInterval = (__bridge CFNumberRef)(@(25*2)); status = VTSessionSetProperty(_encoderSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, maxKeyFrameInterval); / / ready to code status = VTCompressionSessionPrepareToEncodeFrames (_encoderSession); } void encodeCallBack ( void * CM_NULLABLE outputCallbackRefCon, void * CM_NULLABLEsourceFrameRefCon,
OSStatus status,
VTEncodeInfoFlags infoFlags,
CM_NULLABLE CMSampleBufferRef sampleBuffer ){
if(status ! = noErr) { NSLog(@"encodeVideoCallBack: encode error, status = %d",(int)status);
return;
}
if(! CMSampleBufferDataIsReady(sampleBuffer)) { NSLog(@"encodeVideoCallBack: data is not ready");
return; } Demo2ViewController *VC = (__bridge Demo2ViewController *)(outputCallbackRefCon); BOOL isKeyFrame = NO; CFArrayRef attachArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer,true); isKeyFrame = ! CFDictionaryContainsKey(CFArrayGetValueAtIndex(attachArray, 0), kCMSampleAttachmentKey_NotSync); //(note the inverse sign)if(isKeyFrame && ! VC->hasSpsPps) { size_t spsSize, spsCount; size_t ppsSize, ppsCount; const uint8_t *spsData, *ppsData; / / get the image source format CMFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription (sampleBuffer); OSStatus status1 = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDesc, 0, &spsData, &spsSize, &spsCount, 0); OSStatus status2 = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDesc, 1, &ppsData, &ppsSize, &ppsCount, 0);if (status1 == noErr && status2 == noErr) {
VC->hasSpsPps = true; //sps data VC->sps = [NSMutableData dataWithCapacity:4 + spsSize]; [VC->sps appendBytes:StartCode length:4]; [VC->sps appendBytes:spsData length:spsSize]; //pps data VC->pps = [NSMutableData dataWithCapacity:4 + ppsSize]; [VC->pps appendBytes:StartCode length:4]; [VC->pps appendBytes:ppsData length:ppsSize]; [VC decodeH264Data:VC->sps]; [VC decodeH264Data:VC->pps]; } // get NALU data size_t lengthAtOffset, totalLength; char *dataPoint; / / copy the data to the former dataPoint CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer (sampleBuffer); OSStatus error = CMBlockBufferGetDataPointer(blockBuffer, 0, &lengthAtOffset, &totalLength, &dataPoint);if(error ! = kCMBlockBufferNoErr) { NSLog(@"VideoEncodeCallback: get datapoint failed, status = %d", (int)error);
return; } // loop to get nalu data size_t offet = 0; Const int lengthInfoSize = 4;while(offet < totalLength - lengthInfoSize) { uint32_t naluLength = 0; // Obtain the length of nALU data memcpy(&naluLength, dataPoint + offet, lengthInfoSize); NaluLength = CFSwapInt32BigToHost(naluLength); NSMutableData *data = [NSMutableData dataWithCapacity:4 + naluLength]; [data appendBytes:StartCode length:4]; [data appendBytes:dataPoint + offet + lengthInfoSize length:naluLength]; dispatch_async(VC.encoderCallbackQueue, ^{ [VC decodeH264Data:data]; }); Offet += lengthInfoSize + naluLength; } } - (void)encodeSampleBuffer:(CMSampleBufferRef)sampleBuffer{if(! self.encoderSession) { [self encodeInit]; } CFRetain(sampleBuffer); dispatch_async(self.encoderQueue, ^{ CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); CMTime TimeStamp = CMTimeMake(self->frameID, 1000); self->frameID++; CMTime duration = kCMTimeInvalid; VTEncodeInfoFlags infoFlagsOut; OSStatus status = VTCompressionSessionEncodeFrame(self.encoderSession, imageBuffer, TimeStamp, duration, NULL, NULL, &infoFlagsOut);if(status ! = noErr) { NSLog(@"error");
}
CFRelease(sampleBuffer);
});
}
# Pragma Mark - Video decoding// Void decodeVideoInit {const Uint8_t * const parameterSetsailors [2] = {_sps, _pps}; const size_t parameterSetSizes[2] = {_spsSize, _ppsSize}; int naluHeaderLen = 4; / / decoding parameters according to the SPS PPS configuration OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets (kCFAllocatorDefault, 2, parameterSetPointers, parameterSetSizes, naluHeaderLen, &_decodeDesc);if(status ! = noErr) { NSLog(@"Video hard DecodeSession create H264ParameterSets(sps, pps) failed status= %d", (int)status);
return; } / / configuration video output parameters NSDictionary * destinationPixBufferAttrs = @ {kCVPixelBufferPixelFormatTypeKey (id) : [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange], // On iOS nv12(UVUV configuration) instead of NV21 (VUVU configuration) (ID) [NSNumber numberWithInteger:_width], (id)kCVPixelBufferHeightKey: [NSNumber numberWithInteger:_height], (id)kCVPixelBufferOpenGLCompatibilityKey: [NSNumber numberWithBool:true]}; VTDecompressionOutputCallbackRecord callbackRecord; callbackRecord.decompressionOutputCallback = videoDecompressionOutputCallback; callbackRecord.decompressionOutputRefCon = (__bridge void * _Nullable)(self); / / create the decoder status = VTDecompressionSessionCreate (kCFAllocatorDefault _decodeDesc, NULL, (__bridge CFDictionaryRef _Nullable)(destinationPixBufferAttrs), &callbackRecord, &_decoderSession);if(status ! = noErr) { NSLog(@"Video hard DecodeSession create failed status= %d", (int)status);
return; } / / set the decoding real-time VTSessionSetProperty (_decoderSession kVTDecompressionPropertyKey_RealTime, kCFBooleanTrue); } / decoding callback function * * * / void videoDecompressionOutputCallback (void * CM_NULLABLE decompressionOutputRefCon, void * CM_NULLABLEsourceFrameRefCon,
OSStatus status,
VTDecodeInfoFlags infoFlags,
CM_NULLABLE CVImageBufferRef imageBuffer,
CMTime presentationTimeStamp,
CMTime presentationDuration ) {
if(status ! = noErr) { NSLog(@"Video hard decode callback error status=%d", (int)status);
return; } // Decoded datasourceFrameRefCon -> CVPixelBufferRef
CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon; *outputPixelBuffer = CVPixelBufferRetain(imageBuffer); / / get the self Demo2ViewController * decoder = (__bridge Demo2ViewController *) (decompressionOutputRefCon); / / call the callback queue dispatch_async (decoder. DecoderCallbackQueue, CVPixelBufferRelease(imageBuffer); }); } // decode - (void)decodeH264Data:(NSData *)h264Data {if(! self.decoderSession) { [self decodeVideoInit]; } uint8_t *frame = (uint8_t *)h264Data.bytes; uint32_t size = (uint32_t)h264Data.length; inttype = (frame[4] & 0x1F);
// 将NALU的开始码转为4字节大端NALU的长度信息
uint32_t naluSize = size - 4;
uint8_t *pNaluSize = (uint8_t *)(&naluSize);
frame[0] = *(pNaluSize + 3);
frame[1] = *(pNaluSize + 2);
frame[2] = *(pNaluSize + 1);
frame[3] = *(pNaluSize);
switch (type) {
case0x05: // keyframe [self decode:frame withSize:size];break;
case 0x06:
//NSLog(@"SEI"); // Enhance informationbreak;
case 0x07: //sps
_spsSize = naluSize;
_sps = malloc(_spsSize);
memcpy(_sps, &frame[4], _spsSize);
break;
case 0x08: //pps
_ppsSize = naluSize;
_pps = malloc(_ppsSize);
memcpy(_pps, &frame[4], _ppsSize);
break; Default: // Other frames (1-5) [self decode:frame withSize:size];break; } // decode function - (void)decode:(uint8_t *)frame withSize:(uint32_t)frameSize {CVPixelBufferRef outputPixelBuffer = NULL; CMBlockBufferRef blockBuffer = NULL; CMBlockBufferFlags flag0 = 0; OSStatus status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, frame, frameSize, kCFAllocatorNull, NULL, 0, frameSize, flag0, &blockBuffer);if(status ! = kCMBlockBufferNoErr) { NSLog(@"Video hard decode create blockBuffer error code=%d", (int)status);
return;
}
CMSampleBufferRef sampleBuffer = NULL;
const size_t sampleSizeArray[] = {frameSize};
status = CMSampleBufferCreateReady(kCFAllocatorDefault, blockBuffer, _decodeDesc, 1, 0, NULL, 1, sampleSizeArray, &sampleBuffer);
if(status ! = noErr || ! sampleBuffer) { NSLog(@"Video hard decode create sampleBuffer failed status=%d", (int)status);
CFRelease(blockBuffer);
return; VTDecodeFrameFlags flag1 = kVTDecodeFrame_1xRealTimePlayback; // VTDecodeInfoFlags infoFlag = kVTDecodeInfo_Asynchronous; // Decoded data /* Parameters 1: decoded session parameters 2: CMsampleBuffer parameters that contain one or more video frames 3: decoded flag parameters 4: outputPixelBuffer parameters 5: Synchronous/asynchronous identifier * / status = VTDecompressionSessionDecodeFrame (_decoderSession sampleBuffer, flag1, & outputPixelBuffer, &infoFlag);if (status == kVTInvalidSessionErr) {
NSLog(@"Video hard decode InvalidSessionErr status =%d", (int)status);
} else if (status == kVTVideoDecoderBadDataErr) {
NSLog(@"Video hard decode BadData status =%d", (int)status);
} else if(status ! = noErr) { NSLog(@"Video hard decode failed status =%d", (int)status);
}
CFRelease(sampleBuffer);
CFRelease(blockBuffer);
}
#pragma mark - AVCaptureVideoDataOutputSampleBufferDelegate
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection{
if(connection == self.videoConnection) { [self encodeSampleBuffer:sampleBuffer]; }}Pragma mark - destructor
- (void)dealloc {
if (_decoderSession) {
VTDecompressionSessionInvalidate(_decoderSession);
CFRelease(_decoderSession);
_decoderSession = NULL;
}
if (_encoderSession) {
VTCompressionSessionInvalidate(_encoderSession);
CFRelease(_encoderSession);
_encoderSession = NULL;
}
}
@end
Copy the code
AudioToolbox
The coding part
Initialize the
- (void) setupAudioConverter: CMSampleBufferRef sampleBuffer {/ / by real-time decoding data to obtain AudioStreamBasicDescription input information inputDescription = *CMAudioFormatDescriptionGetStreamBasicDescription(CMSampleBufferGetFormatDescription(sampleBuffer)); / / define the PCM output information AudioStreamBasicDescription outputDescription = {, mSampleRate = 44100, mFormatID = kAudioFormatMPEG4AAC, .mFormatFlags = kMPEG4Object_AAC_LC, .mBytesPerPacket = 0, .mFramesPerPacket = 1024, .mBytesPerFrame = 0, .mChannelsPerFrame = 1, .mBitsPerChannel = 0, .mReserved = 0 }; OSStatus status = AudioConverterNew(&inputDescription, &outputDescription, &_AudioConverter);if(status ! = noErr) { NSLog(@"error");
return; } // Set output quality UInt32 Temp = kAudioConverterQuality_High; AudioConverterSetProperty(_audioConverter, kAudioConverterCodecQuality, sizeof(temp), &temp); // Set bitRate UInt32 bitRate = 96000; AudioConverterSetProperty(_audioConverter, kAudioConverterEncodeBitRate, sizeof(bitRate), &bitRate); }Copy the code
The input information is first obtained through real-time audio data, then the PCM output information is defined, and then the decoder is created through these two parameters. Set some decoder properties after successful creation.
Coding function
- (void)setAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer{
CFRetain(sampleBuffer);
if(! self.audioConverter) { [self setupAudioConverter:sampleBuffer]; } dispatch_async(self.encodeQueue, ^{ CMBlockBufferRef bufferRef = CMSampleBufferGetDataBuffer(sampleBuffer); CFRetain(bufferRef); CMBlockBufferGetDataPointer(bufferRef, 0, NULL, &self->_pcmBufferSize, &self->_pcmBuffer); char *pcmbuffer = malloc(self->_pcmBufferSize); memset(pcmbuffer, 0, self->_pcmBufferSize); AudioBufferList audioBufferList = {0}; audioBufferList.mNumberBuffers = 1; audioBufferList.mBuffers[0].mData = pcmbuffer; audioBufferList.mBuffers[0].mDataByteSize = (uint32_t)self->_pcmBufferSize; audioBufferList.mBuffers[0].mNumberChannels = 1; UInt32 dataPacketSize = 1; OSStatus status = AudioConverterFillComplexBuffer(self.audioConverter, aacEncodeInputDataProc, (__bridge void * _Nullable)(self), &dataPacketSize, &audioBufferList, NULL);if(status == noErr) { NSData *aacData = [NSData dataWithBytes:audioBufferList.mBuffers[0].mData length:audioBufferList.mBuffers[0].mDataByteSize]; free(pcmbuffer); / / add ADTS head, want to get naked, please ignore add ADTS head, write to a file, you must add / / NSData * adtsHeader = [self adtsDataForPacketLength: rawAAC, length]. //NSMutableData *fullData = [NSMutableData dataWithCapacity:adtsHeader.length + rawAAC.length]; //[fullData appendData:adtsHeader]; //[fullData appendData:rawAAC]; dispatch_async(self.encodeCallbackQueue, ^{ [self decodeAudioAACData:aacData]; }); } CFRelease(bufferRef); CFRelease(sampleBuffer); }); }Copy the code
Audio decoding is different from video decoding. The first step is to get the PCM data and store it in a global variable, and then create an AudioBufferList to receive the data. The decoding works in the decoding callback. The pointer, and create a callback AudioBufferList incoming AudioConverterFillComplexBuffer, will continue to decode data just the way they are.
Coding the callback
static OSStatus aacEncodeInputDataProc(
AudioConverterRef inAudioConverter,
UInt32 *ioNumberDataPackets,
AudioBufferList *ioData,
AudioStreamPacketDescription **outDataPacketDescription,
void *inUserData){
AudioViewController *audioVC = (__bridge AudioViewController *)(inUserData);
if(! audioVC.pcmBufferSize) { *ioNumberDataPackets = 0;return- 1; } // ioData->mBuffers[0]. MDataByteSize = (UInt32) AudiOVC.pcMBUFFerSize; ioData->mBuffers[0].mData = audioVC.pcmBuffer; ioData->mBuffers[0].mNumberChannels = 1; audioVC.pcmBufferSize = 0; *ioNumberDataPackets = 1;return noErr;
}
Copy the code
The encoding callback does the work of populating the PCM data so that it can be encoded
Decoding part
Initialize the
- (void) setupEncoder {/ / output parameters PCM AudioStreamBasicDescription outputAudioDes = {0}; outputAudioDes.mSampleRate = (Float64)self.sampleRate; / / sampling rate outputAudioDes mChannelsPerFrame = (UInt32) self. ChannelCount; // Outputauds35. mFormatID = kAudioFormatLinearPCM; / / output format outputAudioDes. MFormatFlags = (kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked); / / code 12 outputAudioDes. MFramesPerPacket = 1; // Number of frames per packet; outputAudioDes.mBitsPerChannel = 16; // The number of bits sampled for each channel in the data frame. outputAudioDes.mBytesPerFrame = outputAudioDes.mBitsPerChannel / 8 *outputAudioDes.mChannelsPerFrame; / / each frame size (sampling) the digits / 8 * the number of channels outputAudioDes. MBytesPerPacket = outputAudioDes. MBytesPerFrame * outputAudioDes mFramesPerPacket; // Each packet size (frame size * frame number) outputauds35s.mreserved = 0; / / the way 0 (8 byte alignment) / / input parameters aac AudioStreamBasicDescription inputAduioDes = {0}; inputAduioDes.mSampleRate = (Float64)self.sampleRate; inputAduioDes.mFormatID = kAudioFormatMPEG4AAC; inputAduioDes.mFormatFlags = kMPEG4Object_AAC_LC; inputAduioDes.mFramesPerPacket = 1024; inputAduioDes.mChannelsPerFrame = (UInt32)self.channelCount; // Input information UInt32inDesSize = sizeof(inputAduioDes);
AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &inDesSize, &inputAduioDes);
OSStatus status = AudioConverterNew(&inputAduioDes, &outputAudioDes, &_audioDecodeConverter);
if(status ! = noErr) { NSLog(@"Error! : hard decoding AAC creation failed, status= %d", (int)status);
return; }}Copy the code
The initialization of a decoder is similar to that of an encoder, but with more elaboration
The decoding function
- (void)decodeAudioAACData:(NSData *)aacData {
if(! _audioDecodeConverter) { [self setupEncoder]; } dispatch_async(self.decodeQueue, ^{CCAudioUserData userData = {0}; userData.channelCount = (UInt32)self.channelCount; userData.data = (char *)[aacData bytes]; userData.size = (UInt32)aacData.length; userData.packetDesc.mDataByteSize = (UInt32)aacData.length; userData.packetDesc.mStartOffset = 0; userData.packetDesc.mVariableFramesInPacket = 0; // Output size and Number of packets UInt32 pcmBufferSize = (UInt32)(2048 * self.channelcount); UInt32 pcmDataPacketSize = 1024; PCM uint8_t *pcmBuffer = malloc(pcmBufferSize); memset(pcmBuffer, 0, pcmBufferSize); AudioBufferList outAudioBufferList = {0}; outAudioBufferList.mNumberBuffers = 1; outAudioBufferList.mBuffers[0].mNumberChannels = (uint32_t)self.channelCount; outAudioBufferList.mBuffers[0].mDataByteSize = (UInt32)pcmBufferSize; outAudioBufferList.mBuffers[0].mData = pcmBuffer; / / output describe AudioStreamPacketDescription outputPacketDesc = {0}; // Configure the fill function, To obtain the output data OSStatus status = AudioConverterFillComplexBuffer (self - > _audioDecodeConverter, AudioDecoderConverterComplexInputDataProc, &userData, &pcmDataPacketSize, &outAudioBufferList, &outputPacketDesc);if(status ! = noErr) { NSLog(@"Error: AAC Decoder error, status=%d",(int)status);
return; } // If data is retrievedif(outAudioBufferList.mBuffers[0].mDataByteSize > 0) { NSData *rawData = [NSData dataWithBytes:outAudioBufferList.mBuffers[0].mData length:outAudioBufferList.mBuffers[0].mDataByteSize]; Dispatch_async (self) decodeCallbackQueue, ^ {/ / here can handle decoding of PCM data NSLog (@"% @",rawData);
});
}
free(pcmBuffer);
});
}
Copy the code
Audio decoding function and coding function are AudioConverterFillComplexBuffer, parameter is the same, the only difference is that the output data is PCM data after decoding
Decoding the callback
static OSStatus AudioDecoderConverterComplexInputDataProc( AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) {
CCAudioUserData *audioDecoder = (CCAudioUserData *)(inUserData);
if (audioDecoder->size <= 0) {
ioNumberDataPackets = 0;
return- 1; } // outDataPacketDescription = &audioDecoder->packetDesc; (*outDataPacketDescription)[0].mStartOffset = 0; (*outDataPacketDescription)[0].mDataByteSize = audioDecoder->size; (*outDataPacketDescription)[0].mVariableFramesInPacket = 0; ioData->mBuffers[0].mData = audioDecoder->data; ioData->mBuffers[0].mDataByteSize = audioDecoder->size; ioData->mBuffers[0].mNumberChannels = audioDecoder->channelCount;return noErr;
}
Copy the code
The complete code
//
// AudioViewController.m
// Demo
//
// Created by Noah on 2020/3/21.
// Copyright © 2020 Noah. All rights reserved.
//
#import "AudioViewController.h"
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>typedef struct { char * data; UInt32 size; UInt32 channelCount; AudioStreamPacketDescription packetDesc; } CCAudioUserData; @interface AudioViewController ()<AVCaptureAudioDataOutputSampleBufferDelegate> @property (nonatomic, strong) AVCaptureSession *captureSession; @property (nonatomic, strong) dispatch_queue_t captureQueue; @property (nonatomic, strong) AVCaptureInput *audioDeviceInput; @property (nonatomic, strong) AVCaptureAudioDataOutput *audioDeviceOutput; @property (nonatomic, strong) dispatch_queue_t encodeQueue; @property (nonatomic, strong) dispatch_queue_t encodeCallbackQueue; @property (nonatomic, strong) AVCaptureConnection *audioConnettion; @property (nonatomic, unsafe_unretained) AudioConverterRef audioConverter; @property (nonatomic) char *pcmBuffer; @property (nonatomic) size_t pcmBufferSize; @property (nonatomic, strong) dispatch_queue_t decodeQueue; @property (nonatomic, strong) dispatch_queue_t decodeCallbackQueue; @property (nonatomic) AudioConverterRef audioDecodeConverter; @property (nonatomic) char *aacBuffer; @property (nonatomic) UInt32 aacBufferSize; /** bitrate */ @property (nonatomic, assign) NSInteger bitRate; //(96000) /** channel */ @property (nonatomic, assign) NSInteger channelCount; // (1) /** sampleRate */ @property (nonatomic, assign) NSInteger sampleRate; //(default 44100) /** Sample point quantization */ @property (nonatomic, assign) NSInteger sampleSize; //(16) @end @implementation AudioViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.bitrate = 96000; self.channelCount = 1; self.sampleRate = 44100; self.sampleSize = 16; [self setupAudio]; [self.captureSession startRunning]; }#pragma mark - Lazy loading
- (AVCaptureSession *)captureSession{
if(! _captureSession) { _captureSession = [[AVCaptureSession alloc]init]; }return _captureSession;
}
- (dispatch_queue_t)captureQueue{
if(! _captureQueue) { _captureQueue = dispatch_queue_create("capture queue", NULL);
}
return _captureQueue;
}
- (dispatch_queue_t)encodeQueue{
if(! _encodeQueue) { _encodeQueue = dispatch_queue_create("encode queue", NULL);
}
return _encodeQueue;
}
- (dispatch_queue_t)encodeCallbackQueue{
if(! _encodeCallbackQueue) { _encodeCallbackQueue = dispatch_queue_create("encode callback queue", NULL);
}
return _encodeCallbackQueue;
}
- (dispatch_queue_t)decodeQueue{
if(! _decodeQueue) { _decodeQueue = dispatch_queue_create("decode queue", NULL);
}
return _decodeQueue;
}
- (dispatch_queue_t)decodeCallbackQueue{
if(! _decodeCallbackQueue) { _decodeCallbackQueue = dispatch_queue_create("decode callback queue", NULL);
}
return _decodeCallbackQueue;
}
#pragma Mark - Audio initialization
- (void)setupAudio{
AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
self.audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:nil];
self.audioDeviceOutput = [[AVCaptureAudioDataOutput alloc]init];
[self.audioDeviceOutput setSampleBufferDelegate:self queue:self.captureQueue];
[self.captureSession beginConfiguration];
if ([self.captureSession canAddInput:self.audioDeviceInput]) {
[self.captureSession addInput:self.audioDeviceInput];
}
if ([self.captureSession canAddOutput:self.audioDeviceOutput]) {
[self.captureSession addOutput:self.audioDeviceOutput];
}
[self.captureSession commitConfiguration];
self.audioConnettion = [self.audioDeviceOutput connectionWithMediaType:AVMediaTypeAudio];
}
#pragma Mark - Audio coding
- (void)setupAudioConverter:(CMSampleBufferRef)sampleBuffer{
AudioStreamBasicDescription inputDescription = *CMAudioFormatDescriptionGetStreamBasicDescription(CMSampleBufferGetFormatDescription(sampleBuffer));
AudioStreamBasicDescription outputDescription =
{
.mSampleRate = 44100,
.mFormatID = kAudioFormatMPEG4AAC,
.mFormatFlags = kMPEG4Object_AAC_LC,
.mBytesPerPacket = 0,
.mFramesPerPacket = 1024,
.mBytesPerFrame = 0,
.mChannelsPerFrame = 1,
.mBitsPerChannel = 0,
.mReserved = 0
};
OSStatus status = AudioConverterNew(&inputDescription, &outputDescription, &_audioConverter);
if(status ! = noErr) { NSLog(@"error");
return;
}
UInt32 temp = kAudioConverterQuality_High;
AudioConverterSetProperty(_audioConverter, kAudioConverterCodecQuality, sizeof(temp), &temp);
UInt32 bitRate = 96000;
AudioConverterSetProperty(_audioConverter, kAudioConverterEncodeBitRate, sizeof(bitRate), &bitRate);
}
static OSStatus aacEncodeInputDataProc(
AudioConverterRef inAudioConverter,
UInt32 *ioNumberDataPackets,
AudioBufferList *ioData,
AudioStreamPacketDescription **outDataPacketDescription,
void *inUserData){
AudioViewController *audioVC = (__bridge AudioViewController *)(inUserData);
if(! audioVC.pcmBufferSize) { *ioNumberDataPackets = 0;return- 1; } ioData->mBuffers[0].mDataByteSize = (UInt32)audioVC.pcmBufferSize; ioData->mBuffers[0].mData = audioVC.pcmBuffer; ioData->mBuffers[0].mNumberChannels = 1; audioVC.pcmBufferSize = 0; *ioNumberDataPackets = 1;return noErr;
}
- (void)setAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer{
CFRetain(sampleBuffer);
if(! self.audioConverter) { [self setupAudioConverter:sampleBuffer]; } dispatch_async(self.encodeQueue, ^{ CMBlockBufferRef bufferRef = CMSampleBufferGetDataBuffer(sampleBuffer); CFRetain(bufferRef); CMBlockBufferGetDataPointer(bufferRef, 0, NULL, &self->_pcmBufferSize, &self->_pcmBuffer); char *pcmbuffer = malloc(self->_pcmBufferSize); memset(pcmbuffer, 0, self->_pcmBufferSize); AudioBufferList audioBufferList = {0}; audioBufferList.mNumberBuffers = 1; audioBufferList.mBuffers[0].mData = pcmbuffer; audioBufferList.mBuffers[0].mDataByteSize = (uint32_t)self->_pcmBufferSize; audioBufferList.mBuffers[0].mNumberChannels = 1; UInt32 dataPacketSize = 1; OSStatus status = AudioConverterFillComplexBuffer(self.audioConverter, aacEncodeInputDataProc, (__bridge void * _Nullable)(self), &dataPacketSize, &audioBufferList, NULL);if(status == noErr) { NSData *aacData = [NSData dataWithBytes:audioBufferList.mBuffers[0].mData length:audioBufferList.mBuffers[0].mDataByteSize]; free(pcmbuffer); / / add ADTS head, want to get naked, please ignore add ADTS head, write to a file, you must add / / NSData * adtsHeader = [self adtsDataForPacketLength: rawAAC, length]. // NSMutableData *fullData = [NSMutableData dataWithCapacity:adtsHeader.length + rawAAC.length];; // [fullData appendData:adtsHeader]; // [fullData appendData:rawAAC]; dispatch_async(self.encodeCallbackQueue, ^{ [self decodeAudioAACData:aacData]; }); } CFRelease(bufferRef); CFRelease(sampleBuffer); }); } // AAC ADtS header - (NSData*)adtsDataForPacketLength:(NSUInteger)packetLength {int adtsLength = 7; char *packet = malloc(sizeof(char) * adtsLength); // Variables Recycled by addADTStoPacket int profile = 2; //AAC LC //39=MediaCodecInfo.CodecProfileLevel.AACObjectELD; int freqIdx = 4; //3:48000 Hz, 4:44.1khz, 8: 16000 Hz, 11: 8000 Hz int chanCfg = 1; //MPEG-4 Audio Channel Configuration. 1 Channel front-center
NSUInteger fullLength = adtsLength + packetLength;
// fill in ADTS data
packet[0] = (char)0xFF; // 11111111 = syncword
packet[1] = (char)0xF9; // 1111 1 00 1 = syncword MPEG-2 Layer CRC
packet[2] = (char)(((profile-1)<<6) + (freqIdx<<2) +(chanCfg> > 2)); packet[3] = (char)(((chanCfg&3)<<6) + (fullLength>>11));
packet[4] = (char)((fullLength&0x7FF) >> 3);
packet[5] = (char)(((fullLength&7)<<5) + 0x1F);
packet[6] = (char)0xFC;
NSData *data = [NSData dataWithBytesNoCopy:packet length:adtsLength freeWhenDone:YES];
return data;
}
#pragma Mark - Audio decoding/ / initialize the - (void) setupEncoder {/ / output parameters PCM AudioStreamBasicDescription outputAudioDes = {0}; outputAudioDes.mSampleRate = (Float64)self.sampleRate; / / sampling rate outputAudioDes mChannelsPerFrame = (UInt32) self. ChannelCount; // Outputauds35. mFormatID = kAudioFormatLinearPCM; / / output format outputAudioDes. MFormatFlags = (kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked); / / code 12 outputAudioDes. MFramesPerPacket = 1; // Number of frames per packet; outputAudioDes.mBitsPerChannel = 16; // The number of bits sampled for each channel in the data frame. outputAudioDes.mBytesPerFrame = outputAudioDes.mBitsPerChannel / 8 *outputAudioDes.mChannelsPerFrame; / / each frame size (sampling) the digits / 8 * the number of channels outputAudioDes. MBytesPerPacket = outputAudioDes. MBytesPerFrame * outputAudioDes mFramesPerPacket; // Each packet size (frame size * frame number) outputauds35s.mreserved = 0; / / the way 0 (8 byte alignment) / / input parameters aac AudioStreamBasicDescription inputAduioDes = {0}; inputAduioDes.mSampleRate = (Float64)self.sampleRate; inputAduioDes.mFormatID = kAudioFormatMPEG4AAC; inputAduioDes.mFormatFlags = kMPEG4Object_AAC_LC; inputAduioDes.mFramesPerPacket = 1024; inputAduioDes.mChannelsPerFrame = (UInt32)self.channelCount; // Padding output information UInt32inDesSize = sizeof(inputAduioDes);
AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &inDesSize, &inputAduioDes);
OSStatus status = AudioConverterNew(&inputAduioDes, &outputAudioDes, &_audioDecodeConverter);
if(status ! = noErr) { NSLog(@"Error! : hard decoding AAC creation failed, status= %d", (int)status);
return; }} / / decoder callback function static OSStatus AudioDecoderConverterComplexInputDataProc (AudioConverterRefinAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) {
CCAudioUserData *audioDecoder = (CCAudioUserData *)(inUserData);
if (audioDecoder->size <= 0) {
ioNumberDataPackets = 0;
return- 1; } // outDataPacketDescription = &audioDecoder->packetDesc; (*outDataPacketDescription)[0].mStartOffset = 0; (*outDataPacketDescription)[0].mDataByteSize = audioDecoder->size; (*outDataPacketDescription)[0].mVariableFramesInPacket = 0; ioData->mBuffers[0].mData = audioDecoder->data; ioData->mBuffers[0].mDataByteSize = audioDecoder->size; ioData->mBuffers[0].mNumberChannels = audioDecoder->channelCount;return noErr;
}
- (void)decodeAudioAACData:(NSData *)aacData {
if(! _audioDecodeConverter) { [self setupEncoder]; } dispatch_async(self.decodeQueue, ^{CCAudioUserData userData = {0}; userData.channelCount = (UInt32)self.channelCount; userData.data = (char *)[aacData bytes]; userData.size = (UInt32)aacData.length; userData.packetDesc.mDataByteSize = (UInt32)aacData.length; userData.packetDesc.mStartOffset = 0; userData.packetDesc.mVariableFramesInPacket = 0; // Output size and Number of packets UInt32 pcmBufferSize = (UInt32)(2048 * self.channelcount); UInt32 pcmDataPacketSize = 1024; PCM uint8_t *pcmBuffer = malloc(pcmBufferSize); memset(pcmBuffer, 0, pcmBufferSize); AudioBufferList outAudioBufferList = {0}; outAudioBufferList.mNumberBuffers = 1; outAudioBufferList.mBuffers[0].mNumberChannels = (uint32_t)self.channelCount; outAudioBufferList.mBuffers[0].mDataByteSize = (UInt32)pcmBufferSize; outAudioBufferList.mBuffers[0].mData = pcmBuffer; / / output describe AudioStreamPacketDescription outputPacketDesc = {0}; // Configure the fill function, To obtain the output data OSStatus status = AudioConverterFillComplexBuffer (self - > _audioDecodeConverter, AudioDecoderConverterComplexInputDataProc, &userData, &pcmDataPacketSize, &outAudioBufferList, &outputPacketDesc);if(status ! = noErr) { NSLog(@"Error: AAC Decoder error, status=%d",(int)status);
return; } // If data is retrievedif(outAudioBufferList.mBuffers[0].mDataByteSize > 0) { NSData *rawData = [NSData dataWithBytes:outAudioBufferList.mBuffers[0].mData length:outAudioBufferList.mBuffers[0].mDataByteSize]; Dispatch_async (self) decodeCallbackQueue, ^ {/ / here can handle decoding of PCM data NSLog (@"% @",rawData);
});
}
free(pcmBuffer);
});
}
#pragma mark - AVCaptureAudioDataOutputSampleBufferDelegate
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection{
if (connection == self.audioConnettion) {
[self setAudioSampleBuffer:sampleBuffer]; }}Pragma mark - destructor
- (void)dealloc {
if (_audioConverter) {
AudioConverterDispose(_audioConverter);
_audioConverter = NULL;
}
if (_audioDecodeConverter) {
AudioConverterDispose(_audioDecodeConverter);
_audioDecodeConverter = NULL;
}
}
@end
Copy the code
Refer to the article
VideoToolbox: data compression session