2023年5月1日を持ちまして、株式会社KDDIウェブコミュニケーションズのTwilioリセール事業が終了したため、本記事に記載されている内容は正確ではないことを予めご了承ください。
諸注意
本日の資料
以下にアップされています
http://bit.ly/2uDhMzc (に・ユー・デー・エイチ・エム・ゼット・シー)
本日のスケジュール
時間 | 内容 |
---|---|
12:45 | ヒカリエ11階集合 |
13:00~13:10 | オープニング |
13:10~13:40 | Twilioの概要&ハンズオン準備 |
13:40~17:20 | Twilioハンズオン(Webアプリケーション開発) |
17:20~17:30 | クロージング/アンケート |
17:30~18:30 | 懇親会 |
対象者
- Twilioが初めての方
- 名前は知っているけど、触ったことがない方
- WebRTCに興味のある方
カリキュラム(座学)
- STEP0 ハンズオンの準備とTwilio Videoの概要
- STEP1 ブラウザでカメラ、マイクを利用してみよう
- STEP2 ピアツーピアルームのビデオチャットを実装してみよう
- STEP3 グループルーム機能を利用して複数人によるビデオチャットを実装してみよう
自己紹介
髙橋克己(たかはしかつみ)
(株)KDDIウェブコミュニケーションズ
Twilio事業部 エバンジェリスト
Facebook: katsumi.takahashi
GitHub: mobilebiz
katsumi.takahashi@kddi-web.com
ハンズオン用ソースコード
https://github.com/mobilebiz/webrtc-handson
masterブランチをローカルにダウンロードして下さい
STEP1〜STEP2までの各フォルダに各STEPの完成形のソースコードが格納されています
handsonフォルダのコードをベースに開発を進めて下さい
githubを使ったことがない方はZIPでダウンロードして展開して下さい
STEP0 ハンズオンの準備とTwilio Videoの概要
本テキストでは、予め用意したアカウントを利用していますが、このハンズオンの内容はトライアルアカウントでも利用できます。
トライアルアカウントの作成は、以下のURLの右上にあるサインアップより行なえます。
https://twilio.kddi-web.com/
また、アカウント作成手順を動画でアップしているので、そちらも併せてご確認ください。
手順1. Twilioの管理コンソールにログインします。
- ブラウザでログイン画面を表示し、配布されたIDとパスワードでログインをします。
- ログインID: twiliohandson@kddi-web.com
- パスワード: 講師の指示に従ってください
今回のハンズオンでは、ログインIDはすべて共通となります。パスワードを間違えると他の方がロックアウトされてしまうので、間違えないようにしてください。
また、二要素認証を有効にしないように注意してください。
-
サブアカウントの一覧が表示されたら、ご自分のユーザ名(userXX)の右側にあるサブアカウントを表示をクリックします。
-
右上にご自分のユーザ名が表示されることを確認します。
-
ブラウザを閉じてしまった場合やログアウトしてしまった場合は、再度サブアカウントにログインしてください。
Programmable Video
Programmable Video
- WebRTCをベースに構築された音声・動画・データ通信サービス
- JavaScript SDK / iOS SDK / Android SDKを提供
- アクセストークンを使った認証
- ルームベースでの実装(呼び出し機能はありません)
- 「ピアツーピア(P2P)ルーム」と、SFUベースの「グループルーム」「スモールグループルーム」の3種類が利用可能
- ピアツーピアルームは最大10名まで、スモールグループルームは最大4名まで、グループルームは最大50名までの参加者を収容可能
- TURN/STUNサービスも提供
- 画面共有に対応(ChromeとFirefoxのみ)
- 録画機能に対応(グループルームのみ)
- メディアコーデックには、Opus(音声)、VP8 / H.264(映像)に対応
<参考> P2P方式とSFU方式
- P2P(Peer to Peer)方式
- ブラウザ同士が直接映像などをやり取りする
- もっとも高解像度で自由度が高い
- WebRTCが前提としている接続方式でリアルタイム通信に向いている
- 接続する相手が増えてくるとブラウザ側の負担が大きくなる(特にモバイル)
- Twilio Videoの「ピアツーピアルーム」で利用される方式
- SFU(Selective Forwarding Unit)方式
- 映像や音声を中継するサーバーを配置
- 録画などの付加機能を実施することが可能
- 上りのコネクションは1本、下りは相手の数分必要
- ブラウザが増えた場合に、ブラウザ側の負担がP2Pに比べて少ない
- SFU側では画像の加工などは行わないため、SFU自体の負担は低い
- Twilio Videoの「スモールグループルーム」「グループルーム」で利用される方式
<参考> TURN/STUN
- NAT越えをするための仕組み
- ICE(Interactive Connectivity Establishment)として説明されることもある
- TURN
- 直接通信できない場合に中継するサーバー
- STUN
- 自分が外部に出たときに、外部から見える情報を取得する
- Twilioは、Network TraversalとしてTURN/STUNを提供
- STUNの利用料は無料、TURNは従量制(詳しくは、こちら)
- TwilioのTURNサーバーは9つのリージョンから最も近い場所を利用可能
- Twilio Videoは使わずに、TURN/STUNのみの利用も可能
Twilio Video SDKが対応するブラウザ
CHROME | MS EDGE | FIREFOX | SAFARI | |
---|---|---|---|---|
Android | ✓ | - | ✓ | - |
iOS | * | - | * | ✓ |
Linux | ✓ | - | ✓ | - |
macOS | ✓ | - | ✓ | ✓ |
Windows | ✓ | ✘ | ✓ | - |
- EDGEは現時点で非対応
- iOSのChromeとFirefoxでは、WebRTC APIにはアクセスできません。
Video APIの認証
- Twilio VideoのAPIにアクセスするためにはアクセストークンが必要
- アクセストークンは、以下の情報を使って動的に生成
- ACCOUNT SID (Twilioのアカウントに紐付いたID)
- API KEY
- API SECRET
- IDENTITY (セッション中にユーザを識別するための値)
- アクセストークンに対して、接続可能なルームを埋め込むことが可能
- API経由で生成する他、管理コンソール上でも生成が可能
アクセストークンの生成プログラム(API経由)を作る
シナリオ
Programmable Videoで利用するアクセストークンを生成するプログラムを作りましょう。
今回は、実際にブラウザから動的に生成できるように、Twilioのサーバーレス環境であるTwilio Functionsを利用します。
手順1. APIキーを生成する
- 管理コンソールのスライドメニューから、Programmable Videoを選択します。
- Programmable Videoメニューのツール > API Keysを選択します。
- Create API Keyボタンを押すか、赤い**+**アイコンを押して、新しいAPI Keyを作成します。
- 名前欄に「Video」と入力し、キータイプは「Standard」を選択します。
- APIキーを作成するボタンを押します。
- 表示されるSID(SKから始まる文字列)と、SECRETに表示されている文字列の両方をメモ帳に保存します。
- **完了しました!**のチェックボックスにチェックを入れて、終了ボタンを押します。
手順2. アクセストークン発行用Functionの作成
Programmable Videoを利用するためには、先程作成したAPIキーを使って、アクセストークンを動的に生成する必要があります。
今回は、Twilio Functionsを使って、特定のルームにだけアクセス可能なアクセストークンを生成してみましょう。
- スライドメニューから、Runtimeを選択します。
- Runtimeメニューの中からFunctionsを選択し、さらにConfigureを選択します。
- Enable ACCOUNT_SID and AUTH_TOKENのチェックボックスをONにします。
- Environment Variablesの赤いプラスアイコンを2回押して、以下の変数を設定します。
- それぞれのKEY/VALUEを以下のように設定します。
KEY | VALUE |
---|---|
TWILIO_VIDEO_KEY | SKから始まるAPIキー |
TWILIO_VIDEO_SECRET | APIキーとセットで設定されたSECRET |
- Dependencies項目の中のtwilioのバージョンを「3.17.4」に変更します。
- Saveボタンを押して設定を保存します。
次に、IDENTITYをランダムに生成するためのモジュールをアップロードします。
- Twilio管理コンソールのRuntime > Assetsを選択します。
- Create an assetボタンを押して、今回のハンズオン用ソースコードフォルダにある「randomname.js」を選択します。MAKE PRIVATEのチェックを付けます。
- Uploadボタンを押して、アップロードを完了します(デプロイが完了するまで待ちます)。
次に、実際にアクセストークンを生成するFunctionを作成します。
- RuntimeメニューのFunctions > Manageを選択します。
- Create a Functionボタンをクリックして、新しいFunctionを生成します。
- テンプレートの一覧からBlankを選択して、Createボタンを押します。
- FUNCTION NAMEに「VideoToken」と入力し、PATHには、「/video-token」と入力します。
- ACCESS CONTROLのチェックは外しておきます。
- CODE欄に書かれているコードを以下のコードに置き換えます。
exports.handler = function(context, event, callback) {
let path = Runtime.getAssets()['randomname.js'].path;
let randomName = require(path);
const ACCOUNT_SID = context.ACCOUNT_SID;
const API_KEY = context.TWILIO_VIDEO_KEY;
const API_SECRET = context.TWILIO_VIDEO_SECRET;
const IDENTITY = randomName(); // ランダムにクライアント名を生成
const ROOM_NAME = 'VideoRoom'; // ルーム名は今回は固定
const AccessToken = Twilio.jwt.AccessToken;
const VideoGrant = AccessToken.VideoGrant;
// 参加できるルームをトークンで限定
const videoGrant = new VideoGrant({
room: ROOM_NAME
});
const accessToken = new AccessToken(
ACCOUNT_SID,
API_KEY,
API_SECRET
);
accessToken.addGrant(videoGrant);
accessToken.identity = IDENTITY;
callback(null, {
token: accessToken.toJwt()
});
};
- Saveボタンを押して、デプロイされるのを待ちます。
- デプロイが完了したら、PATH欄のURLをコピーして、別のブラウザタブで実行してみます。
- JSON形式でtoken(長い文字列)が表示されたら、トークンの生成は成功です。
STEP1 ブラウザでカメラ、マイクを利用してみよう
- getUserMedia()を利用してカメラとマイクを取得する
- Promiseによる非同期処理を行うAPI
navigator.mediaDevices.getUserMedia({video: true, audio: true})
.then(function (stream) { // success
}).catch(function (error) { // error
return;
});
getUserMediaに必要な処理を追加する
-
script.js
に追記して下さい
let localStream = null;
navigator.mediaDevices.getUserMedia({video: true, audio: true})
.then(stream => {
$('#myStream').get(0).srcObject = stream;
localStream = stream;
}).catch(error => {
console.error(`mediaDevice.getUserMedia() error: ${error}`);
return;
});
コンテンツをアップロードする
Twilioには、Assetsというストレージサービスがあります。
Assetsにアップロードした個々のファイルには、それぞれURLが払い出されます。これにより、AssetsをWebサーバーのように利用することが可能になります。
- Twilio管理コンソールのRuntime > Assetsを選択します。
- +アイコンを押して、アップロードするファイル「index.html」を選択します。MAKE PRIVATEのチェックは外しておきます。
- Uploadボタンを押して、アップロードを完了します(デプロイが完了するまで待ちます)。
- 同様に、script.jsとstyle.cssもアップロードします。
重要
Twilio Assetsにアップロードしたコンテンツのうち、コンテンツ内から参照されるファイル(今回の場合は、script.js)は、Twilio上でキャッシュされてしまうために、そのままでは更新しても内容が反映されません。
そのため、index.html側で、読み込むscript.jsにバージョンをクエリーとして付加することで、キャッシュされないようにする必要があります。
- アップロードが終わったら、index.htmlのURLをコピーして、別のタブで開きます。
実装する際のポイント
- VideoとAudioの選択
{video: true, audio: true}
- 最低でも640x480以上、出来たら1280x720が良いけどだめならよしなにやってください
{ audio: true, video: { width: {min: 640, ideal: 1280}, height: {min: 480,ideal: 720} } }
- 絶対に640x480がいいです…それ以外だったら動作しなくてもOK
- min,maxの代わりに exact: 640 と指定することも可能
{ audio: true, video: { width: {min: 640, max: 640}, height: {min: 480, max: 480} } }
- フレームレートの設定(一例/Chrome限定)
{ audio: true, video: { frameRate: { min: 10, max: 15 } } }
使用する上の注意点
- 許可を求めるダイアログが出てくる
- 複数のカメラやマイクが接続されている場合は、適切なものを選択する必要あり
chrome
Firefox
STEP2 ピアツーピアルームのビデオチャットを実装してみよう
ピアツーピアルームを利用して1:1のビデオチャットを実現してみます。
Twilio VideoのSDKを利用する
今回のハンズオンのHTMLには既に記載済みですが、以下の通りScript要素でSDKをインポートします。
<script type="text/javascript" src="//media.twiliocdn.com/sdk/js/video/v1/twilio-video.min.js"></script>
ルームの設定を変更する
- Twilioの管理コンソールを開き、スライドメニューからProgrammable Videoを選択します。
- 部屋(Rooms)の設定(Setting)を選択します。
- Room Topologyを以下のように設定します。
設定項目 | 設定値 | 備考 |
---|---|---|
ROOM TYPE | Peer-to-peer | ピアツーピアルーム |
TURN | ENABLED | 必要に応じてTURNを利用する |
MAXIMUM PERTICIPANTS | 2 | 最大参加者数を2に制限 |
- Saveボタンを押します。
アクセストークンをAjax経由で取得する
JavaScript内から先ほど作成したアクセストークン生成用のURLをAjaxを利用して取得します。
$.getJSON(`../video-token`, function (tokenResponse) {
console.log(tokenResponse.token);
});
取得したアクセストークンはJSON形式で返ってくるので、tokenResponse.token
でトークン本体を取得できます。
アクセストークンを使って、ルームに接続する
// ルームに接続
Twilio.Video.connect(tokenResponse.token, {
name: 'videoRoom',
})
.then(room => {
console.log(`Connected to Room ${room.name}`);
});
Roomへの接続時には、アクセストークンを渡すことが必要です。またオプションで、下記を渡すこともできます
- オーディオおよびビデオのオプション: 有効時には、Roomへの接続すると直ちにローカルのカメラおよびマイクからのオーディオおよびビデオのトラックが作成および公開されます。
- ローカルオーディオまたはビデオトラック: Roomへの接続に先立って作成済みのローカルメディアの他の参加者との共有を開始します。
- ルーム名: 参加したいルームの名前を動的に指定できます。 (メモ: ルーム名をアクセストークンにエンコードできます。 こうすることでユーザーはトークンで指定されたRoomにのみ接続できます。)
- ICEトランスポートポリシー: テスト用途として、強制的にTURNリレー経由で通話を行えるようになります。
- デバッグレベル: デバッグ用のログレベルです。
オプションの詳細については、こちらを御覧ください。
ルームの名前には、参加したいルームを指定します。 その名前のルームがまだ存在していない場合、接続に先立って作成されます。 ルームがすでにアクティブな場合はルームに接続され、同じルームに接続されている他の参加者からの通知を受信します。ルーム名はアカウント内で一意でなければなりません。
-
script.js
に、以下の内容を追記します。
let localStream = null;
let videoRoom = null; // 追加します
navigator.mediaDevices.getUserMedia({video: true, audio: true})
// 省略
});
$.getJSON(`../video-token`, function (tokenResponse) {
console.log(tokenResponse.token);
// ルームに接続
Twilio.Video.connect(tokenResponse.token, {
name: 'videoRoom',
})
.then(room => {
console.log(`Connected to Room ${room.name}`);
$('#my-id').text(room.localParticipant.identity); // 自分のIDを表示
videoRoom = room;
});
});
index.html
内のscript.jsのバージョンを更新し、 index.html
と script.js
をAssetsにアップロードします。
ブラウザで接続確認を行い、デバッグコンソールに以下のように表示がされればOKです。
すでに接続済みの参加者を表示する
ルームに参加すると、roomオブジェクトの中に、perticipantマップリストが返りますので、それを使ってすでに参加しているユーザの情報を取得できます。
// 省略
room.participants.forEach(participantConnected);
// 省略
function participantConnected(participant) {
console.log(`Participant ${participant.identity} connected'`);
}
participantクラスのドキュメントはこちらを御覧ください。
script.js
を変更し、ルームに接続したあとに、すでに参加している参加者の映像を表示します。
$.getJSON(`../video-token`, function (tokenResponse) {
console.log(tokenResponse.token);
// ルームに接続
Twilio.Video.connect(tokenResponse.token, {
name: 'videoRoom',
})
.then(room => {
console.log(`Connected to Room ${room.name}`);
$('#my-id').text(room.localParticipant.identity);
// 追加
videoRoom = room;
room.participants.forEach(participantConnected);
});
});
// すでに接続している参加者に関する処理を追加
function participantConnected(participant) {
console.log(`Participant ${participant.identity} connected'`);
const videoDom = document.createElement('div');
videoDom.id = participant.sid;
videoDom.className = 'videoDom';
participant.on('trackAdded', track => trackAdded(videoDom, track));
participant.tracks.forEach(track => trackAdded(videoDom, track));
participant.on('trackRemoved', trackRemoved);
$('.videosContainer').append(videoDom);
}
// トラックを追加します
function trackAdded(videoDom, track) {
videoDom.appendChild(track.attach());
}
// トラックを削除します
function trackRemoved(track) {
track.detach().forEach(element => element.remove());
}
divエレメントを動的に生成し、その中に参加者のトラックを追加します。
一般的なビデオ会議では、1参加者に対して、音声トラックと映像トラックの2つが登録されます。
トラックが追加されると、以下のようなdivエレメントが生成されます。
index.html(バージョンアップを忘れずに)と、script.jsをAssetsにアップロードして、ブラウザで動作を確認します。
この時点では、2枚めのタブで参加した側には、お互いの映像が見えるはずです。
最初に参加していた側に、あとから参加した人の映像を表示する
あとから参加者が参加したことは、roomのparticipantConnectedイベントで取得することができます。
また同様に、参加者が退出したこともparticipantDisconnectedイベントで取得できます。
これらを利用して、最初に参加していた側に、あとから参加した人の映像を表示させます。
script.js
を修正してみましょう。
$.getJSON(`../video-token`, function (tokenResponse) {
console.log(tokenResponse.token);
// ルームに接続
Twilio.Video.connect(tokenResponse.token, {
name: 'videoRoom',
})
.then(room => {
console.log(`Connected to Room ${room.name}`);
$('#my-id').text(room.localParticipant.identity);
videoRoom = room;
room.participants.forEach(participantConnected);
// 参加者が増えたり減ったりした場合の処理を追加
room.on('participantConnected', participantConnected);
room.on('participantDisconnected', participantDisconnected);
room.once('disconnected', error => room.participants.forEach(participantDisconnected));
});
});
// 省略
// 参加者が退出したときの処理を追加
function participantDisconnected(participant) {
console.log(`Participant ${participant.identity} disconnected.`);
participant.tracks.forEach(trackRemoved);
document.getElementById(participant.sid).remove();
}
今回は新しい参加者が入ってきた時と、参加者が退出したときのイベントに加えて、自分以外から退出をさせられた場合のdisconnectedイベントも記述してあります。
退出時の処理としては、すでに接続しているトラックの削除と、画面上のdivエレメントの削除を行っています。
index.html(バージョンアップを忘れずに)と、script.jsをAssetsにアップロードして、ブラウザで動作を確認します。
この時点で、最初に参加した人にもお互いの映像が見えるはずです。
JoinボタンとLeaveボタンを使って、動的にルームへの参加と退出を行う
現在のままでは、URLを開くと自動的にルームに参加してしまい、退出するにはブラウザを閉じるしかありません。
そこで、Joinボタンを押したらルームに参加し、Leaveボタンを押すと退出できるようにしましょう。
今回は、Joinボタンが押されたら、アクセストークンを取得する部分から実行するようにします。
script.js
を修正してみましょう。
// joinボタンを押した
$('#make-call').submit((e) => {
e.preventDefault();
$.getJSON(`../video-token`, function (tokenResponse) {
console.log(tokenResponse.token);
// 省略
room.on('participantConnected', participantConnected);
room.on('participantDisconnected', participantDisconnected);
room.once('disconnected', error => room.participants.forEach(participantDisconnected));
// ボタンのUIを切り替えを追加
$('#make-call').hide();
$('#end-call').show();
});
});
});
// leaveボタンを押した処理を追加
$('#end-call').click(() => {
videoRoom.disconnect();
console.log(`Disconnected to Room ${videoRoom.name}`);
$('#make-call').show();
$('#end-call').hide();
});
ルームから退出する一番簡単な方法は、**room.disconnect()**を実行することです。
退出したことは、他の参加者にも通知されます。
index.html(バージョンアップを忘れずに)と、script.jsをAssetsにアップロードして、ブラウザで動作を確認します。
これで、STEP2は完成です。
STEP3 グループルーム機能を利用して複数人によるビデオチャットを実装してみよう
STEP2では、ピアツーピアルームを利用して、最大2名までのビデオ会議を実装しましたが、このステップでは、グループルームを利用して、最大50名までのビデオ会議に変更していきたいと思います。
しかし実際にはコードを変更する必要はありません。
管理コンソールの設定を変更するだけでグループルームになります。
手順1. 設定の変更
- Twilioの管理コンソールを開き、スライドメニューからProgrammable Videoを選択します。
- 部屋(Rooms)の設定(Setting)を選択します。
- Room Topologyを以下のように設定します。
設定項目 | 設定値 | 備考 |
---|---|---|
ROOM TYPE | Group | グループルーム |
VIDEO CODEC | VP8 & H.264 | VP8が使えなければH.264 |
RECORDING | DISABLED | 録画は無効 |
MAXIMUM PERTICIPANTS | 50 | 最大参加者数を50に設定 |
- Saveボタンを押します。
手順2. テスト
- ブラウザを強制リロードして、3人以上でもビデオ会議ができることを確認します。
参考情報
今まで紹介してこなかったTwilio Videoの機能について、簡単に説明します。
- データトラック
- 画面共有
- 録画
- 料金
データチャネル
- 音声や動画以外に、データ通信用のトラックを利用することできます。
- チャットのような文字列のやり取りや、マウスの動きを送信して共同編集が可能です。
- データトラック用に、DataTrack APIが用意されています。
- ネットワークの状態によって、パケットのロスが発生することがあります。これを調整するためのパラメータとして、以下の2つを用意しています。
- maxPacketLifeTime: パケットの生存時間(この時間内であれば再送の対象とする。単位はms)
- maxRetransmits: パケットの再送回数(この回数を越えても送信できない場合はドロップ)
画面共有
- ChromeかFirefoxを利用する場合、画面共有が可能です。
- Chromeの場合は、Chrome Extensionを利用します。
- Firefoxはバージョン52以上で利用可能です。
- Chromeを使った画面共有のついては、こちらの記事をご参照ください。
- Firefoxを使った画面共有については、こちらの記事をご参照ください。
録画(Video Recording)
- 録画をするためには、グループルームの初期値で録画を有効にするか、REST APIでルームの作成時に録画を有効にします。
- 録画ファイルは、参加者ごとに、音声(mkaファイル)と動画(mkvファイル)の2つが作成されます。
- メディアファイルのダウンロードは、管理サイトから手動で行うか、以下のURLを使ってダウンロードすることができます。
https://video.twilio.com/v1/Recordings/${recordingSid}/Media
- 上記URLにアクセスするためには、APIキーとSECRETによるBASIC認証が必要です。
- メディアファイルをAWS S3に直接保存させることも可能です。
- Twilio側にメディアファイルを保存する場合は、ユーザの秘密鍵で暗号化することができます。
- これらの設定は、管理コンソール > Programmable Video > 録音 > 設定で行います。
- Compositions APIを利用することで、複数の参加者のメディアデータをまとめて、1つのmp4もしくはwebmに加工することができます。
Programmable Videoの料金(ピアツーピア)
ピアツーピアを利用する場合、接続するユーザによって以下の料金が掛かります。
- 1参加者 1分あたりの接続料金: 0.15円
Programmable Videoの料金(グループルーム/スモールグループルーム)
Group Room を利用する場合、接続するユーザによって以下の料金が掛かります。
- 1参加者 1分あたりの接続料金
- スモールグループルーム: 0.6円
- グループルーム: 1.5円
録画機能を利用する場合には以下の追加料金が発生します。
- 1参加者 1分あたりの録画料金: 1.5円
コンポジット機能を利用する場合には以下の追加料金が発生します。
- コンポジット後の動画ファイル 1分あたりの料金: 1.5円
データトラック機能を利用する場合には以下の追加料金が発生します。
※データトラックの最大送信メッセージサイズは16KBです。
- 1参加者がメッセージを送信する毎に以下の料金が発生
- グループルーム: 1,500円/1,000,000メッセージ
- スモールグループルーム: 600円/1,000,000メッセージ
例:スモールグループで、一人の参加者が1秒間に10メッセージを送信し、10分継続した場合
- メッセージ数: 10 x 60 x 10 = 6,000メッセージ
- 利用料: 600円 x (6,000) / 1,000,000 = 3.6円