GStreamer 公式のドキュメントには Cocoa application に GStreamer を組み込む手順は無いんですが、やってみたら思いのほか簡単だった (Android や iOS に似てた) ので紹介します。
ここでは GStreamer の解説をするつもりは全くなく、「Cocoa application にはこんな感じでGStreamer 入れるんだー、へー。」くらいに見てください。
完成形
題材として、 iOS tutorial 3 の macOS 版 + α を作っていきます。
見た目はこんな感じ。
完成形のソースコードはこちら↓から
環境
- Macbook Pro Early 2015
- macOS Sierra 10.12.6
- Xcode 8.3.3
1. GStreamer.framework のインストール
https://gstreamer.freedesktop.org/data/pkg/osx/ からパッケージを2つダウンロードします。
※ 偶数バージョン (1.10.*, 1.12.* とか) のうち新しいものをダウンロードしておけばOK
今回ダウンロードしたもの↓
gstreamer-1.0-1.12.3-x86_64.pkggstreamer-1.0-devel-1.12.3-x86_64.pkg
ダウンロードしたら2つともインストールしてください。
2. プロジェクトの作成
Cocoa Application として作ります。
3. プロジェクトの設定
プロジェクトの初期設定は Installing on Mac OS X を参考にやっていきます。
3.1. GStreamer.framework の設定
はじめに、Linked Frameworks and Libraries へ /Library/Frameworks/GStreamer.framework を追加します.
いれるとこんな感じに。
3.2. Build Settings の設定
Build Settings へ移動し、検索窓に header search path と入れます。
その中の Header Search Paths に /Library/Frameworks/GStreamer.framework/Headers を recursive で追加します。
次に、検索窓に c flags と入れて Other C Flags に
-I/Library/Frameworks/GStreamer.framework/Headers を追加します。
4. GStreamer のリンク確認
iOS tutorial 3 相当の実装を始める前に、GStreamer のリンクを確認するため、ミニマムな Tutorial 1 (画面なし) の実装をしてみます。
4.1. GStreamerBackend クラスの追加
Cocoa class を選択して追加します。
言語は Objective-C を選択します (間違って swift を選択した場合はやり直しです
)
「Bridging header いる?」って聞かれるので作っておいてください。
4.2. GStreamerBackend クラスの実装
クラス追加ができたらあとは実装です。
# import <Foundation/Foundation.h>
@interface GStreamerBackend : NSObject
-(NSString*) getGStreamerVersion;
@end
# import "GStreamerBackend.h"
# include <gst/gst.h>
@implementation GStreamerBackend
-(NSString*) getGStreamerVersion
{
char *version_utf8 = gst_version_string();
NSString *version_string = [NSString stringWithUTF8String:version_utf8];
g_free(version_utf8);
return version_string;
}
@end
4.3. Bridge-Header に GStreamerBackend を追加
Swift から Objective-C のクラスを使用するために Bridge header に GStreamerBackend.h を追加します
# import "GStreamerBackend.h"
4.4. ViewController から GStreamerBackend クラスを使う
GStreamerBackend インスタンスを作って、getGStreamerVersion() を叩きます。
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(GStreamerBackend().getGStreamerVersion())
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
4.5. 動かしてみる
以上で iOS tutorial 1 相当の実装ができました。実際に動かしてみましょう。
デバッグログに GStreamer バージョンが表示されるなんとも味気ないアプリの完成です ![]()
5. 画面作り
ここからやっと iOS tutorial 3 相当の実装が始まります。まずは画面を作りましょう。
ボタンの配置や Auto layout を設定するかは自由ですが、以下の 5つの View と 2つの Constraint は必ず必要です。(その他は気分で設定してください笑)
- Views
- Button x2 → 再生と一時停止
- Label x1 → パイプラインの状態を表示するためのメッセージ
- CustomBox x1 → 動画の表示領域を決定
- CustomView x1 → GStreamer の描画先
- Constraints
- CustomView の width
- CustomView の height
ちなみに、 iOS tutorial 3 では UIView 中に UIView だったんですが、Appkit だと StoryBoard 上で NSView の background color を付けれなかったので Box を使っています。
background color を気にしないのであれば、NSView の中に NSView でも全く問題なしです :)
6. Storyboard と ViewController の連携
iOS アプリ作っている人にはおなじみの アレ です。ペタペタしてやってください。
※ CustomBox に関しては、Box の中にある NSView を Container としてください。
StoryBoard
ViewController
import Cocoa
class ViewController: NSViewController, GStreamerBackendDelegate {
+ @IBOutlet weak var video_container_view: NSView!
+ @IBOutlet weak var video_view: NSView!
+ @IBOutlet weak var video_view_width_constraint: NSLayoutConstraint!
+ @IBOutlet weak var video_view_height_constraint: NSLayoutConstraint!
+ @IBOutlet weak var play_button: NSButton!
+ @IBOutlet weak var pause_button: NSButton!
+ @IBOutlet weak var message_label: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
}
[省略]
}
7. OpenglNSView の実装
GStreamer の描画先として NSView を使うには EaglUIView 相当のもの (= OpenglNSView) を作る必要があります。
iOS tutorial 3: EaglUIView
https://gstreamer.freedesktop.org/documentation/tutorials/ios/video.html#eagluiview
New File... から Swift file を追加し OpenglNSView.swift を作ります。
OpenglNSView.swift に以下のコードをコピペします。
import Foundation
import OpenGL
import Cocoa
class OpenglNSView: NSView {
override func makeBackingLayer() -> CALayer {
return CAOpenGLLayer();
}
}
最後に StoryBoard の Video view のクラスを OpenglNSView に変更します。
OpenglNSView の詳細が気になる人は チュートリアル と Apple のドキュメント を読んでみてください。
8. iOS tutorial 3からコードをコピペ
iOS tutorial 3 から以下の3つのコードをコピペしてきます。
9. ビルドを通す
チュートリアルからコピペしただけではビルドが通りません。通すために以下の2つを行います。
- UIView を NSView に置換
- NSView のために Cocoa/Cocoa.h をインポート
UIView の置換
UIView を NSView に置換します。Xcode の Replace 機能なり、sed なりで行ってください。全部でこのくらいあるはずです。
Cocoa/Cocoa.h のインポート
GStreamerBackend.h に Cocoa/Cocoa.h を追加します。
#import <Foundation/Foundation.h>
+ #import <Cocoa/Cocoa.h>
#import "GStreamerBackendDelegate.h"
@interface GStreamerBackend : NSObject
[省略]
@end
この2ステップを行えば ビルドは通るようになった はずです。
10. ViewController の実装
チュートリアルの ViewController を swift 化します。
import Cocoa
class ViewController: NSViewController, GStreamerBackendDelegate {
@IBOutlet weak var video_container_view: NSView!
@IBOutlet weak var video_view: NSView!
@IBOutlet weak var video_view_width_constraint: NSLayoutConstraint!
@IBOutlet weak var video_view_height_constraint: NSLayoutConstraint!
@IBOutlet weak var play_button: NSButton!
@IBOutlet weak var pause_button: NSButton!
@IBOutlet weak var message_label: NSTextField!
let media_width: CGFloat = 320
let media_height: CGFloat = 240
var gst_backend: GStreamerBackend? = nil
override func viewDidLoad() {
super.viewDidLoad()
play_button.isEnabled = false
pause_button.isEnabled = false
gst_backend = GStreamerBackend.init(self, videoView: video_view)
}
@IBAction func play(_ sender: Any) {
gst_backend?.play()
}
@IBAction func pause(_ sender: Any) {
gst_backend?.pause()
}
override func viewDidLayout() {
let view_width: CGFloat = video_container_view.bounds.size.width;
let view_height: CGFloat = video_container_view.bounds.size.height;
let correct_height = view_width * media_height / media_width;
let correct_width = view_height * media_width / media_height;
if (correct_height < view_height) {
video_view_height_constraint.constant = correct_height;
video_view_width_constraint.constant = view_width;
} else {
video_view_width_constraint.constant = correct_width;
video_view_height_constraint.constant = view_height;
}
}
func gstreamerInitialized() {
DispatchQueue.main.async {
self.play_button.isEnabled = true
self.pause_button.isEnabled = true
self.message_label.stringValue = "Ready"
}
}
func gstreamerSetUIMessage(_ message: String!) {
DispatchQueue.main.async {
self.message_label.stringValue = message
}
}
}
11. gst_init() を追加
ViewController を実装して完成!と思いきや、 重要なステップ が残っています。
そう、まだこのコードは gst_init() をしてないんです。gst_init() をしなければ、プラグインのロードも行われないので、「エレメント見つかりません」エラーが出てしまいます。
GStreamerBackend.m の app_function() へ gst_init(NULL, NULL) 追加しましょう。
[省略]
/* Main method for the bus monitoring code */
-(void) app_function
{
GstBus *bus;
GSource *bus_source;
GError *error = NULL;
gst_init(NULL, 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("playbin uri=https://download.blender.org/durian/trailer/sintel_trailer-480p.mp4", &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;
}
[省略]
}
@end
↑ここではついでに gst_parse_launch() のパイプラインも変えておきました。この辺は好みで変えてください。
完成
お疲れ様です、以上で完成です! ![]()
重要なところは、 7. OpenglNSView の実装, 9. ビルドを通す, 11. gst_init() の追加 くらいなものなので iOS → macOS は意外と簡単ですね。
あとは、パイプラインを変えるなり UI をいじるなりして遊んでみてください :)













