要编译的有libalsa, libmad, utils alsa.sh. 不多解释了,直接给脚本吧。
首先是编译环境的设置,用一个脚本env-codesoucery.sh来用它。需要的东西有CODESOUCERY编译工具(android自已的和GNU linux有点差别,要改源码,这里不用它)。glibc这与根文件系统有点类似,是gun linux的标准库,codesoucery自已也带有,但更新的慢。android NDK的包,基本没用,下了做出错时文件对比用。
#!/bin/bash export PATH=$PATH:/media/ququ/android/other-android/arm-2014.05/bin/#这是设置CODESOUCERY的linux 工具目录。
PROJECT_BASE=$(pwd); #PROJECT_BASE=/media/ququ/android/other-android/project/alsaplay/ REPOSITORY=$PROJECT_BASE/download ROOTFS=$PROJECT_BASE/compiled/rootfs
#这个需要生成编译的目录结构。在当前目录下生成build, compiled, download三个目录
#comiled 下生成rootfs目录 ## edit this export SYSROOT_ANDROID="/media/ququ/android/other-android/android-ndk-r10e/platforms/android-16/arch-arm/usr" export SYSROOT_CODESOUCERY="/media/ququ/android/other-android/arm-2014.05/arm-none-linux-gnueabi/libc" export SYSROOT_GLIBC="/media/ququ/android/other-android/project/glibc"
##依自已解包三个工具的情况改上需三个目录变量 export CC="arm-none-linux-gnueabi-gcc" export CXX="arm-none-linux-gnueabi-g++" export RANLIB="arm-none-linux-gnueabi-ranlib" export STRIP='arm-none-linux-gnueabi-strip' export LD='arm-none-linux-gnueabi-ld' export AR='arm-none-linux-gnueabi-ar' export HOST="arm-none-linux-gnueabi" export CPPFLAGS="-I${ROOTFS}/include" export LDFLAGS="-L${ROOFTS}/lib"
这里是libalsa.sh脚本
#/bin/bash source env-codesoucery.sh ME=alsa-lib-1.1.6
#cd $REPOSITORY && wget ftp://ftp.alsa-project.org/pub/lib/alsa-lib-1.1.6.tar.bz2 cd $PROJECT_BASE/build && tar -xvf $REPOSITORY/$ME.tar.bz2 #&& cd ./$ME cd $PROJECT_BASE/build/$ME
CC="arm-none-linux-gnueabi-gcc --sysroot=$SYSROOT_GLIBC" \ CXX="arm-none-linux-gnueabi-g++ --sysroot=$SYSROOT_GLIBC" \ CPPFLAGS="-I$ROOTFS/include -I$SYSROOT_CODESOUCERY/usr/include" \ #CPPFLAGS="-I$ROOTFS/include" \ #LDFLAGS="-L$ROOTFS/lib -L$SYSROOT_ANDROID/lib --static" \ LDFLAGS="-L$ROOTFS/lib -L$SYSROOT_GLIBC/lib --static" \
./configure \ --prefix="$ROOTFS" \ --enable-static \ --disable-shared \ --with-configdir=/usr/local/share \ --host=$HOST
make #make install
#下面的作法是因为要生成一些.conf的文件,需要系统信息,不知为何它需要root权限。因为要生成lib这个安装还是必需的。
#实际上这些.conf配置不要的话,只是在命令或代码中把硬件的相关能数写全,写对就好。 #在目录下 #su #export PATH=$PATH:/media/ququ/android/other-android/arm-2014.05/bin/ #make install
这里是alsa-utils.sh脚本
#/bin/bash source env-codesoucery.sh ME=alsa-utils-1.1.6
#cd $REPOSITORY && wget ftp://ftp.alsa-project.org/pub/utils/alsa-utils-1.1.6.tar.bz2 cd $PROJECT_BASE/build && tar -xvf $REPOSITORY/$ME.tar.bz2 #&& cd ./$ME cd $PROJECT_BASE/build/$ME
CC="arm-none-linux-gnueabi-gcc --sysroot=$SYSROOT_GLIBC" \ CXX="arm-none-linux-gnueabi-g++ --sysroot=$SYSROOT_GLIBC" \ CPPFLAGS="-I$ROOTFS/include -I$SYSROOT_CODESOUCERY/usr/include" \ #CPPFLAGS="-I$ROOTFS/include" \ #LDFLAGS="-L$ROOTFS/lib -L$SYSROOT_ANDROID/lib --static" \ LDFLAGS="-L$ROOTFS/lib -L$SYSROOT_GLIBC/lib --static" \
./configure \ --prefix="$ROOTFS" \ --enable-static \ --enable-shared \ --disable-alsamixer \ --disable-xmlto \ --with-alsa-inc-prefix=/media/ququ/android/other-android/project/alsaplay/compiled/usr/alsalib/include \ --host=$HOST
make #make install
这里是libmad.sh脚本,这是一个mp3解码器
#/bin/bash source env-codesoucery.sh ME=libmad-0.15.1b
#cd $REPOSITORY && wget ftp://ftp.mars.org/pub/mpeg/$ME.tar.gz cd $PROJECT_BASE/build && tar -zxvf $REPOSITORY/$ME.tar.gz #&& cd ./$ME cd $PROJECT_BASE/build/$ME
CC="arm-none-linux-gnueabi-gcc --sysroot=$SYSROOT_GLIBC" \ CXX="arm-none-linux-gnueabi-g++ --sysroot=$SYSROOT_GLIBC" \ CPPFLAGS="-I$ROOTFS/include -I$SYSROOT_CODESOUCERY/usr/include" \ #CPPFLAGS="-I$ROOTFS/include" \ #LDFLAGS="-L$ROOTFS/lib -L$SYSROOT_ANDROID/lib --static" \ LDFLAGS="-L$ROOTFS/lib -L$SYSROOT_GLIBC/lib --static" \
./configure \ --prefix="$ROOTFS" \ --disable-shared \ --enable-static \ --host=$HOST
sed -i 's/-fforce-mem //g' ./Makefile #-fforce-mem这个gcc选项不用。
make make install
如上脚本有些注解需要去除。脚本是在ubuntu18.04下实测过的。编译过程遇到的问题有:
1,wget因为网站原因不一定能下到,一些需要自已手工下载后放到download目录下。依下的包改一下脚本。
2,libmad编译,出现错误:error: unrecognized command line option “-fforce-mem” 。就在Makefile中找到“-fforce-mem”字符串然后删除。
3最多的就是,找不到XXX的错误,这些是因为库没设置对。细心研读一下编译过程提示,gcc的全部参数,看看与自已相的一样不,再就是研读一下编译各参数的意义。有时把build下的解包目录删了再来做解决一些上一次./configure之前编译存留而产生的错误。
4,./configure对包个包有类似,但必不一定。具体的./configure --help看一下。 再不能解决就只能看其下的文本内容了。很多configure的参数应用是类似的,细心了解一个对别的编译有利,对自已写也有利。一些编译工具自动生成的configure,可读性相当差。手工写的已不多。手工不如直接写makefile.
5,如下是,编译网下的一个mp3小代码的makefile, 看明白的话对configure如何利用设定参数有进一步的理解。比如编译参数--L/media/ququ/android/other-android/project/glibc/lib -lm -ldl -lpthread -lrt 这实际上是libasound(也就是alsalib)的依赖库,从install到的目录库下的相关.la文件中可以找到这个参数。-lm -ldl -lpthread -lrt 东西少一个都会产生错误。要链接的库主要分二部分,一个是系统的,一个就是自编译的。-L/media/ququ/android/other-android/project/glibc/lib -lm -ldl -lpthread -lrt这个的写法是一个路径后,加下这个路径要需要链接的库。-lm库的文件名实际上是libm.so或者静态库libm.a。报错的话就去目录下先看看。
TOOLPATH:=/media/ququ/android/other-android/arm-2014.05/bin ROOTFS:=/media/ququ/android/other-android/project/alsaplay/compiled/rootfs SYSROOT_CODESOUCERY:=/media/ququ/android/other-android/arm-2014.05/arm-none-linux-gnueabi/libc SYSROOT_GLIBC:=/media/ququ/android/other-android/project/glibc CC:=$(TOOLPATH)/arm-none-linux-gnueabi-gcc --sysroot=$(SYSROOT_GLIBC) CPPFLAGS:=-I$(ROOTFS)/include -I$(SYSROOT_CODESOUCERY)/usr/include LDFLAGS:=-L$(ROOTFS)/lib -L$(SYSROOT_GLIBC)/usr/lib -L$(SYSROOT_CODESOUCERY)/usr/lib
ver = debug ifeq ($(ver),debug) all: $(CC) -g -Ddebug $(CPPFLAGS) mp3-play.c -c -o mp3-play.o $(TOOLPATH)/arm-none-linux-gnueabi-gcc -static mp3-play.o -o myplay -L/media/ququ/android/other-android/project/alsaplay/compiled/rootfs/lib -lmad -lasound -L/media/ququ/android/other-android/project/glibc/lib -lm -ldl -lpthread -lrt else all: $(CC) -g -Ddebug mp3-play.c $(LDFLAGS) -pthread $(ROOTFS)/lib/libmad.a $(ROOTFS)/lib/libasound.a $(CPPFLAGS) -o myplayer endif
下需是一个调试时需要用到的命令。我是在联想a385e这个垃圾手机上做测试的。
./aplay -Dhw:0,0 -f cd test2.wmv
aplay到build下去找吧,安装没好似没安rootfs下的bin中。adb push的到手机中。还需要alsa.conf到手机中。这个要放在--with-configdir=/usr/local/share \找定的目录下。在make install时这个东西就放到PC的/usr/local/share下。所以这install时需要root权限。手机中没这个目录,su后mkdir出来,临时试一下,以后要不改目录,要不改手机启动脚本,不要的话自已写代码,是可以不用它的。./aplay --help有简单的参数说明。test2.wmv在PC上。用如下命令生成
arecord -Dhw:0,0 -r44100 -f cd -c 2 test2.wmv -d 10
生成后adb push到手机。-Dhw:0,0 指第一个声卡的第一通道,在/dev/snd/下有pcmC0D0c这个设备,-r44100采样率,-f cd 文件的格式标准,与播放时一至就好。-c 2双通道,-d 10采样10秒。
网上有如下说法:
在目标板上,以下文件必须被拷贝至对应位置:
1) lib 库文件,放在 /lib/ 中 (这个对嵌入式系统的,android的要用的话,用静态编译吧,文件大一点,android动态库加载与GNU有所不同。)
2) conf 文件,应放在 /usr/local/share 中,与编译时指定的目录相同 (这个最好做了)
3) 应用文件, util 能产生 aplay,amixer,arecord 可执行程序,这些文件可放在 /usr/sbin 中 (随意放,加路径,就好)
4) 必须保证有 /dev/snd/ 目录中,此目录下应包含以下几个设备文件 ( 驱动 ) ,controlC0, pcmC0D0c, pcmC0D0p, timer
如果这些文件已经在 /dev/ 下,可拷贝到 snd 目录中。(这个要确认一下。)
除却了第一点android4.1与嵌入式系统基本一致。这也是在android用嵌入式系统代码的方法,静态编译,不与系统产生过多关系。静态编译是解决应用与系统代码版本冲突的方法。这里只是大版本冲突。
补一些对libmad的测试:
libmad自带了一个minimad.c 的文件,只它,在交叉编译环境下编译,可以编一出一个MP3,转RAW的程序。
./minimad <test.mp3 1>t2.raw
把test.mp3转换成t1.raw.播放用
./aplay -Dhw:0,0 -f S16_LE -r 44100 -c 2 t1.raw
还下了一个madplay的小代码,其应用的硬件是OSS的,所以不能直接播放,但可以转格式。
./madplay -o raw:t1.raw test1.mp3
网上下了别人一个用代码移数据的小程序,结果在android linux上总是出现underrun错误,还不如用如下的管道命令用。
./minimad <test1.mp3 | ./play-alsa
./play-alsa是个把控制台数据直接写入alsa的小程序。这种流控制还是相当高效的。代码中数据的移动中出现underrun,可能的原因在于硬件缓冲区大小,与libmad一帧输出数据折大小不配所至。从面把libmad的代码卡在等硬件中,而后,libmad输出大数据贞的时间片又不足,如果两个代码模块都是可以调定的话,依硬件调缓冲区大小。如果两个模块,不好改的话。看上去开两个进程用管道的结果很不错。管道这种内核机制还是相当高效的。这种原理也可以用在代码中。找到实现方法。再补全。
出现underrunw的代码在PC的编译运行没有什么问题,但编译到arm-linux在手机上就不播放声音了。开始定位在代码编译问题上,因为出现了静态库的warning。这些实际上不是错误的话问题不大。还有主是libmad在PC与arm上定义的参数可能不同。下别的代码一部分一部分排除。双把gdb代码编译了一下,才定位到underrun。因为下的代码简写了出现后打出信息,所以直接动态调试总都没注意到这个。代码不多。但两个模块的组合方式,网上下的就是新手中的新手写的。我在了解流程后,就明白其可能的原因就是移缓冲区的时间上。就想改它。想到的缓冲区切换,必不合适这种libmad的这种非自已写的模块。因为在调试过程中,分类测试时会用到了minimad和play-alsa这两个是独立的分别测libmad与alsa的简代码。这种简代码是相当高效的。网上下的新手参考这两个代码把它合在一起的方法就是在一个代码的输出调用中加入写别一个模块的输入代码。这里就有个问题,因为缓冲区是单一的,调用写入模块会阻塞;前一个模的输出返回。在PC的能通,因为现在的PC对处理音频量的数据说,太强大了。这在arm.512M内存。其上的android进程还有不少。在这样的性能下就出问题了。其实正确的做法就是用FIFO(命令管道的别一叫法)把模块线程化并独立。这个新手,可能是现在的软件速成出来的。要不就大一,大二的新手。只是对我这个不了解模块图写别的功能用现成东西的人说,坑了我两天。不过把gdb,也了解了一下。再编一下GCC的arm版。
现在做程序,实际上没前景的,只是无聊时,聊以自慰。程序所能实现的功能空间,很近似于30年前的化学所能实现的材料空间,也近似与50年前机械设计所能实现的功能空间。30年前的机械设计,给机械设计增加了一部分可以实现的功能空间。计算机的发现同时也机械设计机械设计所能实现的空间在扩大。但这种影响,到现在其本停止了。末来能扩大人类所实现的空间的增长点在什么地方?我看不清,也看不到。
代码改为fork出一个新进程后,用PIPE相连,微调一下两个代码,实现mp3歌曲的正常播放。直接在手机的LINUX下试了。
补记:socket-server -q 与 /socket-client fd72:9e38:5ed3:0:4d65:64ff:d568:f32 7838是自编的发送与接收程序,socket-server -q打接收到的全部putchar. /socket-client fd72:9e38:5ed3:0:4d65:64ff:d568:f32 7838用的是ipV6.写时server端没写ipV4的listen .但在本机自收自已不存在这个问题。打127.0.0.1有效,但在不同手机上,有问题。只能用ipv6. 没想过改写,就只能这样去测了。
anroid 下: ./socket-server -q | ./play-alsa 对应PC下 ./client.out :ce07:e4ff:fe5c:c465 7838 <t1.raw
反向操作 anroid 下: ./socket-client fd72:9e38:5ed3:0 7838 <t1.raw 对应PC下 ./server.out -q | ./play-alsa 上面的这两组命令都是有效可用的。
中下的组合实测有问题,问题出在缓冲上(解码器太慢了)。
./server.out -q |./play-alsa与 ./minimad < test1.mp3 | ./socket-client fd72:9e38:5ed3:0:4d65:64ff:d568:f32 7838
./server.out -q | ./myplay 与 ./socket-client fd72:9e38:5ed3:0:4d65:64ff:d568:f32 7838 < test1.mp3
./myplay是自已改写的程序。./myplay<test1.mp3 是没问题的。 中间加了网络后,缓冲的的问题就出来了。回为数据不急时,播放会退出。这里也可能有myplay用了mmap这样的零拷备处理有关。/play-alsa用的是标准的read函数。所以与标准输入不配合好。./minimad虽解也有标准输入读数据,但用了mmap只能定向到文件才好用。比如:
./minimad < /dev/urandom 这个就不能产生数据。直接退出。mmap调用不成功就退出了。因为都参考了minimad 的写法,用了零拷备mmap所以,在定位文件大小时,会就报错了。
补:
在linux下尝试用alsa的配置转声音到手机不成功。配置文件用.asoundrc配在用户目录下。不成功的原因主要是网络设计中的缓冲与声卡的工作方式不配。解决方法,配成一个个分片文件去播放。时间与效率相兼。这种方式与流媒体的工作方式基本差不多了。只是转用化,小型化了。前面的方式能成攻在与传送的是文件。这也与管道的工作方式可能相关。用流媒体服务器去实现这种应用也是一种方法。从这里也可以看出TCP/IP的实时性问题。这个要细心设计去解决。流媒体的工作方式实时性不好。直接优化。
如下的配置是可以的:
1,PC端 .asoundrc 配一个用管道的PCM
pcm.usingfile { type plug # slave.pcm "file:|/work/developing-software/wifi-uart/play-alsa" slave.pcm "file:|/work/developing-software/wifi-uart/socket-client fd72:9e38:5ed3:0:ce07:e4ff:fe5c:c465 7838" }
socket-client 代码的输入为标准输入,不能用mmap的零拷备方式。用文件读取read
aplay -D usingfile -f cd t1.raw
android:
./socket-server -q | ./play-alsa
今天终于用基本的命令组合,实现了在手机上播放电脑声音的目的。基本的工具有:
1,./socket-server -q |./play-alsa 这是 android上的接收入播放工具,play-alsa是alib中的测试代码,改了一下就可以用。socket-server是自编代码,改自网上的其本TCP/IP。
2,./record-alsa |./socket-client fd72:9e38:5ed3:0:ce07:e4ff:fe5c:c465 7838 这个同上。
3,aplay -D testtwo -f dat t1.raw aplay是alsa的一个小播放代码。
要配的就是.asoundrc。
# .asoundrc pcm.multi { type route; slave.pcm { type multi; slaves.a.pcm "output"; slaves.b.pcm "loopin"; slaves.a.channels 2; slaves.b.channels 2; bindings.0.slave a; bindings.0.channel 0; bindings.1.slave a; bindings.1.channel 1; bindings.2.slave b; bindings.2.channel 0; bindings.3.slave b; bindings.3.channel 1; }
ttable.0.0 1; ttable.1.1 1; ttable.0.2 1; ttable.1.3 1; }
pcm.testtwo { type plug slave.pcm "multi" }
pcm.output { # type hw # card 0 type plug slave.pcm "hw:0,0" }
pcm.loopin { type plug slave.pcm "hw:Loopback,0,0" }
pcm.loopout { type plug slave.pcm "hw:Loopback,1,0" }
这样配后,双卡输出。这个卡loop 回, 采样回给网络。 用 file的alsa的plugin是无用的,因为它与multi不能配合。
过程遇到的问题:
1,缓冲问题,socket-client与play-alsa借用了零拷备技术中的mmap.这个对本的的文件操作相当方便,但对网络代码不工作。
2,钟差问题。晶振的钟差在发数据读不足时直接用前一帧的数据补效果最好。这个通常差太小,1分钟差不出一个数据的。比较明显的问题是,手机没用重采样模块,对有些频率设定时就近做选择了。听上去歌声怪怪的。比如我遇到的在android 设计采样44100听上去明显的快一点。实际的去比较了一下,应是48000的。cd 格式的是44100改如下,就不会出现明显的转送数据不足了。只会在开始时有一点。采样频率当然要保证与原歌一样,因为是raw播放,格式只在指定的参数中,文件中没有。只是歌声听上去有点让人不适的感觉。
aplay -D testtwo -f cd t1.raw
aplay -D testtwo -f dat t1.raw
下一频的工作就是参数loopback模块的代码,直接改到网络上。
用脚本也是一个不错的选择。
#phone-sound.sh在每想用之前启动一下。
#配声卡用前接口。并启动snd-aloop生成虚拟声卡
echo "123456" | sudo -S amixer set 'Master' unmute sudo amixer set 'Headphone' unmute amixer -c 0 sset 'Master',0 100%,80% unmute amixer -c 0 sset 'Headphone',0 100%,80% unmute sudo rmmod snd-aloop sudo modprobe snd-aloop pcm_substreams=1
#脚本式的网络传送。
gnome-terminal -t "socket-client" -x bash -c 'adb shell "su -c data/ququfile/socket-server -q |su -c data/ququfile/play-alsa";exec bash;' gnome-terminal -t "socket-client" -x bash -c "/work/developing-software/wifi-uart/record-alsa |/work/developing-software/wifi-uart/socket-client fd72:9e38:5ed3:0:ce07:e4ff:fe5c:c465 7838;exec bash;"
在VLC中选新此类出的声卡就好。如果不好,先启动VLC再重新启一下脚本。