HarmonyOS 鸿蒙Next 按照下方代码播放pcm,会有颤音,哪位大佬帮忙看下
HarmonyOS 鸿蒙Next 按照下方代码播放pcm,会有颤音,哪位大佬帮忙看下
按照下方代码,播放pcm,会有颤音,那位大佬帮忙看下,代码:
import { audio } from ‘@kit.AudioKit’;
import { PlayerStateCallback } from ‘./PlayerCallback’;
import { BusinessError } from ‘@kit.BasicServicesKit’;
import { fileIo } from ‘@kit.CoreFileKit’;
const TAG = ‘AudioRenderer’;
class Options {
offset?: number;
length?: number;
}
export class AudioRenderPlayer {
private playerCallback: PlayerStateCallback | undefined = undefined
private bufferSize: number = 0;
private renderModel: audio.AudioRenderer | undefined = undefined;
private audioStreamInfo: audio.AudioStreamInfo = {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_16000,
// 采样率
channels: audio.AudioChannel.CHANNEL_1,
// 通道
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
// 采样格式
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
// 编码格式
}
private audioRendererInfo: audio.AudioRendererInfo = {
usage: audio.StreamUsage.STREAM_USAGE_VOICE_MESSAGE,
// 音频流使用类型
rendererFlags: 0
// 音频渲染器标志
}
private audioRendererOptions: audio.AudioRendererOptions =
{ streamInfo: this.audioStreamInfo, rendererInfo: this.audioRendererInfo }
private path = getContext()
.cacheDir;
//确保该路径下存在该资源
private file?: fileIo.File
// 初始化,创建实例,设置监听事件
init(filePath: string, callback?: PlayerStateCallback) {
this.file = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY);
this.playerCallback = callback
let writeDataCallback = (buffer: ArrayBuffer) => {
let options: Options = { offset: this.bufferSize, length: buffer.byteLength }
let num: number = 0;
if (this.file !== undefined) {
num = fileIo.readSync(this.file?.fd, buffer, options);
}
// -读本地PCM文件播放
if (num <= 0) {
this.playerCallback?.(PlayState.STOP)
this.stop();
}
this.bufferSize += num;
}
audio.createAudioRenderer(this.audioRendererOptions, (err,
renderer) => {
// 创建AudioRenderer实例
if (!err) {
console.info(${TAG}: creating AudioRenderer success
);
this.renderModel = renderer;
if (this.renderModel !== undefined) {
(this.renderModel as audio.AudioRenderer).on(‘writeData’, writeDataCallback);
this.start();
}
} else {
console.info(${TAG}: creating AudioRenderer failed, error: ${err.message}
);
}
});
}
// 开始一次音频渲染
start() {
if (this.renderModel !== undefined) {
let stateGroup = [audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED];
if (stateGroup.indexOf((this.renderModel as audio.AudioRenderer).state.valueOf()) ===
-1) {
// 当且仅当状态为prepared、paused和stopped之一时才能启动渲染
console.error(TAG + ‘start failed’);
return;
}
// 启动渲染
(this.renderModel as audio.AudioRenderer).start((err: BusinessError) => {
if (err) {
console.error(‘Renderer start failed.’);
} else {
this.playerCallback?.(PlayState.START)
console.info(‘Renderer start success.’);
}
});
}
}
// 暂停渲染
pause() {
if (this.renderModel !== undefined) {
// 只有渲染器状态为running的时候才能暂停
if ((this.renderModel as audio.AudioRenderer).state.valueOf() !== audio.AudioState.STATE_RUNNING) {
console.info(‘Renderer is not running’);
return;
}
// 暂停渲染
(this.renderModel as audio.AudioRenderer).pause((err: BusinessError) => {
if (err) {
console.error(‘Renderer pause failed.’);
} else {
console.info(‘Renderer pause success.’);
}
});
}
}
// 停止渲染
stop() {
if (this.renderModel !== undefined) {
// 只有渲染器状态为running或paused的时候才可以停止
if ((this.renderModel as audio.AudioRenderer).state.valueOf() !== audio.AudioState.STATE_RUNNING &&
(this.renderModel as audio.AudioRenderer).state.valueOf() !== audio.AudioState.STATE_PAUSED) {
console.info(‘Renderer is not running or paused.’);
return;
}
// 停止渲染
(this.renderModel as audio.AudioRenderer).stop((err: BusinessError) => {
if (err) {
console.error(‘Renderer stop failed.’);
} else {
if (this.file !== undefined) {
fileIo.close(this.file);
this.file = undefined
}
this.bufferSize = 0;
console.info(‘Renderer stop success.’);
}
});
}
}
// 销毁实例,释放资源
release() {
if (this.renderModel !== undefined) {
// 渲染器状态不是released状态,才能release
if (this.renderModel.state.valueOf() === audio.AudioState.STATE_RELEASED) {
console.info(‘Renderer already released’);
return;
}
// 释放资源
(this.renderModel as audio.AudioRenderer).release((err: BusinessError) => {
if (err) {
console.error(‘Renderer release failed.’);
} else {
console.info(‘Renderer release success.’);
}
});
}
}
}
2 回复
我按照以下demo修改,声音正常,您参考:
import { audio } from '[@kit](/user/kit).AudioKit';
import { BusinessError } from '[@kit](/user/kit).BasicServicesKit';
import { fileIo } from '[@kit](/user/kit).CoreFileKit';
import fs, { ReadOptions } from '[@ohos](/user/ohos).file.fs';
import { common } from '[@kit](/user/kit).AbilityKit';
import { util } from '[@kit](/user/kit).ArkTS';
const TAG = '[AudioRendererPage]';
[@Entry](/user/Entry)
[@Component](/user/Component)
export struct AudioRendererPage {
[@State](/user/State) audioFiles: Array<[string, string]> = [];
[@State](/user/State) currentIndex: number = 0;
[@State](/user/State) isOpacity: boolean = false;
[@State](/user/State) isPlay: boolean = false;
[@State](/user/State) currentTime: number = 0;
[@State](/user/State) durationTime: number = 0;
[@State](/user/State) durationStringTime: string = '00:00';
[@State](/user/State) currentStringTime: string = '00:00';
[@State](/user/State) flag: boolean = false;
isPlaying: boolean = false; // 标识符,表示是否正在渲染
isDucked: boolean = false; // 标识符,表示是否被降低音量
private audioRenderer: audio.AudioRenderer | undefined = undefined;
[@State](/user/State) fileNames: string[] = [];
private context = getContext(this) as common.UIAbilityContext;
private fileDir: string = this.context.filesDir; // /data/storage/el2/base/haps/entry/files
aboutToAppear(): void {
this.initFiles();
let audioStreamInfo: audio.AudioStreamInfo = {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_16000, // 采样率
channels: audio.AudioChannel.CHANNEL_1, // 通道
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式
}
// 音频流信息
let audioRendererInfo: audio.AudioRendererInfo = {
usage: audio.StreamUsage.STREAM_USAGE_VOICE_MESSAGE, // 音频流使用类型
rendererFlags: 0 // 音频渲染器标志
}
// 音频流是否可以被其他应用录制
let audioPrivacyType: audio.AudioPrivacyType = audio.AudioPrivacyType.PRIVACY_TYPE_PUBLIC;
let audioRendererOptions: audio.AudioRendererOptions = {
streamInfo: audioStreamInfo,
rendererInfo: audioRendererInfo,
privacyType: audioPrivacyType // 非必填
}
audio.createAudioRenderer(audioRendererOptions).then((data) => {
this.audioRenderer = data;
this.setCallBack(this.audioRenderer);
console.info(TAG, 'AudioRenderer Created : Success : Stream Type: SUCCESS');
}).catch((err: BusinessError) => {
console.error(TAG, `AudioRenderer Created : ERROR : ${JSON.stringify(err)}`);
});
}
aboutToDisappear(): void {
this.release();
}
build() {
NavDestination() {
Column({ space: 20 }) {
Scroll() {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Start }) {
Column() {
Text(`正在播放:${this.audioFiles[this.currentIndex][0]}`)
.fontSize(20)
.fontWeight(500)
.fontColor(Color.Gray)
}
.width('100%')
.padding(20)
.borderRadius(20)
.backgroundColor(Color.White)
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start }) {
Text('音频列表')
.fontSize(24)
.fontWeight(500)
.fontColor(Color.Gray)
.align(Alignment.Center);
List({ space: 5 }) {
ForEach(this.audioFiles, (item: [string, number], index: number) => {
ListItem() {
Text(item[0])
.backgroundColor('#f1f3f5')
.padding({
left: 20,
right: 20,
top: 8,
bottom: 8
})
.width('100%')
.borderRadius(15)
.onClick(() => {
this.currentIndex = index
})
}
})
}
}
.width('100%')
.padding(20)
.margin({ top: 20, bottom: 20 })
.borderRadius(20)
GridRow({
columns: 2,
gutter: { x: 5, y: 10 },
direction: GridRowDirection.Row
}) {
GridCol({ span: 1, offset: 0, order: 0 }) {
Button('播 放')
.onClick(() => {
this.startPlayingProcess()
})
}
GridCol({ span: 1, offset: 0, order: 0 }) {
Button('暂停')
.onClick(() => {
this.pausePlayingProcess()
})
}
}
.margin({ bottom: 20 })
.borderRadius(20)
.backgroundColor(Color.White)
}
}
.scrollBar(BarState.Off)
.margin({ bottom: 20 })
}
.width('100%')
}
}
async startPlayingProcess() {
if (this.audioRenderer) {
let stateGroup = [audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED];
if (stateGroup.indexOf(this.audioRenderer.state.valueOf()) === -1) { // 当且仅当状态为prepared、paused和stopped之一时才能启动渲染
console.error(TAG, 'start failed');
return;
}
// 启动渲染
await this.audioRenderer.start().then(() => {
console.info(TAG, 'Renderer started');
}).catch((err: BusinessError) => {
console.error(TAG, `ERROR: ${JSON.stringify(err)}`);
});
const bufferSize = await this.audioRenderer.getBufferSize();
let filePath = this.audioFiles[this.currentIndex][1];
let file = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY);
let stat = fileIo.statSync(filePath);
let buf = new ArrayBuffer(bufferSize);
let len =
stat.size % bufferSize === 0 ? Math.floor(stat.size / bufferSize) : Math.floor(stat.size / bufferSize + 1);
class Options {
offset: number = 0;
length: number = 0
}
// 循环读取
for (let i = 0; i < len; i++) {
let options: Options = {
offset: i * bufferSize,
length: bufferSize
};
let readsize = fileIo.readSync(file.fd, buf, options);
console.info(TAG, 'read size: ' + readsize);
// buf是要写入缓冲区的音频数据,在调用AudioRenderer.write()方法前可以进行音频数据的预处理,实现个性化的音频播放功能,AudioRenderer会读出写入缓冲区的音频数据进行渲染
let writeSize: number = await this.audioRenderer.write(buf);
console.info(TAG, 'writeSize: ' + writeSize);
if (this.audioRenderer.state.valueOf() === audio.AudioState.STATE_RELEASED) { // 如果渲染器状态为released,关闭资源
fileIo.close(file);
}
if (this.audioRenderer.state.valueOf() === audio.AudioState.STATE_RUNNING) {
if (i === len - 1) { // 如果音频文件已经被读取完,停止渲染
await this.audioRenderer.stop();
}
}
}
}
}
// 暂停渲染
async pausePlayingProcess() {
if (this.audioRenderer) {
// 只有渲染器状态为running的时候才能暂停
if (this.audioRenderer.state.valueOf() !== audio.AudioState.STATE_RUNNING) {
console.info(TAG, 'Renderer is not running');
return;
}
await this.audioRenderer.pause(); // 暂停渲染
if (this.audioRenderer.state.valueOf() === audio.AudioState.STATE_PAUSED) {
console.info(TAG, 'Renderer is paused.');
} else {
console.error(TAG, 'Pausing renderer failed.');
}
}
}
// 销毁实例,释放资源
async release() {
if (this.audioRenderer !== undefined) {
// 渲染器状态不是released状态,才能release
if (this.audioRenderer.state.valueOf() === audio.AudioState.STATE_RELEASED) {
console.info(TAG, 'Renderer already released');
return;
}
await this.audioRenderer.release(); // 释放资源
if (this.audioRenderer.state.valueOf() === audio.AudioState.STATE_RELEASED) {
console.info(TAG, 'Renderer released');
} else {
console.error(TAG, 'Renderer release failed.');
}
}
}
initFiles() {
let fileList: string[] = getContext().resourceManager.getRawFileListSync('audio');
fileList.forEach((fileStr: string) => {
let filePath = this.resourcesFile2SandboxFile(`audio/${fileStr}`);
this.audioFiles.push([fileStr, filePath])
})
}
resourcesFile2SandboxFile(resourcesPath: string) {
// 1、读取文件
let uint8Array: Uint8Array = getContext().resourceManager.getRawFileContentSync(resourcesPath);
let fileName = resourcesPath.substring(resourcesPath.lastIndexOf('/') + 1)
// 2、创建沙箱文件
let filePath = this.fileDir + `/${fileName}`;
if (fileIo.accessSync(filePath)) {
fileIo.unlinkSync(filePath);
}
let file: fileIo.File = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
// 3、resources下文件写入沙箱文件
fileIo.writeSync(file.fd, uint8Array.buffer);
// 4、关闭文件
fileIo.closeSync(file)
return filePath;
}
setCallBack(audioRenderer: audio.AudioRenderer) {
// 订阅监听状态变化
audioRenderer.on('stateChange', (state: audio.AudioState) => {
if (state == 1) {
console.info(TAG, 'audio renderer state is: STATE_PREPARED');
}
if (state == 2) {
console.info(TAG, 'audio renderer state is: STATE_RUNNING');
}
});
// 订阅到达标记的事件
audioRenderer.on('markReach', 1000, (position: number) => {
if (position == 1000) {
console.info(TAG, 'ON Triggered successfully');
}
});
// 订阅到达标记的事件
audioRenderer.on('periodReach', 1000, (position: number) => {
if (position == 1000) {
console.info(TAG, 'ON Triggered successfully');
}
});
// 监听音频中断事件
audioRenderer.on('audioInterrupt', (interruptEvent: audio.InterruptEvent) => {
if (interruptEvent.forceType == audio.InterruptForceType.INTERRUPT_FORCE) {
// 由系统进行操作,强制打断音频渲染,应用需更新自身状态及显示内容等
switch (interruptEvent.hintType) {
case audio.InterruptHint.INTERRUPT_HINT_PAUSE:
// 音频流已被暂停,临时失去焦点,待可重获焦点时会收到resume对应的interruptEvent
console.info(TAG, 'Force paused. Update playing status and stop writing');
this.isPlaying = false; // 简化处理,代表应用切换至暂停状态的若干操作
break;
case audio.InterruptHint.INTERRUPT_HINT_STOP:
// 音频流已被停止,永久失去焦点,若想恢复渲染,需用户主动触发
console.info(TAG, 'Force stopped. Update playing status and stop writing');
this.isPlaying = false; // 简化处理,代表应用切换至暂停状态的若干操作
break;
case audio.InterruptHint.INTERRUPT_HINT_DUCK:
// 音频流已被降低音量渲染
console.info(TAG, 'Force ducked. Update volume status');
this.isDucked = true; // 简化处理,代表应用更新音量状态的若干操作
break;
case audio.InterruptHint.INTERRUPT_HINT_UNDUCK:
// 音频流已被恢复正常音量渲染
console.info(TAG, 'Force ducked. Update volume status');
this.isDucked = false; // 简化处理,代表应用更新音量状态的若干操作
break;
default:
console.info(TAG, 'Invalid interruptEvent');
break;
}
} else if (interruptEvent.forceType == audio.InterruptForceType.INTERRUPT_SHARE) {
// 由应用进行操作,应用可以自主选择打断或忽略
switch (interruptEvent.hintType) {
case audio.InterruptHint.INTERRUPT_HINT_RESUME:
// 建议应用继续渲染(说明音频流此前被强制暂停,临时失去焦点,现在可以恢复渲染)
console.info(TAG, 'Resume force paused renderer or ignore');
// 若选择继续渲染,需在此处主动执行开始渲染的若干操作
break;
case audio.InterruptHint.INTERRUPT_HINT_PAUSE:
// 建议应用暂停渲染
console.info(TAG, 'Choose to pause or ignore');
// 若选择暂停渲染,需在此处主动执行暂停渲染的若干操作
break;
case audio.InterruptHint.INTERRUPT_HINT_STOP:
// 建议应用停止渲染
console.info(TAG, 'Choose to stop or ignore');
// 若选择停止渲染,需在此处主动执行停止渲染的若干操作
break;
case audio.InterruptHint.INTERRUPT_HINT_DUCK:
// 建议应用降低音量渲染
console.info(TAG, 'Choose to duck or ignore');
// 若选择降低音量渲染,需在此处主动执行降低音量渲染的若干操作
break;
case audio.InterruptHint.INTERRUPT_HINT_UNDUCK:
// 建议应用恢复正常音量渲染
console.info(TAG, 'Choose to unduck or ignore');
// 若选择恢复正常音量渲染,需在此处主动执行恢复正常音量渲染的若干操作
break;
default:
break;
}
}
});
}
}