[原创]发一个程序吧,一个播放音频的解决方案
很不容易从公司搞出来的。<br>有兴趣地可以看看。<br>这个也是一个解决问题的办法,更加重要的是给出一个方法和思维方式。<br>[attach]11917[/attach]<br>[align=right][color=#000066][此贴子已经被作者于2006-9-19 19:42:04编辑过][/color][/align]
RockCarry JJ把自己的东西拿出来分享了<BR>支持支持----虽然还没下呢[em02] 刚下载,顶一个。 这个代码初学者可能看不太懂,简单解释一下。<BR>RME_CMD和RME_Server都是Windows程序,推荐使用VC6.0进行编译。<BR>RMELib是DOS程序,推荐使用TC2.0编译。<BR>RME_Server是一个服务器,他响应RMELib发出的请求,然后完成相应的功能。<BR>一般情况下DOS程序和Windows程序是很难通信的,因此需要RME_CMD程序作为一个中介。<BR>RMELib把命令发送给RME_CMD程序,RME_CMD再负责转发给RME_Server。<BR>RMELib发送命令给RME_CMD的办法就是system函数了,好像找不到别的方法了。 在性能上,RME的效率还是不错的。<br>RMELib在调用system函数时,会消耗大量的CPU资源,这导致了效率低下,是性能提升的瓶颈,但是这个是由操作系统提供的,所以也没法优化。<br>在RMELib发送命令以后,所有的任务都由RME_Server来处理,由于RME_Server是一个Windows程序,在性能上有很大优势。<br>在RME_Server和RME_CMD中采用了进程间通信、同步和共享内存等技术,使得RME_Server在等待命令时对CPU的占用为0。而在RME_Server执行播放任务的时候,对CPU的占用也很小。<br>结论:在RMELib发送命令时,对CPU的消耗较大,在命令发送完毕,开始播放音频时,对CPU的占用极少。
[align=right][color=#000066][此贴子已经被作者于2006-9-20 17:15:43编辑过][/color][/align]
<P>嗯,剑走偏锋,做得不错,比以前在crazybugs下载的要好,而且开源了,谢谢RockCarry!</P> 本程序也是给出了在Windows平台上DOS程序与windows程序进行通信的一种方法。<BR>对于DOS程序难以完成的功能(如音频播放),都可以使用这个方法,交给Windows程序去完成。<BR>最终的测试程序在效果上还是非常不错的。<BR>当然本程序中,采用的线程同步技术和共享内存技术也是可以学习的。 <P>大家可以尝试修改其代码,增加更多的功能,使其成为一个较为实用的模块,甚至可以用到DOS游戏编程中。<br>当然,这样的DOS程序也只能运行在Windows平台,纯DOS环境下就不能运行了。</P>
[align=right][color=#000066][此贴子已经被作者于2006-9-20 17:26:46编辑过][/color][/align]
正在观摩源码中,期待文档能早点出来,RockCarry的文档一向是比较详尽~~ <P>看来国庆节是买不上电脑了,现在还差三千多块钱。<BR>最近都在做2410的SD卡驱动,没有时间做自己的事情了。<BR>至于文档,还没有写,其实写出来了也不好发布,因为公司严格保密,所有的东西都带不出去的。<BR>过段时间还要写一个Camera和电视芯片的驱动程序,应该也非常的忙。<BR>也只有等到买了电脑,而自己又有了空闲以后再搞吧。</P> <P>==================<br> RME 实现原理分析 <br>==================</P>
<P>RME 全名为 RockCarry Media Engine,是我为解决 Windows 平台下,DOS 程序的音频播放问题而<br>制作的一个引擎。为什么叫引擎,而不叫 Lib,就是因为它不仅仅是一个 Lib,还包括了许多特别<br>的东西。</P>
<P>问题的提出。为什么要写 RME 呢?可能个人是一个偏执狂,偏执于在现代化的今天,使用古老的编<br>译器、研究古老的技术,偏执于对计算机世界本质的探索和追求。所以我目前研究得最深入的还是 <br>DOS 操作系统、Turbo C 编译器。</P>
<P>要解决的问题是什么呢?大家都知道 windows 操作系统是可以兼容运行 DOS 程序,但是在 <br>WindowsXP 系统中,对 DOS 程序的兼容性做得比较差,特别是对音频部分的兼容性做得不好,我做<br>过测试 WindowsXP 的虚拟 DOS 只能支持到 SB2.01 标准,并且效率也非常差,使用 DMA 方式播放<br>音频,CPU 也经常用到 50% 以上,可见 WindowsXP 对声卡的支持是通过软件模拟实现的。</P>
<P>许多情况下,我们希望我们的 DOS 程序能够在 XP 上运行,并且能够播放音频。由于 XP 对 DOS <br>程序的兼容性问题,使得依靠传统的 DOS 声卡编程技术也难以达到以上目的(其实还是可以实现的<br>)。另一方面,由于我们希望在 WindowsXP 上运行 DOS 程序,因此就可以依靠强大的 Windows 平</P>
<P>台所提供的功能,采用非传统的 DOS 编程技术来达到我们的目的,这个技术的核心就是如何让 DOS <br>程序与 Windows 通信。如果 DOS 程序能够与 Windows 进行通信,那么就可以有办法让 DOS 程序<br>调用 WinAPI (当然,是间接的),这样所有 DOS 编程技术无法解决的技术难题,都可以交给 <br>Windows 去解决,而前面提到的音频播放的问题也就迎刃而解了。RME 就是利用这样的技术现实的。</P>
<P>DOS 能不能调用 WinAPI ?答案是肯定的,当然不能。直接的 WinAPI 调用在 DOS 程序中是不可能<br>做到的,但是我们能够想到间接的办法。这个办法就是 Turbo C 提供的 system 函数,该函数的原<br>型如下:<br> int _Cdecl system(const char *command);<br>参数 command 是一个命令字符串,调用该函数,其结果就等于在命令行中敲入了 command 字符串<br>所给出的命令。</P>
<P>在 Windows 下同样也有命令行的概念,并且在命令行下敲入一个可执行程序的名字,就是执行这个<br>程序。那么在 DOS 下,Turbo C 所提供的 system 函数是否真正有这样神奇的功能呢?我们可以写<br>一个测试程序,如下:</P>
<P>+------------------------------------------------------------+<br>#include <stdlib.h><br>#include <process.h></P>
<P>void main()<br>{<br> system("notepad");<br> getch();<br>}<br>+------------------------------------------------------------+</P>
<P>利用 TC2.0 编译并在 XP 下运行以上程序,我们可以发现弹出了一个记事本。这也证明了,一个 <br>DOS 程序的确可以调用 Windows 的程序。并且,经过我的测试,system 函数在 XP 的功能与在命<br>令行中敲入命令基本上是完全等效的,也就是说,你能在 Windows 的命令行下敲入那些命令来实现<br>那些功能,你就能通过 system 函数来实现全部相同的功能。</P>
<P>这样就为我们解决问题找到了突破口。最简单的想法就是,写一个 windows 的命令行程序,使其能<br>够播放音频文件,这在 Windows 程序中是很容易实现的,不管是使用 MCI 还是 DirectX,都是比<br>较容易实现的,其实最简单的是 PlaySound 函数,直接调用就可以,例如:</P>
<P>+------------------------------------------------------------+<br>// RME V0.1<br>#include <windows.h></P>
<P>void main(int argc, char *argv[])<br>{<br> if (argc < 2) return;<br> PlaySound(argv[1], NULL, SND_FILENAME | SND_SYNC);<br>}<br>+------------------------------------------------------------+</P>
<P>使用 VC 编译以上程序,将程序命名为 RME01.exe,就可以使用了,如下:</P>
<P>+------------------------------------------------------------+<br>#include <stdlib.h><br>#include <process.h></P>
<P>void main()<br>{<br> system("RME01 test.wav"); /*播放test.wav文件*/<br> getch();<br>}<br>+------------------------------------------------------------+</P>
<P>以上就是最简单的实现,但是这样的实现还存在许多问题:<br> 1. 每播放一个文件就要开启一个 RME01 的进程<br> 2. 无法进行播放进度等控制<br> 3. 只有播放完成以后,控制权才会重新交给 DOS 程序<br> 4. 效率低,每一个功能就要写一个 Windows 程序<br> 5. 不便于使用和管理<br> 6. 更多...</P>
<P>因此,我们需要采用更加先进的架构,一下使我的 RME 中采用的方法:<br> 1. 实现一个名为 RME_Server 的 Windows 程序,作为服务程序<br> 2. 实现一个名为 RME_CMD 的 Windows 程序,作为命令转发程序<br> 3. 实现一个名为 RME_Lib 的 Turbo C 函数,用于与 RME_CMD 通信</P>
<P>DOS 程序与 RME_CMD 通信的方法就是通过 system 函数,如下:<br>+------------------------------------------------------------+<br> system("RME_CMD cmdname cmdarg");<br>+------------------------------------------------------------+</P>
<P>在这里 RME_CMD 只起到了一个命令转发的作用,他并不直接相应 DOS 程序发出的命令,而是由 <br>RME_Server 程序来真正的相应命令。这样做的好处在于,system 函数执行后,DOS 程序将一直处<br>于等待状态,直到所运行的命令结束。这就需要 system 函数做调用的程序能够非常快速的执行,<br>否则将会影响整个系统地执行效率。RME_CMD 作为一个命令转发程序,能够快速的执行命令转发的<br>任务,他只负责命令转发,而命令的执行交给 RME_Server 完成,在命令转发完以后,RME_CMD 立<br>即运行结束,以保证系统效率。</P>
<P>DOS 程序与 RME_CMD 的通信就是通过 system 函数实现,比较容易理解。而 RME_CMD 与 <br>RME_Server 的通信,则需要通过 Windows 的进程间通信技术来实现。做过 Windows 服务程序的人<br>对此会比较了解,Windows 平台提供了强大的多线程处理能力,在 RME 中主要采用了事件(Event<br>)和共享内存的方法进行进程间的通信与同步。主要用到的 API 如下:</P>
<P>+-----------------------------------------------------------------------------+<br>BOOL CreateProcess(<br> LPCTSTR lpApplicationName, // name of executable module<br> LPTSTR lpCommandLine, // command line string<br> LPSECURITY_ATTRIBUTES lpProcessAttributes, // SD<br> LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD<br> BOOL bInheritHandles, // handle inheritance option<br> DWORD dwCreationFlags, // creation flags<br> LPVOID lpEnvironment, // new environment block<br> LPCTSTR lpCurrentDirectory, // current directory name<br> LPSTARTUPINFO lpStartupInfo, // startup information<br> LPPROCESS_INFORMATION lpProcessInformation // process information<br>);</P>
<P>HANDLE CreateEvent(<br> LPSECURITY_ATTRIBUTES lpEventAttributes, // SD<br> BOOL bManualReset, // reset type<br> BOOL bInitialState, // initial state<br> LPCTSTR lpName // object name<br>);</P>
<P>HANDLE CreateFileMapping(<br> HANDLE hFile, // handle to file<br> LPSECURITY_ATTRIBUTES lpAttributes, // security<br> DWORD flProtect, // protection<br> DWORD dwMaximumSizeHigh, // high-order DWORD of size<br> DWORD dwMaximumSizeLow, // low-order DWORD of size<br> LPCTSTR lpName // object name<br>);</P>
<P>LPVOID MapViewOfFile(<br> HANDLE hFileMappingObject, // handle to file-mapping object<br> DWORD dwDesiredAccess, // access mode<br> DWORD dwFileOffsetHigh, // high-order DWORD of offset<br> DWORD dwFileOffsetLow, // low-order DWORD of offset<br> SIZE_T dwNumberOfBytesToMap // number of bytes to map<br>);</P>
<P>BOOL UnmapViewOfFile(<br> LPCVOID lpBaseAddress // starting address<br>);</P>
<P>BOOL SetEvent(<br> HANDLE hEvent // handle to event<br>);</P>
<P>DWORD WaitForSingleObject(<br> HANDLE hHandle, // handle to object<br> DWORD dwMilliseconds // time-out interval<br>);</P>
<P>BOOL CloseHandle(<br> HANDLE hObject // handle to object<br>);<br>+-----------------------------------------------------------------------------+</P>
<P>有关这些 API 的具体含义和使用方法请参考 MSDN 和 Windows 多线程编程的相关文档,本文不再<br>介绍。</P>
<P>重点讲一下 RME 的通信和服务流程。<br>命令的格式:<br>+------------------------------------------------------------+<br> RME_CMD cmd arg<br>+------------------------------------------------------------+<br> 其中:cmd 为命令代号为数字<br> arg 为命令参数</P>
<P>RME 中所有的命令定义如下:<br>+------------------------------------------------------------+<br>#define PBUF_SIZE 1024 // 参数缓冲区大小</P>
<P>#define RME_INIT 0 // RME初始化命令<br>#define RME_CLOSE 1 // RME关闭命令</P>
<P>#define RME_PLAYWAVE 10 // 播放 WAVE 文件<br>#define RME_STOPWAVE 11 // 停止播放 WAVE<br>#define RME_PAUSEWAVE 12 // 暂停播放 WAVE</P>
<P>#define RME_PLAYMIDI 20 // 播放 MIDI 文件<br>#define RME_STOPMIDI 21 // 停止播放 MIDI<br>#define RME_PAUSEMIDI 22 // 暂停播放 MIDI<br>+------------------------------------------------------------+</P>
<P>由于当前的 RME 只是一个演示版本,因此并没有实现更多功能,连 MIDI 的重复播放都没有作,因<br>此有点不实用。</P>
<P>在 RME 中并不是所有的命令都有参数,例如初始化命令:<br>+------------------------------------------------------------+<br> RME_CMD 0<br>+------------------------------------------------------------+<br>关闭命令也是如此。</P>
<P>RME 命令的使用流程,也可以叫做 RME 协议:<br> 1. Send RME_INIT commond to RME_Server<br> RME_CMD 0<br> This command will start and init the RME_Server.</P>
<P> 2. Send media playing control command to RME_Server<br> RME_CMD 10 test.wav<br> RME_CMD 20 test.mid<br> ...<br> After these command send to RME_Server, it will response to these command, and act the cmd you want.</P>
<P> 3. Send RME_CLOSE command to RME_Server<br> RME_CMD 1<br> This command will stop and close the RME_Server.</P>
<P>RME_CMD 程序可以理解为一个命令行程序,他只是简单的处理命令行的输入,而前面所讲的各种命<br>令都是通过命令行参数传递给 RME_CMD 程序的。</P>
<P>对于 RME_INIT 命令,RME_CMD 将创建 RME_Server 进程,以开启 RME_Server 服务。代码如下:<br>+------------------------------------------------------------+<br> if (cmd==RME_INIT)<br> {<br> PROCESS_INFORMATION pinfo = {0};<br> STARTUPINFO sinfo = {0};<br> CreateProcess("RME_Server.exe", NULL, NULL, NULL, NULL, <br> CREATE_NEW_CONSOLE, NULL, NULL, &sinfo, &pinfo);<br> return TRUE;<br> }<br>+------------------------------------------------------------+</P>
<P>RME_Server 程序内部有特别的处理,以保证 RME_Server 程序在 Windows 中只会运行一个实例,<br>这是通过一个内核对象进行判断的,代码如下:<br>+------------------------------------------------------------------+<br> // 使程序只运行一个实例<br> hRqtEvent = CreateEvent(NULL, FALSE, FALSE, "RME_RQT_Event");<br> if (GetLastError()==ERROR_ALREADY_EXISTS)<br> {<br> CloseHandle(hRqtEvent);<br> return;<br> }<br>+------------------------------------------------------------------+</P>
<P>对于其他命令,都是通过事件和共享内存向 RME_Server 发送。RME_Server 将执行一个 while 循<br>环,在这个 while 循环中等待命令事件的产生,如下:<br>+------------------------------------------------------------------+<br> // 进入Server循环<br> while(!bExit)<br> {<br> WaitForSingleObject(hRqtEvent, INFINITE); // 等待客户请求<br> // todo: get and handle cmd<br> // ...<br> SetEvent(hAckEvent); // 发送应答事件<br> }<br>+------------------------------------------------------------------+</P>
<P>在这里使用到了两个有名的事件对象一个是 RME_RQT_Event 用于 RMD_CMD 发出命令请求,一个是 <br>RME_ACK_Event,用于 RME_Server 对 RME_CMD 进行应答。</P>
<P>在发送命令时 RME_CMD 先将命令代码和参数打包到一个 RME_CMD 的结构体变量中,如下:<br>+------------------------------------------------------------------+<br>typedef struct<br>{<br> int cmd; // 请求命令<br> int rval; // 返回值<br> BYTE pbuf[PBUF_SIZE]; // 参数缓冲区<br>}RME_CMD;<br>+------------------------------------------------------------------+</P>
<P>然后将这个命令的结构体变量复制到名为 RME_CMD 的共享内存中。之后设置 RME_RQT_Event 事件,<br>向 RME_Server 发出命令请求。之后等待 RME_Server 的响应,也就是等待 RME_ACK_Event。</P>
<P>RME_Server 在等到 RME_RQT_Event 事件后,会从共享内存取得命令的代码和命令参数,然后对不<br>同的命令进行处理,处理完后会设置 RME_ACK_Event 事件给 RME_CMD 一个应答。RME_Server 的音<br>频播放的实现,也就是最简单的采用了 PlaySound 和 MCI。</P>
<P>RME_CMD 在得到 RME_Server 的应答以后,关闭所有句柄、释放资源,然后结束程序。<br>经典的三次握手,一次命令的发送、接收、处理和应答的流程就是如此简单了。</P>
<P>对于 RME_CLOSE 命令,当 RME_Server 收到该命令后,将推出 while 循环,然后跑到进程的终点,<br>最终结束服务进程的运行。</P>
<P>在 Client 这一端,RME_CMD 是命令的转发者,而 system 函数保证了 DOS 程序与 RME_CMD 的通<br>信。另外还需要实现一个 RMELib 的 Turbo C 函数库,以方便 Turbo C 用户使用。由于 RMELib 只<br>需要简单的调用 system 函数,原理非常简单,因此不再介绍。<br><br>整个系统地实现都是相对简单,而且比较合理高效,实际的运行效果也非常理想,绝对可以满足一<br>般的简单的 DOS 游戏开发的需求。效率的瓶颈就在于 RME 系统三中个模块之间的通信,RME_CMD 与 <br>RME_Server 之间的通信还算比较快,而 system 函数则成为了最大的系统瓶颈。当然,这样的系统<br>设计,是绝对不可能满足高性能游戏的需求的,这只是在特定平台下的解决办法。如果要开发高性<br>能的游戏,就选择高性能的平台吧,如流行的 DirectX,这就不在本文的讨论范围。</P>
<P>另外,RME 目前仅仅是一个 Demo,旨在给出一种方法和思路而已。大家可以根据这个思路,设计出<br>更加实用、更加通用的系统。</P>
<P> RockCarry<br> 2007-3-31</P><br><br><br><br><br>
<P><br></P>
[align=right][color=#000066][此贴子已经被作者于2007-6-1 16:58:42编辑过][/color][/align]
<P>今天终于完成了<br>一直都想写,可是现在下班后就什么都不想做,连显示器都不想看。<br>今天努力了下,把他完成了<br>还有很多技术文章,都想写出来,可惜啊,可能是自己太累了吧<br>大家帮忙找找错别字吧。</P>
[align=right][color=#000066][此贴子已经被作者于2007-5-30 20:49:57编辑过][/color][/align]
以上就是最简单的实现,但是这样的实现还存在许多问题:<BR> 1. 没播放一个文件就要开启一个 RME01 的进程<BR>“没”->“每”<BR><BR>不错,支持!我现在也在做一个类似的C/S程序,原理与这个相似,而且它支持更多的功能,比如图形处理等等。<BR> 最关键的是要搞懂原理,看到本质。<BR>网上许多开源的代码写的并不是最好的,但是只要弄清楚了原理,就可以自己去实现。 你们真历害呢~可是我有个疑问:<BR>我觉得DOS程序在原理学习和技术研究上还是很有用的.可是在实用方面就大可不必了.毕竟大部分人都用WINDOWS.<BR>既然这样,为什么还要在DOS下折腾呢?<BR>如果要作个播放器,用VC可以方便作出来吧?<BR><BR>不过楼主说得好,<BR>这个也是一个解决问题的办法,更加重要的是给出一个方法和思维方式。[em17]<BR> <P>编程的乐趣就在于优美的实现,把不可能优雅的变成可能。DOS 的确很落后了,所以有很多的不可能,当然把这些不可能便成为可能的时候,会是无比的激动和兴奋,而自己对编程、对平台、对整个计算机系统的理解也会更加的深入。<br>Windows 确实太强大了,强大到不用写代码就可以实现许多功能,比如 MFC。但是,少了自己的努力,一切都变得很无趣,你不觉得吗。我当时学 MFC 时真的不知道如何下手,因为代码都给你做好了,你要做的只是添加和修改。<br><br>软件开发的实质,就是深入的理解平台,最大限度的利用平台已经提供的功能,去实现自己需要而平台却没有提供的功能。编程的乐趣和所带来的价值,都在于实现上,实现是一个劳动的过程,因此是一个创造价值的过程。如果一个程序很容易实现,那么就少了乐趣,也少了价值。就如同 HelloWorld 程序,谁都会实现,可是谈不上乐趣,更加谈不上价值。<br><br>为什么沉迷于 DOS,就是因为 DOS 是一个开放的平台,非常适合于学习一些底层的知识,并且容易入门,相关的参考资料也比较多。当然,再任何一个平台上,都有值得理解和学习的东西,也有需要去实现的东西。实现的过程,其实也是一个平台搭建的过程,平台就是这样一层一层的实现,一层一层的搭建起来的。个人认为,做的层的东西,是很难的,然后却是很有价值的。<br><br>如果要问为什么,我的答案就是,我们需要站在巨人的肩膀上,但我们不愿意简单的站在别人的平台上。理解技术的本质,尝试重新实现,夸张的说,自己做出更好的 Windows 吧。</P>
[align=right][color=#000066][此贴子已经被作者于2007-6-7 17:51:22编辑过][/color][/align]
实用性是一方面,学习是另一方面。<BR>也许实用性达不到要求,但是不会白白努力的,可以学习很多东西。<BR>就算 RME 最终不实用,但是我能把 RME 做到这个程度,对 Windows 的多线程编程技术也已经有了较为深入的理解。<BR>如果我没有这个兴趣,没有这样的执著,恐怕到现在也就是一事无成。<BR>关键要看自己的兴趣了。 RockCarry不仅技术好,心态也好呢.你的观点我同意了.向你学习~以后多指点我这个小初哥哦.<BR><BR> 这么好的贴也不顶,要什么才顶,顶起~~
回复:(RockCarry)[原创]发一个程序吧,一个播放音频...
说到心坎上了。。顶!页:
[1]
2
