Hands-on Digital transmission with your soundcard

This article is part of the fundamentals of my real-world tutorial on digital communications using a cheap soundcard as the radio. If this notebook is interesting to you, check out the full tutorial!

10 - Fixing the problem of lost frames

In the previous notebook, we integrated our physical layer with a packeting scheme and a data source that was fed by a UDP network connection. With this setup, we were able to transmit arbitrary data over the audio channel. However, we experienced problems that some frames were lost, either due to too many errors or due to an error in the PHY frame header.

In this notebook, we will address this problem by repeatedly transmitting the same frame multiple times. However, in order to not receive multiple copies of the same data at the application layer, a duplicate detection and omission needs to be implemented in between.

Let's start with the default imports:

In [1]:
import matplotlib
import matplotlib.pyplot as plt
%matplotlib notebook

%load_ext autoreload
%autoreload 2

import numpy as np
import sys; sys.path.append('..')
In [2]:
    %load_ext tikzmagic
except ModuleNotFoundError:
    print ("Did not find tikzmagic. You will not be able to compile the tex code!")
In [3]:
%matplotlib notebook
In [4]:
from audioComms import Environment, TX
from audioComms.utils import get_filter, StatefulFilter
from audioComms.buffers import RingBuffer
from audioComms.channels import AudioChannel, SimulatedChannel, IdentityChannelEffect, HighpassChannelEffect
from audioComms.plotting import PlotEyeDiagram
from audioComms.components import Component, Upconversion, Downconversion, TimingRecovery
from audioComms.components import DetectFrames, UDPSource, PacketizedBPSKTransmitter, TextDataSink
from audioComms.packeting import Packeting, int2bin, bin2int

Our strategy to overcome the problem with losing frames is to transmit multiple copies of each frame, hoping that at least one repetition gets through successfully. However, we need to add some logic to identify duplicated frames at the receiver, such that we do not forward the same information twice. Therefore, each frame gets an ID, the so-called frameCounter, which corresponds to the first 8 bits in the payload.

Let's write a component, which takes data and packs it into a frame with a frame ID. Then, this frame is repeated 3 times. In addition, frameCounter==0 denotes an empty or dummy frame, where no payload would be contained.

In [5]:
class RepeatAndAddFrameCounter(Component):
    def __init__(self, environment):
        super().__init__(environment, None)
        self._frameCounter = 1
    def receive(self, payload):
        # add the frame counter index to the frame
        frame = np.hstack([int2bin(self._frameCounter, 8), payload])
        # wrap around counter at 256
        self._frameCounter = max(1, (self._frameCounter+1) % 256)  
        # transmit the same frame 3 times
        for i in range(3):

At the receiver, we implement the counterpart: For each received frame, we decode the frame counter, and only forward the payload, if the decoded counter is different from the previous frame ID:

In [6]:
class DecodeFrameCounter(Component):
    def __init__(self, environment):
        super().__init__(environment, None)
        self._lastFrameCounter = 0
    def receive(self, packet):
        frameCounter = bin2int(packet[:8]) # decode the frame counter
        # check, if frame counter has changed from last packet to current
        if frameCounter != self._lastFrameCounter and frameCounter > 0:
            # it has changed, so forward the contained payload
            self._lastFrameCounter = frameCounter

These two additional components now allow us to finish our data transmission chain. Pictorially, we have the following components:

In [5]:
# Source code has been redacted in online version
# Omitting 35 lines of source code
In [7]:
def runTransmission(Fc, channelFunc):
    samplerate = 44100
    env = Environment(samplerate=samplerate)
    Ts = 1/(2*441)  # symbol duration
    Ns = int(Ts*samplerate)
    # 32 bit payload means 4 byte per frame
    packeting = Packeting(np.array([1,1,1,0,1,0,0,0,1,0,0,0,1,0]), PayloadLength=32)  
    defaultPayload = np.zeros(8)
    source = UDPSource(env, nPayloadBytes=3)  # use 3 bytes payload, as 1 byte is for the counter
    repeater = RepeatAndAddFrameCounter(env)
    transmitter = PacketizedBPSKTransmitter(env, Ts, packeting, defaultPayload=defaultPayload)
    upconversion = Upconversion(env, Fc=Fc)
    channel = channelFunc(env)
    downconversion = Downconversion(env, Fc=Fc, B=4/Ts, removeCarrier=True)
    timingRecovery = TimingRecovery(env, Ns)
    detectFrames = DetectFrames(env, packeting)
    decodeFrames = DecodeFrameCounter(env)
    printFrames = TextDataSink(env)
    plotEye = PlotEyeDiagram(env, Ts=Ts, figsize=(8,3))

    timingRecovery.transmitsTo(detectFrames, stream='samples')

First, let's try out the chain in the simulated channel: As in the previous notebook, send data via UDP to the chain:

In [8]:
runTransmission(15000, lambda env: SimulatedChannel(env, channelEffect=HighpassChannelEffect(gain=0.2)))
Frame data: Hi

Frame data: ABC
Frame data: DEF
Frame data: GHI
Frame data: JKL
Frame data: MNO
Frame data: PQR
Frame data: STU
Frame data: VWX
Frame data: YZ

Stop received from external

In above example, I send first the text Hi to the chain. Then, I sent ABCDEFGHIJKLMNOPQRSTUVWXYZ. As we see, all bytes are successfully received and decoded. No frames were lost. Moreover, no duplicates are shown, meaning that the duplicate detection worked successfully. Now, let's eventually try out the transmission over the real audio channel:

In [10]:
runTransmission(15800, AudioChannel)
Frame data: ABC
Frame data: DEF
Frame data: GHI
Frame data: JKL
Frame data: MNO
Frame data: PQR
Frame data: STU
Frame data: VWX
Frame data: YZ

Again, we received all frames and no frames were lost. Hence, we have successfully mitigated the problem of losing frames due to the erroneous header bits. However, unquestionably, the transmission efficiency is very low, as we have to transmit every frame 3 times.


We have now reached the end of this little toy example of a digital transmission system. Throughout the course, we have gained some hands-on knowledge on the following points:

  1. Setting up the environment for using the soundcard as a wireless transmitter.
  2. Using a channel simulator to achieve reproducable results for testing independent of the soundcard.
  3. Implementation of baseband transmission. Here we found, that the soundcard is not suitable for baseband transmission, as the DC component cannot be represented by the audio modulator.
  4. Passband transmission using up- and downconversion.
  5. Timing recovery as a means to find out, when to sample the received signal.
  6. Channel coding to mitigate inevitable bit errors during transmission.
  7. Definition of a frame structure to give each bit a meaning.
  8. Integration of the frame structure into the overall transmission chain.
  9. Integration of a UDP interface to transmit arbitrary data.
  10. Overcoming the issue of losing frames due to corrupted headers by performing repetition coding on the frame level.

It is clear, that the presented techniques are not optimized for efficiency. For example, the employed repetition code is very weak, and the frame detection based on the header is very error-prone. Instead, we used the simplest methods possible to achieve a reliable transmission.

Moreover, throughout the course, we did not focus on mathematics, but only on applications. For more information about the theoretical background, see e.g. the articles at http://dspillustrations.com.

This article is part of the fundamentals of my real-world tutorial on digital communications using a cheap soundcard as the radio. If this notebook is interesting to you, check out the full tutorial!

Copyright (C) 2018 - dspillustrations.com

DSPIllustrations.com is a participant in the Amazon Services LLC Associates Program, an affiliate advertising program designed to provide a means for sites to earn advertising fees by advertising and linking to amazon.com, amazon.de, amazon.co.uk, amazon.it.