This project is on hold for now

Record GPS information to a Video Camera

The following describes a way of annotating video with GPS (location) information (GPS and Video synchronization). It uses the video camera audio channel to store positioning information that is generated by a custom device described below. Video GPS annotation is performed without an external PC/laptop/GPS recorder etc. The price to construct this is in the range of 50€, GPS included.


You are planning your road trip, but the only photos you find on the internet are hotel close-ups and some well-known places not typical of the whole route. Imagine this:
You have an electronic road map on your PC, you click two points on a road and you can see a video of the course between those two points! That would be a great trip advisor. I started working on this idea in 2005, then left home for my military service. Since then google and microsoft already started to provide this service called 'Street level zoom' on their e-map web pages. (, But probably they will never cover the whole world road network.
Moreover using this device anyone can build his own "Street Level" electronic map, containing his own memories and experiences and "drive" again the same roads after years!.

Approach: convert GPS NMEA data to audio

The obvious way to record GPS information this is to connect both a gps device and a video camera to a PC, and record the two signals simultaneously. That way there will be a correspondance between gps positioning and time position in a video file. Another solution might be to synchronize GPS with video offline: record gps information separately and combine it afterwards with a video file based on their timestamp, supposing the video camera has an accurate enough clock.

Since I wanted to use this device on a motorcycle, I devised something more compact: convert data coming from a GPS device into audio signal. The latter can then be fed to the 'audio in' connection of a video camera and be recorded as one of the two available audio channels (DV video cameras support 2 or 4 channels). That way all video recorded will have an equivalent synchronized GPS information on the audio channel.

I used a cheap serial (RS-232) Sirf-II GPS (Model:AQ-MAX aq-300, ebay price: about 15€) to supply NMEA data. I chose to generate simple DTMF pulses since it's a cheap, very easy and noise-proof way to transmit data. DTMF pulses are generated by an MT8880 tranceiver chip (only the transmitter part is used). Everything is been controled by a PICAXE 18X which also outputs GPS readings to an LCD screen.

Approx. dimensions: 12x8cm

And here's a small audio file with DTMF output of this device, recorded in Athens, Greece. Here are the software decoded DTMF pulses. If arranged horizontaly you may see they represent the memory contents as stated in the comments of the source code. The first sequence reads:

A        185452  375865400 0234542470 05   00000 ### 300507 01839 B
The four main data screens in the LCD resemble to the following:
Time: hh:mm:ss
Date: DD/MM/YY
LT: 37o58.5376'N  
SPD: xx.13 Kts
HDG: xxx Degrees
Alt: 0183.9m
Sats Used: 05


Trim hardware down, put everything in a box Done
Field test it with a camera Done
• Create custom dtmf decoding/video mapping software. Almost done.
• Create custom map/video viewing software: completed prototype in matlab. In progress
• Make a descent PCB prototype
• Try speeding up dtmf pulses to increase gps resolution. (Hmm)
• Create a gym bicycle that can navigate inside virtual-but-real streets!


Picaxe Code

Some Comments

Picaxe chips are great: easy, versatile and cheap, they don't need a programmer, but have their limitations. It was tricky to decode NMEA messages since they are variable length and picaxe couldn't read the entire line in memory or in its variables.
For example take a look at the following possible NMEA time/date GPRMC message parts:

Both sentences seem to be valid:


Here are all possible UTC time/date-part positions with the equivalent label names in the code that handle them (date1-date8)

x.xx,,230705,------- date1
xx.xx,,230705,------ date2
xxx.xx,,230705,----- date3
x.xx,x.xx,230705,--- date4
x.xx,xx.xx,230705,-- date5
xx.xx,x.xx,230705,-- date5
x.xx,xxx.xx,230705,- date6
xx.xx,xx.xx,230705,- date6
xxx.xx,x.xx,230705,- date6
xx.xx,xxx.xx,230705, date7
xxx.xx,xx.xx,230705, date7
xxx.xx,xxx.xx,230705 date8

The following code copes (hopefuly) for all possible message length variations. The memory gets filled by data which are then fed to the DTMF chip.

Picaxe Listing

'Read gps NMEA data and convert to DTMF audio
'using a picaxe 18X and MT8880/CM8880 DTMF tranceiver
'Can be used to record gps data to the audio channel of a video tape
'to synchronize video with gps information
'(c) 2005 Spiros Ioannou -- sivann {at}
'Written by sivann 7/Aug/2005
'18X firmware 5
' firmware 8.5

'RAM gets filled like this:
'address	bytes		what		example		format
'80-85		6		UTCTIME:	164924.435		hhmmss.sss (From GPRMC-1)
'86-94		9		LATITUDE:	3758.5376,N		ddmm.mmmm,N (or S)(From GPRMC-2)
'95-104		10		LONGITUDE:	02347.1589,E	dddmm.mmmm,E (or S)(From GPRMC-3)
'105-106	2		Sats Used:	07			0-12 (From GPGGA-4)
'107-111	5		speed:	xx0.13 (Knots)	(From GPRMC-5)
'112-114	3		direction:	xxx.--		(From GPRMC-5)
'115-120	6		date:		230705		ddmmyy (From GPRMC-6)
'121-125	5		altitude:	x252.8		Meters (From GPGGA-4)
'200 from 0 to 4, what to display in lcd

' pin connections:
'     7       6    5    4     3   2   1   0
'232out CS RW RS0  D3 D2 D1 D0

'NMEA example:
'  1) UTC Time (hhmmss.sss)
'  2) Status, A=data valid or V=data not valid
'  3) Latitude ddmm.mmmm
'  4) N or S
'  5) Longitude  dddmm.mmmm
'  6) E or W
'  7) Speed over ground, knots
'  8) Course over ground (degrees)
'  9) Date, ddmmyy
' 10) Magnetic Variation, degrees
' 11) E or W
' 12) Checksum 

'serout 7,N2400,(254,128) ' move to start of first line
'serout 7,N2400,(254,1) ' Clear Display (must be followed by a 'pause 30' command)
'254,128 Move to line 1, position 1
'254, y Move to line 1, position x (where y = 128 + x)
'254,192 Move to line 2, position 1
'254, y Move to line 2, position x (where y = 192 + x)

'SYMBOL RS_p   = 4 ' Register-select pin (0=data). 
'SYMBOL RW_p   = 5 ' Read/Write pin (0=write). 
SYMBOL CS_p   = 6 ' Chip-select pin (0=active). 
SYMBOL digit  = b2 'digits to dial. 
'SYMBOL SIN_p  = 1 'pin connected to rs232 in (2)
SYMBOL SIN_p  = 7 'pin connected to rs232 in (2)
SYMBOL SOUT_p = 7 'pin connected to rs232 out (3) or serial LCD
SYMBOL SOUT_speed=N2400

pause 500 'for the lcd to initialize
poke 200,0

gosub clearlcd
serout SOUT_p,SOUT_speed,("GPS2DTMF v0.1")
serout SOUT_p,SOUT_speed,(254,192)
serout SOUT_p,SOUT_speed,("(C) sivann 2005")
pause 2000

'init MT8880
'let pins = 255  ' All pins high to deselect MT8880. 
let pins = %01111111
let pins = %00011011 ' Set up CRA, next write to CRB.
high CS_p 
let pins = %00010000 ' Clear register B; ready to send DTMF. 
high CS_p 

	high CS_p  ' just to be sure deselect audio chip 
	gosub clearlcd
   	serout SOUT_p,SOUT_speed,("Waiting $GPRMC") 'debug
   	pause 100

	gosub gettimefix

	if b12 = "A" then validfix
	gosub clearlcd
	serout SOUT_p,SOUT_speed,("Invalid Sat. Fix")
	pause 1500
	goto mainloop

'--- Read data and store in RAM
	gosub clearlcd
	serout SOUT_p,SOUT_speed,("Fix VALID")
	serout SOUT_p,SOUT_speed,(254,192) 'goto line 2
	serout SOUT_p,SOUT_speed,("Reading data...")

	'store UTC TIME
	poke 80,b1
	poke 81,b2
	poke 82,b3
	poke 83,b4
	poke 84,b5
	poke 85,b6

	gosub getlat
	if b12="V" then invalidfix
	'store LATITUDE
	poke 86,b1
	poke 87,b2
	poke 88,b3
	poke 89,b4
	poke 90,b5
	poke 91,b6
	poke 92,b7
	poke 93,b8
	poke 94,b9

	gosub getlong
	if b12="V" then invalidfix
	poke 95,b1
	poke 96,b2
	poke 97,b3
	poke 98,b4
	poke 99,b5
	poke 100,b6
	poke 101,b7
	poke 102,b8
	poke 103,b9	
	poke 104,b10

	gosub getsatalt 'must also read altitude on GGA

	poke 105,b1
	poke 106,b2
	if b3="," then hdop2 'hdop has two digits before comma
	if b4="." then h1alt1
	if b5="." then h1alt2'hdop has one digit,alt has two before comma
	if b6="." then h1alt3
	if b7="." then h1alt4
	goto sataltinv 'how tall is everest again?
	if b5="." then h2alt1
	if b6="." then h2alt2
	if b7="." then h2alt3
	if b8="." then h2alt4	
	goto sataltinv 'invalid

	poke 121,"0"
	poke 122,"0"	
	poke 123,"0"
	poke 124,b3
	poke 125,b5
	goto aftersatalt		
	poke 121,"0"
	poke 122,"0"	
	poke 123,b3
	poke 124,b4
	poke 125,b6
	goto aftersatalt		
	poke 121,"0"
	poke 122,b3	
	poke 123,b4
	poke 124,b5
	poke 125,b7
	goto aftersatalt		
	poke 121,b3
	poke 122,b4	
	poke 123,b5
	poke 124,b6
	poke 125,b8
	goto aftersatalt		
	poke 121,"0"
	poke 122,"0"	
	poke 123,"0"
	poke 124,b4
	poke 125,b5
	goto aftersatalt		
	poke 121,"0"
	poke 122,"0"	
	poke 123,b4
	poke 124,b5
	poke 125,b7
	goto aftersatalt		
	poke 121,"0"
	poke 122,b4	
	poke 123,b5
	poke 124,b6
	poke 125,b8
	goto aftersatalt			
	poke 121,b4
	poke 122,b5	
	poke 123,b6
	poke 124,b7
	poke 125,b9
	goto aftersatalt				
	for b0=121 to 125
		poke b0,"X" 'dateinv
	next b0

	gosub getspeed
	if b12="V" then invalidfix
	if b1="," then speedinv
	if b2="." then speed1
	if b3="." then speed2
	if b4="." then speed3
	goto speedinv

	poke 107,"0"
	poke 108,"0"
	poke 109,b1
	poke 110,b3
	poke 111,b4
	if b6="," then dirinv
	if b7="." then speed1dir1
	if b8="." then speed1dir2
	if b9="." then speed1dir3
	goto dirinv
	poke 112,"0"
	poke 113,"0"
	poke 114,b6
	goto afterspeed
	poke 112,"0"
	poke 113,b6
	poke 114,b7
	goto afterspeed
	poke 112,b6
	poke 113,b7
	poke 114,b8
	goto afterspeed

	poke 107,"0"
	poke 108,b1
	poke 109,b2
	poke 110,b4
	poke 111,b5
	if b7=","  then dirinv
	if b8="."  then speed2dir1
	if b9="."  then speed2dir2
	if b10="." then speed2dir3
	goto dirinv
	poke 112,"0"
	poke 113,"0"
	poke 114,b7
	goto afterspeed
	poke 112,"0"
	poke 113,b7
	poke 114,b8
	goto afterspeed
	poke 112,b7
	poke 113,b8
	poke 114,b9
	goto afterspeed

	poke 107,b1
	poke 108,b2
	poke 109,b3
	poke 110,b5
	poke 111,b6
	if b8=","  then dirinv
	if b9="."  then speed3dir1
	if b10="." then speed3dir2
	if b11="." then speed3dir3
	goto dirinv
	poke 112,"0"
	poke 113,"0"
	poke 114,b8
	goto afterspeed
	poke 112,"0"
	poke 113,b8
	poke 114,b9
	goto afterspeed
	poke 112,b8
	poke 113,b9
	poke 114,b10
	goto afterspeed

	for b0=107 to 111
		poke b0,"X" 'speed invalid or non-existent
	next b0

	'if speed is invalid, direction isn't read
	poke 112,"X"  ' direction invalid
	poke 113,"X"  
	poke 114,"X"  


	'gosub clearlcd
	'serout SOUT_p,SOUT_speed,("Waiting date")
	gosub getdate

'serout SOUT_p,SOUT_speed,("b0=",b0," b1=",b1," b2=",b2," b3=",b3," b4=",b4," b5=",b5," b6=",b6," b7=",b7," b8=",b8," b9=",b9," b10=",b10," b11=",b11," b12=",b12," b13=",b13,13,10)

	if b7=100 then date0
	if b3 <> "." and b6="," then date1
	if b4 <> "." and b7="," then date2
	if b1 = ","  then date3
	if b3 = ","  then date4
	if b4 = "," then date5
	if b5="," then date6
	if b6="," and b3="." then date7
	if b4="." then date8
	goto dateinv

	poke 115,b1
	poke 116,b2
	poke 117,b3
	poke 118,b4
	poke 119,b5
	poke 120,b6
	goto afterdate	
	poke 115,b0
	poke 116,b1
	poke 117,b2
	poke 118,b3
	poke 119,b4
	poke 120,b5
	goto afterdate	
	poke 115,b1
	poke 116,b2
	poke 117,b3
	poke 118,b4
	poke 119,b5
	poke 120,b6
	goto afterdate	
	poke 115,b2
	poke 116,b3
	poke 117,b4
	poke 118,b5
	poke 119,b6
	poke 120,b7
	goto afterdate	
	poke 115,b4
	poke 116,b5
	poke 117,b6
	poke 118,b7
	poke 119,b8
	poke 120,b9
	goto afterdate	
	poke 115,b5
	poke 116,b6
	poke 117,b7
	poke 118,b8
	poke 119,b9
	poke 120,b10
	goto afterdate	
	poke 115,b6
	poke 116,b7
	poke 117,b8
	poke 118,b9
	poke 119,b10
	poke 120,b11
	goto afterdate	
	poke 115,b7
	poke 116,b8
	poke 117,b9
	poke 118,b10
	poke 119,b11
	poke 120,b12
	goto afterdate	
	poke 115,b8
	poke 116,b9
	poke 117,b10
	poke 118,b11
	poke 119,b12
	poke 120,b13
	goto afterdate	
	for b0=115 to 120
		poke b0,"X" 'dateinv
	next b0

	gosub clearlcd
	serout SOUT_p,SOUT_speed,("Writing data...")
	'now all data is in RAM
	'output DTMF
	'start sequence
	gosub dtout
	for b0 = 80 to 125
		peek b0,b1
		gosub dtout
	next b0
	'stop sequence
	gosub dtout

	gosub clearlcd
	peek 200,b13
	branch b13, (lcd0,lcd1,lcd2,lcd3)
	peek 80,b1 'Time, hhmmss
	peek 81,b2
	peek 82,b3
	peek 83,b4
	peek 84,b5
	peek 85,b6
	serout SOUT_p,SOUT_speed,("Time: ",b1,b2,":",b3,b4,":",b5,b6)
	peek 115,b1 'Date, ddmmyy
	peek 116,b2
	peek 117,b3
	peek 118,b4
	peek 119,b5
	peek 120,b6
	serout SOUT_p,SOUT_speed,(254,192) 'goto line 2
	serout SOUT_p,SOUT_speed,("Date: ",b1,b2,"/",b3,b4,"/",b5,b6)
	goto afterlcd	
	'86-94		9	LATITUDE:	3758.5376,N
	'95-104		10	LONGITUDE:	02347.1589,E
	peek 86,b1
	peek 87,b2
	peek 88,b3
	peek 89,b4
	peek 90,b5
	peek 91,b6
	peek 92,b7
	peek 93,b8
	peek 94,b9
	serout SOUT_p,SOUT_speed,("LT: ",b1,b2,223,b3,b4,".",b5,b6,b7,b8,"'",b9)
	peek 95,b1
	peek 96,b2
	peek 97,b3
	peek 98,b4
	peek 99,b5
	peek 100,b6
	peek 101,b7
	peek 102,b8
	peek 103,b9
	peek 104,b10
	serout SOUT_p,SOUT_speed,(254,192) 'goto line 2
	serout SOUT_p,SOUT_speed,("LG:",b1,b2,b3,223,b4,b5,".",b6,b7,b8,b9,"'",b10)
	goto afterlcd
	'107-111	5	speed:		xx0.13
	'112-114	3	direction:	xxx.--
	peek 107,b1
	peek 108,b2
	peek 109,b3
	peek 110,b4
	peek 111,b5

	peek 112,b6
	peek 113,b7
	peek 114,b8
	serout SOUT_p,SOUT_speed,("SPD: ",b1,b2,b3,".",b4,b5," Kts")
	serout SOUT_p,SOUT_speed,(254,192)
	serout SOUT_p,SOUT_speed,("HDG: ",b6,b7,b8,223)
	goto afterlcd:
	'121-125	5	altitude:	x252.8
	peek 121,b1
	peek 122,b2
	peek 123,b3
	peek 124,b4
	peek 125,b5
	serout SOUT_p,SOUT_speed,("Alt: ",b1,b2,b3,b4,".",b5,"m")
	serout SOUT_p,SOUT_speed,(254,192)
	'105-106	2	Sats Used:	07		0-12 
	peek 105,b1
	peek 106,b2
	serout SOUT_p,SOUT_speed,("Sats Used: ",b1,b2)	

	goto afterlcd:

	;rotate counter
	'peek 200,b13
	poke 200,b13
	pause 6000
goto mainloop

'------ Read from Serial Port -------------------------------------------------------------------------------------------------

'-- gettimefix ------
	'b12 contains A/V
	serin SIN_p, N4800, ("$GPRMC,"), b1,b2,b3,b4,b5,b6, b0,b0,b0,b0,b0,b12

'-- GETLAT --------
	'b9 contains N/S
	'b12 contains A/V
	serin SIN_p,N4800, ("$GPRMC,"), b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0, b12, b0, b1,b2,b3,b4, b0,b5,b6,b7,b8,b0, b9

'-- GETLONG --------
	'b10 contains E/S
	'b12 contains A/V
	serin SIN_p,N4800,("$GPRMC,"),b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0, b12, b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0, b1,b2,b3,b4,b5, b0, b6,b7,b8,b9, b0, b10

'--getsatalt -------
	serin SIN_p,N4800,("$GPGGA,"),b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0, b1,b2, b0,b0,b0,b0,b0, b3,b4,b5,b6,b7,b8,b9

'-- GETSPEED -------
	'b12 contains A/V
	serin SIN_p,N4800,("$GPRMC,"),b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0, b12,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0, b1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11

'--- GETDATE -------
	peek 107,b0
	if b0="X" then dt1 'speed invalid
	serin SIN_p,N4800,("$GPRMC,"),b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,  b0,b0,b0,b0,b0,b0,b0, b1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11,b12,b13
	goto dtret
	serin SIN_p,N4800,("$GPRMC,"),b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,  b0,b0,b1,b2,b3,b4,b5,b6

'-- DTMFOUT --------------------------------
dtout:	'output appropriate dtmf based on value of b1

	'serout SOUT_p,SOUT_speed,("#b1=",#b1,"(",b1,")")
	if b1>=49 AND b1<=57 then L19	'1-9
	if b1=48 then L0		'0
	if b1=44 then LC		',
	if b1=46 then LP		'.
	if b1=69 then LE		'E
	if b1=87 then LW		'W
	if b1=78 then LN		'N
	if b1=83 then LS		'S
	if b1=65 then LA		'A
	if b1=86 then LV		'V
	if b1=91 then LSTART	'[
	if b1=93 then LEND		']
	if b1=88 then LX		'X

	b3=b1-48 			'numbers. convert ascii char to dec and store to b3
	goto dtcont
	pins=10 			'0 is after 9
	goto dtcont
	pins=15 			'C
	goto dtcont
	pins=11 			'*
	goto dtcont
	pins=10 			'E=0
	goto dtcont
	pins=1 				'W=1
	goto dtcont
	pins=10 			'N=0
	goto dtcont
	pins=1 				'S=1
	goto dtcont
	pins=10 			'A=0 (data valid)
	goto dtcont
	pins=1 				'V=1 (data invalid)
	goto dtcont
	pins=14 			'B ends sequence
	goto dtcont
	pins=13 			'A starts sequence
	goto dtcont
	pins=12 			'# invalid/non-existent field
	goto dtcont

	high CS_p ' Done with write. 
	pause 250 ' Wait to dial next digit. DTMF pause.

'-- DTMFOUT end --------------------------------

	serout SOUT_p,SOUT_speed,(254,128) ' move to start of first line
	serout SOUT_p,SOUT_speed,(254,1) ' Clear Display (must be followed by a 'pause 30' command)
	pause 30



After having the video file (.avi) with the dtmf on the audio channel the following matlab code can be used to extract DTMF and make a virtual drive.

1. Decode DTMF

Decode DTMF from audio (.wav) file (extracted from .avi) dtmfdec.m. You also need dtmfparse.m (called from dtmfdec.m).

2. Drive

Running this will "drive" you in the video at constant driving speed of your choosing, regardless of the vehicle (camera) speed at the time of the filming. drive.m Speed can be set in variable curspeed (line 105) and is in m/s. 4.1 m/s =~ 15Km/h.


This is a Borland C++ Builder implementation using Goertzel dtmf detection. After running avdtmfdec.exe, open the .avi file (with uncompressed, not compressed audio channel, you may need to recode the original .avi). This will show the video and a small google maps position on the lower right. To compile you will also need the libraries from

Borland sources

[Go Back]

© 2005-2007 Spiros Ioannou