One of the most exciting aspects of a mobile device is its ability to let you to interact with your surroundings. This article takes a look at a method of controlling consumer appliances (stereo, DVD player, VCR, television, hobby robots, etc...) using their built-in infrared (IR) receivers. There are several good applications available that allow you to send/receive IR signals from your Pocket PC, but they all suffer from the same problem: the IRDA port on most models is too weak to produce a consumer IR signal that can go further than 8 feet. The clever developers at Griffin Technology came up with a nifty device that plugs into your mobile PC's audio port and can send much stronger signals. My hat is off to their developers for coming up with this idea, as it is truly inventive. This article will explore how they can accomplish the feat of sending of consumer IR signals using, well, ... sound, and how you can use it in your own applications.
Note: This is NOT a paid advertisement, and I have absolutely no relationship with the Griffin Technology people. Rather, when this device first came, I contacted Griffin about getting access to an SDK so that I could incorporate the sending of IR codes in my own applications. I was very disappointed with the one-line response I received and the attitude towards consumers in their postings on other web forums. So I decided to work work this puzzle out for myself. Hey, how hard could it be? Famous last words.
Background - IR Signals in a nutshell (or a very very small box)
First things first: What is an IR code?
There are articles on the web that can explain IR signals in much greater detail than I can here. For example, Barry Gordon has a great write-up about how infrared signaling works over at RemoteCentral.com. That site also has loads of IR codes in their IR database that you can use to control your own devices. But anyway, I will take crack at explaining simple consumer IR signaling in a nutshell:
When you use your remote control to (say) turn on your television, you are really sending a command to the TV on a beam of infrared (IR) light. Unlike a flashlight, the IR light is not visible to the naked eye so you won't actually see the beam. But the sensitive IR receiver on your television can "see" it, and uses the information it contains to perform a given command. What command should it perform? Well, the beam of IR light that your remote control sends is really comprised of many little ON and OFF pulses of light. The combination of ON and OFF pulses determines the exact command that your television should perform. One set of ON and OFF pulses means TURN ON, and another means CHANNEL UP. A diagram of what is happening can be seen below:
Now, behind the scenes, your television remote is doing a little extra work than is outlined above. Yes, it sends a series of ON and OFF pulses and their sequence determines the specific command it is trying to send (power on, power off, change channel, etc...). But these ON and OFF pulses are really made up of a wave of IR light at a given carrier frequency. All this means is that the long pulse of ON is comprised of many little ON/OFF flickering of IR light. The IR light goes on-off-on-off-on-off... for a duration of the total ON time. Each little on-off duration is at the carrier frequency.
This carrier frequency serves several purposes including power savings and the better handling of "noise". (Note: even though IR signals are at a higher wavelength than visible light, they are still subject to the introduction of noise produced by visible light and other radiating sources). The carrier frequency varies from remote-to-remote and brand-to-brand. Normally, consumer IR remotes operate in the 36KHz (that's 36,000 cycles per second) to 40Khz range. Remember this range, as it is very important later on. An example of a wave using a carrier frequency can be seen below:
Background - Code breaking
The last section was a very brief description of how a remote control sends its code using an Infrared (IR) wave. In this section, we will take a quick (and I mean quick) look at what the remote control sends. This is known as the IR code. Remember, in order for a remote control to send different commands, it needs to send a specific IR code for each command. This way, the receiving unit (say the television) can distinguish between a channel up command or a power off command.
A popular example of an IR code format is the Pronto CCF format. The Pronto is a consumer device made by Philips that learns your remote control codes and allows you to setup your own user-interface to play them back. When the Pronto learns your remote codes, it is basically analyzing the IR wave your remote control produces when you press a given button. This is the same wave we discussed above. It is the unique series of ON/OFF values from this wave that gets stored as the IR code for that button. An example of a typical IR code (in Pronto format) is below:
0000 0067 000D 000D 0060 0019 0018 0018 0018 0018 0018 0018 0030 0018 0030 0018 0018 0018 0018 0018 0030 0018 0030 0018 0018 0018 0030 0018 0018 03FB 0060 0018 0018 0018 0018 0018 0018 0018 0030 0018 0030 0018 0018 0018 0018 0018 0030 0018 0030 0018 0018 0018 0030 0018 0018 03E5
There is nothing like a big long string of Hex codes to make you reach for another dose of aspirin and a cup of coffee. But in this case, hold off. The code above is really quite simple and shouldn't cause you overdue stress. The first 4 values are just a descriptive header. The rest of the code is comprised of 1 or 2 signals (depending on the header).
Header: 0000 0067 000D 000D
The header is made up of 4 words. The first word (0x0000 in this case) specifies the code format. For our purposes, this will always be 0000 denoting a "Raw" format. There are other values for this indicating different types of encoding. Feel free to read up on those other format types, but we won't be covering them here. The second word (0x0067 = 103 decimal) is the frequency divider of the carrier signal, which is relative to the Pronto's internal clock that operates at 4,145,146Hz. So the carrier frequency for this IR code is really 4,145,146Hz /103 = 40,244Hz. The next words in the header describe what signals are contained within the code.
The third word (0x000D = 13 decimal) is known as the "once count" and indicates that this IR code has 13 on/off pairs of data known as signal bursts. The "once" signal is the signal that is sent if you press and let-go of a key on your remote control. Note that this value indicates the number of signal pairs, which means there will really be 2*Value of data for this signal. In this case, 13 pairs means we will be reading 26 values. The values are the alternating ON/OFF duration of the code. So you can see (using the color coding) that the green "once" signal starts with an ON value of 0x0060 followed by an OFF value of 0x0019 followed by an ON value of 0x0018 followed by... well, you get the picture. If the value of this word is 0x0000, it means that there is no "once" code, and that you should always send the "repeat" code no matter if you are pressing the key once, or repeating.
The fourth word (0x000D = 13 decimal) is the "repeat count" and indicates that there are also 13 signal bursts in the "repeat" signal. The "repeat" signal is the signal that is sent if you hold a key down (say, the volume key) on your remote control. If you hold a key, the "once" signal is sent followed by as many repetitions of the "repeat" signal as possible (while you are holding the remote control key down). When you let go off the key, the remote stops sending. So again using the color coding, you can see that the purple signal is the repeat signal. If the value of this word is 0x0000, it means there is no "repeat" code and the key doesn't repeat.
That's it! An extremely brief overview of a summarized version of a condensed beginner's guide to the workings of consumer IR codes. Now go grab yourself that coffee and let's get on with the meat of the article.
Mystery Unfolds - How do they do it?
Now that we know a little about IR codes and how they are built, the real question is: How can we send IR waves through the audio port of our PDA? The Total Remote comes with software to accomplish this, but I really would like to have the ability to do this from my own mobile application. So I broke out my trusty/dusty USB oscilloscope, a pad and paper, an old Sony VCR, and a pack of Mentos candy and started to investigate this conundrum myself. Four hours and 2 cavities later, I came up with how to send the proper signals through the audio port to tell my VCR to start/stop/play/etc...
To work on this problem, I wrote down a list of questions I thought needed answering in order to come to the solution. My overriding goal here was to figure out the how without actually breaking open the hardware. You see, I am cheap and clumsy. So prying open this little piece of hardware with the tools of choice (hammer and vice grips) would mostly likely lead to me right into buying a replacement. This scenario did not fall within my zero-budget. So I had to do the cheapest thing I could and actually exercise the mental muscle. Here is a list of what came to mind:
1. How can an audio signal turn into a 40Khz carrier signal?
By examining the Total Remote hardware itself, the answer to this question became apparent. The hardware has 2 IR emitting LEDs on the top, and a stereo mini-jack plug which connects it to the PDA. I was very puzzled by the 2 separate LEDs. At first, I thought they used 2 in order to give a wider transmission angle. In other words, 2 LEDs spaced slightly apart might beam a signal cone wider than just a single LED. But further examination and reflection proved this was not necessarily the case.
Next, I was wondering how I could send any 40Khz signal through the audio port. You see, audio ports are manufactured to really only pass signals within the range of human hearing which tops out around 20Khz. Anything over 20Khz would be squashed by either noise filtering or simply the performance of the port itself. So how could I possibly get a 40Khz signal from a 20KHz port? What I really needed was a port that could pass twice the bandwidth. Twice. Two. Hrm... where have I heard that number before....two ... I just ate two Mentos, no.... there were those twins I knew in college, no ....waittaminnut! Could it be a coincidence that there are two LEDs and that I need twice the bandwidth. Yeah, probably just coincidence.
No, wait! Something I was forgetting is that, sure, the audio port is limited to around 20KHz, but the audio port is in stereo. This means there are two channels (left and right), each capable of sending a 20KHz audio signal. Heyyy, maybe each of those LEDs is wired to a separate channel and would account for the stereo mini-jack plug they used. But even so, how can I get 40Khz from a 20KHz channel? The answer is to send a 20KHz signal through each channel and to send one signal out-of-phase with the other. This will double the effective frequency of the emitted signal, thus giving us the 40Khz we need to send carrier signals.
2. How can we get enough power from the audio port to drive the IR signal further than a few feet?
OK, so we figured out how it was theoretically possible to send a 40Khz signal through the IR port, but that would be useless unless we can send it a reasonable distance. After all, what good is a remote control that you have to get yourself out of your chair to use! I did some web searches and I found that audio ports on PDAs are not really known for being very powerful. So I wondered how much power the Total Remote software was generating from the audio port. There was only one way to conclusively find out: bring out the oscilloscope!
I plugged in my handy dandy USB oscilloscope, ran the Total Remote software, pressed a key to send a signal, and recorded the output. What I saw baffled me at first. Here is a screenshot of the beginning of the signal:
Yes, I pretty much had the same reaction as you, looking at the beginning of the signal. What the heck is that? With a little more observation, I calculated that when I pressed a remote control key (in the software), the signal that was generated started with this "preamble" of a 500Hz wave on both channels that was out-of-phase. After this preamble, I could see what I expected: periods of on/off signal bursts that represented the IR code. This looked like:
So, I was curious. What is the purpose of the preamble? The answer appears to be that in order to boost the power from the audio port to send the IR signal a reasonable distance, we need to "charge up" the total remote. My guess (because I never really pulled out the hammer and opened the hardware) is that inside there is a little capacitor tuned to this preamble signal. The preamble charges up the capacitor and this power is subsequently released when the rest of the IR signal is sent. This power boost enables the IR signal to travel further. Again, this is just a guess. Whether I am right or wrong is immaterial as long as I can reproduce the same preamble.
3. How can I send a signal through software?
Ok, so at this point, I had an idea of what needed to be accomplished in order to send an IR signal using the Total Remote. I needed to create an out-of-phase stereo audio signal that included a "preamble" of 500Hz to charge up the total remote, followed by on/off signal bursts of the given carrier frequency. If I could create this signal in some sort of audio format, I could simply play the sound file to send the IR command. And what is the first thing that comes to mind when you think of raw audio files?
Doing the WAV
So my mission is clear. I need to create raw audio files that represent the IR code I want to send, and each audio file should have a "preamble" to charge up the Total Remote. I believe the WAV file format is perfectly suited for this task. It is well documented, and has the flexibility to handle my needs. I won't go full-bore into the structure of the WAV file format but you can easily Google the information from the web. Stanford.edu has a good short description of the WAV format, and there is a different one over at SonicSpot.
When you download my source code, you will see only three classes that I created to support generating and sending the audio files. The
PCMWaveGen object encapsulates the writing of the actual WAV file. The
IRWav class is derived from
PCMWaveGen. It takes an IR Code and generates the proper wav file, and optionally sends it to the Total Remote (audio port). Finally, we have the
ProntoCCF object which encapsulates the file format used to input IR codes.
This class encapsulates the writing of a WAV file. If you look at the source code, you will see it basically consists of a list of properties and several
Write methods. All of the properties map to properties in the the WAV file format. The basic WAV file consists of headers followed by sample data. The sample data is really data at-a-point-in-time, or a "sampling" of the output stream. The headers just describe what kind of sample data to expect. An overview of the file format layout looks something like:
Section 1: RIFF Header Chunk
Section 2: FMT sub-chunk
Section 3: Data sub-chunk
PCMWaveGen object is really not that interesting by itself, but the two methods to note are the
WriteFormat methods. At the very beginning of a WAV file, there is a "RIFF" header. As you can see, it is very simple.
public bool WriteHeader(BinaryWriter writer)
The real meat-and-potatoes of the information is in the Format section. As you can see, it consists of the audio format, number of channels, bit rate, etc...
public void WriteFormat(BinaryWriter writer)
This simple class takes the string representation of a Pronto CCF formatted IR code and translates it into signal arrays. The signal arrays are burst pairs just like we discussed at the top of this article. Instead of creating yet another arbitrary format, I figured it would be easier in the long run to write a simple class that parses out the Pronto codes. To use this class, you only need to pass the raw IR code string to its
FromString() method. All of the processing is done for you.
If you are looking for IR codes to use with your own devices, I would suggest RemoteCentral.com. They have a great database of different brands of remote control codes that you can use. One thing to note: although a lot of times it is easy to grab an IR code from the web and have it work, they can sometimes be very temperamental. If you have a method of learning your own IR codes, this is much more reliable than using someone's code that might not work for you (for whatever reason). I only mention this because in the past, I have lost hours of work thinking there was an error in my code, when in reality, a simple change of the IR code was the answer.
This is the real workhorse class, as it encapsulates the generation of a WAV file from an IR code. To begin using this class, you only need to do the following:
ProntoCCF irCommand = new
ProntoCCF("0000 006D 001A 0000 ...rest of ir code here...");
IRWav totalRemote = new IRWav();
MessageBox.Show("Failed to send the signal");
When designing this class, I wanted to make it as efficient as possible. You see, creating WAV files on the fly is no easy task for a little 'ole PDA. Sure, it can handle it, but you really don't want the user waiting for 5 seconds every time you send an IR signal. To eliminate this waiting, I decided to cache the IR signals so that they only get generated once. They are cached by using a unique ID (GUID) that is associated with every IR command. So when you see methods that take a
Guid idCommand parameter, this is the reason. When you send a command and it doesn't find the ID in the cache, it will generate the signal and cache it for later use. The next time you send the signal, it looks up the ID form the cache and sends the pre-generated signal. If you want to just send a temporary signal (like in the above example), either leave off the ID or send
Guid.Empty in its place. If you do this, the code will generate the IR WAV file every time (and you won't gain the benefits of caching).
One thing to note: if you change the actual IR code, you must clear the cache before sending the new code. Otherwise, it will look in the cache and pull up the old pre-generated version and send that instead. As you can see, I provide two methods of clearing the cache. The first clears the entire cache, and the second clears the pre-generated signal for a given command.
public void ClearCache()
public void ClearCache(Guid idCommand)
string strFilename = CacheDirectory + idCommand.ToString();
When you send a signal using
IRWave, you will see several things going on. First, you will notice that we turn up the volume on the audio port to its maximum level. This is the equivalent of us saying we want to send the IR signal with maximum power. Remember in the earlier sections, how we discovered that preamble signal and how it is used to boost the power? Well, if we don't push the audio port's volume to maximum power before we send the signals, the preamble would be for nothing. So we save the current audio volume level and put it up to 100% power for maximum range. Next, we check the cache to see if the signal has already been created. If it is in the cache, we use it. Otherwise, we take the hit and generate the WAV file from the IR command. Finally, we put the volume back to its original level. I figured this was a nice touch, so that when you later plug your headphones in and queue up your Celine Dione MP3 collection, you won't get popped eardrums. Well, no more than normally at least.
public bool SendSignal(Guid idCommand, double irCarrier,
short signal, bool usePreamble)
bool success = true;
string strFilename = this.TempWavFilename;
strFilename = CacheDirectory + idCommand.ToString();
success = Sound.Play(strFilename);
success = false;
The next step in processing is to examine the
WriteIRWAVFile method. This method draws upon the base class (
PCMWavGen) to write the necessary information to create a valid WAV file.
WriteFormat generated sections #1 and #2 of the WAV file format. The 3rd section is generated by calling the
WriteWAVSignal method which (finally) gets to the issue of generating the proper audio WAV data.
public bool WriteIRWAV(BinaryWriter destWriter,
double carrierFreq, short signal, bool usePreamble)
bool success = true;
success = false;
WriteWAVSignal method creates audio data for the given IR code. If you can remember from previous sections, the IR code consists of a series of ON/OFF burst pairs. The ON time is comprised of many on-off transitions of the signal, the frequency of which is determined by the carrier frequency of the IR code. The OFF time is, well, simply time where we want to be absolutely quiet and send no signal at all.
public bool WriteWAVSignal(BinaryWriter writer, double irCarrier,
short irSignalPairs, bool usePreamble)
bool success = true;
long dataSizePos = writer.BaseStream.Position;
int dataSize = 0;
long dataStartPos = writer.BaseStream.Position;
bool high = true;
double time = 0;
double halfCarrier = irCarrier/2;
for(int i = 0; i < irSignalPairs.Length; i++)
if(i == irSignalPairs.Length-1 && !high)
this.SampleHigh = high?255:128;
this.SampleLow = high?0:128;
time = (double)irSignalPairs[i] / irCarrier;
high = !high;
dataSize = (int)(writer.BaseStream.Position-dataStartPos);
writer.BaseStream.Position = dataSizePos;
writer.BaseStream.Position = writer.BaseStream.Length;
success = false;
The last method of note creates the data for a stereo carrier wave. Originally, I created a method that calculated a carrier wave with a given phase-shift and called it twice to get a stereo wave. Due to speed issues, I rewrote this to calculate both channels at once. (The original method, although unused, is included in
IRWav). Also, I took one more caching step and cached the carrier wave that was generated. The IR codes that come from a single remote generally use the exact same carrier frequency. So I managed to eek out more speed improvement by caching the last carrier frequency. Then I simply copy enough of these carrier cycles into the destination signal that will fit in the given duration.
public void CreateStereoCarrier(BinaryWriter writer,
double carrierFreq, short cycles, bool phaseShift)
if(carrierFreq != _lastCarrierFreq || _lastCarrierWave == null
|| _lastCarrierWave.Length == 0 || _lastCarrierPhase != phaseShift)
_lastCarrierWave= new MemoryStream();
BinaryWriter bw = new BinaryWriter(_lastCarrierWave); double
samplesPerWave = this.SampleRate/carrierFreq; double
quant = 2*Math.PI/samplesPerWave; double
sampleValue = 0; byte
bytValue = 0; int
i = 0; double
delta = Math.Abs(this.SampleHigh-this.SampleLow); double
cycleDuration = 1 / carrierFreq;
double step = 1/ ((double)this.SampleRate);
for(double time = 0; time < cycleDuration; time += step)
sampleValue = delta/2 * (1 + Math.Cos(quant*i));
bytValue = (byte)(this.SampleLow+sampleValue);
bytValue = (byte)(this.SampleLow + delta - sampleValue);
_lastNullCarrierWave = new byte[_lastCarrierWave.Length];
for(int j= 0;j<_lastNullCarrierWave.Length;j++)
_lastNullCarrierWave[j] = (byte)(this.SampleLow+delta/2);
if(writer != null)
if(this.SampleHigh - this.SampleLow == 0)
for(short nI=0; nI < cycles/2; nI++)
for(short nI=0; nI < cycles/2; nI++)
The sample application is very simple and is meant to be nothing more than an illustration of how to use the underlying IR code. To really take advantage of the sample application, you need 2 things.
- First, you need to purchase or borrow a $29 Total Remote audio dongle. This is the hardware that fits in the audio port, as shown in the picture at the top of this article.
- Second, you need to locate valid IR code data for the devices you wish to control (television, VCR, DVD, etc...). This second part is the trickiest, but you should have good luck finding them in the places I mention in this article.
The actual sending of the IR data couldn't be simpler, as illustrated by the following snippet:
IRWav _totalRemote = new IRWav();
void SendIRCommand(Guid idCommand, DForge.IR.Formats.ProntoCCF irCommand)
MessageBox.Show("Failed to send the signal");
The sample application reads the config.xml file and dynamically generates buttons for the user interface. (Please note that the config.xml file must reside in the same directory as the executable.) When you press a button, its associated IR code is then sent through the total remote. Each entry in the config.xml file consists of an ID (used to uniquely identify the button), a Name (displayed on the button), and the Code (actual Pronto CCF data representing the IR code you want to send). An example of the config.xml file is as follows:
<Button ID="Button1" type=button>
<Name>Sony VCR Play</Name>
<Code>0000 006D 0020 0000 005E 0019 0017 0019 0017
0019 0017 0019 0030 0019 002E 0019 0017 0019 0017
0019 0017 0019 0030 0019 0017 0019 0030 0019 002F
0019 0030 0019 0017 0019 0030 00AF 0060 0019 0017
0019 0017 0019 0017 0019 0030 0019 0030 0019 0017
0019 0017 0019 0017 0019 0030 0019 0017 0019 0030
0019 0030 0019 0030 0019 0017 0019 0030 3F03</Code>
<Button ID="Button2" type=button>
<Name>Sony VCR Stop</Name>
<Code>0000 0067 001A 0000 0060 0019 0018 0018 0018
0018 0018 0018 0030 0018 0030 0018 0018 0018 0018
0018 0030 0018 0030 0018 0018 0018 0030 0018 0018
03FB 0060 0018 0018 0018 0018 0018 0018 0018 0030
0018 0030 0018 0018 0018 0018 0018 0030 0018 0030
0018 0018 0018 0030 0018 0018 03E5</Code>
<Button ID="Button3" type=button>
<Name>Zenith Television Power ON/OFF</Name>
<Code>0000 006D 0022 0000 0018 0011 0018 00A5 0018
00CF 0018 0011 0018 00A5 0018 0010 0018 00A5 0019
00CD 0018 0010 0018 00A5 0018 00CE 0018 0011 0018
00A5 0018 00CE 0016 00CF 0018 0011 0018 13B6 0018
0011 0018 00A5 0018 00CF 0018 0011 0018 00A5 0018
0010 0018 00A5 0019 00CD 0018 0010 0018 00A5 0018
00CE 0018 0011 0018 00A5 0018 00CE 0016 00CF 0018
0011 0018 13B6</Code>
Wrapping it up
So, there we have it from start to finish. I really must commend Griffin for devising a very clever solution to sending IR signals through the audio port. The method is ingenious and quite simple. I even toyed with creating a homemade version of the dongle, but just haven't had the time. I think the recipe is as follows:
1 part stereo mini-jack +
1 part capacitor +
1 part resistor +
2 parts LEDs +
1 part Elfin Magic
= Homemade Total Remote
The problem is that I am a bit short on the Elfin magic. Maybe someone will take this article as the basis and come up with the home version. But either way, once you know a bit about how IR signals are created, the process of determining how to convert audio to IR is pretty straightforward.
I do have a couple closing notes:
I cannot stress enough that when using this application to send IR codes to your own devices that the IR code is very important! Not only do you need to find the proper IR code for your make/model of device, but it has to be a good code. Generally, the codes I found were usable. But a lot of codes were missing the full code. Hint: If you get an IR code that doesn't seem to work, try sending it twice in rapid succession. If this still doesn't work, then it is probably a bad code. Also, please do not send me requests for IR codes!!! I don't have a big-book-of-IR-codes. Search the web (or look at the sites I references) and you will find them.
This method of using the audio port may not work on all PDAs. If the hardware manufacturer skimped on the audio port, it might not be capable of sending a signal (or at least a strong one). For example, I believe the Dell Axim (at least older models) have a problem with using the audio port in this manner. See the Griffin Technology site for a complete list of supported models.
Boy howdy, I certainly referenced a lot when developing this project. Here are the links of the relevant sources:
Here are some related IR links:
- LIRC - LIRC is a LINUX package that allows you to decode and send infra-red signals of many (but not all) commonly used remote controls.
- WinLIRC - WinLIRC is the Windows equivalent of LIRC, the Linux Infrared Remote Control program.
- Ultramote - a Compact Flash IR extender for the pocket PC.
- PDAWin extender schematic - a hardware schematic for building your own pocketpc IR extender.
- 4/18/04 - Article submitted
- 4/22/04 - Minor editing and fixing of hyperlinks
- 6/10/04 - Added extra references