android音频基础与SoundPool

android音频处理

音频基础概念

基础知识可以参考以下几篇文章:

JSSRC的使用
  1. github下载代码:
  2. 因为源代码中用到了java的一个audio库,这个需要自己在gradle中键入文件依赖,但是直接放在app或者其他模块中会有一个潜在问题:as2.2以后版本需要jdk1.8才可以,会遇到兼容错误,解决方法是将这个JSSRC作为一个java的lib库引入,也就是作为一个独立mudule,该module特点是gradle中需要如下配置:
    apply plugin: 'java'

    buildscript {
        tasks.withType(JavaCompile) {
        //        sourceCompatibility = JavaVersion.VERSION_1_7
        //        targetCompatibility = JavaVersion.VERSION_1_7
    sourceCompatibility = "1.7"
    targetCompatibility = "1.7"

        }
    }

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile files('/Library/Java/JavaVirtualMachines/jdk1.8.0_101.jdk/Contents/Home/jre/lib/rt.jar')
    }

然后app模块依赖这个module就可以了。

SoundPool

1. 官方Api翻译

The SoundPool class manages and plays audio resources for applications.

SoundPool为app管理和播放音频。

A SoundPool is a collection of samples that can be loaded into memory from a resource inside the APK or from a file in the file system. The SoundPool library uses the MediaPlayer service to decode the audio into a raw 16-bit PCM mono or stereo stream. This allows applications to ship with compressed streams without having to suffer the CPU load and latency of decompressing during playback.

一个SoundPool对象是一系列样本的集合,这里的样本来源于apk包内的资源或者是文件系统里的文件,可以被加载到内存中。SoundPool的库利用MediaPlayer服务将音频解码成一个16-bit单声道或者立体声的PCM流,这使得app可以直接用压缩数据流同时摆脱CPU加载数据的压力和播放时重解压的延迟。

In addition to low-latency playback, SoundPool can also manage the number of audio streams being rendered at once. When the SoundPool object is constructed, the maxStreams parameter sets the maximum number of streams that can be played at a time from this single SoundPool. SoundPool tracks the number of active streams. If the maximum number of streams is exceeded, SoundPool will automatically stop a previously playing stream based first on priority and then by age within that priority. Limiting the maximum number of streams helps to cap CPU loading and reducing the likelihood that audio mixing will impact visuals or UI performance.

除了播放时的低延迟,SoundPool还可以管理同一时间下渲染的音频流的数量。创建SoundPool对象时,参数maxStreams可以设置同一时刻这个单一SoundPool同事播放的流的最大数目。SoundPool跟踪活动流的最大数目。如果超过了流的最大设置数目,SoundPool将会自动停止前一个正在播放的音频流。前一个到底是哪一个的标准首先是优先级然后是此优先级的时间先后。限制最大音频流数目有助于cpu加载的性能,降低音频混音对视觉或者UI性能上影响的概率。

Sounds can be looped by setting a non-zero loop value. A value of -1 causes the sound to loop forever. In this case, the application must explicitly call the stop() function to stop the sound. Any other non-zero value will cause the sound to repeat the specified number of times, e.g. a value of 3 causes the sound to play a total of 4 times.

声音可以通过设置一个非零的loop值来控制循环播放次数。-1代表一直循环播放,此时,app必须显式的调用stop()函数停止声音播放。任意的非零值都会使声音重复播放指定的次数。比如,设置为3则声音一共会播放4次,3指的是循环次数。

The playback rate can also be changed. A playback rate of 1.0 causes the sound to play at its original frequency (resampled, if necessary, to the hardware output frequency). A playback rate of 2.0 causes the sound to play at twice its original frequency, and a playback rate of 0.5 causes it to play at half its original frequency. The playback rate range is 0.5 to 2.0.

播放速度也可以控制。1.0的播放速度标识声音按照原来的频率播放(当然,如果有必要的话,会重采样为硬件支持的输出频率)。2.0的速度会按照原始速度的两倍播放,0.5的速度则会按照原始速度的一半播放。速度的可调范围为[0.5, 2.0]。

Priority runs low to high, i.e. higher numbers are higher priority. Priority is used when a call to play() would cause the number of active streams to exceed the value established by the maxStreams parameter when the SoundPool was created. In this case, the stream allocator will stop the lowest priority stream. If there are multiple streams with the same low priority, it will choose the oldest stream to stop. In the case where the priority of the new stream is lower than all the active streams, the new sound will not play and the play() function will return a streamID of zero.

优先级由低到高分布,也就是越大的数表示越高的优先级。创建SoundPool时会指定一个最大音频流的参数,当play()方法的调用导致当前活动音频流的数目超过这个最大参数的时候,优先级就会被应用。此时,流分配器会停止最低优先级的流。如果最低优先级的流有多个,那就停止最早创建的那个(最老的那个)。如果最新的流的优先级比所有活动的流的优先级都低,那么这个最新的音频流将不会被播放,同时paly()函数将会返回一个值为0的streamId。

Let's examine a typical use case: A game consists of several levels of play. For each level, there is a set of unique sounds that are used only by that level. In this case, the game logic should create a new SoundPool object when the first level is loaded. The level data itself might contain the list of sounds to be used by this level. The loading logic iterates through the list of sounds calling the appropriate SoundPool.load() function. This should typically be done early in the process to allow time for decompressing the audio to raw PCM format before they are needed for playback.

来看一个典型的使用场景:一个游戏含有很多层级播放场景。对每个级别,都有一个唯一的声音集合只供该级别使用。此时,在第一个场景加载的时候,游戏逻辑应该创建一个新的SoundPool对象。级别数据本身可以包含它自己要使用的声音列表。加载逻辑通过声音列表调用合适的SoundPool.load()函数迭代进行。一般这些load在程序中会先做以保证在播放时有时间将音频解压成原始PCM格式的数据。

Once the sounds are loaded and play has started, the application can trigger sounds by calling SoundPool.play(). Playing streams can be paused or resumed, and the application can also alter the pitch by adjusting the playback rate in real-time for doppler or synthesis effects.

一旦声音的加载和游戏开始了,app可以通过调用SoundPool.play()触发声音播放。播放的流可以暂停或者恢复,app也可以通过实时调节播放速度来修改音高以达到多普勒或者合成效果。

Note that since streams can be stopped due to resource constraints, the streamID is a reference to a particular instance of a stream. If the stream is stopped to allow a higher priority stream to play, the stream is no longer be valid. However, the application is allowed to call methods on the streamID without error. This may help simplify program logic since the application need not concern itself with the stream lifecycle.

注意,声音流可能由于资源约束问题而停止,streamID是指向一个流的特殊的引用。如果为了播放高优先级的流而停止了一个流,那么这个流将不再有效。但是,app可以调用这个streamID上的方法而不会报错。这样的可以简化程序的逻辑,app不必自己去管理流的生命周期。

In our example, when the player has completed the level, the game logic should call SoundPool.release() to release all the native resources in use and then set the SoundPool reference to null. If the player starts another level, a new SoundPool is created, sounds are loaded, and play resumes.

在我们的例子中,当玩家已将完成了本级别,游戏逻辑应该调用SoundPool.release()去释放使用中所有的native资源并将SoundPool的引用指向null。如果玩家开始了另一个级别,将会创建一个新的SoundPool,声音被加载,并恢复播放。

2. 相关博客

SoundPool适合短且对反应速度比较高的情况(游戏音效或按键声等),文件大小一般控制在几十K到几百K,最好不超过1M,可以与MediaPlayer同时播放,SoundPool也可以同时播放多个声音;最终编解码实现与MediaPlayer相同;
MediaPlayer只能同时播放一个声音,加载文件有一定的时间,适合文件比较大,响应时间要是不是非常高的场景;

二、Android中使用声音池SoundPool。

对于对SoundPool还不了解的童鞋,我先附上一段网上摘要下来的资料,因为之前查的资料忘了附上原作者了,如有需要联系我加上吧,先引用一下吧。

在Android开发中我们经常使用MediaPlayer来播放音频文件,但是MediaPlayer存在一些不足,例如:资源占用量较高、延迟时间较长、不支持多个音频 同时播放等。这些缺点决定了MediaPlayer在某些场合的使用情况不会很理想,例如在对时间精准度要求相对较高的游戏开发中。

下面说说SoundPool的特点:

  1. SoundPool载入音乐文件使用了独立的线程,不会阻塞UI主线程的操作。但是这里如果音效文件过大没有载入完成,我们调用play方法时可能产生严 重的后果,这里Android SDK提供了一个SoundPool.OnLoadCompleteListener类来帮助我们了解媒体文件是否载入完成,我们重载 onLoadComplete(SoundPool soundPool, int sampleId, int status) 方法即可获得。

  2. 从上面的onLoadComplete方法可以看出该类有很多参数,比如类似id,是的SoundPool在load时可以处理多个媒体一次初始化并放入内存中,这里效率比MediaPlayer高了很多。

  3. SoundPool类支持同时播放多个音效,这对于游戏来说是十分必要的,而MediaPlayer类是同步执行的只能一个文件一个文件的播放。

最后贴上SoundPool的简单使用方法:

  SoundPool soundPool = new SoundPool(5,AudioManager.STREAM_SYSTEM,0);
    soundPool.load(mContext,R.raw.raws,1);
  soundPool.play(1,1, 1, 0, 0, 1);

如上就是一个简单的播放了,其中各个参数的意义大家可以自行上网查找,具体说说上述步骤的不足之处。
刚开始我使用的时候一直出不来声音,后来使用Debug一步一步调试,却能走通,声音也出来了,想想二者运行唯一不同的是调试的时候我很缓慢的走,难道是加载需要时间?后来研究得到确认,确实是load()这个方法需要加载时间,不能立即播放,也许你也遇到这问题,解决办法是开启一个线程,在线程中sleep一秒,然后在进行play方法的调用,完美解决。当然,如果你的声音播放不是需要立即执行,而是需要点击某个按钮之类的,那就不会出现这问题了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容