質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.35%

Linuxで、マイク入力データをスピーカーからリアルタイムで出力したい

解決済

回答 4

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 1,444

YOshim

score 180

現在、Ubuntuでマイクからの信号をスピーカーから出力しようと四苦八苦しております。
"マイクからの信号"といっても通常のライン入力ではなく、
一度マイコンでAD変換された信号をシリアル通信でPCに送り、
PC側でRAWデータのままスピーカーから出力しようとしています。
*複数のマイクを用途に応じて使い分けるため、
全てのマイクのデータを一旦マイコンで受けれる構成にしています。

現在、シリアル通信で1つのマイクのデータを受信し、そのデータをスピーカーから出力できるようになったのですが、
音が途切れ途切れになってしまいます。

原因は、シリアル信号の受信と音声出力が同時に行われないためと推測しますが、
対策方法がわかりません。

対策方法や、もっと簡単な方法があるのであればご教授願いたく。

ソースコードは以下です。

#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#define SERIAL_PORT "/dev/ttyUSB2"
#define BAUDRATE B115200

int main(int argc, char* argv[]){
        unsigned char buf[255];
        FILE *fp;
        fp = fopen("tmpFile.raw","w");

        //シリアル通信の設定
        int fd;
        struct termios oldtio, newtio;
        fd = open(SERIAL_PORT, O_RDWR);
        ioctl(fd, TCGETS, &oldtio);
        newtio = oldtio;
        newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
        ioctl(fd, TCSETS, &newtio);

        while(1){
                int pid;
                pid = fork();
                if(pid < 0){
                        return -1;
                }
                if(pid == 0){
                        execlp("aplay", "aplay", "tmpFile.raw", NULL);//音声を再生
                        exit(99);
                }else{
                        int status;
                        waitpid(pid, &status, 0);
                        fp = fopen("tmpFile.raw", "w");//tmpFileをクリアする
                        int len = read(fd, buf, sizeof(buf));
                        if(len > 0){
                                for(int i=0; i<len; i++){
                                        fputc(buf[i],fp);//ファイルに書き込み
                                }
                        }
                        fflush(fp);//ファイルにバッファ分を書き込み
                }
        }

        ioctl(fd, TCSETS, &oldtio);
        fclose(fp);
        close(fd);
}

追記:
上記のコードは、シリアル通信のデータを毎回ファイルに書き込んでいますが、
シリアル通信の結果をパイプでaplayに接続すれば、高速に処理出来て音飛びが発生しないのでは
と考えています。
*まだそのやりかたがわかっていません・・・

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

+1

追記の方法ですと, シリアルから読み込んだデータを直接標準出力に書き込みを行い
ターミナル上で作成したプログラムと aplay をパイプで組み合わせてみるのはどうでしょうか.

例えば以下を実行すると砂嵐のような音が流れます.

cat /etc/urandom | aplay

記述されているコードですと, プロセスの終了を待っているため連続的に音を流すのは難しいと思います.
なので, aplay を簡易的なコードを実装してシングルプロセスで動作させるのが最善だと思います.

Introduction to Sound Programming with ALSA
このページに ALSA の使用方法が記載されています.

ALSA project - the C library reference
ALSA を使用したコードのサンプルと関数の詳細が記載されています.

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/11/01 17:39 編集

    回答ありがとうございます。
    シリアル通信の受信信号を標準出力とし、
    ./a.out | aplay(./a.out > aplayではなく)

    とすることに致しました。

    アドバイス頂きありがとうございます。

    キャンセル

  • この投稿は削除されました

+1

aplayが標準入力を読み込むというのであれば、

#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#define SERIAL_PORT "/dev/ttyUSB1"
#define BAUDRATE B115200

int main(int argc, char* argv[]){
        int fd;
        struct termios oldtio, newtio;
        fd = open(SERIAL_PORT, O_RDWR);
        ioctl(fd, TCGETS, &oldtio);
        newtio = oldtio; 
        newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
        ioctl(fd, TCSETS, &newtio);

        dup2(fd, 0);
        execlp("aplay", "aplay", NULL);
}

でいいと思います。

ちょっと説明。

  • ファイルディスクリプタ0番は、標準入力。
  • dup2は、ファイルディスクリプタのコピー。つまり、上記ではシリアルポートを標準入力に割り当て。
  • forkやexecなんちゃらは、ファイルディスクリプタの状態を引き継ぐ。つまり、aplayの実行時には標準入力はシリアルポートのまま。

こっちの方が読み込みをaplayの方に任せられる分、問題が起きにくいのではないかと。

これで解決するかどうかわかりませんが、というか自分で試していないので動くかどうかすらわかりませんが、まぁそれはそれで置いといて、dup2(もしくは、他にもdupとか)で標準入出力を置き換えるのはunixプログラムでよくやる手段なので、覚えておいて損はないと思います。


あと、dup2(fd,1)ではないですか。

0で間違いないですよ。
以下のようなプログラムで確認しました。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    int fd;

    fd = open("test01.c", O_RDONLY);
    dup2(fd, 0);
        // これでもいいと思うが、
    //execlp("cat", "cat", NULL);
        // こちらのほうがわかりやすいかと。
    execlp("sed", "sed", "-e", "s/test01/aaaa01/", NULL);
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/11/03 00:28

    なるほど。今までaplay単体で実行したことがなかったので、把握していませんでした。
    来週月曜にでも結果をおしらせします。

    キャンセル

  • 2018/11/05 08:20 編集

    残念ながら、aplayは入力を指定する必要があるようでした。
    あと、dup2(fd,1)ではないですか。

    編集:aplayの入力指定は、必要かどうかまだ不明です。オプションコマンドつければ、入力データを記述しなくともエラーは発生しませんでした。
    音も出ませんでしたが・・・

    キャンセル

  • 2018/11/05 10:44

    dup2の使用方法について理解不足なため、うまくいきませんでした・・・
    結果的に、異なる方法で実施できました。
    お手数おかけしましたが、
    この度は回答・コメント頂きありがとうございました。

    キャンセル

check解決した方法

0

シリアル通信を標準出力としてaplayで音声を再生することに致しました。

#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#define SERIAL_PORT "/dev/ttyUSB1"
#define BAUDRATE B115200

int main(int argc, char* argv[]){
        unsigned char buf[100];
        int fd;
        struct termios oldtio, newtio;
        fd = open(SERIAL_PORT, O_RDWR);
        ioctl(fd, TCGETS, &oldtio);
        newtio = oldtio; 
        newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
        ioctl(fd, TCSETS, &newtio);
        while(1){
                int len = read(fd, buf, sizeof(buf));
                if(len > 0){
                        for(int i = 0;i<len; i++){
                                fputc(buf[i],stdout);
                        }
                }
                fflush(stdout);
        }

        ioctl(fd, TCSETS, &oldtio); 
        fclose(fp);
        close(fd);
}


まだ正常な音声が出力されてはいませんが、
その原因はLinux、PCではなくマイコン側のソースコードの問題と考えています。

正常な音声が出力されていない以上、より良い方法を模索しようと思います。

追記:

下記のコードで音飛びなく動作するようになりました。

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#define SERIAL_PORT "/dev/ttyUSB0"
#define BAUDRATE B115200

#define BUFSIZE 100

int main(int argc, char* argv[]){
        unsigned char buf[BUFSIZE];
        int fd;
        struct termios oldtio, newtio;
        FILE *fp;
        fd = open(SERIAL_PORT, O_RDWR);
        ioctl(fd, TCGETS, &oldtio);
        newtio = oldtio;
        newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
        ioctl(fd, TCSETS, &newtio);
        while(1){
                int len = read(fd, buf,BUFSIZE);
                if(len > 0){
                        fwrite(buf,BUFSIZE,1,stdout);
                }
}       
        ioctl(fd, TCSETS, &oldtio);
        fclose(fp);
        close(fd);
}

主な変更点はfputcfwriteに変えたくらいですが、これはLivengaさんが最初におっしゃったことでしたね。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/11/02 06:31

    マイコンのタイマー機能で5kHzで8bitデータを取得しています。

    Aplayから音を出す際は、-r5000のオプションを記載して5kHzでサンプリングしています。
    チャンネル数は録音も再生も1です。

    キャンセル

  • 2018/11/02 10:22

    なるほど。それで直接再生では正しい音が出るのですね。

    キャンセル

  • 2018/11/02 12:55

    "直接再生"というのかわかりませんが
    マイクからの信号をファイルに保存し、保存したファイルをaplayで再生すると
    音は飛び飛びにはならず、正常に再生されているように聞こえます。

    キャンセル

0

そうすると次は転送ですね。PCの性能や同時に動作しているプロセスの影響も考えられますが、それは一旦置いといて、オーディオ転送用のUSBプロトコルはUSBスピーカーの様に、リアルタイム的動作が保証されるIsochronous転送を使うべきです。通常はこの様な目的のためにある、USB Audio Classという方式をマイコンとPCの通信で使用します。USBオーディオミキサー等の機器では通常、そういった通信をしています。

今回使われているUSBシリアル転送は本当のシリアル転送とは違い、4種類のUSB転送方式の中で一番優先順位が低い「Bulk転送」です。つまり「いつ終わってもいいから、CPUやUSBコントローラーが空いている時に転送してね」という方式です。例えばUSBキーボードやマウスは、より優先順位が高いInperrupt転送を使用します。

正直言って、この事が現在発生している音飛びに影響しているかどうかはわかりません。しかし問題を解決するならば、このような問題を一つずつ潰して行くしか無いと思います。可能であればまず、本当のシリアル(RS-232C)で接続して音飛びが変わるかどうかを確認してみるのはいかがでしょうか。実際のところ本来のMIDIは、本当のシリアル(RS-232C)で転送しています。通常的な利用でリアルタイム性に問題があるUSB Bulk転送方式は使用しません。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/11/03 00:18 編集

    USBの技術仕様は重要なことですが、
    実際に参考となるコードで回答を頂きたいです。

    キャンセル

  • 2018/11/03 00:34

    意味が伝わらなくて残念です。ALSAやLinuxのUSBドライバ、マイコン側のUSB Audioの使いこなしは大変でしょうから、まずは比較的簡単にできる/dev/ttyUSB2とPCのハードウエアをケーブル変えるだけで可能なRS-232Cを試してみてはいかがでしょうかと言っているだけです。

    RS-232C自体は直接USBの技術や仕様とは関係ないし、既存のコードもそのまま動くはずです。もしRS-232Cの意味がわからないのでしたら調べてください。

    キャンセル

  • 2018/11/03 00:36

    誤字修正

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.35%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る