zephirの変数のついて

どうも、こんにちは。
最近zephirを触ってるんですけど、zephirの仕様について学んだことを書きます。

zephirで変数を使用するには宣言が必要です。コンパイラっぽいですねぇ

宣言

一行にまとめて

var a, b, c;

複数行で

var a;
var b;
var c;


宣言時に初期値の代入も可能です。

var a = "hello", b = 0, c = 1.0;
int d = 50, bool some = ture;

値のスコープ


変数の宣言はメソッド内で行わないといけないみたいです。

public function someMethod1()
{
    int a = 1, b = 2;
    return a + b;
}

ただし、メソッド外で変数宣言を行う場合は、アクセス修飾子のみつけて変数を宣言すれば大丈夫

class Router
{
  private s;
  protected t;
  public u;


その際に関数内で変数を使用するにはthisをつけて使用します。
また変数に値を代入するときはletを付ける必要みたいです。

public function __construct(int a)
{
  let this->t = a;
}


さらにzephirではPHPのスーパーグローバル変数を使用することができる

public function Get(string g)
{
    return _GET[g];
}

public function Server(string g)
{
    return _SERVER[g];
}


型について

ダイナミックタイプ

varはPHPの変数と同じふるまいをしてくれて、値を型を気にせずに自由に代入することが可能です。

public function someMethod2()
{
    var a = "Hello";
    var res;
    let res = a;
    let res = res . " World";
    let res = 10;
    return res;
}

ダイナミックタイプで使用できる型 float double string array object resource null

boolean
integer
float
double
string
array
object
resource
null

スタティックタイプ

スタティックタイプは変数宣言時に型を宣言し、その型にそった値しか代入することができません。 スタティックタイプで宣言できる型

boolean
integer
unsigned integer
char
unsigned char
long
unsigned long
float
double
string
array

簡単ではありますが、zephirの変数の利用と仕様についてでした。

とりあえずdockerにzephirのビルド環境を準備してみた

phpフレームワークであるphalconが好きで、phalconがzephirで実装されているとのことです。
zephirはphpのextensionの言語らいしく、phpっぽく書けるんですけど、実行速度が早いらしいです。
この「extension」っという響きがかっこいい….

っということでzephirのビルド環境を準備したいと思います。

環境情報

ホストOS : macOS Sierra
docker version: 1.11(tool box)
コンテナOS : debian 8
zephir version : 0.9.11
PHP version : 7.0.20

dokcer

まずはコンテナ用意

docker run -it -d -p 8181:80 -v /Users/name/zephir:/home/name/workspace --privileged --name zephir debian:8 /bin/bash


まぁざっとこんな感じかな

ビルド環境

次、PHPのバージョン7を入れます。
PHPのバージョンは5でも大丈夫?なんですけど、今後のことを考えるとバージョン7のほうがいいのでバージョン7をインストールします。

PHP 7のインストー

cd
apt-get install wget
echo "deb http://packages.dotdeb.org jessie all" >> /etc/apt/sources.list
wget https://www.dotdeb.org/dotdeb.gpg
apt-key add dotdeb.gpg
apt-get update


次にzephirをインストー

apt-get install git gcc make re2c php php-json php-dev php-xml libpcre3-dev sudo 
git clone https://github.com/phalcon/zephir
cd zephir
./install -c


動作確認

次にzephirのプロジェクトの雛形を作成

zephir init utils

これで下記のファイル、ディレクトリが作成される

ext/
コンパイルに使用されたファイル、ディレクトリが入る

utils/
initで指定された名前のディレクトリ。zephirのコードをここに書いていく

config.json
設定ファイル。zephirの振る舞いを定義



では早速zephirのコードを作成

greeting.zep

namespace Utils;

class Greeting { public static function say() { echo "Hello world!"; } }

次、PHPのコード

sample.php
<?php
echo Utils\Greeting::say(), "¥n";

では早速ビルド

cd utils
zephir build

注意
php-xmlをインストールしないとこんなエラーが出る
PHP Fatal error: Uncaught Error: Call to undefined function Zephir\utf8_decode() in /root/zephir/Library/Compiler.php:2023
php-xmlをインストールしてなくって、、エラーが出てしまったんでとりあえず記述。


/usr/lib/php/20151012/
ここにsoファイルができる

注意
phpize -v
でsoファイルの出力先を確認



php.iniにextensionを追加

extension=utils.so

php -mで確認
[PHP Modules]
・・・
sysvsem
sysvshm
tokenizer
utils
wddx
xml
・・・

OK、utilsがある。
実際に実行
# php sample.php
Hello world!

おし、動いた。<- Hello Worldで満足


次、ちょっと変更して、文字列を一文字ずつ表示するプログラムの実装

filter.zep

namespace Utils;

class Filter { public function alpha(string str) { char ch; for ch in str { echo ch, "\n"; } } }


sample.php

<?php
$f = new Utils\Filter();
$f->alpha("hello");

ではビルド
zephir build

そして、実行
# php sample.php
h
e
l
l
o

ビルド環境の準備と動作確認完了。


補足
今回遭遇したエラー内容、、、
zephir\CompilerException: Unexpected class name Utils\Filter in file: utils/greeting.zep, expected: utils/filter.zep
原因
greeting.zep内にFilterクラスを書いてしまったから、、、
ファイル名とクラス名は決まっているみたい。

docker コンテナでphpenvの環境を構築

環境

  • debian 8(コンテナ)
  • phpenv 1.1.1-2

まず初めに

dockerが好きでなんでもdockerでしてみたい、そして環境もdockerで用意したい、と思いコンテナでphpの環境を作りました。
自分の思考的に、、、どうしてもdebianベースでコンテナを用意してしまいます。
実際に行った内容をメモ的な感じで書いていきます。
Dockerfileはめんどくさいんで作りません。もしコンテナとしてサーバに展開してバージョン管理するなら、作りますが、、、

準備

コンテナ起動

docker run -it -d ~~~~~ --name php debian:8 /bin/bash

必要なパッケージのインストー

docker exec -it php /bin/bash
apt-get install git gcc g++ make file autoconf automake libxml2-dev bzip2 libbz2-dev libssl-dev re2c libcurl4-openssl-dev pkg-config libjpeg-dev libpng-dev libmcrypt-dev libreadline6 libreadline6-dev libtidy-dev libxslt-dev

phpenvのインストー

git clone https://github.com/CHH/phpenv.git
phpenv/bin/phpenv-install.sh

※phpenv-install.shで~/.phpenvが作られるため、すでに作成されていないか確認。すでにある場合は削除。

phpenv-の環境パスの設定

~/.bashrcに追記

PATH=$HOME/.phpenv/bin:$PATH
eval "$(phpenv init -)"

反映

source ~/.bashrc

php-buildのインストー

git clone https://github.com/CHH/php-build.git ~/.phpenv/plugins/php-build

これでinstallコマンドが使えるようになった。

PHPのセッティング

PHPのインストー

今回は7.0.8をインストー

phpenv install 7.0.8

下記、ログで成功

[Info]: Enabling Opcache...
[Info]: Done
[Info]: The Log File is not empty, but the Build did not fail. Maybe just warnings got logged. You can review the log in /tmp/php-build.7.0.8.20170627162000.log
[Success]: Built 7.0.8 successfully.

失敗したらその都度、トライアンドエラーで対処。

インストールしたPHPを指定

phpenv global 7.0.8
phpenv rehash

PHPのバージョン確認

php -v
PHP 5.6.30-0+deb8u1 (cli) (built: Feb  8 2017 08:50:21)
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies
    with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2016, by Zend Technologies

これで自分のPCでPHPの環境の準備完了。

mediasoup H264対応

mediasoupでH264コーデックで映像を配信しようとしたときの対処になります。
どうやらVP8とH264では挙動が違うようで、、困りますね。

ブラウザはchromeでバージョンは58.0を使用していまいた。
どうやら映像の送信でサーバまでは届いているようなのですが、受信時にエラーがあるみたいです。
しかもそのエラーがconsoleやサーバ側のログでわからないのが、しんどいですね。

原因はH264時のSDPのパラメータの設定が問題だった問題だったようです。

問題箇所のSDP

サーバ側

・・・
a=rtpmap:126 H264/90000
a=fmtp:126 packetization-mode=0
・・・

クライアント側

・・・
a=rtpmap:126 H264/90000
a=fmtp:126 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
・・・

ここの packetization-mode っていうのが違うようでダメだったみたいです。 クライアント側のpacketization-modeを0に設定を変更することで無事に繋がることができました

sdp = description.replace(/packetization-mode=1/g, 'packetization-mode=0');

こんな感じで、後はsetLocalDescriptionにsdpをセットすればOK、

ちなみにpacketization-modeっていうのはRTPペイロードタイプのプロパティか、レシーバ実装の機能を示しているようです。

下記、設定一覧です

内容
0 シングルNALモード(デフォルト)
1 非インタリーブモード
2 インタリーブモード

情報 : https://tools.ietf.org/html/rfc6184


ちなみにNALと言うのはH264の生のストリームデータを区切る処理階層のことを言うみたいです。(詳しくわかりません。すみません)


H264とVP8でいろいろと実装が違うみたいで、めんどくさいですね。
今後はどっちかに絞って実装するようにしましょうかね

mediasoupでのsendonlyとreconlyをSDPだけで試した

webRTCでは接続してきたユーザはメディアの送信、受信を両方共できるのですが、
今回はメディアの送信のみと配信のみで接続させてみたいと思います。

今回はクライアントの設定のみで行いたいと思います。

修正箇所を見てみる

webRTCではSDP(session description protocol)っという接続情報を記述しているものがあります。
これをもとにP2Pでの接続時にお互いの情報(コーデックなど…)を交換しあい、接続をすることができるということです。
このSDPの中を変更し、送信のみと受信のみと切り分けることができます。
下記が設定できる、送受信モードです。

属性 内容
sendonly 送信のみのモード
recvonly 受信のみのモード
sendrecv 送受信モード
inactive 非アクティブ

デフォルトではSDPの中に

a=sendrecv

っと記述しているのでここを修正

// 送信のみ
function modifiSendOnlyMode(description) {
    sdp = description.replace(/a=sendrecv\r\n/g, 'a=sendonly\r\n');
    return sdp;
}
// 配信のみ
function modifiReceiveOnlyMode(description) {
    sdp = description.replace(/a=sendrecv\r\n/g, 'a=recvonly\r\n');
    return sdp;
}

修正したSDPをLocalDescriptionにセットし、相手に通知すればOK

無事に送信のみのクライアントと受信のみにクライアントの切り替え成功です。

mediasoupのインストールと基本的な実装

mediasoupとは

  • nodejsのSFUサーバのライブラリ。ライブラリなので基本的にメディア(映像、音声)の受信、配信のみを行います。
  • クライアントはwebRTCを想定しています。

SFUとは

SFU(Selective Forwarding Unit)とは配信者に代わり映像、音声を視聴者に配信する仕組み。配信の代行的なことを行うサーバですかね。

とりあえず環境情報ともろもろ

  • OS
    debian 8.8

  • node
    version 6.10

  • npm
    3.10.0



調査期間 : 2017/5/20 〜 2017/6/2 調査期間中にいろいろ仕様とか変わっているかもしれませんので、、ご了承下さい。

mediasoupのインストー

npmにてインストールすることができるが、mediasoupの実装がC++なのでコンパイルできる環境を用意

apt-get install gcc g++ make build-essential

pythonも必要だか、バージョンが2.x系じゃないといけないみたいです。なので2.x系のやつが使える環境にしておきます。

package.json

...
"dependencies": {
    ....
    "mediasoup": "1.2.0"
}
....

npm でインストー

npm install

mediasoupの実装

大体の流れとして下記のリスト。

  1. roomの作成
  2. roomからPeerの取得
  3. PeerからPeerConnectionの作成

—- シグナリング処理 —-
4. SDPのOfferを作成し、localDescriptionにセット、クライアントにSDPを送信
5. クライアントからAnswerが返ってきてremoteDescriptionにセット

0. mediasoupの準備

const mediasoup = require("mediasoup");

クライアントと接続するときに使用するクラスの読み込み

const RTCPeerConnection = mediasoup.webrtc.RTCPeerConnection;
const RTCSessionDescription = mediasoup.webrtc.RTCSessionDescription;

mediasoupのサーバ設定

const mediaServer = mediasoup.Server({
    ...
});

※ 設定内容参照 : https://mediasoup.org/api/#Server-dictionaries

1. roomの作成

クライアントが接続するroomを作成。そのroomにいるユーザが配信した場合は同一のroomに接続している全ユーザに配信されます。 room作成

const roomOptions = {
    mediaCodecs: [
        {
            kind: 'audio',
            name: 'audio/opus',
            clockRate: 48000,
            payloadType: 111
        },
        {
            kind: 'video',
            name: 'video/vp8',
            clockRate: 90000,
            payloadType: 100
        }
    ]
};
mediaServer.createRoom(roomOptions)
.then(function(room) {
    // 引数のroomが作成したroomになる
});

※ roomOptionsは作成するroomのオプション、コーデックとかを設定
参照:
https://mediasoup.org/api/#RtpDictionaries-RtpCodecParameters https://mediasoup.org/api/#server-createRoom

2. roomからPeerの作成

クライアントとメディアストリームの配信、受信を行います。
Peerの作成

peer = room.Peer("peerName");

peerName: room内のPeerの名前になります。ひとつのroom内に同じ名前のPeerを作成することはできないため、注意が必要(別のroomならOK)。

3. PeerからPeerConnectionの作成

Peerからコネクションを作成します。webRTCのPeerConnectionみたいな感じです。
だけどちょっと違うようです。
コネクションの作成

peerConnection = new RTCPeerConnection({
  peer: peer
});

引数の設定はここを参照: https://mediasoup.org/api/#webrtc-RTCPeerConnection-dictionaries
usePlanBはSDPの規格になるため、これでシグナリングが成功しないときもあるかも、
chromefirefoxで違いがあるため要注意(2017/5/30 現在)


—- シグナリング処理 —-

シグナリングの方法はwebRTCでは決まっていない。そのため、どんな実装でもいいのだが、今回はwebSocketにて行います。
実装も簡単、一番楽ですし、情報も多い。

4. SDPのOfferを作成し、localDescriptionにセット、クライアントにSDPを送信

webRTCのシグナリングで使用するSDPの作成を行う。mediasoupからofferを送らないとシグナリングができないようです。
なので、クライアントがofferを送ってきて、setRemoteDescriptionでセットするとエラーになってしまいました…

offerの作成

peerConnection.createOffer({
        offerToReceiveAudio: 1,
        offerToReceiveVideo: 1
})
.then(function(desc) {
    // 作成したSDPをローカルの情報として保存
    peerConnection.setCapabilities(desc);
    return peerConnection.setLocalDescription(desc);
})
.then(function() {
    // webSocket経由でクライアントにSDPの情報を送信
    const message = JSON.stringify(peerConnection.localDescription.serialize());
    return ws.send(message);
})

5. クライアントからAnswerが返ってきてremoteDescriptionにセット

offerをクライアントに送信し、無事にanswerが返ってきたらその情報をリモートの情報としてConnectionにセットする

remote = new RTCSessionDescription(description);
peerConnection.setRemoteDescription(remote);

これで無事にmediasoupとクライアントでコネクションが確立できて、通信ができるようになります(なるはず)。