fl2440——按键控制madplay音乐播放器
今日诗词分享:该段诗词描述的是国产武侠游戏《剑侠情缘三》中的职业——天策(游戏中对唐朝军人的称呼)。
天策
长河落日东都城,铁马戍边将军坟。
尽诛宵小天策义,长枪独守大唐魂。
==========================================================================
主机操作系统:Centos 6.7
交叉编译器环境:arm-linux-gcc-4.5.4 (可通过命令/opt/buildroot-2012.08/arm920t/usr/bin/arm-linux-gcc -v查询)
开发板平台: fl2440
Linux内核版本: linux-3.0 .54
==========================================================================
好了,接下来进入正题。之前,我们通过两种方法移植了madplay播放器,现在,开始尝试通过应用程序来实现用按键控制madplay播放器。按键用的是我们自己写的platform的按键驱动,不是内核自带的。
基于我们的程序,fl2440有四个按键,分别设置为:
KEY1:播放/暂停
KEY2:停止
KEY3:上一首
KEY4:下一首
按键控制播放器代码:
/********************************************************************************** Copyright: (C) 2017 qicheng* All rights reserved.** Filename: bbb.c* Description: This file * * Version: 1.0.0(04/27/2017)* Author: yangni <497049229@qq.com>* ChangeLog: 1, Release initial version on "04/27/2017 07:49:28 PM"* ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>
#include <sys/wait.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define KEY1 0x1 //1<<1
#define KEY2 0x2 //1<<2
#define KEY3 0x4 //1<<3
#define KEY4 0x8 //1<<4 /*共享内存申请标记*/
#define PERM S_IRUSR|S_IWUSR/*双向循环列表:存放歌曲名*/
struct song
{char songname[64];struct song *prev;struct song *next;
};/*孙子进程id号*/
pid_t gradchild;/*子进程id号*/
pid_t pid;/*共享内存描述标记*/
int shmid;char *p_addr;/*播放标记*/
int first_key=1;
int play_flag=0;/*************************************************
Function name: play
Parameter : struct song *
Description : 播放函数
Return : void
Argument : void
**************************************************/
void play(struct song *currentsong)
{pid_t fd;char *c_addr;char *p;int len;char my_song[64]="/mp3/song/";while(currentsong){/*创建子进程,即孙子进程*/fd = fork();if(fd == -1){ perror("fork");exit(1);}else if(fd == 0){/*把歌曲名加上根路径*/strcat(my_song,currentsong->songname);p = my_song;len = strlen(p);/*去掉文件名最后的'\n'*/my_song[len-1]='\0';printf("THIS SONG IS %s\n",my_song);execl("/mp3/madplay","madplay",my_song,NULL);printf("\n\n\n");}else{/*内存映射*/c_addr = shmat(shmid,0,0);/*把孙子进程的id和当前播放歌曲的节点指针传入共享内存*/memcpy(c_addr,&fd,sizeof(pid_t));memcpy(c_addr + sizeof(pid_t)+1,¤tsong,4);/*使用wait阻塞孙子进程,直到孙子进程播放完才能被唤醒;当被唤醒时,表示播放MP3期间没有按键按下,则继续顺序播放下一首MP3*/if(fd == wait(NULL)){currentsong = currentsong->next;printf("THE NEXT SONG IS %s\n",currentsong->songname);}}}
}/*************************************************
Function name: creat_song_list
Parameter : void
Description : 创建歌曲名的双向循环链表
Return : struct song *
Argument : void
**************************************************/
struct song *creat_song_list(void)
{ FILE *fd;size_t size;size_t len;char *line = NULL;struct song *head;struct song *p1;struct song *p2;system("ls /mp3/song > song_list");fd = fopen("song_list","r");p1 = (struct song *)malloc(sizeof(struct song));printf("==================================song list=====================================\n");system("ls /mp3/song"); printf("\n");printf("================================================================================\n");size = getline(&line,&len,fd);strncpy(p1->songname,line,strlen(line));head = p1;while((size = getline(&line,&len,fd)) != -1){ p2 = p1;p1 = (struct song *)malloc(sizeof(struct song));strncpy(p1->songname,line,strlen(line));p2->next = p1;p1->prev = p2; }p1->next = head;head->prev = p1;p1 = NULL;p2 = NULL;system("rm -rf song_list");return head;
}
/*************************************************
Function name: startplay
Parameter : pid_t *,struct song *
Description : 开始播放函数
Return : void
Argument : void
**************************************************/
void startplay(pid_t *childpid,struct song *my_song)
{pid_t pid;int ret;/*创建子进程*/pid = fork();if(pid > 0){*childpid = pid;play_flag = 1;sleep(1);/*把孙子进程的pid传给父进程*/memcpy(&gradchild,p_addr,sizeof(pid_t));}else if(0 == pid){ /*子进程播放MP3函数*/play(my_song);}
}
/*************************************************
Function name: my_pause
Parameter : pid_t
Description : 暂停函数
Return : void
Argument : void
**************************************************/
void my_pause(pid_t pid)
{printf("=======================PAUSE!PRESS K1 TO CONTINUE===================\n");kill(pid,SIGSTOP); //对孙子进程发送SKGSTOP信号play_flag = 0;
}/*************************************************
Function name: my_pause
Parameter : pid_t
Description : 停止播放函数
Return : void
Argument : void
**************************************************/
void my_stop(pid_t g_pid)
{printf("=======================STOP!PRESS K1 TO START PLAY===================\n");kill(g_pid,SIGKILL); //对孙子进程发送SKGKILL信号kill(pid,SIGKILL); //对子进程发送SKGKILL信号first_key=1;}/*************************************************
Function name: conti_play
Parameter : pid_t
Description : 继续函数
Return : void
Argument : void
**************************************************/
void conti_play(pid_t pid)
{printf("===============================CONTINUE=============================\n");kill(pid,SIGCONT); //对孙子进程发送SIGCONT信号play_flag=1;
}/*************************************************
Function name: next
Parameter : pid_t
Description : 下一首函数
Return : void
Argument : void
**************************************************/
void next(pid_t next_pid)
{struct song *nextsong;printf("===============================NEXT MP3=============================\n");/*从共享内存获得孙子进程播放歌曲的节点指针*/memcpy(&nextsong,p_addr + sizeof(pid_t)+1,4);/*指向下首歌曲的节点*/nextsong = nextsong->next;/*杀死当前歌曲播放的子进程,孙子进程*/kill(pid,SIGKILL);kill(next_pid,SIGKILL);wait(NULL);startplay(&pid,nextsong);
}/*************************************************
Function name: prev
Parameter : pid_t
Description : 上一首函数
Return : void
Argument : void
Autor & date : yuanhui 09.12.08
**************************************************/
void prev(pid_t prev_pid)
{struct song *prevsong;/*从共享内存获得孙子进程播放歌曲的节点指针*/printf("===============================PRIOR MP3=============================\n");memcpy(&prevsong,p_addr + sizeof(pid_t)+1,4);/*指向上首歌曲的节点*/prevsong = prevsong->prev;/*杀死当前歌曲播放的子进程,孙子进程*/kill(pid,SIGKILL);kill(prev_pid,SIGKILL);wait(NULL);startplay(&pid,prevsong);
}/*************************************************
Function name: main
Parameter : void
Description : 主函数
Return : int
Argument : void
**************************************************/
int main(void)
{int buttons_fd;int key_value;struct song *head;/*打开设备文件*/buttons_fd = open("/dev/button", 0);if (buttons_fd < 0) {perror("open device buttons");exit(1);}/*创建播放列表*/head = creat_song_list();printf("===================================OPTION=======================================\n\n\n\n");printf(" K1:START/PAUSE K2:STOP K3:NEXT K4:PRIOR\n\n\n\n");printf("================================================================================\n");/*共享内存:用于存放子进程ID,播放列表位置*/if((shmid = shmget(IPC_PRIVATE,5,PERM))== -1)exit(1);p_addr = shmat(shmid,0,0);memset(p_addr,'\0',1024);while(1) {fd_set rds;int ret;FD_ZERO(&rds);FD_SET(buttons_fd, &rds);/*监听获取键值*/ret = select(buttons_fd + 1, &rds, NULL, NULL, NULL);if (ret < 0) {perror("select");exit(1);}if (ret == 0) printf("Timeout.\n");else if (FD_ISSET(buttons_fd, &rds)){int ret = read(buttons_fd, &key_value, sizeof key_value);if (ret != sizeof key_value) {if (errno != EAGAIN)perror("read buttons\n");continue;} else{//printf("buttons_value: %d\n", key_value+1);/*首次播放,必须是按键1*/if(first_key){switch(key_value){ case KEY1:startplay(&pid,head);first_key=0;break;case KEY2:case KEY3:case KEY4:printf("=======================PRESS K1 TO START PLAY===================\n");break;default:printf("=======================PRESS K1 TO START PLAY===================\n");break;} //end switch}//end if(first_key)/*若不是首次播放,则根据不同键值处理*/else if(!first_key){switch(key_value){case KEY1://printf("play_flag:%d\n",play_flag);if(play_flag)my_pause(gradchild);elseconti_play(gradchild);break;case KEY2:my_stop(gradchild);break;case KEY3:next(gradchild);break;case KEY4:prev(gradchild);break;} //end switch}//end if(!first_key)}}}close(buttons_fd);return 0;
}
从main函数入手看,该程序首先打开我们的按键设备。然后创建播放列表,再分配一段共享内存并初始化(用于存放子进程ID,播放列表位置)。然后通过select函数监听按键,通过播放标记first_key判断是否第一次播放,若first_key=1,则调用startplay()函数并把,first_key设为0,然后程序开始播放。
用交叉编译器编译完成后,把它下载到开发板上就可以运行了。注意,先得在开发板上下载好你的歌曲,需要提前把名字改为英文,然后创建文件夹,把歌曲放在一个文件夹下。比如我在根目录下创建了一个/mp3目录,/mp3目录下建了一个song目录,并把歌曲下载到song目录下,所以我的歌曲目录就为/mp3/song/,然后把madplay工具放在/mp3目录下,总之,打开的目录一定要对。
主要函数分析:
一、创建双向循环链表:
struct song *creat_song_list(void)
{ FILE *fd;size_t size;size_t len;char *line = NULL;struct song *head;struct song *p1;struct song *p2;system("ls /mp3/song > song_list");fd = fopen("song_list","r");p1 = (struct song *)malloc(sizeof(struct song));printf("==================================song list=====================================\n");system("ls /mp3/song"); printf("\n");printf("================================================================================\n");size = getline(&line,&len,fd);strncpy(p1->songname,line,strlen(line));head = p1;while((size = getline(&line,&len,fd)) != -1){ p2 = p1;p1 = (struct song *)malloc(sizeof(struct song));strncpy(p1->songname,line,strlen(line));p2->next = p1;p1->prev = p2; }p1->next = head;head->prev = p1;p1 = NULL;p2 = NULL;system("rm -rf song_list");return head;
}
(1)system("ls /mp3/song > song_list");
这里相当于execl()函数,用于执行系统命令,将/mp3/song下的内容重定向的song_list中。如果没有指定重定向到哪,就默认打印到屏幕上。
(2)size = getline(&line,&len,fd);
其中fd是FILE数据结构,用于表示一个文件,和文件描述符类似。fd=fopen("song_list","w");这就是说读取song_list文件的数据,将其一行的首地址给&line,line保存读到的文本,len为要读的文件的大小,fd代表文件指针。fd=fopen("song_list","w");其实就是以写的方式打开文件,并返回文件指针给fd,但是这个Line是保存的是一行内容,需要调节指针不断读取文件才能读整个文件的内容。
(3)创建双向循环列表:
因为歌曲需要实现上一首和下一首,所以链表能够前后访问。双向链表拥有头节点和尾节点。
struct song //创建一个双向链表
{char songname[64];struct song *prev;struct song *next;
};
struct song *head; //分别定义song的结构体struct song *p1;struct song *p2;
p2 = p1;p1 = (struct song *)malloc(sizeof(struct song));strncpy(p1->songname,line,strlen(line));p2->next = p1;p1->prev = p2;
头结点head相当于数组中的首地址,可以通过传参head来访问该链表。
二、开始播放函数:
void startplay(pid_t *childpid,struct song *my_song)
{pid_t pid;int ret;/*创建子进程*/pid = fork();if(pid > 0){*childpid = pid;play_flag = 1;sleep(1);/*把孙子进程的pid传给父进程*/memcpy(&gradchild,p_addr,sizeof(pid_t));}else if(0 == pid){ /*子进程播放MP3函数*/play(my_song);}
}
(1)函数调用在main中:
startplay(&pid,head);
这里head是双向链表的返回值,也是它的头节点,相当于首地址。
(2)fork()函数:
fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
- 在父进程中,fork返回新创建子进程的进程ID;
- 在子进程中,fork返回0;
- 如果出现错误,fork返回一个负值;
知道fork()函数使用后,我们就可以开始分析这段代码了。首先,创建了一个子进程(克隆一个自己),因为返回值有两次,所以父进程和子进程各做各的事,因为父进程睡眠了1秒。所以子进程就优先完成了调用play函数的工作。接下来我们就来看看play函数。
二、play函数:
void play(struct song *currentsong)
{pid_t fd;char *c_addr;char *p;int len;char my_song[64]="/mp3/song/"; //指定歌曲存放路径,最后的“/”不能少while(currentsong){/*创建子进程,即孙子进程*/fd = fork();if(fd == -1){ perror("fork");exit(1);}else if(fd == 0){/*把歌曲名加上根路径*/strcat(my_song,currentsong->songname);p = my_song;len = strlen(p);/*去掉文件名最后的'\n'*/my_song[len-1]='\0';printf("THIS SONG IS %s\n",my_song);execl("/mp3/madplay","madplay",my_song,NULL);printf("\n\n\n");}else{/*内存映射*/c_addr = shmat(shmid,0,0);/*把孙子进程的id和当前播放歌曲的节点指针传入共享内存*/memcpy(c_addr,&fd,sizeof(pid_t));memcpy(c_addr + sizeof(pid_t)+1,¤tsong,4);/*使用wait阻塞孙子进程,直到孙子进程播放完才能被唤醒;当被唤醒时,表示播放MP3期间没有按键按下,则继续顺序播放下一首MP3*/if(fd == wait(NULL)){currentsong = currentsong->next;printf("THE NEXT SONG IS %s\n",currentsong->songname);}}}
}
因为执行该函数之前先执行startplay()函数, startplay函数已经创建了一个子进程,本函数由子进程执行过程中调用,所以本函数创建的子进程就 相当于孙进程了。
然后孙进程要做的事就是播放当前的歌曲。而子进程则映射了一段内存,把孙进程的id和当前的歌曲节点指针放入该内存,然后用wait()函数阻塞孙进程,直到孙进程播放结束后才能唤醒,当前歌曲指针指向下一首歌。
(1)内存映射 void *shmat(int shmid, const void *shmaddr, int shmflg);
shmat()是用来允许本进程访问一块共享内存的函数。
第一个参数:shmget返回的标识符,
第二个参数:如果 shmaddr 是NULL,系统将自动选择一个合适的地址! 如果shmaddr不是NULL 并且没有指定SHM_RND 则此段连接到addr所指定的地址上 如果shmaddr非0 并且指定了SHM_RND 则此段连接到shmaddr -(shmaddr mod SHMLAB)所表示的地址上.SHM_RND命令的意思是取整,SHMLAB的意思是低边界地址的倍数,它总是2的乘方。该算式是将地址向下取最近一个 SHMLAB的倍数。除非只计划在一种硬件上运行应用程序(这在当今是不大可能的),否则不用指定共享段所连接到的地址。所以一般应指定shmaddr为0,以便由内核选择地址。
第三个参数:如果在flag中指定了SHM_RDONLY位,则以只读方式连接此段,否则以读写的方式连接此段shmat返回值是该段所连接的实际地址 如果出错返回-1
(2) wait(等待子进程中断或结束)
当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号。因为子进程终止是个异步事件,这种信号也是内核向父进程发的异步通知。父进程可以忽略该信号,或者提供一个该信号发生时即被调用执行的函数(信号处理程序)。
父进程同步等待子进程退出时则调用wait函数,此时父进程可能会有如下三种情形:
① 阻塞(如果其所有子进程都还在运行)。
② 带回子进程的终止状态立即返回(如果已有一个子进程终止,正等待父进程取其终止状态)。
③ 出错立即返回(如果它没有任何子进程)。
(3)execl()函数:
execl("/mp3/madplay","madplay",my_song,NULL);
第一个参数为命令的路径,第二个参数为命名的名字,后面跟的是参数(可以为多个),NULL表示结尾。
三、暂停函数:
void my_pause(pid_t pid)
{printf("=======================PAUSE!PRESS K1 TO CONTINUE===================\n");kill(pid,SIGSTOP); //对孙子进程发送SKGSTOP信号play_flag = 0;
}
该函数调用了kill函数,发送信号将子进程暂停,并把播放标志设置为0 。my_stop(),conti_play()的用法与之类似。
(1) my_pause(gradchild); //调用该函数
(2)SIGKILL和SIGSTOP的区别
SIGKILL提供给管理员杀死进程的权利,SIGSTOP提供给管理员暂停进程的权利, 所以这两个信号不能被忽略和重定义。
(3)int kill(pid_t pid, int sig);
函数说明:kill()可以用来送参数sig 指定的信号给参数pid 指定的进程。参数pid 有几种情况:
- pid>0 将信号传给进程识别码为pid 的进程.
- pid=0 将信号传给和目前进程相同进程组的所有进程
- pid=-1 将信号广播传送给系统内所有的进程
- pid<0 将信号传给进程组识别码为pid 绝对值的所有进程参数 sig 代表的信号编号可参考附录D
返回值:执行成功则返回0, 如果有错误则返回-1.
错误代码:
1、EINVAL 参数sig 不合法
2、ESRCH 参数pid 所指定的进程或进程组不存在
3、EPERM 权限不够无法传送信号给指定进程
四、切换到下一曲 void next()函数:
void next(pid_t next_pid)
{struct song *nextsong;printf("===============================NEXT MP3=============================\n");/*从共享内存获得孙子进程播放歌曲的节点指针*/memcpy(&nextsong,p_addr + sizeof(pid_t)+1,4);/*指向下首歌曲的节点*/nextsong = nextsong->next;/*杀死当前歌曲播放的子进程,孙子进程*/kill(pid,SIGKILL);kill(next_pid,SIGKILL);wait(NULL);startplay(&pid,nextsong);
}
这里重点是将从共享内存获得孙子进程播放歌曲的节点指针,将指针指向下首歌曲的节点,然后停止当前进程,等待子进程退出,再进行下一轮播放。切换到上一首歌曲的函数和该函数类似,只需要将指针指向前驱即可。
五、memcpy()函数
void *memcpy(void*dest, const void *src, size_t n);
功能:从源src所指的内存地址的起始位置开始,拷贝n个字节的数据到目标dest所指的内存地址的起始位置中。
1)src和dest所指内存区域不能重叠,函数返回指向dest的指针。如果src和dest以任何形式出现了重叠,它的结果是未定义的。
2)与strcpy相比,memcpy遇到’\0’不结束,而且一定会复制完n个字节。只要保证src开始有n字节的有效数据,dest开始有n字节内存空间就行。
3)如果目标数组本身已有数据,执行memcpy之后,将覆盖原有数据(最多覆盖n个)。如果要追加数据,则每次执行memcpy()后,要将目标地址增加到要追加数据的地址。
4)source和destin都不一定是数组,任意的可读写的空间均可。
关于 shmget()和shmat()函数的用法可参考原博客:
本文参考博客:http://blog.sina.com.cn/s/blog_95268f5001016gnf.html
fl2440——按键控制madplay音乐播放器相关推荐
- Linux开发板怎么用madplay,Linux中madplay 音乐播放器移植步骤
madplay 音乐播放器移植步骤 madplay版本: madplay-0.15.2 交叉编译器版本: arm-linux-gcc 3.4.1 操作系统平台: Linux -- Red Hat 9. ...
- Android怎样实现控制第三方音乐播放器暂停、播放
1.需求 怎么控制第三方音乐播放器暂停 播放呢 2.解决思路 写一个服务,当第三方播放器打开时 ,开启这个服务,音乐暂停:关闭服务,音乐继续 3.开启和关闭服务 startService(new In ...
- Android实现控制第三方音乐播放器暂停/播放
实现控制第三方音乐播放器思路: 1.参考方法:可能实现第三方音乐播放器暂停/播放/下一曲/上一曲 思路:android模拟发送键值,就像蓝牙耳机等远程设备切换歌曲操作,但是行不通.原因是现在Andro ...
- Android 控制第三方音乐播放器
项目场景: 最近在做穿戴项目时,需要针对第三方播放进行控制,如:播放.暂停.上一曲.下一曲等 问题描述 在百度上发现现在大都是通过AudioManager控制 /*** 播放 暂停*/public v ...
- android 车载控制手机音乐播放器,【图】浅谈车载音响播放器之安卓篇
汽车音响无疑占据着开车乐趣个部分,无论你音乐发烧友与否,陶冶灵魂玩意我想有人去抗拒它,因为毕竟它个美东西,今天本人拿些玩音乐小心跟大家分享,欢迎交流探讨,相互学习! 抛开那 ...
- 控制iphone音乐播放器的相关函数
https://github.com/rono23/GlovePod 国外牛人的开源工程,其说明如下 You can control music without taking off your glo ...
- 计算机毕业论文乐谱播放器,原创】电子音乐播放器的设计和制作毕业论文(20201002130117...
<原创]电子音乐播放器的设计和制作毕业论文(20201002130117>由会员分享,可在线阅读,更多相关<原创]电子音乐播放器的设计和制作毕业论文(20201002130117(1 ...
- (附源码)springboot+基于微信小程序音乐播放器的设计与实现 毕业设计271156
Springboot音乐播放小程序的设计与实现 摘 要 本文设计了一种基于微信小程序的音乐播放器,系统为人们提供了方便快捷.即用即搜的音乐搜索播放服务,包括音乐资讯.音乐库推荐.交流论坛.注册登录.最 ...
- android 音乐播放器论文,Android音乐播放器论文-Android文档类资源
基于android系统的音乐播放器论文.里边有详细的介绍,没有代码.只是单独的一个论文. XXX科技大学本科生毕业设计(论文) 摘要 当今社会的生活节奏越来越快,随着硬件移动设备的越来越先进,人们 对 ...
最新文章
- go语言实现排序算法
- 解决复制虚拟机时候网络不从eth0开始问题
- nrf52832 DFU详细步骤 SDK13
- Bitmasking for introspection of Objective-C object pointers i
- boost::hana::all用法的测试程序
- hdu 3501 欧拉函数
- spring in action 读书笔记
- 【渝粤题库】广东开放大学社会学概论形成性考核
- spring boot 配置文件加密数据库用户名/密码
- 大数据_Flink_Java版_数据处理_流处理API_Flink中的UDF函数类---Flink工作笔记0036
- 三 数据结构 --数和二叉树
- tf.nn的conv2d卷积与max_pool池化
- 苹果PD20W快充方案缺货,芯片供应不足,怎么破局?
- Tushare Day3——了解stock_company并与stock_basic数据规模进行比较
- 机器学习-凸优化理论-课堂笔记
- visual studio 2017 安装离线MSDN
- pyqt5 图片随窗口变化等比例缩放
- 新手程序员去哪里?避雷小技巧交给你
- 音叉公振频率与双臂质量的计算关系
- 生成android使用的BKS证书
热门文章
- 计算机组装 仿真交互,《计算机硬件组装》仿真交互系统设计与实现.doc
- 【Android App】实战项目之实现你问我答的智能语音机器人(超详细 附源码和演示视频)
- 终于有人将TWI(串行通讯接口)给讲通了!
- 动作捕捉系统用于柔性机械臂的末端定位控制
- DVWA系列(二)----DVWA环境搭建
- web前端入门到实战:HTML5 video视频播放
- 数字化加速时刻,天津港解锁了“天工开物”新篇
- Deep Learning Paper读后简记
- 计算机网路络课设_学生宿舍网络规划与设计
- XMOS软件开发入门(4) - xc语言(2)之并发机制