最簡單的iOS 推流代碼,視頻捕獲,軟編碼(faac,x264),硬編碼(aac,h264),美顏,flv編碼,rtmp協(xié)議,陸續(xù)更新代碼解析,你想學的知識這里都有,愿意懂直播技術的同學快來看??!
前面介紹了如何通過相機實時獲取音視頻數(shù)據(jù)。
我們接下來就需要了解獲取到的數(shù)據(jù)到底是什么樣的。
使用系統(tǒng)提供的接口獲取到的音視頻數(shù)據(jù)都保存在CMSampleBufferRef中。
使用GPUImamge獲取到的音頻數(shù)據(jù)為CMSampleBufferRef,獲取到的視頻格式為BGRA格式的二進制數(shù)據(jù)。
CMSampleBufferRef介紹
這個結構在iOS中表示一幀音頻/視頻數(shù)據(jù)。
它里面包含了這一幀數(shù)據(jù)的內(nèi)容和格式。
我們可以把它的內(nèi)容取出來,提取出/轉換成 我們想要的數(shù)據(jù)。
代表視頻的CMSampleBufferRef中保存的數(shù)據(jù)是yuv420格式的視頻幀(因為我們在視頻輸出設置中將輸出格式設為:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)。
代表音頻的CMSampleBufferRef中保存的數(shù)據(jù)是PCM格式的音頻幀。
yuv是什么?NV12又是什么?
視頻是由一幀一幀的數(shù)據(jù)連接而成,而一幀視頻數(shù)據(jù)其實就是一張圖片。
yuv是一種圖片儲存格式,跟RGB格式類似。
RGB格式的圖片很好理解,計算機中的大多數(shù)圖片,都是以RGB格式存儲的。
yuv中,y表示亮度,單獨只有y數(shù)據(jù)就可以形成一張圖片,只不過這張圖片是灰色的。u和v表示色差(u和v也被稱為:Cb-藍色差,Cr-紅色差),
為什么要yuv?
有一定歷史原因,最早的電視信號,為了兼容黑白電視,采用的就是yuv格式。
一張yuv的圖像,去掉uv,只保留y,這張圖片就是黑白的。
而且yuv可以通過拋棄色差來進行帶寬優(yōu)化。
比如yuv420格式圖像相比RGB來說,要節(jié)省一半的字節(jié)大小,拋棄相鄰的色差對于人眼來說,差別不大。
一張yuv格式的圖像,占用字節(jié)數(shù)為 (width * height + (width * height) / 4 + (width * height) / 4) = (width * height) * 3 / 2
一張RGB格式的圖像,占用字節(jié)數(shù)為(width * height) * 3
在傳輸上,yuv格式的視頻也更靈活(yuv3種數(shù)據(jù)可分別傳輸)。
很多視頻編碼器最初是不支持rgb格式的。但是所有的視頻編碼器都支持yuv格式。
綜合來講,我們選擇使用yuv格式,所以我們編碼之前,首先將視頻數(shù)據(jù)轉成yuv格式。
我們這里使用的就是yuv420格式的視頻。
yuv420也包含不同的數(shù)據(jù)排列格式:I420,NV12,NV21.
其格式分別如下,
I420格式:y,u,v 3個部分分別存儲:Y0,Y1...Yn,U0,U1...Un/2,V0,V1...Vn/2
NV12格式:y和uv 2個部分分別存儲:Y0,Y1...Yn,U0,V0,U1,V1...Un/2,Vn/2
NV21格式:同NV12,只是U和V的順序相反。
綜合來說,除了存儲順序不同之外,上述格式對于顯示來說沒有任何區(qū)別。
使用哪種視頻的格式,取決于初始化相機時設置的視頻輸出格式。
設置為kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange時,表示輸出的視頻格式為NV12;
設置為kCVPixelFormatType_420YpCbCr8Planar時,表示使用I420。
GPUImage設置相機輸出數(shù)據(jù)時,使用的就是NV12.
為了一致,我們這里也選擇NV12格式輸出視頻。
PCM是什么?
脈沖編碼調(diào)制,其實是將不規(guī)則的模擬信號轉換成數(shù)字信號,這樣就可以通過物理介質存儲起來。
而聲音也是一種特定頻率(20-20000HZ)的模擬信號,也可以通過這種技術轉換成數(shù)字信號,從而保存下來。
PCM格式,就是錄制聲音時,保存的最原始的聲音數(shù)據(jù)格式。
相信你應該聽說過wav格式的音頻,它其實就是給PCM數(shù)據(jù)流加上一段header數(shù)據(jù),就成為了wav格式。
而wav格式有時候之所以被稱為無損格式,就是因為他保存的是原始pcm數(shù)據(jù)(也跟采樣率和比特率有關)。
像我們耳熟能詳?shù)哪切┮纛l格式,mp3,aac等等,都是有損壓縮,為了節(jié)約占用空間,在很少損失音效的基礎上,進行最大程度的壓縮。
所有的音頻編碼器,都支持pcm編碼,而且錄制的聲音,默認也是PCM格式,所以我們下一步就是要獲取錄制的PCM數(shù)據(jù)。
從CMSampleBufferRef中提取yuv數(shù)據(jù)
在前面的文章(使用系統(tǒng)接口捕獲視頻)中,初始化輸出設備時,我們將輸出的數(shù)據(jù)設置為kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange。
因此在CMSampleBufferRef中保存的是yuv420(NV12)格式數(shù)據(jù)。
通過下面的方法將CMSampleBufferRef轉為yuv420(NV12)。
// AWVideoEncoder.m文件
-(NSData *) convertVideoSmapleBufferToYuvData:(CMSampleBufferRef) videoSample{
// 獲取yuv數(shù)據(jù)
// 通過CMSampleBufferGetImageBuffer方法,獲得CVImageBufferRef。
// 這里面就包含了yuv420(NV12)數(shù)據(jù)的指針
CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(videoSample);
//表示開始操作數(shù)據(jù)
CVPixelBufferLockBaseAddress(pixelBuffer, 0);
//圖像寬度(像素)
size_t pixelWidth = CVPixelBufferGetWidth(pixelBuffer);
//圖像高度(像素)
size_t pixelHeight = CVPixelBufferGetHeight(pixelBuffer);
//yuv中的y所占字節(jié)數(shù)
size_t y_size = pixelWidth * pixelHeight;
//yuv中的uv所占的字節(jié)數(shù)
size_t uv_size = y_size / 2;
uint8_t *yuv_frame = aw_alloc(uv_size + y_size);
//獲取CVImageBufferRef中的y數(shù)據(jù)
uint8_t *y_frame = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
memcpy(yuv_frame, y_frame, y_size);
//獲取CMVImageBufferRef中的uv數(shù)據(jù)
uint8_t *uv_frame = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
memcpy(yuv_frame + y_size, uv_frame, uv_size);
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
//返回數(shù)據(jù)
return [NSData dataWithBytesNoCopy:yuv_frame length:y_size + uv_size];
}
將GPUImage獲取到的BGRA格式的圖片轉成yuv(NV12)格式
//AWGPUImageAVCapture.m文件
-(void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex{
[super newFrameReadyAtTime:frameTime atIndex:textureIndex];
if(!self.capture || !self.capture.isCapturing){
return;
}
//將bgra轉為yuv
//圖像寬度
int width = imageSize.width;
//圖像高度
int height = imageSize.height;
//寬*高
int w_x_h = width * height;
//yuv數(shù)據(jù)長度 = (寬 * 高) * 3 / 2
int yuv_len = w_x_h * 3 / 2;
//yuv數(shù)據(jù)
uint8_t *yuv_bytes = malloc(yuv_len);
//ARGBToNV12這個函數(shù)是libyuv這個第三方庫提供的一個將bgra圖片轉為yuv420格式的一個函數(shù)。
//libyuv是google提供的高性能的圖片轉碼操作。支持大量關于圖片的各種高效操作,是視頻推流不可缺少的重要組件,你值得擁有。
[self lockFramebufferForReading];
ARGBToNV12(self.rawBytesForImage, width * 4, yuv_bytes, width, yuv_bytes + w_x_h, width, width, height);
[self unlockFramebufferAfterReading];
NSData *yuvData = [NSData dataWithBytesNoCopy:yuv_bytes length:yuv_len];
[self.capture sendVideoYuvData:yuvData];
}
從CMSampleBufferRef中提取PCM數(shù)據(jù)
// AWAudioEncoder.m 文件
-(NSData *) convertAudioSmapleBufferToPcmData:(CMSampleBufferRef) audioSample{
//獲取pcm數(shù)據(jù)大小
NSInteger audioDataSize = CMSampleBufferGetTotalSampleSize(audioSample);
//分配空間
int8_t *audio_data = aw_alloc((int32_t)audioDataSize);
//獲取CMBlockBufferRef
//這個結構里面就保存了 PCM數(shù)據(jù)
CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(audioSample);
//直接將數(shù)據(jù)copy至我們自己分配的內(nèi)存中
CMBlockBufferCopyDataBytes(dataBuffer, 0, audioDataSize, audio_data);
//返回數(shù)據(jù)
return [NSData dataWithBytesNoCopy:audio_data length:audioDataSize];
}
至此我們已經(jīng)將捕獲的視頻數(shù)據(jù)轉為了yuv420格式,將音頻數(shù)據(jù)轉為了pcm格式。
接下來就可以對這些數(shù)據(jù)進行各種編碼了。編碼完成后,就可以將數(shù)據(jù)發(fā)送給服務器了。
文章列表
- 1小時學會:最簡單的iOS直播推流(一)項目介紹
- 1小時學會:最簡單的iOS直播推流(二)代碼架構概述
- 1小時學會:最簡單的iOS直播推流(三)使用系統(tǒng)接口捕獲音視頻
- 1小時學會:最簡單的iOS直播推流(四)如何使用GPUImage,如何美顏
- 1小時學會:最簡單的iOS直播推流(五)yuv、pcm數(shù)據(jù)的介紹和獲取
- 1小時學會:最簡單的iOS直播推流(六)h264、aac、flv介紹
- 1小時學會:最簡單的iOS直播推流(七)h264/aac 硬編碼
- 1小時學會:最簡單的iOS直播推流(八)h264/aac 軟編碼
- 1小時學會:最簡單的iOS直播推流(九)flv 編碼與音視頻時間戳同步
- 1小時學會:最簡單的iOS直播推流(十)librtmp使用介紹
- 1小時學會:最簡單的iOS直播推流(十一)sps&pps和AudioSpecificConfig介紹(完結)