libyuv NV12裁剪

有時候想對攝像頭采集的視頻流進(jìn)行區(qū)域裁剪,可以使用libyuv這個庫,原理就是先把NV12轉(zhuǎn)換為i420,對i420做裁剪悉抵,然后再把i420轉(zhuǎn)換為NV12,NV12再轉(zhuǎn)換為CVPixelBufferRef摘完,CVPixelBufferRef再轉(zhuǎn)換為CMSampleBufferRef姥饰。

這里有幾個注意點:
1.iOS13使用了64位對齊,也就是步長是64的倍數(shù)描焰。而之前的版本使用的是16位對齊媳否。
2.使用 libyuv::ConvertToI420 方法時栅螟,src_width需要填入步長而不是寬度荆秦,因為yuv內(nèi)部要根據(jù)步長來取U、V數(shù)據(jù)力图,如果是填入的寬度步绸,那么就會取值位移錯誤,導(dǎo)致轉(zhuǎn)換失真吃媒。
3.因為我是直接NV12數(shù)據(jù)轉(zhuǎn)換的瓤介,所以填寫的類型是:libyuv::FOURCC_NV12。應(yīng)該根據(jù)當(dāng)前數(shù)據(jù)的類型選擇對應(yīng)的格式赘那。
4.NV12轉(zhuǎn)換為CVPixelBufferRef時刑桑,填入對應(yīng)的步長:nv12_plane1_stride。

關(guān)于步長解釋:http://www.reibang.com/p/eace8c08b169

一:對NV12裁剪代碼如下:
+ (CVPixelBufferRef)convertNV12ToI420Screenshots:(CMSampleBufferRef)sampleBufRef screenshotsFrame:(CGRect)screenshotsFrame {
    int screenshots_x = screenshotsFrame.origin.x;
    int screenshots_y = screenshotsFrame.origin.y;
    int screenshots_width = screenshotsFrame.size.width;
    int screenshots_hight = screenshotsFrame.size.height;
    // 確保寬高是偶數(shù)
    if (screenshots_width % 2 != 0) {
        screenshots_width++;
    }
    if (screenshots_hight % 2 != 0) {
        screenshots_hight++;
    }
    //CVPixelBufferRef是CVImageBufferRef的別名,兩者操作幾乎一致募舟。
    //獲取CMSampleBuffer的圖像地址
    CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBufRef);
    if (!pixelBuffer) {
        return nil;
    }
    //表示開始操作數(shù)據(jù)
    CVPixelBufferLockBaseAddress(pixelBuffer, 0);
    //圖像高度(像素)
    size_t buffer_width = CVPixelBufferGetWidth(pixelBuffer);
    size_t buffer_height = CVPixelBufferGetHeight(pixelBuffer);
    //獲取CVImageBufferRef中的y數(shù)據(jù)
    uint8_t *src_y_frame = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
    //y stride
    size_t plane1_stride = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
    //uv stride
    size_t plane2_stride = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
    //y height
    size_t plane1_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
    //uv height
    size_t plane2_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
    //y_size
    size_t plane1_size = plane1_stride * plane1_height;
    //uv_size
    size_t plane2_size = plane2_stride * plane2_height;
    //yuv_size(內(nèi)存空間)
    size_t frame_size = plane1_size + plane2_size;
    
    // 截取區(qū)域不能超出原視頻大小
    if (screenshots_x >= buffer_width ||
        screenshots_width > buffer_width ||
        screenshots_x + screenshots_width > buffer_width ||
        screenshots_y >= buffer_height ||
        screenshots_hight > buffer_height ||
        screenshots_y + screenshots_hight > buffer_height) {
        return nil;
    }
    
    // 1.NV12數(shù)據(jù)進(jìn)行相應(yīng)的裁剪
    // 步長必須是16的倍數(shù)祠斧,因為涉及到字節(jié)對齊,而且iOS13和之前的版本處理方式不一樣拱礁,要注意
    int stride_length = 16;
    int scale_plane1_stride = screenshots_width;
    if ([UIDevice currentDevice].systemVersion.floatValue >= 13.0) {
        stride_length = 64;
    } else {
        stride_length = 16;
    }
    if ((screenshots_width % stride_length) != 0) {
        scale_plane1_stride = (screenshots_width / stride_length + 1) * stride_length;
    }
    
    int scale_plane2_stride = scale_plane1_stride;
    int scale_plane1_height = screenshots_hight;
    int scale_plane2_height = screenshots_hight / 2;
    int scale_plane1_size = scale_plane1_stride * scale_plane1_height;
    int scale_plane2_size = scale_plane2_stride * scale_plane2_height;
    int scale_frame_size = scale_plane1_size + scale_plane2_size;

    uint8* scale_buffer = (unsigned char *)malloc(scale_frame_size);
    uint8* scale_buffer_u = scale_buffer + scale_plane1_size;
    uint8* scale_buffer_v = scale_buffer_u + scale_plane1_size / 4;
    
    libyuv::ConvertToI420(/*const uint8 *src_frame*/ src_y_frame,
                          /*size_t src_size*/ frame_size,
                          /*uint8 *dst_y*/ scale_buffer,
                          /*int dst_stride_y*/ scale_plane1_stride,
                          /*uint8 *dst_u*/ scale_buffer_u,
                          /*int dst_stride_u*/ scale_plane1_stride >> 1,
                          /*uint8 *dst_v*/ scale_buffer_v,
                          /*int dst_stride_v*/ scale_plane1_stride >> 1,
                          /*int crop_x*/ screenshots_x,
                          /*int crop_y*/ screenshots_y,
                          /*int src_width*/ (int)plane1_stride, // 注意這里使用的是步長琢锋,不是寬度辕漂,因為yuv內(nèi)部要根據(jù)步長來取U、V數(shù)據(jù)
                          /*int src_height*/ (int)buffer_height,
                          /*int crop_width*/ screenshots_width,
                          /*int crop_height*/ screenshots_hight,
                          /*enum RotationMode rotation*/ libyuv::kRotate0,
                          /*uint32 format*/ libyuv::FOURCC_NV12);
    
    // 2.把縮放后的I420數(shù)據(jù)轉(zhuǎn)換為NV12
    int nv12_plane1_stride = scale_plane1_stride;
    int nv12_width = screenshots_width;
    int nv12_hight = screenshots_hight;
    int nv12_frame_size = scale_frame_size;
    
    uint8 *nv12_dst_y = (uint8 *)malloc(nv12_frame_size);
    uint8 *nv12_dst_uv = nv12_dst_y + nv12_plane1_stride * nv12_hight;

    libyuv::I420ToNV12(/*const uint8 *src_y*/ scale_buffer,
                       /*int src_stride_y*/ scale_plane1_stride,
                       /*const uint8 *src_u*/ scale_buffer_u,
                       /*int src_stride_u*/ scale_plane1_stride >> 1,
                       /*const uint8 *src_v*/ scale_buffer_v,
                       /*int src_stride_v*/ scale_plane1_stride >> 1,
                       /*uint8 *dst_y*/ nv12_dst_y,
                       /*int dst_stride_y*/ nv12_plane1_stride,
                       /*uint8 *dst_uv*/ nv12_dst_uv,
                       /*int dst_stride_uv*/ nv12_plane1_stride,
                       /*int width*/ nv12_width,
                       /*int height*/ nv12_hight);
    
    CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
    free(scale_buffer);
    
    // 4.NV12轉(zhuǎn)換為CVPixelBufferRef
    NSDictionary *pixelAttributes = @{(id)kCVPixelBufferIOSurfacePropertiesKey : @{}};
    CVPixelBufferRef dstPixelBuffer = NULL;
    CVReturn result = CVPixelBufferCreate(kCFAllocatorDefault,
                                          nv12_width, nv12_hight, kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
                                          (__bridge CFDictionaryRef)pixelAttributes, &dstPixelBuffer);

    CVPixelBufferLockBaseAddress(dstPixelBuffer, 0);
    uint8_t *yDstPlane = (uint8*)CVPixelBufferGetBaseAddressOfPlane(dstPixelBuffer, 0);
    memcpy(yDstPlane, nv12_dst_y, nv12_plane1_stride * nv12_hight);
    uint8_t *uvDstPlane = (uint8*)CVPixelBufferGetBaseAddressOfPlane(dstPixelBuffer, 1);
    memcpy(uvDstPlane, nv12_dst_uv, nv12_plane1_stride * nv12_hight / 2);
    if (result != kCVReturnSuccess) {
        NSLog(@"Unable to create cvpixelbuffer %d", result);
    }
    CVPixelBufferUnlockBaseAddress(dstPixelBuffer, 0);

    free(nv12_dst_y);

    return dstPixelBuffer;
}
二:CVPixelBufferRef轉(zhuǎn)換為CMSampleBufferRef:
// NV12數(shù)據(jù)轉(zhuǎn)換為數(shù)據(jù)流
+ (CMSampleBufferRef)pixelBufferToSampleBuffer:(CVPixelBufferRef)pixelBuffer {
    CMSampleBufferRef sampleBuffer;
    CMTime frameTime = CMTimeMakeWithSeconds([[NSDate date] timeIntervalSince1970], 1000000000);
    CMSampleTimingInfo timing = {frameTime, frameTime, kCMTimeInvalid};
    CMVideoFormatDescriptionRef videoInfo = NULL;
    CMVideoFormatDescriptionCreateForImageBuffer(NULL, pixelBuffer, &videoInfo);
    
    OSStatus status = CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer, true, NULL, NULL, videoInfo, &timing, &sampleBuffer);
    if (status != noErr) {
        NSLog(@"Failed to create sample buffer with error %d.", (int)status);
    }
    CVPixelBufferRelease(pixelBuffer);
    if (videoInfo) {
        CFRelease(videoInfo);
    }
    return sampleBuffer;
}
三:對NV12裁剪代碼2:

其實這個方法更多的是介紹怎么把i420進(jìn)行裁剪吴超。
我沒有單獨弄i420文件钉嘹,這里直接先把NV12轉(zhuǎn)換為i420,再進(jìn)行裁剪

+ (CVPixelBufferRef)convertNV12ToI420ScreenshotsType1:(CMSampleBufferRef)sampleBufRef screenshotsFrame:(CGRect)screenshotsFrame {
    int screenshots_x = screenshotsFrame.origin.x;
    int screenshots_y = screenshotsFrame.origin.y;
    int screenshots_width = screenshotsFrame.size.width;
    int screenshots_hight = screenshotsFrame.size.height;
    // 確保寬高是偶數(shù)
    if (screenshots_width % 2 != 0) {
        screenshots_width++;
    }
    if (screenshots_hight % 2 != 0) {
        screenshots_hight++;
    }
    //CVPixelBufferRef是CVImageBufferRef的別名,兩者操作幾乎一致鲸阻。
    //獲取CMSampleBuffer的圖像地址
    CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBufRef);
    if (!pixelBuffer) {
        return nil;
    }
    //表示開始操作數(shù)據(jù)
    CVPixelBufferLockBaseAddress(pixelBuffer, 0);
    //圖像寬度(像素)
    size_t buffer_width = CVPixelBufferGetWidth(pixelBuffer);
    //圖像高度(像素)
    size_t buffer_height = CVPixelBufferGetHeight(pixelBuffer);
    //獲取CVImageBufferRef中的y數(shù)據(jù)
    uint8_t *src_y_frame = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
    //獲取CMVImageBufferRef中的uv數(shù)據(jù)
    uint8_t *src_uv_frame =(unsigned char *) CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
    //y stride
    size_t plane1_stride = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
    //uv stride
    size_t plane2_stride = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
    //y height
    size_t plane1_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
    //uv height
    size_t plane2_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
    //y_size
    size_t plane1_size = plane1_stride * plane1_height;
    //uv_size
    size_t plane2_size = plane2_stride * plane2_height;
    //yuv_size(內(nèi)存空間)
    size_t frame_size = plane1_size + plane2_size;
    size_t buffer_u_strate = plane2_stride / 2;
    size_t buffer_v_strate = plane2_stride / 2;
    
    // 截取區(qū)域不能超出原視頻大小
    if (screenshots_x >= buffer_width ||
        screenshots_width > buffer_width ||
        screenshots_x + screenshots_width > buffer_width ||
        screenshots_y >= buffer_height ||
        screenshots_hight > buffer_height ||
        screenshots_y + screenshots_hight > buffer_height) {
        return nil;
    }

    // 1.NV12轉(zhuǎn)換為I420
    uint8* buffer_frame = (unsigned char *)malloc(frame_size);
    uint8* buffer_u = buffer_frame + plane1_size;
    uint8* buffer_v = buffer_u + plane1_size / 4;

    libyuv::NV12ToI420(/*const uint8 *src_y*/ src_y_frame,
                       /*int src_stride_y*/ (int)plane1_stride,
                       /*const uint8 *src_uv*/ src_uv_frame,
                       /*int src_stride_uv*/ (int)plane2_stride,
                       /*uint8 *dst_y*/ buffer_frame,
                       /*int dst_stride_y*/ (int)plane1_stride,
                       /*uint8 *dst_u*/ buffer_u,
                       /*int dst_stride_u*/ (int)buffer_u_strate,
                       /*uint8 *dst_v*/ buffer_v,
                       /*int dst_stride_v*/ (int)buffer_v_strate,
                       /*int width*/ (int)buffer_width,
                       /*int height*/ (int)buffer_height);
    
    // 2.I420數(shù)據(jù)進(jìn)行相應(yīng)的裁剪
    // 步長必須是16的倍數(shù)跋涣,因為涉及到字節(jié)對齊,而且iOS13和之前的版本處理方式不一樣赘娄,要注意
    int stride_length = 16;
    int scale_plane1_stride = screenshots_width;
    if ([UIDevice currentDevice].systemVersion.floatValue >= 13.0) {
        stride_length = 64;
    } else {
        stride_length = 16;
    }
    if ((screenshots_width % stride_length) != 0) {
        scale_plane1_stride = (screenshots_width / stride_length + 1) * stride_length;
    }
    
    int scale_plane2_stride = scale_plane1_stride;
    int scale_plane1_height = screenshots_hight;
    int scale_plane2_height = screenshots_hight / 2;
    int scale_plane1_size = scale_plane1_stride * scale_plane1_height;
    int scale_plane2_size = scale_plane2_stride * scale_plane2_height;
    int scale_frame_size = scale_plane1_size + scale_plane2_size;

    uint8* scale_buffer = (unsigned char *)malloc(scale_frame_size);
    uint8* scale_buffer_u = scale_buffer + scale_plane1_size;
    uint8* scale_buffer_v = scale_buffer_u + scale_plane1_size / 4;
    
    
    libyuv::ConvertToI420(/*const uint8 *src_frame*/ buffer_frame,
                          /*size_t src_size*/ frame_size,
                          /*uint8 *dst_y*/ scale_buffer,
                          /*int dst_stride_y*/ scale_plane1_stride,
                          /*uint8 *dst_u*/ scale_buffer_u,
                          /*int dst_stride_u*/ scale_plane1_stride >> 1,
                          /*uint8 *dst_v*/ scale_buffer_v,
                          /*int dst_stride_v*/ scale_plane1_stride >> 1,
                          /*int crop_x*/ screenshots_x,
                          /*int crop_y*/ screenshots_y,
                          /*int src_width*/ (int)plane1_stride,
                          /*int src_height*/ (int)buffer_height,
                          /*int crop_width*/ screenshots_width,
                          /*int crop_height*/ screenshots_hight,
                          /*enum RotationMode rotation*/ libyuv::kRotate0,
                          /*uint32 format*/ libyuv::FOURCC_I420);
    
    // 3.把縮放后的I420數(shù)據(jù)轉(zhuǎn)換為NV12
    int nv12_plane1_stride = scale_plane1_stride;
    int nv12_width = screenshots_width;
    int nv12_hight = screenshots_hight;
    int nv12_frame_size = scale_frame_size;
    
    uint8 *nv12_dst_y = (uint8 *)malloc(nv12_frame_size);
    uint8 *nv12_dst_uv = nv12_dst_y + nv12_plane1_stride * nv12_hight;

    libyuv::I420ToNV12(/*const uint8 *src_y*/ scale_buffer,
                       /*int src_stride_y*/ scale_plane1_stride,
                       /*const uint8 *src_u*/ scale_buffer_u,
                       /*int src_stride_u*/ scale_plane1_stride >> 1,
                       /*const uint8 *src_v*/ scale_buffer_v,
                       /*int src_stride_v*/ scale_plane1_stride >> 1,
                       /*uint8 *dst_y*/ nv12_dst_y,
                       /*int dst_stride_y*/ nv12_plane1_stride,
                       /*uint8 *dst_uv*/ nv12_dst_uv,
                       /*int dst_stride_uv*/ nv12_plane1_stride,
                       /*int width*/ nv12_width,
                       /*int height*/ nv12_hight);
    
    CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
    free(buffer_frame);
    free(scale_buffer);
    
    // 4.NV12轉(zhuǎn)換為CVPixelBufferRef
    NSDictionary *pixelAttributes = @{(id)kCVPixelBufferIOSurfacePropertiesKey : @{}};
    CVPixelBufferRef dstPixelBuffer = NULL;
    CVReturn result = CVPixelBufferCreate(kCFAllocatorDefault,
                                          nv12_width, nv12_hight, kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
                                          (__bridge CFDictionaryRef)pixelAttributes, &dstPixelBuffer);

    CVPixelBufferLockBaseAddress(dstPixelBuffer, 0);
    uint8_t *yDstPlane = (uint8*)CVPixelBufferGetBaseAddressOfPlane(dstPixelBuffer, 0);
    memcpy(yDstPlane, nv12_dst_y, nv12_plane1_stride * nv12_hight);
    uint8_t *uvDstPlane = (uint8*)CVPixelBufferGetBaseAddressOfPlane(dstPixelBuffer, 1);
    memcpy(uvDstPlane, nv12_dst_uv, nv12_plane1_stride * nv12_hight / 2);
    if (result != kCVReturnSuccess) {
        NSLog(@"Unable to create cvpixelbuffer %d", result);
    }
    CVPixelBufferUnlockBaseAddress(dstPixelBuffer, 0);

    free(nv12_dst_y);

    return dstPixelBuffer;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末仆潮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子遣臼,更是在濱河造成了極大的恐慌性置,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件揍堰,死亡現(xiàn)場離奇詭異鹏浅,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)屏歹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門隐砸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蝙眶,你說我怎么就攤上這事季希。” “怎么了幽纷?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵式塌,是天一觀的道長。 經(jīng)常有香客問我友浸,道長峰尝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任收恢,我火速辦了婚禮武学,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘伦意。我一直安慰自己火窒,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布驮肉。 她就那樣靜靜地躺著熏矿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上曲掰,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天疾捍,我揣著相機(jī)與錄音,去河邊找鬼栏妖。 笑死乱豆,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的吊趾。 我是一名探鬼主播宛裕,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼论泛!你這毒婦竟也來了揩尸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤屁奏,失蹤者是張志新(化名)和其女友劉穎岩榆,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坟瓢,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡勇边,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了折联。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片粒褒。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖诚镰,靈堂內(nèi)的尸體忽然破棺而出奕坟,到底是詐尸還是另有隱情,我是刑警寧澤清笨,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布月杉,位于F島的核電站,受9級特大地震影響函筋,放射性物質(zhì)發(fā)生泄漏沙合。R本人自食惡果不足惜奠伪,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一跌帐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧绊率,春花似錦谨敛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春炊甲,著一層夾襖步出監(jiān)牢的瞬間泥彤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工卿啡, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留吟吝,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓颈娜,卻偏偏與公主長得像剑逃,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子官辽,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容