あるべるのIT関連メモ

素人エンジニアが何かするときに困ったものなどを参考程度にメモっておこうかと

nilとオプショナル(Optional)型

間が空くといつも忘れるのでいい加減まとめておく。

オプショナル型とは?

Swiftでは通常の変数にnilを代入することは許されていない。nilを代入するためにはオプショナル(Optional)型というもので元のインスタンスをラップする必要がある。イメージは次のような感じか(?)

Optional<T> {
    union {
        T t;
        Nil n;
     }
}

オプショナル型の宣言

オプショナル型の変数は、型の最後に'?'または'!'をつけて宣言する。通常オプショナル型というと'?'で宣言したものを指し、'!'で宣言したものは暗黙的アンラップ オプショナル型になるらしい。両者の違いについては事項で。

var str1: String?
var str2: String!

オプショナル型変数の使用

オプショナル型の変数はアンラップしないと使うことができない。アンラップの仕方は大きく3つあるので1つずつ眺めていく。

強制アンラップ

オプショナル型の変数は使用時に明示的にアンラップ(変数の後ろに!をつける)してやる必要があるのだが、暗黙的アンラップ型で宣言した変数は使用時に自動で=暗黙的にアンラップされる。 ここらへん、C/C++のポインタのように同じ記号の意味が変わるのでイヤらしい。

var str1: String? = "世界"
print("こんにちは" + str1!)     // 変数に!をつけて明示的にアンラップ

var str2: String! = "World"
print("Hello " + str2)        // 特別な操作はせずそのまま使える(自動的にアンラップされる)

nilのチェック付きアンラップ

var hello: String? = "Hello"
print(hello)     // -> Optional("Hello") と表示される

アンラップする際に、値がnilである可能性があるためそれをチェックするための構文が存在する。 以下のコードはhelloがnilだった場合、if文のブロック内のコードは実行されない。

if let hello = hello {
    print(hello)    // -> Hello と表示される
}

オプショナルチェイニング

オプショナル型で宣言したインスタンスのメンバーにアクセスする際にいちいちnilかどうかをチェックするなんてめんどくさいことをしなくてもすむ記法がある。またオプショナルチェイニングでアンラップしたものが返す値はnilでない限りオプショナル型でラップされたものになる。

var obj1: MyClass? = MyClass()
var obj2: MyClass?

obj1?.method()    // obj1 はnilでないのでmethod()が呼ばれる
obj2?.method()    // obj2 はnilなのでmethod()は呼ばれずにnilが返る

obj1?.property    // Optional(property)が返る

感想

オプショナル型を理解してきちんと使えば、nilが紛れ込みにくい堅牢なコードになるはずだが、少し離れると毎回記号の意味を忘れてXcodeのエラーに踊らされてしまう。次からは?や!の意味を忘れても落ち着いてこの記事に帰ってくることでnilのない世界に少しでも近づけるはず。

MP4(コンテナ) - 2.trak

前回の記事でMP4はBox(またはAtom)要素からなる木構造を持つということでBoxの定義自体について説明した。 albel06.hatenablog.com

今回はmp4ファイルの大枠のコンテナ構成を記述する。

chunk

MP4コンテナを説明する前に、メディアデータの記録方法について説明する。MP4に記録される動画や音声データは、全体のデータを一定の時間のデータに分割したchunkという状態で記録される。下記のようにvideoのデータとaudioのデータが交互に順番に記録される。1 chunkの大きさに規定はなく、メーカーやソフトによりまちまちである。
|video|audio|video|audio|...|video|audio|

moov Box

メタデータを記録するBox。ファイルのトップレベルに置かれ、通常ファイルの先頭の方か末尾の方に記録されるが、実際に記録する位置に関する決まりはない。ただしストリーミング配信するためにはvideoやaudioのメディアデータよりも前に記録されている必要がある(理由は後述)。

Sample.mp4
  ├ ftyp
  ├ moov
  │   ├ mvhd
  │   ├ trak
  │   └ trak
  └ mdat

trak Box

1つのtrackに関する情報を記録するBox。videoやaudioのメディアデータがそれぞれ1 trackとなり、通常の動画ファイルではmoov以下にtrak(video)とtrak(audio)と2つのtrak Boxが記録される。
videoやaudioのデータはH.264AACなど圧縮されているものが多い。圧縮されているということはデータ自体は可変長であり、chunkの大きさも一定ではなくなる。trak Boxにはvideoやaudioのメディアデータがどのように記録されているかを記述し、再生する側はtrakの情報からメディアデータをシークし再生する。
trak自体はmoov Boxの下に記録されており、上記のとおりtrakをparseして初めてvideoやaudioのデコードが可能になるため、ストリーミング配信をするためにはmoov(の下のtrak)がファイルの先頭に配置され、一番最初にダウンロードされる必要がある。
先にあげたコンテナ構成にtrak以下を少し追記したものが下記で、さらにmdia以下にchunkの詳細が記述されていくのだがそれはまた次回に。。

Sample.mp4
  ├ ftyp
  ├ moov
  │   ├ mvhd
  │   ├ trak
  │   │   ├ tkhd
  │   │   ├ edts
  │   │   └ mdia 
  │   └ trak
  └ mdat

MP4(コンテナ) - 1.Box構造

出自

Apple QuickTime format (mov) を元にISO/IEC 14496-1が策定され のちにISOベースメディアファイルフォーマットとしてISO/IEC 14496-12を策定。 MP4はそれを元に拡張しISO/IEC 14496-14 (Part 14: MP4 file format)で標準化された。

Box構造

MP4はBoxという要素からなる木構造を持つ。元になったmovではAtomと言われている。 Boxは先頭の4byteで自身のサイズを、続く4byteでBox Typeを表す。

aligned(8) class Box (unsigned int(32) box type, 
                      optional unsigned int(8)[16] extended_type) {
    unsigned int(32) size;
    unsigned int(32) type = boxtype;
    if (size == 1) {
        unsigned int(64) largesize;
    } else if (size == 0) {
        // box extends to end of file
    }
    if (boxtype == 'uuid') {
        unsigned int(8)[16] usertype = extended_type;
    }
}

代表的なBoxとして
moov: メタデータを格納
mdat: 動画や音声などの実データを格納
がある。

4GB超えファイルの扱い

最近では4K動画やハイフレームレートの動画などビットレートの高い動画が増え、4GBを超える動画が記録されるようになっている。 動画のデータは'mdat'というBoxに書かれるのだが、上述のBoxの先頭に記録できるsizeは32bit分で4GBを超えると足りない。 そのときのためにextended sizeというものが用意されていて、Boxの先頭に記録するsizeを1(*)とし、typeのあとに64bit符号無し整数としてlargesize=Boxサイズを記録できるようになっている。
(*)size=1のBoxは存在できない。Boxは最低でもsize+typeで8byte以上ある。

Full Box

Boxを拡張してバージョン番号とflagフィールドを追加したFull Boxというものがあり、実際に各種データを保存するBoxはFull Boxであることが多い様子。
バージョン番号とflagフィールドの意味は各Boxの仕様に記載されている。

aligned(8) class FullBox(unsigned int(32) boxtype, unsigned int(8) v, bit(24) f)
    extends Box(boxtype) {
    unsigned int(8) version = v;
    bit(24) flags = f;
}

ユーザー定義Box

Boxの定義にはoptionalでextended_typeというものがあり、ユーザーがuuidを設定することでユニークなユーザー定義Boxを使用することができる。そのときのBoxタイプは'uuid'とし、typeの後ろに16byteのuuidを記録する。uuid Box以下、残りの領域は自由に定義が可能。

実際のデータとコンテナの関係はこちら

albel06.hatenablog.com

Swift - ダウンキャスト(as, as!, as?)

ほんとはオプショナル型の〜とか説明が必要なのだけど

  • as
    アップキャストなど確定的なキャストに使用。

  • as!
    強制キャスト。C++でいうところのreinterpret_cast。
    強制キャストなので実行時エラーを起こす可能性がある。

  • as?
    C++でいうところのdynamic_cast。
    キャストに失敗した場合はnilを返す。

とりあえずざっくりとC++プログラマ向けの対比。

git branch のよく使うオプション

リモートブランチも含めたブランチを一覧表示

$git branch -a
* master
  remotes/origin/featuredev
  remotes/origin/master

-a オプションを使用する。

リモートブランチをチェックアウトする

$git checkout -b local_branch origin/remote_branch

-b オプションを使用することで"local_branch"ブランチの作成と “origin/remote_branch"のチェックアウトを同時に行う。

ローカルブランチをリモートブランチとしてpushする

$git push origin local_branch

ブランチの削除

  • マージ済みローカルブランチの削除
$git branch -d local_branch

マージが完了したブランチは、一度削除した方が精神衛生上よろしい気がする (ブランチの生存期間が長くなるほど問題が起こる気がする)。

  • ローカルブランチの削除(強制)
$git branch -D local_branch

何かの検討用にブランチ切って検討終了後にそのまま消したりするのでよく使う。

  • リモートブランチの削除
$git push --delete origin remote_branch

または

$git push origin :remote_branch

こちらの意味は以前何かで見たけど忘れた (確か、ローカルのブランチを指定しない=空のブランチを"remote_branch"にpushするから削除になるとかだったと)。

pthreadの使い方

pthreadについて理解したことのメモ

pthreadとは

POSIXスレッドとはスレッドのPOSIX標準である。
スレッド生成やスレッド操作のApiを定義している。
POSIXスレッド - Wikipedia

・・・つまりマルチスレッドプログラミングができるようになります。

gccオプション

gccコンパイルするためには"-pthread"オプションをつけます。

新しいスレッドを作成する

pthread_create()関数を使います。

int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)

2つめのAttribute引数の意味はよく分かっていませんが、NULLを指定することでデフォルトとなるようです。

#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>

void *thread_func(void *param)
{
    printf("thread_func started\n");
}

int main(int argc, char *argv[])
{
    pthread_t thread;
    int ret = 0;
    ret = pthread_create(&thread, NULL, thread_func, NULL);
    if (ret != 0) {
        printf("pthread_create() failed.\n");
    }
    printf("pthread_create() called.\n");
    pthread_join(thread, NULL);    // threadの終了を待つ.
                         
    return 0;
}

出力

>./a.out
pthread_create() called.
thread_func started

main関数でpthread_create()でthreadを作成、指定したthread_funcが呼び出されています。

排他

スレッドの排他を実現するためにmutex(MUTual EXclusion)が用意されています。

int pthread_mutex_lock(pthread_mutex_t &mutex)
int pthread_mutex_unlock(pthread_mutex_t &mutex)

mutexでロックされている区間では同時に1つのスレッドしか操作ができません。
ロックしたら必ずアンロックもセットで行います。

pthread_mutex_t mymutex;
void process(void)
{
    pthread_mutex_lock(&mymutex);
    // 何か処理.
    pthread_mutex_unlock(&mymutex);
}

あるスレッドにてmymutexがロックされている間
他のスレッドはmymutexのロックの取得ができないため
pthread_mutex_lockで待ち状態になります。

条件変数

mutex区間で何か条件の成立を待ちたい、そんなときはpthread_cond_wait()を使用します。
mutexのlock/unlockと同じように
pthread_cond_signal()(またはpthread_cond_broadcast())とセット使用します。

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
int pthread_cond_signal(pthread_cond_t *cond);

これがめちゃ難解でした(のでこれを書いている)。

まず条件の成立を待つ側はpthread_cond_wait(&mycond, &mymutex)を呼び出します。
pthread_cond_wait()は呼ばれるとまず最初にmymutexのロックを解除します。
こうして他のスレッドがmymutex区間にアクセスできるようにします。
そして条件mycondを待ちます。

ここでmymutexのロックが解除されているので
別のスレッドはmymutexのロックを取得して処理を行います。
それによってmycondが成立した場合にpthread_cond_signal(&mycond)を呼び出します。

するとmycondを待っている最初のスレッドがこの時点で目覚めます。
またこのときpthread_cond_wait()はmymutexのロックを再取得します。
mymutexのロックが取得できた後に初めて応答し、最初のスレッドの処理を続行させます。

// スレッド1がmymutexのロックを取得
pthread_mutex_lock(&mymutex);

// ある条件が整うまで以下を呼び出し待つ
// ここでmymutexのロックは解除される(pthread_mutex_unlock(&mymutex)が実行される)
pthread_cond_wait(&mycond, &mymutex);

// スレッド2がある条件を成立させたのでスレッド1を起こす
// このときpthread_cond_waitはpthread_mutex_lock(&mymutex)が可能になるまで応答しない
pthread_cond_signal(&mycond);

こちらを参考にしました。
一般的なスレッド: POSIX スレッドの説明: 第3回