65.9K
CodeProject 正在变化。 阅读更多。
Home

使用音频端口的 C# 远程控制

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (73投票s)

2004年4月21日

CPOL

23分钟阅读

viewsIcon

360605

downloadIcon

2868

本文描述了C#代码的开发,该代码允许您使用音频端口从您的移动设备发送消费级红外码。

Total Remote closeup, and inserted into the audio port

引言

移动设备最令人兴奋的方面之一是它能让您与周围环境互动。本文将探讨一种方法,通过使用消费电器(立体音响、DVD播放器、录像机、电视、爱好机器人等)内置的红外(IR)接收器来控制它们。目前有几款不错的应用程序允许您从Pocket PC发送/接收红外信号,但它们都存在同样的问题:大多数型号的IRDA端口太弱,无法产生超过8英尺的消费级红外信号。Griffin Technology 的聪明开发者们想出了一种巧妙的设备,它插入您的移动PC的音频端口,可以发送更强的信号。我对他们的开发者提出这个想法表示敬意,因为它确实很有创意。本文将探讨他们如何通过声音实现发送消费级红外信号的壮举,以及您如何在自己的应用程序中使用它。

注意:这不是付费广告,我与Griffin Technology公司没有任何关系。相反,当这个设备刚出来的时候,我联系了Griffin,希望获得SDK的访问权限,这样我就可以在我的应用程序中集成红外码的发送功能。我对他们收到的简短回复以及他们在其他网络论坛上对消费者的态度感到非常失望。所以我决定自己解决这个问题。嘿,这能有多难?——这是我最后说的话。

背景 - 简述红外信号(或一个非常非常小的盒子)

首先:什么是红外码?

网络上有很多文章可以比我在这里更详细地解释红外信号。例如,Barry GordonRemoteCentral.com上有一篇关于红外信号如何工作的精彩文章。该网站的红外数据库中也有大量的红外码,您可以用来控制自己的设备。不过,我还是会简要解释一下简单的消费级红外信号。

当您使用遥控器(例如)打开电视时,您实际上是向电视发送了一道红外(IR)光束上的命令。与手电筒不同,红外光对肉眼不可见,所以您实际上看不到光束。但是电视上灵敏的红外接收器可以“看到”它,并利用其中包含的信息执行给定的命令。它应该执行什么命令呢?遥控器发送的红外光束实际上是由许多微小的ON和OFF光脉冲组成的。ON和OFF脉冲的组合决定了电视应该执行的确切命令。一组ON和OFF脉冲表示“开机”,另一组表示“频道上调”。下面可以看到发生情况的示意图。

Sample IR wave with pulses ON and OFF

现在,在幕后,您的电视遥控器做了一些比上面概述的额外工作。是的,它发送了一系列ON和OFF脉冲,它们的序列决定了它试图发送的特定命令(开机、关机、换频道等)。但这些ON和OFF脉冲实际上是由特定载波频率的红外光波组成的。这意味着长时间的ON脉冲由许多微小的红外光ON/OFF闪烁组成。红外光以ON-OFF-ON-OFF-ON-OFF...的模式持续总的ON时间。每个微小的ON-OFF持续时间都在载波频率下。

此载波频率有多种用途,包括省电和更好地处理“噪音”。(注意:尽管红外信号的波长高于可见光,但它们仍会受到可见光和其他辐射源产生的噪音的影响。) 载波频率因遥控器和品牌而异。通常,消费级红外遥控器在36KHz(即每秒36,000个周期)到40KHz范围内工作。请记住这个范围,因为它在后面非常重要。下面是一个使用载波频率的波形示例。

Wave with IR Carrier

背景 - 代码破解

上一节非常简要地描述了遥控器**如何**使用红外(IR)波发送其代码。在本节中,我们将快速(我是说非常快速)地了解遥控器**发送了什么**。这被称为IR代码。请记住,为了让遥控器发送不同的命令,它需要为每个命令发送一个特定的IR代码。这样,接收单元(例如电视)就可以区分频道上调命令或关机命令。

一个流行的红外码格式示例是Pronto CCF格式。Pronto是飞利浦制造的一种消费级设备,它可以学习您的遥控器代码,并允许您设置自己的用户界面来回放这些代码。当Pronto学习您的遥控器代码时,它基本上是分析您按下某个按钮时遥控器产生的红外波。这与我们上面讨论的波是相同的。正是这个波中独特的ON/OFF值序列被存储为该按钮的红外码。下面是一个典型红外码(Pronto格式)的示例

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

没有什么比一长串十六进制代码更能让人想来一片阿司匹林和一杯咖啡了。但在这种情况下,请稍等。上面的代码实际上非常简单,不应引起过度压力。前4个值只是一个描述性标题。代码的其余部分由1或2个信号组成(取决于标题)。

标题:0000 0067 000D 000D

标题由4个字组成。第一个字(本例中为0x0000)指定代码格式。出于我们的目的,这将始终是0000,表示“原始”格式。还有其他值表示不同类型的编码。您可以随意阅读其他格式类型,但我们在这里不作介绍。第二个字(0x0067 = 十进制103)是载波信号的频率分频器,它相对于Pronto内部时钟的4,145,146Hz运行。因此,此IR代码的载波频率实际上是4,145,146Hz / 103 = 40,244Hz。标题中的下一个字描述了代码中包含的信号。

第三个字(0x000D = 十进制13)被称为“一次计数”(once count),表示此红外码具有13对ON/OFF数据,称为信号突发。“一次”信号是您按下并松开遥控器上的按键时发送的信号。请注意,此值表示信号**对**的数量,这意味着该信号实际上将有2*值的数据。在本例中,13对意味着我们将读取26个值。这些值是代码交替的ON/OFF持续时间。因此您可以看到(使用颜色编码),绿色的“一次”信号以0x0060的ON值开始,后跟0x0019的OFF值,再后跟0x0018的ON值……您懂的。如果此字的值为0x0000,则表示没有“一次”代码,并且无论您是按一次键还是重复按键,都应始终发送“重复”代码。

第四个字(0x000D = 十进制13)是“重复计数”,表示“重复”信号中也有13个信号突发。“重复”信号是您按住遥控器上的某个按键(例如音量键)时发送的信号。如果您按住一个按键,“一次”信号会发送,然后尽可能多地重复发送“重复”信号(在您按住遥控器按键期间)。当您松开按键时,遥控器停止发送。所以再次使用颜色编码,您可以看到紫色信号是重复信号。如果此字的值为0x0000,则表示没有“重复”代码,并且该按键不会重复。

就是这样!这是对消费级红外码工作原理的简明扼要的入门指南的极度简短概述。现在去给自己泡杯咖啡,让我们开始文章的重点吧。

谜团揭开 - 他们是如何做到的?

现在我们对红外码及其构建方式有了一些了解,真正的问题是:我们如何通过PDA的音频端口发送红外波呢?Total Remote附带了实现此功能的软件,但我真的希望能够在自己的移动应用程序中实现此功能。所以我拿出了我那值得信赖/布满灰尘的USB示波器、一块记事本和纸、一台旧索尼录像机和一包曼妥思糖果,开始亲自研究这个难题。四个小时和两个蛀牙之后,我找到了通过音频端口发送适当信号来告诉我的录像机开始/停止/播放等的方法……

为了解决这个问题,我写下了一系列我认为需要回答的问题,以便找到解决方案。我的首要目标是在不实际拆开硬件的情况下弄清楚**如何**实现。你看,我又小气又笨拙。所以用我选择的工具(锤子和老虎钳)撬开这块小硬件很可能会让我直接去买一个替代品。这种情况不符合我的零预算。所以我不得不做我能做的最便宜的事情,那就是真正锻炼我的大脑。以下是我想到的清单:

1. 音频信号如何转换为40Khz载波信号?

通过检查Total Remote硬件本身,这个问题的答案变得显而易见。该硬件顶部有2个红外发射LED,以及一个连接到PDA的立体声迷你插孔。我对这2个独立的LED感到非常困惑。起初,我以为他们使用2个是为了提供更宽的传输角度。换句话说,稍微分开的2个LED可能会比单个LED发出更宽的信号锥。但进一步的检查和思考证明情况并非如此。

接下来,我在想如何通过音频端口发送任何40Khz的信号。你看,音频端口的制造实际上只允许传输人耳听力范围内的信号,最高约为20Khz。任何超过20Khz的信号都会被噪音过滤或端口本身的性能所压制。那么,我怎么可能从一个20KHz的端口获得40Khz的信号呢?我真正需要的是一个能够传输两倍带宽的端口。两倍。两个。嗯……我以前在哪里听过这个数字……两个……我刚吃了两颗曼妥思,不……我大学时认识那对双胞胎,不……等一下!会不会是**两个**LED和我需要**两倍**带宽只是巧合?是的,可能只是巧合。

不,等等!我忘记了一件事,音频端口确实限制在大约20KHz,但音频端口是**立体声**的。这意味着有两个通道(左和右),每个通道都能发送20KHz的音频信号。嘿,也许这些LED中的每一个都连接到一个单独的通道,这就能解释他们使用的立体声迷你插孔。但即便如此,我如何从20KHz的通道获得40KHz的信号呢?答案是通过每个通道发送一个20KHz的信号,并且使一个信号与另一个信号异相。这将使发射信号的有效频率加倍,从而给我们发送载波信号所需的40KHz。

Out-of-phase signals produce desired result

2. 我们如何从音频端口获得足够的功率来驱动红外信号超过几英尺的距离?

好的,我们已经弄清楚了如何理论上通过红外端口发送40Khz信号,但除非我们能将其发送到合理的距离,否则这将毫无用处。毕竟,一个需要您从椅子上站起来才能使用的遥控器有什么用呢!我做了一些网络搜索,发现PDA上的音频端口并不以其强大的功率而闻名。所以我想知道Total Remote软件从音频端口产生了多少功率。只有一个方法可以最终确定:拿出示波器!

我插上我的便携式USB示波器,运行Total Remote软件,按下一个键发送信号,并记录了输出。我所看到的起初让我感到困惑。这是信号开始时的屏幕截图。

Oscilloscope view of actual preamble waveform

是的,看着信号的开头,我的反应和您差不多。那到底是什么鬼?经过一番观察,我计算出,当我按下遥控器按键(在软件中)时,生成的信号以500Hz波形作为“前导”,在两个通道上异相。在这个前导之后,我看到了我所期望的:代表红外码的ON/OFF信号爆发周期。这看起来像这样:

Oscilloscope view of actual ON/OFF signal burst pairs

所以,我很好奇。前导的目的是什么?答案似乎是,为了提高音频端口的功率以将红外信号发送到合理的距离,我们需要“充电”Total Remote。我的猜测(因为我从未真正拿出锤子并打开硬件)是,里面有一个调谐到此前导信号的小电容器。前导给电容器充电,当其余的红外信号发送时,这些能量随后被释放。这种功率提升使红外信号能够传播得更远。再说一次,这只是一个猜测。只要我能重现相同的前导,我是对是错都无关紧要。

3. 我如何通过软件发送信号?

好的,至此,我已经有了一个如何使用Total Remote发送红外信号的初步想法。我需要创建一个异相立体声音频信号,其中包括一个500Hz的“前导”来给Total Remote充电,然后是给定载波频率的开/关信号爆发。如果我能以某种音频格式创建这个信号,我就可以简单地播放这个声音文件来发送红外命令。那么,当您想到原始音频文件时,首先想到的是什么?

制作WAV文件

所以我的任务很明确。我需要创建代表我想发送的红外码的原始音频文件,并且每个音频文件都应该有一个“前导”来给Total Remote充电。我认为WAV文件格式非常适合这项任务。它有很好的文档记录,并且具有满足我需求的灵活性。我不会详细介绍WAV文件格式的结构,但您可以轻松地从网上搜索到相关信息。Stanford.edu对WAV格式有一个很好的简短描述,SonicSpot上还有一篇不同的文章。

当您下载我的源代码时,您会看到我只创建了三个类来支持生成和发送音频文件。`PCMWaveGen` 对象封装了实际WAV文件的写入。`IRWav` 类派生自 `PCMWaveGen`。它接受一个红外码并生成适当的wav文件,并可选择将其发送到Total Remote(音频端口)。最后,我们有 `ProntoCCF` 对象,它封装了用于输入红外码的文件格式。

PCMWaveGen

这个类封装了WAV文件的写入。如果您查看源代码,您会发现它基本上由一个属性列表和几个`Write`方法组成。所有属性都映射到WAV文件格式中的属性。基本的WAV文件由头部和样本数据组成。样本数据实际上是某个时间点的数据,或者是输出流的“采样”。头部只是描述要期望的样本数据类型。文件格式布局的概述看起来像这样

Section 1: RIFF Header Chunk
Section 2: FMT sub-chunk
Section 3: Data sub-chunk

这个`PCMWaveGen`对象本身并不是那么有趣,但需要注意的两个方法是`WriteHeader`和`WriteFormat`方法。在WAV文件的最开始,有一个“RIFF”头。如您所见,它非常简单。

public bool WriteHeader(BinaryWriter writer)
{
    WriteString(writer,"RIFF");
    writer.Write(this.ChunkSize);
    WriteString(writer,"WAVE");
}

信息的真正核心内容在Format(格式)部分。如您所见,它包含音频格式、通道数、比特率等信息。

public void WriteFormat(BinaryWriter writer)
{
    WriteString(writer,"fmt ");
    writer.Write(this.FormatChunkSize);
    writer.Write(this.AudioFormat);
    writer.Write(this.NumChannels);
    writer.Write(this.SampleRate);
    writer.Write(this.ByteRate);
    writer.Write(this.BlockAlign);
    writer.Write(this.BitsPerSample);
}

ProntoCCF

这个简单的类接受Pronto CCF格式红外码的字符串表示,并将其转换为信号数组。信号数组是我们在本文开头讨论过的脉冲对。我没有创建另一个任意格式,而是觉得从长远来看,编写一个解析Pronto码的简单类会更容易。要使用这个类,您只需将原始红外码字符串传递给它的`FromString()`方法。所有的处理都已为您完成。

如果您正在寻找用于自己设备的红外码,我建议访问RemoteCentral.com。他们有一个庞大的不同品牌遥控器代码数据库供您使用。需要注意的一点是:尽管很多时候从网上获取红外码并使其工作很容易,但它们有时可能非常不稳定。如果您有学习自己红外码的方法,这比使用可能不适用于您的代码(无论出于何种原因)要可靠得多。我之所以提及这一点,是因为过去我曾因认为我的代码有错误而浪费了数小时的工作时间,而实际上,简单地更改红外码就是答案。

IRWav

这是真正的核心类,因为它封装了从红外码生成WAV文件。要开始使用此类的,您只需执行以下操作:

using DForge.IR.Formats;
using DForge.IR.TotalRemote;

ProntoCCF irCommand = new 
    ProntoCCF("0000 006D 001A 0000 ...rest of ir code here...");
IRWav totalRemote = new IRWav();
if(!totalRemote.SendSignal(irCommand,true))
    MessageBox.Show("Failed to send the signal");

在设计这个类时,我希望它尽可能高效。你看,对于一个小小的PDA来说,动态创建WAV文件并不是一件容易的事。当然,它可以处理,但你真的不希望用户每次发送IR信号都等待5秒。为了消除这种等待,我决定缓存IR信号,以便它们只生成一次。它们通过与每个IR命令关联的唯一ID (GUID) 进行缓存。所以,当你看到接受 `Guid idCommand` 参数的方法时,这就是原因。当你发送一个命令并且在缓存中找不到ID时,它会生成信号并缓存以供以后使用。下次你发送信号时,它会从缓存中查找ID并发送预生成的信号。如果你只想发送一个临时信号(如上面的示例),要么省略ID,要么用 `Guid.Empty` 代替。如果你这样做,代码每次都会生成IR WAV文件(你将无法获得缓存的好处)。

需要注意的一点是:如果您更改了实际的红外码,您**必须**在发送新代码之前清除缓存。否则,它会查找缓存并取出旧的预生成版本并发送。如您所见,我提供了两种清除缓存的方法。第一种清除整个缓存,第二种清除给定命令的预生成信号。

/// <summary>
/// Clears the cached signal directory
/// </summary>

public void ClearCache()
{
    if(Directory.Exists(this.CacheDirectory))
        Directory.Delete(this.CacheDirectory,true);
    Directory.CreateDirectory(this.CacheDirectory);
}

/// <summary>
/// Clears the cached signal for the given command
/// </summary>
public void ClearCache(Guid idCommand)
{
    string strFilename = CacheDirectory + idCommand.ToString();
    if(File.Exists(strFilename))
        File.Delete(strFilename);
}

当您使用`IRWave`发送信号时,您会看到几件事发生。首先,您会注意到我们将音频端口的音量调到最大。这相当于我们说我们要以最大功率发送红外信号。还记得在前面的章节中,我们是如何发现前导信号以及它是如何用于增强功率的吗?好吧,如果我们在发送信号之前不将音频端口的音量推到最大功率,前导就毫无意义了。所以我们保存当前的音频音量级别,并将其调到100%功率以实现最大范围。接下来,我们检查缓存以查看信号是否已创建。如果它在缓存中,我们就使用它。否则,我们就会付出代价并从红外命令生成WAV文件。最后,我们将音量恢复到其原始级别。我觉得这是一个不错的细节,这样当您以后插入耳机并播放您的Celine Dione MP3收藏时,您的耳膜就不会被震破。嗯,至少不会比平时更严重。

/// Sends the given IR signal via IR
public bool SendSignal(Guid idCommand, double irCarrier, 
                              short[] signal, bool usePreamble)
{
    bool success = true;

    try
    {
        // For total remote we need to send maximum power to the audio port
        // so that we get the greatest range.
        Sound.PushVolumePercent(100);

        string strFilename = this.TempWavFilename;
        if(idCommand!=Guid.Empty)
        {   // We should cache commands with ids.
            // Only generate a signal file if it doesn't already exist
            strFilename = CacheDirectory + idCommand.ToString();
            if(!File.Exists(strFilename))
              this.WriteIRWAVFile(strFilename,irCarrier,signal,usePreamble);
        }
        else
        {   // For commands without Ids (temporary ones) then we just
            // write out the audio version of the IR signal to a temp file
            this.WriteIRWAVFile(strFilename,irCarrier,signal,usePreamble);
        }

        // Play the audio signal through the total remote,
        // generating an IR signal
        success = Sound.Play(strFilename);

        // Make sure we reset the volume to its previous value.
        Sound.PopVolume();
    }
    catch(Exception except)
    {
        MessageBox.Show(except.Message);
        success = false;
    }

    return success;
}

处理的下一步是检查 `WriteIRWAVFile` 方法。此方法利用基类(`PCMWavGen`)写入必要的信息以创建有效的WAV文件。`WriteHeader` 和 `WriteFormat` 生成了WAV文件格式的第1和第2部分。第3部分通过调用 `WriteWAVSignal` 方法生成,该方法(最终)解决了生成正确的音频WAV数据的问题。

public bool WriteIRWAV(BinaryWriter destWriter, 
       double carrierFreq, short[] signal, bool usePreamble)
{
    bool success = true;

    try
    {
        WriteHeader(destWriter);
        WriteFormat(destWriter);
        WriteWAVSignal(destWriter,carrierFreq,signal,usePreamble);
    }
    catch(Exception e)
    {
        MessageBox.Show(e.Message);
        success = false;
    }

    return success;
}

`WriteWAVSignal` 方法为给定的红外码创建音频数据。如果您还记得前几节的内容,红外码由一系列ON/OFF突发对组成。ON时间由信号的许多ON-OFF转换组成,其频率由红外码的载波频率决定。OFF时间,嗯,简单地说就是我们希望完全安静并且不发送任何信号的时间。

/// Creates a WAV audio signal that represents
/// the given consumer IR remote control code.
/// </SUMMARY>
/// <PARAM name="writer">Destination of the WAV data</PARAM>
/// <PARAM name="carrierFreq">Carrier of the IR code in hz (ie. 38000)</PARAM>

/// <PARAM name="irSignalPairs">Array of alternating short values 
///      representing ON and OFF times of the carrier signal for the IR code. 
///      Times are given in multiples of the carrier signal.</PARAM>
/// <PARAM name="usePreamble">If true, the WAV signal will be prefixed 
///      by a 1Khz preamble (necessary for the Total Remote hardware)</PARAM>
/// <returns></returns>
public bool WriteWAVSignal(BinaryWriter writer, double irCarrier, 
                             short[] irSignalPairs, bool usePreamble)
{
    bool success = true;

    try
    {
        // Indicate a DATA chunk is to follow in the WAV
        WriteString(writer,"data");

        // Mark the data size location for later
        long dataSizePos = writer.BaseStream.Position;
        // Write a dummy size for now
        int dataSize = 0;
        writer.Write(dataSize);

        // Mark beginning of data to determine size later
        long dataStartPos = writer.BaseStream.Position;

        // If including the preamble for the Total Remote, then write it
        if(usePreamble)
            CreatePreamble(writer);

        bool high = true;
        double time = 0;
        double halfCarrier = irCarrier/2;

        // For each value in the IR code, generate its waveform.
        // The array consists of pairs of short values indicating
        // ON and OFF values of the carrier.
        // An example would be { 0x5E, 0x19, 0x17, 0x19 }
        // would produce an ON signal of
        // duration 0x5E*Carrier followed by a "silence"
        // of 0x19*Carrier followed by an 
        // ON signal of 0x17*Carrier followed by a "silence"
        // of 0x19*Carrier ....
        for(int i = 0; i < irSignalPairs.Length; i++)
        {
            // If last is a long "low" then ignore for now
            if(i == irSignalPairs.Length-1 && !high)
                break;

            this.SampleHigh = high?255:128;
            this.SampleLow = high?0:128;

            time = (double)irSignalPairs[i] / irCarrier;
            CreateStereoCarrier(writer,halfCarrier,irSignalPairs[i] ,true);

            // toggle ON and OFF values
            high = !high;
        }

        // Determine the overall Data size and write it to the WAV
        dataSize = (int)(writer.BaseStream.Position-dataStartPos);
        writer.BaseStream.Position = dataSizePos;
        writer.Write(dataSize);

        // Go ahead to end
        writer.BaseStream.Position = writer.BaseStream.Length;

    }
    catch(Exception e)
    {
        MessageBox.Show(e.Message);
        success = false;
    }
    return success;
}

最后一个值得注意的方法是为立体声载波生成数据。最初,我创建了一个方法来计算具有给定相位偏移的载波,并调用它两次以获得立体声波。由于速度问题,我重写了它,使其一次计算两个通道。(原始方法虽然未使用,但包含在`IRWav`中)。此外,我又增加了一个缓存步骤,并缓存了生成的载波。来自单个遥控器的红外码通常使用完全相同的载波频率。因此,我通过缓存最后一个载波频率设法进一步提高了速度。然后我只需将足够多的这些载波周期复制到目标信号中,使其符合给定的持续时间。

public void CreateStereoCarrier(BinaryWriter writer, 
  double carrierFreq, short cycles, bool phaseShift) 
{ 
   if(carrierFreq != _lastCarrierFreq || _lastCarrierWave == null 
        || _lastCarrierWave.Length == 0 || _lastCarrierPhase != phaseShift) 
   { 
        if(_lastCarrierWave!=null)
            _lastCarrierWave.Close();
        _lastCarrierWave= new MemoryStream(); 
        _lastCarrierFreq= carrierFreq; 
        _lastCarrierPhase= phaseShift; 

        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 each sample, determine the value that would create a signal 
        //of the given carrier frequency for the duration specified 
        for(double time = 0; time < cycleDuration; time += step) 
        {
            sampleValue = delta/2 * (1 + Math.Cos(quant*i)); 
            bytValue = (byte)(this.SampleLow+sampleValue); 
            //First channel 
            bw.Write(bytValue);
            //Are we phase shifting the second channel? 
            if(phaseShift)
                bytValue = (byte)(this.SampleLow + delta - sampleValue); 
            //Second Channel 
            bw.Write(bytValue);
            i++;
        }
        //create a "silent" wave as well. 
        _lastNullCarrierWave = new byte[_lastCarrierWave.Length]; 
        for(int j= 0;j<_lastNullCarrierWave.Length;j++)
            _lastNullCarrierWave[j] = (byte)(this.SampleLow+delta/2);
    }

    if(writer != null)
    {
        // Now make an exact copy of the carrier
        // cycle for each of the remaining cycles
        if(this.SampleHigh - this.SampleLow == 0)
        {
            for(short nI=0; nI < cycles/2; nI++)
                writer.Write(_lastNullCarrierWave,0,
                    (int)_lastNullCarrierWave.Length);
        }
        else
        {
            for(short nI=0; nI < cycles/2; nI++)
                writer.Write(_lastCarrierWave.GetBuffer(),
                    0,(int)_lastCarrierWave.Length);
        }
    }
}

示例应用

Screenshot of pocketpc application

示例应用程序非常简单,其目的仅仅是为了说明如何使用底层红外代码。要真正利用示例应用程序,您需要两样东西。

  • 首先,您需要购买或借用一个价值29美元的Total Remote音频加密狗。这是插入音频端口的硬件,如本文顶部的图片所示。
  • 其次,您需要找到要控制的设备(电视、录像机、DVD等)的有效红外码数据。这第二部分最棘手,但您应该能在我本文中提到的地方找到它们。

红外数据的实际发送再简单不过了,如下面的代码片段所示

/// <summary>
/// Private member that allows us to send signals to the TotalRemote
/// </summary>
IRWav _totalRemote = new IRWav();

/// <summary>
/// Sends an IR command
/// </summary>
/// <PARAM name="idCommand">Unique ID of the command (or Guid.Empty)</PARAM>

/// <PARAM name="irCommand">IR Command to send</PARAM>
void SendIRCommand(Guid idCommand, DForge.IR.Formats.ProntoCCF irCommand)
{
    if(!_totalRemote.SendSignal(idCommand,irCommand,true))
        MessageBox.Show("Failed to send the signal");
}

示例应用程序读取 `config.xml` 文件并动态生成用户界面按钮。(请注意,`config.xml` 文件必须与可执行文件位于同一目录中。)当您按下按钮时,其关联的红外码就会通过 Total Remote 发送出去。`config.xml` 文件中的每个条目都包含一个 ID(用于唯一标识按钮)、一个名称(显示在按钮上)和代码(表示您要发送的红外码的实际 Pronto CCF 数据)。`config.xml` 文件的示例如下:

<?xml version="1.0" encoding="utf-8" ?> 

<Buttons>
    <Button ID="Button1" type=button>
        <Id>6A25C487-F171-49a2-A6D8-2C5F28FF546F</Id>
        <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>
    <Button ID="Button2" type=button>
        <Id>F96CA367-E471-4bff-AC39-7D3594E135F8</Id>
        <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>
    <Button ID="Button3" type=button>
        <Id>7A4F502A-2593-4d00-A4F3-3672B7B390B2</Id>
        <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>

    </Button>
</Buttons>

总结

至此,我们从头到尾都讲完了。我真的要赞扬 Griffin 提出了一个非常巧妙的解决方案,通过音频端口发送红外信号。这种方法既巧妙又相当简单。我甚至想自己制作一个加密狗的家庭版本,但一直没有时间。我想配方如下:

1 份立体声迷你插孔 +
1 份电容器 +
1 份电阻器 +
2 份 LED +
1 份精灵魔术
= 自制 Total Remote

问题是我有点缺乏精灵魔法。也许有人会以这篇文章为基础,想出家用版本。但无论如何,一旦您对红外信号的创建方式有所了解,将音频转换为红外的过程就相当简单了。

我还有几点总结说明

我再怎么强调也不为过,当使用此应用程序向您自己的设备发送红外码时,**红外码非常重要**!您不仅需要为您的设备型号找到正确的红外码,而且它必须是一个**好的**代码。通常,我找到的代码都是可用的。但很多代码都缺少完整的代码。提示:如果您获得一个似乎不起作用的红外码,请尝试快速连续发送两次。如果仍然不起作用,那么它可能是一个坏代码。另外,请不要向我发送红外码请求!!!我没有一本“红外码大全”。请搜索网络(或查看我引用的网站),您会找到它们。

这种使用音频端口的方法可能不适用于所有PDA。如果硬件制造商在音频端口上偷工减料,它可能无法发送信号(或至少是强信号)。例如,我认为戴尔Axim(至少是旧型号)在使用音频端口方面存在问题。请参阅Griffin Technology网站,获取支持型号的完整列表。

参考文献

天哪,在开发这个项目时,我确实参考了很多资料。以下是相关来源的链接:

更多阅读

以下是一些相关的红外链接

  • LIRC - LIRC是一个LINUX软件包,允许您解码和发送许多(但不是全部)常用遥控器的红外信号。
  • WinLIRC - WinLIRC是LIRC(Linux红外遥控程序)的Windows版本。
  • Ultramote - 用于Pocket PC的Compact Flash红外扩展器。
  • PDAWin 扩展器原理图 - 用于构建您自己的掌上电脑红外扩展器的硬件原理图。

历史

  • 2004年4月18日 - 文章提交
  • 2004年4月22日 - 小幅编辑和超链接修复
  • 2004年6月10日 - 添加了额外参考资料
C# 使用音频端口的遥控器 - CodeProject - 代码之家
© . All rights reserved.