当前位置: 编程技术>移动开发
本页文章导读:
▪关于上拉框的一些应用 关于下拉框的一些应用
做下拉框可以利用系统自带的方法创建adpter,通过数组传入需要的字符串。布局文件如下<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/widget28".........
▪ libgdx-汉语言显示与汉字绘制 libgdx--中文显示与汉字绘制
要用libgdx框架下显示中文 那么不得不用到hiero工具首先下载hiero.jar工具,运行hiero.jar 设置自己喜欢的字体 并保存入下图自动生成俩个文件 *.png,和*.fnt并把这俩.........
▪ 【ALSA】关于alsa的小结 【ALSA】关于alsa的总结
一. alsa展现的三层结构:
(1)audio interface:
audio interface就是声卡,它含有hardware buffer,注意,这个hardware buffer是在声卡里面,不是内存。
(2.........
[1]关于上拉框的一些应用
来源: 互联网 发布时间: 2014-02-18
关于下拉框的一些应用
做下拉框可以利用系统自带的方法创建adpter,通过数组传入需要的字符串。
布局文件如下
java代码如下:
小结
1.Spinner中textview显示的内容为list中第一个元素。
2.继续学习自定义风格的下拉框并且改变spinner的默认值。
做下拉框可以利用系统自带的方法创建adpter,通过数组传入需要的字符串。
布局文件如下
<?xml version="1.0" encoding="utf-8"?> <LinearLayout android:id="@+id/widget28" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" android:layout_gravity="center_vertical" android:gravity="center_horizontal" xmlns:android="http://schemas.android.com/apk/res/android" > <TextView android:id="@+id/TextView_Show" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="你选择的是" android:textSize="25sp"> </TextView> <Spinner android:id="@+id/spinner_City" android:layout_width="fill_parent" android:layout_height="wrap_content"> </Spinner> <TextView android:id="@+id/TextView_Show1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="你选择的是" android:textSize="25sp"> </TextView> <Spinner android:id="@+id/spinner_City1" android:layout_width="fill_parent" android:layout_height="wrap_content"> </Spinner> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="50dp" android:layout_gravity="center_horizontal" android:gravity="center_horizontal" android:text="OK" android:id="@+id/button" android:visibility="invisible"/> </LinearLayout>
java代码如下:
package com.example.mydrawsector; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.Spinner; import android.widget.TextView; public class SpinnerActivity extends Activity { /** Called when the activity is first created. */ private List<String> list = new ArrayList<String>(); private List<String> list1 = new ArrayList<String>(); private TextView myTextView; private TextView myTextView1; private Spinner mySpinner; private Spinner mySpinner1; private ArrayAdapter<String> adapter; private ArrayAdapter<String> adapter1; private Button button; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //第一步:添加一个下拉列表项的list,这里添加的项就是下拉列表的菜单项 list.add("北京"); list.add("上海"); list.add("深圳"); list.add("南京"); list.add("重庆"); list1.add("公交"); list1.add("地铁"); list1.add("自驾游"); button = (Button)findViewById(R.id.button); myTextView = (TextView)findViewById(R.id.TextView_Show); mySpinner = (Spinner)findViewById(R.id.spinner_City); myTextView1 = (TextView)findViewById(R.id.TextView_Show1); mySpinner1 = (Spinner)findViewById(R.id.spinner_City1); //第二步:为下拉列表定义一个适配器,这里就用到里前面定义的list。 adapter = new ArrayAdapter<String>(this,android.R.layout.simple_spinner_item, list); //第三步:为适配器设置下拉列表下拉时的菜单样式。 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); //第四步:将适配器添加到下拉列表上 adapter1 = new ArrayAdapter<String>(this,android.R.layout.simple_spinner_item, list1); //第三步:为适配器设置下拉列表下拉时的菜单样式。 adapter1.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mySpinner.setAdapter(adapter); mySpinner1.setAdapter(adapter1); //第五步:为下拉列表设置各种事件的响应,这个事响应菜单被选中 mySpinner.setOnItemSelectedListener(new Spinner.OnItemSelectedListener(){ @SuppressWarnings("unchecked") public void onItemSelected(AdapterView arg0, View arg1, int arg2, long arg3) { // TODO Auto-generated method stub /* 将所选mySpinner 的值带入myTextView 中*/ myTextView.setText("您选择的是:"+ adapter.getItem(arg2)); /* 将mySpinner 显示*/ arg0.setVisibility(View.VISIBLE); isShowOKButton(); } @SuppressWarnings("unchecked") public void onNothingSelected(AdapterView arg0) { // // TODO Auto-generated method stub // myTextView.setText("NONE"); // arg0.setVisibility(View.VISIBLE); } }); mySpinner1.setOnItemSelectedListener(new Spinner.OnItemSelectedListener(){ @SuppressWarnings("unchecked") public void onItemSelected(AdapterView arg0, View arg1, int arg2, long arg3) { // TODO Auto-generated method stub /* 将所选mySpinner 的值带入myTextView 中*/ myTextView1.setText("您选择的是:"+ adapter1.getItem(arg2)); /* 将mySpinner 显示*/ arg0.setVisibility(View.VISIBLE); isShowOKButton(); } @SuppressWarnings("unchecked") public void onNothingSelected(AdapterView arg0) { // TODO Auto-generated method stub // myTextView1.setText("NONE"); // arg0.setVisibility(View.VISIBLE); } }); // /*下拉菜单弹出的内容选项触屏事件处理*/ // mySpinner.setOnTouchListener(new Spinner.OnTouchListener(){ // public boolean onTouch(View v, MotionEvent event) { // // TODO Auto-generated method stub // /* 将mySpinner 隐藏,不隐藏也可以,看自己爱好*/ // v.setVisibility(View.INVISIBLE); // return false; // } // }); /*下拉菜单弹出的内容选项焦点改变事件处理*/ mySpinner.setOnFocusChangeListener(new Spinner.OnFocusChangeListener(){ public void onFocusChange(View v, boolean hasFocus) { // TODO Auto-generated method stub v.setVisibility(View.VISIBLE); } }); } public void isShowOKButton(){ if((myTextView.getText()!=null||myTextView.getText()!="")&&(myTextView1.getText()!=null||myTextView1.getText()!="")){ button.setVisibility(View.VISIBLE); } } }
小结
1.Spinner中textview显示的内容为list中第一个元素。
2.继续学习自定义风格的下拉框并且改变spinner的默认值。
[2] libgdx-汉语言显示与汉字绘制
来源: 互联网 发布时间: 2014-02-18
libgdx--中文显示与汉字绘制
要用libgdx框架下显示中文 那么不得不用到hiero工具首先下载hiero.jar工具,运行hiero.jar 设置自己喜欢的字体 并保存入下图
自动生成俩个文件 *.png,和*.fnt并把这俩个文件存入assets文件夹中。
private BitmapFont bitmapfont;
private SpriteBatch batch;
@Override
public void create() {
// TODO Auto-generated method stub
batch=new SpriteBatch();
bitmapfont = new BitmapFont(Gdx.files.internal("1.fnt"), Gdx.files.internal("1.png"), false);
}
@Override
public void render() {
// TODO Auto-generated method stub
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); //清屏
batch.begin();
//bitmapfont.draw(batch, "FPS" + Gdx.graphics.getFramesPerSecond() , 50, Gdx.graphics.getHeight()-10);
bitmapfont.draw(batch, "祝大家新年快乐", 0, Gdx.graphics.getHeight()/2-8);
batch.end();
}
要用libgdx框架下显示中文 那么不得不用到hiero工具首先下载hiero.jar工具,运行hiero.jar 设置自己喜欢的字体 并保存入下图
自动生成俩个文件 *.png,和*.fnt并把这俩个文件存入assets文件夹中。
private BitmapFont bitmapfont;
private SpriteBatch batch;
@Override
public void create() {
// TODO Auto-generated method stub
batch=new SpriteBatch();
bitmapfont = new BitmapFont(Gdx.files.internal("1.fnt"), Gdx.files.internal("1.png"), false);
}
@Override
public void render() {
// TODO Auto-generated method stub
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); //清屏
batch.begin();
//bitmapfont.draw(batch, "FPS" + Gdx.graphics.getFramesPerSecond() , 50, Gdx.graphics.getHeight()-10);
bitmapfont.draw(batch, "祝大家新年快乐", 0, Gdx.graphics.getHeight()/2-8);
batch.end();
}
[3] 【ALSA】关于alsa的小结
来源: 互联网 发布时间: 2014-02-18
【ALSA】关于alsa的总结
一. alsa展现的三层结构:
(1)audio interface:
audio interface就是声卡,它含有hardware buffer,注意,这个hardware buffer是在声卡里面,不是内存。
(2)computer:
这个指的是计算机的内核和驱动(驱动由alsa提供),当(1)的audio interfacce引发中断,内核会捕捉到,再把处理移交alsa。
(3)application:
这个就是你写的程序,你开辟一个buffer,比如playback,就交给alsa来play。
在上面的框架下,流程如下:
(1)playback:
application开辟一个buffer,填上数据,调用alsa接口,alsa把buffer数据复制到其驱动的空间,再把数据交给 hardware buffer。
(2)record:
同playback,相似的。
二. 细节:
按照上面的流程,其中有许多细节我们可以加以控制,这里仅仅指出应用程序需要关心的:
1) 操作的设备:
在alsa驱动这一层,目前为止,抽象出了4层设备:
一是如hw:0,0,二是plughw:0,0,三是default:0,四是default至于 一是清楚了,二和二以上可以做数据转换,以支持一个动态的范围,比如你要播放7000hz的东西,那么就可以用二和二以上的。而你用7000hz作为参 数,去设置一,就会报错。三和四,支持软件混音。我觉得default:0表示对第一个声卡软件混音,default表示对整个系统软件混音,由于我没有 多声卡,所以没法试验四的效果。
这里提出两点:
(1)一般为了让所有的程序都可以发音,为使用更多的默认策略,我们选用三和四,这样少一些控制权,多一些方便。
(2)对不同的层次的设备,相同的函数,结果可能是不一样的。
比如,设置Hardware Parameters里的period和buffer size,这个是对硬件的设置,所以,default和default:0这两种设备是不能设置的。
比如,如果直接操作hw:0,0,那么snd_pcm_writei只能写如8的倍数的frame,比如16,24,否则就会剩下一点不写入而退回,而 default,就可以想写多少就写多少,我们也不必要关心里面具体的策略。
2) Hardware Parameters:
说明:之所以叫做Hardware Parameters,是因为alsa这一层API是较为底层的,它允许用户对上面提到的三层结构的audio interface和computer两层都做设置。其中对computer,也就是alsa驱动这一层的设置,叫做Software Parameters,而对audio interface(声卡)的设置叫做Hardware Parameters。(当然,要设置hardware parameters,也肯定是通过alsa驱动来完成,只不过哪些参数是指导硬件的,哪些是指导alsa驱动的,分开设置了)
(1)Sample rate: 不用说了(这些,对于default设备也能设的,上面已经说了)
(2)Sample format: 不用说了
(3)Number of channels: 不用说了
(4)Data access and layout:简单点,就是说,在一个period以内,数据是按照channel1排完了再排channel2呢,还是一个frame一个frame的 来排(frame在alsa里指的是一次采样时间内,两个channel的数据放一块儿就是一个frame)。默认是第二种。
(5)Interrupt interval:中断间隔,就是靠periods决定的,有函数来设置periods,也就是说这个hardware buffer在一次遍历之内,要中断多少次,来通知内核(最终是到alsa驱动)来写入或读走数据。比如buffer是8192个frame大,而 period设为4个frame大,那么比如playback,则每当有4个frame大的hardware buffer空间空出,就会中断,通知内核(alsa驱动)来写入数据。这个是影响实时效果的关键!!!但是,我观察的,我的电脑的默认period就是 4个frame,按16字节,双通道来算的话,也就是16个字节!所以,默认就很实时了!!一般的实时程序已经够用了!!一般不用调整。
(6)Buffer size:就是hardware buffer的大小,如果alsa整套体系主要靠这个来做缓冲,那么这个的大小,将影响缓冲效果,但是一般也不调整。
3) Software Parameters:
(1)snd_pcm_sw_params_set_avail_min (playback_handle, sw_params, 4096)
这个仅用在interrupt-driven模式。这个模式是alsa驱动层的,不是硬件的interrupt。它的意思是,用户使用 snd_pcm_wait()时,这个实际封装的是系统的poll调用,表示用户在等待,那么在等待什么呢?对于playback来讲,就是等待下面的声 卡的hardware buffer里有一定数量的空间,可以放入新的数据了,对于record来讲,就是等待下面声卡新采集的数据达到了一定数量了。这个一定数量,就是用 snd_pcm_sw_params_set_avail_min来设置。单位是frame。实际运作,没读驱动代码,不是很清楚,可能是alsa驱动根 据用户设的这个参数,来设置Hardware Parameters里面的period,也可能是不改变硬件的period,每次硬件中断还是copy到自己的空间,然后数据积累到一定数量再 interrupt应用程序,使之从wait()出来。我不知道,也不必深究。
这种模式的使用,需要用户在snd_pcm_wait()出来以后,调用一个平常的wirtei或readi函数,来写入或读取那个“一定数量”的数据。 如果用户不用interrupt-driven模式,那么这个函数不必使用。
(2)snd_pcm_sw_params_set_start_threshold (playback_handle, sw_params, 0U)
这个函数指导什么时候开启audio interface的AD/DA,就是什么时候启动声卡。
对于playback,假设第三个参数设为320,那么就是说,当用户调用writei,写入的数据,将暂时存在alsa驱动空间里,当这个数据量达到 320帧时,alsa驱动才开始将数据写入hardware buffer,并启动DA转换。对于record,当用户调用readi,这个数据量达到320帧时,alsa驱动才开始启动AD转换,捕捉数据。我一般 把它设为0,我没试过非0,如果是非0, 我想第一次的writei和readi一定得够数量才行,否则设备不启动。
这个对实时效果是需要的,将第三个参数设置为0,保证声卡的立即启动。
(3)what to do about xruns:
xrun指的是,声卡period一过,引发一个中断,告诉alsa驱动,要填入数据,或读走数据,但是,问题在于,alsa的读取和写入操作,好象是必 须用户调用writei和readi才会发生的,它不会去缓存数据!!!,所以如果上层没有用户调用writei和readi,那么就会产生 overrun(录制时,数据都满了,还没被alsa驱动读走)和underrun(需要数据来播放,alsa驱动却不写入数据),统称为xrun。 我对它的理解是,不是一个period引发的中断就叫做xrun,而是当整个hardware buffer都被写满了(record时)或空了(play时),这个时候的中断下的情况才指的是xrun。无所谓了,怎么立解都行,不影响编程:)
这个东西,需要用一些函数来设置,比如snd_pcm_sw_params_set_silence_threshold(),是针对playback 的,就是设置当xxx的情况下,就用silence来写入hardware buffer。至于xxx情况,以及写入多少silence,我都不是很清楚,还有,比如xrun到什么情况下,可以停止这个设备等等函数。这个(3)的 涉及的参数,我都没试过,一般情况下,就用alsa驱动的默认的xrun处理策略吧,等以后出了错误再说,而且例子里也没有提到。
但是,关于xrun,在编程时,最好这样写:
while ((pcmreturn = snd_pcm_writei(pcm_handle, data, frames)) < 0) {
snd_pcm_prepare(pcm_handle);
fprintf(stderr, "<<<<<<<<<<<<<<< Buffer Underrun >>>>>>>>>>>>>>>\n");
}
就是说,如果这次读/写距离上次读/写,时间可能过长,那么这次去读/写的时候,device已经xrun了,在不知道alsa驱动对xrun的默认策略 的情况下,最好调用snd_pcm_prepare()来重新准备好设备,然后再开始下一次读写。我想,prepare()意思,可能相当于复位,不很清 楚。我想,windows下的那套API下的驱动,一定是已经有了一套对xrun的处理策略,用户根本没有接口可以调整它,我想它的策略比如 playback遇到underrun,也就是填入silence罢了。
(4)transfer chunk size:
这个应该是用不上的,我没找到文档里有用这个的。
三. 编码模式:
TODO LIST:以后把下面两点补上:
(1)一般读写模式:
(2)interrupt-driven模式:作者推荐这个模式,我决定用这个模式来做吧。
我推测的原因:它非常清晰的告诉了用户,你需要读取数据或写入数据了! 这样允许用户即时的作出操作:比如现在让用户从wait()出来,用户知道需要比如写入数据了,它可以决定写入真实的数据,或者写如silence,或者 其他。而用一般读写模式,你不会即时知道下层的需求!!所以,相当于你只能在你下一次读/写的时候,判断有没有xrun,其他你什么都做不了:) (并且在实时性要求不高的情况下,设置一个较大的interrupt间隔,真个alsa的效率会高一点呵呵。而且这种interrupt模式,可以使得代 码简单改下就可以用到其他采用interrupt的系统上,作者这米说的)
TODO: 再好好想想这个模式,其实我不确定实际使用上有什么好?
四. 和windows API的比较:
windows audio接口的那些需要加入的buffer,属于应用程序的buffer,windows靠用户添加buffer queue来设置缓冲,而alsa并不提供接口让用户设置应用层的缓冲区,缓冲区的作用,就是减弱或消除数据流或用户操作偶尔过快或过慢造成的影响,所以 alsa是一定要有缓冲区的,我不知道是否,hardware buffer独自承担着alsa里面的buffer角色?
alsa的writei和readi好象可以改成
TODO: 根据实际的编程结果,从效果看看,这样做是否效果也很好。
五. 其他:
对声卡的编程就是对两个设备进行指导的过程, 下面分3点叙述:
1. 关于Mixer编程.
我了解到的声卡,有三个主要部分:
(1)mixer
(2)dsp(ad,da)
(3)buffer
可以认为它们按上面(1)(2)(3)的顺序联接起来, 而buffer和计算机总线间接相连.
目前认为的原理是这样的:
(1)图1表现了一个mixer, 图2是很多个mixer, 但实际上, 声卡里至少有两个mixer, 一个叫做input mixer, 一个叫做output mixer. input mixer接收外来模拟信号, output mixer接收dsp给它的模拟信号. 需要知道的是, mixer的输入和输出都是模拟信号, 输入和输出的线路也叫做混音通道, 我觉得这是物理线路的范畴, 这也就是为什么一个程序调整mixer, 会影响另一个程序的原因, 因为我觉得它调整的是物理参数. 图中蓝色的方框就是可调节增益的地方(总之, 我认为它们是可供软件调节的硬件线路参数)
(2)一个mixer的输出可以作为另一个mixer的输入. 比如考虑这样一种情景, 怎样混合CD和自己的歌声? 我估计就是CD的数字信号, 先到buffer, 经dsp的D/A转换, 输入"输出混音器"的一个混音通道(source line), 再从destination line出来后, 不送往speaker, 而是送往"输入混音器"混音通道, 这样和人声(从"输入混音器"的microphone混音通道进入)一起混音, 再从destination line出来, 送往dsp做A/D转换, 最后送往buffer.
而调节各个source line/destination line的增益, 以及route souce line和destination line就是mixer做的, 也是软件可以调节的.
至于系统mixer面板上的master和pcm的区别, 我想pcm是dsp输出的模拟信号, 也就是outmixer的source line的增益吧, 而master也许是outmixer的destination line的增益吧.这个其实找不到具体的声卡结构图是很难搞清楚的.
2. 关于dsp编程.
这个最好理解为是上述mixer体系中的一条默认的流程.对于用户来讲,dsp设备就是完成基本的播放和录音功能.
而用户当然应该指导dsp完成工作, 就象指导mxier一样. 用户给dsp的指导参数主要是format(frame/s和bits/frame)和channel数, 要不然dsp怎样能做D/A或A/D转换呢.
3. 关于buffer.
涉及到的buffer有三个, 一是声卡的buffer, 二是驱动的kernel buffer, 三是用户指定的user buffer. 数据就是这样一个接一个复制上来的,或一个一个复制下去的, 复制的信号是声卡的interrupt信号. 当buffer满了(读)或buffer空了(写)时, 就会引发中断.
一. alsa展现的三层结构:
(1)audio interface:
audio interface就是声卡,它含有hardware buffer,注意,这个hardware buffer是在声卡里面,不是内存。
(2)computer:
这个指的是计算机的内核和驱动(驱动由alsa提供),当(1)的audio interfacce引发中断,内核会捕捉到,再把处理移交alsa。
(3)application:
这个就是你写的程序,你开辟一个buffer,比如playback,就交给alsa来play。
在上面的框架下,流程如下:
(1)playback:
application开辟一个buffer,填上数据,调用alsa接口,alsa把buffer数据复制到其驱动的空间,再把数据交给 hardware buffer。
(2)record:
同playback,相似的。
二. 细节:
按照上面的流程,其中有许多细节我们可以加以控制,这里仅仅指出应用程序需要关心的:
1) 操作的设备:
在alsa驱动这一层,目前为止,抽象出了4层设备:
一是如hw:0,0,二是plughw:0,0,三是default:0,四是default至于 一是清楚了,二和二以上可以做数据转换,以支持一个动态的范围,比如你要播放7000hz的东西,那么就可以用二和二以上的。而你用7000hz作为参 数,去设置一,就会报错。三和四,支持软件混音。我觉得default:0表示对第一个声卡软件混音,default表示对整个系统软件混音,由于我没有 多声卡,所以没法试验四的效果。
这里提出两点:
(1)一般为了让所有的程序都可以发音,为使用更多的默认策略,我们选用三和四,这样少一些控制权,多一些方便。
(2)对不同的层次的设备,相同的函数,结果可能是不一样的。
比如,设置Hardware Parameters里的period和buffer size,这个是对硬件的设置,所以,default和default:0这两种设备是不能设置的。
比如,如果直接操作hw:0,0,那么snd_pcm_writei只能写如8的倍数的frame,比如16,24,否则就会剩下一点不写入而退回,而 default,就可以想写多少就写多少,我们也不必要关心里面具体的策略。
2) Hardware Parameters:
说明:之所以叫做Hardware Parameters,是因为alsa这一层API是较为底层的,它允许用户对上面提到的三层结构的audio interface和computer两层都做设置。其中对computer,也就是alsa驱动这一层的设置,叫做Software Parameters,而对audio interface(声卡)的设置叫做Hardware Parameters。(当然,要设置hardware parameters,也肯定是通过alsa驱动来完成,只不过哪些参数是指导硬件的,哪些是指导alsa驱动的,分开设置了)
(1)Sample rate: 不用说了(这些,对于default设备也能设的,上面已经说了)
(2)Sample format: 不用说了
(3)Number of channels: 不用说了
(4)Data access and layout:简单点,就是说,在一个period以内,数据是按照channel1排完了再排channel2呢,还是一个frame一个frame的 来排(frame在alsa里指的是一次采样时间内,两个channel的数据放一块儿就是一个frame)。默认是第二种。
(5)Interrupt interval:中断间隔,就是靠periods决定的,有函数来设置periods,也就是说这个hardware buffer在一次遍历之内,要中断多少次,来通知内核(最终是到alsa驱动)来写入或读走数据。比如buffer是8192个frame大,而 period设为4个frame大,那么比如playback,则每当有4个frame大的hardware buffer空间空出,就会中断,通知内核(alsa驱动)来写入数据。这个是影响实时效果的关键!!!但是,我观察的,我的电脑的默认period就是 4个frame,按16字节,双通道来算的话,也就是16个字节!所以,默认就很实时了!!一般的实时程序已经够用了!!一般不用调整。
(6)Buffer size:就是hardware buffer的大小,如果alsa整套体系主要靠这个来做缓冲,那么这个的大小,将影响缓冲效果,但是一般也不调整。
3) Software Parameters:
(1)snd_pcm_sw_params_set_avail_min (playback_handle, sw_params, 4096)
这个仅用在interrupt-driven模式。这个模式是alsa驱动层的,不是硬件的interrupt。它的意思是,用户使用 snd_pcm_wait()时,这个实际封装的是系统的poll调用,表示用户在等待,那么在等待什么呢?对于playback来讲,就是等待下面的声 卡的hardware buffer里有一定数量的空间,可以放入新的数据了,对于record来讲,就是等待下面声卡新采集的数据达到了一定数量了。这个一定数量,就是用 snd_pcm_sw_params_set_avail_min来设置。单位是frame。实际运作,没读驱动代码,不是很清楚,可能是alsa驱动根 据用户设的这个参数,来设置Hardware Parameters里面的period,也可能是不改变硬件的period,每次硬件中断还是copy到自己的空间,然后数据积累到一定数量再 interrupt应用程序,使之从wait()出来。我不知道,也不必深究。
这种模式的使用,需要用户在snd_pcm_wait()出来以后,调用一个平常的wirtei或readi函数,来写入或读取那个“一定数量”的数据。 如果用户不用interrupt-driven模式,那么这个函数不必使用。
(2)snd_pcm_sw_params_set_start_threshold (playback_handle, sw_params, 0U)
这个函数指导什么时候开启audio interface的AD/DA,就是什么时候启动声卡。
对于playback,假设第三个参数设为320,那么就是说,当用户调用writei,写入的数据,将暂时存在alsa驱动空间里,当这个数据量达到 320帧时,alsa驱动才开始将数据写入hardware buffer,并启动DA转换。对于record,当用户调用readi,这个数据量达到320帧时,alsa驱动才开始启动AD转换,捕捉数据。我一般 把它设为0,我没试过非0,如果是非0, 我想第一次的writei和readi一定得够数量才行,否则设备不启动。
这个对实时效果是需要的,将第三个参数设置为0,保证声卡的立即启动。
(3)what to do about xruns:
xrun指的是,声卡period一过,引发一个中断,告诉alsa驱动,要填入数据,或读走数据,但是,问题在于,alsa的读取和写入操作,好象是必 须用户调用writei和readi才会发生的,它不会去缓存数据!!!,所以如果上层没有用户调用writei和readi,那么就会产生 overrun(录制时,数据都满了,还没被alsa驱动读走)和underrun(需要数据来播放,alsa驱动却不写入数据),统称为xrun。 我对它的理解是,不是一个period引发的中断就叫做xrun,而是当整个hardware buffer都被写满了(record时)或空了(play时),这个时候的中断下的情况才指的是xrun。无所谓了,怎么立解都行,不影响编程:)
这个东西,需要用一些函数来设置,比如snd_pcm_sw_params_set_silence_threshold(),是针对playback 的,就是设置当xxx的情况下,就用silence来写入hardware buffer。至于xxx情况,以及写入多少silence,我都不是很清楚,还有,比如xrun到什么情况下,可以停止这个设备等等函数。这个(3)的 涉及的参数,我都没试过,一般情况下,就用alsa驱动的默认的xrun处理策略吧,等以后出了错误再说,而且例子里也没有提到。
但是,关于xrun,在编程时,最好这样写:
while ((pcmreturn = snd_pcm_writei(pcm_handle, data, frames)) < 0) {
snd_pcm_prepare(pcm_handle);
fprintf(stderr, "<<<<<<<<<<<<<<< Buffer Underrun >>>>>>>>>>>>>>>\n");
}
就是说,如果这次读/写距离上次读/写,时间可能过长,那么这次去读/写的时候,device已经xrun了,在不知道alsa驱动对xrun的默认策略 的情况下,最好调用snd_pcm_prepare()来重新准备好设备,然后再开始下一次读写。我想,prepare()意思,可能相当于复位,不很清 楚。我想,windows下的那套API下的驱动,一定是已经有了一套对xrun的处理策略,用户根本没有接口可以调整它,我想它的策略比如 playback遇到underrun,也就是填入silence罢了。
(4)transfer chunk size:
这个应该是用不上的,我没找到文档里有用这个的。
三. 编码模式:
TODO LIST:以后把下面两点补上:
(1)一般读写模式:
(2)interrupt-driven模式:作者推荐这个模式,我决定用这个模式来做吧。
我推测的原因:它非常清晰的告诉了用户,你需要读取数据或写入数据了! 这样允许用户即时的作出操作:比如现在让用户从wait()出来,用户知道需要比如写入数据了,它可以决定写入真实的数据,或者写如silence,或者 其他。而用一般读写模式,你不会即时知道下层的需求!!所以,相当于你只能在你下一次读/写的时候,判断有没有xrun,其他你什么都做不了:) (并且在实时性要求不高的情况下,设置一个较大的interrupt间隔,真个alsa的效率会高一点呵呵。而且这种interrupt模式,可以使得代 码简单改下就可以用到其他采用interrupt的系统上,作者这米说的)
TODO: 再好好想想这个模式,其实我不确定实际使用上有什么好?
四. 和windows API的比较:
windows audio接口的那些需要加入的buffer,属于应用程序的buffer,windows靠用户添加buffer queue来设置缓冲,而alsa并不提供接口让用户设置应用层的缓冲区,缓冲区的作用,就是减弱或消除数据流或用户操作偶尔过快或过慢造成的影响,所以 alsa是一定要有缓冲区的,我不知道是否,hardware buffer独自承担着alsa里面的buffer角色?
alsa的writei和readi好象可以改成
TODO: 根据实际的编程结果,从效果看看,这样做是否效果也很好。
五. 其他:
对声卡的编程就是对两个设备进行指导的过程, 下面分3点叙述:
1. 关于Mixer编程.
我了解到的声卡,有三个主要部分:
(1)mixer
(2)dsp(ad,da)
(3)buffer
可以认为它们按上面(1)(2)(3)的顺序联接起来, 而buffer和计算机总线间接相连.
目前认为的原理是这样的:
(1)图1表现了一个mixer, 图2是很多个mixer, 但实际上, 声卡里至少有两个mixer, 一个叫做input mixer, 一个叫做output mixer. input mixer接收外来模拟信号, output mixer接收dsp给它的模拟信号. 需要知道的是, mixer的输入和输出都是模拟信号, 输入和输出的线路也叫做混音通道, 我觉得这是物理线路的范畴, 这也就是为什么一个程序调整mixer, 会影响另一个程序的原因, 因为我觉得它调整的是物理参数. 图中蓝色的方框就是可调节增益的地方(总之, 我认为它们是可供软件调节的硬件线路参数)
(2)一个mixer的输出可以作为另一个mixer的输入. 比如考虑这样一种情景, 怎样混合CD和自己的歌声? 我估计就是CD的数字信号, 先到buffer, 经dsp的D/A转换, 输入"输出混音器"的一个混音通道(source line), 再从destination line出来后, 不送往speaker, 而是送往"输入混音器"混音通道, 这样和人声(从"输入混音器"的microphone混音通道进入)一起混音, 再从destination line出来, 送往dsp做A/D转换, 最后送往buffer.
而调节各个source line/destination line的增益, 以及route souce line和destination line就是mixer做的, 也是软件可以调节的.
至于系统mixer面板上的master和pcm的区别, 我想pcm是dsp输出的模拟信号, 也就是outmixer的source line的增益吧, 而master也许是outmixer的destination line的增益吧.这个其实找不到具体的声卡结构图是很难搞清楚的.
2. 关于dsp编程.
这个最好理解为是上述mixer体系中的一条默认的流程.对于用户来讲,dsp设备就是完成基本的播放和录音功能.
而用户当然应该指导dsp完成工作, 就象指导mxier一样. 用户给dsp的指导参数主要是format(frame/s和bits/frame)和channel数, 要不然dsp怎样能做D/A或A/D转换呢.
3. 关于buffer.
涉及到的buffer有三个, 一是声卡的buffer, 二是驱动的kernel buffer, 三是用户指定的user buffer. 数据就是这样一个接一个复制上来的,或一个一个复制下去的, 复制的信号是声卡的interrupt信号. 当buffer满了(读)或buffer空了(写)时, 就会引发中断.
最新技术文章: