关于iOS12+屏幕共享-总结篇

之前写到了 屏幕共享的调起进程间的通讯CFNotificationCenterGetDarwinNotifyCenter进程间通信-App Groups
此篇文章基于屏幕共享的调起进程间通信-App Groups、实现屏幕流的获取及推送(使用的aliRTC)

由于之前的socket传输一直不稳定、更换了传输方式。

\color{red}{tip:}aliRTC使用的是1.19版本以上、\color{red}{外部视频输入接口}已经开放了\color{red}{AliRtcVideosourceScreenShareType}通道、如果使用以下版本,请继续使用\color{red}{AliRtcVideosourceCameraLargeType}通道(把屏幕流推入此通道即可)
可参考:官网文档

视频流处理

//
//  AppGroupData.h
//  Demo
//
//  Created by 斌小狼 on 2021/4/1.
//  Copyright © 2021 bxl. All rights reserved.
//

#import <AVFoundation/AVFoundation.h>

static NSString * _Nonnull kUserDefaultFrame = @"KEY_BXL_DEFAULT_FRAME"; // 接收屏幕共享(屏幕流)监听的Key
static NSString * _Nonnull kUserDefaultState = @"KEY_BXL_DEFAULT_STATE"; // 接收屏幕共享(开始/结束 状态)监听的Key

static NSString * _Nonnull kPropFormat = @"format";
static NSString * _Nonnull kPropWidth = @"width";
static NSString * _Nonnull kPropHeight = @"height";
static NSString * _Nonnull kPropStrideY = @"strideY";
static NSString * _Nonnull kPropStrideU = @"strideU";
static NSString * _Nonnull kPropStrideV = @"strideV";
static NSString * _Nonnull kPropDataLength = @"dataLength";
static NSString * _Nonnull kPropData = @"data";
static NSString * _Nonnull kPropRotation = @"rotation";

@interface AppGroupData : NSObject

+ (NSDictionary *_Nonnull)packetWithSampleBuffer:(CMSampleBufferRef _Nullable )sampleBuffer;

@end
//
//  AppGroupData.m
//  Demo
//
//  Created by 斌小狼 on 2021/4/1.
//  Copyright © 2021 bxl. All rights reserved.
//

#import "AppGroupData.h"
#import <CoreMedia/CoreMedia.h>
#import <ReplayKit/ReplayKit.h>
#import <Foundation/Foundation.h>

@interface AppGroupData ()

@end

@implementation AppGroupData

+ (NSDictionary *_Nonnull)packetWithSampleBuffer:(CMSampleBufferRef _Nullable )sampleBuffer{
    // output data
    int16_t format = -1;
    int32_t strideY = -1;
    int32_t strideU = -1;
    int32_t strideV = -1;
    uint8_t * dataPtr = NULL;
    int32_t width = -1;
    int32_t height = -1;
    uint32_t dataLength = 0;
    int32_t rotation = 0;
    // get CVPixelBuffer
    CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    const OSType pixel_format = CVPixelBufferGetPixelFormatType(pixelBuffer);
    // CVPixelBuffer to yuv(nv12) data
    if(pixel_format == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange ||
       pixel_format == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) {
        CVPixelBufferLockBaseAddress(pixelBuffer, 0);
        size_t w = CVPixelBufferGetWidth(pixelBuffer);
        size_t h = CVPixelBufferGetHeight(pixelBuffer);
        size_t src_y_stride = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
        size_t src_uv_stride = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
       
        size_t bufferSize = w * h * 3 / 2;
        // buffer
        uint8_t * buffer = (uint8_t*)malloc(bufferSize);
        unsigned char* dst = buffer;
        unsigned char* src_y = (unsigned char*)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
        unsigned char* src_uv = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
        // copy y
        size_t height_y = h;
        for (unsigned int rIdx = 0; rIdx < height_y; ++rIdx, dst += w, src_y += src_y_stride) {
            memcpy(dst, src_y, w);
        }
        // copy uv
        size_t height_uv = h >> 1;
        for (unsigned int rIdx = 0; rIdx < height_uv; ++rIdx, dst += w, src_uv += src_uv_stride) {
            memcpy(dst, src_uv, w);
        }
        CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
        
        // frame
        format = 3; // AliRtcVideoFormat_NV12;
     
        strideY = (int32_t)w;
        strideU = (int32_t)w;
        strideV = (int32_t)w / 2;
        dataPtr = buffer;
        width = (int32_t)w;
        height = (int32_t)h;
        dataLength = bufferSize;
        
    } else if(pixel_format == kCVPixelFormatType_32BGRA){ // CVPixelBuffer to rgb data
        CVPixelBufferLockBaseAddress(pixelBuffer, 0);
        size_t w = CVPixelBufferGetWidth(pixelBuffer);
        size_t h = CVPixelBufferGetHeight(pixelBuffer);
        uint8_t *buffer = (uint8_t *)CVPixelBufferGetBaseAddress(pixelBuffer);
        size_t stride = CVPixelBufferGetBytesPerRow(pixelBuffer);
        CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
        
        // frame
        format = 0; // AliRtcVideoFormat_BGRA;
        strideY = (int32_t)stride;
        strideU = 0;
        strideV = 0;
        dataPtr = buffer;
        width = (int32_t)w;
        height = (int32_t)h;
        dataLength = stride * h;
    } else {
        NSLog(@"(Error)unsupported pixelBuffer format");
        return NULL;
    }
    if(format == -1 ||  dataPtr == NULL || width == -1 || height == -1 || dataLength == 0){
        NSLog(@"(Error)wrong output params");
        return NULL;
    }
    int32_t orientation = ((NSNumber *)CMGetAttachment(sampleBuffer,
                                                            (__bridge CFStringRef)RPVideoSampleOrientationKey,
                                                          NULL)).intValue;
    switch (orientation) {
        case kCGImagePropertyOrientationUp:
            rotation = 0;
            break;
        case kCGImagePropertyOrientationLeft:
            rotation = 90;
            break;
        case kCGImagePropertyOrientationDown:
            rotation = 180;
            break;
        case kCGImagePropertyOrientationRight:
            rotation = 270;
            break;
        default:
            break;
    }
    //int64_t timeStampNs = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) * 1000000000;
    NSData *data = [NSData dataWithBytesNoCopy:dataPtr length:dataLength];
    // 组合成frame(一般屏幕采集都为nv12)
    NSDictionary *nv12Frame = @{
        kPropFormat: @(format),
        kPropWidth: @(width),
        kPropHeight: @(height),
        kPropStrideY: @(strideY),
        kPropStrideU: @(strideU),
        kPropStrideV: @(strideV),
        kPropDataLength: @(dataLength),
        kPropData: data,
        kPropRotation: @(rotation),
    };
    return nv12Frame;
}
@end

在Extension中获取数据流

//
//  SampleHandler.m
//  Demo
//
//  Created by 斌小狼 on 2021/4/1.
//  Copyright © 2021 bxl. All rights reserved.
//


#import "SampleHandler.h"
#import "AppGroupData.h"

static NSString * _Nonnull kAppGroup = @"你的app Group命名";

@interface SampleHandler()

@property (nonatomic, strong) NSUserDefaults *userDefaults;

@end

@implementation SampleHandler

- (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo {
    // User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional.
    self.userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kAppGroup];
    [self.userDefaults setObject:@{@"state":@"初始化"} forKey:kUserDefaultState];//开始字段
}

- (void)broadcastPaused {
    // User has requested to pause the broadcast. Samples will stop being delivered.
    NSLog(@"暂停");
}

- (void)broadcastResumed {
    // User has requested to resume the broadcast. Samples delivery will resume.
    NSLog(@"继续");
}

- (void)broadcastFinished {
    // User has requested to finish the broadcast.
    [self.userDefaults setObject:@{@"state":@"停止"} forKey:kUserDefaultState];//结束字段
    
}
- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
    //监听数据回流:
    switch (sampleBufferType) {
        case RPSampleBufferTypeVideo:
            @autoreleasepool {
                NSDictionary *nv12Frame = [AppGroupData packetWithSampleBuffer:sampleBuffer];
                [self.userDefaults setObject:nv12Frame forKey:kUserDefaultFrame];//屏幕流数据
                [self.userDefaults synchronize];
            }
            // Handle video sample buffer
            break;
        case RPSampleBufferTypeAudioApp:
            // Handle audio sample buffer for app audio
            //            NSLog(@"--app audio---");
            //音频流信息:44100,双声道,16bit
            break;
        case RPSampleBufferTypeAudioMic:
            // Handle audio sample buffer for mic audio
            //            NSLog(@"-------输出--mic audio---");
            //音频流信息:48000,单声道,16bit
            //            [self sendAudioData:sampleBuffer];
            break;
        default:
            break;
    }
}

@end

hostApp中屏幕流获取及使用

//
//  RTCSampleChatViewController.m
//  RtcSample
//
//  Created by 斌小狼 on 2021/4/1.
//  Copyright © 2021年 bxl. All rights reserved.
//

#import "RTCSampleChatViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "AppGroupData.h"
#import <ReplayKit/ReplayKit.h>

static NSString * _Nonnull kAppGroup = @"你的app Group命名";
static void *KVOContext = &KVOContext;

@interface RTCSampleChatViewController
//....
//....
//....
@property (nonatomic, strong) NSUserDefaults *userDefaults;
@end

@implementation RTCSampleChatViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //....
    //....
    //通过UserDefaults建立数据通道
    [self setupUserDefaults];      
}

- (void)setupUserDefaults{
    // 通过UserDefaults建立数据通道,接收Extension传递来的视频帧
    self.userDefaults = [[NSUserDefaults alloc] initWithSuiteName:kAppGroup];
    [self.userDefaults setObject:@{@"state":@"x"} forKey:kUserDefaultState];//给状态一个默认值
    [self.userDefaults addObserver:self forKeyPath:kUserDefaultState options:NSKeyValueObservingOptionNew context:KVOContext];
    [self.userDefaults addObserver:self forKeyPath:kUserDefaultFrame options:NSKeyValueObservingOptionNew context:KVOContext];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    if ([keyPath isEqualToString:kUserDefaultState]) {
        NSDictionary *string = change[NSKeyValueChangeNewKey];
        if ([string[@"state"] isEqual:@"初始化"]) {
            //开启 RTC:外部视频输入通道,开始推送屏幕流(configLocalScreenPublish)
            [self screenShareStart];
        }
        if ([string[@"state"] isEqual:@"停止"]) {
            //关闭 RTC:外部视频输入通道,停止推送屏幕流
            [self screenShareStop];
        }
        return;
    }
    if (![keyPath isEqualToString:kUserDefaultFrame] || startScreen == 0 ) {
        return;
    }
    NSDictionary *NV12Frame = change[NSKeyValueChangeNewKey];
    // 组建NV12 VideoFrame
    AliRtcVideoDataSample *dataSample = [[AliRtcVideoDataSample alloc] init];
    dataSample.dataPtr = (long)[NV12Frame[kPropData] bytes];
    dataSample.format = (AliRtcVideoFormat)[NV12Frame[kPropFormat] intValue];
    dataSample.width = [NV12Frame[kPropWidth] unsignedIntValue];
    dataSample.height = [NV12Frame[kPropHeight] unsignedIntValue];
    dataSample.strideY = [NV12Frame[kPropStrideY] unsignedIntValue];
    dataSample.strideU = [NV12Frame[kPropStrideU] unsignedIntValue];
    dataSample.strideV = [NV12Frame[kPropStrideV] unsignedIntValue];
    dataSample.dataLength = [NV12Frame[kPropDataLength] unsignedIntValue];
    dataSample.rotation = [NV12Frame[kPropRotation] unsignedIntValue];
    // 输入yuv frame
    //RTC:输入视频数据接口-----推送屏幕流到AliRtcVideosourceScreenShareType通道(如使用RTC1.19一下版本,请推送到AliRtcVideosourceCameraLargeType通道)
    [self.engine pushExternalVideoFrame:dataSample sourceType:AliRtcVideosourceScreenShareType];
}
@end

关于屏幕共享篇:\color{red}{完结}

感谢各位客官的帮助与支持🙏
更希望您能提出有意义的问题和建议、沟通会使我们共同成长~
\color{red}{共勉~}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,306评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,657评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,928评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,688评论 0 220
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,105评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,024评论 1 225
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,159评论 2 318
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,937评论 0 212
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,689评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,851评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,325评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,651评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,364评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,192评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,985评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,154评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,955评论 2 279

推荐阅读更多精彩内容