原文链接:https://developer.chrome.com/native-client/devguide/coding/audio
注意:已针对ChromeOS以外的平台公布了此处所述技术的弃用。
请访问我们的 迁移指南 了解详情。
音频
本节介绍如何使用Pepper音频API播放音频流。Pepper音频API提供了一种播放Native Client模块生成的音频样本流的低级方法。API通常如下工作:Native Client模块创建表示音频流的音频资源,并告诉浏览器开始或停止播放音频资源。每次需要从音频流播放数据时,浏览器都会调用Native Client模块中的函数来填充带有音频样本的缓冲区。
本节中的代码示例描述了一个简单的Native Client模块,该模块使用频率为440 Hz的正弦波生成音频样本。模块加载到浏览器后立即开始播放音频样本。有关稍微复杂的示例,请参阅audio
示例(SDK目录中的源代码examples/api/audio
),该示例允许用户指定正弦波的频率,并单击按钮以启动和停止音频播放。
参考信息
有关Pepper audio API的参考信息,请参阅以下文档:
关于Pepper音频API
Pepper音频API允许Native Client模块在浏览器中播放音频流。要播放音频流,模块会生成音频样本并将其写入缓冲区。浏览器从缓冲区中读取音频样本,并使用客户端计算机上的音频设备播放它们。
这种机制很简单但很低级。如果要在Web应用程序中播放纯文本声音文件,可能需要考虑更高级别的替代方法,例如使用HTML <audio>
标记,JavaScript或新的Web Audio API。
如果您想在Web应用程序中进行音频处理,Pepper音频API是播放音频数据的好选择。例如,如果要将声音效果应用于声音,合成自己的声音或执行任何其他类型的CPU密集型音频采样处理,则可以使用音频API。另一个可能的用例是游戏应用程序:您可以使用游戏库来处理音频数据,然后只需使用音频API来输出处理过的数据。
Pepper音频API易于使用:
- 您的模块将创建音频配置资源和音频资源。
- 您的模块实现了一个回调函数,该函数用数据填充音频缓冲区。
- 您的模块调用音频资源的StartPlayback和StopPlayback方法(例如,在发生某些事件时)。
- 只要需要播放音频数据,浏览器就会调用您的回调函数。您的回调函数可以通过多种方式生成音频数据 - 例如,它可以生成新数据,或者可以将预先混合的数据复制到音频缓冲区中。
此基本交互如下所示,并在后面的章节中详细介绍。
数字音频概念
在使用Pepper音频API之前,了解一些对于如何记录和播放数字音频至关重要的概念是有帮助的:
采样率
每秒输入声源采样次数; 相应地,每秒播放的样本数
位深度
用于表示样本的位数
渠道
每个采样间隔记录的输入源数量; 相应地,同时播放的输出数量(通常使用不同的扬声器)
用于记录声波的采样率和比特深度越高,声波的再现就越准确,因为它将被更频繁地采样并使用更高级别的量化来存储。常见采样率包括44,100 Hz(每张44,100个样本,CD上使用的采样率)和48,000 Hz(DVD和数字音频磁带上使用的采样率)。公共比特深度是每个样本16比特,并且共同数量的信道是2(立体声声音的左和右声道)。
Pepper音频API目前允许Native Client模块使用以下配置播放音频流:
- 采样率:44,100 Hz或48,000 Hz
- 位深度:16
- 频道:2(立体声)
设置模块
下面的代码示例描述了一个简单的Native Client模块,该模块使用频率为440 Hz的正弦波生成音频样本。模块加载到浏览器后立即开始播放音频样本。
Native Client模块是通过实现pp::Module
和pp::Instance
类的子类来设置的 ,正常情况下。
class SineSynthInstance : public pp::Instance {
public:
explicit SineSynthInstance(PP_Instance instance);
virtual ~SineSynthInstance() {}
// Called by the browser once the NaCl module is loaded and ready to
// initialize. Creates a Pepper audio context and initializes it. Returns
// true on success. Returning false causes the NaCl module to be deleted
// and no other functions to be called.
virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]);
private:
// Function called by the browser when it needs more audio samples.
static void SineWaveCallback(void* samples,
uint32_t buffer_size,
void* data);
// Audio resource.
pp::Audio audio_;
...
};
class SineSynthModule : public pp::Module {
public:
SineSynthModule() : pp::Module() {}
~SineSynthModule() {}
// Create and return a SineSynthInstance object.
virtual pp::Instance* CreateInstance(PP_Instance instance) {
return new SineSynthInstance(instance);
}
};
创建音频配置资源
资源
在模块可以播放音频流之前,它必须创建两个资源:音频配置资源和音频资源。资源是浏览器提供给模块实例的对象的句柄。音频资源是表示音频流状态的对象,包括流是暂停还是正在播放,以及当流缓冲区中的样本用完时要调用的回调函数。音频配置资源是存储音频资源的配置数据的对象,包括音频样本的采样频率,以及当浏览器调用它时回调函数必须提供的样本数。
样本帧数
在创建音频配置资源之前,模块应调用 RecommendSampleFrameCount
以从浏览器获取样本帧计数。样本帧计数是每次浏览器调用回调函数时回调函数必须为每个通道提供的样本数。例如,如果立体声音频流的样本帧计数为4096,则回调函数必须提供8192个样本(左声道为4096,右声道为4096)。
模块可以请求特定的样本帧计数,但是浏览器可以根据客户端设备的能力返回不同的样本帧计数。目前,RecommendSampleFrameCount
只需绑定检查所请求的样本帧计数(请参阅include/ppapi/c/ppb_audio_config.h
最小和最大样本帧计数,当前为64和32768)。将来,RecommendSampleFrameCount
可以执行更复杂的计算,尤其是在客户端设备存在固有缓冲区大小的情况下。
选择音频流的样本帧计数涉及延迟和CPU使用之间的权衡。如果您希望模块具有较短的音频延迟,以便能够快速更改音频流中正在播放的内容,则应该请求少量的样本帧数。这在游戏应用中可能是有用的,例如,声音必须经常响应游戏动作而改变。但是,较小的样本帧计数会导致较高的CPU使用率,因为浏览器必须经常调用回调函数来重新填充音频缓冲区。相反,大样本帧计数会导致更高的延迟但CPU使用率更低。如果您的模块将播放长而不间断的音频片段,则应该请求大量样本帧数。
支持的音频配置
模块获得样本帧计数后,可以创建音频配置资源。目前,胡椒音频API支持与所示的配置设置的音频流之上。C ++模块可以通过实例化pp::AudioConfig
对象来创建配置资源 。检查audio_config.h
支持的最新配置。
bool SineSynthInstance::Init(uint32_t argc,
const char* argn[],
const char* argv[]) {
// Ask the browser/device for an appropriate sample frame count size.
sample_frame_count_ =
pp::AudioConfig::RecommendSampleFrameCount(PP_AUDIOSAMPLERATE_44100,
kSampleFrameCount);
// Create an audio configuration resource.
pp::AudioConfig audio_config = pp::AudioConfig(this,
PP_AUDIOSAMPLERATE_44100,
sample_frame_count_);
// Create an audio resource.
audio_ = pp::Audio(this,
audio_config,
SineWaveCallback,
this);
// Start playback when the module instance is initialized.
return audio_.StartPlayback();
}
创建音频资源
模块创建音频配置资源后,可以创建音频资源。为此,它实例化一个pp::Audio
对象,传入指向模块实例的指针,音频配置资源,回调函数和指向用户数据的指针(回调函数中使用的数据)。请参阅上面的示例。
实现回调函数
每当需要更多样本播放时,浏览器就会调用与音频资源相关的回调函数。回调函数可以生成新样本(例如,通过应用声音效果),或者将预先混合的样本复制到音频缓冲器中。下面的示例通过计算正弦波的值来生成新样本。
传递给回调函数的最后一个参数是函数可用于处理样本的通用用户数据。在下面的示例中,用户数据是指向模块实例的指针,其中包含成员变量 sample_frame_count_
(从浏览器获取的样本帧计数)和 theta_
(用于计算前一个回调中的正弦值的最后一个角度;这使得该函数通过从该角度加上一个小的三角形开始产生平滑的正弦波。
class SineSynthInstance : public pp::Instance {
public:
...
private:
static void SineWaveCallback(void* samples,
uint32_t buffer_size,
void* data) {
// The user data in this example is a pointer to the module instance.
SineSynthInstance* sine_synth_instance =
reinterpret_cast<SineSynthInstance*>(data);
// Delta by which to increase theta_ for each sample.
const double delta = kTwoPi * kFrequency / PP_AUDIOSAMPLERATE_44100;
// Amount by which to scale up the computed sine value.
const int16_t max_int16 = std::numeric_limits<int16_t>::max();
int16_t* buff = reinterpret_cast<int16_t*>(samples);
// Make sure we can't write outside the buffer.
assert(buffer_size >= (sizeof(*buff) * kChannels *
sine_synth_instance->sample_frame_count_));
for (size_t sample_i = 0;
sample_i < sine_synth_instance->sample_frame_count_;
++sample_i, sine_synth_instance->theta_ += delta) {
// Keep theta_ from going beyond 2*Pi.
if (sine_synth_instance->theta_ > kTwoPi) {
sine_synth_instance->theta_ -= kTwoPi;
}
// Compute the sine value for the current theta_, scale it up,
// and write it into the buffer once for each channel.
double sin_value(std::sin(sine_synth_instance->theta_));
int16_t scaled_value = static_cast<int16_t>(sin_value * max_int16);
for (size_t channel = 0; channel < kChannels; ++channel) {
*buff++ = scaled_value;
}
}
}
...
};
应用程序线程和实时要求
回调函数在后台应用程序线程中运行。这样即使应用程序忙于执行其他操作,也可以继续进行音频处理。如果主应用程序线程和回调线程访问相同的数据,您可能会尝试使用锁来控制对该数据的访问。但是,您应该避免在回调线程中使用锁,因为尝试获取锁可能会导致线程被换出,从而导致音频丢失。
通常,您必须仔细编程回调线程,因为Pepper音频API是一个非常低级别的API,需要满足硬实时要求。如果回调线程花费太多时间进行处理,则很容易错过实时截止时间,从而导致音频丢失。回调线程可能错过最后期限的一种方法是花费太多时间进行计算。回调线程可能错过最后期限的另一种方法是执行一个交换回调线程的函数调用。不幸的是,这样的函数调用几乎包括所有C运行时(CRT)库调用和Pepper API调用。因此,回调线程应该避免调用malloc,gettimeofday,mutex,condvars,critical sections等等; 任何此类调用都可能尝试获取锁定并交换回调线程,这对于音频回放来说将是灾难性的。类似地,回调线程应该避免Pepper API调用。由于线程交换导致的音频丢失非常罕见,很难追踪和调试 - 最好避免首先进行系统/ Pepper调用。简而言之,音频(回调)线程应该使用“无锁”技术并避免进行CRT库调用。
要注意的另一个问题是StartPlayback
函数(下面讨论)是异步RPC; 即,它不会阻止。这意味着在调用之后可能不会立即调用回调函数 StartPlayback
。如果将回调线程与另一个线程同步以使音频流与应用程序中的另一个操作同时开始播放很重要,则必须手动处理此类同步。
开始和停止播放
要启动和停止音频播放,模块只会对JavaScript消息做出反应。
const char* const kPlaySoundId = "playSound";
const char* const kStopSoundId = "stopSound";
void SineSynthInstance::HandleMessage(const pp::Var& var_message) {
if (!var_message.is_string()) {
return;
}
std::string message = var_message.AsString();
if (message == kPlaySoundId) {
audio_.StartPlayback();
} else if (message == kStopSoundId) {
audio_.StopPlayback();
} else if (...) {
...
}
}
CC-By 3.0许可下提供的内容