_           _                 
 | |_ ___  __| |____  ___ _   _ 
 | __/ _ \/ _` |_  / / _ \ | | |
 | ||  __/ (_| |/ / |  __/ |_| |
  \__\___|\__,_/___(_)___|\__,_|

Binary signal detective video generator sem 2 2025-26

Introduction

This program (create_videos) generates a batch of binary signal videos for a provided list of student numbers, for use in the Binary Signal Detective assignment in EEPP2 (module ELEC 1011). For each student, a random 4-character code word is generated. This code is encoded as a 40-bit binary sequence (4 bytes, each with a start and stop bit) that is displayed in the video in the form of a flashing square area of pixels. The square is white for each '1' bit and black for each '0' bit.

The C source code is provided below. To compile it:

gcc create_videos.c -o create_videos

To generate videos for a batch of students, first prepare a text file called students.txt that contains a list of student numbers. For example, students.txt might contain the following:

C12345678
A87654321
D12341234

To run the generator, just use:

./create_videos

During the generation process, create_videos logs each student's number and code in a file codes.csv, as shown below:

C12345678,Demo
A87654321,G5n6
D12341234,Uk0x

Example video:

Source code

//
// create_videos.c - Written by Ted Burke - 9-Feb-2026
//
// To compile,
//
//  gcc create_videos.c -o create_videos
//
// To run, first create a file called students.txt containing
// a list of student numbers, then:
//
//  ./create_videos
//

#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/time.h>

#define W 640 // video frame width
#define H 480 // video frame height
#define L 256 // length of side of flashing square

uint8_t p[2][H*W*3] = {0}; // pixel buffer for 'zero' and 'one' frames

int debug = 0;

int main()
{
    char student[10]; // student number
    char codeword[5]; // 4-character code word

    int m, n; // used for code generation
    int x, y, a, b, i; // used for video generation
    int bits[40];

    FILE *finput = fopen("students.txt", "r");
    FILE *foutput = fopen("codes.csv", "w");

    while(1)
    {
        // Generate a 4-character code word

        // Seed random number generation with a value
        // between 0 and 1000000000 using the current time
        struct timeval tv;
        gettimeofday(&tv, NULL);
        srand(1000000*(tv.tv_sec % 1000)+tv.tv_usec);

        for (a=0 ; a<4 ; ++a)
        {
            // Generate random alphanumeric character
            //   characters 0-9 are ascii 48-57
            //   characters A-Z are ascii 65-90
            //   characters a-z are ascii 97-122
            n = (rand() % 62) + 48;
            if (n > 57) n += 7;
            if (n > 90) n += 6;
            codeword[a] = n;
        }
        codeword[a] = 0; // null termination character

        // Read a student number from the input file
        if (fscanf(finput, "%9s", student) == EOF) break;
        if (strncmp(student, "C12345678", 9) == 0) strcpy(codeword, "Demo");
        fprintf(foutput, "%s,%s\n", student, codeword);
        fprintf(stderr, "Generating video for student %s with codeword %s\n",
                student, codeword);

        // Parse bits from 4 character code
        for (a=0 ; a<4 ; ++a)
        {
            char c = codeword[a];
            bits[a*10] = 1; // start bit
            for (b=0 ; b<8 ; ++b) bits[a*10+1+b] = (c >> b) & 1u; // 8 data bits
            bits[a*10+9] = 0; // stop bit
        }

        // Render background image for current student number
        if (debug) fprintf(stderr, "Creating background image for frames\n");
        char command[1024];
        sprintf(command, "convert +antialias -density 90 -background gray -fill "
                         "black -size %dx%d -pointsize 72 -gravity South "
                         "-depth 24 label:%s gray:temp.bin", W, H, student);
        system(command);

        // Load background image in 'zero' and 'one' frame templates
        if (debug) fprintf(stderr, "Creating 'zero' and 'one' frames\n");
        FILE *ftemp = fopen("temp.bin", "rb");
        fread(&p[0][0], H*W*3, 1, ftemp); // 'zero' frame background
        fclose(ftemp);
        memcpy(&p[1][0], &p[0][0], H*W*3); // 'one' frame background
        for (m=0 ; m<2 ; ++m)
            for (y=H/2-L/2 ; y<H/2+L/2 ; ++y)
                for (x=W/2-L/2 ; x<W/2+L/2 ; ++x)
        {
            i = 3*(y*W+x);
            p[m][i] = m * 255;
            p[m][++i] = m * 255;
            p[m][++i] = m * 255;
        }

        // Write frames to video by piping them to ffmpeg
        if (debug) fprintf(stderr, "Writing frames to video\n");
        errno = 0;
        sprintf(command, "ffmpeg -hide_banner -loglevel error -y -f rawvideo "
                         "-vcodec rawvideo -pix_fmt rgb24 -s %dx%d -r 30 -i - "
                         "-f mp4 -q:v 5 -an -vcodec mpeg4 %s.mp4", W, H, student);
        //fprintf(stderr, "Command: %s\n", command);
        FILE *pout = popen(command, "w");

        if (!pout)
        {
            fprintf(stderr, "Error opening pipe to ffmpeg. errno=%d\n", errno);
            break;
        }

        // Write the equivalent of 20 zero bits as preamble before transmission
        if (debug) fprintf(stderr, "Writing preamble zero frames\n");
        for (b=0 ; b<20 ; ++b) for (i=0 ; i<6 ; ++i)
            fwrite(&p[0][0], W*H*3, 1, pout);

        // Write frames for the 40 bits in the transmission (n frames per bit)
        if (debug) fprintf(stderr, "Writing data frames\n");
        for (b=0 ; b<40 ; ++b) for (i=0 ; i<6 ; ++i)
            fwrite(&p[bits[b]][0], W*H*3, 1, pout);

        // Write the equivalent of 20 zero bits as padding after transmission
        if (debug) fprintf(stderr, "Writing padding zero frames\n");
        for (b=0 ; b<20 ; ++b) for (i=0 ; i<6 ; ++i)
            fwrite(&p[0][0], W*H*3, 1, pout);

        // Flush and close pipe to ffmpeg
        if (debug) fprintf(stderr, "Flushing and closing pipe\n");
        fflush(pout);
        pclose(pout);

        // Create a browser-friendly webm version of the video
        if (debug) fprintf(stderr, "Converting video to webm format\n");
        errno = 0;
        sprintf(command, "ffmpeg -hide_banner -loglevel error -y -i %s.mp4 "
                         "-f webm -vcodec libvpx-vp9 -vb 1024k %s.webm",
                         student, student);
        if (system(command))
        {
            fprintf(stderr, "Error using ffmpeg to convert video to webm. "
                            "errno=%d\n", errno);
            break;
        }
    }

    // Close input and output files
    fclose(finput);
    fclose(foutput);

    return 0;
}

Online validator code

This is the PHP code for the online validator (validator.php):

<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

// Array containing code words
$codes = [
    "C12345678" => "Demo",
    "C87654321" => "abcd",
    // etc, etc
];

// Default strings
$correct_code = 0;
$student_id = "";
$code_word = "";

// Max length for each submitted string
$max_length = 100;

// If form was submitted, update strings
if ( $_SERVER['REQUEST_METHOD'] === 'POST' && 
      isset($_POST['student_id']) && isset($_POST['code_word']))
{
  // Retrieve student_id and code_word from POST request parameters
  // Limit the length in characters of each time to $max_length (set above)
  $student_id = strtoupper(substr(trim($_POST['student_id']), 0, $max_length));
  $code_word = substr(trim($_POST['code_word']), 0, $max_length);

  if (array_key_exists($student_id, $codes))
  {
    if ($codes[$student_id] == $code_word && $code_word != "")
    {
        $correct_code = 1;
    }
  }
}
?>

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="../jotz.css">
  <title>EEPP2 Binary Signal Detective validator</title>
</head>
<body>

<pre class="asciiart">
 ____  _                          ____  _                   _ 
| __ )(_)_ __   __ _ _ __ _   _  / ___|(_) __ _ _ __   __ _| |
|  _ \| | '_ \ / _` | '__| | | | \___ \| |/ _` | '_ \ / _` | |
| |_) | | | | | (_| | |  | |_| |  ___) | | (_| | | | | (_| | |
|____/|_|_| |_|\__,_|_|   \__, | |____/|_|\__, |_| |_|\__,_|_|
                          |___/           |___/               
 ____       _            _   _           
|  _ \  ___| |_ ___  ___| |_(_)_   _____ 
| | | |/ _ \ __/ _ \/ __| __| \ \ / / _ \
| |_| |  __/ ||  __/ (__| |_| |\ V /  __/
|____/ \___|\__\___|\___|\__|_| \_/ \___|

</pre>


<h1>Binary Signal Detective Validator</h1>

<?php
$webm_filename = "$student_id.webm";
$mp4_filename = "$student_id.mp4";
if (file_exists($webm_filename)) 
{
  echo "<p>Video download links:</p>" . PHP_EOL;
  echo "<ul>" . PHP_EOL;
  echo "<li><a href=\"$webm_filename\">$webm_filename</a></li>" . PHP_EOL;
  echo "<li><a href=\"$mp4_filename\">$mp4_filename</a></li>" . PHP_EOL;
  echo "</ul>" . PHP_EOL;
}
else
{
  echo "<p>To retrieve your video, just type in your student number, leave the code word blank, and press the button to submit.</p>";
}  
?>

<form action="validator.php" id="validator_form" method="post">
  <label for="student_id">Student number:</label><br>
  <input type="text" id="student_id" name="student_id" value="<?php echo($student_id) ?>"><br>
  <label for="lname">4-character code word (case sensitive):</label><br>
  <input type="text" id="code_word" name="code_word" value="<?php echo($code_word) ?>"><br><br>
  <input type="submit" value="Validate code word / Get video link">
</form> 

<div style="font-size:200%;">

<?php
if ($correct_code)
{
  echo "<br>Congratulations $student_id,<br>\"$code_word\" is the correct code! 🎉🎉🎉<br>";
}
else if ($code_word != "" && $student_id != "")
{
  echo "Sorry $student_id,<br>\"$code_word\" is not the correct code. 😢<br>";
}
echo "<br>";
?>
</div>

<?php
if (file_exists($webm_filename))
{
  echo "<video controls autoplay loop>";
  echo "<source src=\"$webm_filename\" type=\"video/webm\">";
  echo "Your browser does not support the video tag.";
  echo "</video>";
}
?>

</body>
</html>

Arduino code to record the signal from the screen

void setup()
{
  pinMode(2, OUTPUT);
  Serial.begin(115200);
}

void loop()
{
  // Limit printing to one sample every 20 ms
  static unsigned long t_prev = 0;
  while(millis() < t_prev + 20)
  {
    // I'm using an LED on pin D2 to do a
    // quick check that the loop function actually
    // arrives back at this point within 20 ms
    if (t_prev) digitalWrite(2, HIGH);
  }
  t_prev += 20;

  // To reduce 50 Hz interference, the printed sample is actually
  // the average of 4 samples taken at 5 ms intervals
  int n = 4, v = 0;
  while(n)
  {
    v += analogRead(6);
    if (--n) delayMicroseconds(500);
  }
  v /= 4;

  Serial.println(v);
}