Add native H264 EveCanvas stream decoder
This commit is contained in:
parent
5a67296fcf
commit
897d73a98c
6 changed files with 294 additions and 11 deletions
5
Makefile
5
Makefile
|
|
@ -10,9 +10,10 @@ EveCanvas_FILES = \
|
||||||
Sources/EVEAppDelegate.m \
|
Sources/EVEAppDelegate.m \
|
||||||
Sources/EVEViewController.mm \
|
Sources/EVEViewController.mm \
|
||||||
Sources/EVEGLView.mm \
|
Sources/EVEGLView.mm \
|
||||||
Sources/EVEFrameStreamClient.m
|
Sources/EVEFrameStreamClient.m \
|
||||||
|
Sources/EVEH264StreamDecoder.m
|
||||||
|
|
||||||
EveCanvas_FRAMEWORKS = UIKit QuartzCore OpenGLES CoreMotion
|
EveCanvas_FRAMEWORKS = UIKit QuartzCore OpenGLES CoreMotion AVFoundation CoreMedia CoreVideo VideoToolbox
|
||||||
EveCanvas_CFLAGS = -fobjc-arc -Wall -Wextra -DGLES_SILENCE_DEPRECATION
|
EveCanvas_CFLAGS = -fobjc-arc -Wall -Wextra -DGLES_SILENCE_DEPRECATION
|
||||||
EveCanvas_CODESIGN_FLAGS = -Sentitlements.plist
|
EveCanvas_CODESIGN_FLAGS = -Sentitlements.plist
|
||||||
EveCanvas_INSTALL_PATH = /Applications
|
EveCanvas_INSTALL_PATH = /Applications
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
@interface EVEFrameStreamClient : NSObject
|
@interface EVEFrameStreamClient : NSObject
|
||||||
|
|
||||||
- (instancetype)initWithURL:(NSURL *)url delegate:(id<EVEFrameStreamClientDelegate>)delegate;
|
- (instancetype)initWithURL:(NSURL *)url delegate:(id<EVEFrameStreamClientDelegate>)delegate;
|
||||||
|
- (instancetype)initWithURLs:(NSArray<NSURL *> *)urls delegate:(id<EVEFrameStreamClientDelegate>)delegate;
|
||||||
- (void)connect;
|
- (void)connect;
|
||||||
- (void)disconnect;
|
- (void)disconnect;
|
||||||
- (void)sendPointerPhase:(NSString *)phase x:(CGFloat)x y:(CGFloat)y;
|
- (void)sendPointerPhase:(NSString *)phase x:(CGFloat)x y:(CGFloat)y;
|
||||||
|
|
@ -14,7 +15,9 @@
|
||||||
@protocol EVEFrameStreamClientDelegate <NSObject>
|
@protocol EVEFrameStreamClientDelegate <NSObject>
|
||||||
|
|
||||||
- (void)frameStreamClient:(EVEFrameStreamClient *)client didReceiveImage:(UIImage *)image;
|
- (void)frameStreamClient:(EVEFrameStreamClient *)client didReceiveImage:(UIImage *)image;
|
||||||
|
- (void)frameStreamClient:(EVEFrameStreamClient *)client didReceiveVideoAccessUnit:(NSData *)data;
|
||||||
- (void)frameStreamClient:(EVEFrameStreamClient *)client didReceiveViewportWidth:(CGFloat)width height:(CGFloat)height scale:(CGFloat)scale;
|
- (void)frameStreamClient:(EVEFrameStreamClient *)client didReceiveViewportWidth:(CGFloat)width height:(CGFloat)height scale:(CGFloat)scale;
|
||||||
|
- (void)frameStreamClient:(EVEFrameStreamClient *)client didReceiveCodec:(NSString *)codec;
|
||||||
- (void)frameStreamClient:(EVEFrameStreamClient *)client didChangeStatus:(NSString *)status;
|
- (void)frameStreamClient:(EVEFrameStreamClient *)client didChangeStatus:(NSString *)status;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
|
|
||||||
@interface EVEFrameStreamClient ()
|
@interface EVEFrameStreamClient ()
|
||||||
|
|
||||||
@property(nonatomic, strong) NSURL *url;
|
@property(nonatomic, copy) NSArray<NSURL *> *urls;
|
||||||
|
@property(nonatomic, assign) NSUInteger urlIndex;
|
||||||
@property(nonatomic, weak) id<EVEFrameStreamClientDelegate> delegate;
|
@property(nonatomic, weak) id<EVEFrameStreamClientDelegate> delegate;
|
||||||
@property(nonatomic, strong) NSURLSession *session;
|
@property(nonatomic, strong) NSURLSession *session;
|
||||||
@property(nonatomic, strong) NSURLSessionWebSocketTask *task;
|
@property(nonatomic, strong) NSURLSessionWebSocketTask *task;
|
||||||
|
|
@ -13,12 +14,16 @@
|
||||||
@implementation EVEFrameStreamClient
|
@implementation EVEFrameStreamClient
|
||||||
|
|
||||||
- (instancetype)initWithURL:(NSURL *)url delegate:(id<EVEFrameStreamClientDelegate>)delegate {
|
- (instancetype)initWithURL:(NSURL *)url delegate:(id<EVEFrameStreamClientDelegate>)delegate {
|
||||||
|
return [self initWithURLs:url ? @[url] : @[] delegate:delegate];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithURLs:(NSArray<NSURL *> *)urls delegate:(id<EVEFrameStreamClientDelegate>)delegate {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (!self) {
|
if (!self) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.url = url;
|
self.urls = urls.count > 0 ? urls : @[];
|
||||||
self.delegate = delegate;
|
self.delegate = delegate;
|
||||||
self.session = [NSURLSession sessionWithConfiguration:NSURLSessionConfiguration.defaultSessionConfiguration];
|
self.session = [NSURLSession sessionWithConfiguration:NSURLSessionConfiguration.defaultSessionConfiguration];
|
||||||
self.shouldReconnect = YES;
|
self.shouldReconnect = YES;
|
||||||
|
|
@ -28,9 +33,15 @@
|
||||||
- (void)connect {
|
- (void)connect {
|
||||||
self.shouldReconnect = YES;
|
self.shouldReconnect = YES;
|
||||||
[self.task cancelWithCloseCode:NSURLSessionWebSocketCloseCodeNormalClosure reason:nil];
|
[self.task cancelWithCloseCode:NSURLSessionWebSocketCloseCodeNormalClosure reason:nil];
|
||||||
self.task = [self.session webSocketTaskWithURL:self.url];
|
NSURL *url = self.urls.count > 0 ? self.urls[self.urlIndex % self.urls.count] : nil;
|
||||||
|
if (!url) {
|
||||||
|
[self publishStatus:@"stream URL missing"];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.task = [self.session webSocketTaskWithURL:url];
|
||||||
[self.task resume];
|
[self.task resume];
|
||||||
[self publishStatus:@"connecting"];
|
[self publishStatus:[NSString stringWithFormat:@"connecting %@", url.host ?: @""]];
|
||||||
[self receiveNextMessage];
|
[self receiveNextMessage];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,10 +91,18 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.type == NSURLSessionWebSocketMessageTypeData) {
|
if (message.type == NSURLSessionWebSocketMessageTypeData) {
|
||||||
UIImage *image = [UIImage imageWithData:message.data];
|
const uint8_t *bytes = message.data.bytes;
|
||||||
if (image) {
|
BOOL isJpeg = message.data.length > 2 && bytes[0] == 0xff && bytes[1] == 0xd8;
|
||||||
|
if (isJpeg) {
|
||||||
|
UIImage *image = [UIImage imageWithData:message.data];
|
||||||
|
if (image) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[self.delegate frameStreamClient:self didReceiveImage:image];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
[self.delegate frameStreamClient:self didReceiveImage:image];
|
[self.delegate frameStreamClient:self didReceiveVideoAccessUnit:message.data];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (message.type == NSURLSessionWebSocketMessageTypeString) {
|
} else if (message.type == NSURLSessionWebSocketMessageTypeString) {
|
||||||
|
|
@ -114,6 +133,10 @@
|
||||||
}
|
}
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
[self.delegate frameStreamClient:self didReceiveViewportWidth:width height:height scale:scale];
|
[self.delegate frameStreamClient:self didReceiveViewportWidth:width height:height scale:scale];
|
||||||
|
NSString *codec = message[@"codec"];
|
||||||
|
if ([codec isKindOfClass:NSString.class]) {
|
||||||
|
[self.delegate frameStreamClient:self didReceiveCodec:codec];
|
||||||
|
}
|
||||||
[self.delegate frameStreamClient:self didChangeStatus:@"stream live"];
|
[self.delegate frameStreamClient:self didChangeStatus:@"stream live"];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -124,6 +147,10 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self.urls.count > 1) {
|
||||||
|
self.urlIndex = (self.urlIndex + 1) % self.urls.count;
|
||||||
|
}
|
||||||
|
|
||||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||||
if (self.shouldReconnect) {
|
if (self.shouldReconnect) {
|
||||||
[self connect];
|
[self connect];
|
||||||
|
|
|
||||||
15
Sources/EVEH264StreamDecoder.h
Normal file
15
Sources/EVEH264StreamDecoder.h
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#import <AVFoundation/AVFoundation.h>
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface EVEH264StreamDecoder : NSObject
|
||||||
|
|
||||||
|
@property(nonatomic, readonly) AVSampleBufferDisplayLayer *displayLayer;
|
||||||
|
|
||||||
|
- (void)reset;
|
||||||
|
- (void)consumeAnnexBAccessUnit:(NSData *)data;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
205
Sources/EVEH264StreamDecoder.m
Normal file
205
Sources/EVEH264StreamDecoder.m
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
#import "EVEH264StreamDecoder.h"
|
||||||
|
|
||||||
|
#import <CoreMedia/CoreMedia.h>
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
#import <VideoToolbox/VideoToolbox.h>
|
||||||
|
|
||||||
|
@interface EVEH264StreamDecoder ()
|
||||||
|
|
||||||
|
@property(nonatomic, strong) AVSampleBufferDisplayLayer *displayLayer;
|
||||||
|
@property(nonatomic) CMVideoFormatDescriptionRef formatDescription;
|
||||||
|
@property(nonatomic, strong) NSData *sps;
|
||||||
|
@property(nonatomic, strong) NSData *pps;
|
||||||
|
@property(nonatomic, assign) int64_t frameIndex;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation EVEH264StreamDecoder
|
||||||
|
|
||||||
|
- (instancetype)init {
|
||||||
|
self = [super init];
|
||||||
|
if (!self) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
_displayLayer = [[AVSampleBufferDisplayLayer alloc] init];
|
||||||
|
_displayLayer.videoGravity = AVLayerVideoGravityResizeAspect;
|
||||||
|
_displayLayer.backgroundColor = UIColor.blackColor.CGColor;
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc {
|
||||||
|
[self reset];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)reset {
|
||||||
|
if (_formatDescription) {
|
||||||
|
CFRelease(_formatDescription);
|
||||||
|
_formatDescription = NULL;
|
||||||
|
}
|
||||||
|
self.sps = nil;
|
||||||
|
self.pps = nil;
|
||||||
|
self.frameIndex = 0;
|
||||||
|
[self.displayLayer flushAndRemoveImage];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)consumeAnnexBAccessUnit:(NSData *)data {
|
||||||
|
NSArray<NSData *> *nals = [self parseAnnexB:data];
|
||||||
|
if (nals.count == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSMutableData *avcc = [NSMutableData data];
|
||||||
|
BOOL hasSlice = NO;
|
||||||
|
for (NSData *nal in nals) {
|
||||||
|
if (nal.length == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t *bytes = nal.bytes;
|
||||||
|
uint8_t nalType = bytes[0] & 0x1f;
|
||||||
|
if (nalType == 7) {
|
||||||
|
self.sps = nal;
|
||||||
|
[self rebuildFormatDescriptionIfReady];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (nalType == 8) {
|
||||||
|
self.pps = nal;
|
||||||
|
[self rebuildFormatDescriptionIfReady];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (nalType == 1 || nalType == 5) {
|
||||||
|
hasSlice = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t length = CFSwapInt32HostToBig((uint32_t)nal.length);
|
||||||
|
[avcc appendBytes:&length length:sizeof(length)];
|
||||||
|
[avcc appendData:nal];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasSlice || !_formatDescription || avcc.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CMBlockBufferRef blockBuffer = NULL;
|
||||||
|
OSStatus status = CMBlockBufferCreateWithMemoryBlock(
|
||||||
|
kCFAllocatorDefault,
|
||||||
|
NULL,
|
||||||
|
avcc.length,
|
||||||
|
kCFAllocatorDefault,
|
||||||
|
NULL,
|
||||||
|
0,
|
||||||
|
avcc.length,
|
||||||
|
0,
|
||||||
|
&blockBuffer);
|
||||||
|
if (status != noErr || !blockBuffer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = CMBlockBufferReplaceDataBytes(avcc.bytes, blockBuffer, 0, avcc.length);
|
||||||
|
if (status != noErr) {
|
||||||
|
CFRelease(blockBuffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CMSampleTimingInfo timing = {
|
||||||
|
.duration = CMTimeMake(1, 60),
|
||||||
|
.presentationTimeStamp = CMTimeMake(self.frameIndex++, 60),
|
||||||
|
.decodeTimeStamp = kCMTimeInvalid,
|
||||||
|
};
|
||||||
|
size_t sampleSize = avcc.length;
|
||||||
|
CMSampleBufferRef sampleBuffer = NULL;
|
||||||
|
status = CMSampleBufferCreateReady(
|
||||||
|
kCFAllocatorDefault,
|
||||||
|
blockBuffer,
|
||||||
|
_formatDescription,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
&timing,
|
||||||
|
1,
|
||||||
|
&sampleSize,
|
||||||
|
&sampleBuffer);
|
||||||
|
CFRelease(blockBuffer);
|
||||||
|
if (status != noErr || !sampleBuffer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, YES);
|
||||||
|
if (attachments && CFArrayGetCount(attachments) > 0) {
|
||||||
|
CFMutableDictionaryRef attachment = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
|
||||||
|
CFDictionarySetValue(attachment, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.displayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) {
|
||||||
|
[self.displayLayer flush];
|
||||||
|
}
|
||||||
|
[self.displayLayer enqueueSampleBuffer:sampleBuffer];
|
||||||
|
CFRelease(sampleBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)rebuildFormatDescriptionIfReady {
|
||||||
|
if (!self.sps || !self.pps) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_formatDescription) {
|
||||||
|
CFRelease(_formatDescription);
|
||||||
|
_formatDescription = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t *parameterSets[] = { self.sps.bytes, self.pps.bytes };
|
||||||
|
size_t parameterSetSizes[] = { self.sps.length, self.pps.length };
|
||||||
|
CMVideoFormatDescriptionCreateFromH264ParameterSets(
|
||||||
|
kCFAllocatorDefault,
|
||||||
|
2,
|
||||||
|
parameterSets,
|
||||||
|
parameterSetSizes,
|
||||||
|
4,
|
||||||
|
&_formatDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSArray<NSData *> *)parseAnnexB:(NSData *)data {
|
||||||
|
NSMutableArray<NSData *> *nals = [NSMutableArray array];
|
||||||
|
const uint8_t *bytes = data.bytes;
|
||||||
|
NSUInteger length = data.length;
|
||||||
|
NSUInteger cursor = 0;
|
||||||
|
while (cursor + 3 < length) {
|
||||||
|
NSUInteger start = [self findStartCode:bytes length:length offset:cursor startCodeLength:NULL];
|
||||||
|
if (start == NSNotFound) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSUInteger startCodeLength = 0;
|
||||||
|
[self findStartCode:bytes length:length offset:start startCodeLength:&startCodeLength];
|
||||||
|
NSUInteger payloadStart = start + startCodeLength;
|
||||||
|
NSUInteger next = [self findStartCode:bytes length:length offset:payloadStart startCodeLength:NULL];
|
||||||
|
NSUInteger payloadEnd = next == NSNotFound ? length : next;
|
||||||
|
if (payloadEnd > payloadStart) {
|
||||||
|
[nals addObject:[data subdataWithRange:NSMakeRange(payloadStart, payloadEnd - payloadStart)]];
|
||||||
|
}
|
||||||
|
cursor = payloadEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nals;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSUInteger)findStartCode:(const uint8_t *)bytes length:(NSUInteger)length offset:(NSUInteger)offset startCodeLength:(NSUInteger *)startCodeLength {
|
||||||
|
for (NSUInteger index = offset; index + 3 < length; index++) {
|
||||||
|
if (bytes[index] == 0 && bytes[index + 1] == 0 && bytes[index + 2] == 1) {
|
||||||
|
if (startCodeLength) {
|
||||||
|
*startCodeLength = 3;
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
if (index + 4 < length && bytes[index] == 0 && bytes[index + 1] == 0 && bytes[index + 2] == 0 && bytes[index + 3] == 1) {
|
||||||
|
if (startCodeLength) {
|
||||||
|
*startCodeLength = 4;
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NSNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
@ -5,11 +5,13 @@
|
||||||
|
|
||||||
#import "EVEFrameStreamClient.h"
|
#import "EVEFrameStreamClient.h"
|
||||||
#import "EVEGLView.h"
|
#import "EVEGLView.h"
|
||||||
|
#import "EVEH264StreamDecoder.h"
|
||||||
|
|
||||||
@interface EVEViewController () <EVEFrameStreamClientDelegate>
|
@interface EVEViewController () <EVEFrameStreamClientDelegate>
|
||||||
|
|
||||||
@property(nonatomic, strong) EVEGLView *glView;
|
@property(nonatomic, strong) EVEGLView *glView;
|
||||||
@property(nonatomic, strong) UIImageView *streamImageView;
|
@property(nonatomic, strong) UIImageView *streamImageView;
|
||||||
|
@property(nonatomic, strong) EVEH264StreamDecoder *videoDecoder;
|
||||||
@property(nonatomic, strong) UILabel *overlayLabel;
|
@property(nonatomic, strong) UILabel *overlayLabel;
|
||||||
@property(nonatomic, strong) CADisplayLink *displayLink;
|
@property(nonatomic, strong) CADisplayLink *displayLink;
|
||||||
@property(nonatomic, strong) CMMotionManager *motionManager;
|
@property(nonatomic, strong) CMMotionManager *motionManager;
|
||||||
|
|
@ -20,6 +22,7 @@
|
||||||
@property(nonatomic, assign) CGSize streamViewportSize;
|
@property(nonatomic, assign) CGSize streamViewportSize;
|
||||||
@property(nonatomic, assign) CGFloat streamScale;
|
@property(nonatomic, assign) CGFloat streamScale;
|
||||||
@property(nonatomic, copy) NSString *streamStatus;
|
@property(nonatomic, copy) NSString *streamStatus;
|
||||||
|
@property(nonatomic, copy) NSString *streamCodec;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
@ -41,6 +44,11 @@
|
||||||
self.streamImageView.userInteractionEnabled = NO;
|
self.streamImageView.userInteractionEnabled = NO;
|
||||||
[root addSubview:self.streamImageView];
|
[root addSubview:self.streamImageView];
|
||||||
|
|
||||||
|
self.videoDecoder = [[EVEH264StreamDecoder alloc] init];
|
||||||
|
self.videoDecoder.displayLayer.frame = root.bounds;
|
||||||
|
self.videoDecoder.displayLayer.hidden = YES;
|
||||||
|
[root.layer addSublayer:self.videoDecoder.displayLayer];
|
||||||
|
|
||||||
self.overlayLabel = [[UILabel alloc] initWithFrame:CGRectZero];
|
self.overlayLabel = [[UILabel alloc] initWithFrame:CGRectZero];
|
||||||
self.overlayLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
self.overlayLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||||
self.overlayLabel.numberOfLines = 0;
|
self.overlayLabel.numberOfLines = 0;
|
||||||
|
|
@ -77,8 +85,12 @@
|
||||||
self.streamViewportSize = CGSizeMake(1620.0, 2160.0);
|
self.streamViewportSize = CGSizeMake(1620.0, 2160.0);
|
||||||
self.streamScale = 2.0;
|
self.streamScale = 2.0;
|
||||||
self.streamStatus = @"stream idle";
|
self.streamStatus = @"stream idle";
|
||||||
NSURL *streamURL = [NSURL URLWithString:@"ws://192.168.1.66:8792/stream"];
|
self.streamCodec = @"jpeg";
|
||||||
self.streamClient = [[EVEFrameStreamClient alloc] initWithURL:streamURL delegate:self];
|
NSArray<NSURL *> *streamURLs = @[
|
||||||
|
[NSURL URLWithString:@"ws://127.0.0.1:8792/stream"],
|
||||||
|
[NSURL URLWithString:@"ws://192.168.1.66:8792/stream"],
|
||||||
|
];
|
||||||
|
self.streamClient = [[EVEFrameStreamClient alloc] initWithURLs:streamURLs delegate:self];
|
||||||
[self.streamClient connect];
|
[self.streamClient connect];
|
||||||
|
|
||||||
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(frameTick:)];
|
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(frameTick:)];
|
||||||
|
|
@ -107,6 +119,7 @@
|
||||||
- (void)viewDidLayoutSubviews {
|
- (void)viewDidLayoutSubviews {
|
||||||
[super viewDidLayoutSubviews];
|
[super viewDidLayoutSubviews];
|
||||||
[self.glView resizeDrawableIfNeeded];
|
[self.glView resizeDrawableIfNeeded];
|
||||||
|
self.videoDecoder.displayLayer.frame = self.view.bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)frameTick:(CADisplayLink *)link {
|
- (void)frameTick:(CADisplayLink *)link {
|
||||||
|
|
@ -136,12 +149,14 @@
|
||||||
@"EVE Canvas\n"
|
@"EVE Canvas\n"
|
||||||
"CEF stream + native touch\n"
|
"CEF stream + native touch\n"
|
||||||
"%@\n"
|
"%@\n"
|
||||||
|
"codec %@\n"
|
||||||
"points %.0fx%.0f pixels %.0fx%.0f @ %.1fx\n"
|
"points %.0fx%.0f pixels %.0fx%.0f @ %.1fx\n"
|
||||||
"stream %.0fx%.0f @ %.1fx\n"
|
"stream %.0fx%.0f @ %.1fx\n"
|
||||||
"fps %.1f touches %lu\n"
|
"fps %.1f touches %lu\n"
|
||||||
"accel %+0.2f %+0.2f %+0.2f\n"
|
"accel %+0.2f %+0.2f %+0.2f\n"
|
||||||
"gyro %+0.2f %+0.2f %+0.2f",
|
"gyro %+0.2f %+0.2f %+0.2f",
|
||||||
self.streamStatus ?: @"stream",
|
self.streamStatus ?: @"stream",
|
||||||
|
self.streamCodec ?: @"unknown",
|
||||||
points.width, points.height, pixels.width, pixels.height, scale,
|
points.width, points.height, pixels.width, pixels.height, scale,
|
||||||
self.streamViewportSize.width, self.streamViewportSize.height, self.streamScale,
|
self.streamViewportSize.width, self.streamViewportSize.height, self.streamScale,
|
||||||
self.filteredFPS, (unsigned long)self.touchCount,
|
self.filteredFPS, (unsigned long)self.touchCount,
|
||||||
|
|
@ -211,9 +226,18 @@
|
||||||
|
|
||||||
- (void)frameStreamClient:(EVEFrameStreamClient *)client didReceiveImage:(UIImage *)image {
|
- (void)frameStreamClient:(EVEFrameStreamClient *)client didReceiveImage:(UIImage *)image {
|
||||||
(void)client;
|
(void)client;
|
||||||
|
self.videoDecoder.displayLayer.hidden = YES;
|
||||||
|
self.streamImageView.hidden = NO;
|
||||||
self.streamImageView.image = image;
|
self.streamImageView.image = image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)frameStreamClient:(EVEFrameStreamClient *)client didReceiveVideoAccessUnit:(NSData *)data {
|
||||||
|
(void)client;
|
||||||
|
self.streamImageView.hidden = YES;
|
||||||
|
self.videoDecoder.displayLayer.hidden = NO;
|
||||||
|
[self.videoDecoder consumeAnnexBAccessUnit:data];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)frameStreamClient:(EVEFrameStreamClient *)client didReceiveViewportWidth:(CGFloat)width height:(CGFloat)height scale:(CGFloat)scale {
|
- (void)frameStreamClient:(EVEFrameStreamClient *)client didReceiveViewportWidth:(CGFloat)width height:(CGFloat)height scale:(CGFloat)scale {
|
||||||
(void)client;
|
(void)client;
|
||||||
if (width > 0 && height > 0) {
|
if (width > 0 && height > 0) {
|
||||||
|
|
@ -224,6 +248,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)frameStreamClient:(EVEFrameStreamClient *)client didReceiveCodec:(NSString *)codec {
|
||||||
|
(void)client;
|
||||||
|
if (![codec isEqualToString:self.streamCodec]) {
|
||||||
|
[self.videoDecoder reset];
|
||||||
|
}
|
||||||
|
self.streamCodec = codec;
|
||||||
|
}
|
||||||
|
|
||||||
- (void)frameStreamClient:(EVEFrameStreamClient *)client didChangeStatus:(NSString *)status {
|
- (void)frameStreamClient:(EVEFrameStreamClient *)client didChangeStatus:(NSString *)status {
|
||||||
(void)client;
|
(void)client;
|
||||||
self.streamStatus = status;
|
self.streamStatus = status;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue