ASMPだからこそ効果的!SPRESENSEのWAVやMP3に対応したソフトウェア・エンコーダ/デコーダ徹底解説


図図1:今回はサブコア上でWAVやMP3のソフトウェアエンコーダ/デコーダを実行します

IoTサービスを支えるエッジデバイスには、特定のイベントをトリガーとして音声データ(WAVやMP3など)を再生する、または周囲の音を収集し音声データとして保存する機能が度々求められます。旧来のシステムではエンコード(音声/オーディオ入力からデータへの変換)やデコード(データから音声/オーディオ出力への変換)専用に設計されたハードウェア・エンジンを搭載することにより、ハードウェア実装のメリットである高い性能を活かしてスムーズな再生/録音機能を実現していました。しかし、IoTサービスのエッジデバイスの場合、常に再生・録音を繰り返さない(特定の条件に合致した時のみ再生・録音する)ため、こうした専用ハードウェアの採用はコストの低減を阻害する、悩ましい課題です。

そこで、今回の初心者講座では、SPRESENSE SDKの提供しているソフトウェア・エンコーダ/デコーダにフォーカス。ソフトウェア・エンコーダ/デコーダの特徴、ASMPアーキテクチャとの高い親和性、使い方を紹介いたします(図1)。

図

▼目次

ソフトウェア・エンコーダ/デコーダの特徴

WAV形式やMP3形式の音声データを扱うためには、エンコード/デコード処理が必要となります。特に、MP3形式の場合は音声を圧縮して保存しているため、エンコードでは圧縮処理、デコードでは展開処理を高ビットレートのオーディオデータに対して適用しなければならないため、高い演算処理性能が不可欠です。そのため、システムの根幹を担うCPUがエンコードやデコードの処理を担当すると、他の処理を開始できず高いレスポンス性や優れたリアルタイム性の実現が難しくなります。

Wikipedia - MP3

この課題を解決するため、旧来のシステムではエンコード/デコード処理をハードウェアで実装した「ハードウェア・エンコーダ/デコーダ」を搭載していました。ハードウェア実装のエンコーダ/デコーダは、システムの根幹を担うCPUの使用率を大きく低減することができます。なお、WAVやMP3を扱う処理は定型的なためブロック化し、切り出しやすい機能です(再利用しやすい開発資産です)。

一方、ハードウェア・エンコーダ/デコーダには、WAVやMP3を処理していない間はハードウェアが活用されないという課題があります(図2)。

図
図2:ハードウェア・エンコーダ/デコーダは音声処理中のみ効果を発揮する

これらの課題を解決するため、SPRESENSEは汎用CPU(サブコアであるCortex-M4)上でWAVやMP3を処理できる「ソフトウェア・エンコーダ/デコーダ」機能を提供しています。ソフトウェア・エンコーダ/デコーダをサブコア上で実行することにより、システムの根幹を担うメインコアに負荷をかけることなく音声処理を実現。また、サブコアは汎用CPUであるため、WAVやMP3を処理していない間には他のタスクを実行するリソースとして活用することができます(図3)。さらには、SPRESENSE SDKはWAVやMP3のエンコーダ/デコーダをコンパイル済みの実行ファイル形式(ELF形式)として提供しているため、ソフトウェア・エンコーダ/デコーダの開発工数を必要としません(図4)。

図
図3:ASMPアーキテクチャ+ソフトウェア・エンコーダ/デコーダ

ファイル名 提供する機能 形式
MP3DEC MP3オーディオの再生処理 ELF形式(実行ファイル形式)
MP3ENC MP3オーディオの録音処理 ELF形式(実行ファイル形式)
SRC WAV、PCMオーディオの録音処理 ELF形式(実行ファイル形式)
WAVDEC WAVオーディオの再生処理 ELF形式(実行ファイル形式)

図
図4:Spresense SDK同梱のソフトウェア・エンコーダ/デコーダ(ELF形式)

マルチコアを活用し音声を再生・録音する

それでは早速、SPRESENSEのサブコアを使って、WAVやMP3のソフトウェア・エンコーダ/デコーダを実行してみましょう。内部処理は記事後半にて詳解いたします。

必要となる機材

本記事にて扱う音声の録音・再生には、SPRESENSE Main Boardに加えて、オーディオの入出力端子を備えたSPRESENSE Extension Board(図5)、SPRESENSE Extension Boardのマイク入力端子に4chのアナログマイク(WM-61A相当品)を簡単に接続できるAPS学習ボードが必要です。なお、音声はSPRESENSE Extension Boardのヘッドホンジャックから出力されます。

図

図
図5:SPRESENSE Extension Boardに搭載されたオーディオ入出力端子

実行するサンプル・アプリケーションの準備

ソースコードの入手

今回はサンプル・アプリケーションとして「APS-Academy(Multicore) #7 Sample Code」を利用します。下記の手順に従ってソースコード一式をPCにダウンロードしてください。

		# SPRESENSEのgitのリリースリストが表示されます
		$ git tag -l
		v1.0.0
		v1.0.1
		...
		v1.4.0.is.4.0
		# ソースコードから"1.4.0.is.4.0"という名前のブランチを生成します
		$ git checkout -b 1.4.0.is.4.0 refs/tags/v1.4.0.is.4.0
		Switched to a new branch '1.4.0.is.4.0'
		# このように切り替わっています
		$ git branch
		* 1.4.0.is.4.0
			master
		# 1.4.0.is.4.0の初期状態にリセットします
		$ git reset --hard HEAD

入手したソースコードのビルド方法

ソースコード入手後、「再生機能」を試す場合はソースコード中の「spresense」フォルダを右クリックし「Spresense:SDKコンフィグ」-「新規作成」-「Examples」-「audio_player」を選択(図6)、SDKコンフィグを保存した後、「Spresense:アプリケーションのビルド」-「Spresense:ビルドと実行」を実行します。

図

図
図6:SDKコンフィグ方法(再生機能を提供するサンプル・アプリケーション)

「録音機能」はソースコード中の「spresense」フォルダを右クリックし「Spresense:SDKコンフィグ」-「新規作成」-「Examples」-「audio_recorder」を選択(図7)、SDKコンフィグを保存した後、「Spresense:アプリケーションのビルド」-「Spresense:ビルドと実行」を実行します。

audio_playerとaudio_recorderを同時に選択した場合のビルドエラーについて

図
図7:SDKコンフィグ方法(録音機能を提供するサンプル・アプリケーション)

SDカードの準備

音声データ(再生音源、録音結果)はSPRESENSE Extension Boardに挿入したmicro-SDカードに保存されます。また、SDカードには、事前にソフトウェア・エンコーダ/デコーダの実行ファイルも格納する必要があります。

SDカードをフォーマットする

まず「FAT32形式のSDカード」をご用意ください。

2GB以下のSDカード、もしくは2GB以下のパーティションが作成されているSDカードの場合、FAT16形式でフォーマットされていることがあるため、注意が必要です。SDカードのフォーマットは、Windowsのスタートメニューを右クリック、「ディスクの管理」から確認することができます(Windows10の場合:図8)。

図
図8:「FAT32」フォーマットのSDカードを準備する

SDカード上にオーディオの再生・録音に必要なファイルを格納する

次に、SDカード上に「再生するオーディオデータ(MP3形式)」「再生リスト(csv形式)」「ソフトウェア・エンコーダ/デコーダの実行ファイル」を格納します。なお、録音するaudio_recorderサンプル・アプリケーションを実行すると「録音したデータ(WAV形式)」がSDカード上に生成されます。以下のリンクから必要なファイル一式を入手し、SDカードに格納してください(格納後、図9のフォルダ構成となるようにしてください)。

図
図9:SDカードに格納するデータ

JTAG-ICEデバッガの接続(オプション)

JTAG-ICEデバッガとVisual Studio Codeを組み合わせ、実行中のプログラムを追いかけることで、本記事やサンプル・アプリケーションをより深く理解することができます。JTAG-ICEデバッガを利用するには、SPRESENSE Extension BoardのCN1に、JTAG-ICEデバッガ(LPC-Link2)を接続してください。詳細は「SPRESENSEハードウェアドキュメント」をご参照ください。

図
図10:JTAG-ICEデバッガを接続することにより、メインコアがどのようにソフトウェア・エンコーダ/デコーダを制御しているのかをVisual Studio Code上で確認することができる

録音データの確認(audio_recorder)

audio_recorderアプリケーションをnshから実行すると、録音が開始され、10秒間録音した後にSDカード上に音声ファイルが保存されます(図11)。

図
図11:audio_recorderの実行(nsh上)

録音されたデータ(WAVファイル)の構造

audio_recorderは、10秒間マイクからステレオ音声(左(L)ch、右(R)ch)を収集、WAV形式の音声データをSDカードのRECフォルダ内に保存するサンプル・アプリケーションです。WAV形式のファイルは、ヘッダ情報が先頭に格納され、その後ろにL(左マイクからの録音データ)とR(右マイクからの録音データ)がペアで記録されている扱いやすいデータ構造となっています(図12)。

Wikipedia(EN) - WAV

図
図12:保存されたWAVファイルの構造

ヘッダには下記の情報が保存されています。すべてのフィールドはリトル・エンディアン形式(マルチバイトデータを最下位バイトが先頭として記録する形式)で記録されています。

フィールド番号 意味
1 固定文字列 "RIFF"
2 本フィールド以降のサイズ 0x001C2024
3 固定文字列 "WAVE"
4 固定文字列 "fmt "
5 フォーマットのチャンク数 0x10(リニアPCM)
6 データフォーマット 0x01(PCM)
7 チャンネル数 0x02(L+R)
8 サンプリング周波数 0xDD80
9 Byte/s 0x02EE00(192kB/s)
10 全チャネルの合計バイト数 0x0004(4Byte)
11 1chのビット数 0x0010(16bit)
12 固定文字列 "data"
13 音声データのサイズ 0x001C2000
以降、データ L(16bit)+R(16bit) -

録音データの活用方法

WAV形式のファイルは圧縮されていない単純な構造となっているため、簡単に録音データを加工・解析することができます。もちろん、SPRESENSEのマルチコア構成を活かし、データの加工/解析処理を別のサブコアに担当させることも可能です。

例えば、SPRESENSEに内蔵された高品質なオーディオ入力機能を活用し、微弱な音を録音。LチャンネルとRチャンネル間の相関を解析することにより、音源方向の推定などが可能です。

なお、LチャンネルとRチャンネルのデータそれぞれを別々のサブコア上で解析したい場合、ステレオ形式のデータはLとRのデータが近接しているため、サブコアからのメモリアクセスが衝突し、バスアービトレーション(データ通信バスの調停処理)が発生。期待する性能を得られないことがあります。そのため、スムーズな処理を実現するためには、2個のマイクのデータをモノラル形式として別々のメモリタイルに記録するといった工夫が必要です(図13)。

図
図13:音声データ解析時のバスアービトレーション回避

audio_player|メインコアからサブコア上のデコーダを制御する仕組みを理解する

続いて、サブコア用の実行ファイルがどのように制御され音声処理機能を提供しているのかを、JTAGデバッガのステップ実行を利用し、紹介いたします。

アプリケーションを開始する

audio_playerでは、下記ソースコードによりパラメータを設定しています。実行環境にあわせて調整してください。なお "/mnt/sd0" SPRESENSE Extension Boardに挿入されているmicro-SDカードとなります(非対応SDカードの場合、sd0はSPRESENSE上で非表示となります)。

アプリケーションをビルドしデバッグを開始すると、RTOS(NuttX)のエントリポイントでプログラムが一時停止します(図14)。プログラムを再開し、ターミナルに「audio_player」を入力し、Enterキーを押すことにより、サンプル・アプリケーションを実行できます。

図
図14:RTOS(NuttX)のエントリポイント

サブコア上にソフトウェア・デコーダを展開・起動する

プログラム開始後、まず、メインコア側の準備処理(app_init_libraries)により、サブコアと通信するためのメッセージキューやオーディオ処理用のメモリプールなどのリソースを初期化。続いて、サブコア上にて実行するソフトウェア・デコーダの起動処理(app_create_audio_sub_system)によりサブコア上でタスクが開始されます。

audio_playerの動作シーケンス

app_create_audio_sub_systemにブレークポイントを設定し、ステップイン実行することにより、audio_playerからSDKの提供する機能「AS_CreateAudioManager」が実行されタスクが生成されている様子を見ることができます。

図
図15:まず、サブコア上にソフトウェア・デコーダ実行用タスクを起動する(task_create)

サブコア上のMP3ソフトウェア・デコーダを制御する

上記の処理により、メインコアおよび、サブコア上のMP3データのソフトウェア・デコーダが利用可能な状態となっています。メインコアからサブコアへ再生、停止、音量調整等の処理の依頼はメッセージキューを介して行われます。メインコアからサブコアへのメッセージ送出は、SDKの提供する機能「AS_SendAudioCommand」により解決されます。

メインコアからサブコアへと制御を依頼できるAPIを下記に示します。これらのAPIの組み合わせによって、WAVやMP3のファイルフォーマットに初めて触れるユーザーでも、短い工数で再生機能や録音機能を備えたIoTエッジデバイスを開発することができます。

L589 - ソフトウェア・デコーダの制御API

static bool app_power_off(void)
{
	AudioCommand command;
	command.header.packet_length = LENGTH_SET_POWEROFF_STATUS;
	command.header.command_code  = AUDCMD_SETPOWEROFFSTATUS;
	command.header.sub_code      = 0x00;
	AS_SendAudioCommand(&command);
	...  

static bool app_set_ready(void);
static bool app_get_status(void);
static bool app_init_output_select(void);
static bool app_set_volume(int master_db);
static bool app_set_player_status(void);
static int app_init_player(uint8_t codec_type,
                           uint32_t sampling_rate,
                           uint8_t channel_number,
						   uint8_t bit_length);
static int app_play_player(void);
static bool app_stop_player(int mode);
static bool app_set_clkmode(int clk_mode);

図
図16:初期化や再生にあわせてサブコアのタスクへメッセージを送出する

まとめ

SPRESENSEのサブコアを活用することにより、これまでハードウェア実装が必要とされていた様々な機能をサブコア上で実現。機能が利用されていない間は、別の機能を実行させる、またハードウェアリソースを余らせることなく使い切る「省ハードウェアリソース」のIoTシステムを構築できます。

なお、本記事にて紹介したSonyから提供されているWAVやMP3のエンコーダ/デコーダだけでなく、3rd-Party製の暗号化機能や復号機能、正統性の検証機能といったセキュリティに関する実行ファイルも登場しています。是非ご活用ください。

補足

複数のサンプルアプリケーションを同時にビルドする際の注意点

Spresense SDK開発環境では、ビルドスクリプトにより、メインコア用の実行ファイル(nuttx.spk)は、RTOS(NuttX)も含めて1個のバイナリデータとして生成されます。そのため、SDKコンフィグにて「examples/audio_player」と「examples/audio_recorder」を同時に選択すると、同じ名前のオブジェクト(pool_numなど)が同じ名前空間(MemMgrLite::〜)に2個定義されるため、関数やオブジェクトを一意に特定できずビルドエラーが発生します(図17)。

実際の開発では、関数・クラス・オブジェクト等が同名である必要はないため、録音機能と再生機能を両方含むアプリケーションも実現可能です(図18)。

図
図17:複数のオーディオ機能を同時にビルドした場合、名前重複のエラーが出力される

図
図18:名前の衝突を解決することにより機能を統合できる