美文网首页
ios直播基础篇三关于推流

ios直播基础篇三关于推流

作者: 子夏的不语 | 来源:发表于2017-09-25 11:22 被阅读0次

    一:推流需要的三方库和一些常用格式和协议介绍

    1.rtmp协议 :实时消息传输协议,Adobe Systems公司为Flash播放器和服务器之间音频、视频和数据传输开发的开  放协议,因为是开放协议所以都可以使用了。RTMP协议用于对象、视频、音频的传输。这个协议建立在TCP协议或者轮询HTTP协议之上。RTMP协议就像一个用来装数据包的容器,这些数据可以是FLV中的视音频数据。一个单一的连接可以通过不同的通道传输多路网络流,这些通道中的包都是按照固定大小的包传输的

    2.nginx:免费开源web服务器,常用来配置流媒体服务器。(后面会写一篇介绍如何在mac上搭建Nginx服务器)

    3.常用直播协议介绍与对比

    HLS:由Apple公司定义的用于实时流传输的协议,HLS基于HTTP协议实现,传输内容包括两部分,一是M3U8描述文件,二是TS媒体文件。可实现流媒体的直播和点播,主要应用在iOS系统

    HLS是以点播的技术方式来实现直播

    HLS是自适应码率流播,客户端会根据网络状况自动选择不同码率的视频流,条件允许的情况下使用高码率,网络繁忙的时候使用低码率,并且自动在二者间随意切换。这对移动设备网络状况不稳定的情况下保障流畅播放非常有帮助。

    实现方法是服务器端提供多码率视频流,并且在列表文件中注明,播放器根据播放进度和下载速度自动调整。

    HLS与RTMP对比:HLS主要是延时比较大,RTMP主要优势在于延时低

    HLS协议的小切片方式会生成大量的文件,存储或处理这些文件会造成大量资源浪费

    相比使用RTSP协议的好处在于,一旦切分完成,之后的分发过程完全不需要额外使用任何专门软件,普通的网络服务器即可,大大降低了CDN边缘服务器的配置要求,可以使用任何现成的CDN,而一般服务器很少支持RTSP。

    HTTP-FLV:基于HTTP协议流式的传输媒体内容。

    相对于RTMP,HTTP更简单和广为人知,内容延迟同样可以做到1~3秒,打开速度更快,因为HTTP本身没有复杂的状态交互。所以从延迟角度来看,HTTP-FLV要优于RTMP。

    RTSP:实时流传输协议,定义了一对多应用程序如何有效地通过IP网络传送多媒体数据.

    RTP:实时传输协议,RTP是建立在UDP协议上的,常与RTCP一起使用,其本身并没有提供按时发送机制或其它服务质量(QoS)保证,它依赖于低层服务去实现这一过程。

    RTCP:RTP的配套协议,主要功能是为RTP所提供的服务质量(QoS)提供反馈,收集相关媒体连接的统计信息,例如传输字节数,传输分组数,丢失分组数,单向和双向网络延迟等等

    关于协议的选择方面:即时性要求较高或有互动需求的可以采用RTMP,RTSP;对于有回放或跨平台需求的,推荐使用HLS

    4.视频封装格式:

    TS: 一种流媒体封装格式,流媒体封装有一个好处,就是不需要加载索引再播放,大大减少了首次载入的延迟,如果片子比较长,mp4文件的索引相当大,影响用户体验

    为什么要用TS:这是因为两个TS片段可以无缝拼接,播放器能连续播放

    FLV: 一种流媒体封装格式,由于它形成的文件极小、加载速度极快,使得网络观看视频文件成为可能,因此FLV格式成为了当今主流视频格式

    5 需要的库文件

    librtmp:这是一个C++的开源工程。主要作用是下载RTMP流媒体

    libfaac :将获取到的音频数据编码成acc格式以及将aac数据合成flv格式

    libx264:把视频原数据YUV编码压缩成H.264格式

    libyuv:将获取到的视频转化为yuv(NV12)格式

    二:推流流程

    关于推流流程我会主要用代码截图来展示

    1 :获取视频音频流 此处主要用不带美颜效果的系统获取方法

    (1):初始化视频设备

    (2)创建输入输出管道

    (3)创建会话

    (4)创建预览

    (5)在前面几步实现后我们就可以来用系统方法获取视频音频流了,这个方法是AVCaptureAudioDataOutputSampleBufferDelegate的代理方法,由于系统返回没有区分是视频数据还是音频数据 所以我们需要自己代码判断如下图:

    2.视频编码及推流

    (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服务器

    3 音频数据编码和推流

    (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直播基础篇三关于推流

          本文链接:https://www.haomeiwen.com/subject/rmurextx.html