音频使用/函数

本指南讨论如何使用 Corona audio 库播放声音效果和流媒体音频。

概述

Corona audio 系统让您可以访问高级的 OpenAL 功能。它具有 32 个不同的声道,您可以在这些声道上播放声音效果或流媒体音频文件。该系统为每个声道提供唯一的音量设置,并为所有声道提供主音量级别。

音频系统是一个“尽力而为”的系统。请求的声音将尽快播放,但不能保证它们会在确切的时间开始或结束。如果您正在流式传输歌曲,并且由于 CPU 压力导致缓冲区欠载,音频系统将在可以的情况下继续播放,但歌曲的播放完成时间可能会晚于预期。

请继续阅读以了解音频 API 函数和方法、可接受的文件格式以及性能技巧。

声音 vs. 流媒体

音频系统提供两种加载函数:audio.loadSound()audio.loadStream()。您提供给这两个函数的文件之间没有强制性的区别,但应根据以下一般准则使用适当的方法。

所有 audio API 都可以应用于通过任一方法加载的文件。但是,在某些情况下,差异并非完全透明。

音频格式

Corona 音频系统支持不同的格式,具体取决于环境/平台。

平台 .wav .mp3 .ogg .aac .caf .aif
iOS
Android
macOS
Windows 桌面

请注意,只有在系统安装了 DirectX Media Foundation 的情况下,Windows 桌面才支持 .aac。

格式说明

  • 跨平台 .wav 文件必须是 16 位未压缩的。

  • 对于高度压缩的格式,例如 .mp3.aac (.mp4),后者是更好的选择。.aac 是 MPEG 集团官方指定的 .mp3 的继任者。.mp3 存在各种专利和版税问题,这些问题已于 2017 年到期,但在 .aac 获得批准时,已同意分发无需支付版税。

  • Ogg Vorbis (.ogg) 是免版税和免专利的,但在 iOS 上不受支持。

  • 请注意,某些格式,尤其是高度压缩的有损格式,如 .mp3.aac.ogg,可能会在音频样本的末尾填充/删除样本,并可能破坏“完美循环”的音乐片段。如果您在循环播放中遇到间隙,请尝试使用 .wav 并确保您的起点和终点是干净的。

  • 根据格式(尤其是 .mp3),audio.getDuration() 调用可能会返回不准确的信息,尤其是对于通过 audio.loadStream() 加载的文件。

声道和音量

如概述中所述,有 32 个音频声道可用。每个声音效果或流媒体音轨都必须在不同的声道上播放。如果您没有显式设置要在其上播放音频的声道,Corona 将尝试找到一个空闲声道来播放音频文件。

在某些情况下,为不同目的**保留**某些声道很有用。例如,您可能希望为音乐、语音和声音效果设置不同的音量级别。在这种情况下,您可以保留一些位于范围较低端的声道,并有效地防止在这些声道上自动分配。这是通过 audio.reserveChannels() API 完成的,其中您通过audio.reserveChannels( x )阻止通道 1 到 x。然后,假设您为音乐和语音保留了几个声道,则其余声道可用于自动分配声音效果。

Corona 还提供各种函数来检查声道的状态。这些函数包括 audio.isChannelActive()audio.isChannelPlaying()audio.isChannelPaused()

音量控制

Corona 音频系统具有“主”音量级别和每个声道的音量级别。主音量和各个声道音量都可以通过将 0%-100% 的十进制表示形式传递给 audio.setVolume() API 来控制。请注意,主音量不一定与设备的内部音量相同,但所有音量级别都与该内部音量成比例地缩放。

如果您希望为**所有**声道设置主音量,只需传递一个没有声道规范的音量级别,如下所示

audio.setVolume( 0.5 )  --set the master volume to 50%

或者,要设置特定声道上的音量,请传递一个包含 channel 键的可选表,如下所示

audio.setVolume( 0.5, { channel=1 } )  --set the volume level of channel 1 to 50%

最后,提供以下函数来设置最小和最大音量

  • audio.setMinVolume( volume ) — 将最小音量限制为一个值。低于该值的任何音量都将以最小音量级别播放。

  • audio.setMaxVolume( volume ) — 将最大音量限制为一个值。超过该值的任何音量都将以最大音量级别播放。

注意
  • 主音量按比例控制所有其他声道的音量。如果您将主音量设置为 40%,然后将声道 2 的音量设置为 1.0,则声道 2 将以主音量 (40%) 的 100% 播放。类似地,如果您将主音量设置为 50%,然后将声道 2 的音量设置为 0.2,则声道 2 将以主音量 (50%) 的 20% 播放,有效音量为 10%。

  • 设置音量后,该级别将一直保持到您再次设置它为止。如果您更改声道 4 的音量,即使在该声道上声音播放结束后,该声道仍将保持该音量级别。

  • 如果您正在自动分配声道上播放音频,请记住,这些声道还将保留您声明的特定音量设置。因此,您可能会考虑为需要特定音量级别的音频手动分配声道。

音频句柄

当您使用 audio.loadSound()audio.loadStream() 加载音频文件时,Corona 会返回该音频文件的**句柄**。假设句柄保留在内存中,则可以使用此句柄来引用和播放音频文件。

以下示例显示如何将基本声音文件加载到句柄 soundEffect 中并立即播放它。

local soundEffect = audio.loadSound( "chime.wav" )
audio.play( soundEffect )

请注意,只有在您需要播放、释放、搜索/倒带或检查音频文件的持续时间时才使用音频句柄。音频文件播放后,您无法通过引用其句柄来暂停、停止或控制其音量。相反,您必须引用它正在播放的**声道**,因为同一个文件(句柄)可能在多个声道上播放。

播放音频

声音

如前所述,`audio.loadSound()` 函数将整个声音加载到内存中。它应该用于较短的音频文件,这些文件可能会在整个应用程序中重复使用。虽然您可以将每个声音加载到本地句柄中,但通常最好使用命名的键来组织 Lua 表中的声音

local soundTable = {

    chimeSound = audio.loadSound( "chime.wav" ),
    bellSound = audio.loadSound( "bell.wav" ),
    buzzSound = audio.loadSound( "buzz.aac" ),
    clickSound = audio.loadSound( "click.aac" )
}

然后,有了这种结构,播放声音效果就变得简单了

audio.play( soundTable["chimeSound"] )
重要

通过 `audio.loadSound()` 函数加载的音频文件几乎应该总是在应用程序启动或新关卡/场景开始时**预加载**。在时间紧迫的期间加载声音可能会导致应用程序在系统将音频文件加载到内存时跳过。

流媒体

由于 `audio.loadStream()` 函数在其持续时间内读取一小块音频文件,因此使用此方法加载的音频不需要提前加载。只需加载并播放文件,如下所示

local backgroundMusic = audio.loadStream( "backgroundMusic.wav" )
audio.play( backgroundMusic )

音频事件

音频系统为 `audio.play()` 提供了一个完成事件,可用于执行与声音相关的操作。例如,如果您有一个滴答作响的定时炸弹对象,您可以使用 `onComplete` 回调来触发动画炸弹爆炸和单独的爆炸声。

要侦听声音的完成事件,只需在选项表的 `onComplete` 值中指定侦听器函数即可

local function bombExplode( event )
    --bomb has exploded!
end

local tickSound = audio.loadSound( "tick.wav" )
audio.play( tickSound, { onComplete=bombExplode } )

反过来,当调用侦听器函数时,可以使用以下 `event` 参数

控制音频

除了上面描述的音量控制方法外,Corona 还提供以下音频控制函数

函数 描述
audio.pause() 暂停一个或所有正在播放的频道的音频播放。
audio.resume() 恢复一个或所有已暂停频道的音频播放。
audio.rewind() 将一个或所有活动频道/句柄的音频倒回至起始位置。
audio.seek() 在一个活动频道或指定的音频句柄上跳转到指定的时间位置。
audio.stop() 停止一个或所有频道的音频播放,并清除频道,以便可以再次播放。
audio.stopWithDelay() 在指定的时间延迟后停止一个或所有频道的音频播放。
audio.fade() 在指定的时间内将一个或所有频道的音量淡入或淡出到指定音量。
audio.fadeOut() 在指定的时间内将一个或所有频道的音量淡出到最小音量。

释放音频

如果您已完成使用音频文件,则**必须**释放它们。 这将释放分配给音频文件的内存。 要释放音频文件,请调用 audio.dispose() 函数,并将音频文件的**句柄**作为其唯一参数传递。

audio.dispose( audioHandle )

如果您已将音频句柄组织到一个表中,如播放音频中所示,则可以使用简单的 pairs 循环来释放每个音频文件。

audio.stop()

for s,v in pairs( soundTable ) do
    audio.dispose( soundTable[s] )
    soundTable[s] = nil
end
重要

当您尝试释放音频文件时,该音频**不应**在任何频道上处于活动状态(播放/暂停)。 考虑在 audio.dispose() 之前调用 audio.stop() 以确保频道被释放。 此外,请勿在音频文件被释放且内存被释放后尝试使用该句柄。

性能技巧

播放频率

在项目 config.lua 文件中,您可以指定 audioPlayFrequency 参数。 此优化提示会促使 OpenAL 以特定频率进行混音/播放。

application =
{
    content =
    {
        width = 320,
        height = 480,
        scale = "letterbox",
        audioPlayFrequency = 22050
    },
}

为获得最佳结果,请将此值设置为不高于实际需要的频率。 如果您需要高质量,请将该值设置为 44100。 否则,22050 应该足够了。 请注意,此值只是一个“提示”,底层音频系统可以忽略它,尽管 iOS 似乎始终遵循它。 此外,为了获得最佳性能,请以与此设置相同的频率对所有音频文件进行编码。 例如,如果您将此设置为 22050,则您的音频文件应以 22050 赫兹进行编码。

支持的值为 110252205044100。 其他值未经测试。

单声道 vs. 立体声

单声道声音占用的内存是立体声声音的一半,在移动设备上,除非用户通过耳机收听,否则差异通常可以忽略不计。

线性 PCM

为了最快的加载/解码时间,请使用线性16 位有符号整数小端原始 PCM 样本。 通常,.wav 文件使用此格式,但您可能需要根据音频文件的来源进行确认。