Y901x」タグアーカイブ

GStreamer Change Volume

GStreamer Player のボリュームを直線的に変更したい。
GtkHScale の値を直で渡すと曲線になって使いにくい、DirectShow はもっと酷かったけど。

dBデシベルの話し 音の大きさ

音量のデシベルは非常にややこしい。
GStreamer マニュアルの GstStreamVolumeFormat 以下にも 20 * log10 (val) とある。

gststreamvolume

実は私の Cinema という Windows 用 DirectShow プレイヤーは手抜きをしていて…
DirectShow ボリュームは最大 0、無音 -10,000 をデシベル単位でということなのですが…

void CDirectA::SetVolume(int nVolume)
{
	double d = pow((double)(nVolume * 2), 2.0);
	m_nVolume =  static_cast(-d);
	if (pBasicAudio)
		pBasicAudio->put_Volume(m_nVolume);
}

というベキ乗を利用してなんとなく似たようなカーブにしている。
ぶっちゃけ WMP とは全然カーブが違うんだが面倒だということで(ぉい!

GStreamer ボリュームは最大 100.0、無音 0.0 なのでコレを Y901x で真似るには

def on_volume_value_changed(self, widget, event=None):
    val = widget.get_value()**2 / 10000.0
    self.player.set_property("volume", val)

とやってみたけど完全に違う、GStreamer では同じ手は使えないようだ。
キチンと調べないといけないみたい、Totem はどうやっているかコードを漁る。

bacon-video-widget-gst-0.10.c の bacon_video_widget_set_volume 関数で
GST_STREAM_VOLUME_FORMAT_CUBIC を指定している、この型と相互変換が必要か。

gststreamvolume を pygst から利用するには c ヘッダが gst/interfaces 以下にあるので

import gst
dir(gst.interfaces)

みたいにやれば関数が一覧されるので探ってみる。
stream_volume_set_volume は流石に使えないようだけど convert はできるようだ。
CLAMP 関数も必要かな C言語でCLAMP(a,b,c) | OKWave
一行なのでラムダ式を使えと言われそうだが CLAMP という関数名も覚書に使いたい。

def set_volume(self, value):
    v = gst.interfaces.stream_volume_convert_volume(
            gst.interfaces.STREAM_VOLUME_FORMAT_CUBIC,
            gst.interfaces.STREAM_VOLUME_FORMAT_LINEAR,
            value)
    def clamp(a,b,c): return min(max(a,b),c)
    v = clamp(v, 0.0, 1.0)
    self.player.set_property("volume", v)

def on_volume_value_changed(self, widget, event=None):
    self.set_volume(widget.get_value() / 100.0)

おっし、これで Totem とまったく同じ音量カーブになった。
CUBIC を LINEAR に変換すればいいのね、実はいまいちよく解っていなかったり(ぉい!

もう少し Debug して Y901x の Playbin2 化 version 0.3 はなんとかなりそう。

playbin2

Totem は

gst_stream_volume_set_volume

でボリューム調節を行っているということだけ理解した。
その関数ってもしかして playbin の volume Property とリンクしているんじゃないの?

米国の Google でコードを探し試しまくったけどさっぱり解らない…
3. Pipeline
自前でパイプラインを構築するか、しかし情報少なすぎ!

いや、そういえば playbin は古いので新しい PulseAudio に対応しているはずがない。
だから playbin2 に移行しろということなのか…

playbin2

playbin

Y901x の GstPipeline を playbin2 に書き換えて試す。

self.player = gst.element_factory_make("playbin2", "player")

エラーが出る場所をとにかくコメントアウトしてボリュームを試す。

あーあ、マジでそんだけだった、しかも mute Property にて簡単にミュート。
しかし DirectShow 同様に非線形カーブなので GtkHScale 値をそのまま使えないみたい。
ミュートも「サウンドの設定」の「アプリケーション」タブと見事連動。

これではもう playbin2 に移行するしか選択肢が無いようだ。

# Get GstStreamInfo List
info_arr = self.player.get_property("stream-info-value-array")

の所で例外になる、そりゃ playbin2 にそんなプロパティは無い。
本当はもの凄い遠回りをしたけど一応こう書き換えたら上手く動いた。

def gst_on_message(self, bus, message):
    t = message.type
    if t == gst.MESSAGE_EOS:
        self.clear()
        # Repeat
        if self.settingwin["repeat"] > 1:
            self.on_next(self)
        elif self.settingwin["repeat"] == 1:
            self.set_play_position(0)
            self.player.set_state(gst.STATE_PLAYING)
    elif t == gst.MESSAGE_ERROR:
        self.clear()
        err, debug = message.parse_error()
        self.messagebox(str(err) + "\n" + str(debug))  
        self.toolbox.change_img(0)
    elif t == gst.MESSAGE_STATE_CHANGED:
        if gst.STATE_PLAYING == message.parse_state_changed()[1]:
            self.toolbox.change_img(0)
            # This is First Play
            if not self.is_playing:
                try:
                    self.p_duration = self.player.query_duration(gst.FORMAT_TIME)[0]
                except:
                    self.p_duration = gst.CLOCK_TIME_NONE
                if self.p_duration != gst.CLOCK_TIME_NONE:
                    self.toolbox.seekadj.upper = self.p_duration
                    self.is_playing = True
                    # toolbox Buttons Enabled
                    self.toolbox.set_enable_ctrl(True)
                    # Timer on
                    self.timer_id = gobject.timeout_add(200, self.on_timer)
                    # File Names Listup
                    self.listup_change()
                    #
                    # playbin2
                    #
                    vsink = self.player.get_property("video-sink")
                    # vsink == None @ Music File
                    if vsink:
                        for pad in vsink.pads():
                            # print pad.get_caps()[0].get_name() # is Check
                            # W Buffer Off
                            self.video_window.unset_flags(gtk.DOUBLE_BUFFERED)
                            # caps[0] @ GstStructure
                            caps = pad.get_negotiated_caps()
                            self.video["width"] = caps[0]["width"]
                            self.video["height"] = caps[0]["height"]
                            #
                            flt = float(self.video["width"]) / float(self.video["height"])
                            self.aframe.set(xalign=0.5, yalign=0.5, ratio=flt, obey_child=False)
                            self.change_video_size()
                    """ under Old PlayBin src
                    # Get GstStreamInfo List
                    info_arr = self.player.get_property("stream-info-value-array")
                    if len(info_arr) == 1:
                        # Music
                        pass
                    else:
                        # W Buffer Off
                        self.video_window.unset_flags(gtk.DOUBLE_BUFFERED)
                    for info in info_arr:
                        # Get Video Size
                        pad = info.get_property("object")
                        #print pad
                        caps = pad.get_negotiated_caps()
                        if caps != None and "video" in caps.to_string():
                            # caps[0] @ GstStructure
                            self.video["width"] = caps[0]["width"]
                            self.video["height"] = caps[0]["height"]
                            #
                            flt = float(self.video["width"]) / float(self.video["height"])
                            self.aframe.set(xalign=0.5, yalign=0.5, ratio=flt, obey_child=False)
                            self.change_video_size()"""
        else:
            self.toolbox.change_img(1)

GstStreamInfo のリストからの情報で設定するより短くなってもーたw
video-sink なんてテキトーだったのにビンゴだったのは自分で驚いたマジで。

これで playbin2 化はなんとかなりそうだ、後はボリューム値の線形化だ。
って以前のように動かせるだけで見た目は何も進化していないのが少し悲しい…

Mandriva to Ubuntu p3

Ubuntu 10.04 の「外観の設定」を開くと何か足りない。
9.10 まではあったツールバーやメニューのアイコン設定が無くなっている。

まあこれは gconf で弄くれるので覚書。
gconf-editor にて

/desktop/gnome/interface/toolbar_style
を icons にするとツールバーテキストを消せる。

/desktop/gnome/interface/menus_have_icons
を On にするとメニューにアイコンを全部付けられる。

ついでに Nautilus で場所バーが GtkEntry に変更できなくなったと書いたができた。

Ubuntu日本語フォーラム / [Lucid] Nautilus 2.30.0でディレクトリの絶対パスを表示&コピーしたい

場所バーで直接入力切り替えボタンが無くなったのは私も一瞬困ったと思ったけど…
端末で cd 移動するのが面倒な時と Blog に長いフルパスを書く時以外は必要無いわ。

cd 移動が面倒な時はボタンを端末にドロップすればいい。
ファイルも一旦端末にドロップした後でコピーすればフルパスも簡単にコピペ完了。
今までと少し手段を変えればいいだけ、それならやっぱり切り替えボタンはいらないや。

つーかコマンドが解っているなら

~/.gnome2/nautilus-scripts

に登録すればいいのにと思うんだが、どうしてもボタンでやりたいのだろうか…

それより今頃気がついたのだが。
Y901x でボリューム調節ができなくなってしまった!
Totem はできる、Y901x とは違う方法でボリューム調節しているようだ。

Totem と同時起動してサウンドの設定を開き本体ボリュームバーを動かしてみる。
Y901x は何も起こらないが Totem は見事に連動、Totem は PulseAudio を直で変更?
環境も糞も Mandriva で使っていた頃とまったくマシンは同じだ。

VirtualBox 仮想マシン上の 9.10 でも結果は同じだが Y901x でボリューム調節できる。
何かの仕様が変わったのだろうけど PulseAudio 自体を弄くるようにしないとダメか。
Windows の Vista 以降同様にアプリ毎に指定できるんだからそのほうが自然か。

あーあ、いつまでたってもやらなきゃいけないことが無くならない。
というかミュートボタンを付けたいんだが上手い方法が思いつかない…

しかし OS 乗り換えを思い切って行うと色々なことを発見するもんだ。

gtk.Widget.allocation.height

4/18 に gtk.Widget.allocation.height が表示後でないと取得できないと書いた。
で、フルスクリーンコントローラを一旦表示するまで 50px という仮に値を入れてごまかした。

いや、よく考えたらフルスクリーンコントローラ自体のサイズを得る必要は無かった。
フルスクリーンにする前に既に toolbox, statusbar は表示されているのだから。

ということでやってみたら縦サイズが 1 に、あれ?
そうか、parent 変更すると property が消えて非表示扱いになるんだった。

ということはオブジェクト作成側で調べてパラメータで渡す…
のコードを一度書いてみたけれど何故か気に入らない、なるべくパーツ側で処理したい。
もっと上手い方法は…と考えてみたらこうすりゃいいじゃんと思いついた。

class CFullCtrl(gtk.Window):
    """
        Full Screen Controrer
    """
    def __init__(self, parent, box, parent2, bar):
        """
            GtkWindow at GTK_WINDOW_POPUP
        """
        gtk.Window.__init__(self, gtk.WINDOW_POPUP)
        # memory pointer
        self.p1 = parent
        self.p2 = parent2
        self.box = box
        self.bar = bar
        # Get Desktop Window Geometry
        root = gtk.gdk.get_default_root_window()
        x, y, w, h, d = root.get_geometry()
        # move and resize
        self.set_size_request(w, -1)
        self.y_pos = h - box.allocation.height - bar.allocation.height
        self.move(0, self.y_pos)
        # Change parent
        vbox = gtk.VBox()
        self.box.reparent(vbox)
        self.bar.reparent(vbox)
        vbox.show_all()
        self.add(vbox)
        self.connect("destroy", self.on_destroy)

    def on_destroy(self, widget, event=None):
        """
            Return Parent
        """
        self.box.reparent(self.p1)
        self.p1.set_child_packing(self.box, False, True, 0, gtk.PACK_END)
        self.bar.reparent(self.p2)
        self.p2.set_child_packing(self.bar, False, False, 0, gtk.PACK_START)
        return True

そうだよ、parent 変更する前に値を調べておけばいいじゃないの。
後はフルスクリーン切り替え時に作成や削除をやればつじつまが合うじゃないか。

def change_fullscreen(self):
    if self.fullscreen:
        self.fullscreen = False
        self.w.window.unfullscreen()
        self.w.unfullscreen()
        self.fullobj.destroy()
        self.fullobj = None
        self.w.resize(self.fullsize[0], self.fullsize[1])
    else:
        self.fullscreen = True
        cx, cy = self.w.get_size()
        self.fullsize[0] = cx
        self.fullsize[1] = cy
        self.fullobj = CFullCtrl(
                self.hbox_ctrl,
                self.toolbox,
                self.vbox_main,
                self.statusbar
            )
        self.w.fullscreen()
        self.w.window.fullscreen()

こんな感じの関数を作ればいいしパラメータも増やさずにすんだ。
しかしよく考えたらパーツ作成のパラメータは本体ポインタのみでいいような気がしてきた。
アトリビュートやメンバ関数はそのポインタから得ることができるのだから。

class CStatusBar(gtk.VBox):
    def __init__(self, owner, arg):
        self._win = owner.w
        self._owner = owner
        gtk.VBox.__init__(self)

みたいな感じで、後は self._owner 経由で本体 class のメンバを参照できる。
というか本体のコードをなるべく少なくしたいのですよ、後々のメンテナンスを考えると。
ラムダ式とかは数学系の人は好んで使うようだけどメンテが多いアプリでは怖くて使えませんわ。
ちなみに Windows 用の Y901 最終版は本体コードだけで九千行、この手では短いほうだ。

他、再生停止でマウスカーソルを表示させるのを忘れていた。
ということで Y901x-0.2.5 公開、作った本人以外に使っている人がいるかどうか知らない。

gtk.gdk.Display.get_pointer

Y901x-0.2.3 は大失敗だった。

motion-notify-event で処理しているから VideoArea 領域を外れたら認識しない。
つまり 16:9 の動画を 16:10 ディスプレイで表示した場合とかでは下に黒帯が出る。
その黒帯部分では領域外なので motion を認識なんてするはずがない。
つまりフルスクリーンでのコントローラは出ないわけだ。

んー Cinema では WM_MOUSEMOVE で計算してやっていたんだけどなぁ。
GTK+ ではレイアウタなので超ややこしいし、WindowsAPI の GetCapture みたく…
ってソレどうやるの?面倒なので Timer で処理するようにしよう。

# GetCursolPos
a, x, y, b = gtk.gdk.display_get_default().get_pointer()

で x と y にスクリーン座標で現在のマウスカーソル位置が取得できるようだ。
a は gtk.gdk.Screen、b は Shift や Ctrl の Mask、ここでは使わないのでダミー。
この値を元に処理したらとりあえずディスプレイより横長動画でも処理できた。

せっかくタイマー処理でマウスカーソル位置を取得しているんだからカーソル消去も。
只の Y901 からの伝統で通常時でも画面上は静止状態マウスカーソルを一秒後に消す。
っっって、GTK+ で WindowsAPI の ShowCursor(FALSE); ってどうするの?

gtk.gdk.Cursor

の下のほうに普通に書いていた、つまり自分で作らないといけないんだね。
これが解れば処理方法自体は同じなので Python コードにて書き換えるだけだ。
ということで細かい処理は Y901x のコードを見てもらうとして簡単に。

class Y901window(dbus.service.Object):
    def __init__(self, bus_name):
        # ...
        self.mousepoint = [0, 0]
        self.invisiblecount = 0;
        # Create invisible cursor
        pixmap = gtk.gdk.Pixmap(None, 1, 1, 1)
        color = gtk.gdk.Color()
        self.noncursor = gtk.gdk.Cursor(pixmap, pixmap, color, color, 0, 0)

省略しすぎかな、とにかくタイマーで

    def on_timer(self):
        # ...
        # GetCursolPos
        a, x, y, b = gtk.gdk.display_get_default().get_pointer()
        # FullScreen at Controler
        if self.fullobj:
            if self.fullobj.y_pos < y:
                self.fullobj.show_all()
            else:
                self.fullobj.hide()
        # Cursor Auto Invisible
        if self.mousepoint[0] == x and self.mousepoint[1] == y:
            if self.invisiblecount < 5:
                self.invisiblecount += 1
            else:
                self.video_window.window.set_cursor(self.noncursor)
        else:
            self.invisiblecount = 0;
            self.mousepoint[0] = x
            self.mousepoint[1] = y
            self.video_window.window.set_cursor(None)
        # return True is Repeat
        return self.is_playing

と時間表示とフルスクリーンコントローラとカーソル消去を全部やってみた。
とりあえずこれでつじつまが合っている、かなり Windows 用 Y901(Cinema) を再現できた。
ということで Y901x-0.2.4 公開しました。
やっぱり全然人増えねぇ、Windows 用のようにいかないわな。