概述:
最近公司新增了个功能,就是录屏推流功能,使用的是系统的ReplayKit 框架.
问题难点:
1.扩展应用无法引入腾讯云 SDK 文件
2.使用ReplayKit扩展应用无法与主应用互传参数.
1.扩展应用无法引入腾讯云 SDK 文件解决方法
如若使用 CocoaPods安装的 三方库,先将其移除,依照官方文档方法手动导入,而后选择扩展应用使用同样的方法导入 SDK 即可
2.使用ReplayKit扩展应用无法与主应用互传参数解决方案.
这里使用的是 Scoket 短连接来实现通讯,
更多Scoket内容请看我关于Scoket实现文章https://www.jianshu.com/p/af99261795d1
创建ReplayKit
image.pngfile -> new -> target
输入扩展项目名称,格式和正常创建文件无样
创建完成会目录会多出两个文件夹如图:
image.pngSampleHandler.m 中实现对应推流方法,这里已腾讯云为例
#import "SampleHandler.h"
#import <TXLiteAVSDK_Professional/TXLiteAVSDK.h>
#import<sys/socket.h>
#import<netinet/in.h>
#import<arpa/inet.h>
#import<ifaddrs.h>
#import<net/if.h>
static CMSampleBufferRef lastSampleBuffer;
static TXLivePush * s_txLivePublisher;
@interface SampleHandler ()<TXLivePushListener>
@end
@implementation SampleHandler
- (instancetype)init
{
self = [super init];
if (self) {
[self txLiteAVSDKProfessional];
[self accept];
}
return self;
}
#pragma mark - 腾讯云直播配置
-(void)txLiteAVSDKProfessional{
NSString * const licenceURL = @"填写自己的 URL";
NSString * const licenceKey = @"填写自己的 Key";
//TXLiveBase 位于 "TXLiveBase.h" 头文件中
[TXLiveBase setLicenceURL:licenceURL key:licenceKey];
}
- (void)initPublisher {
if (s_txLivePublisher) {
[s_txLivePublisher stopPush];
}
TXLivePushConfig * config = [[TXLivePushConfig alloc] init];
config.customModeType |= CUSTOM_MODE_VIDEO_CAPTURE; //自定义视频模式
config.enableAutoBitrate = YES;
config.enableHWAcceleration = YES;
config.customModeType |= CUSTOM_MODE_AUDIO_CAPTURE; //自定义音频模式
config.audioSampleRate = AUDIO_SAMPLE_RATE_44100; //系统录屏的音频采样率为44100
config.audioChannels = 1;
config.autoSampleBufferSize = NO;
config.sampleBufferSize = CGSizeMake(544,960);
config.homeOrientation = HOME_ORIENTATION_DOWN;
s_txLivePublisher = [[TXLivePush alloc] initWithConfig:config];
s_txLivePublisher.delegate = self;
}
#pragma mark - 监听端口接收数据
-(void)accept{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
struct sockaddr_in servAddr;
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(9091);
char tempChar[1000];
const char * ip = strcpy(tempChar,(char *)[[self getIPAddress] UTF8String]);
servAddr.sin_addr.s_addr = inet_addr(ip);
int serverSocker = socket(AF_INET, SOCK_STREAM, 0);
int sClient = bind(serverSocker, (const struct sockaddr *)&servAddr, sizeof(servAddr));
if (sClient== -1) {
NSLog(@"绑定失败");
return;
}
int listenS = listen(serverSocker,1);
if (listenS == -1) {
NSLog(@"监听失败");
return;
}
unsigned int nAddrlen = sizeof(servAddr);
int linkSocket = accept(serverSocker,(struct sockaddr *)&servAddr, &nAddrlen);
if (linkSocket == -1) {
NSLog(@"接受链接失败%d",sClient);
return ;
}
[self recv:linkSocket];
});
}
#pragma mark - 接收数据
-(void)recv:(int)clintSocket{
char butter[1024] = {0};
ssize_t recvlen = recv(clintSocket, butter, sizeof(butter), 0);
if (recvlen != -1) {
NSString * pushUrl = [NSString stringWithUTF8String:butter];
NSLog(@"msg:%@ code:%ld",pushUrl,recvlen);
int star = [s_txLivePublisher startPush:pushUrl];
if (star == 0) {
NSLog(@"推流启动成功");
}
else{
NSLog(@"推流启动失败");
}
if (lastSampleBuffer) {
[s_txLivePublisher sendVideoSampleBuffer:lastSampleBuffer];
}
memset(butter, 0, sizeof(butter));
}
// close(clintSocket);
}
#pragma mark - 开始录屏
- (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo {
[self initPublisher];
}
#pragma mark - 暂停录制
- (void)broadcastPaused {
[s_txLivePublisher setSendAudioSampleBufferMuted:YES];
}
#pragma mark - 恢复录制
- (void)broadcastResumed {
[s_txLivePublisher setSendAudioSampleBufferMuted:NO];
}
#pragma mark - 录制完成
- (void)broadcastFinished {
if (s_txLivePublisher) {
[s_txLivePublisher stopPush];
s_txLivePublisher = nil;
}
}
- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
switch (sampleBufferType) {
case RPSampleBufferTypeVideo:
if (!CMSampleBufferIsValid(sampleBuffer))
return;
//保存一帧在 startPush 时发送,防止推流启动后或切换横竖屏因无画面数据而推流不成功
if (lastSampleBuffer) {
CFRelease(lastSampleBuffer);
lastSampleBuffer = NULL;
}
lastSampleBuffer = sampleBuffer;
CFRetain(lastSampleBuffer);
[s_txLivePublisher sendVideoSampleBuffer:sampleBuffer];
break;
case RPSampleBufferTypeAudioApp:
// Handle audio sample buffer for app audio
break;
case RPSampleBufferTypeAudioMic:
// Handle audio sample buffer for mic audio
break;
default:
break;
}
}
#pragma mark - 腾讯云事件通知
- (void)onPushEvent:(int)EvtID withParam:(NSDictionary *)param{
NSLog(@"onPushEvent %d", EvtID);
if (EvtID == PUSH_ERR_NET_DISCONNECT) {
NSLog(@"腾讯云录屏失败");
} else if (EvtID == PUSH_EVT_PUSH_BEGIN) {
NSLog(@"腾讯云录屏连接成功");
} else if (EvtID == PUSH_WARNING_NET_BUSY) {
NSLog(@"腾讯云录屏网络上行带宽不足");
}
}
#pragma mark - 腾讯云状态通知
- (void)onNetStatus:(NSDictionary *)param{
}
@end
用户端发送数据代码
#pragma mark - 连接服务端
-(BOOL)clintSocket{
clintSocket = socket(AF_INET , SOCK_STREAM, 0);
struct sockaddr_in servAddr;
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(9091);
char tempChar[1000];
const char * ip = strcpy(tempChar,(char *)[[self getIPAddress] UTF8String]);
servAddr.sin_addr.s_addr = inet_addr(ip);
int connetRect = connect(clintSocket, (const struct sockaddr *)&servAddr, sizeof(servAddr));
if (connetRect == -1) {
return NO;
}else{
return YES;
}
}
-(void)sendMsa:(NSString *)sendText{
ssize_t senlen = send(clintSocket, sendText.UTF8String, strlen(sendText.UTF8String), 0);
NSLog(@"发送了%ld个字符",senlen);
// 6.关闭
close(clintSocket);
}
#pragma mark - 获取 IP
- (NSString *)getIPAddress
{
NSString *address = @"error";
struct ifaddrs *interfaces = NULL;
struct ifaddrs *temp_addr = NULL;
int success = 0;
success = getifaddrs(&interfaces);
if (success == 0) {
temp_addr = interfaces;
while (temp_addr != NULL) {
if( temp_addr->ifa_addr->sa_family == AF_INET) {
if ([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) {
address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];
}
}
temp_addr = temp_addr->ifa_next;
}
}
// Free memory
freeifaddrs(interfaces);
return address;
}
网友评论