iOS 视频直播开发笔记(二)

一、iOS视频编码及推流实现细节

(1)将视频流变成yuvdata数据

-(NSData*) convertVideoSmapleBufferToYuvData:(CMSampleBufferRef) videoSample{

//获取yuv数据

//通过CMSampleBufferGetImageBuffer方法,获得CVImageBufferRef。

//这里面就包含了yuv420数据的指针

CVImageBufferRefpixelBuffer =CMSampleBufferGetImageBuffer(videoSample);

//表示开始操作数据

CVPixelBufferLockBaseAddress(pixelBuffer,0);

//图像宽度(像素)

size_tpixelWidth =CVPixelBufferGetWidth(pixelBuffer);

//图像高度(像素)

size_tpixelHeight =CVPixelBufferGetHeight(pixelBuffer);

//yuv中的y所占字节数

size_ty_size = pixelWidth * pixelHeight;

//yuv中的u和v分别所占的字节数

size_tuv_size = y_size /4;

uint8_t*yuv_frame =aw_alloc(uv_size *2+ y_size);

//获取CVImageBufferRef中的y数据

uint8_t*y_frame =CVPixelBufferGetBaseAddressOfPlane(pixelBuffer,0);

memcpy(yuv_frame, y_frame, y_size);

//获取CMVImageBufferRef中的uv数据

uint8_t*uv_frame =CVPixelBufferGetBaseAddressOfPlane(pixelBuffer,1);

memcpy(yuv_frame + y_size, uv_frame, uv_size *2);

CVPixelBufferUnlockBaseAddress(pixelBuffer,0);

NSData*nv12Data = [NSDatadataWithBytesNoCopy:yuv_framelength:y_size + uv_size *2];

//旋转

return[selfrotateNV12Data:nv12Data];

}

(2)yuv格式---->nv12格式

-(NSData*)rotateNV12Data:(NSData*)nv12Data{

intdegree =0;

switch(self.videoConfig.orientation) {

caseUIInterfaceOrientationLandscapeLeft:

degree =90;

break;

caseUIInterfaceOrientationLandscapeRight:

degree =270;

break;

default:

//do nothing

break;

}

if(degree !=0) {

uint8_t*src_nv12_bytes = (uint8_t*)nv12Data.bytes;

uint32_twidth = (uint32_t)self.videoConfig.width;

uint32_theight = (uint32_t)self.videoConfig.height;

uint32_tw_x_h = (uint32_t)(self.videoConfig.width*self.videoConfig.height);

uint8_t*rotatedI420Bytes =aw_alloc(nv12Data.length);

NV12ToI420Rotate(src_nv12_bytes, width,

src_nv12_bytes + w_x_h, width,

rotatedI420Bytes, height,

rotatedI420Bytes + w_x_h, height /2,

rotatedI420Bytes + w_x_h + w_x_h /4, height /2,

width, height, (RotationModeEnum)degree);

I420ToNV12(rotatedI420Bytes, height,

rotatedI420Bytes + w_x_h, height /2,

rotatedI420Bytes + w_x_h + w_x_h /4, height /2,

src_nv12_bytes, height, src_nv12_bytes + w_x_h, height,

height, width);

aw_free(rotatedI420Bytes);

}

returnnv12Data;

}

(3)nv12格式数据合成flv格式

-(aw_flv_video_tag*)encodeYUVDataToFlvTag:(NSData*)yuvData{

if(!_vEnSession) {

returnNULL;

}

//yuv变成转CVPixelBufferRef

OSStatusstatus =noErr;

//视频宽度

size_tpixelWidth =self.videoConfig.pushStreamWidth;

//视频高度

size_tpixelHeight =self.videoConfig.pushStreamHeight;

//现在要把NV12数据放入CVPixelBufferRef中,因为硬编码主要调用VTCompressionSessionEncodeFrame函数,此函数不接受yuv数据,但是接受CVPixelBufferRef类型。

CVPixelBufferRefpixelBuf =NULL;

//初始化pixelBuf,数据类型是kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,此类型数据格式同NV12格式相同。

CVPixelBufferCreate(NULL, pixelWidth, pixelHeight,kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,NULL, &pixelBuf);

// Lock address,锁定数据,应该是多线程防止重入操作。

if(CVPixelBufferLockBaseAddress(pixelBuf,0) !=kCVReturnSuccess){

[selfonErrorWithCode:AWEncoderErrorCodeLockSampleBaseAddressFaileddes:@"encode video lock base address failed"];

returnNULL;

}

//将yuv数据填充到CVPixelBufferRef中

size_ty_size =aw_stride(pixelWidth) * pixelHeight;

size_tuv_size = y_size /4;

uint8_t*yuv_frame = (uint8_t*)yuvData.bytes;

//处理y frame

uint8_t*y_frame =CVPixelBufferGetBaseAddressOfPlane(pixelBuf,0);

memcpy(y_frame, yuv_frame, y_size);

uint8_t*uv_frame =CVPixelBufferGetBaseAddressOfPlane(pixelBuf,1);

memcpy(uv_frame, yuv_frame + y_size, uv_size *2);

//硬编码CmSampleBufRef

//时间戳

uint32_tptsMs =self.manager.timestamp+1;//self.vFrameCount++ * 1000.f / self.videoConfig.fps;

CMTimepts =CMTimeMake(ptsMs,1000);

//硬编码主要其实就这一句。将携带NV12数据的PixelBuf送到硬编码器中,进行编码。

status =VTCompressionSessionEncodeFrame(_vEnSession, pixelBuf, pts,kCMTimeInvalid,NULL, pixelBuf,NULL);

if(status ==noErr) {

dispatch_semaphore_wait(self.vSemaphore,DISPATCH_TIME_FOREVER);

if(_naluData) {

//此处硬编码成功,_naluData内的数据即为h264视频帧。

//我们是推流,所以获取帧长度,转成大端字节序,放到数据的最前面

uint32_tnaluLen = (uint32_t)_naluData.length;

//小端转大端。计算机内一般都是小端,而网络和文件中一般都是大端。大端转小端和小端转大端算法一样,就是字节序反转就行了。

uint8_tnaluLenArr[4] = {naluLen >>24&0xff, naluLen >>16&0xff, naluLen >>8&0xff, naluLen &0xff};

//将数据拼在一起

NSMutableData*mutableData = [NSMutableDatadataWithBytes:naluLenArrlength:4];

[mutableDataappendData:_naluData];

//将h264数据合成flv tag,合成flvtag之后就可以直接发送到服务端了。后续会介绍

aw_flv_video_tag*video_tag =aw_encoder_create_video_tag((int8_t*)mutableData.bytes, mutableData.length, ptsMs,0,self.isKeyFrame);

//到此,编码工作完成,清除状态。

_naluData=nil;

_isKeyFrame=NO;

CVPixelBufferUnlockBaseAddress(pixelBuf,0);

CFRelease(pixelBuf);

returnvideo_tag;

}

}else{

[selfonErrorWithCode:AWEncoderErrorCodeEncodeVideoFrameFaileddes:@"encode video frame error"];

}

CVPixelBufferUnlockBaseAddress(pixelBuf,0);

CFRelease(pixelBuf);

returnNULL;

}

(4)发送视频flv到rtmp服务器

二、音频数据编码和推流

(1)将音频流转换成data数据

-(NSData*) convertAudioSmapleBufferToPcmData:(CMSampleBufferRef) audioSample{

//获取pcm数据大小

NSIntegeraudioDataSize =CMSampleBufferGetTotalSampleSize(audioSample);

//分配空间

int8_t*audio_data =aw_alloc((int32_t)audioDataSize);

//获取CMBlockBufferRef

//这个结构里面就保存了PCM数据

CMBlockBufferRefdataBuffer =CMSampleBufferGetDataBuffer(audioSample);

//直接将数据copy至我们自己分配的内存中

CMBlockBufferCopyDataBytes(dataBuffer,0, audioDataSize, audio_data);

//返回数据

return[NSDatadataWithBytesNoCopy:audio_datalength:audioDataSize];

}

(2)将音频data数据编码成acc格式并合成为flv

-(aw_flv_audio_tag*)encodePCMDataToFlvTag:(NSData*)pcmData{

self.curFramePcmData= pcmData;

AudioBufferListoutAudioBufferList = {0};

outAudioBufferList.mNumberBuffers=1;

outAudioBufferList.mBuffers[0].mNumberChannels= (uint32_t)self.audioConfig.channelCount;

outAudioBufferList.mBuffers[0].mDataByteSize=self.aMaxOutputFrameSize;

outAudioBufferList.mBuffers[0].mData=malloc(self.aMaxOutputFrameSize);

uint32_toutputDataPacketSize =1;

OSStatusstatus =AudioConverterFillComplexBuffer(_aConverter,aacEncodeInputDataProc, (__bridgevoid*_Nullable)(self), &outputDataPacketSize, &outAudioBufferList,NULL);

if(status ==noErr) {

NSData*rawAAC = [NSDatadataWithBytesNoCopy: outAudioBufferList.mBuffers[0].mDatalength:outAudioBufferList.mBuffers[0].mDataByteSize];

self.manager.timestamp+=1024*1000/self.audioConfig.sampleRate;

returnaw_encoder_create_audio_tag((int8_t*)rawAAC.bytes, rawAAC.length, (uint32_t)self.manager.timestamp, &_faacConfig);

}else{

[selfonErrorWithCode:AWEncoderErrorCodeAudioEncoderFaileddes:@"aac编码错误"];

}

returnNULL;

}

(3)发送音频flv到rtmp服务器

        至此,我们就把flv格式的音视频数据发送到了rtmp服务器,服务器通过cdn分发后我们用ijkplayer打开就可以播了

三、 需要注意的部分

1、获取完视频退出是要记得销毁会话

2 、编(解)码分硬(解)编码和软编(解)码

         软编码:使用CPU进行编码,性能高,低码率下通常质量低于硬编码器,但部分产品在GPU硬件平台移植了优秀的软编码算法(如X264)的,质量基本等同于软编码。

        硬编码:使用非CPU进行编码,如显卡GPU、专用的DSP、FPGA、ASIC芯片等,实现直接、简单,参数调整方便,升级易,但CPU负载重,性能较硬编码低,低码率下质量通常比硬编码要好一点。

你可能感兴趣的:(iOS)