Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom video encoder settings and dynamic screen sizes #406

Closed
wants to merge 12 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 13 additions & 2 deletions SmartDeviceLink/SDLProxy.m
Expand Up @@ -29,7 +29,7 @@
#import "SDLProtocolMessage.h"
#import "SDLProtocolMessage.h"
#import "SDLPutFile.h"
#import "SDLRPCPayload.h"
#import "SDLRegisterAppInterfaceResponse.h"
#import "SDLRPCPayload.h"
#import "SDLRPCRequestFactory.h"
#import "SDLRPCResponse.h"
Expand All @@ -56,7 +56,8 @@ @interface SDLProxy () {
}

@property (strong, nonatomic) NSMutableSet *mutableProxyListeners;
@property (nonatomic, strong, readwrite) SDLStreamingMediaManager *streamingMediaManager;
@property (nonatomic, strong, readwrite, nullable) SDLStreamingMediaManager *streamingMediaManager;
@property (nonatomic, strong, nullable) SDLDisplayCapabilities* displayCapabilities;

@end

Expand Down Expand Up @@ -101,6 +102,8 @@ - (void)destructObjects {
_transport = nil;
_protocol = nil;
_mutableProxyListeners = nil;
_streamingMediaManager = nil;
_displayCapabilities = nil;
}
}

Expand Down Expand Up @@ -166,7 +169,11 @@ - (NSString *)proxyVersion {

- (SDLStreamingMediaManager *)streamingMediaManager {
if (_streamingMediaManager == nil) {
if (self.displayCapabilities == nil) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"SDLStreamingMediaManager must be accessed only after a successful RegisterAppInterfaceResponse" userInfo:nil];
}
_streamingMediaManager = [[SDLStreamingMediaManager alloc] initWithProtocol:self.protocol];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we deprecate initWithProtocol: and add initWithProtocol:displayCapabilities:? The displayCapabilities property on SMM will have to stay nullable for now, but that would clarify the actual dependencies.

_streamingMediaManager.displayCapabilities = self.displayCapabilities;
[self.protocol.protocolDelegateTable addObject:_streamingMediaManager];
}

Expand Down Expand Up @@ -328,6 +335,10 @@ - (void)handleRegisterAppInterfaceResponse:(SDLRPCResponse *)response {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sendMobileHMIState) name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sendMobileHMIState) name:UIApplicationDidEnterBackgroundNotification object:nil];
}

// Extract the display capabilties to successfully build SDLStreamingMediaManager's video encoder.
SDLRegisterAppInterfaceResponse* registerResponse = (SDLRegisterAppInterfaceResponse*)response;
self.displayCapabilities = registerResponse.displayCapabilities;
}

- (void)handleSyncPData:(SDLRPCMessage *)message {
Expand Down
23 changes: 23 additions & 0 deletions SmartDeviceLink/SDLStreamingMediaManager.h
Expand Up @@ -12,6 +12,7 @@
#import "SDLProtocolListener.h"

@class SDLAbstractProtocol;
@class SDLDisplayCapabilities;


NS_ASSUME_NONNULL_BEGIN
Expand Down Expand Up @@ -85,6 +86,28 @@ typedef void (^SDLStreamingStartBlock)(BOOL success, NSError *__nullable error);
@property (assign, nonatomic, readonly) BOOL videoSessionConnected;
@property (assign, nonatomic, readonly) BOOL audioSessionConnected;

/**
* The settings used in a VTCompressionSessionRef encoder. These will be verified when the video stream is started. Acceptable properties for this are located in VTCompressionProperties. If set to nil, the defaultVideoEncoderSettings will be used.
*
* @warning Video streaming must not be connected to update the encoder properties. If it is running, issue a stopVideoSession before updating.
*/
@property (strong, nonatomic, null_resettable) NSDictionary* videoEncoderSettings;

/**
* Provides default video encoder settings used.
*/
@property (strong, nonatomic, readonly) NSDictionary* defaultVideoEncoderSettings;

/**
* The capabilities of the display that is currently connected to.
*
*/
@property (strong, nonatomic) SDLDisplayCapabilities* displayCapabilities;

/**
* This is the current screen size of a connected display. This will be the size the video encoder uses to encode the raw image data.
*/
@property (assign, nonatomic, readonly) CGSize screenSize;

@end

Expand Down
100 changes: 84 additions & 16 deletions SmartDeviceLink/SDLStreamingMediaManager.m
Expand Up @@ -11,8 +11,10 @@
@import UIKit;

#import "SDLAbstractProtocol.h"
#import "SDLDisplayCapabilities.h"
#import "SDLGlobals.h"

#import "SDLImageResolution.h"
#import "SDLScreenParams.h"

NSString *const SDLErrorDomainStreamingMediaVideo = @"com.sdl.streamingmediamanager.video";
NSString *const SDLErrorDomainStreamingMediaAudio = @"com.sdl.streamingmediamanager.audio";
Expand All @@ -39,6 +41,8 @@ @interface SDLStreamingMediaManager ()

@implementation SDLStreamingMediaManager

@synthesize videoEncoderSettings = _videoEncoderSettings;

#pragma mark - Class Lifecycle

- (instancetype)initWithProtocol:(SDLAbstractProtocol *)protocol {
Expand All @@ -56,6 +60,8 @@ - (instancetype)initWithProtocol:(SDLAbstractProtocol *)protocol {

_videoStartBlock = nil;
_audioStartBlock = nil;

_screenSize = CGSizeMake(640, 480);

return self;
}
Expand Down Expand Up @@ -127,6 +133,52 @@ - (BOOL)sendAudioData:(NSData *)pcmAudioData {
return YES;
}

#pragma mark - Update video encoder

- (void)setVideoEncoderSettings:(NSDictionary * _Nullable)videoEncoderSettings {
if (self.videoSessionConnected) {
@throw [NSException exceptionWithName:SDLErrorDomainStreamingMediaVideo reason:@"Cannot update video encoder settings while video session is connected." userInfo:nil];
return;
}

if (videoEncoderSettings) {
_videoEncoderSettings = videoEncoderSettings;
} else {
_videoEncoderSettings = self.defaultVideoEncoderSettings;
}
}

- (NSDictionary*)videoEncoderSettings {
if (!_videoEncoderSettings) {
_videoEncoderSettings = self.defaultVideoEncoderSettings;
}
return _videoEncoderSettings;
}

- (NSDictionary*)defaultVideoEncoderSettings {
static NSDictionary* defaultVideoEncoderSettings = nil;
if (defaultVideoEncoderSettings == nil) {
defaultVideoEncoderSettings = @{
(__bridge NSString*)kVTCompressionPropertyKey_ProfileLevel : (__bridge NSString*)kVTProfileLevel_H264_Baseline_AutoLevel,
(__bridge NSString*)kVTCompressionPropertyKey_RealTime : @YES
};
}
return defaultVideoEncoderSettings;
}

- (void)setDisplayCapabilities:(SDLDisplayCapabilities *)displayCapabilities {
_displayCapabilities = displayCapabilities;

SDLImageResolution* resolution = displayCapabilities.screenParams.resolution;
if (resolution != nil) {
_screenSize = CGSizeMake(resolution.resolutionWidth.floatValue,
resolution.resolutionHeight.floatValue);
} else {
NSLog(@"Could not retrieve screen size. Defaulting to 640 x 480.");
_screenSize = CGSizeMake(640,
480);
}
}

#pragma mark - SDLProtocolListener Methods

Expand Down Expand Up @@ -224,36 +276,52 @@ - (BOOL)sdl_configureVideoEncoderWithError:(NSError *__autoreleasing *)error {
OSStatus status;

// Create a compression session
// TODO (Joel F.)[2015-08-18]: Dimensions should be from the Head Unit
status = VTCompressionSessionCreate(NULL, 640, 480, kCMVideoCodecType_H264, NULL, NULL, NULL, &sdl_videoEncoderOutputCallback, (__bridge void *)self, &_compressionSession);
status = VTCompressionSessionCreate(NULL, self.screenSize.width, self.screenSize.height, kCMVideoCodecType_H264, NULL, NULL, NULL, &sdl_videoEncoderOutputCallback, (__bridge void *)self, &_compressionSession);

if (status != noErr) {
// TODO: Log the error
if (*error != nil) {
if (*error == nil) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These were correct originally. They're checking that the passed NSError address can be dereferenced, and later retrieved by the developer. See "Generating your own errors" at https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/ErrorHandling/ErrorHandling.html

*error = [NSError errorWithDomain:SDLErrorDomainStreamingMediaVideo code:SDLStreamingVideoErrorConfigurationCompressionSessionCreationFailure userInfo:@{ @"OSStatus" : @(status) }];
}

return NO;
}

// Set the profile level of the video stream
status = VTSessionSetProperty(self.compressionSession, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_AutoLevel);
// Validate that the video encoder properties are valid.
CFDictionaryRef supportedProperties;
status = VTSessionCopySupportedPropertyDictionary(self.compressionSession, &supportedProperties);
if (status != noErr) {
if (*error != nil) {
if (*error == nil) {
*error = [NSError errorWithDomain:SDLErrorDomainStreamingMediaVideo code:SDLStreamingVideoErrorConfigurationCompressionSessionSetPropertyFailure userInfo:@{ @"OSStatus" : @(status) }];
}

return NO;
}

// Set the session to compress in real time
status = VTSessionSetProperty(self.compressionSession, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
if (status != noErr) {
if (*error != nil) {
*error = [NSError errorWithDomain:SDLErrorDomainStreamingMediaVideo code:SDLStreamingVideoErrorConfigurationCompressionSessionSetPropertyFailure userInfo:@{ @"OSStatus" : @(status) }];

for (NSString* key in self.videoEncoderSettings.allKeys) {
if (CFDictionaryContainsKey(supportedProperties, (__bridge CFStringRef)key) == false) {
if (*error == nil) {
NSString* description = [NSString stringWithFormat:@"\"%@\" is not a supported key.", key];
*error = [NSError errorWithDomain:SDLErrorDomainStreamingMediaVideo code:SDLStreamingVideoErrorConfigurationCompressionSessionSetPropertyFailure userInfo:@{NSLocalizedDescriptionKey : description}];
}
CFRelease(supportedProperties);
return NO;
}
}
CFRelease(supportedProperties);

// Populate the video encoder settings from provided dictionary.
for (NSString* key in self.videoEncoderSettings.allKeys) {
id value = self.videoEncoderSettings[key];

status = VTSessionSetProperty(self.compressionSession, (__bridge CFStringRef)key, (__bridge CFTypeRef)value);
if (status != noErr) {
if (*error == nil) {
*error = [NSError errorWithDomain:SDLErrorDomainStreamingMediaVideo code:SDLStreamingVideoErrorConfigurationCompressionSessionSetPropertyFailure userInfo:@{ @"OSStatus" : @(status) }];
}

return NO;
}

return NO;
}

return YES;
Expand Down