-
-
Save nkallen/67f5d19d1198d86e7f6b4ee6a8e53571 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "pipeline.h" | |
#include <gst/gst.h> | |
#import <Foundation/Foundation.h> | |
#include <gst/video/video.h> | |
GstState state2GstState(State state) { | |
switch (state) { | |
case playing: | |
return GST_STATE_PLAYING; | |
break; | |
case paused: | |
return GST_STATE_PAUSED; | |
break; | |
case null: | |
return GST_STATE_NULL; | |
break; | |
case ready: | |
return GST_STATE_READY; | |
break; | |
default: | |
break; | |
} | |
} | |
State gstState2State(GstState state) { | |
switch (state) { | |
case GST_STATE_PLAYING: | |
return playing; | |
break; | |
case GST_STATE_PAUSED: | |
return paused; | |
break; | |
case GST_STATE_READY: | |
return ready; | |
break; | |
default: | |
return null; | |
break; | |
} | |
} | |
NSError *parseError(GError *err) { | |
NSDictionary *userInfo = @{@"message": [NSString stringWithUTF8String:err->message]}; | |
NSError *error = [[NSError alloc] initWithDomain:[NSString stringWithUTF8String:g_quark_to_string(err->domain)] code:err->code userInfo:userInfo]; | |
return error; | |
} | |
@interface XObject () | |
@property (readonly) GObject *underlying; | |
@end | |
@implementation XObject // FIXME | |
@synthesize underlying = _underlying; | |
- (id)initWithUnderlying:(GObject *)underlying { | |
if ( self = [super init] ) { | |
if (underlying) { | |
_underlying = underlying; | |
return self; | |
} else { | |
return nil; | |
} | |
} else { | |
return nil; | |
} | |
} | |
- (void)set:(NSString *)name to:(id)value { | |
if ([value isKindOfClass:[NSString class]]) { | |
g_object_set(self.underlying, [name UTF8String], [value UTF8String], NULL); | |
} else if ([value isKindOfClass:[NSNumber class]]) { | |
g_object_set(self.underlying, [name UTF8String], [value intValue], NULL); | |
} | |
} | |
-(void)dealloc { | |
g_object_unref(_underlying); | |
} | |
@end | |
@interface Context () | |
@property (readonly) GMainContext *underlying; | |
@end | |
@implementation Context | |
@synthesize underlying = _underlying; | |
- (id)initWithUnderlying:(GMainContext *)underlying { | |
if ( self = [super init] ) { | |
if (underlying) { | |
_underlying = underlying; | |
return self; | |
} else { | |
return nil; | |
} | |
} else { | |
return nil; | |
} | |
} | |
- (void)pushThreadDefault { | |
g_main_context_push_thread_default(self.underlying); | |
} | |
- (void)popThreadDefault { | |
g_main_context_pop_thread_default(self.underlying); | |
} | |
@end | |
@interface Source () | |
@property (readonly) GSource *underlying; | |
@end | |
@implementation Source | |
@synthesize underlying = _underlying; | |
- (id)initWithUnderlying:(GSource *)underlying { | |
if ( self = [super init] ) { | |
if (underlying) { | |
_underlying = underlying; | |
return self; | |
} else { | |
return nil; | |
} | |
} else { | |
return nil; | |
} | |
} | |
- (void)setCallbackAsync { | |
g_source_set_callback(self.underlying, (GSourceFunc) gst_bus_async_signal_func, NULL, NULL); | |
} | |
- (void)attach:(Context *)context { | |
g_source_attach(self.underlying, context.underlying); | |
} | |
-(void)dealloc { | |
g_source_unref (self.underlying); | |
} | |
@end | |
@interface Main () | |
@property (readonly) GMainLoop *underlying; | |
@end | |
@implementation Main | |
@synthesize underlying = _underlying; | |
- (id)initWithContext:(Context *)context { | |
if ( self = [super init] ) { | |
GMainLoop *underlying = g_main_loop_new (context.underlying, FALSE); | |
if (underlying) { | |
_underlying = underlying; | |
return self; | |
} else { | |
return nil; | |
} | |
} else { | |
return nil; | |
} | |
} | |
- (void)run { | |
g_main_loop_run(self.underlying); | |
} | |
- (void)dealloc { | |
g_main_loop_unref(self.underlying); | |
} | |
@end | |
// MARK: GStreamer | |
@interface Element () | |
+ (Element *)wrap: (GstElement *)element; | |
@end | |
@interface Message () | |
@property (readonly) GstMessage *underlying; | |
@end | |
@implementation StateChangeMessage | |
@synthesize oldState = _oldState; | |
@synthesize newState = _newState; | |
@synthesize pendingState = _pendingState; | |
- (id)initWithOldState:(State)oldState newState:(State)newState pendingState:(State)pendingState { | |
if ( self = [super init] ) { | |
_oldState = oldState; | |
_newState = newState; | |
_pendingState = pendingState; | |
return self; | |
} else { | |
return nil; | |
} | |
} | |
- (NSString *)description { | |
NSString *descriptionString = [NSString stringWithFormat:@"State Change: %s -> %s", gst_element_state_get_name(state2GstState(self.oldState)), gst_element_state_get_name(state2GstState(self.newState))]; | |
return descriptionString; | |
} | |
@end | |
@implementation ErrorMessage | |
@synthesize error = _error; | |
@synthesize debugInfo = _debugInfo; | |
- (id)initWithError:(NSError *)error debugInfo:(NSString *)debugInfo { | |
if ( self = [super init] ) { | |
_error = error; | |
_debugInfo = debugInfo; | |
return self; | |
} else { | |
return nil; | |
} | |
} | |
@end | |
@implementation Message | |
- (id)initWithUnderlying:(GstMessage *)underlying { | |
if ( self = [super init] ) { | |
if (underlying) { | |
_underlying = underlying; | |
return self; | |
} else { | |
return nil; | |
} | |
} else { | |
return nil; | |
} | |
} | |
- (Element *)source { | |
GstObject *object = GST_MESSAGE_SRC(self.underlying); | |
gst_object_ref(object); | |
return [Element wrap: GST_ELEMENT(object)]; // FIXME bad cast | |
} | |
- (StateChangeMessage *)parseStateChanged { | |
GstState old_state, new_state, pending_state; | |
gst_message_parse_state_changed(self.underlying, &old_state, &new_state, &pending_state); | |
return [[StateChangeMessage alloc] initWithOldState:gstState2State(old_state) newState:gstState2State(new_state) pendingState:gstState2State(pending_state)]; | |
} | |
- (ErrorMessage *)parseError { | |
GError *err; | |
gchar *debug_info; | |
gst_message_parse_error(self.underlying, &err, &debug_info); | |
NSError *error = parseError(err); | |
g_clear_error (&err); | |
g_free (debug_info); | |
return [[ErrorMessage alloc] initWithError:error debugInfo:[NSString stringWithUTF8String:debug_info]]; | |
} | |
@end | |
@implementation Bus | |
- (Source *)createWatch { | |
return [[Source alloc] initWithUnderlying: gst_bus_create_watch(GST_BUS(self.underlying))]; | |
} | |
void CallbackAdapter(GstBus *bus, GstMessage *msg, void *data) { | |
void (^block)(Bus *bus, Message *message) = (__bridge void (^)(Bus *bus, Message *message))data; | |
gst_object_ref(bus); | |
block([[Bus alloc] initWithUnderlying:G_OBJECT(bus)], [[Message alloc] initWithUnderlying:msg]); | |
} | |
- (void)onMessage:(NSString *)name callback:(void (^)(Bus *bus, Message *message))callbackBlock { | |
NSString *preciseMessage = [NSString stringWithFormat:@"message::%@", name]; | |
g_signal_connect (self.underlying, [preciseMessage UTF8String], (GCallback)CallbackAdapter, (__bridge_retained void *)callbackBlock); | |
} | |
@end | |
@implementation Element | |
+ (nonnull Element *)wrap: (nonnull GstElement *)element { | |
if (GST_IS_PIPELINE(element)) { | |
return [[Pipeline alloc] initWithUnderlying:G_OBJECT(element)]; | |
} else if (GST_IS_VIDEO_OVERLAY(element)) { | |
return [[VideoOverlay alloc] initWithUnderlying:G_OBJECT(element)]; | |
} else { | |
return [[Element alloc] initWithUnderlying:G_OBJECT(element)]; | |
} | |
} | |
- (BOOL)linkTo:(Element *)other { | |
return gst_element_link(GST_ELEMENT(self.underlying), GST_ELEMENT(other.underlying)); | |
} | |
- (BOOL)setState:(State)state { | |
return gst_element_set_state(GST_ELEMENT(self.underlying), state2GstState(state)); | |
} | |
- (NSString *)name { | |
return [NSString stringWithUTF8String:gst_element_get_name(self.underlying)]; | |
} | |
- (void) setName: (NSString *) name { | |
gst_element_set_name(self.underlying, [name UTF8String]); | |
} | |
- (BOOL)isEqual:(id)object { | |
if (self == object) { | |
return YES; | |
} | |
if (![object isKindOfClass:[Element class]]) { | |
return NO; | |
} | |
return self.underlying == ((Element *)object).underlying; | |
} | |
@end | |
@implementation Pipeline | |
- (id)init { | |
GObject *underlying = G_OBJECT(gst_pipeline_new(NULL)); | |
if (underlying) { | |
if ([super initWithUnderlying: underlying]) { | |
return self; | |
} else { | |
return nil; | |
} | |
} else { | |
return nil; | |
} | |
} | |
- (void)add:(Element *)element { | |
gst_bin_add(GST_BIN(self.underlying), GST_ELEMENT(element.underlying)); | |
} | |
- (Element *)getByInterface { // FIXME should take GType as arg | |
GType type = GST_TYPE_VIDEO_OVERLAY; | |
GstElement *element = gst_bin_get_by_interface(GST_BIN(self.underlying), type); | |
if (element == NULL) return NULL; | |
return [Element wrap:element]; | |
} | |
- (Bus *)bus { | |
return [[Bus alloc] initWithUnderlying:G_OBJECT(gst_element_get_bus(GST_ELEMENT(self.underlying)))]; | |
} | |
@end | |
@implementation ElementFactory | |
+ (Element *)make:(NSString *)name { | |
Element *element = [Element wrap:gst_element_factory_make([name UTF8String], NULL)]; | |
return element; | |
} | |
@end | |
@implementation VideoOverlay | |
- (void)setWindowHandle:(UIView *)windowHandle { | |
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(self.underlying), (guintptr) (id) windowHandle); | |
} | |
@end | |
@implementation GStreamer | |
+ (nullable Pipeline *)parseLaunch:(NSString *)command error:(NSError **)error { | |
GError *err = NULL; | |
GstElement *pipeline = gst_parse_launch([command UTF8String], &err); | |
if (err) { | |
*error = parseError(err); | |
g_clear_error (&err); | |
return NULL; | |
} | |
return [[Pipeline alloc] initWithUnderlying:G_OBJECT(pipeline)]; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment