iOSのオーディオとビデオのキャプチャやフォーマット変換(RGB YUV RPM)

最近のプロジェクトでは、オーディオとビデオのiOSに関連する何かに遭遇し、ライブラリは、この記事を書くための進捗状況を構築し、BGRA、ずっと忘れないようにするために使用されるこのナレッジワークにlibyuv NV12を使用します。

1.オーディオとビデオキャプチャ(AVFoundationを使用して)

基本的なプロセス
1.初期化入力装置
2は、出力デバイスの初期化
3.ビデオおよびデータ管理キャプチャするために使用される、AVCaptureSessionを作成し
、プレビューを作成4.

// 初始化输入设备
- (void)initInputDevice{
    //获得输入设备
    AVCaptureDevice *backCaptureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];//取得后置摄像头
    AVCaptureDevice *frontCaptureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionFront];//取得前置摄像头
    
    //根据输入设备初始化设备输入对象,用于获得输入数据
    _backCamera = [[AVCaptureDeviceInput alloc]initWithDevice:backCaptureDevice error:nil];
    _frontCamera = [[AVCaptureDeviceInput alloc]initWithDevice:frontCaptureDevice error:nil];
    
    AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
    self.audioInputDevice = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:nil];
}
// 初始化输出设备
- (void)initOutputDevice{
    //创建数据获取线程
    dispatch_queue_t captureQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //视频数据输出
    self.videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
    //设置代理,需要当前类实现protocol:AVCaptureVideoDataOutputSampleBufferDelegate
    [self.videoDataOutput setSampleBufferDelegate:self queue:captureQueue];
    //抛弃过期帧,保证实时性
    [self.videoDataOutput setAlwaysDiscardsLateVideoFrames:YES];
    //设置输出格式为 yuv420
    [self.videoDataOutput setVideoSettings:@{
                                             (__bridge NSString *)kCVPixelBufferPixelFormatTypeKey:@(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)
                                             }];
    
    //音频数据输出
    self.audioDataOutput = [[AVCaptureAudioDataOutput alloc] init];
    //设置代理,需要当前类实现protocol:AVCaptureAudioDataOutputSampleBufferDelegate
    [self.audioDataOutput setSampleBufferDelegate:self queue:captureQueue];
}

// 创建AVCaptureSession
- (void)createAVCaptureSession{
    
    self.captureSession = [[AVCaptureSession alloc] init];
    
    // 改变会话的配置前一定要先开启配置,配置完成后提交配置改变
    [self.captureSession beginConfiguration];
    // 设置分辨率
    [self setVideoPreset];

    //将设备输入添加到会话中
    if ([self.captureSession canAddInput:self.backCamera]) {
        [self.captureSession addInput:self.backCamera];
    }
    
    if ([self.captureSession canAddInput:self.audioInputDevice]) {
        [self.captureSession addInput:self.audioInputDevice];
    }
    
    //将设备输出添加到会话中
    if ([self.captureSession canAddOutput:self.videoDataOutput]) {
        [self.captureSession addOutput:self.videoDataOutput];
    }
    
    if ([self.captureSession canAddOutput:self.audioDataOutput]) {
        [self.captureSession addOutput:self.audioDataOutput];
    }
    
    [self createPreviewLayer];
    
    //提交配置变更
    [self.captureSession commitConfiguration];
    
    [self startRunning];
    
}

// 创建预览视图
- (void)createPreviewLayer{
    
    [self.view addSubview:self.preView];
    
    //创建视频预览层,用于实时展示摄像头状态
    _captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:self.captureSession];
    
    _captureVideoPreviewLayer.frame = self.view.bounds;
    _captureVideoPreviewLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//填充模式
    //将视频预览层添加到界面中
    [self.view.layer addSublayer:_captureVideoPreviewLayer];
}


#pragma mark - Control start/stop capture or change camera
- (void)startRunning{
    if (!self.captureSession.isRunning) {
        [self.captureSession startRunning];
    }
}
- (void)stop{
    if (self.captureSession.isRunning) {
        [self.captureSession stopRunning];
    }
    
}

/**设置分辨率**/
- (void)setVideoPreset{
    if ([self.captureSession canSetSessionPreset:AVCaptureSessionPreset1920x1080])  {
        self.captureSession.sessionPreset = AVCaptureSessionPreset1920x1080;
    }else if ([self.captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
        self.captureSession.sessionPreset = AVCaptureSessionPreset1280x720;
    }else{
        self.captureSession.sessionPreset = AVCaptureSessionPreset640x480;
    }
    
}

/**
 *  取得指定位置的摄像头
 *
 *  @param position 摄像头位置
 *
 *  @return 摄像头设备
 */
-(AVCaptureDevice *)getCameraDeviceWithPosition:(AVCaptureDevicePosition )position{
    NSArray *cameras= [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    for (AVCaptureDevice *camera in cameras) {
        if ([camera position]==position) {
            return camera;
        }
    }
    return nil;
}

2. CMSampleBufferRef

この構造は、iOSで、リアルタイムのオーディオおよびビデオデータを取得するためにどのようにカメラの前に導入され、我々は、システムによって提供されたデータを取得するために取得したデータ、オーディオとビデオのインターフェースの種類を最後に知っておく必要がありCMSampleBufferRefに格納されていますオーディオ/ビデオデータを表し、それはフレームデータの内容と形式が含まれている、我々はその内容を取り出すことができ、データが抽出されて/私たちが望むものに変換します。
(:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange私たちは映像出力設定が出力フォーマットを設定したため)データのビデオフォーマットを表す保存CMSampleBufferRefは、YUV420ビデオ・フレームです。
以下のコールバックでは、データは最終CMSampleBufferRefを得ることができます

-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection{
            
}

3.yuv、NV12

ビデオは、実際に絵にしたときに、データのフレームによって接続されているビデオが作られます。
YUVは、RGBフォーマットで同様の画像記憶フォーマットです。
RGB形式の画像よく理解され、画像のコンピュータほとんどがRGBフォーマットで格納されています。
YUVで、Yは明るさを表し、Yのみデータが単独で画像を形成することができるが、この画像は灰色です。uおよびvは、色差を表す(uおよびvの別名:CB-青色差、CR-赤の色差)、
なぜYUV必要がありますか?
白黒テレビとの互換性のために、YUVフォーマットが使用される特定の歴史的な理由から、最初のテレビジョン信号があります。
YUV画像は、Yのみを残して、UVを削除、この画像は黒と白です。
YUVと帯域幅の最適化は、色を破棄することによって行うことができます。
例えば、人間の眼に隣接して色を放棄するために、バイト単位で半分のサイズを保存する必要がありますRGB形式の画像YUV420と比較して、それは非常に異なるものではありません。
YUV画像フォーマット、占有バイト数(幅*高さ+(幅*高さ)/ 4 +(幅*高さ)/ 4)=(幅*高さ)* 3/2
RGB形式の画像、占有されたバイト数(幅*高さ)* 3
伝送において、ビデオフォーマットYUVは、より柔軟(データのyuv3種類は、それぞれ、送信することができます)。
多くのビデオエンコーダは、最初はRGB形式をサポートしていません。しかし、すべてのビデオエンコーダは、YUVフォーマットをサポートしています。
それは私たちがここでYUV420フォーマットを使用するビデオです。
YUV420は、異なるデータ配列の形式を含む:I420、NV12、NV21
次のようなフォーマットであり、
I420フォーマット:Y、U、V 3つの部分が格納されている:U0、UL、のY0、Y1の... Ynのを... UN / 2、V0、VL ... / 2のVnの
NV12形式では:Y及びUV 2部店舗:Y0、Y1の... U0、V0、UL、VL ... UN / 2、/ 2のVnと、のYnの
NV21フォーマット:NV12とが、U及びVの逆の順序で
要約すると、順次表示するためのフォーマットを格納する以外は差はありません。
どのビデオフォーマットカメラの映像出力フォーマットを初期化するときセットに応じて、使用します。
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRangeに設定すると、出力のビデオフォーマットを示すことNV12である
kCVPixelFormatType_420YpCbCr8Planar、指示I420を設定します。
kCVPixelFormatType_32RGBA、表示BGRAに設定した場合。

GPUImageは、カメラ出力データの使用を設定するとNV12です。
一貫性を保つため、ここでNV12は、出力ビデオ形式を選択してください。

4.libyuv

libyuv Googleは、さまざまなRGBとYUVライブラリ間のスケーリング、相互変換、回転を達成するために、オープンソースです。このような加速などSSE、AVX、NEON SIMD命令をサポートするアーキテクチャ上で動作するようにコンパイルARM、WindowsやLinux、Macの、Androidや他のオペレーティングシステムのx86、x64ので利用可能な、クロスプラットフォームです。

RGBAにNV12ます5.使用libyuv

インポートlibyuvライブラリ、および検索パスを設定するか、エラーになります


1070332-d918c7620865ea90.png
ヘッダ検索パス
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection{
    CVPixelBufferRef initialPixelBuffer= CMSampleBufferGetImageBuffer(sampleBuffer);
    if (initialPixelBuffer == NULL) {
        return;
    }
    // 获取最终的音视频数据
    CVPixelBufferRef newPixelBuffer = [self convertVideoSmapleBufferToBGRAData:sampleBuffer];
    
    // 将CVPixelBufferRef转换成CMSampleBufferRef
    [self pixelBufferToSampleBuffer:newPixelBuffer];
    NSLog(@"initialPixelBuffer%@,newPixelBuffer%@", initialPixelBuffer, newPixelBuffer);

    // 使用完newPixelBuffer记得释放,否则内存会会溢出
    CFRelease(newPixelBuffer);
}


//转化
-(CVPixelBufferRef)convertVideoSmapleBufferToBGRAData:(CMSampleBufferRef)videoSample{
    
    //CVPixelBufferRef是CVImageBufferRef的别名,两者操作几乎一致。
    //获取CMSampleBuffer的图像地址
    CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(videoSample);
  //VideoToolbox解码后的图像数据并不能直接给CPU访问,需先用CVPixelBufferLockBaseAddress()锁定地址才能从主存访问,否则调用CVPixelBufferGetBaseAddressOfPlane等函数则返回NULL或无效值。值得注意的是,CVPixelBufferLockBaseAddress自身的调用并不消耗多少性能,一般情况,锁定之后,往CVPixelBuffer拷贝内存才是相对耗时的操作,比如计算内存偏移。
    CVPixelBufferLockBaseAddress(pixelBuffer, 0);
    //图像宽度(像素)
    size_t pixelWidth = CVPixelBufferGetWidth(pixelBuffer);
    //图像高度(像素)
    size_t pixelHeight = CVPixelBufferGetHeight(pixelBuffer);
    //获取CVImageBufferRef中的y数据
    uint8_t *y_frame = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
    //获取CMVImageBufferRef中的uv数据
    uint8_t *uv_frame =(unsigned char *) CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
    
    
    // 创建一个空的32BGRA格式的CVPixelBufferRef
    NSDictionary *pixelAttributes = @{(id)kCVPixelBufferIOSurfacePropertiesKey : @{}};
    CVPixelBufferRef pixelBuffer1 = NULL;
    CVReturn result = CVPixelBufferCreate(kCFAllocatorDefault,
                                          pixelWidth,pixelHeight,kCVPixelFormatType_32BGRA,
                                          (__bridge CFDictionaryRef)pixelAttributes,&pixelBuffer1);
    if (result != kCVReturnSuccess) {
        NSLog(@"Unable to create cvpixelbuffer %d", result);
        return NULL;
    }
    CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
    
    result = CVPixelBufferLockBaseAddress(pixelBuffer1, 0);
    if (result != kCVReturnSuccess) {
        CFRelease(pixelBuffer1);
        NSLog(@"Failed to lock base address: %d", result);
        return NULL;
    }
    
    // 得到新创建的CVPixelBufferRef中 rgb数据的首地址
    uint8_t *rgb_data = (uint8*)CVPixelBufferGetBaseAddress(pixelBuffer1);
    
    // 使用libyuv为rgb_data写入数据,将NV12转换为BGRA
    int ret = NV12ToARGB(y_frame, pixelWidth, uv_frame, pixelWidth, rgb_data, pixelWidth * 4, pixelWidth, pixelHeight);
    if (ret) {
        NSLog(@"Error converting NV12 VideoFrame to BGRA: %d", result);
        CFRelease(pixelBuffer1);
        return NULL;
    }
    CVPixelBufferUnlockBaseAddress(pixelBuffer1, 0);
    
    return pixelBuffer1;
}

// 将CVPixelBufferRef转换成CMSampleBufferRef
-(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 %zd.", status);
    }
    CVPixelBufferRelease(pixelBuffer);
    if(videoInfo)
        CFRelease(videoInfo);
    
    return sampleBuffer;
}

6.まとめ

AVFoundationの記事では、これはもはや詳細に行われ、多くありますが、ビデオキャプチャの使用を記載していないし、フォーマット変換、オーディオとビデオ関連知識をlibyuv使用しています。

ダウンロードのデモ

おすすめ

転載: blog.csdn.net/weixin_34405332/article/details/91019203
おすすめ