使用Goertzel算法处理音频信号的结果

4
我做了一个小型的信号处理应用程序,它使用Goerztel算法处理特定频率下的音频信号 (摩尔斯电码)。该应用程序将临时文件保存到文件系统中,在录制完成后开始检测信号。现在我得到了一堆幅度值,但不知道如何从这些幅度值中解码出摩尔斯电码,也不知道如何读取它们。我尝试查找参考资料,但没有地方解释结果是什么以及如何读取它们。
编辑:我的摩尔斯电码应用程序是用Delphi制作的,并使用Windows Beep函数以特定频率发送信号,我使用1200 Hz进行信号发送,同时根据维基百科描述准确地记录了信号之间、单词之间和摩尔斯电码之间的暂停时间。Goertzel.java:
 public class Goertzel {

        private float samplingRate;
        private float targetFrequency;
        private int n;

        private double coeff, Q1, Q2;
        private double sine, cosine;

        public Goertzel(float samplingRate, float targetFrequency, int inN) {
            this.samplingRate = samplingRate;
            this.targetFrequency = targetFrequency;
            n = inN;

            sine = Math.sin(2 * Math.PI * (targetFrequency / samplingRate));
            cosine = Math.cos(2 * Math.PI * (targetFrequency / samplingRate));
            coeff = 2 * cosine;
        }

        public void resetGoertzel() {
            Q1 = 0;
            Q2 = 0;
        }

        public void initGoertzel() {
            int k;
            float floatN;
            double omega;

            floatN = (float) n;
            k = (int) (0.5 + ((floatN * targetFrequency) / samplingRate));
            omega = (2.0 * Math.PI * k) / floatN;
            sine = Math.sin(omega);
            cosine = Math.cos(omega);
            coeff = 2.0 * cosine;

            resetGoertzel();
        }

        public void processSample(double sample) {
            double Q0;

            Q0 = coeff * Q1 - Q2 + sample;
            Q2 = Q1;
            Q1 = Q0;
        }

        public double[] getRealImag(double[] parts) {
            parts[0] = (Q1 - Q2 * cosine);
            parts[1] = (Q2 * sine);

            return parts;
        }

        public double getMagnitudeSquared() {
            return (Q1 * Q1 + Q2 * Q2 - Q1 * Q2 * coeff);
        }
    }

SoundCompareActivity.java

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

    public class SoundCompareActivity extends Activity {

        private static final int RECORDER_SAMPLE_RATE = 8000; // at least 2 times
                                                                // higher than sound
                                                                // frequency,
        private static final int RECORDER_CHANNELS = AudioFormat.CHANNEL_CONFIGURATION_MONO;
        private static final int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;

        private AudioRecord recorder = null;
        private int bufferSize = 0;
        private Thread recordingThread = null;
        private boolean isRecording = false;

        private Button startRecBtn;
        private Button stopRecBtn;

        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);

            startRecBtn = (Button) findViewById(R.id.button1);
            stopRecBtn = (Button) findViewById(R.id.button2);

            startRecBtn.setEnabled(true);
            stopRecBtn.setEnabled(false);

            bufferSize = AudioRecord.getMinBufferSize(RECORDER_SAMPLE_RATE,
                    RECORDER_CHANNELS, RECORDER_AUDIO_ENCODING);

            startRecBtn.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    Log.d("SOUNDCOMPARE", "Start Recording");

                    startRecBtn.setEnabled(false);
                    stopRecBtn.setEnabled(true);
                    stopRecBtn.requestFocus();

                    startRecording();
                }
            });

            stopRecBtn.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    Log.d("SOUNDCOMPARE", "Stop recording");

                    startRecBtn.setEnabled(true);
                    stopRecBtn.setEnabled(false);
                    startRecBtn.requestFocus();

                    stopRecording();
                }
            });
        }

        private void startRecording() {
            recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
                    RECORDER_SAMPLE_RATE, RECORDER_CHANNELS,
                    RECORDER_AUDIO_ENCODING, bufferSize);

            recorder.startRecording();

            isRecording = true;

            recordingThread = new Thread(new Runnable() {

                @Override
                public void run() {
                    writeAudioDataToTempFile();
                }
            }, "AudioRecorder Thread");

            recordingThread.start();
        }

        private String getTempFilename() {
            File file = new File(getFilesDir(), "tempaudio");

            if (!file.exists()) {
                file.mkdirs();
            }

            File tempFile = new File(getFilesDir(), "signal.raw");

            if (tempFile.exists())
                tempFile.delete();

            return (file.getAbsolutePath() + "/" + "signal.raw");
        }

        private void writeAudioDataToTempFile() {
            byte data[] = new byte[bufferSize];
            String filename = getTempFilename();
            FileOutputStream os = null;

            try {
                os = new FileOutputStream(filename);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }

            int read = 0;

            if (os != null) {
                while (isRecording) {
                    read = recorder.read(data, 0, bufferSize);

                    if (read != AudioRecord.ERROR_INVALID_OPERATION) {
                        try {
                            os.write(data);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }

                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        private void deleteTempFile() {
            File file = new File(getTempFilename());

            file.delete();
        }

        private void stopRecording() {
            if (recorder != null) {
                isRecording = false;

                recorder.stop();
                recorder.release();

                recorder = null;
                recordingThread = null;
            }

            new MorseDecoder().execute(new File(getTempFilename()));    
        }
    }

MorseDecoder.java:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.os.AsyncTask;
import android.util.Log;

public class MorseDecoder extends AsyncTask<File, Void, Void> {
    private FileInputStream is = null;

    @Override
    protected Void doInBackground(File... files) {
        int index;
        //double magnitudeSquared; 
        double magnitude; 

        int bufferSize = AudioRecord.getMinBufferSize(8000,
                AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT);

        Goertzel g = new Goertzel(8000, 1200, bufferSize);
        g.initGoertzel();

        for (int i = 0; i < files.length; i++) {
            byte[] data = new byte[bufferSize];

            try {
                is = new FileInputStream(files[i]);

                while(is.read(data) != -1) {
                    ShortBuffer sbuf = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
                    short[] audioShorts = new short[sbuf.capacity()];
                    sbuf.get(audioShorts);

                    float[] audioFloats = new float[audioShorts.length];

                    for (int j = 0; j < audioShorts.length; j++) {
                        audioFloats[j] = ((float)audioShorts[j]) / 0x8000;
                    }

                    for (index = 0; index < audioFloats.length; index++) { 
                        g.processSample(data[index]); 
                    }

                    magnitude = Math.sqrt(g.getMagnitudeSquared());


                    Log.d("SoundCompare", "Relative magnitude = " + magnitude);

                    g.resetGoertzel();
                }

                is.close();

            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return null;
    }
}

编辑2:

注意到处理样本中的一些错误。在while循环中更改了代码。

while(is.read(data) != -1) {
                    ShortBuffer sbuf = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
                    short[] audioShorts = new short[sbuf.capacity()];
                    sbuf.get(audioShorts);

                    float[] audioFloats = new float[audioShorts.length];

                    for (int j = 0; j < audioShorts.length; j++) {
                        audioFloats[j] = ((float)audioShorts[j]) / 0x8000;
                    }

                    for (index = 0; index < audioFloats.length; index++) { 
                        g.processSample(audioFloats[index]); 

                        magnitude = Math.sqrt(g.getMagnitudeSquared());
                        Log.d("SoundCompare", "Relative magnitude = " + magnitude);
                    }

                    //magnitude = Math.sqrt(g.getMagnitudeSquared());


                    //Log.d("SoundCompare", "Relative magnitude = " + magnitude);

                    g.resetGoertzel();
                }

问候, 恶意分子

2个回答

7
您的Goertzel滤波器的输出会随着其通带内的音调存在而增加,一旦音调消失就会减少。为了检测音调的脉冲,例如摩尔斯电码,您需要在滤波器的输出上添加某种阈值检测器,该检测器将仅基于逐个样本的布尔值(“有音调”/“没有音调”)进行判断。尝试绘制输出值,一旦以图形形式查看,问题应该很明显。

嗯,如果我在不发送信号的情况下记录这个转储文件,那么幅度看起来与莫尔斯电码发出的声音相同。有什么可视化工具吗?或者如何查看这些正弦波? - evilone
是的,Goertzel滤波器会在每个新输入样本时更新,因此如果您有N = 1024个样本,则会得到1024个(经过滤波的)样本。 - Paul R
只要您在每个样本上都更新过滤器并对输出值进行了某些处理(无论是存储还是以某种方式进行处理),那么就可以了。如果没有,那么您需要修复它。 - Paul R
我注意到,我正在处理错误的数组 - 是字节数组而不是浮点数数组。好的,我正在更新我的问题代码,请看看现在是否正确。 - evilone
好的 - 你应该能够看到滤波器输出值与输入音调是否存在之间的某种相关性。 - Paul R
显示剩余16条评论

0

在图表上绘制信号幅度与时间的关系(一些PC上的CW解码应用程序可以实时完成此操作)。现在找出每个莫尔斯电码符号的图形应该是什么样子。然后研究一些模式匹配算法。如果存在足够的噪声,您可能需要尝试一些统计模式匹配方法。

这里是正确的莫尔斯电码定时的维基百科链接


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接