本次项目是基于Linux环境的交叉编译arm-linux-gcc,在GEC6818arm开发板上运行,通过SOCKET编程,应用基于TCP/IP传输数据的HTTPS协议连接机器人API接口,根据用户输入的问题建立通信,返回数据,通过屏幕显示以及语音播报告知用户,实现聊天功能;通过语音识别或键盘输入选择功能:查询城市天气(API)、切换机器人动态表情(线程滚动)、看视频听音乐查看图片(Apache服务器在线查看)
[1 ] 显示机器人表情(jpg/bmp图像显示)动态
[ 2] 实现基本的聊天功能(在键盘输入问题,就会在开发板的LCD屏幕上显示对应回答信息)HTTP请求
[ 3] 可以根据用户的输入查询不同城市的天气并显示到LCD设备上 JSON数据处理
[ 4] 可以浏览服务器上的图片文件(Apache) 利用HTTP协议下载文件
[5 ] 可以播放网络服务器上的视频文件 mplayer和http协议
[ 6] 自行设计一些新的功能提供用户使用
开发环境:
Linux
开放工具:
arm-linux-gcc、Notepad++
其他工具:
mplayer开源多媒体库、jpeglib库、font库、Apache服务器、讯飞语音、ALSA库
编程实现:
通过开源代码编译器notepad++编写代码,利用arm-linux-gcc交叉编译,再通过LINUX平台的SSH服务器将编译生成的程序文件传输到开发板中,最后执行。
Mplayer开源多媒体库
MPlayer是一款开源的多媒体播放器,以GNU通用公共许可证发布。此款软件可在各主流作业系统使用,例如Linux和其他类Unix作业系统、微软的视窗系统及苹果电脑的Mac OS X系统。
jpeglib库
libjpeg是一个完全用C语言编写的库,包含了被广泛使用的JPEG解码、JPEG编码和其他的JPEG功能的实现。这个库由独立JPEG工作组维护。
font库
通过封装font字库,让开发者能够自定义画板和文字颜色及大小。具体运用代码如下:
struct LcdDevice *init_lcd(const char *device)//初始化LCd
{
//申请空间
struct LcdDevice* lcd = malloc(sizeof(struct LcdDevice));
if(lcd == NULL)
{
return NULL;
}
lcd->fd = open(device, O_RDWR);//1打开设备
if(lcd->fd < 0)
{
perror("open lcd fail");
free(lcd);
return NULL;
}
lcd->mp = mmap(NULL,800*480*4,PROT_READ|PROT_WRITE,MAP_SHARED,lcd->fd,0);//映射
return lcd;
}
int show_date(char*name)//字库显示字符串
{
struct LcdDevice* lcd = init_lcd("/dev/fb0"); //初始化Lcd
font *f = fontLoad("/usr/share/fonts/DroidSansFallback.ttf"); //打开字体
fontSetSize(f,40); //字体大小的设置
bitmap *bm = createBitmapWithInit(400,230,4,getColor(0,0,0,0)); 创建一个画板(点阵图),改变画板颜色
char buf[1024];
strcpy(buf,name);
fontPrint(f,bm,0,0,buf,getColor(0,201,174,255),400);//将字体写到点阵图上
show_font_to_lcd(lcd->mp,190,135,bm);//把字体框输出到LCD屏幕上
fontUnload(f);//关闭字体,关闭画板
destroyBitmap(bm);
}
Apache阿帕奇服务器
Apache音译为阿帕奇, 是全世界最受欢迎的web服务器,因其快速、可靠并且可通过简单的API扩充,能将Python\Perl等解释器部署在其上面等优势,受到广泛的关注与使用。
此次项目就运用阿帕奇服务器搭建一个WEB服务器,使开发板可以直接连接到WEB服务器下载或显示文件。
讯飞语音
此次项目使用的是科大讯飞识别库(包括了语音合成和识别),开发板通过ALSA库的搭建能够录制语音,通过UDP协议传输到Linux平台,平台再通过识别语音生成文字返还给开发板,从而达到识别的功能。语音播报同样也是通过UDP协议将需要播报的字符串传送至Linux,通过语音合成音频文件,保存到WEB服务器的音频目录下,开发板通过在线播放改音频达到语音播报的功能。
具体项目实例如下:
ALSA库
ALSA是Advanced Linux Sound Architecture,高级Linux声音架构的简称,它在Linux操作系统上提供了音频和MIDI(Musical Instrument Digital Interface,音乐设备数字化接口)的支持。在2.6系列内核中,ALSA已经成为默认的声音子系统,用来替换2.4系列内核中的OSS(Open Sound System,开放声音系统)。
此次项目运用的主要是录音功能。
基本
实现思路:
1.当用户选择需要显示的GIF表情时,程序通过创建链表、遍历对应表情目录所有图片存进节点,再创建线程循环滚动图片(记得加延迟时间);
2.当需要切换表情或者选择其他功能的时候,程序会清空该表情的链表节点、结束线程,然后再重新存新的表情包进链表节点,重复步骤1…
具体效果和代码如下:
3.项目中添加了11种动态表情:wink、笑、哭、怒、喜欢、困、爱心、发呆、疑问、音乐
实体效果:
具体部分代码:
int main()
{
head = malloc(sizeof(struct double_list)); //gif表情头节点
head->next = head;
head->prev = head;
head->pic_name[200]=0;
char http[1024];
char word[1024];
char buf[1024] = {0};
printf("\n");
show_pohoto("pingmu.bmp");
while(1)
{
printf("选择小白表情:wink、笑、哭、怒、喜欢、困、爱心、发呆、疑问 \n");
scanf("%s",num);
if(strcmp(num,"wink") == 0)//显示wink表情
{
if(i==0)
{
show_face(num,head);//显示表情
i=1;
}
if(i==1)
{
pthread_cancel(tid);//取消线程
delete_list(head);//删除所有节点
show_face(num,head);//显示表情
}
}
else if(strcmp(num,"笑") == 0)//显示笑表情
{
if(i==0)
{
show_face(num,head);//显示表情
i=1;
}
if(i==1)
{
pthread_cancel(tid);//取消线程
delete_list(head);//删除所有节点
show_face(num,head);//显示表情
}
}
}
int show_face(char *face,struct double_list *head)//遍历表情目录存进链表,再用线程滚动链表图片
{
char buff[64]="/robot/";
strcat(buff,face);
DIR *dir_p=opendir(buff);//打开图片目录
struct dirent *msg;
while(msg=readdir(dir_p))//遍历目录内的文件
{
if( strstr(msg->d_name,".JPG")!=NULL)
{
inser_list(head,msg->d_name);//将目录中的视频名字依次保存在节点中
}
}
show_list(head);
usleep(400000);
pthread_create(&tid,NULL,roll_wink,NULL);
}
void *roll_wink(void *arg)//线程滚动链表图片
{
struct double_list *tmp=head;
char buff[64];
while(1)
{
tmp=tmp->next;
if(tmp==head)
{
tmp=tmp->next;
}
bzero(buff,sizeof(buff));
sprintf(buff,"/robot/%s/%s",num,tmp->pic_name);
show_jpg(buff,190,135);
usleep(300000);
}
}
实现思路
通过SOCKET编程,使用HTTP协议连接机器人API接口,输入用户问题,客户端处理完返还数据,实现双向传输;将返回的数据经过字符串的一系列处理,通过字库显示至屏幕
具体效果和代码如下:
实体效果:
部分代码:
//使用IPV4的协议,基于TCP
int new_socket = socket(AF_INET, SOCK_STREAM, 0);
//设置需要链接到的服务器IP地址信息 例如:百度服务器 14.215.177.38 端口8080
struct sockaddr_in sockaddr_i;
sockaddr_i.sin_family = AF_INET;
sockaddr_i.sin_port = htons(80);
sockaddr_i.sin_addr.s_addr = inet_addr("47.107.120.234");
//链接服务器
int ret=connect(new_socket, (struct sockaddr *)&sockaddr_i, sizeof(sockaddr_i));
sprintf(http, "GET /api.php?key=free&appid=0&msg=%s HTTP/1.1\r\nHost:api.qingyunke.com\r\n\r\n", num);
write(new_socket,http,strlen(http));
read(new_socket, buf, 1024);
//-----------------------------字符串数据处理---------------------------------
char *handle=strstr(buf,"{");
char *handle2=strstr(handle,"content");
char *handle3=strstr(handle2,":");
char xiaobai[1024]="小白";
strcat(xiaobai,handle3);
show_date(xiaobai);//显示小白的回答
make_wav(handle3);
i=xiaobai_face(xiaobai,head);//判断小白回答是否产生表情
bzero(buf,1024);
实现思路:
通过SOCKET编程,使用HTTP协议连接天气API接口,输入查询天气的城市,客户端处理完返还数据,实现双向传输;将返回的数据经过JOSN数据处理提取所需要的信息,再通过字库显示至屏幕。
具体效果和代码如下:
实体效果:
部分代码:
int answer_weather()//回答城市的天气
{
while(1)
{
printf("请输入你要查询的城市:广州、深圳、惠州、肇庆、东莞、梅州、湛江、珠海 或 退出\n");
scanf("%s",num);
if(strcmp(num,"1") == 0)//语音识别
{
speech_recognition();
}
printf("date=%s\n",num);
if(strcmp(num,"广州") == 0)//显示广州天气
{
char city_id[64]="101280101";
show_weather(city_id);
}
else if(strcmp(num,"深圳") == 0)//显示深圳天气
{
char city_id[64]="101280601";
show_weather(city_id);
}
//省略其他城市天气
else if(strcmp(num,"退出") == 0)//退出
{
printf("\n");
show_pohoto("pingmu.bmp");
show_jpg("待机.JPG",190,135);
break;
}
else
{
printf("没有找到 %s 的天气\n",num);
}
}
}
int show_weather(char *city_id)//显示城市天气情况
{
//1.使用IPV4的协议,基于TCP
int new_socket = socket(AF_INET, SOCK_STREAM, 0);
//设置需要链接到的服务器IP地址信息 链接到自己的HTTP 服务器
struct sockaddr_in sockaddr_i;
sockaddr_i.sin_family = AF_INET;
sockaddr_i.sin_port = htons(80);
sockaddr_i.sin_addr.s_addr = inet_addr("58.222.18.2");
//链接服务器
int ret=connect(new_socket, (struct sockaddr *)&sockaddr_i, sizeof(sockaddr_i));
char http[1024];
sprintf(http,"GET /api/weather/city/%s HTTP/1.1\r\nHost:t.weather.itboy.net\r\n\r\n",city_id);
write(new_socket,http,strlen(http));
sleep(2);
char buf[4096*2] = {0};
read(new_socket, buf, 4096*2);
char *handle=strstr(buf,"{");
//把数据转换成 JSON 对象
cJSON *obj=cJSON_Parse(handle);
char date[64];//日期
char city[64];//城市
char week[64];//星期
char weather[64];//天气
char low[64];//低温
char high[64];//高温
char notice[64];//建议
cJSON *new_obj=cJSON_GetObjectItem(obj,"cityInfo");
cJSON *value=cJSON_GetObjectItem(new_obj,"city");
strcpy(city,cJSON_Print(value));
cJSON *obj_1=cJSON_GetObjectItem(obj,"data");
cJSON *obj_arry=cJSON_GetObjectItem(obj_1,"forecast");
cJSON *obj_arry_0=cJSON_GetArrayItem(obj_arry,0);
//日期
value=cJSON_GetObjectItem(obj_arry_0,"ymd");
strcpy(date,cJSON_Print(value));
//星期
value=cJSON_GetObjectItem(obj_arry_0,"week");
strcpy(week,cJSON_Print(value));
//天气
value=cJSON_GetObjectItem(obj_arry_0,"type");
strcpy(weather,cJSON_Print(value));
//低温
value=cJSON_GetObjectItem(obj_arry_0,"low");
strcpy(low,cJSON_Print(value));
//高温
value=cJSON_GetObjectItem(obj_arry_0,"high");
strcpy(high,cJSON_Print(value));
//建议
value=cJSON_GetObjectItem(obj_arry_0,"notice");
strcpy(notice,cJSON_Print(value));
char word_weather[256];
sprintf(word_weather,"日期:%s\n 城市:%s\n 星期:%s\n 天气:%s\n %s %s\n %s\n",date,city,week,weather,low,high,notice);
show_date(word_weather);//字库显示
make_wav(word_weather);//语音播报
}
实现思路:
通过system指令直接连接Apache服务器上音乐目录下的相应歌曲(实现在线播放,再通过管道实现歌曲调节功能);通过线程滚动显示“音乐”GIF表情以及字库显示歌曲名字。
具体效果和代码如下:
实体效果:
int interweb_music(char *num)//音乐功能菜单
{
char buff[128];
char buf[64];
int music_fd = open("/robot/pipe2",O_RDWR);//打开通信的管道文件
sprintf(buff,"mplayer -slave -quiet -input file=/robot/pipe2 http://192.168.8.72/music/%s.mp3 &",num);//将音乐指令拼接到BUFF中
system(buff);
usleep(400000);
system("echo volume 10 1 > /robot/pipe2"); //降低初始音量
music_name(num);//显示歌曲名字
strcpy(num,"音乐");
show_face(num,head);//显示音乐表情
while(1)
{
printf("请选择播放功能:1、暂停or播放 ; 2、放大音量 ; 3、降低音量 ; 4、退出播放\n");
scanf("%s",buf);
if(strcmp(buf,"1") == 0)//暂停or播放
{
write(music_fd,"pause\n",strlen("pause\n"));
}
else if(strcmp(buf,"2") == 0)//放大音量
{
write(music_fd,"volume +20\n",strlen("volume +20\n"));
}
else if(strcmp(buf,"3") == 0)//降低音量
{
write(music_fd,"volume -20\n",strlen("volume -20\n"));
}
else if(strcmp(buf,"4") == 0)//停止播放
{
write(music_fd,"quit\n",strlen("quit\n"));//停止播放
pthread_cancel(tid);
delete_list(head);
return 1;
}
}
}
实现思路:
http协议连接Apache服务器下的相应视频,下载至开发板,再通过Mplayer播放视频,实现调节功能。
具体效果和代码如下:
实体效果:
部分代码:
int interweb_video(char *movie)//下载并播放视频
{
int new_socket = socket(AF_INET, SOCK_STREAM, 0);
//设置需要链接到的服务器IP地址信息 链接到自己的HTTP 服务器
struct sockaddr_in sockaddr_i;
sockaddr_i.sin_family = AF_INET;
sockaddr_i.sin_port = htons(80);
sockaddr_i.sin_addr.s_addr = inet_addr("192.168.8.72");
//链接服务器
int ret=connect(new_socket, (struct sockaddr *)&sockaddr_i, sizeof(sockaddr_i));
char http[1024];
sprintf(http,"GET /video/%s.avi HTTP/1.1\r\nHost:192.168.8.72\r\n\r\n",movie);
write(new_socket,http,strlen(http));
//创建一个本地文件
int fd=open("my.avi",O_RDWR|O_CREAT,0777);
int flag=0;
//接收服务器的回发数据
while(1)
{
//读取服务器的数据
char buf[4096]={0};
int ret=read(new_socket,buf,4096);
if(flag==0)
{
char *tmp=strstr(buf,"\r\n\r\n");
int len=tmp-buf+4;
write(fd,buf+len,ret-len);
flag=1;
}
else
write(fd,buf,ret);//写入到本地文件中
}
close(fd);
pthread_cancel(tid3);
usleep(100000);
pid_t pid;
pid =fork();
if(pid == 0) //子进程
{
char buff[100];//接收拼接好的播放视频指令
printf("\n");
show_pohoto("shiping.bmp");
drw_sound(5);
int fd = open("/robot/pipe1",O_RDWR);//打开通信的管道文件
dup2(fd,1);//重定向标准输出设备描述符
//=======刷新进度条=======
pthread_t tid;
pthread_create(&tid,NULL,func,NULL);
pthread_detach(tid);
//=======进行触摸屏操作========
pthread_t tid1;
pthread_create(&tid1,NULL,finger,NULL);
pthread_detach(tid1);
//=============退出操作===========
pthread_t tid2;
pthread_create(&tid2,NULL,video_exit,NULL);
system("mplayer -slave -quiet -input file=/robot/pipe -geometry 0:0 -zoom -x 750 -y 410 /robot/my.avi");
exit(0);
}
wait(NULL);
}
实现思路:
http协议连接Apache服务器下的相应照片下载至开发板,再通过映射显示BMP图片。
具体效果和代码如下:
实体效果:
部分代码:
int down_file(char *file_name,char *http)//下载显示图片
{
//使用IPV4的协议,基于TCP
int new_socket = socket(AF_INET, SOCK_STREAM, 0);
//设置需要链接到的服务器IP地址信息 例如:百度服务器 14.215.177.38 端口8080
struct sockaddr_in sockaddr_i;
sockaddr_i.sin_family = AF_INET;
sockaddr_i.sin_port = htons(80);
sockaddr_i.sin_addr.s_addr = inet_addr("192.168.8.72");
//链接服务器
int ret=connect(new_socket, (struct sockaddr *)&sockaddr_i, sizeof(struct sockaddr_in));
char host[64];
char url[512];
//字符串处理
char tmp_http[1024]={0};
strcpy(tmp_http,http);
strcpy(url,strstr(strstr(tmp_http,"//")+2,"/"));
//去掉HTTP头
strcpy(host,strtok(strstr(tmp_http,"//"),"/"));
//发送HTTP 请求
char http_reques[1024]={0};
sprintf(http_reques,"GET %s HTTP/1.1\r\nHost:%s\r\n\r\n",url,host);
write(new_socket,http_reques,strlen(http_reques));
//接收服务器返回的数据
char head[1024] = {0};
ret=read(new_socket,head,1024);
//printf("head=%s\n",head);
int file_size=0;
//获取文件大小
sscanf(strstr(head,"Content-Length: "),"Content-Length: %d\r\n",&file_size);
//printf("file_size=%d\n",file_size);
//获取头数据的大小
unsigned int head_len = (unsigned int)((strstr(head,"\r\n\r\n")+4)-head);
//printf("%d\n",head_len);
//写入头数据后的数据
int file_fd=open(file_name,O_RDWR|O_CREAT|O_TRUNC,0777);
file_size-=ret-head_len;
write(file_fd,(strstr(head,"\r\n\r\n")+4),(ret-head_len));
char buf[4096]={0};
while(1)
{
bzero(buf,4096);
ret=read(new_socket,buf,4096);
write(file_fd,buf,ret);
file_size-=ret; //计算读取的大小
if(file_size == 0)
{
printf("下载文件 %s 完毕\n",file_name);
close(new_socket); //关闭通信
break;
}
}
show_jpg("my.jpg",0,0);//显示图片
}
此次项目主要运用了网络编程中TCP/IP协议、UDP协议、HTTP协议、Apache服务器的使用、机器人和天气API接口以及科大讯飞语音识别、链表的使用、mplayer指令的选择、触摸屏的坐标处理、进/线程的创建与终止,对文件IO路径的认识。
主要难点在于机器人GIF表情的生成及切换(通过创建链表,节点存取,删除节点,线程滚动节点),天气JSON数据处理(需要对JSON数据处理有一定的了解,处理需要提取的字符串),语音识别和合成(利用UDP协议实现arm与语音识别、arm与语音合成)…