1. 音频延迟:游戏体验的隐形杀手
在移植一款Qt开发的音乐节奏游戏(Rhythm Game)到鸿蒙平台时,我们遭遇了滑铁卢。
在Desktop端,使用QMediaPlayer或QSoundEffect播放按键音效(SE)几乎是实时的。但在鸿蒙真机上,我们明显感觉到了"手眼不一"------手指按下去,画面有了反馈,但声音慢了大概200ms。
对于普通App这可能无所谓,但对于音游,200ms的延迟是致命的。
2. 架构对比:Qt Multimedia vs 鸿蒙 Native Audio
为什么会有延迟?我们需要看下音频数据的流向。
使用 QMediaPlayer:
Qt Multimedia为了跨平台,通常维护着较大的内部缓冲区,且在不同平台上可能使用较高层的API(如Java层的AudioTrack),这在经过JNI桥接时会引入额外开销。
使用鸿蒙 Native AudioRenderer:
Raw PCM Direct Write Fast Path Qt App Native Audio Wrapper OH_AudioRenderer Speaker
鸿蒙提供了基于C的OH_AudioRenderer接口(OpenHarmony Audio),它类似于Android的Oboe或OpenSL ES,专为低延迟设计。
3. 实战:封装 OH_AudioRenderer
为了解决延迟,我们决定放弃QSoundEffect,自己封装一个基于鸿蒙Native API的音频播放器。
核心代码:AudioPlayer.h
cpp
#include <ohaudio/native_audiostreambuilder.h>
#include <ohaudio/native_audiorenderer.h>
#include <QObject>
class HarmonyAudioPlayer : public QObject {
Q_OBJECT
public:
explicit HarmonyAudioPlayer(QObject *parent = nullptr);
~HarmonyAudioPlayer();
bool loadWav(const QString &filePath);
void play();
private:
OH_AudioRenderer *m_renderer = nullptr;
QByteArray m_pcmData;
// 音频参数
int m_sampleRate = 44100;
int m_channelCount = 2;
};
核心代码:AudioPlayer.cpp
cpp
// 初始化渲染器
bool HarmonyAudioPlayer::initRenderer() {
OH_AudioStreamBuilder *builder;
OH_AudioStreamBuilder_Create(&builder, AUDIOSTREAM_TYPE_RENDERER);
// 设置低延迟模式
OH_AudioStreamBuilder_SetLatencyMode(builder, AUDIOSTREAM_LATENCY_MODE_FAST);
OH_AudioStreamBuilder_SetSamplingRate(builder, m_sampleRate);
OH_AudioStreamBuilder_SetChannelCount(builder, m_channelCount);
OH_AudioStreamBuilder_SetSampleFormat(builder, AUDIOSTREAM_SAMPLE_S16LE);
OH_AudioStreamBuilder_SetEncodingType(builder, AUDIOSTREAM_ENCODING_TYPE_RAW);
// 创建Renderer实例
OH_AudioStreamBuilder_GenerateRenderer(builder, &m_renderer);
OH_AudioStreamBuilder_Destroy(builder); // Builder用完即毁
return m_renderer != nullptr;
}
// 播放逻辑
void HarmonyAudioPlayer::play() {
if (!m_renderer) return;
// 1. 启动流
OH_AudioRenderer_Start(m_renderer);
// 2. 写入数据(简单的一次性写入示例)
// 实际生产中应该使用回调模式(Callback)来持续填充Buffer
int32_t ret = OH_AudioRenderer_Write(
m_renderer,
reinterpret_cast<uint8_t*>(m_pcmData.data()),
m_pcmData.size()
);
if (ret < 0) {
qWarning() << "Write audio failed:" << ret;
}
}
4. 杂音问题:采样率不匹配
在实现上述代码后,延迟确实降低了,但我们听到了明显的"沙沙"声和变调。
Bug分析:
我们在loadWav中读取的WAV文件可能是48000Hz的,但我们在Builder中硬编码了44100Hz。
OpenHarmony的音频系统在某些版本下,如果写入数据的采样率与配置不一致,不会自动重采样(Resample),而是直接按错误的速率播放,导致变调。
修复:
必须解析WAV头(WAV Header),动态设置Builder的参数。
cpp
struct WavHeader {
char riff[4];
uint32_t size;
char wave[4];
// ...
uint32_t sampleRate;
// ...
};
// 在loadWav中
WavHeader header = readHeader(file);
m_sampleRate = header.sampleRate; // 动态获取
// ...
OH_AudioStreamBuilder_SetSamplingRate(builder, m_sampleRate);
5. 权限配置
别忘了,播放音频需要权限。虽然播放通常是普通权限,但在某些场景(如后台播放)需要配置。
在module.json5中:
json
"requestPermissions": [
{
"name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
"reason": "$string:background_audio_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
如果只是前台音效,通常不需要特殊权限。
6. 总结
对于Qt应用在鸿蒙上的音频优化:
- 一般应用 :使用
QMediaPlayer即可,方便快捷。 - 低延迟需求(游戏/乐器) :必须绕过Qt Multimedia,直接使用
OH_AudioRenderer。 - 模式选择 :务必设置
AUDIOSTREAM_LATENCY_MODE_FAST。 - 格式匹配:确保源文件的采样率/位深与Renderer配置完全一致,避免手动重采样的麻烦。
通过这层Native封装,我们将按键音效的延迟从200ms压缩到了40ms以内(通过高速相机实测),达到了音游的判定标准。