1. 目标
在Basic和Playback的教程中(注:另外两个教程),GStreamer可以和GLib的主循环完美的集成起来,这样能用一种简单的方法同时监控pipeline的操作和UI的处理。而在不支持GLib的iOS和Android平台上就必须小心对于pipeline的操作——不能阻塞住UI。
这份教程讲述了:
#import "ViewController.h" #import "GStreamerBackend.h" #import <UIKit/UIKit.h> @interface ViewController () { GStreamerBackend *gst_backend; } @end @implementation ViewController /* * Methods from UIViewController */ - (void)viewDidLoad { [super viewDidLoad]; play_button.enabled = FALSE; pause_button.enabled = FALSE; gst_backend = [[GStreamerBackend alloc] init:self]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } /* Called when the Play button is pressed */ -(IBAction) play:(id)sender { [gst_backend play]; } /* Called when the Pause button is pressed */ -(IBAction) pause:(id)sender { [gst_backend pause]; } /* * Methods from GstreamerBackendDelegate */ -(void) gstreamerInitialized { dispatch_async(dispatch_get_main_queue(), ^{ play_button.enabled = TRUE; pause_button.enabled = TRUE; message_label.text = @"Ready"; }); } -(void) gstreamerSetUIMessage:(NSString *)message { dispatch_async(dispatch_get_main_queue(), ^{ message_label.text = message; }); } @end
@interface ViewController () { GStreamerBackend *gst_backend; }在viewDidLoad方法里面创建并调用了自定义的init方法
- (void)viewDidLoad { [super viewDidLoad]; play_button.enabled = FALSE; pause_button.enabled = FALSE; gst_backend = [[GStreamerBackend alloc] init:self]; }这个自定义的init方法必须传入一个对象作为UI的delegate(本例是使用了self)
/* Called when the Play button is pressed */ -(IBAction) play:(id)sender { [gst_backend play]; } /* Called when the Pause button is pressed */ -(IBAction) pause:(id)sender { [gst_backend pause]; }在用户按下Play/Pause按钮时,上面的方法会被调用。我们看到仅仅就是简单的调用了GStreamerBackend里面对应的方法。
-(void) gstreamerInitialized { dispatch_async(dispatch_get_main_queue(), ^{ play_button.enabled = TRUE; pause_button.enabled = TRUE; message_label.text = @"Ready"; }); }gstreamerInitialized方法是定义在GStreamerBackendDelegate协议里面的,用来标识后台已经准备好,可以接受命令了。在这个例子中我们把Play/Pause按钮激活,并显示“Ready”信息。这个方法不是在UI线程里面运行的,所以要用dispatch_async()方法把UI的内容封起来。
-(void) gstreamerSetUIMessage:(NSString *)message { dispatch_async(dispatch_get_main_queue(), ^{ message_label.text = message; }); }gstreamerSetUIMessage:方法同样是定义在GStreamerBackendDelegate协议里面的,当后台有消息通知UI的时候会调用这个方法。在这个例子里面消息会拷贝到界面的Label控件上,当然,也同样要用dispatch_async()方法来封装。
#import "GStreamerBackend.h" #include <gst/gst.h> GST_DEBUG_CATEGORY_STATIC (debug_category); #define GST_CAT_DEFAULT debug_category @interface GStreamerBackend() -(void)setUIMessage:(gchar*) message; -(void)app_function; -(void)check_initialization_complete; @end @implementation GStreamerBackend { id ui_delegate; /* Class that we use to interact with the user interface */ GstElement *pipeline; /* The running pipeline */ GMainContext *context; /* GLib context used to run the main loop */ GMainLoop *main_loop; /* GLib main loop */ gboolean initialized; /* To avoid informing the UI multiple times about the initialization */ } /* * Interface methods */ -(id) init:(id) uiDelegate { if (self = [super init]) { self->ui_delegate = uiDelegate; GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-2", 0, "iOS tutorial 2"); gst_debug_set_threshold_for_name("tutorial-2", GST_LEVEL_DEBUG); /* Start the bus monitoring task */ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self app_function]; }); } return self; } -(void) dealloc { if (pipeline) { GST_DEBUG("Setting the pipeline to NULL"); gst_element_set_state(pipeline, GST_STATE_NULL); gst_object_unref(pipeline); pipeline = NULL; } } -(void) play { if(gst_element_set_state(pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { [self setUIMessage:"Failed to set pipeline to playing"]; } } -(void) pause { if(gst_element_set_state(pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) { [self setUIMessage:"Failed to set pipeline to paused"]; } } /* * Private methods */ /* Change the message on the UI through the UI delegate */ -(void)setUIMessage:(gchar*) message { NSString *string = [NSString stringWithUTF8String:message]; if(ui_delegate && [ui_delegate respondsToSelector:@selector(gstreamerSetUIMessage:)]) { [ui_delegate gstreamerSetUIMessage:string]; } } /* Retrieve errors from the bus and show them on the UI */ static void error_cb (GstBus *bus, GstMessage *msg, GStreamerBackend *self) { GError *err; gchar *debug_info; gchar *message_string; gst_message_parse_error (msg, &err, &debug_info); message_string = g_strdup_printf ("Error received from element %s: %s", GST_OBJECT_NAME (msg->src), err->message); g_clear_error (&err); g_free (debug_info); [self setUIMessage:message_string]; g_free (message_string); gst_element_set_state (self->pipeline, GST_STATE_NULL); } /* Notify UI about pipeline state changes */ static void state_changed_cb (GstBus *bus, GstMessage *msg, GStreamerBackend *self) { GstState old_state, new_state, pending_state; gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); /* Only pay attention to messages coming from the pipeline, not its children */ if (GST_MESSAGE_SRC (msg) == GST_OBJECT (self->pipeline)) { gchar *message = g_strdup_printf("State changed to %s", gst_element_state_get_name(new_state)); [self setUIMessage:message]; g_free (message); } } /* Check if all conditions are met to report GStreamer as initialized. * These conditions will change depending on the application */ -(void) check_initialization_complete { if (!initialized && main_loop) { GST_DEBUG ("Initialization complete, notifying application."); if (ui_delegate && [ui_delegate respondsToSelector:@selector(gstreamerInitialized)]) { [ui_delegate gstreamerInitialized]; } initialized = TRUE; } } /* Main method for the bus monitoring code */ -(void) app_function { GstBus *bus; GSource *bus_source; GError *error = NULL; GST_DEBUG ("Creating pipeline"); /* Create our own GLib Main Context and make it the default one */ context = g_main_context_new (); g_main_context_push_thread_default(context); /* Build pipeline */ pipeline = gst_parse_launch("audiotestsrc ! audioconvert ! audioresample ! autoaudiosink", &error); if (error) { gchar *message = g_strdup_printf("Unable to build pipeline: %s", error->message); g_clear_error (&error); [self setUIMessage:message]; g_free (message); return; } /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */ bus = gst_element_get_bus (pipeline); bus_source = gst_bus_create_watch (bus); g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func, NULL, NULL); g_source_attach (bus_source, context); g_source_unref (bus_source); g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, (__bridge void *)self); g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, (__bridge void *)self); gst_object_unref (bus); /* Create a GLib Main Loop and set it to run */ GST_DEBUG ("Entering main loop..."); main_loop = g_main_loop_new (context, FALSE); [self check_initialization_complete]; g_main_loop_run (main_loop); GST_DEBUG ("Exited main loop"); g_main_loop_unref (main_loop); main_loop = NULL; /* Free resources */ g_main_context_pop_thread_default(context); g_main_context_unref (context); gst_element_set_state (pipeline, GST_STATE_NULL); gst_object_unref (pipeline); return; } @end其中,接口方法是:
-(id) init:(id) uiDelegate { if (self = [super init]) { self->ui_delegate = uiDelegate; GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-2", 0, "iOS tutorial 2"); gst_debug_set_threshold_for_name("tutorial-2", GST_LEVEL_DEBUG); /* Start the bus monitoring task */ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self app_function]; }); } return self; }这个init方法通过调用[super init]来生成实例,保存delegate的对象用来做UI互动,接着调用app_function并运行在一个独立的并发的线程里面,app_function会一直监听GStreamer总线,看看有没有应用需要处理的消息或者警告发生。
-(void) dealloc { if (pipeline) { GST_DEBUG("Setting the pipeline to NULL"); gst_element_set_state(pipeline, GST_STATE_NULL); gst_object_unref(pipeline); pipeline = NULL; } }dealloc方法把pipeline置成NULL状态并释放它。
-(void) play { if(gst_element_set_state(pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { [self setUIMessage:"Failed to set pipeline to playing"]; } } -(void) pause { if(gst_element_set_state(pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) { [self setUIMessage:"Failed to set pipeline to paused"]; } }play/pause方法仅仅简单的设置pipeline状态的该变,并在出错的时候通知UI
-(void)setUIMessage:(gchar*) message { NSString *string = [NSString stringWithUTF8String:message]; if(ui_delegate && [ui_delegate respondsToSelector:@selector(gstreamerSetUIMessage:)]) { [ui_delegate gstreamerSetUIMessage:string]; } }setUIMessage:方法是把GStreamer使用的C的字符串转变成NSString*字符串,然后调用GStreamerBackendProtocal协议里面的gstreamerSetUIMessage:方法来在屏幕上显示出来。
/* Retrieve errors from the bus and show them on the UI */ static void error_cb (GstBus *bus, GstMessage *msg, GStreamerBackend *self) { GError *err; gchar *debug_info; gchar *message_string; gst_message_parse_error (msg, &err, &debug_info); message_string = g_strdup_printf ("Error received from element %s: %s", GST_OBJECT_NAME (msg->src), err->message); g_clear_error (&err); g_free (debug_info); [self setUIMessage:message_string]; g_free (message_string); gst_element_set_state (self->pipeline, GST_STATE_NULL); } /* Notify UI about pipeline state changes */ static void state_changed_cb (GstBus *bus, GstMessage *msg, GStreamerBackend *self) { GstState old_state, new_state, pending_state; gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); /* Only pay attention to messages coming from the pipeline, not its children */ if (GST_MESSAGE_SRC (msg) == GST_OBJECT (self->pipeline)) { gchar *message = g_strdup_printf("State changed to %s", gst_element_state_get_name(new_state)); [self setUIMessage:message]; g_free (message); } }error_cb()和state_changed_cb()是注册的两个回调,分别在GStreamer出错和状态变化的时候被调用。这两个回调的目的是当事件发生时能通知到用户。
-(void) check_initialization_complete { if (!initialized && main_loop) { GST_DEBUG ("Initialization complete, notifying application."); if (ui_delegate && [ui_delegate respondsToSelector:@selector(gstreamerInitialized)]) { [ui_delegate gstreamerInitialized]; } initialized = TRUE; } }check_initialization_complete()方法确认满足所有的条件之后通知UI后台GStreamer准备完成。在这个教程里面这个条件非常简单,仅仅是主循环存在并且没有通知过UI。后续的教程这里会更加复杂。
/* Create our own GLib Main Context and make it the default one */ context = g_main_context_new (); g_main_context_push_thread_default(context);这里第一次创建了一个GLib的上下文,使用g_main_context_new(),然后用g_main_context_push_thread_default()来创建了一个线程
/* Build pipeline */ pipeline = gst_parse_launch("audiotestsrc ! audioconvert ! audioresample ! autoaudiosink", &error); if (error) { gchar *message = g_strdup_printf("Unable to build pipeline: %s", error->message); g_clear_error (&error); [self setUIMessage:message]; g_free (message); return; }这里用gst_arse_launch()方法很轻易的创建了一个pipeline。在这个教程里面仅仅audiotestsrc和autoaudiosink两个element需要完成适配。
/* Instruct the bus to emit signals for each received message, and connect to the interesting signals */ bus = gst_element_get_bus (pipeline); bus_source = gst_bus_create_watch (bus); g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func, NULL, NULL); g_source_attach (bus_source, context); g_source_unref (bus_source); g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, (__bridge void *)self); g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, (__bridge void *)self); gst_object_unref (bus);这几行创建了一个总线信号的监视器,并和设置了需要监视的信号,这些和Basic教程里面的做法也是一致的。这个监视器我们这里是一步一步创建的,并非调用gst_bus_add_signal_watch()来创建,这样可以看清如何使用GLib的一些内容。这里需要指出的是使用了__bridge来把一个ObjC的对象指针转换成C语言里面的指针。
这里我们不需要太担心对象的所有权的转移问题,因为在回调里面userdata会把这个指针带回来,重新转换成GStreamerBackend的对象指针
/* Create a GLib Main Loop and set it to run */ GST_DEBUG ("Entering main loop..."); main_loop = g_main_loop_new (context, FALSE); [self check_initialization_complete]; g_main_loop_run (main_loop); GST_DEBUG ("Exited main loop"); g_main_loop_unref (main_loop); main_loop = NULL;最后,主循环创建并开始运行,在进入主循环之前,我们调用了check_initialization_complete()方法。主循环会一直运行,直到退出为止。
这篇教程有点长了,主要是需要讲清楚一系列的基础内容。在这个基础之上,后面的会比较短一些,并且会只关注新的内容。
5. 结论
这篇教程主要讲述了:
在这篇教程里面的方法,象check_initialization_complete()和app_function(),init:,play;,pause:,gstreamerInitialized:和setUIMessage:等接口后续会简单修改一下继续使用,所以最好熟悉一下。