3

I want to write sonar/radar like application. For sake of simplicity (and not to use adruino and specialized RTOS) - I simply used using audio in/out analog ports of a motherboard integrated card as an sample hardware setup + Windows API Steinberg ASIO SDK with MSVS 2015. So, for my latency tests i just used jack-jack cable from output(green) to linein(blue) minijacks. And, in my application, i have 2 important callbacks:

const int halfPeriod = 2; // 48khz rect wave (192khz samplerate)
void on_playback_finished(short* buffer, int length=1024) { 
    for(int i=0;i<length;i++)
        buffer[i]= (i<256) ? 
            (((i / halfPeriod) % 2) * 60000 - 30000 )
            : 0;
}

It's quite simple, it produces short "spike" (1/4th of length of the whole buffer) of square wave, rest of buffer is filled with silence.

And then second callback - which is called just when i recorded buffer from line in. And this is my question. I must somehow detect this spike (is it actually recorded, at all?) and measure latency, offset from start of recorded buffer in samples; and return that sample offset:

int on_recorded_buffer(short* buffer, int length=1024) {
    // there is our spike, somewhere in the buffer[] samples array
    // how to detect beginning of that spike, 
    // considering attenuation and possible induced noise?
    return 0; // ideal case - zero sample latency
    // that means "spike is detected at very beginning of recorded buffer"
    // return 42; // signal start is detected at 42th sample of recorded buffer
}

what to do? need assistance, some simple algorithm)

UPD. Consider signal is mono(channels=1), bits/sample=16, Sampling rate is 192kHz, Signal length is 256 samples (1.33333ms), pause between signals is at least 768 samples (4ms). I am asking about proper algorithm of doing sample offset calculation!

UPD2 Iam not very good drawer, but to easier understanding look: sonar dsp task

To avoid extra questions: Transmitted signal always starts at beginning of buffer. Consider transmit and recieve buffers have same timepoint (e.g. software is perfect, adds no latency). Red lines represent HW/Software transitions. Detect recieved signal and calculate offset of it start.

xakepp35
  • 2,878
  • 7
  • 26
  • 54
  • 2
    If you don't get a good answer here, you could try dsp.stackexchange.com – m69's been on strike for years May 13 '17 at 00:04
  • 1
    @m69 thanks, i also will ask there. i alredy know about that great project – xakepp35 May 13 '17 at 00:12
  • 2
    I'm no expert, but it seems to me that a better test signal would be a short impulse (burst of maximum amplitude samples). That you can detect by just finding the place in the buffer where the difference between one sample and the next is greatest (sort of a test of high-frequency content, I guess...). – Linuxios May 13 '17 at 02:44
  • Better algorithms for that (detecting onset of an impulse) can probably be found with some googling about impulse detection or percussion onset detection too. – Linuxios May 13 '17 at 02:46
  • Use FFT to convolve the incoming signal with the outgoing one and look for the offset in the convolved result which has the highest value? – mcdowella May 13 '17 at 04:10
  • 1
    Standard PC type motherboards have significant latency jitter. Standard approach is to simultaneously capture the attenuated output signal in the same recorder channel or another one sampled at the same time. Then the only latency is in the transducers. – doug May 13 '17 at 04:29
  • @Linuxios nice approach, thanks! i will test your advice asap) – xakepp35 May 13 '17 at 05:59
  • @mcdowella uhh.. i cant figure out what you mean.. could you offer some code example piece please? – xakepp35 May 13 '17 at 06:00
  • @doug latency jitter is introduced mainly by OS, and this is windows, in my case, it is very far from realtime os. but.. what could be wrong with hardware itself? i doubt that integrated audio has bigger latency, than, to say, creative emu 1212m pcie, which i also have at my disposal.. both of them are sitting on pci-e bus and had to have same latency lag. they could differs only in opamp/ADC/DAC quality, in this hardware context. and yes, specialized cards have specialized drivers, which works at kernel mode and could have latency lowered via properly optimized driver code at kernel level.. – xakepp35 May 13 '17 at 06:06
  • @Linuxios you could write your solution down, as an answer ;-) just add some simple small code snippet, what will you do inside on_recorded_buffer()? what will you return? – xakepp35 May 13 '17 at 06:17
  • 1
    You could try using the asio4all driver instead of your soundcard's standard driver; it will take exclusive control of it and give lower latency and probably also more consistent results. – m69's been on strike for years May 13 '17 at 17:05
  • Typically, audio I/O goes into / out of circular queue's in chunks. It all depends on what spacial resolution your "sonar" needs. You can easily get down to fractions of an inch if you bypass the jitter otherwise you could have several feet (1-2 ms). Measure it and see if it fits your needs. – doug May 13 '17 at 18:30
  • Also, the latency jitter occurs as variation per experiment. Each sequence, once started, is quite stable. Uncertainty of latency occurs when a process is initiated, not during the process. – doug May 13 '17 at 18:42
  • @m69 sure, i will – xakepp35 May 13 '17 at 20:33
  • @doug alredy using circular chain buffers. resolution? need 1cm resolution - 48khz yields to 0.7cm resolution with 340m/s. we're fine here. But question was about offset. I edited post, take a look. – xakepp35 May 13 '17 at 20:36
  • @doug your jitter assumption is correct, at least with waveXXX() functions. i had to restart program until i get <1ms latency. As simpliest solution i've used threshold detection: `if( buffer[i]>detectorThreshold ) return i;` – xakepp35 May 13 '17 at 20:38
  • You might also consider a Barker Code, https://en.wikipedia.org/wiki/Barker_code which had nice properties for this echo location. It can be cross correlated and produces a nice, relatively isolated peak. – doug May 14 '17 at 21:09

1 Answers1

2

Prologue

Well this is a big problem on PC and especially on Windows. Back in the time I was writing sound module for my ZX Spectrum emulator I did my fair share of trial&error and I also did build ultrasonic sonar system for this mobile robot:

Netopier

So Here some insights:

  1. Sound API

    There are more sound capable API on Windows and not all are suitable for this task. I prefer WAVEIN / WAVEOUT as it has best latency from all I tried (DirectSound is the worst). But for continuous duty is latency not as important.

  2. Jitter and latency offset

    As mentioned in the comments you need to receive both direct and reflected signal to have something to synchronize with:

    sonar

    So use 2 MICs or one that is hearing both direct and reflected signal (for example mechanically connected to reproductor). The best number of impulses send is usually 7 (do not ask me why it is empiric knowledge form the old guys in the field and has the best results especially for ultrasound ~40KHz).

    The gap between consequent measurements must be large enough for reflected signals to fully dampen.

    If your setup uses continuous buffered reproduction than the offset between Out and L should be the same all the time (after sound pipeline is fully started) but of coarse will be different between your app starts so yo can not use some constant instead.

    Stereo input channels are sampled at the same time so this way you ignore the sound pipeline latencies. If you want just mono signal for the MICs then you can Wire or them together with different weights.

  3. HW

    As you are using 48KHz I hope your reproductor and MICs are capable of transmitting/detecting such signal. If you got just standard audio stuff than use lower frequencies instead like 8KHz. To check for that you can use this:

    download my Win32 sound-card Oscilloscope,generator and Spectrum analyzer run the generator and oscilloscope or spectrum analyzer. on generator set desired square wave and look on oscilloscope if the signal is present and how it looks...

P.S.

Now if you need help with detecting the signal in L,R wee need to see the actual received signals first (you can screen-shot the oscilloscpoe).

[Edit1] sample echo

I modified your image a bit so I can extract the sample points:

echo sample

So I added (in paint) red squares to detect the sample points leading to this:

int data[22]={ 370, 370, 368, 371, 367, 376, 323, 157, 273, 580, 488, 148, 260, 593, 476, 144, 261, 595, 476, 142, 259, 594 };

Where index step in array represent 30 pixels which is 1T = 1/192000 sec That should match your sample audio but scaled to image so the amplitudes can have different offset and scale in y axis.

So now we have sample test data so how to detect start of the echo signal?

  1. compute average zero

    so average few first samples where there is no echo yet and call it y0

  2. detect peaks

    so determine some threshold thr which will detect the pulse peaks. Peak is when sample[i]>thr that means some signal is present.

  3. detect zero crossings

    simply remember last peak sign and if opposite to present peak you crossed the zero (y0).

  4. determine if signal is echo

    if number of zero crossings is around twice as much as pulses send and also the durations of the pulses group send and received are similar then you can classify found signal as echo.

Here some C++ code for this:

    const int w=10;
    int x,y,xx,yy,y0,thr,sig;
    int size=22,data[32]={ 370, 370, 368, 371, 367, 376, 323, 157, 273, 580, 488, 148, 260, 593, 476, 144, 261, 595, 476, 142, 259, 594 };

    //picture pic0,pic1;        // pic0 is input image and pic1 is output image
    pic1=pic0;                  // copy input image to output
/*
    // detect samples from image you can ignore this
    pic1&=0x00FFFFFF;
    size=0; xx=-w; yy=-w;
    for (x=1;x<pic1.xs-1;x++)
     for (y=1;y<pic1.ys-1;y++)
      if (pic1.p[y][x].dd==0x00ED1C24)
       if (((xx-x)*(xx-x))+((yy-y)*(yy-y))>(w*w))
        {
        xx=x+3;
        yy=y+3;
        pic1.p[yy][xx].dd=0;
        data[size]=yy; size++;
        }
*/
    // y0 = average on start of data (no echo) means zero
    for (y0=0,x=0;x<5;x++) y0+=data[x]; y0/=5;
    pic1.bmp->Canvas->Pen->Color=clBlack;
    pic1.bmp->Canvas->MoveTo(      0,y0);
    pic1.bmp->Canvas->LineTo(pic1.xs,y0);
    // detect echo
    thr=y0/10;                  // threshold = 10% of plot half size
    sig=0;
    pic1.bmp->Canvas->Pen->Color=clBlue;
    pic1.bmp->Canvas->Brush->Color=clAqua;
    for (x=1;x<size;x++)
     if (abs(data[x]-y0)>thr)   // peak
        {
        xx=(x*30)+22;           // array index to pixel position
        yy=data[x];
        // peak line
        pic1.bmp->Canvas->MoveTo(xx,y0);
        pic1.bmp->Canvas->LineTo(xx,yy);
        // zero crossing dot
        y=sig;
        if (yy>y0) sig=+1; else sig=-1;
        if (y*sig<=0)
            {
            pic1.bmp->Canvas->Ellipse(xx-w,yy-w,xx+w,yy+w);
            }
        }

You can ignore all the stuff starting with pic0 or pic1 as you already got the samples data[size]. Here result:

result

Black line is the found average zero y0 Blue lines are found peaks above zero and aqua circles are found pulses (around zero crossings).

Spektre
  • 49,595
  • 11
  • 110
  • 380
  • Good drawing! Left channel could be just wire from out to in, as an reference. As i did. Recieved signal is sine 48khz, or close to it. YOu could see screenshot of oscillo: http://i.imgur.com/VgweeYJ.png and http://i.imgur.com/vSVrJ90.png there seems to bo some echo or resonance or what, probably caused by mixer. (that small sine) – xakepp35 May 14 '17 at 18:06
  • Could not see source code of apps. Are them viruses or of some secret? :) Joking. Your apps supports only 44100hz mode, insuffient for me. Has no configurable settings. But fun stuff. Share source code to community. – xakepp35 May 14 '17 at 23:38
  • And i use professional software, Izotope RX for analysing signal realtime, recording and doing manipulations. Ofc, it has spectrum analyser, both realtime and for audio files. And yes i initialize my audio properly, in 192khz mode, and my signal is fine. BUT! my question was on HOW TO DETECT sample difference? So your answer is not actually an answer, but rather is a good story. Read the question again and try to answer the question. – xakepp35 May 14 '17 at 23:39
  • @xakepp35 At that time I coded that apps the max sampling rate was 44100Hz (coded in C++ builder from Borland/Embarcadero). As I mentioned in the answer for detection I need to see the signals first. Your images are without context .... if I assume the second is the echo then just measure `abs(amplitude)` and if bigger than `threshold` mark as pulse... if there is count of pulses together similar to number send (and similar duration) then it is your echo. so simply scan sliding window covering little more that your duration of pulses through whole sampled data and detect the echo position. – Spektre May 15 '17 at 07:42
  • @xakepp35 to detect individual pulses you just check for crossing zero (sign change) or crossing sliding average in case of unsigned signal – Spektre May 15 '17 at 07:46
  • @xakepp35 I just updated some libs of mine and updated my oscilloscope,generator and analyser apps as well. I tried to use 192KHz sampling rate but that does not work on my setup.Playback is working but Recording is down-sampled by driver or HW so I can not get beond 44100 or 48000 Hz with current interface. – Spektre Sep 09 '17 at 15:44