1小時學會:最簡單的iOS直播推流(五)yuv、pcm數據的介紹和獲取

最簡單的iOS 推流代碼,視頻捕獲,軟編碼(faac,x264),硬編碼(aac,h264),美顏,flv編碼,rtmp協議,陸續更新代碼解析,你想學的知識這裏都有,願意懂直播技術的同窗快來看!!git

源代碼:https://github.com/hardman/AWLivegithub

前面介紹瞭如何經過相機實時獲取音視頻數據。bash

咱們接下來就須要瞭解獲取到的數據究竟是什麼樣的。服務器

使用系統提供的接口獲取到的音視頻數據都保存在CMSampleBufferRef中。架構

使用GPUImamge獲取到的音頻數據爲CMSampleBufferRef,獲取到的視頻格式爲BGRA格式的二進制數據。ide

CMSampleBufferRef介紹

這個結構在iOS中表示一幀音頻/視頻數據。函數

它裏面包含了這一幀數據的內容和格式。post

咱們能夠把它的內容取出來,提取出/轉換成 咱們想要的數據。性能

表明視頻的CMSampleBufferRef中保存的數據是yuv420格式的視頻幀(由於咱們在視頻輸出設置中將輸出格式設爲:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)。優化

表明音頻的CMSampleBufferRef中保存的數據是PCM格式的音頻幀。

yuv是什麼?NV12又是什麼?

視頻是由一幀一幀的數據鏈接而成,而一幀視頻數據其實就是一張圖片。

yuv是一種圖片儲存格式,跟RGB格式相似。

RGB格式的圖片很好理解,計算機中的大多數圖片,都是以RGB格式存儲的。

yuv中,y表示亮度,單獨只有y數據就能夠造成一張圖片,只不過這張圖片是灰色的。u和v表示色差(u和v也被稱爲:Cb-藍色差,Cr-紅色差),

爲何要yuv?

有必定歷史緣由,最先的電視信號,爲了兼容黑白電視,採用的就是yuv格式。

一張yuv的圖像,去掉uv,只保留y,這張圖片就是黑白的。

並且yuv能夠經過拋棄色差來進行帶寬優化。

好比yuv420格式圖像相比RGB來講,要節省一半的字節大小,拋棄相鄰的色差對於人眼來講,差異不大。

一張yuv格式的圖像,佔用字節數爲 (width * height + (width * height) / 4 + (width * height) / 4) = (width * height) * 3 / 2 一張RGB格式的圖像,佔用字節數爲(width * height) * 3

在傳輸上,yuv格式的視頻也更靈活(yuv3種數據可分別傳輸)。

不少視頻編碼器最初是不支持rgb格式的。可是全部的視頻編碼器都支持yuv格式。

綜合來說,咱們選擇使用yuv格式,因此咱們編碼以前,首先將視頻數據轉成yuv格式。

咱們這裏使用的就是yuv420格式的視頻。

yuv420也包含不一樣的數據排列格式: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的順序相反。

綜合來講,除了存儲順序不一樣以外,上述格式對於顯示來講沒有任何區別。

使用哪一種視頻的格式,取決於初始化相機時設置的視頻輸出格式。 設置爲kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange時,表示輸出的視頻格式爲NV12; 設置爲kCVPixelFormatType_420YpCbCr8Planar時,表示使用I420。

GPUImage設置相機輸出數據時,使用的就是NV12.

爲了一致,咱們這裏也選擇NV12格式輸出視頻。

PCM是什麼?

脈衝編碼調製,實際上是將不規則的模擬信號轉換成數字信號,這樣就能夠經過物理介質存儲起來。

而聲音也是一種特定頻率(20-20000HZ)的模擬信號,也能夠經過這種技術轉換成數字信號,從而保存下來。

PCM格式,就是錄製聲音時,保存的最原始的聲音數據格式。

相信你應該據說過wav格式的音頻,它其實就是給PCM數據流加上一段header數據,就成爲了wav格式。

而wav格式有時候之因此被稱爲無損格式,就是由於他保存的是原始pcm數據(也跟採樣率和比特率有關)。

像咱們耳熟能詳的那些音頻格式,mp3,aac等等,都是有損壓縮,爲了節約佔用空間,在不多損失音效的基礎上,進行最大程度的壓縮。

全部的音頻編碼器,都支持pcm編碼,並且錄製的聲音,默認也是PCM格式,因此咱們下一步就是要獲取錄製的PCM數據。

從CMSampleBufferRef中提取yuv數據

在前面的文章(使用系統接口捕獲視頻)中,初始化輸出設備時,咱們將輸出的數據設置爲kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange。 所以在CMSampleBufferRef中保存的是yuv420(NV12)格式數據。 經過下面的方法將CMSampleBufferRef轉爲yuv420(NV12)。

// AWVideoEncoder.m文件
-(NSData *) convertVideoSmapleBufferToYuvData:(CMSampleBufferRef) videoSample{
    // 獲取yuv數據
    // 經過CMSampleBufferGetImageBuffer方法,得到CVImageBufferRef。
    // 這裏面就包含了yuv420(NV12)數據的指針
    CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(videoSample);
    
    //表示開始操做數據
    CVPixelBufferLockBaseAddress(pixelBuffer, 0);
    
    //圖像寬度(像素)
    size_t pixelWidth = CVPixelBufferGetWidth(pixelBuffer);
    //圖像高度(像素)
    size_t pixelHeight = CVPixelBufferGetHeight(pixelBuffer);
    //yuv中的y所佔字節數
    size_t y_size = pixelWidth * pixelHeight;
    //yuv中的uv所佔的字節數
    size_t uv_size = y_size / 2;
    
    uint8_t *yuv_frame = aw_alloc(uv_size + 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);
    
    CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
    
    //返回數據
    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數據長度 = (寬 * 高) * 3 / 2
    int yuv_len = w_x_h * 3 / 2;
    
    //yuv數據
    uint8_t *yuv_bytes = malloc(yuv_len);
    
    //ARGBToNV12這個函數是libyuv這個第三方庫提供的一個將bgra圖片轉爲yuv420格式的一個函數。
    //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數據

// AWAudioEncoder.m 文件
-(NSData *) convertAudioSmapleBufferToPcmData:(CMSampleBufferRef) audioSample{
    //獲取pcm數據大小
    NSInteger audioDataSize = CMSampleBufferGetTotalSampleSize(audioSample);
    
    //分配空間
    int8_t *audio_data = aw_alloc((int32_t)audioDataSize);
    
    //獲取CMBlockBufferRef
    //這個結構裏面就保存了 PCM數據
    CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(audioSample);
    //直接將數據copy至咱們本身分配的內存中
    CMBlockBufferCopyDataBytes(dataBuffer, 0, audioDataSize, audio_data);
    
    //返回數據
    return [NSData dataWithBytesNoCopy:audio_data length:audioDataSize];
}
複製代碼

至此咱們已經將捕獲的視頻數據轉爲了yuv420格式,將音頻數據轉爲了pcm格式。

接下來就能夠對這些數據進行各類編碼了。編碼完成後,就能夠將數據發送給服務器了。

文章列表

  1. 1小時學會:最簡單的iOS直播推流(一)項目介紹
  2. 1小時學會:最簡單的iOS直播推流(二)代碼架構概述
  3. 1小時學會:最簡單的iOS直播推流(三)使用系統接口捕獲音視頻
  4. 1小時學會:最簡單的iOS直播推流(四)如何使用GPUImage,如何美顏
  5. 1小時學會:最簡單的iOS直播推流(五)yuv、pcm數據的介紹和獲取
  6. 1小時學會:最簡單的iOS直播推流(六)h26四、aac、flv介紹
  7. 1小時學會:最簡單的iOS直播推流(七)h264/aac 硬編碼
  8. 1小時學會:最簡單的iOS直播推流(八)h264/aac 軟編碼
  9. 1小時學會:最簡單的iOS直播推流(九)flv 編碼與音視頻時間戳同步
  10. 1小時學會:最簡單的iOS直播推流(十)librtmp使用介紹
  11. 1小時學會:最簡單的iOS直播推流(十一)sps&pps和AudioSpecificConfig介紹(完結)
相關文章
相關標籤/搜索