1. 程式人生 > >1小時學會:最簡單的iOS直播推流(五)yuv、pcm資料的介紹和獲取

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

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

前面介紹瞭如何通過相機實時獲取音視訊資料。

我們接下來就需要了解獲取到的資料到底是什麼樣的。

使用GPUImamge獲取到的音訊資料為CMSampleBufferRef,獲取到的視訊格式為BGRA格式的二進位制資料。

CMSampleBufferRef介紹

這個結構在iOS中表示一幀音訊/視訊資料。

它裡面包含了這一幀資料的內容和格式。

我們可以把它的內容取出來,提取出/轉換成 我們想要的資料。

代表視訊的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格式。

接下來就可以對這些資料進行各種編碼了。編碼完成後,就可以將資料傳送給伺服器了。

文章列表