2018年6月20日水曜日

SMPTEタイムコードのデコード(オフライン)

SMPTE Time Codeというレガシーな仕組みをご存知ですか?

映像の再生位置を示す「タイムコード("時間:分:秒:フレーム数"で表現される時間情報)」が,「オーディオ信号」として伝送されるという方式のものです.

SMPTEタイムコードのオーディオデータを生成してダウンロードできるサイトです.まずは,このサイトでオーディオデータを生成して聞いてみましょう.(※音量注意です.聴感上,非常に音が大きく感じられますし,結構不快な音かもしれません.

http://elteesee.pehrhovey.net/


このデータは後で使いますので,Bit-depthを"16-bit signed int"にするのをお忘れなく.

どうでしょうか?「プルルルルルルルルル・・・」という音が聞こえると思います.この音が,実はタイムコードを表しているのです.

例えば,iPhoneでSMPTEタイムコードを再生して,別のiPhoneのマイクロホンでそれを拾い,デコードしている例があります.

http://blog.livedoor.jp/cpiblog00465/archives/52419598.html

このように,音を出力し,それを拾うだけで時間情報の同期ができてしまうのです.

この技術は,現在でも音響だけでなく,照明や映像等が複雑に絡み合うシステムを構築する際によく使われます.例えば,音響は,Pro ToolsのようなDAWでマルチトラック再生し,同時にサウンドカードの別系統からSMPTEタイムコードを再生し出力,それを映像機器や照明機器に入力し,時間を同期して映像の再生や照明演出が進んでいくシステムを構築できます.

ディズニーランドではパレードやアトラクションでの演出の同期に,SMPTEタイムコードによる同期のテクニックが使われています.

https://ggsoku.com/2012/03/projection-mapping/

SMPTEタイムコードの音声信号の波形を拡大してみると,このようになっています.


矩形波のような,正弦波のような波形になっています.本当は,もっとしっかり矩形波になっている筈なんですが,上記ページから入手したものの波形はこんな感じになっています.しかし,実用上問題ありません.

この波形を見ると,幅の長い波と短い波があることがわかります.SMPTEタイムコードでは,幅の短い上下の変化2回で "1" ,幅の長い上下の変化1回で "0" とみなします.


こうして音声波形から0と1を読み取っていくと,二進数列ができます.この二進数列を以下のように読んでいきます.


このデコード処理をMATLABで実装してみたいと思います.(MATLABだと音声ファイルの扱いが簡単なので)

波形のステップ幅の検出は,ステップ時に信号の値が"0"を通過することに着目し,0通過時から次の0通過時までの幅を計測します.0通過は,今回のような矩形波の場合,現在のサンプルと.1つ前のサンプルとの積を求め,その符号を調べ,マイナスになれば0を通過したことがわかります.(プラス x プラスはプラスの値,マイナス x マイナスもプラスの値になりますが,プラス x マイナス,或いはマイナス x プラスはマイナスの値になります.)

Sync wordを基準にして,それが出現したら過去にバッファしたビット列の中から,必要な部分を参照し,タイムコードをデコードしていきます.

ゆくゆくリアルタイムで実装することを見越して,for文の中で1サンプルずつ参照しながらデコードを行う設計になっています.

clear;
s=audioread('smpte.wav');
del=0;%1サンプル前のオーディオデータ
isshort=0;%短いステップ内に入ったことを示すフラグ
count=0;%ステップ内のサンプル数を計測
bitcount=1;%ビットを記録する位置
bitbuf=zeros(16,1);%sync word検出用バッファ
synccode=[1;0;1;1;1;1;1;1;1;1;1;1;1;1;0;0];%sync word
timecode=zeros(1,16*4*2);%検出したビットのバッファ(いつまでもsync wordが検出できない場合に備えてバッファを必要数の2倍にしておく)
%行ベクトルにしているのは、bin2decの引数にする2進数の文字列をint2strによって生成するため
hour=0;
min=0;
sec=0;
frame=0;
for k=1:length(s)
    sgn=sign(s(k)*del);%現在のオーディオデータと1サンプル前のオーディオデータの積の符号
    if (sgn<0) %ステップ内に入った(信号が '0' を通過した)
        if isshort==0 %短いステップ内ではない
            for n=16:-1:2
                bitbuf(n)=bitbuf(n-1);%過去の15ビットをバッファリング
            end
            if count<11 %短いステップであれば現在のビットは '1'(閾値は適当)
                isshort=1;%次にもう一回短いステップがあるはずなのでフラグを '1' にしておく
                bitbuf(1)=1;
            else %ステップが長ければ現在のビットは '0'
                bitbuf(1)=0;
            end
            timecode(bitcount)=bitbuf(1);%ビットのバッファに現在のビットを格納
            bitcount=bitcount+1;%次回の格納先を1ずらす
            if bitcount>16*4*2 %次回の格納先がバッファサイズを超えたら一旦格納先をリセット
                bitcount=1;
            end
        else %2回目の短いステップ検知時は何もしない
            isshort=0;
        end
        if isequal(bitbuf,synccode)==1 %syncwordを検知したら、timecodeにはSMPTE規格のビット列が格納されている筈
            %各値を10進数に変換
            frame=bin2dec(int2str(timecode(4:-1:1)))+10*bin2dec(int2str(timecode(10:-1:9)));
            sec=bin2dec(int2str(timecode(20:-1:17)))+10*bin2dec(int2str(timecode(27:-1:25)));
            min=bin2dec(int2str(timecode(36:-1:33)))+10*bin2dec(int2str(timecode(43:-1:41)));
            hour=bin2dec(int2str(timecode(52:-1:49)))+10*bin2dec(int2str(timecode(58:-1:57)));
            sprintf('%d:%d:%d:%d',hour,min,sec,frame)
            bitcount=1;%バッファの格納先をリセット
        end
        count=0;%ステップ数をリセット
    end
    count=count+1;
    del=s(k);%現在のオーディオデータを保持
end

0 件のコメント:

コメントを投稿