GStreamer iOS教程2 —— 运行pipeline

1. 目标

      在Basic和Playback的教程中(注:另外两个教程),GStreamer可以和GLib的主循环完美的集成起来,这样能用一种简单的方法同时监控pipeline的操作和UI的处理。而在不支持GLib的iOS和Android平台上就必须小心对于pipeline的操作——不能阻塞住UI。

      这份教程讲述了:

  • 如何把GStreamer的相关处理代码放到其他的线程(DispatchQueue),保持UI仍然保留在主线程(MainDispatchQueue)
  • 在ObjC的UI代码中如何和GStreamer的C代码通信
2. 介绍
      当由UI界面的时候,如果应用等待GStreamer的回传消息然后进行UI的处理是非常悲催的。通常的做法是(用GTK+toolkit做例子哈)让GMainLoop(GLib)来处理收到的event,无论是UI的还是GStreamer发出的。
      悲催的是这个方法不适合其他非基于GLib的图形系统(比如iOS的GocoaTouch框架),我们的方法是在另一个线程里面简单的调用GMainLoop,确保不会阻塞UI主线程。
      这个教程还会指出几个ObjC和C互相调用时需要注意的几个地方。
      下面的代码使用的pipeline仅仅使用了audiotestsrc和autoaudiosink两个element。UI上包含两个按钮,用来设置pipeline的状态PLAYING/ PAUSED。还有一个Label用来显示一些信息(错误信息或状态改变)。

3. UI
      这个界面底下包含了一个工具条,工具条上放了Play和Pause两个按钮。工具条上方就是一个Label,用来显示GStreamer的信息。本次教程就包含这么多内容,后面的教程会在这个基础上逐渐增加内容。
ViewController.m
[objc]  view plain copy
  1. #import "ViewController.h"  
  2. #import "GStreamerBackend.h"  
  3. #import <UIKit/UIKit.h>  
  4.   
  5. @interface ViewController () {  
  6.     GStreamerBackend *gst_backend;  
  7. }  
  8.   
  9. @end  
  10.   
  11. @implementation ViewController  
  12.   
  13. /* 
  14.  * Methods from UIViewController 
  15.  */  
  16.   
  17. - (void)viewDidLoad  
  18. {  
  19.     [super viewDidLoad];  
  20.       
  21.     play_button.enabled = FALSE;  
  22.     pause_button.enabled = FALSE;  
  23.   
  24.     gst_backend = [[GStreamerBackend alloc] init:self];  
  25. }  
  26.   
  27. - (void)didReceiveMemoryWarning  
  28. {  
  29.     [super didReceiveMemoryWarning];  
  30.     // Dispose of any resources that can be recreated.  
  31. }  
  32.   
  33. /* Called when the Play button is pressed */  
  34. -(IBAction) play:(id)sender  
  35. {  
  36.     [gst_backend play];  
  37. }  
  38.   
  39. /* Called when the Pause button is pressed */  
  40. -(IBAction) pause:(id)sender  
  41. {  
  42.     [gst_backend pause];  
  43. }  
  44.   
  45. /* 
  46.  * Methods from GstreamerBackendDelegate 
  47.  */  
  48.   
  49. -(void) gstreamerInitialized  
  50. {  
  51.     dispatch_async(dispatch_get_main_queue(), ^{  
  52.         play_button.enabled = TRUE;  
  53.         pause_button.enabled = TRUE;  
  54.         message_label.text = @"Ready";  
  55.     });  
  56. }  
  57.   
  58. -(void) gstreamerSetUIMessage:(NSString *)message  
  59. {  
  60.     dispatch_async(dispatch_get_main_queue(), ^{  
  61.         message_label.text = message;  
  62.     });  
  63. }  
  64.   
  65. @end  
     在这个类里面包含了一个GStreamerBackend的实例,
[objc]  view plain copy
  1. @interface ViewController () {  
  2.     GStreamerBackend *gst_backend;  
  3. }  
     在viewDidLoad方法里面创建并调用了自定义的init方法
[objc]  view plain copy
  1. - (void)viewDidLoad  
  2. {  
  3.     [super viewDidLoad];  
  4.       
  5.     play_button.enabled = FALSE;  
  6.     pause_button.enabled = FALSE;  
  7.   
  8.     gst_backend = [[GStreamerBackend alloc] init:self];  
  9. }  
     这个自定义的init方法必须传入一个对象作为UI的delegate(本例是使用了self)

     在viewDidLoad的时候,Play/Pause两个按钮都是不能使用的,直到GStreamerBackend通知初始化结束为止。
[objc]  view plain copy
  1. <pre code_snippet_id="126256" snippet_file_name="blog_20131224_4_2413796" name="code" class="objc">/* Called when the Play button is pressed */  
  2. -(IBAction) play:(id)sender  
  3. {  
  4.     [gst_backend play];  
  5. }  
  6.   
  7. /* Called when the Pause button is pressed */  
  8. -(IBAction) pause:(id)sender  
  9. {  
  10.     [gst_backend pause];  
  11. }</pre><pre code_snippet_id="126256" snippet_file_name="blog_20131224_5_4191702" name="code" class="objc"></pre><pre code_snippet_id="126256" snippet_file_name="blog_20131224_5_4191702"></pre>      在用户按下Play/Pause按钮时,上面的方法会被调用。我们看到仅仅就是简单的调用了GStreamerBackend里面对应的方法。  
  12. <pre></pre>  
  13. <pre></pre>  
[objc]  view plain copy
  1. -(void) gstreamerInitialized  
  2. {  
  3.     dispatch_async(dispatch_get_main_queue(), ^{  
  4.         play_button.enabled = TRUE;  
  5.         pause_button.enabled = TRUE;  
  6.         message_label.text = @"Ready";  
  7.     });  
  8. }  
     gstreamerInitialized方法是定义在GStreamerBackendDelegate协议里面的,用来标识后台已经准备好,可以接受命令了。在这个例子中我们把Play/Pause按钮激活,并显示“Ready”信息。这个方法不是在UI线程里面运行的,所以要用dispatch_async()方法把UI的内容封起来。
[objc]  view plain copy
  1. -(void) gstreamerSetUIMessage:(NSString *)message  
  2. {  
  3.     dispatch_async(dispatch_get_main_queue(), ^{  
  4.         message_label.text = message;  
  5.     });  
  6. }  
     gstreamerSetUIMessage:方法同样是定义在GStreamerBackendDelegate协议里面的,当后台有消息通知UI的时候会调用这个方法。在这个例子里面消息会拷贝到界面的Label控件上,当然,也同样要用dispatch_async()方法来封装。

4. GStreamer后端
      GStreamerBackend类处理了所有和GStreamer相关的内容,并给应用提供了一个简单的接口。这个接口并不需要实现所有的GStreamer的细节,当需要引起UI的变化的时候,调用GSreamerBackendDelegate协议来解决。
GStreamerBackend.m
[objc]  view plain copy
  1. #import "GStreamerBackend.h"  
  2.   
  3. #include <gst/gst.h>  
  4.   
  5. GST_DEBUG_CATEGORY_STATIC (debug_category);  
  6. #define GST_CAT_DEFAULT debug_category  
  7.   
  8. @interface GStreamerBackend()  
  9. -(void)setUIMessage:(gchar*) message;  
  10. -(void)app_function;  
  11. -(void)check_initialization_complete;  
  12. @end  
  13.   
  14. @implementation GStreamerBackend {  
  15.     id ui_delegate;        /* Class that we use to interact with the user interface */  
  16.     GstElement *pipeline;  /* The running pipeline */  
  17.     GMainContext *context; /* GLib context used to run the main loop */  
  18.     GMainLoop *main_loop;  /* GLib main loop */  
  19.     gboolean initialized;  /* To avoid informing the UI multiple times about the initialization */  
  20. }  
  21.   
  22. /* 
  23.  * Interface methods 
  24.  */  
  25.   
  26. -(id) init:(id) uiDelegate  
  27. {  
  28.     if (self = [super init])  
  29.     {  
  30.         self->ui_delegate = uiDelegate;  
  31.   
  32.         GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-2"0"iOS tutorial 2");  
  33.         gst_debug_set_threshold_for_name("tutorial-2", GST_LEVEL_DEBUG);  
  34.   
  35.         /* Start the bus monitoring task */  
  36.         dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
  37.             [self app_function];  
  38.         });  
  39.     }  
  40.   
  41.     return self;  
  42. }  
  43.   
  44. -(void) dealloc  
  45. {  
  46.     if (pipeline) {  
  47.         GST_DEBUG("Setting the pipeline to NULL");  
  48.         gst_element_set_state(pipeline, GST_STATE_NULL);  
  49.         gst_object_unref(pipeline);  
  50.         pipeline = NULL;  
  51.     }  
  52. }  
  53.   
  54. -(void) play  
  55. {  
  56.     if(gst_element_set_state(pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {  
  57.         [self setUIMessage:"Failed to set pipeline to playing"];  
  58.     }  
  59. }  
  60.   
  61. -(void) pause  
  62. {  
  63.     if(gst_element_set_state(pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) {  
  64.         [self setUIMessage:"Failed to set pipeline to paused"];  
  65.     }  
  66. }  
  67.   
  68. /* 
  69.  * Private methods 
  70.  */  
  71.   
  72. /* Change the message on the UI through the UI delegate */  
  73. -(void)setUIMessage:(gchar*) message  
  74. {  
  75.     NSString *string = [NSString stringWithUTF8String:message];  
  76.     if(ui_delegate && [ui_delegate respondsToSelector:@selector(gstreamerSetUIMessage:)])  
  77.     {  
  78.         [ui_delegate gstreamerSetUIMessage:string];  
  79.     }  
  80. }  
  81.   
  82. /* Retrieve errors from the bus and show them on the UI */  
  83. static void error_cb (GstBus *bus, GstMessage *msg, GStreamerBackend *self)  
  84. {  
  85.     GError *err;  
  86.     gchar *debug_info;  
  87.     gchar *message_string;  
  88.       
  89.     gst_message_parse_error (msg, &err, &debug_info);  
  90.     message_string = g_strdup_printf ("Error received from element %s: %s", GST_OBJECT_NAME (msg->src), err->message);  
  91.     g_clear_error (&err);  
  92.     g_free (debug_info);  
  93.     [self setUIMessage:message_string];  
  94.     g_free (message_string);  
  95.     gst_element_set_state (self->pipeline, GST_STATE_NULL);  
  96. }  
  97.   
  98. /* Notify UI about pipeline state changes */  
  99. static void state_changed_cb (GstBus *bus, GstMessage *msg, GStreamerBackend *self)  
  100. {  
  101.     GstState old_state, new_state, pending_state;  
  102.     gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);  
  103.     /* Only pay attention to messages coming from the pipeline, not its children */  
  104.     if (GST_MESSAGE_SRC (msg) == GST_OBJECT (self->pipeline)) {  
  105.         gchar *message = g_strdup_printf("State changed to %s", gst_element_state_get_name(new_state));  
  106.         [self setUIMessage:message];  
  107.         g_free (message);  
  108.     }  
  109. }  
  110.   
  111. /* Check if all conditions are met to report GStreamer as initialized. 
  112.  * These conditions will change depending on the application */  
  113. -(void) check_initialization_complete  
  114. {  
  115.     if (!initialized && main_loop) {  
  116.         GST_DEBUG ("Initialization complete, notifying application.");  
  117.         if (ui_delegate && [ui_delegate respondsToSelector:@selector(gstreamerInitialized)])  
  118.         {  
  119.             [ui_delegate gstreamerInitialized];  
  120.         }  
  121.         initialized = TRUE;  
  122.     }  
  123. }  
  124.   
  125. /* Main method for the bus monitoring code */  
  126. -(void) app_function  
  127. {  
  128.     GstBus *bus;  
  129.     GSource *bus_source;  
  130.     GError *error = NULL;  
  131.   
  132.     GST_DEBUG ("Creating pipeline");  
  133.   
  134.     /* Create our own GLib Main Context and make it the default one */  
  135.     context = g_main_context_new ();  
  136.     g_main_context_push_thread_default(context);  
  137.       
  138.     /* Build pipeline */  
  139.     pipeline = gst_parse_launch("audiotestsrc ! audioconvert ! audioresample ! autoaudiosink", &error);  
  140.     if (error) {  
  141.         gchar *message = g_strdup_printf("Unable to build pipeline: %s", error->message);  
  142.         g_clear_error (&error);  
  143.         [self setUIMessage:message];  
  144.         g_free (message);  
  145.         return;  
  146.     }  
  147.       
  148.     /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */  
  149.     bus = gst_element_get_bus (pipeline);  
  150.     bus_source = gst_bus_create_watch (bus);  
  151.     g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func, NULLNULL);  
  152.     g_source_attach (bus_source, context);  
  153.     g_source_unref (bus_source);  
  154.     g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, (__bridge voidvoid *)self);  
  155.     g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, (__bridge voidvoid *)self);  
  156.     gst_object_unref (bus);  
  157.       
  158.     /* Create a GLib Main Loop and set it to run */  
  159.     GST_DEBUG ("Entering main loop...");  
  160.     main_loop = g_main_loop_new (context, FALSE);  
  161.     [self check_initialization_complete];  
  162.     g_main_loop_run (main_loop);  
  163.     GST_DEBUG ("Exited main loop");  
  164.     g_main_loop_unref (main_loop);  
  165.     main_loop = NULL;  
  166.       
  167.     /* Free resources */  
  168.     g_main_context_pop_thread_default(context);  
  169.     g_main_context_unref (context);  
  170.     gst_element_set_state (pipeline, GST_STATE_NULL);  
  171.     gst_object_unref (pipeline);  
  172.       
  173.     return;  
  174. }  
  175.   
  176. @end  
      其中,接口方法是:
[objc]  view plain copy
  1. -(id) init:(id) uiDelegate  
  2. {  
  3.     if (self = [super init])  
  4.     {  
  5.         self->ui_delegate = uiDelegate;  
  6.   
  7.         GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-2"0"iOS tutorial 2");  
  8.         gst_debug_set_threshold_for_name("tutorial-2", GST_LEVEL_DEBUG);  
  9.   
  10.         /* Start the bus monitoring task */  
  11.         dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
  12.             [self app_function];  
  13.         });  
  14.     }  
  15.   
  16.     return self;  
  17. }  
      这个init方法通过调用[super init]来生成实例,保存delegate的对象用来做UI互动,接着调用app_function并运行在一个独立的并发的线程里面,app_function会一直监听GStreamer总线,看看有没有应用需要处理的消息或者警告发生。
      init:方法同样注册了一个新的GStreamer调试类别并设置了吐出的信息的等级,我们就可以在Xcode里面看到打印信息了。
[objc]  view plain copy
  1. -(void) dealloc  
  2. {  
  3.     if (pipeline) {  
  4.         GST_DEBUG("Setting the pipeline to NULL");  
  5.         gst_element_set_state(pipeline, GST_STATE_NULL);  
  6.         gst_object_unref(pipeline);  
  7.         pipeline = NULL;  
  8.     }  
  9. }  
      dealloc方法把pipeline置成NULL状态并释放它。
[objc]  view plain copy
  1. -(void) play  
  2. {  
  3.     if(gst_element_set_state(pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {  
  4.         [self setUIMessage:"Failed to set pipeline to playing"];  
  5.     }  
  6. }  
  7.   
  8. -(void) pause  
  9. {  
  10.     if(gst_element_set_state(pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) {  
  11.         [self setUIMessage:"Failed to set pipeline to paused"];  
  12.     }  
  13. }  
    play/pause方法仅仅简单的设置pipeline状态的该变,并在出错的时候通知UI
    下面是文件中几个私有方法:
[objc]  view plain copy
  1. -(void)setUIMessage:(gchar*) message  
  2. {  
  3.     NSString *string = [NSString stringWithUTF8String:message];  
  4.     if(ui_delegate && [ui_delegate respondsToSelector:@selector(gstreamerSetUIMessage:)])  
  5.     {  
  6.         [ui_delegate gstreamerSetUIMessage:string];  
  7.     }  
  8. }  
      setUIMessage:方法是把GStreamer使用的C的字符串转变成NSString*字符串,然后调用GStreamerBackendProtocal协议里面的gstreamerSetUIMessage:方法来在屏幕上显示出来。
      因为这个方法是optional的,所以需要用respondsToSelector来判一下是否存在。
[objc]  view plain copy
  1. /* Retrieve errors from the bus and show them on the UI */  
  2. static void error_cb (GstBus *bus, GstMessage *msg, GStreamerBackend *self)  
  3. {  
  4.     GError *err;  
  5.     gchar *debug_info;  
  6.     gchar *message_string;  
  7.       
  8.     gst_message_parse_error (msg, &err, &debug_info);  
  9.     message_string = g_strdup_printf ("Error received from element %s: %s", GST_OBJECT_NAME (msg->src), err->message);  
  10.     g_clear_error (&err);  
  11.     g_free (debug_info);  
  12.     [self setUIMessage:message_string];  
  13.     g_free (message_string);  
  14.     gst_element_set_state (self->pipeline, GST_STATE_NULL);  
  15. }  
  16.   
  17. /* Notify UI about pipeline state changes */  
  18. static void state_changed_cb (GstBus *bus, GstMessage *msg, GStreamerBackend *self)  
  19. {  
  20.     GstState old_state, new_state, pending_state;  
  21.     gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);  
  22.     /* Only pay attention to messages coming from the pipeline, not its children */  
  23.     if (GST_MESSAGE_SRC (msg) == GST_OBJECT (self->pipeline)) {  
  24.         gchar *message = g_strdup_printf("State changed to %s", gst_element_state_get_name(new_state));  
  25.         [self setUIMessage:message];  
  26.         g_free (message);  
  27.     }  
  28. }  
      error_cb()和state_changed_cb()是注册的两个回调,分别在GStreamer出错和状态变化的时候被调用。这两个回调的目的是当事件发生时能通知到用户。
      这两个回调函数在Base的教程中出现了多次,实现起来也除了下面2点之外基本一致:一个是消息使用私有方法setUIMessage:来传递到UI;第二个是要调用setUIMessage:就需要一个GStreamerBackend的实例,通过callback的userdata来传递,这个在下面讨论app_function里回调的注册时可以看到
[objc]  view plain copy
  1. -(void) check_initialization_complete  
  2. {  
  3.     if (!initialized && main_loop) {  
  4.         GST_DEBUG ("Initialization complete, notifying application.");  
  5.         if (ui_delegate && [ui_delegate respondsToSelector:@selector(gstreamerInitialized)])  
  6.         {  
  7.             [ui_delegate gstreamerInitialized];  
  8.         }  
  9.         initialized = TRUE;  
  10.     }  
  11. }  
       check_initialization_complete()方法确认满足所有的条件之后通知UI后台GStreamer准备完成。在这个教程里面这个条件非常简单,仅仅是主循环存在并且没有通知过UI。后续的教程这里会更加复杂。
      绝大部分的GStreamer的行为都是在app_function里面实现的,这些代码和android的教程几乎一致。
[objc]  view plain copy
  1. /* Create our own GLib Main Context and make it the default one */  
  2. context = g_main_context_new ();  
  3. g_main_context_push_thread_default(context);  
      这里第一次创建了一个GLib的上下文,使用g_main_context_new(),然后用g_main_context_push_thread_default()来创建了一个线程

[objc]  view plain copy
  1. /* Build pipeline */  
  2. pipeline = gst_parse_launch("audiotestsrc ! audioconvert ! audioresample ! autoaudiosink", &error);  
  3. if (error) {  
  4.     gchar *message = g_strdup_printf("Unable to build pipeline: %s", error->message);  
  5.     g_clear_error (&error);  
  6.     [self setUIMessage:message];  
  7.     g_free (message);  
  8.     return;  
  9. }  
      这里用gst_arse_launch()方法很轻易的创建了一个pipeline。在这个教程里面仅仅audiotestsrc和autoaudiosink两个element需要完成适配。

[objc]  view plain copy
  1. /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */  
  2. bus = gst_element_get_bus (pipeline);  
  3. bus_source = gst_bus_create_watch (bus);  
  4. g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func, NULLNULL);  
  5. g_source_attach (bus_source, context);  
  6. g_source_unref (bus_source);  
  7. g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, (__bridge voidvoid *)self);  
  8. g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, (__bridge voidvoid *)self);  
  9. gst_object_unref (bus);  
      这几行创建了一个总线信号的监视器,并和设置了需要监视的信号,这些和Basic教程里面的做法也是一致的。这个监视器我们这里是一步一步创建的,并非调用gst_bus_add_signal_watch()来创建,这样可以看清如何使用GLib的一些内容。这里需要指出的是使用了__bridge来把一个ObjC的对象指针转换成C语言里面的指针。

      这里我们不需要太担心对象的所有权的转移问题,因为在回调里面userdata会把这个指针带回来,重新转换成GStreamerBackend的对象指针

[objc]  view plain copy
  1. /* Create a GLib Main Loop and set it to run */  
  2. GST_DEBUG ("Entering main loop...");  
  3. main_loop = g_main_loop_new (context, FALSE);  
  4. [self check_initialization_complete];  
  5. g_main_loop_run (main_loop);  
  6. GST_DEBUG ("Exited main loop");  
  7. g_main_loop_unref (main_loop);  
  8. main_loop = NULL;  
      最后,主循环创建并开始运行,在进入主循环之前,我们调用了check_initialization_complete()方法。主循环会一直运行,直到退出为止。

      这篇教程有点长了,主要是需要讲清楚一系列的基础内容。在这个基础之上,后面的会比较短一些,并且会只关注新的内容。

5. 结论

      这篇教程主要讲述了:

  • 使用DispatchQueue如何让GStreamer的代码单独运行在子线程中
  • 如何在ObjC的UI代码和GStreamer的C代码中传递对象

      在这篇教程里面的方法,象check_initialization_complete()和app_function(),init:,play;,pause:,gstreamerInitialized:和setUIMessage:等接口后续会简单修改一下继续使用,所以最好熟悉一下。


原文链接:http://blog.csdn.net/sakulafly/article/details/175240

你可能感兴趣的:(GStreamer iOS教程2 —— 运行pipeline)