脳汁portal

アメリカ在住(だった)新米エンジニアがその日学んだIT知識を書き綴るブログ

AWS lambdaとAPI GatewayとEC2を使って特定のURLにアクセスするだけでインスタンスを作成できるようにする

Lambdaの設定

まずは実際の処理部分をLambdaで設定します

関数の作成

関数の作成を選択して必要な情報を入力していきます。
今回ランタイムはPython2.7を選択しています。
f:id:portaltan:20190322115621p:plain

右下の関数の作成を選択すると以下のような画面が表示されます
f:id:portaltan:20190322115852p:plain

下にゆくと以下のように処理を入力するエリアが出てきます
f:id:portaltan:20190322115948p:plain

import boto3
from botocore.exceptions import ClientError
import json
import logging
import os
import datetime

AMIID=os.environ['AMIID']

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def create_userdata():
    str="""#!/bin/sh
date > /tmp/userdate.log
yum -y update
${PROCESS WHICH YOU WANNA DO}
echo "Finished UserData" > /tmp/userdata.log
"""

    return str

def create_name():
    now = datetime.datetime.now()
    return 'instance_from_lambda_' + now.strftime('%Y/%m/%d %H:%M') + '(UTC)'

def lambda_handler(event, context):
    ret={}

    ec2 = boto3.client('ec2',region_name='ap-northeast-1')
    try:
        res = ec2.run_instances(
                                ImageId=AMIID,
                                InstanceType=event['type'],
                                MinCount=1,
                                MaxCount=1,
                                InstanceInitiatedShutdownBehavior='terminate',
                                IamInstanceProfile={'Name': 'your IAM'},
                                NetworkInterfaces=[{
                                            'DeviceIndex': 0,
                                            'SubnetId' : 'your subnet id',
                                            'Groups': ["your security group"],
                                            'AssociatePublicIpAddress': True            
                                        }],
                                TagSpecifications=[
                                            {
                                                'ResourceType':'instance',
                                                'Tags':[
                                                    {
                                                    'Key':'Name',
                                                    'Value':create_name()
                                                    }
                                                ]
                                            }
                                        ],
                                UserData=create_userdata()
                                )
        
        return res['Instances'][0]['InstanceId']
    except ClientError as e:
        ret['code'] = e.response['Error']['Code']
        ret['message'] = e.response['Error']['Message']
        
        raise json.dumps(ret)    
  • 今回はAMIIDは以下のように環境変数で設定します

f:id:portaltan:20190322133447p:plain

  • instance typeはAPI Gatewayのステージ変数で設定します
  • IAMとsubnet id、security groupは自分の環境に応じたものを入力してください

次にタイムアウトの時間を3秒から15秒に伸ばしておきます
f:id:portaltan:20190322134310p:plain

最後に右上の保存を押して設定を保存します

test

次にまずはLambda単体の設定がうまくいっているかどうかテストをします。
保存の隣にあるテストを選択し、テストに必要なインスタンスタイプを入力します
f:id:portaltan:20190322134519p:plain

次に実際にテストを実行します。うまくいくと次のような表示が現れ、responseとして起動したインスタンスIDが返ってきます。
f:id:portaltan:20190322134625p:plain

以上でLambda側の設定を完了します

API Gatewayの作成

次に上記のLambdaの処理を呼び出すAPIを作成します

リソースの作成

API Gateway > APIの作成を選択し、必要な項目を入力してAPIの作成を選択していきます
f:id:portaltan:20190322114008p:plain

以下のような画面になります
f:id:portaltan:20190322114055p:plain

アクションからリソースの作成を選択し、必要な情報を入力します
f:id:portaltan:20190322114212p:plain

次にステージ変数としてインスタンスタイプを送れるようにします
f:id:portaltan:20190322135343p:plain

次にアクションからメソッドの作成を選択し、ドロップダウンでGETを選択します。
選択すると具体的な設定の入力項目が出現するので、今回は統合タイプは「Lambda関数」、Lambda関数には先ほど作成した関数の「test」を入力します
f:id:portaltan:20190322134752p:plain

次に統合リクエストのマッピングテンプレートから、パラメータの設定を行います。
f:id:portaltan:20190322140803p:plain

{
  "type": "$input.params('type')"
}

APIのデプロイ

作成したAPIはまだデプロイしていないので、明示的にデプロイする必要があります。
以下のようにアクションからAPIのデプロイを選択し、名前をつけてデプロイします

  • 今回はtest_deployとした

f:id:portaltan:20190322134917p:plainf:id:portaltan:20190322134941p:plain

ステージの確認

ステージという項目から、先ほどdeployしたAPIのアクションを確認できます。
GETを選択すると、以下のようにAPIのエンドポイントとパラメーター等を確認できます

Lambdaの確認

先ほどのLambdaの画面に戻ると、トリガーの項目にAPI Gatewayが設定されていることが確認できます。

以上でAPIの設定は完了です

確認

ブラウザから作成したAPIを呼び出してみます
https://${your env}.execute-api.ap-northeast-1.amazonaws.com/test_deploy/create/c5.xlarge

  • 今回はインスタンスタイプとしてc5.xlargeを指定しています
  • ${your env}の部分は自分の環境に応じて上記で確認したエンドポイントを入力してください

Nuxt.js Tips

nuxt.jsのtipsです
f:id:portaltan:20190305115710p:plain

componentに値を渡す場合

送る側
<template>
  <MyComponent />
</template>

<script>
import MyComponent from "~/components/MyComponent.vue"

export default {
  components: {
    MyComponent
  }
}
</scipt>
受け取る側(MyComponet.vue)
<script>
export default {
  props: {
    aaa, bbb, ccc
  }
}
</script>
型チェックをしたい場合
<script>
export default {
  props: {
    aaa: String, 
    bbb: Boolean,
    ccc: Array
  }
}
</script>

propsに直接文字列を入れる場合

<MyComponent :myprop="StringDayo" />

とすると

Raw expression: :myprop="StringDayo"

となる

解決策
<MyComponent myprop="StringDayo" />
or
<MyComponent :myprop="'StringDayo'" />

props等の変数をhtmlの要素にいれたい

<template>
  <nuxt-link to="this.link">
    Link Dayo
  </nuxt-link>
</template>

<script>
export default {
  props: {
    link: String
  }
}
</script>

これだとpropsのlinkが呼ばれずに/this.linkというリンクが参照されてしまう
解決策としては変数を使いたいときには要素の前に:をつける

<nuxt-link :to="this.link">

画像をpropsとして読み込む

<template>
  <div>
    <img :src="image_src" />
  </div>
</template>

<script>
export default {
  props: {
    filename: String,
  },
  data() {
    return {
      image_src: require(`~/assets/img/${this.filename}`)
    }
  }
}
</script>

methodsを追加する場合

<template>
  <div class="foobar" @mouseover="mouseOn()" @mouseout="mouseOut()">
    hogefuga
  </div>
</template>

<script>
export default {
  methods: {
    mouseOn() {
      document.getElementById(foobar).style.color = "red";
    },
    mouseOut() {
      var img = document.getElementById(foobar);
      img.style.color = "gray";
    }
  }
}
</scrip>

ページ読み込み時に処理を行いたいとき

<script>
export default {
  mounted() {
    alert('foobar');
  }
}
</script>

jsonファイルを読み込んで表示する

/static/json/test.json

{
  "aaa": true
  "bbb": false
  "ccc": "hogehoge"
}

/pages/test.vue

<script>
export default {
  data() {
    return {
      json: require(`static/json/test.json`)
    }
  }
}
</script>

▼v-for
要素を繰り返えしたいときに使う
上のjsonファイルを使う

<template>
  <div>
    <ol>
      <li v-for"(value, index) in json" property1="index" property2="value"></li>
    </ol>
  </div>
</template>

<script>
export default {
  data() {
    return {
      json: require(`static/json/test.json`)
    }
  }
}
</script>

v-bind:class

クラス属性を条件分岐で設定するしないを分ける

<template>
  <div>
    <ol>
      <li v-for"(value, index) in json" v-bindclass="{ active: aaa }"></li> // 描画される(aaa === true)
      <li v-for"(value, index) in json" v-bindclass="{ active: bbb }"></li> // 描画されない(bbb === false)
    </ol>
  </div>
</template>

<script>
export default {
  data() {
    return {
      json: require(`static/json/test.json`)
    }
  }
}
</script>

nuxt generateで動的ルーティングもbuild対象にする

nuxtは_foobar.vueといったアンダースコアで始まるvueファイルを作ることで動的にルーティングすることができる。
しかしこの動的ルーティングはデフォルトでnuxt generateの静的ファイルビルドの対象外となっている
以下のようにnuxt.config.jsで明示的に指定してやることで静的なルーティングがビルドされる

module.exports = {
  generate: {
    routes:[
      '/hogehoge',
      '/fuga/fuga'
    ]
  }
}

また、ファイル数が多くなって直接編集が面倒になったときは、以下のようにもできる
staticファイル以下のjsonファイルを読み込んでroutingに設定している

const fs =require('fs')
const filename = fs.readdirSync("./static/").map(x => x.match(/(.*)(?:\.([^.]+$))/)[1])

module.exports = {
  generate: {
    routes: filenames
  }
}

ホットリロードで開発サーバをたてる

npm run dev

listen portを開ける

上記のnpm run devでデバッグサーバをたてることが可能になるが、デフォルトではlocalhostからしかアクセスできない。
なのでnuxt.config.jsで以下のように指定する

module.exports = {
  server: {
    host: '0.0.0.0'
  }
}

rss.xmlを設定する

nuxt generate時にrss.xmlを更新する方法
まずstatic/rss.xmlを作る

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>My Home Page</title>
<link>https://my-home-page.com/</link>
<description>このページはぼくのホームページぃぃぃ!!!</description>
<lastBuildDate>${generate by modules}</lastBuildDate>
<language>ja</language>
<item>
<title>日記</title>
<link>https://my-home-page/blog/</link>
<description>はじめての日記</description>
<pubDate>Fri, 01 Mar 2019 18:00:00 +0900</pubDate>
</item>
</channel>

このstaticファイルの内容はコンパイルされずにそのままdist以下に配置されるので、
nuxt generateが終わった時点で${generate by modules}のところを書き換えてやればよい
modules/hook/generate.js

require('date-utils')
const fs = require ('fs')
const now = new Date()
const rss = fs.readFileSync("static/rss.xml", "utf-8").replace("<lastBuildDate>${generate by modules}</lastBuildDate>", `<lastBuildDate>${now.toFormat('DDD, DD MMM YYYY HH24:MI:SS +0900')}</lastBuildDate>`)

module.exports = function () {
  this.nuxt.hook('generate:done', async generator => {
    fs.writeFileSync("dist/rss.xml", rss);
  })
}

moduleを書いたら、nuxt.config.jsで指定する

module.exports = {
  modules: [
    '@/modules/hook/generate'
  ]
}

bootstrapを読み込む際に二重で読み込まれるのを防ぐ

nuxt.config.js
modules.exports = {
  modules: [
    ['bootstrap-vue/nuxt', { css: false }]
  ]
}

v-if/v-else-if/v-else

コンポーネント等の表示切替をv-if等を使って切り替えることができる

<template>
  <div>
    <img v-if="this.type==='image'" :src="src"></img>
    <video v-else-if="this.tyep==='movie'" :src="src" autoplay loop muted></video>
    <p v-else>missing media</p>
  </div>
</template>

<script>
export default {
  props: {
    type: String,
    link: String
  },
  data() {
    return {
      src: require(`~/assets/img/${this.link}`)
    }
  }
}
</script>

v-for内でv-ifを使う

v-forとv-ifを同じ要素内で使うことは推奨されていない

<template>
  <div>
    <div v-for="(value, index) in objects">
      <div v-if="value">aaa</div>
      <div v-if="index===0">bbb</div>
    </div>
  </div>
</template>

外部リソースを読み込む(bootstrapとかjqueryとか)

<script>
export default {
  head () {
    return {
      script: [
        { src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js' },
        { src: 'https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js' }
      ],
      link: [
        { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css?family=Roboto' }
      ]
    }
  }
}
</script>

custom layoutを読み込む

<script>
export default {
  layout: 'custom'
  }
}
</script>

▼404ページの作成
pages/404/index.vueに作成する

▼動的ルーティングで値を受け取る
pages/_id.vue

<template>
  <div>
    {{parameter}}
  </div>
</template>

<script>
export default {
  asyncData({ params }) {
    return {
      parameter: params.id
    }
  }
}
</script>

AWS S3でStatic website hostingを使うけど、bucketへの直リンクアクセスを制限したい場合

AWS S3に配置した静的コンテンツを使ってcloud front経由でwebsiteを公開するときに、S3 bucket自体をoriginとして指定する場合はorigin access identityでcloud frontからのアクセスのみを許可するということが可能になる。

しかし、S3のstatic website hosting機能を使ってエンドポイントを発行した場合は、このorigin access identityが使えない
この場合S3 bucket側でcloud frontからのアクセスのみを許可するといったスマートなアクセス制限はできない。

IPアドレスによる制限は可能だが、ここで指定するIPは実際にアクセスするユーザのIPレンジではなく、cloud frontのIPを指定しなければいけない。それでもいいのだが、このIPは変わる可能性もあるし、大量にルールを書かなければいけないので避けたい。

解決方法としては、cloud frontからcustome headerとして任意の文字をrefererで送り、S3 bucket側でその特定のheaderがある場合のみGetObjectできるといったbucket policyを設定してやればよい

※static website hosting機能にこだわるのは、サブディレクトリでもindex補完機能を使いたいから
(cloud front側で使えるindex補完はrootディレクトリのみとなっている)


1. S3のwebsite hosting機能をonにする

f:id:portaltan:20190221155457p:plain

2. アクセスコントロール/bucket policyの編集を許可する

f:id:portaltan:20190221155638p:plain

3. bucket policyを設定する

{
    "Version": "2008-10-17",
    "Id": "http referer policy",
    "Statement": [
        {
            "Sid": "Referer-Allow",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::example-bucket/*",
            "Condition": {
                "StringLike": {
                    "aws:Referer": "foobar"
                }
            }
        },
        {
            "Sid": "Explicit deny to ensure requests are allowed only from specific referer.",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::example-bucket/*",
            "Condition": {
                "StringNotLike": {
                    "aws:Referer": "foobar"
                }
            }
        }
    ]
}

この時点でまず指定したheaderの有無でアクセス制限がかかっているか確認する

curl --header 'referer:foobar' ${static website hosting Endpoint URL}
→アクセスできる

curl --header 'referer:fizzbazz' ${static website hosting Endpoint URL}
→アクセスできない(403エラー)

4. cloudfrontの設定

  • Origin Domain Name
    • static website hostingのエンドポイントURLを入力
  • custome Header

後は普通に設定すればOKです
distributingが終わったら、一応cloudfrontのdomainにアクセスして確認しまs
(*****.cloudfront.netってやつ)

5. Route53

最後にRoute53で任意のドメインと上記のcloudfrontのドメインを紐づければ完成です

opencvとpythonで色抽出

OpenCVを使ってPythonで特定の色を画像から抜き出します
色を数値で表す方法としてRGBがまず上がりますが、今回はHSVという値を使います。
HSV色空間 - Wikipedia

RGBは時のごとくRed, Green, Blueで値で色を表現しますが、
HSVとは色相(Hue)、彩度(Saturation)、明度(Value)で色を表現し画像認識なんかでよく使われます

元データ

f:id:portaltan:20181101174632j:plain
この某イカの絵のピンク部分を抜き出していきまs

ソース

#!/usr/bin/python
import numpy as np
import cv2

# image info
image_file = 'ika.jpg'
img = cv2.imread(image_file)

# detect pink 
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower = np.array([150, 100, 100])    # 紫に近いピンク
upper = np.array([175, 255, 255]) # 赤に近いピンク
img_mask = cv2.inRange(hsv, lower, upper)
img_color = cv2.bitwise_and(img, img, mask=img_mask)

# debug
cv2.imwrite("ika_hsv.jpg", img_color)

実行結果

f:id:portaltan:20181101175037j:plain

触手の紫を消すために少し修正

lower = np.array([157, 100, 170])
upper = np.array([175, 255, 255])

f:id:portaltan:20181102152222j:plain
いい感じにピンクが抽出できました

Microsoft Azureでラズベリーパイから cloud live streamingする方法

Microsoft AzureのMedia Servicesを使ってラズベリーパイからストリーミングを行う方法です

Azure Media Services

今回はAzureを利用してみます
Media Services : azure.microsoft.com

構成

  • チャネル、プログラム、StreamingEndpointsからなる
  • 各Media Servicesアカウントには、複数のチャネル、複数のプログラム、複数のStereamingEndpointsを含めることができる
  • 帯域幅とセキュリティのニーズに応じて、StreamingEndpointサービスを一つまたは複数のチャネル専用にすることができる
  • StreamingEndpointはどのチャネルからでもプルできる
channel
  • Media Servicesにおいてライブストリーミングコンテンツの処理を担う
  • ライブストリーミングコンテンツを処理するためのパイプラインを表す
  • 入力エンドポイントであり、その取り込みURLをライブトランスコーダーに対して指定する
  • チャネルはライブトランスコーダからライブ入力ストリームを受け取り、1つまたは複数のStreaming Endpointを介してストリーミングできる状態にする
  • ストリームはあらかじめプレビューし、確認した上で処理、配信するが、チャネルはその際にしようするプレビューエンドポイントも提供する
  • チャネル作成時に取り込みようURLとプレビューURLを取得できる
    • チャネルが開始済み状態である必要はない
    • ライブトランスコーダーからチャネルへのデータのプッシュを開始する準備ができたら、チャネルを開始する必要がある
    • ライブトランスコーダーがデータの取り込みを開始した後、ストリームをプレビューできる
  • チャネルの状態
    • 停止済み:初期状態。ストリーミングの許可はされていない
    • 開始中:チャネルを開始している。更新やストリーミングはできない。
    • 実行中:ライブストリームを処理できる
    • 停止中:チャネルを停止している
    • 削除中:チャネルを削除している

Azureの設定

流れとしては以下のようになります
1. Azure Media Servicesのアカウント作成
2. StreamingEndpointsの開始
3. チャネルの作成
4. live eventの作成
5. channel とlive eventのstart

1. Create Azure Media Services Account

Media Servicesを選択
f:id:portaltan:20181024142216p:plain
 
Addを選択
f:id:portaltan:20181024142339p:plain
 
データを入力していきます

  • Account Name
  • Subscription
  • Resource Group
  • Location
  • Storage Account

f:id:portaltan:20181030094317p:plain

作成が完了すると、Media Servicesの一覧に新しい項目が追加されます
f:id:portaltan:20181030094357p:plain

2. Start Streaming Endpoint

1で作成したMedia Serviceのアカウントを選択しOverview BladeからStreaming Endpointを選択します
f:id:portaltan:20181024143305p:plain
そのまま以下の通りEndpointを開始します
f:id:portaltan:20181030094629p:plain

3. Start Channel

Live Streaming BladeからCustom Createを選択します
f:id:portaltan:20181024143801p:plain

  • Setting
    • Endoding type: Pass Through
      • Pass Throughはsingle bit encoding
      • クライアント側の通信速度に応じて画質を変更するmulti bit encodingも可能だが、その分遅延が発生するので今回はやらない
      • Encoding UnitもPass Throughの際は関係ない(Reserved Unit0でよい)
    • Name:なんでも
    • Description: なんでも
    • Automatically start the channel after creation: チェック外す
    • f:id:portaltan:20181030094738p:plain
  • Ingest
    • Streaming protocol: RTMP
    • f:id:portaltan:20181024144954p:plain
  • Preview
    • 変更の必要なし
  • Create
    • f:id:portaltan:20181024144933p:plain

channelが作成されるとLive Streaming Bladeに作成されたChannelが表示されます
作成されたChannelを選択し、INGEST URL(Primary)をメモしておきます
f:id:portaltan:20181030095020p:plain

4. Create Live Event

ChannelのメニューからLive Eventを選択します
f:id:portaltan:20181024145633p:plain

Live Eventのデータを入力します

  • Name
  • asset name
  • archive window
    • 最小設定可能時間は5分
    • archive自体は項目にDayもあるしStorageに余裕がある限り保存できそう
    • ただし一回のストリーミングに関しては連続して8時間以下が推奨とのこと

f:id:portaltan:20181030095142p:plain

Live Eventが作成されたら、Streaming URLをメモしておきます
f:id:portaltan:20181030095214p:plain

5. Start Channel & Live Event

最後にChannelとLive Eventを開始します
f:id:portaltan:20181030095357p:plain
 
f:id:portaltan:20181030095550p:plain

Raspberry Piの設定

設定
# カメラの確認
$ modprobe bcm2835-v4l2  # ラズパイカメラを利用する場合
$ v4l2-ctl --list-device
mmal service 16.1 (platform:bcm2835-v4l2):
        /dev/video0

# 音声入力デバイスの確認
$ arecord -l
**** List of CAPTURE Hardware Devices ****
### 今回はマイク機能なしのカメラを利用

# 映像フォーマット、解像度、フレームレートの組み合わせの確認
$ v4l2-ctl -d /dev/video0 --list-formats-ext
ioctl: VIDIOC_ENUM_FMT
        Index       : 0
        Type        : Video Capture
        Pixel Format: 'YU12'
        Name        : Planar YUV 4:2:0
                Size: Stepwise 16x16 - 3280x2464 with step 2/2

        Index       : 1
        Type        : Video Capture
        Pixel Format: 'YUYV'
        Name        : YUYV 4:2:2
                Size: Stepwise 16x16 - 3280x2464 with step 2/2

        Index       : 2
        Type        : Video Capture
        Pixel Format: 'RGB3'
        Name        : 24-bit RGB 8-8-8
                Size: Stepwise 16x16 - 3280x2464 with step 2/2

        Index       : 3
        Type        : Video Capture
        Pixel Format: 'JPEG' (compressed)
        Name        : JFIF JPEG
                Size: Stepwise 16x16 - 3280x2464 with step 2/2

        Index       : 4
        Type        : Video Capture
        Pixel Format: 'H264' (compressed)
        Name        : H.264
                Size: Stepwise 16x16 - 3280x2464 with step 2/2

        Index       : 5
        Type        : Video Capture
        Pixel Format: 'MJPG' (compressed)
        Name        : Motion-JPEG
                Size: Stepwise 16x16 - 3280x2464 with step 2/2

        Index       : 6
        Type        : Video Capture
        Pixel Format: 'YVYU'
        Name        : YVYU 4:2:2
                Size: Stepwise 16x16 - 3280x2464 with step 2/2

        Index       : 7
        Type        : Video Capture
        Pixel Format: 'VYUY'
        Name        : VYUY 4:2:2
                Size: Stepwise 16x16 - 3280x2464 with step 2/2

        Index       : 8
        Type        : Video Capture
        Pixel Format: 'UYVY'
        Name        : UYVY 4:2:2
                Size: Stepwise 16x16 - 3280x2464 with step 2/2

        Index       : 9
        Type        : Video Capture
        Pixel Format: 'NV12'
        Name        : Y/CbCr 4:2:0
                Size: Stepwise 16x16 - 3280x2464 with step 2/2

        Index       : 10
        Type        : Video Capture
        Pixel Format: 'BGR3'
        Name        : 24-bit BGR 8-8-8
                Size: Stepwise 16x16 - 3280x2464 with step 2/2

        Index       : 11
        Type        : Video Capture
        Pixel Format: 'YV12'
        Name        : Planar YVU 4:2:0
                Size: Stepwise 16x16 - 3280x2464 with step 2/2

        Index       : 12
        Type        : Video Capture
        Pixel Format: 'NV21'
        Name        : Y/CrCb 4:2:0
                Size: Stepwise 16x16 - 3280x2464 with step 2/2

        Index       : 13
        Type        : Video Capture
        Pixel Format: 'BGR4'
        Name        : 32-bit BGRA/X 8-8-8-8
                Size: Stepwise 16x16 - 3280x2464 with step 2/2

# 映像フォーマット一覧の確認
$ ffmpeg -f v4l2 -list_formats all -i /dev/video0
ffmpeg version N-89701-gb2be76c Copyright (c) 2000-2018 the FFmpeg developers
  built with gcc 4.9.2 (Raspbian 4.9.2-10)
  configuration: --arch=armel --target-os=linux --enable-gpl --enable-libx264 --enable-nonfree
  libavutil      56.  7.100 / 56.  7.100
  libavcodec     58.  9.100 / 58.  9.100
  libavformat    58.  3.100 / 58.  3.100
  libavdevice    58.  0.100 / 58.  0.100
  libavfilter     7. 10.100 /  7. 10.100
  libswscale      5.  0.101 /  5.  0.101
  libswresample   3.  0.101 /  3.  0.101
  libpostproc    55.  0.100 / 55.  0.100
[video4linux2,v4l2 @ 0x2f3d1b0] Raw       :     yuv420p :     Planar YUV 4:2:0 : {16-3280, 2}x{16-2464, 2}
[video4linux2,v4l2 @ 0x2f3d1b0] Raw       :     yuyv422 :           YUYV 4:2:2 : {16-3280, 2}x{16-2464, 2}
[video4linux2,v4l2 @ 0x2f3d1b0] Raw       :       rgb24 :     24-bit RGB 8-8-8 : {16-3280, 2}x{16-2464, 2}
[video4linux2,v4l2 @ 0x2f3d1b0] Compressed:       mjpeg :            JFIF JPEG : {16-3280, 2}x{16-2464, 2}
[video4linux2,v4l2 @ 0x2f3d1b0] Compressed:        h264 :                H.264 : {16-3280, 2}x{16-2464, 2}
[video4linux2,v4l2 @ 0x2f3d1b0] Compressed:       mjpeg :          Motion-JPEG : {16-3280, 2}x{16-2464, 2}
[video4linux2,v4l2 @ 0x2f3d1b0] Raw       : Unsupported :           YVYU 4:2:2 : {16-3280, 2}x{16-2464, 2}
[video4linux2,v4l2 @ 0x2f3d1b0] Raw       : Unsupported :           VYUY 4:2:2 : {16-3280, 2}x{16-2464, 2}
[video4linux2,v4l2 @ 0x2f3d1b0] Raw       :     uyvy422 :           UYVY 4:2:2 : {16-3280, 2}x{16-2464, 2}
[video4linux2,v4l2 @ 0x2f3d1b0] Raw       :        nv12 :         Y/CbCr 4:2:0 : {16-3280, 2}x{16-2464, 2}
[video4linux2,v4l2 @ 0x2f3d1b0] Raw       :       bgr24 :     24-bit BGR 8-8-8 : {16-3280, 2}x{16-2464, 2}
[video4linux2,v4l2 @ 0x2f3d1b0] Raw       :     yuv420p :     Planar YVU 4:2:0 : {16-3280, 2}x{16-2464, 2}
[video4linux2,v4l2 @ 0x2f3d1b0] Raw       : Unsupported :         Y/CrCb 4:2:0 : {16-3280, 2}x{16-2464, 2}
[video4linux2,v4l2 @ 0x2f3d1b0] Raw       :        bgr0 : 32-bit BGRA/X 8-8-8-8 : {16-3280, 2}x{16-2464, 2}
/dev/video0: Immediate exit requested
撮影

Azure Media Servicesは、音声データと音声コーデックを指定して送信しないとエラーになり受け付けてもらえません

  • 無音(音声データなし)はNG
  • 無音(音声データあり)はOK

ラズパイカメラやUSBカメラのみでマイクがない場合は、ffmpeg側で無音の音声データを付加してAzure Media Services側に送信する必要があります

# ffmpeg [options] [[infile options] -i infile]... {[outfile options] outfile}...
$ ffmpeg -vsync passthrough -f v4l2 -vcodec h264 -framerate 30 -video_size 320x180 -i /dev/video0 -f lavfi -i aevalsrc=0 -shortest -strict -2 -c:a aac -b:a 7350 -ar 7350 -g 60 -keyint_min 60 -c:v copy -preset ultrafast -b:v 150k -maxrate 150k -bufsize 150k -f flv $INGESTURI/mystream
  • $INGESTURLはAzure側でチャネルを設定した際にメモしたものです
  • さらに注意点として、ffmpegで送信する場合にはINGEST_URIの後ろにmystream等の名前をつけないとエラーになります
    • mystreamに特に意味はなく、なんでもOKです

ffmpegのオプションに関しては

  • f fmt: force format
  • V4l2: Video for linux
  • alsa: audio
  • hw: 1,0 audioのinput の指定
  • c:v videoのcodec
  • c:a audioのcodec
  • framerate / フレームレート
  • r / set frame rate
  • s / size,set frame size(WxH)
  • i / input deviceの指定
  • /dev/video0 / 接続されているvideoデバイスの一つ
  • vcodec / force video codec
  • ultrafast /
    • エンコードのスピードと圧縮率に影響をもたらすオプションのコレクション
    • 速いほうから順に、ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow, placebo
  • acodec / audio codec
  • libfaac / 同上
  • ab / audio bitrate(pleas use -b:a)
  • b / video bitrate(please use -b:v)
  • sc_thresold / シーンチェンジ検出の閾値(0でdefault, -1 でシーンチェンジ検出無効)

たまに落ちる場合は以下みたくして対応

#!/bin/bash

modprobe bcm2835-v4l2
INGESTURI="rtmp://*****************************************************" # primary

while :
do
ffmpeg -vsync passthrough -f v4l2 -vcodec h264 -framerate 30 -video_size 320x180 -i /dev/video0 -f lavfi -i aevalsrc=0 -shortest -strict -2 -c:a aac -b:a 7350 -ar 7350 -g 60 -keyint_min 60 -c:v copy -preset ultrafast -b:v 150k -maxrate 150k -bufsize 150k -f flv $INGESTURI/mystream
sleep 10
done
遅延に関して

だいたい色々チューニングをした結果、16,7秒くらいの遅延が発生します。
Azure側の処理の都合上、最短で15-20秒は遅延が発生するらしいです。
Azure Media Player RTMP Latency
現在Microsoft側でも低レイテンシ版の開発をしているとのことですが、あまり優先度は高くないとのこと。
Azure Media Services: Top (187 ideas) – Customer Feedback for Microsoft Azure

ちなみにGCPの中の人とも話す機会があったのですが、大体おんなじくらいの遅延は発生しますとのこと。
これは現在のクラウドストリーミングのサービスや技術が、大人数にむけて配信することをターゲットとしており、秒単位で遅延を減らす方向にはシフトしていないとのこと。

ブラウザでの表示

AzureのUI上で確認できますが、ストリーミングをWebブラウザに表示する方法です
HTML

<head>
  <link href="//amp.azure.net/libs/amp/2.1.5/skins/amp-default/azuremediaplayer.min.css" rel="stylesheet">
  <script src="//amp.azure.net/libs/amp/2.1.5/azuremediaplayer.min.js"></script>
</head>
  
<body>
  <video id="azuremediaplayer" class="azuremediaplayer amp-default-skin amp-big-play-centered" tabindex="0"></video>
</body>

javascript

$(function() {
  var myOptions = {
        "nativeControlsForTouch": false,
        controls: true,
        autoplay: true,
        width: "640",
        height: "400",
  }
  myPlayer = amp("azuremediaplayer", myOptions);
  myPlayer.src([
          {
                  "src": "$Streaming URL without protocol",
                  "type": "application/vnd.ms-sstr+xml"
          }
  ]);
});

tips

windowsからの配信

配信がうまくいかずに、まずは問題の切り分けとしてWindowsから配信テストを行う方法

### 接続したUSBカメラ等が認識しているか確認
$ ffmpeg -f dshow -list_devices true -i dummy
[dshow @ 0000000000150980] DirectShow video devices (some may be both video and audio devices)
[dshow @ 0000000000150980]  "MS-M103HU USB Camera"
[dshow @ 0000000000150980]     Alternative name "@device_pnp_******************23196}\global"
[dshow @ 0000000000150980] DirectShow audio devices
[dshow @ 0000000000150980] Could not enumerate audio only devices (or none found).
dummy: Immediate exit requested
## 2行目のMS-M103HU USB Cameraが今回接続したUSBカメラ(以降はこの名前をオプションとして指定する)
## Alternative nameも利用することができる。同じ種類のカメラを複数接続したときに利用するっぽい

### 検出されたカメラの仕様を確認する
$ ffmpeg -f dshow -list_options true -i video="MS-M103HU USB Camera"
[dshow @ 00000000003a09c0] DirectShow video device options (from video devices)
[dshow @ 00000000003a09c0]  Pin "Capture" (alternative pin name "0")
[dshow @ 00000000003a09c0]   pixel_format=yuyv422  min s=1280x720 fps=12 max s=1280x720 fps=12
[dshow @ 00000000003a09c0]   pixel_format=yuyv422  min s=1280x720 fps=12 max s=1280x720 fps=12
[dshow @ 00000000003a09c0]   vcodec=mjpeg  min s=1280x720 fps=30 max s=1280x720 fps=30
[dshow @ 00000000003a09c0]   vcodec=mjpeg  min s=1280x720 fps=30 max s=1280x720 fps=30
## このカメラだと2パターンの録画しか出来ない

テスト撮影

# ffmpeg [options] [[infile options] -i infile]... {[outfile options] outfile}...
# -f fmt: force format
# V4l2: Video for linux
# alsa: 音声
# hw: 1,0 音声の入力指定
# -c:v videoのcodec
# -c:a audioのcodec

### infile options
## input optionはカメラが対応していないものを指定してもエラーが返って来る
# このカメラは1280x720しか対応していないので他の解像度を指定してもダメ
> ffmpeg -f dshow -video_size 640x480 -i video="MS-M103HU USB Camera"
[dshow @ 00000000003b0a00] Could not set video options
video=MS-M103HU USB Camera: I/O error

# このカメラは12FPS(yuvj)か30FPS(mjpeg)しか対応していないので他のFPSを指定してもダメ
> ffmpeg -f dshow -framerate 24 -i video="MS-M103HU USB Camera"
[dshow @ 00000000006809c0] Could not set video options
video=MS-M103HU USB Camera: I/O error

# 選択可能なFPSの場合は適したコーデックが自動で選択される
> ffmpeg -f dshow -framerate 30 -i video="MS-M103HU USB Camera"
    Stream #0:0: Video: mjpeg (MJPG / 0x47504A4D), yuvj422p(pc, bt470bg/unknown/unknown), 1280x720 [SAR 96:96 DAR 16:9],
 30 fps, 30 tbr, 10000k tbn, 10000k tbc
> ffmpeg -f dshow -framerate 12 -i video="MS-M103HU USB Camera"
    Stream #0:0: Video: rawvideo (YUY2 / 0x32595559), yuyv422, 1280x720, 12 fps, 12 tbr, 10000k tbn, 10000k tbc

# コーデックを指定する場合は対応したFPSじゃないとダメ
> ffmpeg -f dshow -vcodec mjpeg -framerate 12 -i video="MS-M103HU USB Camera"
[dshow @ 00000000005c0c00] Could not set video options
video=MS-M103HU USB Camera: I/O error

### output option
# mp4ファイルで出力したいとき(yuv420p)
> ffmpeg -f dshow -vcodec mjpeg -framerate 30 -video_size 1280x720 -i video="MS-M103HU USB Camera" -pix_fmt yuv420p rec.mp4

# 出力動画のFPSを落としたいとき(-r 3)
> ffmpeg -f dshow -vcodec mjpeg -framerate 30 -video_size 1280x720 -i video="MS-M103HU USB Camera" -pix_fmt yuv420p -r 3 rec.mp4

実際にWindowsからストリーミング

ffmpeg \
-rtbufsize 100M \
-vsync passthrough \
-f dshow \
-vcodec mjpeg 
-framerate 30 \
-video_size 1280x720 \
-i video="MS-M103HU USB Camera" \
-f lavfi \
-i aevalsrc=0 -shortest \
-strict -2 \
-c:a aac \
-b:a 128k \
-ar 44100 \
-r 30 \
-g 60 \
-keyint_min 60 \
-b:v 400000 \
-c:v libx264 \
-preset ultrafast \
-bufsize 400k \
-maxrate 400k \
-f flv rtmp://<your channel>/mystream

これでもうまくいかない場合はWirecastがAzure media servicesに対応しているので、まずはWirecast経由でストリーミングできるか確認
f:id:portaltan:20181030160236p:plain

最新動向

この投稿を書いてる途中にlow latency modeが実装されたとのこと
ポストによると8-10秒まで落とすことが出来るらしく、更に2秒以下のultra low latency modeの開発がはじまったらしい
Reduce live streaming latency for Azure Media Services live streaming – Customer Feedback for Microsoft Azure
azure.microsoft.com

さらにRTSPのサポートの開発もはじまったとのこと
Directly ingesting RTSP – Customer Feedback for Microsoft Azure

RasperryPiでリアルタイムクロック(RTC-8564NB)を使ってアラームを設定する

ラズベリーパイで以下のRTCを使う方法です
akizukidenshi.com
f:id:portaltan:20181017131108p:plain

事前準備

RTC-8564NBはラズベリーパイとi2cという通信方式で通信を行います
事前にraspi-configでi2c通信を有効にしておきます

接続&認識

上記の秋月の製品はソケット等もついているのでそのままブレッドボード等でラズベリーパイに接続します
接続したらまずはラズベリーパイがRTCをきちんと認識しているか、されていたらアサインされたアドレスの確認をします

$ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- 51 -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --  

次にRTC用のドライバを適用させます

$ sudo sh -c "echo pcf8563 0x51 > /sys/class/i2c-adapter/i2c-1/new_device"
$ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- UU -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --    
  • 8564ではなく8563になっていますが、8563はラズパイにデフォルトで入っていて、かつ8564互換になっています
  • UUは認識しているだけではなく、実際にOSが利用している状態です

時刻あわせ

初期状態のRTCは時刻が正確ではない可能性があるので、まずはシステム側の時間を使って時刻をあわせます

# (念のために)システム側の時刻を最新にupdate(要ネットワーク)
$ sudo ntpdate -v ntp.nict.jp
$ date
Wed 16 Oct 22:08:02 JST 2018

# 次に実際にRTCにシステムの時刻を反映させます
$ sudo hwclock -w

# (逆にRTCの時刻にシステム側を合わせたい場合はsオプションを使います)
$ sudo hwclock -s

# 実際に一致しているかはcオプションで確認します
$ sudo hwclock -c
hw-time      system-time         freq-offset-ppm   tick
1539694800   1539694800.003842
1539694811   1539694811.004140                25      0
1539694822   1539694821.004745                39      0

Alarm機能設定

このRTCは設定した時間で割り込みを行うAlarm機能があります
f:id:portaltan:20181017122320p:plain
端子はオープンドレインなのでAlarmがオンになるとLOWになります
f:id:portaltan:20181017122517p:plain

AF/AIEの設定

割り込みのタイミングとして、決まった時間で割り込みを行うAIE(Alarm Interrupt Enable)と、決まった周期で割り込みを行うTIE(Time Interrupt Enable)という設定があります
そして、割り込みが発生した時点でそれぞれAF(Alarm Flag)、もしくはTF(Timer Flag)がセットされます
これが1になっていると割り込みが発生しないので、都度フラグクリアをしないといけません
f:id:portaltan:20181017130955p:plain

# OSがRTCを掴んでいるので一時的に外します
$ sudo modprobe -r rtc_pcf8563

# AIEをセットし、AFをクリアします(TF/TIEも今回利用しないのでクリアします)
$ sudo i2cset -y 1 0x51 0x01 0x02 b

# 確認
$ sudo i2cget -y 1 0x51 0x01
0x02

今回はAFをクリアして、かつAIEをセットしたいので、上記のレジスタのAddress01(0x01)のbit1だけ1であとは0にします
00000010b(2進数)は16進数で0x02になのでそれをvalueとして送ります

Alarm時刻の設定

次に実際にAlarmの時刻を設定します
レジスタのAddress 09~0Cがそれぞれ以下のように対応しています

  • 0x09: Minute Alarm
  • 0x0A: Hour Alarm
  • 0x0B: Day Alarm
  • 0x0C: Weekday Alarm

例えば毎日10:30にAlarmが発生するように設定します
(RTCにはUTCで時間が入っているのでUTC基準で入力します)

# Minutes Alarm(30分)
sudo i2cset -y 1 0x51 0x09 0x30
# Hour Alarm(10時)
sudo i2cset -y 1 0x51 0x0A 0x10
# Day Alarm(0x80: AE)
sudo i2cset -y 1 0x51 0x0B 0x80
# Weekday Alarm(0x80: AE)
sudo i2cset -y 1 0x51 0x0C 0x80

注意したいのが単純に30分なら16進数に変更して30 -> 0x1Eとすればよいのではなく、レジスタテーブルに記載してある通りに入力する必要があります
f:id:portaltan:20181017133018p:plain
30分の場合は00110000bになり、16進数にすると0x30になります
(このRTCでは仕様上直感的に10進数ライクに数字を入れられるように設計されています)

0x80とはAE(アラーム制御ビット)で、これをセットすると値に関わらずにそのAddressはAlarm状態になります
今回はDayとWeekDayのAEを設定しているので、日にちや週に関わらず毎日毎週10:30になるとAlarmが発生することになります

以上で設定は完了です
設定した時間になると/INTピンがLOWになります

Hiveのselect文でカラム名を一緒に表示させる方法

hive.cli.print.header

hive> set hive.cli.print.header=true;
hive> use ${db名};
hive> select * from ${table名};

もしくはeオプションで

./hive -e "set hive.cli.print.header=true;select * from ${db名}.${table名}" > result.tsv