Clutter」タグアーカイブ

ClutterGst Memory Leak

Y901x beta2 にトンデモなバグがあった。
ネットで拾った 2560×1440 の超デカイ動画を再生してみたら。

memory_leak

あっというまにメモリーリーク。
小さめのファイルでのテストだと気が付かなかったよ。

ファイルサイズより使用メモリのほうが大きいのでバッファではない。
Totem では問題無し、原因はドコだろう。

Clutter, ClutterGst 側のバグかと検索検索。
皆ビデオドライバーが原因ということで終わっている、違うみたい。

自分がやった処理を一つづつコメントアウトして何が悪いか地味に探す。
見つかった、細かい理由は解らないけどそういうことだったのかよ。

out_new_frame

ClutterGstPlayer の ‘new-frame’ シグナルを処理するとこうなるようだ。
void シグナルなので打つ手無し、つまりこのハンドラは使えない。

タイマーを使わずにイケたと思ったんだけーがなぁ。
素直にタイマー処理に、せっかくなので GLib でなく Clutter のを使う。

// Timer
let timeline = new Clutter.Timeline({duration:200});
timeline.connect("new-frame", Lang.bind(this, function() {
    // Timer Handler
}));
this.timer = new Clutter.Score({loop: true});
this.timer.append(null, timeline);
//
// etc...
//
this.timer.start();

これも ‘new-frame’ だがこっちは単なるタイマー。

ハンドラをこちらに移して再び再生。
Totem と同じメモリ使用量になった、なんとか解決。

もう少し弄って明日にでも beta3 に、完成は遠いかも。
ブログネタは Mac プログラミングと交互の予定が狂いまくり。

ClutterGst Aspect Rate

Y901x 1.2 beta2 を公開しました。

色々考えた結果リサイズはキーボード操作のみに。
だってメニューからリサイズしたことなんて作った本人は一度も無いモン!

GTK+ 3.20 でショートカットウィンドウが追加されるようなのでそれで賄う予定。
ソレを使う問題点は Ubuntu には完全未対応となるってことだけ。
Ubuntu GNOME でさえ 3.18 ですから。

その Ubuntu は次で独自 API に切り換えるという破滅への第一歩を選んだみたい。
GNOME が GNOME3 でアレだけシェアを落としたのを見てきたはずなのにねぇ。
とりあえず我がサイトは Ubuntu のページを全削除しておいた。

まあそれはどうでもよくて。
アスペクト比変更機能には本当に困った。

GStreamer-devel – Difference between videoscale and videobox

gst-launch-1.0 には aspect-ratio というオプションがある。
ならば普通に比率を与えれば有効にできる手段があるはず!
と探してみるけど全然見つからない。

手段は違うけどこんなのも見つけた。
kaa-candy/gstreamer.py at master ? freevo/kaa-candy ? GitHub
でも clutter_actor_animatev はもう使うなとのこと。

本当に Clutter はゴロッと変わって以前のサンプルコードが全滅状態。
初期段階で手を出さないでよかったなんて思うくらい。

結局自力でやることに。

        this.actor = new Clutter.Actor();
        this.actor.set_background_color(Clutter.Color.new(0, 0, 0, 255));
        //this.content = new ClutterGst.Aspectratio();
        this.content = new ClutterGst.Content();
        this.actor.set_content(this.content);
		//
		// etc...
		//
        this.set_video_size = function() {
            let allocation = this.vbox.get_allocation();
            let offset = SEEKBAR_HEIGHT;
            if (this.is_fullscreen)
                offset = 0;
            if (this.player.get_uri() != null) {
                let w = this.src_width;
                if (this.aspectrate == 1)
                    w = this.src_height *16 / 9;
                else if (this.aspectrate == 2)
                    w = this.src_height *4 / 3;
                let h = this.src_height;
                let aw = allocation.width;
                let ah = allocation.height - offset;
                let width, height, x, y = 0;
                if (aw * h > ah * w) {
                    width = w * ah / h;
                    height = ah;
                    x = (aw - width) / 2;
                    y = 0;
                } else {
                    width = aw;
                    height = h * aw / w;
                    x = 0;
                    y = (ah - height) / 2;
                }
                this.actor.set_width(width);
                this.actor.set_height(height);
                this.actor.set_position(x, y);
            } else {
                this.actor.set_width(allocation.width);
                this.actor.set_height(allocation.height - offset);
            }
        }

ま、自力変更は x の付かない Y901 でやっていたし。
ClutterGst.Content を適切にレイアウトするだけで難しくはない。

それより色々問題が出ている。

application_id を有効にすると多重起動の一つを閉じただけで落ちる。
GLXBadDrawable というエラーで落ちる。

GtkApplication からもう一つウインドウを開く処理を入れると落ちる。
GLXBadDrawable というエラーで落ちる。

GL* だから OpenGL だよね、Clutter 側がなんとかしてくれないとお手上げ。

ClutterGst 3.0 Get Media Width, Height

スマホやタブレット PC の動画再生は常にフルスクリーン。
ソースの原寸や倍率なんてナンセンス。

で、今や QuickTime, Totem 等のデフォルトプレイヤーはそんな感じに。
だけどまだタブレット PC の普及は進んでいないんですよ。

Y901x beta1 で等倍や二倍を消してしばらく使ってみたけどしっくりこない。
やはり等倍等にする機能は付けることにする。
さて、ClutterGst ではどうやってソースサイズを得るのか。

ClutterGstPlayer: Clutter Gst 3.0.18 Reference Manual

clutter_gst_player_get_video_sink
にて ClutterGstVideoSink 構造体を得ることができる。

Clutter とあるけどチト試すと GstVideoSink と同じように扱えるようだ。
だとすれば C, Python のコードが Web 上に山程ある、もちろん英語で。
ソレらを JavaScript に変換すればよさそう。

つか Y901x 1.1 はズバリだ、早速変換してみよう。
又パラメータや戻り値が違うかもしれないから慎重に。

const Y901Window = new Lang.Class({
    Name: 'Y901Window',
    Extends: Gtk.ApplicationWindow,

    _init: function(app) {
        this.parent({application: app});
        // var
        this.src_width = 0;
        this.src_height = 0;
        //
        // etc...
        //
        this.aspectratio = new ClutterGst.Aspectratio();
        this.player = new ClutterGst.Playback();
        // Ready Signal
        this.player.connect("ready", Lang.bind(this, function(player) {
            // Get Media About
            let vsink = player.get_video_sink();
            let it = vsink.iterate_pads(); // it @ GstPad Iteraror
            it.foreach(Lang.bind(this, function(pad) {
                let caps = pad.get_current_caps();
                let struct = caps.get_structure(0);
                this.src_width = struct.get_int("width")[1];
                this.src_height = struct.get_int("height")[1];
            }));
        }));
        //
        // etc...
        //
        this.show_all();
    }
});

ビックリするほど同じだった。
解説は Python Gst 関連のもっと詳しいサイトで、もちろん英語。

foreach にアレ?と思うかもだがコレ gst_iterator_foreach ですんで。
コイツも Gjs ではメソッドになるので Lang.bind にて this を親に置き換える。
つか Lang.bind ってネストできたのか、試しに書いて上手くいっただけだが。

原寸は得ることができたけどウインドウのリサイズはどうしよう?
gtk_widget_set_size_request は縮小できなるので使えない。

Y901x 1.1 は親ウインドウをリサイズでまかなっていた。
しかし今度のは GtkHeaderBar がある、内寸と外寸が一致しない。
うーん駄目元で中の GtkBox との差分を計算してリサイズしてみよう。

        this.change_video_size = function(n) {
            let w = Math.round(this.src_width * n);
            let h = Math.round(this.src_height * n) + SEEKBAR_HEIGHT;
            let diff_x = this.get_allocated_width() - this.vbox.get_allocated_width();
            let diff_y = this.get_allocated_height() - this.vbox.get_allocated_height();
            this.resize(w + diff_x, h + diff_y);
        };

ちなみに this のメソッドには Lang.bind はいらない。

この関数に 1 を渡すと、見事に原寸の動画が再生できた。
計算方法は意外にもコレでいいようだ、GtkHeaderBar よくワカンネエ!

Clutter @ Mouse Drag

Y901x beta は Clutter でシークバーを作っている。
最初は詰まりまくった、なんたって実際に利用するアプリに使うのは初めて。

普通に button-press-event を使ったら全然動作しなかったし。
実際 clutter_actor_set_reactive 関数を見つけるまで手段に超苦しむことに。

やはり ClutterCanvas で書き込もうとも考えた、昔ながらの方法。
でもそれ OpenGL である Clutter 上で Cairo を使うってことだ、変だよ。
しかも何故かエラー出まくり、cairo_t 引数が null になるしワケワカメ。

何か手段は無いかと試行錯誤してまさかの結論が出た。
そういえば Drag and Drop 操作はシークバーと同じ動作だと思いついた。
マウスで掴んで動かして離す、これはイケそう。

ClutterDragAction: Clutter Reference Manual

おぉ、ClutterAction にはこんなものがあるじゃないか。
コレでトラフ画像の ClutterActor を動かせばシークバーとして使えそう。
引数から X 値を得て横方向だけ動くようにすれば従来どおりの手段が使える。
と思ったけど…

#!/usr/bin/gjs

const Lang = imports.lang;
const Clutter = imports.gi.Clutter;

const ClWin = new Lang.Class({
    Name: 'ClWin',
    Extends: Clutter.Stage,

    _init: function() {
        this.parent();
        this.connect("hide", Lang.bind(this, function() {
            Clutter.main_quit();
        }));
        let actor = new Clutter.Actor();
        actor.set_background_color(Clutter.Color.new(255, 0, 0, 255));
        actor.set_size(50, 50);
        this.add_child(actor);
        // DnD
        let dgaction = new Clutter.DragAction();
        //dgaction.connect("drag-begin", Lang.bind(this, function(action, actor, event_x) {
        //    print(event_x);
        //}));
        dgaction.connect("drag-motion", Lang.bind(this, function(action, actor, delta_x) {
    		//actor.set_position(delta_x, 0);
            print(delta_x);
        }));
        //dgaction.connect("drag-end", Lang.bind(this, function(action, actor, event_x) {
        //    print(event_x);
        //}));
        actor.add_action(dgaction, true);
        actor.set_reactive(true);

        this.set_size(300, 150);
        this.show_all();
    }
});

Clutter.init(null);
new ClWin();
Clutter.main();

clutter_dnd

ハンドラ指定しただけで ClutterActor 自体がドラッグされてしまうジャン!
しかも動かすだけなら drag-motion のみ処理すればよかった。
これってそういう Action だったのNE!

更に引数の delta_x 値はドラッグされている ClutterActor 上の値だった。
つまり変化無し、これじゃシークバーなんかに使えないっつーの。
今回は無駄骨だったけどいつかこの経験が役に立つ、といいな。

だけど、コレをやったおかげで clutter_actor_set_reactive 関数を見つけた。
上記リンク先の Description に普通に書いてあったという。

this.is_drag = false;
let actor = new Clutter.Actor();
actor.connect("button-press-event", Lang.bind(this, function(actor, event) {
    this.is_drag = true;
    let [x, y] = event.get_coords();
    print("x= " + x + "; y= " + y);
}));
actor.set_reactive(true); // !!!

あら動いた、シグナルを有効にするにはやはり必須なのね。

motion-notify-event が無いのも困った。
motion-event だと Drag していなくても反応する、回避は簡単だが。

actor.connect("motion-event", Lang.bind(this, function(actor, event) {
    if (this.is_drag) {

これでなんとかシークバーに利用する用の値は得ることができる。

さて実際の実装方法だ。
ClutterActor は特定領域の塗り潰しができないっぽい、Canvas は使いたくない。
ならば別色の ClutterActor をもう一つ用意して被せれば、俺って天才だ。
せっかくだからデジタル表示も被せちゃえ、ClutterText は背景透過だし。

event.x を得て親の横幅で割った比率を被せた ClutterActor の横幅に適用。
という我ながら大雑把な手段なのに理想どおりに動いた。
しかもその値を ClutterGst にも適用できるというオマケ付きで。

それにしても GUI アプリを作るのって本当に楽しいですね。
思ったとおりに動いてくれた時の嬉しさが CUI の比じゃない。

set_accels_for_action

前回書いたように Y901x の ClutterGst 化をしているのですが。
マジで何を今更なことを見つけた。

GtkApplication: GTK+ 3 Reference Manual#gtk-application-add-accelerator

add_accelerator は 3.14 以降は使うなってさ。
おぃおぃ Gedit 標準プラグインが今でも普通に使っているんですけど。

とりあえず Gjs だとこんな感じでいいみたいみたい。
Gio.SimpleAction のほうは今までどおりでいい。
次回の更新で自作の奴も書き換えるか。

const Y901Application = new Lang.Class({
    Name: 'Y901Application',
    Extends: Gtk.Application,

    _init: function() {
        this.parent({});
        //this.add_accelerator("<Control>Q", "win.ctrl_q_action", null);
        this.set_accels_for_action("win.ctrl_q_action", ["<Control>Q"]);
    },
    vfunc_open: function(files, n_file, hint) {
        let uri = files[0].get_uri();
        let w = new Y901Window(this);
        w.set_uri(uri);
    },
    vfunc_activate: function() {
        new Y901Window(this);
    }
});

ついでに Gjs で上記のように open をオーバーライドするには。

let argv = ["y901main.js"];
ARGV.forEach(function(s) {
    argv.push(s);
});
GtkClutter.init(null);
ClutterGst.init(null);
let application = new Y901Application();
application.run(argv);

みたいに配列の先頭に何か突っ込まないと動作しない。
何故こんな仕様にしてしまったんだろう?

他色々と苦しみながらこんな所までやってみた。
DnD で再生、スペースキーでポーズ、シークバーも一応使える。

zep

我ながら以前の面影が何も無いけどどうしよう、名前は変えようか?
シークバーはコラム無しだしポーズボタンも無いし拡大はマウスのみだし。
シークバーも Clutter で作っているので、まだ手探り中なので。

y901x-1.1.x0.tar.gz

ここまでのソースを置いておきます、単なるバックアップですけど。
もし筆者が死んだら誰か続きをやってね。