ROS2で音声ファイルを再生する方法

# はじめに
　ROS2で音声ファイルを再生する方法を紹介する。

# 動作確認環境
* ROS2 Foxy
* audio_commonパッケージ
  * バイナリパッケージは提供されていない (2024.12.17時点)
  * https://github.com/ros-drivers/audio_common/commit/06c280a798596db952538772918e8c7d44a6e5a8
    * ハッシュ
06c280a798596db952538772918e8c7d44a6e5a8  

# 方法

　[audio_commonパッケージ（ros2ブランチ）](https://github.com/ros-drivers/audio_common/tree/ros2)を使う。audio_commonパッケージは、音声再生、録音などの音声に関連する機能が実装されている。内部では、GStreamerを利用している。
 
## インストール
　[audio_commonパッケージ（ros2ブランチ）](https://github.com/ros-drivers/audio_common/tree/ros2)は、バイナリパッケージは提供されていない（2024.12.17時点）のでビルドする必要がある。

ROS2がインストールしてある環境で、以下のコマンドを実行してビルドする。

例：ワークスペースが```~/ros2_ws```の場合

```bash
cd ~/ros2_ws/src
git clone https://github.com/ros-drivers/audio_common
git checkout ros2
cd ~/ros2_ws
sudo apt update
rosdep install --from-paths src --ignore-src -r -y 
colcon build --symlink-install --packages-up-to sound_play
```

## 使い方

```soundplay_node```を起動して、音声ファイルの情報を付与したトピック(/robotsound)を受信させて音声を鳴らす。```soundplay_node```の仕様は、ROS1と同様である。詳細はROS1の[soundplay_nodeのドキュメント](https://wiki.ros.org/sound_play)を参照。

### 音声再生用のノードの起動

```bash
ros2 launch sound_play soundplay_node.launch.xml
```

起動時に設定できるノードパラメータは以下の通りである。

| パラメータ | 概要| デフォルト | 備考|
|:-:|:-:|:-:|:-:|
|loop_rate | 連続再生の周期(Hz) | 100| [参考](https://github.com/ros-drivers/audio_common/blob/06c280a798596db952538772918e8c7d44a6e5a8/sound_play/scripts/soundplay_node.py#L220) |
| device| 再生デバイス| 'default' |  [参考](https://github.com/ros-drivers/audio_common/blob/06c280a798596db952538772918e8c7d44a6e5a8/sound_play/scripts/soundplay_node.py#L221)|
|  default_voice |再生音声| 'default_voice'| [参考](https://github.com/ros-drivers/audio_common/blob/06c280a798596db952538772918e8c7d44a6e5a8/sound_play/scripts/soundplay_node.py#L222-L223)|

再生デバイスを指定する場合は、以下のようにする。

```bash
ros2 launch sound_play soundplay_node.launch.xml device:=hw:1,0
```
デバイスの指定方法は、ALSAと同じになる。詳細は、以前書いた[「Ubuntuで音声ファイルを再生する方法」](https://qiita.com/ntrlmt/items/b65d9db4d1a3cdb089db)を参照。



### 音声ファイルの再生

　再生したい音声ファイル情報を持ったトピックをpublishすることで音声ファイルを再生させることができる。soundplay_nodeは、```/robot_sound```トピック（型：[SoundRequest](http://docs.ros.org/en/api/sound_play/html/msg/SoundRequest.html)）をsubscribeするので、このトピックをターミナルや別ノードからpublishすることで音声再生できる。

音声ファイル再生時のトピックに設定する変数は以下のようにする。

| トピック変数 |型 | 概要           | 設定値        | 備考|
| :-: |  :-:| :-:| :-: | :-: |
| sound | int8 |音源のタイプ   | -2(PLAY_FILE) | |
| command | int8| 再生方法       | 0(PLAY_ONCE)  | |
| volume | float| 音量 (0.0-1.0) | 1.0           | 1.0を超えても設定は可能、音量も10.0までは上がった |  
| arg |string | 音声ファイルのパス (.wav or .ogg)|test.wav|OGG/WAVファイルの指定が可能
| arg2 | string | 音声ファイルが置かれているパッケージ名|''| 指定しない場合は、空文字('')とする

トピックをPublishし続けている間は、音声が再生される。以下に、コマンドの例を示す。

例：一度のみ、音声ファイル(test.wav)を再生する場合
```bash
ros2 topic pub -1 /robotsound sound_play/msg/SoundRequest "{sound: -2, command: 1, volume: 1.0, arg: 'test.wav', arg2: ''}"
```
* arg2を指定しない場合は、空文字として、音声ファイルは実行ファイルの直下に置く、または、絶対パスを指定する

#### 音声ファイル再生の際の注意点

* command
    * PLAY_STOP(0): 効かない（2024/9/2時点)
        * PLAY_START(2): リピートは可能だが停止できない（2024/9/2時点)
        * PLAY_ALL かつ PLAY_STOPだと、全て停止に実装されている
            * https://github.com/ros-drivers/audio_common/blob/06c280a798596db952538772918e8c7d44a6e5a8/sound_play/scripts/soundplay_node.py#L435-L437
            * ``` "{sound: -1, command: 0, volume: 1.0, arg: '', arg2: ''}"```
    * PLAY_ONCE(1): 問題なく動作（2024/9/2時点)
        * 複数回、Publishすると継続できる
        * トピックのPublishをやめると停止できる
* arg2
    * パッケージの場所を指定することが可能
        * https://github.com/ros-drivers/audio_common/blob/06c280a798596db952538772918e8c7d44a6e5a8/sound_play/scripts/soundplay_node.py#L317-L318
    * 設定されると、音声ファイルの参照パスが、{package_directory}/arg となる
    * 空文字('')を設定する
        * 設定しないと、```None```となり、パッケージ検索の際に例外で落ちるので必ず空文字を指定する

# まとめ
　ROS2で音声ファイルを再生する方法を紹介した。音声ファイルの情報を付与したトピックをpublishすることで簡単に音声再生させることができた。
 
# 参考

https://github.com/ros-drivers/audio_common/tree/ros2

https://wiki.ros.org/sound_play

# トラブルシューティング

## Can't open sound device.See http://wiki.ros.org/sound_play/Troubleshooting

上記のエラーがROSログで表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/638977/d62fb12b-e6cc-242c-db3b-b1d9953bfedd.png)

原因不明、調査中 (2024/9/9)。音声自体は再生できているので問題はなさそう。

# 付録

## soundplay_nodeの詳細

* https://github.com/ros-drivers/audio_common/blob/06c280a798596db952538772918e8c7d44a6e5a8/sound_play/scripts/soundplay_node.py#L82
* GStreamerのplaybin
    * https://gstreamer.freedesktop.org/documentation/playback/playbin.html?gi-language=c
    * 音量 (volume)
        * https://gstreamer.freedesktop.org/documentation/playback/playbin.html?gi-language=c#playbin:volume


## 定義済み音声の再生

* https://github.com/ros-drivers/audio_common/blob/ros2/sound_play/scripts/playbuiltin.py

```bash
ros2 run sound_play playbuiltin.py <ID>
```

例: ID=1の場合
```bash
ros2 run sound_play playbuiltin.py 1
```

* 指定可能なID  
    * http://docs.ros.org/en/jade/api/sound_play/html/msg/SoundRequest.html
    ```bash
    # Sounds
    int8 BACKINGUP = 1
    int8 NEEDS_UNPLUGGING = 2
    int8 NEEDS_PLUGGING = 3
    int8 NEEDS_UNPLUGGING_BADLY = 4
    int8 NEEDS_PLUGGING_BADLY = 5
    ```

## audio_commonのROS2 Humbleでの動作
　ROS2 Humbleでは、別ブランチが必要([参考](https://github.com/ros-drivers/audio_common/pull/212))。別の機会に動作確認予定。