OpenAL播放pcm或wav数据流-windows/ios/android(一)

时间:2022-10-19 15:12:01

OpenAL播放pcm或wav数据流-windows/iOS/Android(一)

 

最近在研究渲染问题,本文采用openal做pcm和wav数据流播放,并非本地文件,demo是windows的,ios通用。网上都是ios的,ios需要引用OpenAl.framework框架,

Android平台需要做openal的jni,android的openal库可以参考

http://blog.csdn.NET/matrix_laboratory/article/details/53319735这篇文章,各个平台需要做稍微处理。

下面是代码:

//.h

  1. /** Copyright (c/c++) <2016.11.22> <zwg/>
  2. * Function
  3. * OpenAL through the buffer queuing mechanism to support the streaming playback of sound. The buffer queue is a buffer associated with a single source contact mechanism.
  4. * when audio playback, continuous rendering of each buffer, as if the buffer is composed of a continuous sound. This can be controlled by some special functions.
  5. * flow is generally the source of the work. In a number of audio buffer by alSourceQueueBuffers () function to queue, and then play the sound source,
  6. * next with property AL_BUFFERS_PROCESSED to query. This property obtains the number of buffers that have been processed,
  7. * allows applications to use the alSourceUnqueueBuffers () function to delete the buffers that have been processed.
  8. * alSourceUnqueueBuffers () function will start from the queue header will be processed in order to remove the buffer. Finally, the rest of the buffer queue in gear.
  9. * Opanal for audio rendering related implementation and definition, etc.
  10. * OpenAL通过缓冲器排队机制支持声音的流式播放。缓冲器排队是多个缓冲器与单一音源相关联的一种机制。
  11. * 当音源播放时,连续对各个缓冲器进行渲染,就好象这些缓冲器组成了一个连续的声音。这可以通过一些特殊函数来控制。
  12. * 流音源的工作一般是这样的。音源里的一批缓冲器通过alSourceQueueBuffers()函数进行排队,然后播放音源,
  13. * 接下来用属性AL_BUFFERS_PROCESSED来查询。该属性得出已经处理好的缓冲器的数量,
  14. * 从而允许应用程序使用alSourceUnqueueBuffers()函数删除那些已经处理好的缓冲器。
  15. * alSourceUnqueueBuffers()函数将从队列头部开始依次将处理好的缓冲器删除。最后,其余的缓冲器在音源上排队。
  16. * OpanAl 用于音频渲染相关实现及定义,等
  17. */
  18. #ifndef __LVS_OPENAL_INTERFACE_H__
  19. #define __LVS_OPENAL_INTERFACE_H__
  20. #include <stdio.h>
  21. #include <stdlib.h>
  22. #include <string>
  23. //windows
  24. #ifdef WIN32
  25. #include <Windows.h>
  26. //openAl库
  27. #include "alut.h"
  28. #pragma comment(lib,"alut.lib")
  29. #pragma comment(lib,"OpenAL32.lib")
  30. //ios
  31. #elif __APPLE__
  32. #include "alut.h"
  33. //ANDROID平台
  34. #elif __ANDROID__
  35. #include "alut.h"
  36. //linux
  37. #else
  38. #include "alut.h"
  39. #endif
  40. //到处宏定义
  41. //windows
  42. #ifdef WIN32
  43. #define LVS_DLLEXPORT __declspec(dllexport)
  44. //ios
  45. #elif __APPLE__
  46. #define LVS_DLLEXPORT
  47. //linux
  48. #else
  49. #define LVS_DLLEXPORT
  50. #endif
  51. using namespace std;
  52. //接口初始化
  53. int lvs_openal_interface_init();
  54. //接口释放
  55. void lvs_openal_interface_uninit();
  56. //接口开始播放
  57. void lvs_openal_interface_playsound();
  58. //接口停止播放
  59. void lvs_openal_interface_stopsound();
  60. //接口设置音量
  61. void lvs_openal_interface_setvolume(float volume);//volume取值范围(0~1)
  62. //接口获取音量
  63. float lvs_openal_interface_getvolume();
  64. //接口传入pcm数据用于播放
  65. int lvs_openal_interface_openaudiofromqueue(char* data,int dataSize,int aSampleRate,int aBit ,int aChannel);
  66. //更新队列数据,删除已经播放的buffer,这个在队列满的时候用
  67. int lvs_openal_interface_updataQueueBuffer();
  68. //获取当前时间戳
  69. long long lvs_openal_interface_getrealpts();
  70. //获取已经播放了多少个数据块
  71. long long lvs_openal_interface_getIsplayBufferSize();
  72. //获取缓存队列长度
  73. int lvs_openal_getnumqueuedsize();
  74. class cclass_openal_interface;
  75. class cclass_openal_interface
  76. {
  77. public:
  78. cclass_openal_interface();
  79. virtual ~cclass_openal_interface();
  80. //开始播放
  81. void playSound();
  82. //停止播放
  83. void stopSound();
  84. //设置音量
  85. void SetVolume(float volume);//volume取值范围(0~1)
  86. //获取音量
  87. float GetVolume();
  88. //传入pcm数据用于播放
  89. int openAudioFromQueue(char* data,int dataSize,int aSampleRate,int aBit ,int aChannel);
  90. //更新队列数据,删除已经播放的buffer
  91. int updataQueueBuffer();
  92. private:
  93. //初始化openal
  94. int initOpenAL();
  95. //释放openal
  96. void cleanUpOpenAL();
  97. public:
  98. int m_numprocessed;             //队列中已经播放过的数量
  99. int m_numqueued;                //队列中缓冲队列数量
  100. long long m_IsplayBufferSize;   //已经播放了多少个音频缓存数目
  101. double m_oneframeduration;      //一帧音频数据持续时间(ms)
  102. float m_volume;                 //当前音量volume取值范围(0~1)
  103. int m_samplerate;               //采样率
  104. int m_bit;                      //样本值
  105. int m_channel;                  //声道数
  106. int m_datasize;                 //一帧音频数据量
  107. private:
  108. ALCdevice * m_Devicde;          //device句柄
  109. ALCcontext * m_Context;         //device context
  110. ALuint m_outSourceId;           //source id 负责播放
  111. };
  112. #endif

//.cpp

  1. #include "Lvs_OpenAl_Interface.h"
  2. static cclass_openal_interface * copenal_interface = NULL;
  3. int lvs_openal_interface_init()
  4. {
  5. int ret = 0;
  6. printf("Device : lvs_openal_interface_init\n");
  7. if(copenal_interface == NULL)
  8. {
  9. copenal_interface = new cclass_openal_interface();
  10. }
  11. return ret;
  12. }
  13. void lvs_openal_interface_uninit()
  14. {
  15. printf("Device : lvs_openal_interface_uninit\n");
  16. if(copenal_interface)
  17. {
  18. delete copenal_interface;
  19. copenal_interface = NULL;
  20. }
  21. return ;
  22. }
  23. void lvs_openal_interface_playsound()
  24. {
  25. copenal_interface->playSound();
  26. }
  27. void lvs_openal_interface_stopsound()
  28. {
  29. copenal_interface->stopSound();
  30. }
  31. void lvs_openal_interface_setvolume(float volume)//volume取值范围(0~1)
  32. {
  33. copenal_interface->SetVolume(volume);
  34. }
  35. float lvs_openal_interface_getvolume()
  36. {
  37. return copenal_interface->GetVolume();
  38. }
  39. int lvs_openal_interface_openaudiofromqueue(char* data,int dataSize,int aSampleRate,int aBit ,int aChannel)
  40. {
  41. return copenal_interface->openAudioFromQueue(data,dataSize,aSampleRate,aBit,aChannel);
  42. }
  43. long long lvs_openal_interface_getrealpts()
  44. {
  45. long long time = (long long )((copenal_interface->m_IsplayBufferSize * copenal_interface->m_oneframeduration) + 0.5);
  46. printf("*****m_IsplayBufferSize : %ld",copenal_interface->m_IsplayBufferSize);
  47. printf("****************time : %lld(ms)\n",time);
  48. return time;
  49. }
  50. long long lvs_openal_interface_getIsplayBufferSize()
  51. {
  52. return copenal_interface->m_IsplayBufferSize;
  53. }
  54. int lvs_openal_getnumqueuedsize()
  55. {
  56. return copenal_interface->m_numqueued;
  57. }
  58. int lvs_openal_interface_updataQueueBuffer()
  59. {
  60. return copenal_interface->updataQueueBuffer();
  61. }
  62. cclass_openal_interface::cclass_openal_interface()
  63. {
  64. m_Devicde = NULL;
  65. m_Context = NULL;
  66. m_outSourceId = 0;
  67. m_numprocessed = 0;
  68. m_numqueued = 0;
  69. m_IsplayBufferSize = 0;
  70. m_oneframeduration = 0.0;
  71. m_volume = 1.0;
  72. m_samplerate = 0;
  73. m_bit = 0;
  74. m_channel = 0;
  75. m_datasize = 0;
  76. //init
  77. initOpenAL();
  78. }
  79. cclass_openal_interface::~cclass_openal_interface()
  80. {
  81. cleanUpOpenAL();
  82. m_Devicde = NULL;
  83. m_Context = NULL;
  84. m_outSourceId = 0;
  85. m_numprocessed = 0;
  86. m_numqueued = 0;
  87. m_IsplayBufferSize = 0;
  88. m_oneframeduration = 0.0;
  89. m_volume = 1.0;
  90. m_samplerate = 0;
  91. m_bit = 0;
  92. m_channel = 0;
  93. m_datasize = 0;
  94. }
  95. int cclass_openal_interface::initOpenAL()
  96. {
  97. int ret = 0;
  98. printf("=======initOpenAl===\n");
  99. #ifdef WIN32
  100. //初始化 ALUT openal函数库
  101. int zwg_argc=1;
  102. //添加函数库名称
  103. char* zwg_argv[]={"ZWG_ALUT"};
  104. ret= alutInit(&zwg_argc, zwg_argv);
  105. #else
  106. #endif
  107. //打开device
  108. m_Devicde = alcOpenDevice(NULL);
  109. if (m_Devicde)
  110. {
  111. #ifdef WIN32
  112. //windows 用这个context 声音不正常,以后处理
  113. #else
  114. //建立声音文本描述
  115. m_Context = alcCreateContext(m_Devicde, NULL);
  116. //设置行为文本描述
  117. alcMakeContextCurrent(m_Context);
  118. #endif
  119. }
  120. //创建一个source并设置一些属性
  121. alGenSources(1, &m_outSourceId);
  122. alSpeedOfSound(1.0);
  123. alDopplerVelocity(1.0);
  124. alDopplerFactor(1.0);
  125. alSourcef(m_outSourceId, AL_PITCH, 1.0f);
  126. alSourcef(m_outSourceId, AL_GAIN, 1.0f);
  127. alSourcei(m_outSourceId, AL_LOOPING, AL_FALSE);
  128. alSourcef(m_outSourceId, AL_SOURCE_TYPE, AL_STREAMING);
  129. return ret;
  130. }
  131. void cclass_openal_interface::cleanUpOpenAL()
  132. {
  133. printf("=======cleanUpOpenAL===\n");
  134. alDeleteSources(1, &m_outSourceId);
  135. #ifdef WIN32
  136. alcCloseDevice(m_Devicde);
  137. m_Devicde = NULL;
  138. alutExit();
  139. #else
  140. ALCcontext * Context = alcGetCurrentContext();
  141. ALCdevice * Devicde = alcGetContextsDevice(Context);
  142. if (Context)
  143. {
  144. alcMakeContextCurrent(NULL);
  145. alcDestroyContext(Context);
  146. m_Context = NULL;
  147. }
  148. alcCloseDevice(m_Devicde);
  149. m_Devicde = NULL;
  150. #endif
  151. }
  152. void cclass_openal_interface::playSound()
  153. {
  154. int ret = 0;
  155. alSourcePlay(m_outSourceId);
  156. if((ret = alGetError()) != AL_NO_ERROR)
  157. {
  158. printf("error alcMakeContextCurrent %x : %s\n", ret,alutGetErrorString (ret));
  159. }
  160. }
  161. void cclass_openal_interface::stopSound()
  162. {
  163. alSourceStop(m_outSourceId);
  164. }
  165. void cclass_openal_interface::SetVolume(float volume)//volume取值范围(0~1)
  166. {
  167. m_volume = volume;
  168. alSourcef(m_outSourceId,AL_GAIN,volume);
  169. }
  170. float cclass_openal_interface::GetVolume()
  171. {
  172. return m_volume;
  173. }
  174. int cclass_openal_interface::updataQueueBuffer()
  175. {
  176. //播放状态字段
  177. ALint stateVaue = 0;
  178. //获取处理队列,得出已经播放过的缓冲器的数量
  179. alGetSourcei(m_outSourceId, AL_BUFFERS_PROCESSED, &m_numprocessed);
  180. //获取缓存队列,缓存的队列数量
  181. alGetSourcei(m_outSourceId, AL_BUFFERS_QUEUED, &m_numqueued);
  182. //获取播放状态,是不是正在播放
  183. alGetSourcei(m_outSourceId, AL_SOURCE_STATE, &stateVaue);
  184. //printf("===statevaue ========================%x\n",stateVaue);
  185. if (stateVaue == AL_STOPPED ||
  186. stateVaue == AL_PAUSED ||
  187. stateVaue == AL_INITIAL)
  188. {
  189. //如果没有数据,或数据播放完了
  190. if (m_numqueued < m_numprocessed || m_numqueued == 0 ||(m_numqueued == 1 && m_numprocessed ==1))
  191. {
  192. //停止播放
  193. printf("...Audio Stop\n");
  194. stopSound();
  195. cleanUpOpenAL();
  196. return 0;
  197. }
  198. if (stateVaue != AL_PLAYING)
  199. {
  200. playSound();
  201. }
  202. }
  203. //将已经播放过的的数据删除掉
  204. while(m_numprocessed --)
  205. {
  206. ALuint buff;
  207. //更新缓存buffer中的数据到source中
  208. alSourceUnqueueBuffers(m_outSourceId, 1, &buff);
  209. //删除缓存buff中的数据
  210. alDeleteBuffers(1, &buff);
  211. //得到已经播放的音频队列多少块
  212. m_IsplayBufferSize ++;
  213. }
  214. long long time = (long long )((m_IsplayBufferSize * m_oneframeduration) + 0.5);
  215. //printf("*****m_IsplayBufferSize : %ld",m_IsplayBufferSize);
  216. //printf("****************time : %ld(ms)\n",time);
  217. return 1;
  218. }
  219. int cclass_openal_interface::openAudioFromQueue(char* data,int dataSize,int aSampleRate,int aBit ,int aChannel)
  220. {
  221. int ret = 0;
  222. //样本数openal的表示方法
  223. ALenum format = 0;
  224. //buffer id 负责缓存,要用局部变量每次数据都是新的地址
  225. ALuint bufferID = 0;
  226. if (m_datasize == 0 &&
  227. m_samplerate == 0 &&
  228. m_bit == 0 &&
  229. m_channel == 0)
  230. {
  231. if (dataSize != 0 &&
  232. aSampleRate != 0 &&
  233. aBit != 0 &&
  234. aChannel != 0)
  235. {
  236. m_datasize = dataSize;
  237. m_samplerate = aSampleRate;
  238. m_bit = aBit;
  239. m_channel = aChannel;
  240. m_oneframeduration = m_datasize * 1.0 /(m_bit/8) /m_channel /m_samplerate * 1000 ;   //计算一帧数据持续时间
  241. }
  242. }
  243. //创建一个buffer
  244. alGenBuffers(1, &bufferID);
  245. if((ret = alGetError()) != AL_NO_ERROR)
  246. {
  247. printf("error alGenBuffers %x : %s\n", ret,alutGetErrorString (ret));
  248. //AL_ILLEGAL_ENUM
  249. //AL_INVALID_VALUE
  250. //#define AL_ILLEGAL_COMMAND                        0xA004
  251. //#define AL_INVALID_OPERATION                      0xA004
  252. }
  253. if (aBit == 8)
  254. {
  255. if (aChannel == 1)
  256. {
  257. format = AL_FORMAT_MONO8;
  258. }
  259. else if(aChannel == 2)
  260. {
  261. format = AL_FORMAT_STEREO8;
  262. }
  263. }
  264. if( aBit == 16 )
  265. {
  266. if( aChannel == 1 )
  267. {
  268. format = AL_FORMAT_MONO16;
  269. }
  270. if( aChannel == 2 )
  271. {
  272. format = AL_FORMAT_STEREO16;
  273. }
  274. }
  275. //指定要将数据复制到缓冲区中的数据
  276. alBufferData(bufferID, format, data, dataSize,aSampleRate);
  277. if((ret = alGetError()) != AL_NO_ERROR)
  278. {
  279. printf("error alBufferData %x : %s\n", ret,alutGetErrorString (ret));
  280. //AL_ILLEGAL_ENUM
  281. //AL_INVALID_VALUE
  282. //#define AL_ILLEGAL_COMMAND                        0xA004
  283. //#define AL_INVALID_OPERATION                      0xA004
  284. }
  285. //附加一个或一组buffer到一个source上
  286. alSourceQueueBuffers(m_outSourceId, 1, &bufferID);
  287. if((ret = alGetError()) != AL_NO_ERROR)
  288. {
  289. printf("error alSourceQueueBuffers %x : %s\n", ret,alutGetErrorString (ret));
  290. }
  291. //更新队列数据
  292. ret = updataQueueBuffer();
  293. //删除一个缓冲 这里不应该删除缓冲,在source里面播放完毕删除
  294. //alDeleteBuffers(1, &bufferID);
  295. bufferID = 0;
  296. return 1;
  297. }

//main.cpp

  1. #include "Lvs_OpenAl_Interface.h"
  2. //要显示的pcm/wav文件路径及名称
  3. #define PCM_STREAM_PATH_NAME  "../pcm_stream/44100_2_16.pcm"
  4. int main()
  5. {
  6. int ret = 0;
  7. int nSampleRate = 44100;                   //采样率
  8. int nBit = 16;                             //样本数
  9. int nChannel = 2;                          //声道
  10. int ndatasize = 1024 * (nBit/8) *nChannel; //每次读取的数据大小
  11. char ndata[4096 + 1] = {0};                //读取的数据
  12. FILE * pFile_pcm = NULL;                   //读取pcm数据的文件句柄
  13. //打开pcm文件
  14. if((pFile_pcm = fopen(PCM_STREAM_PATH_NAME, "rb")) == NULL)
  15. {
  16. printf("filed open file : %s\n",PCM_STREAM_PATH_NAME);
  17. return getchar();
  18. }
  19. else
  20. {
  21. printf("success open file : %s\n",PCM_STREAM_PATH_NAME);
  22. }
  23. //init
  24. lvs_openal_interface_init();
  25. //设置音量volume取值范围(0~1)
  26. lvs_openal_interface_setvolume(1.0);
  27. for(;;)
  28. {
  29. Sleep(23);
  30. //循环读取文件
  31. ret = fread(ndata, 1,ndatasize, pFile_pcm);
  32. if (ret != ndatasize)
  33. {
  34. //seek到文件开头
  35. fseek(pFile_pcm, 0, SEEK_SET);
  36. fread(ndata, 1,ndatasize, pFile_pcm);
  37. }
  38. //具体的处理在这里
  39. ret = lvs_openal_interface_openaudiofromqueue((char *)ndata,ndatasize,nSampleRate,nBit,nChannel);
  40. long long time = lvs_openal_interface_getrealpts();
  41. }
  42. //uinit
  43. lvs_openal_interface_uninit();
  44. //关闭pcm文件
  45. if (pFile_pcm != NULL)
  46. {
  47. fclose(pFile_pcm);
  48. pFile_pcm = NULL;
  49. }
  50. return 1;
  51. }

程序运行效果并能听到声音:

OpenAL播放pcm或wav数据流-windows/ios/android(一)

本demo还需完善。

from:http://blog.csdn.net/zhuweigangzwg/article/details/53286945

OpenAL播放pcm或wav数据流-windows/ios/android(一)的更多相关文章

  1. js判断操作系统windows&comma;ios&comma;android&lpar;笔记&rpar;

    使用JS判断用户使用的系统是利用浏览器的userAgent. navigator.userAgent:userAgent 获取了浏览器用于 HTTP 请求的用户代理头的值. navigator.pla ...

  2. 微软云平台媒体服务实践系列 2- 使用动态封装为iOS&comma; Android &comma; Windows 等多平台提供视频点播(VoD)方案

    文章微软云平台媒体服务实践系列 1- 使用静态封装为iOS, Android 设备实现点播(VoD)方案  介绍了如何针对少数iOS, Android 客户端的场景,出于节约成本的目的使用媒体服务的静 ...

  3. WIN32下使用DirectSound接口的简单音频播放器&lpar;支持wav和mp3&rpar;

    刚好最近接触了一些DirectSound,就写了一个小程序练练手,可以用来添加播放基本的wav和mp3音频文件的播放器.界面只是简单的GDI,dxsdk只使用了DirectSound8相关的接口. D ...

  4. DirectSound播放PCM&lpar;可播放实时采集的音频数据&rpar;

    前言 该篇整理的原始来源为http://blog.csdn.net/leixiaohua1020/article/details/40540147.非常感谢该博主的无私奉献,写了不少关于不同多媒体库的 ...

  5. Web Api 中使用 PCM TO WAV 的语音操作

    /// <summary> /// 语音[文件.上传.解码.保存(WAV)] /// </summary> [DeveloperEx("Liwei:秘书语音需求单&q ...

  6. 最简单的视音频播放示例9:SDL2播放PCM

    本文记录SDL播放音频的技术.在这里使用的版本是SDL2.实际上SDL本身并不提供视音频播放的功能,它只是封装了视音频播放的底层API.在Windows平台下,SDL封装了Direct3D这类的API ...

  7. 最简单的视音频播放示例8:DirectSound播放PCM

    本文记录DirectSound播放音频的技术.DirectSound是Windows下最常见的音频播放技术.目前大部分的音频播放应用都是通过DirectSound来播放的.本文记录一个使用Direct ...

  8. 最简单的视音频播放演示样例8:DirectSound播放PCM

    ===================================================== 最简单的视音频播放演示样例系列文章列表: 最简单的视音频播放演示样例1:总述 最简单的视音频 ...

  9. 微信小程序语音与讯飞语音识别接口(Java),Kronopath&sol;SILKCodec,ffmpeg处理silk,pcm,wav转换

    项目需求,需要使用讯飞的语音识别接口,将微信小程序上传的录音文件识别成文字返回 首先去讯飞开放平台中申请开通语音识别功能 在这里面下载sdk,然后解压,注意appid与sdk是关联的,appid在初始 ...

随机推荐

  1. Python发布包到Pypi

    本地打包:python setup.py sdist 上传Pypi:python setup.py register sdist upload

  2. C&num;开源系统大汇总(转)

    一.AOP框架        Encase 是C#编写开发的为.NET平台提供的AOP框架.Encase 独特的提供了把方面(aspects)部署到运行时代码,而其它AOP框架依赖配置文件的方式.这种 ...

  3. 收集统计信息让SQL走正确的执行计划

    数据库环境:SQL SERVER 2005 今天在生产库里抓到一条跑得慢的SQL,语句不是很复杂,返回的数据才有800多行, 却执行了34分钟,甚至更久. 先看一下执行结果 我贴一下SQL. SELE ...

  4. JavaSE学习笔记

    1.数据类型 boolean char byte short int long double float double array class interface 总结: 前9种基本类型,后3中引用类 ...

  5. 面向对象设计——抽象工厂&lpar;Abstract Factory&rpar;模式

    定义 提供一个创建一系列相关或者相互依赖对象的接口,而无需指定它们具体的类.抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道或关心实际产出的具体产品是什么.这样一来,客户就能从具体的产 ...

  6. Solve fatal error&colon; helper&lowbar;math&period;h&colon; No such file or directory

    When the 'fatal error: helper_math.h: No such file or directory' occurs, it means the 'helper_math.h ...

  7. vue 学习笔记—路由篇

    一.关于三种路由 动态路由 就是path:good/:ops    这种 用 $route.params接收 <router-link>是用来跳转  <router-view> ...

  8. ES6,Array&period;find&lpar;&rpar;和findIndex&lpar;&rpar;函数的用法

    ES6为Array增加了find(),findIndex函数. find()函数用来查找目标元素,找到就返回该元素,找不到返回undefined. findIndex()函数也是查找目标元素,找到就返 ...

  9. Java基础-面向对象&lpar;08&rpar;

    面向过程 完成一个需求的步骤:首先是搞清楚我们要做什么,然后在分析怎么做,最后我们再代码体现.一步一步去实现,而具体的每一步都需要我们去实现和操作.这些步骤相互调用和协作,完成我们的需求.面向过程开发 ...

  10. pip简单配置

    pip安装Python模块的工具,等价于Redhat中的yum! 01.下载 百度云盘:http://pan.baidu.com/s/1eRHGBfk             ###相关的 Linux ...