-
求解释这道题编码规则是什么意思?
2017-06-16 12:56:29•加 密 信 息 机 制 的 核 心 是 由 如 下 的 一 些 由 '0' 和 '1' 形 成 的 关 键 字 序 列 组 成 :0,00,01,10,000,001,010,011,100,101,110,0000,0001,. . .,1011,1110,00000, . . . 在这个序列中的第 1 个... -
标签中href=\"javascript:;\"表示什么意思??
2016-03-21 11:09:12"表示什么意思??2014-01-06 10:02小卡布0202 | 分类:JavaScript | 浏览16509次修改密码 有一种说法是:href="javascript:;"会去解析里面的代码,跟当前标签有关的就会执行,.没关的就跳过。是不是这样理解呢?...<a id="jsPswEdit" class="set-item" href="javascript:;">修改密码</a>
有一种说法是:href="javascript:;"会去解析<script></script>里面的代码,跟当前<a>标签有关的就会执行,.没关的就跳过。
是不是这样理解呢?2014-01-06 10:19提问者采纳javascript: 是一个伪协议,其他的伪协议还有 mail: tel: file: 等等。
1<
a
id
=
"jsPswEdit"
class
=
"set-item"
href
=
"javascript:;"
>修改密码</
a
>
javascript:是表示在触发<a>默认动作时,执行一段JavaScript代码,而 javascript:; 表示什么都不执行,这样点击<a>时就没有任何反应。
一般在这种情况下,会给<a>绑定一个事件回调,来执行业务,如:
1234document.getElementById(
'jsPswEdit'
).addEventListener(
'click'
,
function
(e) {
e.preventDefault();
// 当<a>触发click时,处理业务
},
false
);
追问我能不能这样理解:href="javascript:;"就是去掉a标签的默认行为,跟href="javascript:void(0)"是一样的?
回答是一样的
void 是JavaScript 的一个运算符,void(0)就是什么都不做的意思- 提问者评价
谢谢!
-
阅读系列--什么是JVM
2018-12-20 00:37:31将java文件转换成.class文件,JVM将.class文件放在机器上能运行00011上运行 write once on everywhere 真的是一次编译,到处运行吗??? 例如:windows和linux,...JVM的两个方面是至关重要的 (1)机器码翻译...将java文件转换成.class文件,JVM将.class文件放在机器上能运行00011上运行 write once on everywhere
真的是一次编译,到处运行吗???
例如:windows和linux,其实他们的jar或者war包是一样的,但是环境所安装的jdk版本是不一样的,说到底还是定制化的。
理解这句话的意思还是很微妙的
JVM的两个方面是至关重要的
(1)机器码翻译,将我们认识的指令到机器认识的指令间的转换
(2)内存管理
JVM所涉及java方面的内容就是运行时数据区,这个甚至比内存管理还要重要
类被类加载加载。。验证。。解析后,把东西分别放到运行时数据区。然后类的功能就是分别去运行时数据区里取数据,指令。
bu
大类是数据和指令
程序计数器:指向正在执行的线程的指令地址//也可以理解为指令计数器
它不单要指向还要记录
为什么要记录?
线程是在cpu里执行的,cpu是用时间片来执行的,而时间片是抢占式的,也就是说线程为什么是分时间段执行的,
一个线程可能不一定执行完就去执行另一个线程,所以,指令运行到哪,当然需要记录了,这时候就需要用到程序计数器。
虚拟机栈:存储线程运行方法时所需的数据,指令和
注意是方法,运行时数据区包含了类的所有东西,而虚拟机栈则只存方法的相关东西,但有一个地方是例外的,就是常量池,还存了类的一些东西还有全局变量等,下面有讲。
虚拟机栈里有很多的栈帧组成,每个栈帧有什么组成呢,见图
虚拟机栈是每个线程独享的
如果一个方法里面比较单一,就往虚拟机栈里压一个栈帧,如果方法里面调其它方法,则会压多个栈帧
虚拟机栈存局部变量表,八大基本类型和引用类型的地址,地址指向的是堆里面的对象,局部变量表是32位的,也就说地址也最多是32位
动态链接其实就是常量池,常量池不单单存常量,还有字段,类全限定名,方法等描述的东西也存在常量池里
当.do()方法用到的时候,需要获取service接口的实例,而这个实例存放在常量池里面
出口:方法运行结束的时候,需要出栈;正常情况是return,还有异常的情况,
两个方法互相循环调用,栈里有几个栈帧??TODO
本地方法栈是没有实现类,是有c和c++写的,此处不讨论
方法区:类信息,常量(1.7)静态变量,JIT(just in time)
类信息是指类的元信息,介绍类有什么东西
这里存的常量和常量池里面的常量好像有相同的地方???TODO
哪些是线程独享和共享的。
堆:--->>>得先讲内存模型JMM
首先上来肯定是子孙三代:新生代,老年代,永久代(1.8之前)
观察上面的两张图的颜色可以看出,三代分别在哪?新生代,老年代在堆,永久代在方法区,1.8之后永久代是存放在堆外内存
8:1:1 差不多2/8原则
新生代中的s1放不下了放在老年代
GC算法有很多种,
引用计数法不行,因为a->b b->a就没法回收
GCRoot进行可达性分析来判断,
不可达一会被回收吗?不一定,可以用finalize()来挽救变成可达,但是实用性不强
-
虚拟机破解无线密码全套教程
2014-07-13 21:58:33PIN码是干什么的呢?看说明呗。这里我讲一下,为什么要破解PIN码,PIN码破解出来后,我们就可以很方便的啦,一种是直接用PIN码连接路由,这个软件群共享里也有。另一种,用这个镜像破解出PIN码的时候,其实无线密码... -
deadbeef_于关u-boot中的.balignl 16,0xdeadbeef的理解(原创,请勿转载)
2021-02-27 11:01:46最近在分析u-boot的源代码,...下面我一步步来说明:首先要弄明白.balignl的意思,这个其实应该算是一个伪操作符,伪操作符的意思就是机器码里,并没有一个汇编指令与其对应,是编译器来实现其功能的。.balignl是...最近在分析u-boot的源代码,看到这一行:
.balignl 16,0xdeadbeef
不理解了,不知道为什么要这样写,0xdeadbeef,明显是个单词组,写在这里有何意义呢?然后在查阅了众多资料的时候才晃然大悟。
下面我一步步来说明:
首先要弄明白.balignl的意思,这个其实应该算是一个伪操作符,伪操作符的意思就是机器码里,并没有一个汇编指令与其对应,是编译器来实现其功能的。.balignl是.balign的变体,.balign是意思是,在以当前地址开始,地址计数器必须是以第一个参数为整数倍的地址为尾,在前面记录一个字节长度的信息,信息内容为第二个参数。
.balign 8, 0xde
它的意思就是在以当前地址开始,在地址为8的倍数的位置的前面填入一个字节内容为0xde的内容。如果当前地址正好是8的倍数,则没有东西被写入到内存。
那么以此类推,.balignw则表示第二个参数存入的内容长度为一个字长,即16位,所以一般有这样的形式出现:
.balignw 4,0x368d
因为现在填入的内容为16位了,那就存在以下几种情况
1.当前地址没有偏移就满足了以4为倍数的地址
2.当前地址偏移了1个字节就满足了要求
3.当前地址偏移了2个字节就满足了要求
4.当然地址编移了3个字节就满足了要求
当没有偏移的时候,地址中间肯定没有办法填上信息;
当偏移1个字节的时候,地址中间空隙不够,所以填入的数值,是末定义,也就是说,填入的什么值,不清楚;
当偏移为2个字节的时候,地址中间的空隙正好填入手面的数据,所以就填上了;
当偏移为3个字节的时候,地址中间的空隙大于所要填的内容。手册上给的定义是末定义,在我的理解,其实这个未定义,是指这三个偏移的地址整体的内容是末知的。但是其中必定含有要填的2个字节,只是另一个被填充的字节内容不知道而已
所以以此类推,
.balignl,这个指令用来填与一个长字,即内容长度为长字,即4个字节的长度
如果仔细分析一下填入的情况就知道,如果想要0xdeadbeef一定填到当前地址后面某个部分,就一定得是偏移量为16字节才行,这样才能保证在任何情况下,偏移的地址所留的空隙都能填入所要填的内容。
那0xdeadbeef是什么意思呢?经过我查找才发现,类似这样的值很多,像0xabababab,它的作用大概就是为内存做标记,有点儿像个小旗子,插在那里,表示从这个位置往后,就是干什么的内存,这个位置往前,禁止访问。
以上仅仅是我个人的理解,有可能由于记忆的关系有所不一致,请有更了解这块的大侠给予指正
重要更正:
关于.balignl 16,0xdeadbeef这句中的偏移量,我的理解有误,现在特此更正,希望引用了我这篇博客的同学能修正由我带来的错误认识。那些直接拷贝、粘贴而不注明出处的朋友就惨了,他们对这个知识点的理解将永远是错的。
为了跟以前的有个比较,我把更正内容直接写在下面,使用蓝色的字区别开来,而不修改以前错误的说明,这样给那些引用过本博文的朋友以参考。
其实关于.balignl 16,0xdeadbeef这句,功能说明没有错,就是想在某个位置插入0xdeadbeef这个特殊的内存值。错就错在我对这个16的理解上面。16是16个字节,这是没有错的,但是这个16的由来,并不是我所理解的得至少为16个字节,才能在任何情况下保证插入这个特殊的内存值。我在此篇博客的留言中,回答某位网友的提问,举了个pc为0x00000007地址,偏移量某为8字节时,这个时候就不够4字节的内容了,以此推导出的,至少有16个字节才能保证这个特殊的内存值的插入也是完全错误的。
举个反例,如果按给那位网友的解释,那就算有16个字节的偏移量,那如果pc地址为0x0000000F时,也只有一个字符的空间,那这个deadbeef的值还是不够。以此类推,就算这个值为任意一值,按我之前解释的错误逻辑,也都是有不满足的情况的,呵呵。所以我之前的推论有误,特此更正。我现在把16这个值的由来进行说明。
ARM920T处理器核心,支持32位与16位两种指令长度,16位的指令叫thumb指令集,由于我使用的是32位指令集,所以一切都是以32位指令集进行说明。
既然是32位指令集,所以一条指令就占32位,即4字节,所以在调试器中,地址的显示也是4字节一跳的(调试器的截图在这篇博文的评论中有链接),所以pc的值,也是4字节一跳的,并不存在可能pc的值为0x00000007的情况,呵呵。
这个地方填16个偏移量,是因为
.globl _start //不占内存
_start: b start_code //占4字节内存
ldr pc, _undefined_instruction //占4字节内存
ldr pc, _software_interrupt //占4字节内存
ldr pc, _prefetch_abort //占4字节内存
ldr pc, _data_abort //占4字节内存
ldr pc, _not_used //占4字节内存
ldr pc, _irq //占4字节内存
ldr pc, _fiq //占4字节内存
占了4x8=32字节内存。
_undefined_instruction: .word undefined_instruction //占4字节内存
_software_interrupt: .word software_interrupt //占4字节内存
_prefetch_abort: .word prefetch_abort //占4字节内存
_data_abort: .word data_abort //占4字节内存
_not_used: .word not_used //占4字节内存
_irq: .word irq //占4字节内存
_fiq: .word fiq //占4字节内存
占了4x7=32字节内存。
所以在这个.balignl 16,0xdeadbeef指令之前,一共占了4x15=60个字节的内存,所以本代码的作者当时就简单的在15这个数上,加了个1,即16,把当前指针往后移到地址为64的位置,然后在前面插上了0xdeadbeef这个特殊的值。
我不知道这个地方是作者一个错误,歪打正着呢,还是怎么回子事,其实这个偏移的值还有好多种情况。如果说最小的值的话,那么也可以写成.balignl 8,0xdeadbeef,也可以达到同样的目的。因为60不是8的倍数,但是64是8的倍数,如果写8,也正好插到64前面,也即60这个内存起始地址。如果更大一点儿的呢,那么填32也可以达到同样的效果,即.balignl 32,0xdeadbeef,道理同上。当然,不能为4,因为pc值在任何时候,都是4的倍数,只要不为0就为4的倍数,呵呵,这个值不行,如果用了这个值,0xdeadbeef永远也插不进去,呵呵。
好了,这个问题现在我算是说明白了,给大家带来的误导再次说声抱歉,呵呵,昨天才发现这个问题,今天赶紧在博客里做个更正。这篇文章被引用得还不少,造成的影响很坏,再次道歉,呵呵。
另:也希望引用我文章的人,至少也说个出处吧,虽然好多都是没有经过我同意转载的,但连出处也不说,就太不尊重本人的劳动成果了。如果不是引用的方式,那将来像类似这样的错误,就可能得不到更正,自己这个知识点将永远是错的,唉唉
-
Android SoundPool 的使用以及原理分析
2013-07-25 13:18:52按照官方的意思大多数情况下是给游戏开发用的,比如一个游戏10关,它能在游戏开始前一次加载所有10关的背景音乐,这其中也包括了解码操作,当真正要播放时候就直接把音频数据写设配了,大家自己琢磨下到底有什么好处...好吧,我们今天来聊聊SoundPool这东西。
据说这个东西是冰激凌(Android4.0)里才引入的一个新东西。按照官方的意思大多数情况下是给游戏开发用的,比如一个游戏10关,它能在游戏开始前一次加载所有10关的背景音乐,这其中也包括了解码操作,当真正要播放时候就直接把音频数据写设备了,大家自己琢磨下到底有什么好处,我自己觉得除了预先解码之外真的没发现特别大的好处。
这里我就以拍照音的播放来做切入点一步步分析它是怎么工作的。(当然你自己也可以搜一下SoundPool来找入口点。但话说这玩样儿确实被google造了出来,但是用的却很少。)
这里我说明下,正常拍照音的声音其实不是用SoundPool的,这里只是照相功能另一部分功能BurstCapture,即连拍。因为连拍时播放时间的间隔很短,所以必须省略解码过程。
代码路径:
packages/apps/Camera/src/com/android/camera/SoundClips.java publicSoundPoolPlayer(Context context) { mContext =context; mSoundPool= new SoundPool(NUM_SOUND_STREAMS, audioType, 0); mSoundIDs= new int[SOUND_RES.length]; mSoundIDReady = new boolean[SOUND_RES.length]; for (int i= 0; i < SOUND_RES.length; i++) { mSoundIDs[i] = mSoundPool.load(mContext, SOUND_RES[i], 1); mSoundIDReady[i] = false; } }
首先我们会看到初始化了一个新的SoundPool类,参数为stream数量,音频类型等。
我们先看看SoundPool构造函数做了什么。
frameworks/base/media/java/android/media/SoundPool.java public SoundPool(int maxStreams, int streamType, int srcQuality) { if(native_setup(new WeakReference(this), maxStreams, streamType, srcQuality) !=0) { throw newRuntimeException("Native setup failed"); } frameworks/base/media/jni/soundpool/android_media_SoundPool.cpp static jint android_media_SoundPool_native_setup(JNIEnv *env, jobjectthiz, jobject weakRef, jint maxChannels, jint streamType, jint srcQuality) { ALOGV("android_media_SoundPool_native_setup"); SoundPool *ap = newSoundPool(maxChannels, (audio_stream_type_t) streamType, srcQuality);
这里我直接写上了native层的函数,我们再继续看这里SoundPool类的构造函数,传入的参数名称也容易辨别了(频道数,stream类型,质量)
frameworks/av/media/libmedia/SoundPool.cpp SoundPool::SoundPool(int maxChannels, audio_stream_type_tstreamType, int srcQuality) { // check limits mMaxChannels =maxChannels; if (mMaxChannels< 1) { mMaxChannels =1; } else if(mMaxChannels > 32) { mMaxChannels =32; } mChannelPool = newSoundChannel[mMaxChannels]; for (int i = 0; i< mMaxChannels; ++i) { mChannelPool[i].init(this); mChannels.push_back(&mChannelPool[i]); } // start decodethread startThreads(); }
从函数定义我们可以发现,这里的频道数必须在1到32个之间,即最大支持32路播放。然后又初始化了mChannelPool数组,即多个SoundChannel类,并且对每一个调用其init函数,放入mChannels指针列表。我们这里继续看下SoundChannel类的init函数。
void SoundChannel::init(SoundPool* soundPool) { mSoundPool =soundPool; }
这个函数只是简单的保存了调用类SoundPool的指针到mSoundPool变量里。回到刚才的函数,我们继续看startThreads函数。
bool SoundPool::startThreads() { createThreadEtc(beginThread, this, "SoundPool"); if (mDecodeThread== NULL) mDecodeThread= new SoundPoolThread(this); returnmDecodeThread != NULL; }
从这个函数可以看出每个SoundPool都有一个decode线程(只能有一个),好了,这里只是新建的一个SoundPoolThread,然后就返回了。这样SoundPool在native的初始化就完成了,我们有了一些SoundChannel,还有一个decode线程。
我们回到刚才的SoundClips里
mSoundIDs= new int[SOUND_RES.length]; mSoundIDReady = new boolean[SOUND_RES.length]; for (int i= 0; i < SOUND_RES.length; i++) { mSoundIDs[i] =mSoundPool.load(mContext, SOUND_RES[i], 1); mSoundIDReady[i] = false; }
这里会初始化一个int数组,然后调用SoundPool的load函数,我们在看看它做了神马。
在SoundPool中这个load函数有多个重载对应不同的资源类型,比如内置资源就是用资源id,而外置资源就是用文件路径,或者asset文件路径,或者文件打开后的FD标识。当然这些个玩样儿最终还是调用到native层,我们这里就看下打开文件的那个函数。
/** * Load the soundfrom the specified path. * * @param path thepath to the audio file * @param prioritythe priority of the sound. Currently has no effect. Use * a value of 1 for futurecompatibility. * @return a soundID. This value can be used to play or unload the sound. */ public intload(String path, int priority) { // passnetwork streams to player if(path.startsWith("http:")) return_load(path, priority); // try localpath int id = 0; try { File f =new File(path); ParcelFileDescriptor fd = ParcelFileDescriptor.open(f,ParcelFileDescriptor.MODE_READ_ONLY); if (fd !=null) { id =_load(fd.getFileDescriptor(), 0, f.length(), priority); fd.close(); } } catch(java.io.IOException e) { Log.e(TAG,"error loading " + path); } return id; } static int android_media_SoundPool_load_FD(JNIEnv *env, jobject thiz,jobject fileDescriptor, jlong offset,jlong length, jint priority) { ALOGV("android_media_SoundPool_load_FD"); SoundPool *ap =MusterSoundPool(env, thiz); if (ap == NULL)return 0; returnap->load(jniGetFDFromFileDescriptor(env, fileDescriptor), int64_t(offset), int64_t(length), int(priority)); }
函数MusterSoundPool用来获取之前新建的SoundPool类,并且调用其load函数
int SoundPool::load(const char* path, int priority) { sp<Sample>sample = new Sample(++mNextSampleID, path); mSamples.add(sample->sampleID(), sample); doLoad(sample); returnsample->sampleID(); }
在load函数中,首先会新建一个名叫sample的类且跟上两个参数,sample的id号,以及加载文件路径,并且将这个新建的类保存在mapping列表mSamples中,即每一个需加载的文件有一个Sample类对应,索引号即为其id号。接下来就是加载的过程了,具体看doLoad函数。
void SoundPool::doLoad(sp<Sample>& sample) { sample->startLoad(); mDecodeThread->loadSample(sample->sampleID()); } void startLoad() { mState = LOADING; } void SoundPoolThread::loadSample(int sampleID) { write(SoundPoolMsg(SoundPoolMsg::LOAD_SAMPLE, sampleID)); } void SoundPoolThread::write(SoundPoolMsg msg) { Mutex::Autolocklock(&mLock); while(mMsgQueue.size() >= maxMessages) { mCondition.wait(mLock); } // if thread isquitting, don't add to queue if (mRunning) { mMsgQueue.push(msg); mCondition.signal(); } } int SoundPoolThread::run() { ALOGV("run"); for (;;) { SoundPoolMsgmsg = read(); switch(msg.mMessageType) { caseSoundPoolMsg::LOAD_SAMPLE: doLoadSample(msg.mData); break; default: ALOGW("run: Unrecognized message %d\n", msg.mMessageType); break; } } }
首先调用Sample类中的startLoad函数来设置当前sample的状态,这里即LOADING状态。这里我省略了些不是非常重要的代码。在loadSample函数中会将当前的sampleid号打包成一个消息并调用write函数写到消息队列中,如果消息队列满了会稍微等等,如果还没满则会加入队列并通知取消息的线程(这里的线程就是我们之前创建的mDecodeThread,如果大家不记得了,可以搜索下之前的内容)。在这个线程中会读取消息的类型,这里为LOAD_SAMPLE,并调用doLoadSample函数,参数即为sampleid号。我们看下这个函数。
void SoundPoolThread::doLoadSample(int sampleID) { sp <Sample>sample = mSoundPool->findSample(sampleID); status_t status =-1; if (sample != 0) { status = sample->doLoad(); } mSoundPool->notify(SoundPoolEvent(SoundPoolEvent::SAMPLE_LOADED,sampleID, status)); }
函数会先根据id号找到相对应的Sample类并调用doLoad函数(说实话,我觉得这里转了一大圈才开始加载数据只是为了调度来平衡磁盘操作,大家也可以自己琢磨下。)
status_t Sample::doLoad() { ALOGV("Startdecode"); if (mUrl) { p =MediaPlayer::decode(mUrl, &sampleRate, &numChannels, &format); } else { p =MediaPlayer::decode(mFd, mOffset, mLength, &sampleRate, &numChannels,&format); ALOGV("close(%d)", mFd); ::close(mFd); mFd = -1; } if (p == 0) { ALOGE("Unable to load sample: %s", mUrl); return -1; } ALOGV("pointer = %p, size = %u, sampleRate = %u, numChannels =%d", p->pointer(), p->size(), sampleRate, numChannels); if (sampleRate> kMaxSampleRate) { ALOGE("Sample rate (%u) out of range", sampleRate); return - 1; } if ((numChannels< 1) || (numChannels > 2)) { ALOGE("Sample channel count (%d) out of range", numChannels); return - 1; } mData = p; mSize = p->size();
这个函数有点长,但是也没做很多事。首先他会进行decode即解码文件成波形文件,可以理解为wav文件纯数据。(预加载以备播放,SoundPool的好处之一)。这里还做了些判断,比如采样率不能大于48000,声道数不能小于1或者大于2,然后就保存了这个加载数据。加载完数据后还会通过调用notify函数来调用之前设置的回调函数通知上层,具体实现用户可以自己实现。
我们好像已经走得很远了,让我们回到相机SoundClip里。这时我们已经完成了load函数并且有一份加载好的数据,并且有一个对应其Sample的id号。然后我们就要开始播放了。
mSoundPool.play(mSoundIDs[index], 1f, 1f, 0, 0, 1f);
其实播放也很简单,只需要知道Sampleid号,然后直接调用SoundPool的play函数即可。我们看下play做了些什么。
frameworks/base/media/java/android/media/SoundPool.java public native final int play(int soundID, float leftVolume,float rightVolume, intpriority, int loop, float rate); frameworks/base/media/jni/soundpool/android_media_SoundPool.cpp android_media_SoundPool_play(JNIEnv *env, jobject thiz, jintsampleID, jfloatleftVolume, jfloat rightVolume, jint priority, jint loop, jfloat rate) { SoundPool *ap =MusterSoundPool(env, thiz); return ap->play(sampleID,leftVolume, rightVolume, priority, loop, rate); frameworks/av/media/libmedia/SoundPool.cpp int SoundPool::play(int sampleID, float leftVolume, floatrightVolume, int priority,int loop, float rate) { // is sampleready? sample =findSample(sampleID); // allocate achannel channel =allocateChannel_l(priority); // no channelallocated - return 0 if (!channel) { ALOGV("Nochannel allocated"); return 0; } channelID =++mNextChannelID; channel->play(sample, channelID, leftVolume, rightVolume, priority,loop, rate); return channelID; }
我们一路从java层调用到c++,中间的native只是打打酱油,我们具体看c++层的play函数。这里我们会看到一个新东西Channel,这个东西你可以暂时理解只是为播放用的,我同事形象的比作他是大炮,sample就是炮弹,想听声音就打炮。
首先函数会找炮弹,即用sampleid号找sample,找到了sample就意味着我们有了音频数据。之后会调用allocateChannel_l并有个权限的参数。SoundChannel* SoundPool::allocateChannel_l(int priority) { List<SoundChannel*>::iterator iter; SoundChannel* channel= NULL; // allocate achannel if(!mChannels.empty()) { iter =mChannels.begin(); if (priority>= (*iter)->priority()) { channel =*iter; mChannels.erase(iter); ALOGV("Allocated active channel"); } } // update priorityand put it back in the list if (channel) { channel->setPriority(priority); for (iter =mChannels.begin(); iter != mChannels.end(); ++iter) { if(priority < (*iter)->priority()) { break; } } mChannels.insert(iter, channel); } return channel; }
这个函数其实就是一个算法用来更新channel的,它首先会判断我们传进来的权限值,并找到最接近的那个channel(小于等于)作为返回值,然后再找到最接近的另一个channel(大于等于),并插在它后面。这里的mChannels是我们创建SoundPool时一并初始化的列表(可以理解为我们有几门打炮)。找到了适合弹药口径的打炮,就需要开炮了。让我们看下channel的play函数。// call with sound pool lock held void SoundChannel::play(const sp<Sample>& sample,int nextChannelID, float leftVolume, floatrightVolume, int priority, int loop, float rate) { AudioTrack*oldTrack; AudioTrack*newTrack; status_t status; { // scope for thelock Mutex::Autolock lock(&mLock); // if not idle,this voice is being stolen if (mState !=IDLE) { ALOGV("channel %d stolen - event queued for channel %d",channelID(), nextChannelID); mNextEvent.set(sample, nextChannelID, leftVolume, rightVolume, priority,loop, rate); stop_l(); return; } intnumChannels = sample->numChannels(); // do notcreate a new audio track if current track is compatible with sample parameters #ifdef USE_SHARED_MEM_BUFFER newTrack = newAudioTrack(streamType, sampleRate, sample->format(), channels, sample->getIMemory(), AUDIO_OUTPUT_FLAG_NONE, callback,userData); #else newTrack = newAudioTrack(streamType, sampleRate, sample->format(), channels, frameCount,AUDIO_OUTPUT_FLAG_FAST, callback, userData, bufferFrames); #endif oldTrack =mAudioTrack; mAudioTrack->start(); } exit: ALOGV("deleteoldTrack %p", oldTrack); delete oldTrack; if (status !=NO_ERROR) { deletenewTrack; mAudioTrack =NULL; } }
这个函数也有点长,我们一步步分析。首先会判断这个channel是不是正在播放(这种情况存在于当所有channel都在开炮的时候,我们之后再讨论这个),假设这时候channel还没工作,然后我们就会新建一个AudioTrack(有一定安卓audio开发经验的同学都知道他是播放声音最原始的单元,有了它就能给设配写数据等)。这里我们可以看到两端初始化代码并且使用宏来隔开,区别在于一个是共享内存的AudioTrack,另一个使用FastTrack,即低延时的机制。(这个东西我也不是特别明白,似乎是4.1新加的,大家可以百度学习学习),这里还给了一个回调函数参数用来从文件搬数据到AudioTrack,这个回调函数具体我们就不分析的,简单来说就是AudioTrack定时调用回调函数申请数据,回调函数只管读取数据返回给它。在初始化完AudioTrack之后就调用start函数让它工作了,这个时候声音也就出来了。最后这个play函数还不忘把老的AudioTrack给清空防止内存侧漏,哈哈。
话说,到这里SoundPool基本播放声音的分析就结束了,但是还有个特殊情况我们得分析下。即如果所有的channel都在播放,但应用程序又想播放新的数据会怎么处理呢?这时候就会用到之前play函数里那个判断,即是否当前状态不为空闲状态。// if not idle,this voice is being stolen if (mState !=IDLE) { ALOGV("channel %d stolen - event queued for channel %d",channelID(), nextChannelID); mNextEvent.set(sample, nextChannelID, leftVolume, rightVolume, priority,loop, rate); stop_l(); return; }
如果当前channel已经在播放,又有一个新的sample想被播放,则会进入这个条件语句。这里会先设置一个新的类SoundEvent,即mNextEvent,这个类会保存想要播放的sample以及channel的id号等一些参数。然后停止当前播放的channel,这一过程称之为偷窃。这里channel的id号不是当前播放的channel的id号,正确的讲每次播放新sample时的channel的id号都不一样。然后我们看下stop_l函数。// call with lock held and sound pool lock held void SoundChannel::stop_l() { if (doStop_l()) { mSoundPool->done_l(this); } } // call with lock held bool SoundChannel::doStop_l() { if (mState !=IDLE) { setVolume_l(0,0); mAudioTrack->stop(); return true; } return false; }
这里会先调用doStop_l来停止当前的播放。然后继续调用SoundPool的done_l函数。void SoundPool::done_l(SoundChannel* channel) { ALOGV("done_l(%d)", channel->channelID()); // if"stolen", play next event if(channel->nextChannelID() != 0) { ALOGV("add to restart list"); addToRestartList(channel); }
在这个函数中,会先判断是否这个channel是否被偷窃了,即调用nextChannelID函数来判断。int nextChannelID() { return mNextEvent.channelID(); }
我们可以看到其实就是判断是否mNextEvent是否有id值保存,之前我们已经保存了一份,所以这里的判断就满足了。然后会把当前的channel放到restart列表中。void SoundPool::addToRestartList(SoundChannel* channel) { mRestart.push_back(channel); mCondition.signal(); }
添加完channel到mRestart之后,又会向mCondition发出信号,另一个线程肯定已经等了很久了。这个线程我之前没有提到,当新建SoundPool的时候,会调用createThreadEtc函数来创建一条SoundPool自己的线程并执行,这时候它就知道要开干了。int SoundPool::run() { while (!mQuit) { mCondition.wait(mRestartLock); while(!mRestart.empty()) { SoundChannel* channel; List<SoundChannel*>::iterator iter = mRestart.begin(); channel =*iter; mRestart.erase(iter); if (channel != 0) { Mutex::Autolock lock(&mLock); channel->nextEvent(); }
这个线程里会从mRestart列表中一个个拿出channel并调用nextEvent函数。void SoundChannel::nextEvent() { nextChannelID= mNextEvent.channelID(); if(nextChannelID == 0) { ALOGV("stolen channel has noevent"); return; } sample =mNextEvent.sample(); play(sample,nextChannelID, leftVolume, rightVolume, priority, loop, rate); }
这个函数首先会读取channel的mNextEvent,我们在之前已经设置过一些参数了,包括了sample已经channel的id号等。然后直接调用了之前我们分析的play函数,这样作为play函数来说这就算是一个新的sample需要播放了,然后么,声音就又出来了。
当然什么事都有始有终,有播放就有停止暂停神马的,这些功能实现比较简单,我这里就简单说下,所有sample在播放时都有个channel会返回给应用层,所以停止暂时基本上就是调用那个channel的stop和pause函数,接着对AudioTrack进行函数调用操作。但是如果是被偷窃的channel呢?这个也不必担心,在停止暂停的时候如果当前的channel的id不存在,它会继续找它的mNextEvent里channel的id号,最终还是能达到目的的。
至此,对SoundPool的介绍就到此结束了,最后附上用XMind画的示意图。
-
Modbus协议理解方法
2011-06-30 01:14:33这些日子使用Modbus协议做设计,有关协议的理解,大家理解不一致,我在此做个笔记,和大家分享,说说读线圈,写线圈,读保持寄存器,读输入寄存器是什么意思。 读线圈:就是说读开关量输出的状态,看看开关量输出... -
索然无味!Kotlin开发从入门到上天,一篇文章就搞定了!(万字长文)
2021-01-26 17:50:45标题党? 看起来可能有点标题党的意思,但我知道,不这样,你们可能看不到这篇。 关于Kotlin相关记录,如果有意查看我的github,其超10w字(其中8w是代码吗,哈哈)。...简洁,因为这是提高程序员工作效率的关. -
噩梦吧
2008-05-01 13:53:00昨天中午的时候上去耍了一会游戏,实在没意思,就把游戏给关了,弄毕业设计去了,等到晚上工会活动的时候,我的帐号登陆不上去了,我的第一反映是号被封了难道,因为名字比较哪个 叫玖惩他爹,根本没想到是被盗了,... -
java面试宝典2011整理有答案
2011-11-09 13:36:0618、Spring 的依赖注入是什么意思? 给一个 Bean 的 message 属性, 字符串类型, 注入值为 "Hello" 的 XML 配置文件该怎么写? 125 19、Jdo是什么? 125 20、什么是spring的IOC AOP 126 21、STRUTS的工作流程! 126 22、... -
Linux操作系统基础教程
2013-04-08 21:34:26什么是Linux?.................................................................................................................2 二.安装Linux的好处?..................................................... -
Nginx安装包
2017-09-01 14:17:19再看看我们redis服务器,可以看到里面已经添加了一些数据,有些session是spring自己添加的,具体什么意思我也不是很清楚,但我们找一下,就可以找到我们刚刚添加的session。其中右上角的TIL是我们session剩余有效... -
php高级开发教程说明
2008-11-27 11:39:22什么意思?)在此处讨论中并不重要。重要的是:形式分析的结果越好,逻辑分析就越容易、 越快、越好。 逻辑分析能补偿形式分析中失去的信息,但仅仅是在一个有限的程度上补偿。 你也许能读懂前面的这个句子,但要... -
Oracle Database 9i10g11g编程艺术:深入数据库体系结构(第2版)--详细书签版
2013-02-03 11:42:53并利用具体的例子来全面介绍每个特性,不仅讨论了各个特性是什么,还说明了它是如何工作的,如何使用这个特性来开发软件,以及有关的常见陷阱。 本书面向所有oracle 数据库应用开发人员和dba。 作译者 作者 ... -
ctf总结.md
2019-05-14 14:22:02本句的意思是将Index.php的源码以base64加密后的形式读出来 南邮:文件包含 4.暴力破解:burpsuite暴力破解教程 bugku:输入密码查看flag 5. 文件上传(感觉必考) 例:只能传jpg等不能传php 查看网页源码,很清楚看到... -
[雪鱼探店] 宁夏羊脖子 - https://youtu.be/_QkEdBPHYUA
2020-12-09 06:12:22什么都不蘸的这种羊肉是什么样的味道 49 00:04:31,680 --> 00:04:34,880 先来一个羊排吧 50 00:04:34,880 --> 00:04:38,680 试一试 手抓版 51 00:04:38,680 --> 00:04:43,040 羊排是直接用手一抽 ... -
华为编程开发规范与案例
2008-09-04 16:44:56在一次测试中,并没有记得做了什么操作,发现HONET系统的主机复位了,之后,系统又工作正常了。由于没有打开后台的跟踪窗口,当时查了半天没有眉目。过了半天,现象又出现了,而且这次是主机在反复复位,系统根本... -
电脑高手必备 Windows系统35招实用技巧
2009-06-11 14:42:10代表什么意思? (1)安全模式:选用安全模式启动Windows XP时,系统只使用一些最基本 的文件和驱动程序启动。进入安全模式是诊断故障的一个重要步骤。如果安 全模式启动后无法确定问题,或者根本无法启动安全... -
[老饭骨] 二伯炸鸡- https://youtu.be/9jGg8lsGlfY
2020-12-09 06:15:28翻译主要意思即可,不要超过 100 个字符) 【国宴大师•二伯炸鸡】国宴级别的神仙炸鸡怎么做?炸鸡爱好者千万别错过!结尾有大爷专属答疑时间哦 |老饭骨 简介 小友们好,现在许多人... -
【美食作家王刚】苦瓜酿肉 - https://youtu.be/5bSatykuR08
2020-12-09 03:57:12翻译主要意思即可,不要超过 100 个字符) 厨师长分享:夏日去火好菜“苦瓜酿肉”的家常做法,四伯第一次尝试感觉很满意 简介 大家好,最近四川天气很热并且四伯吃了太多漆二娃做的... -
Qt Creator 的安装和hello world 程序+其他程序的编写--不是一般的好
2011-01-28 17:02:08种方法实现了信号和槽函数的关联,第一个按钮我们直接在设计器中实现其关 联;第二个按钮我们自己写了槽函数语句,其实图形的设计与直接写代码效果是 一样的。 这个程序里我们实现了两类窗口打开的方式,一个是自身...