ios直播基础篇三关于推流

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

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负载重,性能较硬编码低,低码率下质量通常比硬编码要好一点。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容