_ _ | |_ ___ __| |____ ___ _ _ | __/ _ \/ _` |_ / / _ \ | | | | || __/ (_| |/ / | __/ |_| | \__\___|\__,_/___(_)___|\__,_|
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:
//
// 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;
}
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>
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);
}