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

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

ただいまの
回答率

90.04%

AudioTrack 矩形波・正弦波とノイズの関係についての質問

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 698

tcb78

score 3

 前提・実現したいこと

音波を生成して送信するAndroidアプリを作成しています。
開始画面で送信したい周波数と音量を入力してスイッチを押すと、その周波数の音波を生成して指定した音量で送信するシステムです。

矩形波を生成するアプリと正弦波を生成するアプリの2つを作ったのですが、
矩形波送信ではノイズがほとんどなく、正弦波送信ではたくさんのノイズが発生します。

本来は矩形波送信にノイズが発生して正弦波送信にはノイズはほとんど発生しないと考えているのですが、
私の理解が間違えているのか、プログラムが間違えているのか、どちらなのでしょうか。

開始画面

 該当のソースコード

 矩形波送信

package com.example.xxx.rectanglesender;

import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Bundle;
import android.util.Log;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.Switch;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends Activity implements OnCheckedChangeListener {

    int SENDFREQ;
    int VOLUME;
    int SendSR;
    int SendBufSize;
    int musicVolume = 0;

    AudioManager audioManager;
    AudioTrack audioTrack = null;
    private List<SoundDto> soundList = new ArrayList<SoundDto>();
    Thread send;
    boolean bIsPlaying = false;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView SendfreqText = findViewById(R.id.SendfreqText);
        SendfreqText.setText(R.string.SendfreqText);
        TextView volumeText = findViewById(R.id.volumeText);
        volumeText.setText(R.string.volumeText);
        Switch switch1 = findViewById(R.id.Switch);
        switch1.setOnCheckedChangeListener(this);
    }

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

        if(isChecked) {
            EditText SendfreqEdit = findViewById(R.id.SendfreqEdit);
            EditText volumeEdit = findViewById(R.id.volumeEdit);

            SENDFREQ = Integer.parseInt(SendfreqEdit.getText().toString());
            VOLUME = Integer.parseInt(volumeEdit.getText().toString());

            SendSR = 4 * SENDFREQ;
            SendBufSize = 4 * SENDFREQ;

            audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);

            audioTrack = new AudioTrack(
                    AudioManager.STREAM_MUSIC,
                    SendSR,
                    AudioFormat.CHANNEL_OUT_MONO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    SendBufSize,
                    AudioTrack.MODE_STREAM);

            soundList.add(new SoundDto(createWaves(SendSR)));

            musicVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, VOLUME, 0);

            audioTrack.play();
            bIsPlaying = true;
            send = new Thread(new Runnable() {
                @Override
                public void run() {
                    while(bIsPlaying) {
                        for(SoundDto sound : soundList) {
                            audioTrack.write(sound.getSound(), 0, sound.getSound().length);
                        }
                    }
                    audioTrack.stop();
                    audioTrack.release();
                }
            });
            send.start();
        } else {

            if(audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
                audioTrack.stop();
                bIsPlaying = false;
            }
            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, musicVolume, 0);
        }
    }

    //波形データ生成
    public byte[] createWaves(int sampleRate) {
        byte[] data = new byte[sampleRate];
        int flag = 0;

        for(int i = 0; i < sampleRate; i = i + 2) {
            if(flag == 0) {
                data[i] = (byte)0xff;
                data[i + 1] = (byte)0xff;
                flag++;
            } else {
                data[i] = (byte)0x00;
                data[i + 1] = (byte)0x00;
                flag--;
            }
        }
        return data;
    }

}

 正弦波送信

main

package com.example.xxx.sinusoidsender;

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.content.Context;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Bundle;
import android.util.Log;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.Switch;
import android.widget.TextView;

public class MainActivity extends Activity implements OnCheckedChangeListener {

    //音を何秒鳴らすか
    public static final double WHOLE_NOTE = 1.0;

    //信号音の周波数
    public static final double SIGNAL = 20000;
    public static final double START = 1000;

    int SENDFREQ;
    int VOLUME;
    int SendSR = 44100;
    int SendBufSize = 44100;
    int musicVolume = 0;

    AudioManager audioManager;
    AudioTrack audioTrack = null;
    private List<SoundDto> soundList = new ArrayList<SoundDto>();
    Thread send;
    boolean bIsPlaying = false;

    // Sound生成クラス
    DigitalSoundGenerator soundGenerator;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView SendfreqText = findViewById(R.id.SendfreqText);
        SendfreqText.setText(R.string.SendfreqText);
        TextView volumeText = findViewById(R.id.volumeText);
        volumeText.setText(R.string.volumeText);
        Switch switch1 = findViewById(R.id.Switch);
        switch1.setOnCheckedChangeListener(this);

    }

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

        if(isChecked) {

            EditText SendfreqEdit = findViewById(R.id.SendfreqEdit);
            EditText volumeEdit = findViewById(R.id.volumeEdit);

            SENDFREQ = Integer.parseInt(SendfreqEdit.getText().toString());
            VOLUME = Integer.parseInt(volumeEdit.getText().toString());

            audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);

            // SoundGeneratorクラスをサンプルレート44100で作成
            soundGenerator = new DigitalSoundGenerator(44100, 44100);

            // 再生用AudioTrackは、同じサンプルレートで初期化したものを利用する
            audioTrack = soundGenerator.getAudioTrack();

            soundList.add(new SoundDto(generateSound(soundGenerator,SIGNAL), WHOLE_NOTE));

            musicVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, VOLUME, 0);

            audioTrack.play();
            bIsPlaying = true;
            send = new Thread(new Runnable() {
                @Override
                public void run() {
                    while(bIsPlaying) {
                        for(SoundDto sound : soundList) {
                            audioTrack.write(sound.getSound(), 0, sound.getSound().length);
                        }
                    }
                    audioTrack.stop();
                    audioTrack.release();
                }
            });
            send.start();

        } else {

            if(audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
                audioTrack.stop();
                bIsPlaying = false;
            }
            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, musicVolume, 0);

        }

    }

    /**
     * 8ビットのピコピコ音を生成する
     * @param gen Generator
     * @param freq 周波数(音階)
     * @return 音データ
     */
    public byte[] generateSound(DigitalSoundGenerator gen, double freq) {
        return gen.getSound(freq);
    }
}

sound生成

package com.example.xxx.sinusoidsender;

import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;

public class DigitalSoundGenerator {

    private AudioTrack audioTrack;

    private int sampleRate;
    private int bufferSize;

    /**
     * コンストラクタ
     */
    public DigitalSoundGenerator(int sampleRate, int bufferSize) {
        this.sampleRate = sampleRate;
        this.bufferSize = bufferSize;

        // AudioTrackを作成
        this.audioTrack = new AudioTrack(
                AudioManager.STREAM_MUSIC,
                sampleRate,
                AudioFormat.CHANNEL_OUT_MONO,
                AudioFormat.ENCODING_PCM_16BIT,
                bufferSize,
                AudioTrack.MODE_STREAM);
    }

    /**
     * サウンド生成
     * @param frequency 鳴らしたい音の周波数
     * @return 音声データ
     */
    public byte[] getSound(double frequency) {
        frequency = frequency / 2;
        // byteバッファを作成
        byte[] buffer = new byte[(int)Math.ceil(bufferSize)];
        double hz;
        double amplitude = 10; //振幅
        int i;
        double max=0;
        double trans;
        double[] t=new double[buffer.length];
        hz=frequency/this.sampleRate;
        for(i=0;i<buffer.length;i++){
            t[i]=Math.sin(i*2*Math.PI*hz);
            //t[i]=amplitude * Math.sin(i*2*Math.PI*hz);
            if(t[i]>max)
                max=t[i];
        }
        trans=127/max;
        for(i=0;i<buffer.length;i++){
            buffer[i]= (byte) Math.round(t[i]*trans);

        }
        return buffer;
    }

    public AudioTrack getAudioTrack() {
        return this.audioTrack;
    }
}
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

質問への追記・修正、ベストアンサー選択の依頼

  • ikadzuchi

    2018/09/05 21:08

    「本来は矩形波送信にノイズが発生して正弦波送信にはノイズはほとんど発生しないと考えている」のはなぜですか? また、ノイズとは何のことですか?

    キャンセル

  • y_waiwai

    2018/09/05 21:44

    音を出してるところが見当たりませんが、どうやって音を出してる/送信してるんでしょうか

    キャンセル

  • tcb78

    2018/09/06 13:22

    ここでのノイズとは音声としての雑音のことです。「本来は矩形波送信にノイズが発生して正弦波送信にはノイズはほとんど発生しないと考えている」というのは普段我々が耳にする音声は正弦波であるから矩形波には再生したい周波数以外の音が現れるだろうと考えていました。

    キャンセル

  • tcb78

    2018/09/06 13:23

    私の考え方が間違っていれば矩形波に雑音が発生せず正弦波に雑音が発生しない理由を教えていただけないでしょうか。

    キャンセル

回答 2

check解決した方法

0

解決方法

生成する音声データをbyte型からshort型にすることで雑音がなくなりました。

質問時のコードでは音声データをbyte型で生成しており、byte型の値の範囲である-128~127で正規化しています。
それをshort型で生成し、short型の値の範囲である-32768~32767で正規化することにより雑音のない単音が再生出来ました。

おそらく音声データを正規化するための分解能が高くなったことで誤差が小さくなり、雑音が発生しにくくなったのだと考えています。

修正後のコード

/**
 * 正弦波生成
 * @param frequency 音の周波数
 * @return 音声データ
 */
public short[] getSoundShort(double frequency) {
    double[] value = new double[sampleRate];
    double max = 0.0;
    for(int i = 0; i < sampleRate; i++) {
        value[i] = Math.sin(2.0 * Math.PI * frequency * i / sampleRate);
        if(value[i] > max) {
            max = value[i];
        }
    }
    short[] buffer = toShort(value, max);

    return buffer;
}

/**
 * double型をshort型に変換(-32768~32767で正規化)
 * @param val double型音声データ
 * @param max 最大値
 * @return short型音声データ
 */
public short[] toShort(double[] val, double max) {
    double trans = 32767 / max;
    short[] buf = new short[sampleRate];
    for(int i = 0; i < sampleRate; i++) {
        buf[i] = (short)Math.round(val[i] * trans);
    }
    return buf;
}

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/08/29 09:26

    いえですからbyteは符号なしであるべきところ符号付きで使っていたからノイズが出ていて、shortは符号付きであるべきで正しく符号付きにしたからノイズが出なくなったのだと思います。

    キャンセル

  • 2019/08/29 09:28

    まあそりゃ矩形波というのは0か1かですから、ノイズの乗りようもないし。

    キャンセル

  • 2019/08/30 13:36

    そういうことなんですね。いろいろとコメントをくださりありがとうございます。

    キャンセル

0

あ、正弦波の方が値が符号付きになっているようですね。
(矩形波の方では正しく扱っているように)8bitのwavは符号なしです。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/09/06 13:25

    符号付きになっていることがどこから判断できるか今の私では分からないのでどこから分かったか教えていただけないでしょうか。

    キャンセル

  • 2018/09/07 09:40

    符号付きといいますか、入れる値が「Math.sin(i*2*Math.PI*hz);」で正負あるまま、振幅を調整するだけでbyte変数まで入っているように見えます。

    キャンセル

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

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