Результат обработки аудиосигнала по алгоритму Гёртцеля

Я сделал небольшое приложение для обработки сигналов. Он обрабатывает аудиосигнал (код Морзе) на определенной частоте с помощью алгоритма Гёрзтеля. Приложение сохраняет временный файл в файловой системе и после завершения записи начинает обнаруживать сигналы. Теперь я получил результат с кучей величин.

Я действительно не знаю, что читать из этих величин. Как я могу декодировать азбуку Морзе из этих величин? Как я могу их прочитать? Пытался найти ссылки, но нигде не объяснено, каков результат и как его прочитать.

РЕДАКТИРОВАТЬ:

Мое приложение азбуки Морзе сделано с помощью Delphi и использует функцию звукового сигнала Windows для отправки сигналов с определенной частотой. Я использую 1200 Гц для сигналов. Также паузы между сигналами и словами и звуками Морзе похожи на описанную википедию. Все точно.

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;
    }
}

EDIT2:

Замечает некоторые ошибки при обработке образцов. Изменен код в цикле 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 ответа

Решение

Выход вашего фильтра Goertzel будет увеличиваться при наличии тона в его полосе пропускания, а затем уменьшаться при удалении тона. Чтобы обнаружить импульсы тона, например, азбуки Морзе, вам нужен какой-то пороговый детектор на выходе фильтра, который просто выдаст логическое значение для "тона присутствует" / "тона нет" на выборке за образец базы. Попробуйте построить выходные значения, и это должно быть очевидно, как только вы увидите это в графической форме.

График амплитуды сигнала на графике в зависимости от времени (некоторые приложения CW для декодирования ПК делают это в режиме реального времени). Теперь выясните, как должен выглядеть график для каждого символа азбуки Морзе. Затем изучите некоторые алгоритмы сопоставления с образцом. Если присутствует достаточно шума, вы можете попробовать некоторые статистические методы сопоставления с образцом.

Вот ссылка на Википедию для правильного выбора времени азбуки Морзе.

Другие вопросы по тегам