1、需求分析
1)LED灯的控制
2)视频功能
3)音频功能
mp3音乐
4)监控是否有人闯入(红外)
拿按键模拟 实现报警
5)g-sensor 实现计步器
6)在线升级
产出物: 需求分析文档
2、概要设计
硬件选型:
cpu: s5p6818
开放板:x6818
操作系统: linux
设计软件模块 ,以及模块之间的通信方式
3、详细设计
每个模块中有哪些函数
函数的参数、返回值 、出错处理
函数的执行流程图 (viso)
4、编码
5、整合测试
6、项目的发布
1、开发环境的搭建
cd /home/tarena
mkdir project
cd project/
kernel
rootfs
安装交叉编译工具
烧写到开发板 uImage 并运行
nfs方式挂载根文件系统
/home/tarena/project/rootfs *(rw,sync,no_root_squash)
sudo /etc/init.d/nfs-kernel-server restart
setenv bootargs root=/dev/nfs nfsroot=192.168.1.8:/home/tarena/project/rootfs ip=192.168.1.6:192.168.1.8:192.168.1.1:255.255.255.0 console=ttySAC0,115200 maxcpus=1 lcd=wy070ml tp=gslx680-linux cam=OV5645
saveenv
2、QT程序的移植
2.1PC机上的QT开发环境
cd workdir/qt/qt_dev
./qt-opensource-linux-x64-android-5.4.1.run
安装路径 /opt/Qt5.4.1
sudo vi /home/tarena/.bashrc
PATH=$PATH:/opt/arm-cortex_a9-eabi-4.7-eglibc-2.18/bin/:/opt/Qt5.4.1/5.4/gcc_64/bin:/opt/Qt5.4.1/Tools/QtCreator/bin
source /home/tarena/.bashrc
使用qtcreator创建一个test工程
在其中增加了确定按钮
readelf -d test
libQt5Widgets.so.5
libQt5Core.so.5
2.2 编译ARM版本的QT库
http://doc.qt.io/qt-t/embedded-linux.html
1)拿到QT源码 官方网站
2)通过交叉编译工具编译ARM版本的QT库
cd project/
cp /home/tarena/workdir/qt/qt_src/qtbase-opensource-src-5.4.1.tar.xz ./
tar xf qtbase-opensource-src-5.4.1.tar.xz
cd qtbase-opensource-src-5.4.1
vi mkspecs/linux-arm-gnueabi-g++/qmake.conf
14 QMAKE_CC = arm-cortex_a9-linux-gnueabi-gcc
15 QMAKE_CXX = arm-cortex_a9-linux-gnueabi-g++
16 QMAKE_LINK = arm-cortex_a9-linux-gnueabi-g++
17 QMAKE_LINK_SHLIB = arm-cortex_a9-linux-gnueabi-g++
18
19 # modifications to linux.conf
20 QMAKE_AR = arm-cortex_a9-linux-gnueabi-ar cqs
21 QMAKE_OBJCOPY = arm-cortex_a9-linux-gnueabi-objcopy
22 QMAKE_NM = arm-cortex_a9-linux-gnueabi-nm -P
23 QMAKE_STRIP = arm-cortex_a9-linux-gnueabi-strip
./configure --help 查看QT源码编译前的配置选项
./configure -prefix /home/tarena/project/qtlib -release -opensource -qt-libpng -qt-libjpeg -plugin-sql-sqlite -widgets -qt-sql-sqlite -make libs -no-cups -no-nis -no-iconv -no-dbus -no-openssl -no-iconv -no-accessibility -no-sse2 -silent -xplatform linux-arm-gnueabi-g++ -nomake tools -nomake examples -nomake tests -qt-freetype -no-glib -strip -linuxfb -plugindir /home/tarena/project/qtlib/plugins
执行./configure会根据配置生成合适的Makefile
-prefix: 编译后的安装路径
make install
-plugindir: 插件库的安装路径
-xplatform linux-arm-gnueabi-g++
//ls mkspecs/linux-arm-gnueabi-g++
make -j4
make install
2.3 将QT程序移植到开发板运行
2.3.1 编译ARM版本的可执行程序
0)cd ......test/
1)/home/tarena/project/qtlib/bin/qmake //生成合适的Makefile
2) make clean
3) make
2.3.2 将可执行程序及库文件部署到开发板
注意:建议接下来的所有内容都拷贝rootfs/home
1)mkdir /home/tarena/project/rootfs/home/bin -p
2)cp test /home/tarena/project/rootfs/home/bin/
3)mkdir /home/tarena/project/rootfs/home/qt
4)cp /home/tarena/project/qtlib/lib/ /home/tarena/project/rootfs/home/qt/ -a
5)mkdir /home/tarena/project/rootfs/home/lib
6)cp /opt/arm-cortex_a9-eabi-4.7-eglibc-2.18/arm-cortex_a9-linux-gnueabi/sysroot/lib/*.so* /home/tarena/project/rootfs/home/lib/ -a
7)cp /opt/arm-cortex_a9-eabi-4.7-eglibc-2.18/arm-cortex_a9-linux-gnueabi/sysroot/usr/lib/*.so* /home/tarena/project/rootfs/home/lib/ -a
2.3.3 部署QT程序运行时使用的插件
1)cp /home/tarena/project/qtlib/plugins/ rootfs/home/qt/ -a
2.3.4 关于QT的配置文件
1)mkdir rootfs/home/etc -p
2)cp /mnt/hgfs/project/env/qt.zip/profile rootfs/home/etc/
3)关于profile
#插件库路径
1)mkdir rootfs/home/etc -p
2)vi rootfs/home/etc/profile
export PATH=$PATH:/home/bin
export LD_LIBRARY_PATH=/home/qt/lib/:/home/lib/:$LD_LIBRARY_PATH
#插件库路径
export QT_QPA_PLATFORM_PLUGIN_PATH=/home/qt/plugins/
#指定LCD设备 经常修改的就是分辨率
export QT_QPA_PLATFORM=linuxfb:fb=/dev/fb0:size=1024x600:tty=/dev/fb0
#指定字体库的存放位置
export QT_QPA_FONTDIR=/home/qt/lib/fonts
#指定的触摸屏对应的设备文件
#确定触摸屏设备文件的方式
#hexdump /dev/input/eventX
#用手敲击屏幕看是否有数据输出
export QT_QPA_GENERIC_PLUGINS=evdevkeyboard,evdevmouse,evdevtouch:/dev/input/event1
#如果接了USB键盘 使用判断触摸屏设备文件的方式修改event7
#export QWS_USB_KEYBOARD=/dev/input/event7
4) 使配置生效
source /home/etc/profile
2.3.5 运行程序
/home/bin/test
关于开发板GUI界面程序中中文显示乱码的问题:
env/font.zip
1)rm /home/tarena/project/rootfs/home/qt/lib/fonts/* -rf
1)cp /mnt/hgfs/project/env/font/DroidSansFallback.ttf /home/tarena/project/rootfs/home/qt/lib/fonts/
练习: 将QT课程中的小游戏移植到开发板运行
问题:移植qt程序到开发板运行需要哪几步?
参考代码:ehome_final.tar.gz
3、LED的控制
3.1 LED驱动程序
实质就是一个linux字符设备驱动
cd project
mkdir drivers
cd drivers
mkdir leds
cd leds
vi led_drv.c
vi Makefile
make
mkdir ../../rootfs/home/drivers
cp leds_drv.ko ../../rootfs/home/drivers/
vi test.c
编译test.c,测试驱动程序是否好用
3.2 编写LED的控制应用程序
有两种方式
3.2.1 简单的方式
当点击亮灯按钮时,完成该信号的槽函数
在槽函数中
open("/dev/leds", ...)
ioctl(fd, CMD_LED_ON, &i);
修改图片
再次点击时灭灯
open("/dev/leds", ...)
ioctl(fd, CMD_LED_OFF, &i);
修改图片
GUI界面程序一定部署到开发板才能有效果
3.2.2复杂方式
希望GUI界面程序不管是运行在PC
或者运行在开发板,都能控制开发板的LED状态
client (GUI 开发板/PC) Server (UDP 运行在开发板)
点击按钮发送命令LED_ON 接收命令,根据命令open设备 ioctl亮灯
1)界面客户端程序
mkdir gui_client
cd gui_client
qtcreator
建立工程 完成界面编程
先将mainwindow窗口的大小调整文1024*600
添加按钮
槽函数中给UDP服务器发送命令
2)服务器程序
cd project
mkdir server
cd server
vi server.c //接收命令 分发命令
//udp server
recvfrom(cmd)
if(cmd == LED_ON)
open("/dev/leds",...)
ioctl(fd, LED_ON, 0)
vi leds.c: 根据命令不同调用操作灯的函数
vi leds_hw.c:
open
ioctl
arm-cor.....gcc leds.c leds_hw.c server.c -o server
cp server rootfs/home/bin/
启动server (开发板)
启动client 点击按钮实验是否亮灯
前三项在板子上运行
insmod /home/drivers/leds_drv.ko
source /home/etc/profile
/home/bin/server &
可以运行在开发板上页可以运行在PC
/home/bin/client
注意编程过程中的调试技巧
#define DEBUG
#ifdef DEBUG
/*##表示如果可变参数被忽略或为空,将使预处理器( preprocessor )去除掉它前面的那个逗号。*/
#define pr_debug(fmt, ...) printf(fmt, ##__VA_ARGS__)
#else
#define pr_debug(fmt, ...)
#endif
BUG的调试,顺着数据流分析该调用的函数是否调用到
gui界面程序点击按钮 发送命令函数是否被调用
qWarning
命令被正常接收?
在server中打印收到的命令
该命令被正常处理了吗?
leds_operations
leds
ioctl
----------
led_ioctl
ehome_v1.tar.gz
4、视频服务器
4.1摄像头的驱动
uvc子系统: usb video class
内核中自带了满足uvc格式的摄像头驱动
如果你手中的摄像头满足uvc规范,该摄像头就是免驱
只需要对内核进行配置,将uvc模块对应的代码
编译到uImage
如何判断摄像头满足uvc格式规范?
lsusb
再将摄像头插入开发板
lsusb
Bus 001 Device 003: ID 046d:0825
网络搜索 uvc官网 有一个的页面:
列出了uvc框架支持的usb摄像头ID
配置内核,将uvc子系统编译进内核
Device Drivers --->
<*> Multimedia support --->
[*] Video capture adapters --->
[*] V4L USB devices --->
<*> USB Video Class (UVC)
make uImage
让开发板加载包含uvc模块的新的内核
ls /dev/video*
再次插入摄像头
发现多了一个video9 ,就是插入摄像头的设备文件
4.2 应用程序
4.2.1操作摄像头,抓取图像数据
v4l2: video for linux ver2
它属于摄像头软件的中间层
向下统一摄像头驱动的格式
向上为应用软件访问控制摄像头提供统一的接口
简化应用层软件控制摄像头的编程工作
v4l2用户态编程怎么玩?
v4l2提供的有小程序:v4l_demo.zip/capture.c
open设备
ioctl设置工作参数
ioctl(fd, VIDIOC_STREAMON, &type);//开始摄像头开始工作
// 获取图像数据
ioctl(fd, VIDIOC_DQBUF, &buf);
ioctl(fd, VIDIOC_QBUF, &buf);
mjpeg-streamer包含了按照v4l2框架去操作摄像头的代码
而且其中也包含了按照http协议向客户端发送图像数据的代码
重点:移植部署运行mjpeg-streamer
4.2.2mjpeg-streamer的移植:mjpg-streamer.tar.bz2
cd project
mkdir video
cd video
cp /mnt/hgfs/project/env/mjpg-streamer.tar.bz2 ./
tar xf mjpg-streamer.tar.bz2
cd mjpg-streamer/
vi README
make clean all
./mjpg-streamer ....
start.sh
vi Makefile
CC = gcc
find ./ -name "Makefile" -exec sed -i "s/CC = gcc/CC = arm-cortex_a9-linux-gnueabi-gcc/g" {} ;
sed: 文件文件
awk: 行处理
结合正则表达式 功能非常强大
make
4.2.3 部署到开发板
cp mjpg_streamer ../../rootfs/home/bin/
cp *.so ../../rootfs/home/lib/ -a
cp www/ ../../rootfs/home/ -r
4.2.4 运行
/home/bin/mjpg_streamer --help
/home/bin/mjpg_streamer -i "input_uvc.so --help"
/home/bin/mjpg_streamer -i "/home/lib/input_uvc.so -d /dev/video9 -y -r 320x240 -f 30" -o "/home/lib/output_http.so -w /home/www"
-i: 指定输入插件
-d: 指定访问的摄像头设备文件
-y: 采集图像的格式为YUYV
-r: 采集图像的大小
-f: 帧频率
-o: 指定输出插件
-w: 网页资源文件所在目录
打开浏览器,输入“http://192.168.1.6:8080/”
如果手里没有摄像头,可以使用如下方案
/home/bin/mjpg_streamer -i "/home/lib/input_testpicture.so -r 320x240 -d 500" -o "/home/lib/output_http.so -w /home/www"
input_testpicture.so不是采集摄像头数据
自身有两张图片
将这两张图片交替的发送给客户端
打开浏览器,输入“http://192.168.1.6:8080/”
4.3 mjpg-streamer 源码分析
4.3.1 mjpg_streamer.c 高内聚 低耦合
ctags -R *
int main(int argc, char *argv[])
{
/*共享库的运行阶段加载有两种方式
gcc xxx -o a.out -lpthread
./a.out 操作系统加载共享库
程序中自主主动加载共享库(插件库)
*/
"input_uvc.so"
global.in[i].handle = dlopen(global.in[i].plugin, RTLD_LAZY)
/*找到input_uvc.so中的input_init函数对应代码在内存中的位置*/
global.in[i].init = dlsym(global.in[i].handle, "input_init");
global.in[i].run = dlsym(global.in[i].handle, "input_run");
/*执行input_uvc.so中的input_init函数*/
global.in[i].init(&global.in[i].param, i)
/*"output_http.so"*/
global.out[i].handle = dlopen(global.out[i].plugin, RTLD_LAZY);
global.out[i].init = dlsym(global.out[i].handle, "output_init");
global.out[i].run = dlsym(global.out[i].handle, "output_run");
global.out[i].init(&global.out[i].param, i)
global.in[i].run(i)
global.out[i].run(global.out[i].param.id);
pause();
return 0;
}
4.3.2 输入插件plugins/input_uvc/
按照v4l2编程步骤去操作uvc格式的摄像头
官方例程:capture.c
Video for linux 2 example (v4l2 demo) - MetalSeed - 博客频道 - CSDN.NET.png
vi plugins/input_uvc/input_uvc.c
/*打开摄像头 设置工作参数*/
int input_init(input_parameter *param, int id)
{
init_videoIn(cams[id].videoIn, dev, width, he ight, fps, format, 1, cams[id].pglobal, id)
{
init_v4l2(vd)
{
/*打开"/dev/video9"设备文件*/
vd->fd = OPEN_VIDEO(vd->videodevice, O_RDWR)
/*查询当前硬件的工作能力*/
xioctl(vd->fd, VIDIOC_QUERYCAP, &vd->cap)
/*图像格式设置*/
ret = xioctl(vd->fd, VIDIOC_S_FMT, &vd->fmt);
...
}
}
}
int input_run(int id)
{
pthread_create(&(cams[id].threadID), NULL, cam_thread, &(cams[id]));
}
void *cam_thread(void *arg)
{
while(!pglobal->stop)
{
uvcGrab(pcontext->videoIn)
{
video_enable(vd)
{
/*VIDIOC_STREAMON:让摄像头开始工作*/
ret = xioctl(vd->fd, VIDIOC_STREAMON, &type);
}
/*获取一帧图像*/
xioctl(vd->fd, VIDIOC_DQBUF, &vd->buf)
}
/* copy JPG picture to global buffer */
memcpy_picture(pglobal->in[pcontext->id].buf, pcontext->videoIn->tmpbuffer, pcontext->videoIn->buf.bytesused);
}
}
BUG修改:input_uvc/v4l2uvc.c
428 do{
429 memset(&vd->buf, 0, sizeof(struct v4l2_buffer));
430 vd->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
431 vd->buf.memory = V4L2_MEMORY_MMAP;
432
433 ret = xioctl(vd->fd, VIDIOC_DQBUF, &vd->buf);
434 if(ret < 0) {
435 perror("Unable to dequeue buffer");
436 // goto err;
437 }
438 }while(ret < 0);
4.3.3 输出插件 plugins/output_http/
将图像数据封装成http数据包
通过TCP方式下客户端发送
vi plugins/output_http/output_http.c
int output_init(output_parameter *param, int id)
{
port = htons(8080);
...
}
int output_run(int id)
{
pthread_create(&(servers[id].threadID), NULL, server_thread, &(servers[id]));
}
void *server_thread(void *arg)
{
socket(aip2->ai_family, aip2->ai_socktype, 0)
bind(pcontext->sd[i], aip2->ai_addr, aip2->ai_addrlen)
listen(pcontext->sd[i], 10)
while(!pglobal->stop)
{
accept(pcontext->sd[i], (struct sockaddr *)&client_addr, &addr_len)
pthread_create(&client, NULL, &client_thread, pcfd)
}
}
void *client_thread(void *arg)
{
_readline(lcfd.fd, &iobuf, buffer, sizeof(buffer) - 1, 5)
{
_read(fd, iobuf, &c, 1, timeout)
{
read(fd, &iobuf->buffer, IO_BUFFER)
}
}
else if(strstr(buffer, "GET /?action=stream") != NULL)
{
req.type = A_STREAM;//确定客户端请求类型
}
switch(req.type) {
case A_STREAM:
send_stream(lcfd.fd, input_number)
{
while(!pglobal->stop)
{
/**/
write(http头信息)
/*将pglobal->buf数据拷贝到 frame缓冲区*/
memcpy(frame, pglobal->in[input_number].buf, frame_size);
write(fd, frame, frame_size)
write(http尾信息)
}
}
break;
}
练习:
1)在开发板上假设好视频服务器
2)编程实现一个tcp client
向服务器发送 "GET /?action=stream HTTP/1.1
"
recv(sd, buf, 1024)
printf("%s
",buf) ;
结束
5、视频客户端
5.1 HTTP协议:http.zip
超文本传输协议
建立TCP传输的基础上
客户端要给服务器端发送请求request
服务器端根据客户端的请求回送响应response
5.2 对mjpg-streamer的分析得到以下内容
如果客户端发送的请求中包含"GET /?action=stream"
服务器就会将视频数据封装HTTP格式数据帧
不断发送给客户端
按照http 协议 request的数据格式
就是给服务器发送"GET /?action=stream HTTP/1.1
"
5.3 编写一个tcp客户端程序
code_for_mjpgstreamer.rar
1)保证客户端和服务器能够联通
serverip : 开发板IP
端口号: 8080
给服务器发送请求 "GET /?action=stream HTTP/1.1
"
读取一次服务器返回的数据,并将数据打印
2)在1)的基础上,不断地读取数据
把读到的数据保存/tmp/test.jpg文件
保存1M数据后程序退出
3)能不能把http头信息过滤掉 把http尾信息过滤掉
只将图像信息内容保存到/tmp/test.jpg文件去
hexdump -C /tmp/test.jpeg |less
服务器返回的图像是jpeg格式的
jpeg图像帧在存储时是有固定的格式
JPEG是一种有损的图像压缩算法
JPEG文件由两部分组成: 标记码 压缩数据
FF D8 .... ... .... FF D9
参考code_for_mjpgstreamer.rar/03代码
4) 编写GUI客户端显示图像数据
camer:该类负责和HTTP server进行通信
连接服务器
connectToHost
给服务器发送请求"GET /?action=stream HTTP/1.1
"
requestImage
接收server返回的数据
并从中过滤出"ff d8 .... ff d9"
void CamClient::readImage()
{
接收数据
过滤出图像
发送信号 newImageReady(image)给mainwindow
}
void MainWindow::showNewImage(QImage img)
{
/*显示图片*/
ui->imageLabel->setPixmap(QPixmap::fromImage(img));
}
通信过程中使用了QTcpSocket
使用QBtyeArray 缓存图像数据
显示图像使用了QImage
注意1:SIGNAL(readyRead())
当收到readyRead()信号时,去调用对应的槽函数接收数据,过滤数据
camer::camer(QObject *parent) :
QObject(parent)
{
connect(&tcpSocket,SIGNAL(readyRead()),this,SLOT(readImage()));
}
注意2: 自定义的信号
CamClient 当过滤到一个完整的图像帧发出的
MainWindow负责处理该信号
视频数据的压缩 H264
参考代码: 04 视频客户端
ehome_v2.tar.gz : LED + 视频客户端
6、红外报警功能
常见的人体红外感应模块HC-SR501
用按键来模拟红外
当按键按下,有人闯入,需要报警
报警:
beep响 (选择)
发一个短信 (gsm)
6.1驱动程序
按键驱动
drivers/buttons
蜂鸣器驱动
建议按照混杂设备的架构完成
drivers/beep
6.2应用程序
实时监控,满足条件就报警
使用多线程/Qtimer
去轮询红外(按键)是否有数据,有数据报警
使用定时器间隔一段时间就给server发命令,读是否有按键值
如果有键值,给server发蜂鸣器响的命令
客户端程序: client/
服务器程序: server/beep.c
beep_hw.c
leds.c
leds_hw.c
. /home/etc/profile
如果看视频数据
/home/bin/start.sh &
安装设备驱动
insmod home/drivers/beep_drv.ko
insmod home/drivers/leds_drv.ko
insmod home/drivers/btn_dev.ko
insmod home/drivers/btn_drv.ko
按键的驱动:
1)内核中原有的按键驱动裁剪
2)对应的设备文件
cat /proc/bus/input/devices
open("/dev/input/eventX",O_RDONLY|O_NONBLOCK);
启动udp server 接收客户端发送的命令
/home/bin/server &
ehome_v3.tar.gz
7、MP3功能的实现
完成mp3文件的解码工作,
MP3_format_parse.doc
将解码之后的数据写入声卡设备
Linux_sound_guide.doc
mp3/ALSA Audio API 使用指南(中英版) - 幻雪神界 - 博客频道 - CSDN.NET.html
7.1基本概念
PCM: 脉冲编码调制
声音是模拟量
计算机能处理的是数字量,涉及模拟量和数字量的相互转换
录音时是模拟量转数字量
播放时是数字量转模拟量
采样频率:
每秒钟抽取声波幅度样本的次数
当采样频率应该在40kHz左右,保证声音不失真
量化位数:
每次采样,模拟音频信号的振幅用多少个bit来表示
一般使用16bit
声道数:
有单声道和双声道之分,双声道又称为立体声
MP3:
音频数据的压缩算法
有损压缩(还原后是有失真的)
压缩比可以接近10:1-12:1
使压
缩后的文件在回放时能够达到比较接近原音源的声音效果
7.2 MP3文件的解码工作
MP3_format_parse.doc
其存储格式:
TAGV2
{
。。。
}
FRAME
{
帧头
{
。。。。
}
数据实体
}
FRAME
...
TAGV1
{
。。。
}
mp3文件的解码工作,可以使用开源的库完成
mp3.rar/libmad-0.15.1b.tar.gz
libmad库的使用步骤:
1)cd project/
2)mkdir mp3
cd mp3
3)cp /mnt/hgfs/project/env/mp3/libmad-0.15.1b.tar.gz ./
4)tar xf libmad-0.15.1b.tar.gz
5) ./configure --help
./configure CC=arm-cortex_a9-linux-gnueabi-gcc --host=arm-linux --prefix=/home/tarena/project/mp3/install
--host: 运行目标平台
--prefix: 安装路径 make install
用来生成合适的Makefile
6) make && make install
7) minimad.c
libmad API使用的演示demo
从标准输入获取要解码的数据
进行解码
解码后的数据打印到标准输出设备
arm-cortex_a9-linux-gnueabi-gcc minimad.c -o player -L ../install/lib/ -lmad
如果出现“../install/lib//libmad.so:对‘III_imdct_l’未定义的引用”
解决方式:
make clean
make
make install
arm-cortex_a9-linux-gnueabi-gcc minimad.c -o player -L ../install/lib/ -lmad
计划将解码之后的数据输出到声卡设备
7.3 声卡的驱动程序
linux下的声卡驱动分两种:
OSS: Open Sound System
最早出现的
非开源程序
/dev/dsp
/dev/mixer
open("/dev/dsp",...)
ioctl()设置采样频率 量化位数 声道数
write(解码后的数据)
ALSA: 开源
make menuconfig
Device Drivers --->
<*> Sound card support --->
<*> Advanced Linux Sound Architecture --->
ls /dev/snd/
controlC0
controlC1
pcmC0D0c
pcmC0D0p
pcmC1D0p
timer
7.4 alsa库的使用
如果是alsa架构,提供了一套用户空间访问以上设备文件
的库函数alsa-lib-1.1.1.tar.bz2
alsa库封装了对/dev/snd下设备文件的操作
编译步骤:
1) cd mp3/
2) cp /mnt/hgfs/project/env/mp3/alsa-lib-1.1.1.tar.bz2 ./
tar xf alsa-lib-1.1.1.tar.bz2
cd alsa-lib-1.1.1/
3) ./configure CC=arm-cortex_a9-linux-gnueabi-gcc --host=arm-linux --prefix=/home/tarena/project/mp3/install --with-configdir=/opt/alsa --with-plugindir=/opt/alsa_lib
4) make && make install
以上库函数的使用可以参考
alsa使用官网:http://www.alsa-project.org/alsa-doc/alsa-lib/index.html
http://www.alsa-project.org/main/index.php/Download
或者参考:
ALSA Audio API 使用指南(中英版) - 幻雪神界 - 博客频道 - CSDN.NET.html
以上两个库libmad alsa库的使用实例mp3_player.c
mp3_player.c和minimad.c相比修改以下内容
./player xxx.mp3
1)原来从标准输入读取解码数据
现在从xxx.mp3文件中读取解码数据
2)设置声卡的工作属性 44.1KHz 16bit 双声道
set_pcm() //参考文档中“一个最简单的回放程序”
{
/*打开声卡设备*/
rc=snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
//硬件设置为交错模式
snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
//设置16位采样精度
rc=snd_pcm_hw_params_set_format(handle, params, SND_PCM _FORMAT_S16_LE);
//设置声道,1表示单声>道,2表示立体声
rc=snd_pcm_hw_params_set_channels(handle, params, channels);
//设置频率
snd_pcm_hw_params_set_rate_near(handle, params, &rate, &dir);
}
3)修改了output
将解码的音频数据输出到声卡设备
注意:2) 3) 参考 ALSA Audio API 使用指南(中英版) - 幻雪神界 - 博客频道 - CSDN.NET.html
/*libmad解码的音频帧数据写入声卡设备*/
snd_pcm_writei(handle, OutputPtr, n);
编译 部署 运行 player程序
1)arm-cortex_a9-linux-gnueabi-gcc mp3_player.c -lmad -lasound -L install/lib/ -I install/include/ -o player
出现了以下错误:
libmad.so : III_....
解决方法:
cd libmad 源码目录
make clean
make
make install
arm-cortex...... mp3_player.c
2)cp player ../rootfs/home/bin/
3)cp install/lib/* ../rootfs/home/lib/ -a
4)配置文件和插件库文件
注意,配置时指定了路径
--with-configdir=/opt/alsa --with-plugindir=/opt/alsa_lib
部署到开发板上时要保持一致
cp /opt/alsa/ ../rootfs/opt/ -a
cp /opt/alsa_lib/ ../rootfs/opt/ -a
5) mkdir ../rootfs/home/songs
cp /mnt/hgfs/project/env/mp3/*.mp3 ../rootfs/home/songs/
6) 运行
. /home/etc/profile
/home/bin/player /home/songs/I Will Always Love You Always Love You.mp3 1>/dev/null 2>&1
练习:
1)看懂mp3_player.c
2)编译 部署到开发板运行
3)/home/bin/player /home/songs/xxx.mp3
//将/home/songs目录下的所有.mp3文件播放一遍
/home/bin/player /home/songs
参考ehome_final.tar.gz/server/ myplayer*.c
有目录操作
7.5 加一个简单的GUI界面
播放:
1)开启新线程开始播放
或者
2) 唤醒被暂停的播放线程
pthread_cond_signal(&conti);
暂停:stat=1
修改了播放线程中的output
//alsa声卡的暂停,将缓冲区中提提交声卡播放数据全部提交给声卡
snd_pcm_drop(handle);
//让播放线程进入睡眠状态, 暂停了播放
pthread_cond_wait(&conti, &lock);
当再次收到播放命令 会执行到“ pthread_cond_signal(&conti); ”
//设置声卡处于就绪状态
snd_pcm_prepare(handle);
上一首
MUSIC_NEXT music_stat =2
播放线程
music_play()
{
while(遍历歌曲)
{
decode()
{
out_put
{
return MAD_FLOW_STOP//导致decode()执行完毕
}
}
munmap
改变cur_pos //标识要播放的目录中的第几首歌
}
}
下一首
MUSIC_PREV music_stat =3
播放线程
music_play()
{
while(遍历歌曲)
{
decode()
{
out_put
{
return MAD_FLOW_STOP//导致decode()执行完毕
}
}
munmap
cur_pos = cur_pos?(cur_pos-1):0; //标识要播放的目录中的第几首歌
}
}
步骤:
. /home/etc/profile
/home/bin/start.sh &
insmod /home/drivers/beep_drv.ko
insmod /home/drivers/btn_drv.ko
insmod /home/drivers/btn_dev.ko
insmod /home/drivers/leds_drv.ko
/home/bin/server &
8、计步器的功能实现
使用g-sensor实现
如何算一步:
只要xyz任意一轴和上个周期比有了一个比较大的变化
8.1 g-sensor的驱动程序
8.2 应用程序 C/S架构
c/s: client/server : qq
b/s browser/server : 网页版游戏
client: qt gui
周期性(博尔特)的给服务器发送命令
让服务器不断的读取g-sensor的数据
客户端如果收到1,步数+1 ,并显示
可以使用Qtimer
server:
收到相应的命令,读取g-sensor的数据
上次读到的结果进行比较
如果变化比较大(人为定义),
给客户端返回1,否则返回0
数据的比较可以放在服务器上完成
其实也可以放在客户端完成
步骤:
source /home/etc/profile
insmod /home/drivers/beep_drv.ko
insmod /home/drivers/btn_dev.ko
insmod /home/drivers/btn_drv.ko
insmod /home/drivers/leds_drv.ko
insmod /home/drivers/mma8653_drv.ko
/home/bin/start.sh &
/home/bin/server &
9、项目的发布
9.1 配置脚本,实现自启动
/etc/init.d/rcS: 开机自启动的程序可以放入该脚本
/etc/profile: 全局对所有用户有效的环境变量
开机自启动:
vi rootfs/etc/init.d/rcS
最后加入
exec /home/etc/rcS
vi rootfs/home/etc/rcS
#配置开发板IP
ifconfig eth0 192.168.1.6
ifconfig lo up
#加载内核模块
find /home/drivers/ -name *ko -exec insmod {} ;
source /home/etc/profile
#启动视频服务器
/home/bin/start.sh &
#启动UDP服务器
/home/bin/server &
#启动GUI界面程序
/home/bin/client &
chmod +x rootfs/home/etc/rcS
环境变量的设置
vi rootfs/etc/profile
. /home/etc/profile
9.2 分区的规划
前提条件,该分区能够存储下要存储文件
0--------1M---------------17M--------------------273M---------------->剩余
扇区号 0x800 0x8800 0x88800
uboot kernel rootfs userdata
fdisk 2 3 0x100000:0x1000000 0x1100000:0x10000000 0x11100000:0
9.2.1 制作uImage 并完成烧写
tftp 48000000 uImage
mmc write 48000000 0x800 0x3000
9.2.2 制作ext4类型个文件系统 并烧写
#创建256M大小的rootfs.ext4文件
dd if=/dev/zero of=rootfs.ext4 bs=1k count=262144
du -h rootfs.ext4
sudo mkfs.ext4 rootfs.ext4
sudo mkdir /mnt/ext4
sudo mount -t ext4 -o loop rootfs.ext4 /mnt/ext4
sudo cp rootfs/* /mnt/ext4/ -a
sudo umount /mnt/ext4
cp rootfs.ext4 /tftpboot/
tftp 0x48000000 rootfs.ext4
mmc write 48000000 0x8800 0x80000
9.2.3 启动参数的修改
setenv bootargs root=/dev/mmcblk0p2 init=/linuxrc console=ttySAC0,115200 rootfstype=ext4 lcd=wy070ml tp=gslx680-linux cam=OV5645 maxcpus=1
saveenv
系统启动后:
ls /dev/mmcblk0*
cat /proc/partitions
mkfs.vfat /dev/mmcblk0p3
mount /dev/mmcblk0p3 /mnt/
10、系统功能升级
通过网络进行升级
通过U盘来实现升级功能
10.1 U盘的手工挂载
ls /dev/sd*
插入U盘
ls /dev/sd*
mount /dev/sda1 /mnt
umount /mnt
10.2 u盘自动挂载
env/usb.rar
/dev/sda1设备文件自动创建
是由于热插拔事件产生,导致mdev程序被执行
由mdev来去创建的设备文件
其实可以通过设置让mdev在去创建/dev/sda1设备文件
的同时,可以完成u盘的自动挂载
问题1:如何配置让mdev既可以自动创建设备文件/dev/sda1
又可以创建后自动挂载u盘?
mdev.conf
问题2: 如何修改mdev.conf ,语法格式?
vi busybox-1.23.2/docs/mdev.txt
sd[a-z][0-9] 0:0 666 @/home/usb/usb_insert.sh /dev/$MDEV
sd[a-z][0-9], 设备文件的规则,满足该规则的
sda1 sdb2
sda 不满足
0:0 , uid:gid
666, 权限
@, 创建设备文件
/home/usb/usb_insert.sh /dev/$MDEV,
创建sda1/sdb2...设备文件时执行/home/usb/usb_insert.sh脚本,
并且传递参数/dev/sda1(sdb2)
sd[a-z] 0:0 666 $/home/usb/usb_remove.sh
$, 销毁设备文件之前
实验步骤:env/usb.rar
1)在rootfs/etc/mdev.conf
cp /mnt/hgfs/project/env/usb/usb/mdev.conf rootfs/etc/
2) mkdir rootfs/home/usb -p
cp /mnt/hgfs/project/env/usb/usb/usb_insert.sh rootfs/home/usb/
创建挂载点
mkdir rootfs/mnt/usb
3)cp /mnt/hgfs/project/env/usb/usb/usb_remove.sh rootfs/home/usb/
10.3手工操作将u盘中uImage 更新到
dd if=/mnt/usb/uImage of=/dev/mmcblk0p1
输入文件 if指定
输出到哪去 of
10.4当点击按钮时自动更新
system("dd if=/mnt/usb/uImage of=/dev/mmcblk0p1");
GUI: 给服务器发送命令 m
server: 收到命令m
执行system("dd if=/mnt/usb/uImage of=/dev/mmcblk0p1");
参考:ehome_v7.tar.gz