ブラウザやCLIでaudioファイルを逆再生

# 背景

[CTF勉強会](https://weeyble-security.connpass.com/event/121212/)のStegano演習で[audioファイル](https://drive.google.com/file/d/1XTYumod3s_K4YYNzedSx4S73qM1pIx0X/view)からflagを見つける問題が出た。

audio編集ソフトを入れて解いてください、とのことだったが、人によっては持ってきたPCに自由にフリーソフトを入れられない。
そのためマルチツールであるWebブラウザで解けないか、あるいはOSデフォルトで入っているCLIで解けないか、模索してみた。


なお、答えはタイトルでネタバレしてるので悪しからず。

# 要件

- playbackRate(N倍速再生)と逆再生ができる
- ブラウザはChrome/Firefoxで動けば良しとする

# ブラウザ
## ✖️[HTMLMediaElement](https://developer.mozilla.org/ja/docs/Web/API/HTMLMediaElement)

WebAudioAPIより簡単に扱えるが、playbackRateは設定できるものの逆再生のAPIは用意されていない。
そのため自力で実装するしかないが、現状のAPIでは厳しかった。

下記は`audio.duration`で全体の秒数を取得し、そこから`pitch`秒刻みで後ろから再生してみたスクリプト。
答えを知ってると少し聞きとれる気もするが、聞き取るのは難しかった。

```js
const audio = new Audio('http://localhost/~darai0512/Stegano.wav');
audio.onloadeddata = () => {
  const pitch = 0.1;
  let endOffset = audio.duration;
  let startOffset = endOffset - pitch;
  audio.ontimeupdate = () => {
    if (audio.currentTime < endOffset) return;
    audio.pause();
    startOffset -= pitch;
    if (startOffset < 0) return;
    endOffset -= pitch;
    audio.currentTime = startOffset;
    audio.play();
  };
  audio.currentTime = startOffset;
  audio.playbackRate = 0.8;
  audio.play();
};
```

### W3C [Media Fragments URI](https://www.w3.org/TR/media-frags/#naming-time)

`HTMLMediaElement.currentTime`に秒数を入れることでその秒数から再生開始したが、`<audio URL>#t=<startOffset>,<endOffset>`でも区間指定の再生が可能


## ○[Web Audio API](https://developer.mozilla.org/ja/docs/Web/API/Web_Audio_API)

audioファイルをArrayBufferで受け、[decodeAudioData()](https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/decodeAudioData)でmetadataを分離し[Array.prototype.reverse()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse)により逆再生する

demo: https://darai0512.github.io/audio-reverse-play/

- localからaudioファイルを読み込み`fileReader.readAsArrayBuffer()`でArrayBufferに変換する
  - WebサーバーがたてられるならXHRでArrayBufferを作っても良い
- そのまま`ArrayBuffer.reverse()`するとaudio以外のmetadataも反転されファイル形式がおかしくなるため、`AudioContext.decodeAudioData()`にかける
- `connect()`=>`start()`

```html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
<body>
<p>WebAudioAPI playbackRate: <input type="text" id="rate" size="3" value="0.7"/></p>
<input type="file" onchange="handleFile(this.files)">
<button onclick="play(false)">audio play</button>
<button onclick="play(true)">reverse audio play</button>
<script>
const context = new AudioContext();
const fileReader = new FileReader();
let buffer = null;
fileReader.onload = () => {
  context.decodeAudioData(fileReader.result, (b) => buffer = b);
}
const handleFile = ([file]) => fileReader.readAsArrayBuffer(file);
let reversed = false;
const play = (reverse) => {
  if (buffer === null) return;
  if (reverse !== reversed) {
    for (let i=0;i<buffer.numberOfChannels;i++) {
      buffer.getChannelData(i).reverse();
    }
    reversed = !reversed;
  }
  const source = context.createBufferSource();
  source.buffer = buffer;
  source.playbackRate.value = ((r) => 0 < r ? r : 1)(parseFloat(document.getElementById('rate').value));
  source.connect(context.destination);
  source.onended = () => source.stop(0);
  source.start(0);
};
</script>
```

# CLI
## ✖️afconvert

- OSX標準のaudio converter
- codec変換に優れている
  - `afconvert -hf`で扱えるcodec一覧がみれる
  - `afconvert -f <output codec> <input> <output>`
  - しかしエラー（変換できない）も多い......
- reverse変換optionはなさそう

ちなみにaudioファイルのmeta情報を見るのに便利な`afinfo`、CLIからrate指定でaudio再生できる`afplay`もOSX標準搭載

```sh
$afinfo Stegano.wav
File:           Stegano.wav
File type ID:   WAVE
Num Tracks:     1
----
Data format:     1 ch,   8000 Hz, 'lpcm' (0x0000000C) 16-bit little-endian signed integer
                no channel layout.
estimated duration: 3.259625 sec
audio bytes: 52154
audio packets: 26077
bit rate: 128000 bits per second
packet size upper bound: 2
maximum packet size: 2
audio data file offset: 44
not optimized
source bit depth: I16
----
```

## ○ffmpeg

- 標準搭載ではないがunix系で使えてインストールしている人も多いであろう鉄板
  - OSXなら`brew install ffmpeg`
- ムービーを扱う紹介記事が多いが、audioのみももちろん変換可能
  - reverse変換したい場合、videoなら`-vf reverse`、audioなら`-af areverse`

```sh
$ffmpeg -i Stegano.wav -af areverse reverse.wav
$afplay -r 0.7 reverse.wav
```

# GUIフリーソフト
## ✖️QuickTime Player

旧バージョンにはmenuバーの中にそうした機能があったが、手元の10.5ではショートカットキーで逆再生・倍速再生を実現できた

|内容|ショートカット|
|---|---|
|再生|l|
|逆再生|j|

スロー再生はapplescriptで実現可能（ただし逆再生には利かない。スクリプトエディタは`Cmd + space`のSpotlight検索で「applescript Editor」で開ける）

```applescript
tell application "QuickTime Player"
  set rate of document 1 to 0.5
end tell
```

しかし逆再生しても答えが聞き取れなかった......