ポストPPAP時代をMATLABとともに

**純然たるネタ投稿です**

# 1. 概要
もう昨年のことになりますが、政府がパスワードの後送プロトコル、いわゆるPPAPの廃止を発表しました。その代替手段として提示されたものの中に「電話」があったことで、某SNS上で喧々囂々の議論が交わされたのを覚えております。

実際に電話でのパスワード伝送を行う上で、以下のような困難が考えられます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211913/fc47cd95-f700-ea4e-f2c8-24ddbc076a14.png)

そこで、今回はパスワードのビットストリームを周波数偏移変調(FSK: Frequency Shift Keying)した音声信号を送受信するアプリをApp Designerで開発しました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">電話によるパスワード伝送、「先ほどのパスワードはピィイイイヒョロロロロロディウンディウンディウンガーーーーーーーーーーーーーーです。」とか最高にクールでは。</p>&mdash; 🦓幸福🦓 (@HppyCtrlEngnrng) <a href="https://twitter.com/HppyCtrlEngnrng/status/1331152503036018688?ref_src=twsrc%5Etfw">November 24, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 2. システム構成
# 2.1 ハードウェア
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211913/6fce7066-1438-f486-9cd6-596c69b63ae1.png)

ハードウェアの構成は上図の通り、スマホのオーディオI/Oを一度PCに取り込み、MATLABで処理できるようにしています。パスワードを送信する場合はスマホのマイクにFSK変調した音声を再生し、受信する場合はスマホのスピーカーを録音することで相手側から送信される変調信号を取得します。また、PC接続のヘッドセットなどを利用して通常の会話も可能です。

# 2.2 ソフトウェア
開発したMATLAB AppのUIは以下の通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211913/adb48c51-2e6a-faa3-128d-b0438c370a5c.png)

上半分は搬送波周波数や変調レート、送信音声のサンプリング周期、使用するオーディオデバイスの設定などです。

下半分がパスワードの送受信機能で、Sendテキストボックスに送信パスワードを入力してXMITボタンを押すとFSK変調されたパスワードが通話相手に送信されます。またRCV RDYボタンを押下している間、通話先の音声を録音し、録音完了後にFSK復調を行ってReceiveテキストボックスに受信したパスワードを表示します。

# 2.2.1 送信処理
前述のとおり、送信パスワードはFSKで変調します。FSKは周波数偏移変調の名の通り、デジタルデータのビット0, 1に応じて搬送波の周波数を切り替える変調方式です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211913/4f8420f2-274d-3aeb-0fad-437192eecb02.png)
またFSKはさらに多くの周波数を用いることで、複数のビットを同時に送信することができます。ここでは2ビットを4周波数に割り当てる4FSKを採用しています。理由としては伝送速度よりも、携帯電話で正弦波状の音声を送出したとき、(おそらくノイズキャンセラにより)音が消えてしまうので帯域を広げたかったためです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211913/ef959d3f-e5a9-b173-8277-00f9229ea116.png)
App Designer内での4FSKの実装は以下の通りです。

``` matlab
%%% 4FSK変調
% msg: 送信メッセージ(char array)
% y: 変調波形(double array)
function y = make4FskSignal(app, msg)
    % 1文字ごとの変調波形をセルで割り当て
    y = cell(length(msg), 1);

    % ASCIIコードに変換
    bits = double(reshape(dec2bin(msg, 8)', 1, [])) - '0';

    % 偶数ビット、奇数ビットに分割
    bits1 = bits(1:2:end);
    bits2 = bits(2:2:end);

    % 1文字ぶんの長さの時間配列
    tc = (0:1/app.f_sample:1/app.bitrate-1/app.f_sample)';

    % 1文字ぶんの変調波形を順次代入
    for idx_c = 1:length(bits1)
        y{idx_c} = sin(2*pi*app.f_mod*(1+2*bits1(idx_c)+bits2(idx_c))*tc);
    end

    % arrayに戻す
    y = 0.5*cell2mat(y);
end

%% 送信(XMIT)ボタン押下コールバック
function XMITButtonPushed(app, event)
    % Sendパスワードテキストボックスの値を取得
    msg = cell2mat(app.SendTextArea.Value);

    % 多重送信禁止のためボタンをDistable
    app.XMITButton.Enable = false;

    if ~isempty(msg)
        pause(1.0);

        % パスワードから変調信号を取得
        y = app.make4FskSignal(msg);
        % デバッグ用にセーブ
        save("sound_sent", 'y');

        % audioplayerオブジェクトの作成
        player = audioplayer(y, app.f_sample, 16, app.OutputDeviceDropDown.Value);
        % 再生
        playblocking(player);
    end

    app.XMITButton.Enable = true;
end
```
音声の送信にはいつもの`sound`関数ではなく、`audioplayer`を用いてデバイスを指定して送信します。

# 2.2.2 受信処理
録音したパスワードは直交検波により復調します。直交検波は搬送波周波数$f_c$が既知な信号

``` math
y(t)=A \sin(2\pi f_c t+\phi)
```
に対し、同じ周波数の正弦波・余弦波との内積(の絶対値)

``` math
\mathrm{abs}\left(\frac{1}{T}\int_{0}^{T}\mathrm{e}^{j 2\pi f_c t} y(t)\mathrm{d}t\right)= A
```
を評価して信号振幅を復元するものです。($T$は符号長で変調レートの逆数)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211913/dce10375-c7a9-aa4c-a5e6-46e8a97d5d26.png)


``` matlab
%% 直交検波
% y: 録音した音声信号(double array)
% f: 搬送波周波数(double array)
function x = qdem(app, y, f)
    % 1符号(2ビット)あたりの信号長(サンプリングレート/ビットレート)
    symbol_len = round(app.f_sample/app.bitrate);

    % 1符号ぶんの時間ベクトル
    t = ((0:length(y)-1)/app.f_sample)';

    if size(f,1) > size(f,2)
        f = f';
    end

    % 複素正弦波との内積
    x = abs(movsum(exp(1j*2*pi*t*f) .* y, symbol_len/2));

    % 信号が出ている時間のみを抽出
    % 任意の搬送波の振幅が、最大振幅の5 %以上である時間
    t_fskon = sum(x, 2) > max(sum(x,2)) * 0.05;
    x = x(t_fskon, :);

    % パルスの中心のみピックアップ
    x = x(0.5*symbol_len:symbol_len:end, :);

    % 1文字を8 bits/4符号で変調しているため、4の倍数でカット
    x = x(1:floor(size(x,1)/4)*4,:);
end

%% 復調処理
% y: 録音した音声信号(double array)
function pass_demod = demodFskOrth(app, y)
    % 直交検波により搬送波ごとの振幅を取得
    x = app.qdem(y, app.f_mod*(1:4));
    % 各時刻で振幅最大の搬送波を選択
    [~, bits] = max(x, [], 2);
    % 搬送波周波数に対応した2ビットを重みづけ加算しデコード
    pass_demod = char(reshape(bits-1, [], 4)'*[64;16;4;1])';
end

%% 受信(RCV RDY)ボタン押下コールバック
function RCVRDYButtonValueChanged(app, event)
    % ボタン状態取得
    value = app.RCVRDYButton.Value;

    if value == 1 % 受信開始
        app.ReceiveTextArea.Value = "Receiving...";
        % audiorecorderオブジェクトの作成
        app.recorder = audiorecorder(app.f_sample, 16, 1, app.InputDeviceDropDown.Value);
        % 録音開始
        record(app.recorder);
    else % 受信終了
        % 録音停止
        stop(app.recorder);
        % オーディオデバイスから音声信号を取得
        y = getaudiodata(app.recorder);
        % デバッグ用に保存
        save("sound_rcvd", 'y');

        % 復調実施・表示
        pass_demod = app.demodFskOrth(y);
        app.ReceiveTextArea.Value = pass_demod;
    end
end
```
録音には`audiorecorder`関数を使用しています。

# 3. 動作例
以下の設定で動作テストを実施しました。

- 搬送波周波数：600/1200/1800/2400 Hz
- 変調レート：100 Hz
- 音声サンプル周波数：32000 Hz
- パスワード："Fear the old blood."

## 3.1 変調
パスワード"Fear the old blood."をASCIIコードに変換し、2ビットずつ4周波数に割り当てます。信号の先頭部分を下図に示します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211913/a99d94c2-8e69-e2a3-a2cf-af68988afd4f.png)
この音声信号が受信側のスマホに送られます。

## 3.2 復調
MATLABに繋がったスマホを2台用意できなかったため、受信側で音声を録音してもらい、その信号に対して復調処理を実施しました。受信側の波形は以下のようになりました。伝送により振幅に変動が出てしまっていますが、直交検波したパルス信号は綺麗に各符号を捉えています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211913/c91994fe-a24b-281a-1ab9-d16b6b0f0484.png)
受信信号の先頭部分を下図に示します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211913/e6afceb0-cb0e-50a0-fbb1-dfcc89b49a0e.png)
デコード結果も正しく送信したパスワードが得られました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211913/02f3f221-7123-bbbd-7d5f-b60984a54408.png)

実際の音声は以下の通りです。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">変調・復調した音声 <a href="https://t.co/S1Kkg8lkTk">pic.twitter.com/S1Kkg8lkTk</a></p>&mdash; 🦓幸福🦓 (@HppyCtrlEngnrng) <a href="https://twitter.com/HppyCtrlEngnrng/status/1474666871085756420?ref_src=twsrc%5Etfw">December 25, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 4. まとめ
以上、お粗末様でした。
まぁ実用するにはパリティとか諸々加味する必**いやもうチャットでいいよな！**(本末転倒)