HarmonyOS 鸿蒙Next音频录制并保存至用户文件目录, 无法播放
HarmonyOS 鸿蒙Next音频录制并保存至用户文件目录, 无法播放
startRecord() 5秒后stopRecord()停止录音, saveAudio()保存至用户文件,
录制的音频文件保存在cacheDir是可以播放的, 但保存到用户文件的音频无法播放
帮忙看下哪里除了问题
import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';
import fileIo from '@ohos.file.fs';
import { picker } from '@kit.CoreFileKit';
import { abilityAccessCtrl, bundleManager, common, Permissions } from '@kit.AbilityKit';
import { promptAction } from '@kit.ArkUI';
const TAG = 'AudioDemo'
@Entry
@Component
struct AudioDemo {
private audioCapturer: audio.AudioCapturer | undefined
private renderModel: audio.AudioRenderer | undefined = undefined;
private isRecording: boolean = false
private timeOutId = -1
private audioStreamInfo: audio.AudioStreamInfo = {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100, // 采样率
channels: audio.AudioChannel.CHANNEL_2, // 通道
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式
}
private audioCapturerInfo: audio.AudioCapturerInfo = {
source: audio.SourceType.SOURCE_TYPE_MIC, // 音源类型
capturerFlags: 0 // 音频采集器标志
}
private filePath = '';
private fileName = ''
private inputFile: fileIo.File | undefined
private waveArrays: number[] = [4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
@State yMax: number = 40
aboutToDisappear(): void {
if (this.timeOutId != -1) {
clearTimeout(this.timeOutId)
}
this.stopRecord()
}
build() {
Column() {
Button('startRecord(最长5秒)').onClick((event: ClickEvent) => {
this.checkAudioPermission()
})
Button('stopRecord').onClick((event: ClickEvent) => {
this.stopRecord()
})
Button('startPlay').onClick((event: ClickEvent) => {
this.playAudio()
})
Button('stopPlay').onClick((event: ClickEvent) => {
this.stopPlay()
})
}
.height('100%')
.width('100%')
}
checkAudioPermission(){
let grantStatus: abilityAccessCtrl.GrantStatus = this.checkAccessToken('ohos.permission.MICROPHONE')
if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
// Logger.debug('kevin check location 已授权')
this.startRecord()
} else {
let context = getContext(this) as common.UIAbilityContext;
let atManager = abilityAccessCtrl.createAtManager();
atManager.requestPermissionsFromUser(context, ['ohos.permission.MICROPHONE']).then((data) => {
let grantStatus: Array<number> = data.authResults;
let result: boolean = true
for (let i = 0; i < grantStatus.length; i++) {
result = result && grantStatus[i] === 0
}
if (result) {
this.startRecord()
} else {
promptAction.showToast({
message: 'no audio permission',
alignment: Alignment.Center
})
}
})
}
}
checkAccessToken(permission: Permissions): abilityAccessCtrl.GrantStatus {
let AtManager = abilityAccessCtrl.createAtManager()
let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION;
let promise: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED
try {
let bundledata = bundleManager.getBundleInfoForSelfSync(bundleFlags);
let tokenID = bundledata.appInfo.accessTokenId
promise = AtManager.verifyAccessTokenSync(tokenID, permission);
} catch (err) {
}
return promise
}
startRecord() {
let audioCapturerOptions: audio.AudioCapturerOptions = {
streamInfo: this.audioStreamInfo,
capturerInfo: this.audioCapturerInfo
}
let bufferSize = 0
let context = getContext() as common.UIAbilityContext;
let path = context.cacheDir;
this.fileName = 'audio_' +new Date().getTime()+'.wav'
this.filePath = path + '/' + this.fileName;
let lastTime = new Date().getTime()
this.inputFile = fileIo.openSync(this.filePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
let readDataCallback = (buffer: ArrayBuffer) => {
console.log('kevin', 'record --' + buffer.byteLength + ',bufferSize=' + bufferSize)
if (new Date().getTime() - lastTime > 300) {
this.pcm2Db(buffer)
lastTime = new Date().getTime()
}
fileIo.writeSync(this.inputFile?.fd, buffer, {
offset: bufferSize,
length: buffer.byteLength
});
bufferSize += buffer.byteLength;
}
audio.createAudioCapturer(audioCapturerOptions, (err, capturer) => { // 创建AudioCapturer实例
if (err) {
console.error(TAG, `Invoke createAudioCapturer failed, code is ${err.code}, message is ${err.message}`);
return;
}
console.info(TAG, `create AudioCapturer success`);
this.audioCapturer = capturer;
if (this.audioCapturer !== undefined) {
(this.audioCapturer as audio.AudioCapturer).on('readData', readDataCallback);
let stateGroup =
[audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED];
if (stateGroup.indexOf((this.audioCapturer as audio.AudioCapturer).state.valueOf()) === -1) {
// 当且仅当状态为STATE_PREPARED、STATE_PAUSED和STATE_STOPPED之一时才能启动采集
console.error(`${TAG}: start failed`);
return;
}
// 启动采集
(this.audioCapturer as audio.AudioCapturer).start((err: BusinessError) => {
if (err) {
console.error(TAG, 'Capturer start failed.');
} else {
console.info(TAG, 'Capturer start success.');
this.isRecording = true
this.timeOutId = setTimeout(() => {
this.stopRecord()
}, 5000)
}
});
}
});
}
pcm2Db(buffer: ArrayBuffer) {
// 获取一个 Int32Array 类型的视图,该视图将数组缓冲区解释为带符号 32 位整数数组
const intArray = new Int32Array(buffer);
let sum: number = 0;
for (let i = 0; i < intArray.length; i++) {
let sample: number = intArray[i] / 32768;
sum += sample * sample;
}
let rms = Math.sqrt(sum / buffer.byteLength);
let db = 20 * Math.log10(rms);
// console.log('kevin cwq db is ' + db)
if (db > 0) {
this.yMax = Math.min(60, Math.max(2, Math.floor(db)))
}
}
stopRecord() {
if (this.audioCapturer !== undefined) {
// 只有采集器状态为STATE_RUNNING或STATE_PAUSED的时候才可以停止
if ((this.audioCapturer as audio.AudioCapturer).state.valueOf() !== audio.AudioState.STATE_RUNNING &&
(this.audioCapturer as audio.AudioCapturer).state.valueOf() !== audio.AudioState.STATE_PAUSED) {
console.info(TAG, 'Capturer is not running or paused');
return;
}
//停止采集
(this.audioCapturer as audio.AudioCapturer).stop((err: BusinessError) => {
if (err) {
console.error(TAG, 'Capturer stop failed.' + JSON.stringify(err));
} else {
console.info(TAG, 'Capturer stop success.');
if (this.inputFile) {
fileIo.closeSync(this.inputFile)
}
this.release()
this.saveAudio()
}
});
}
}
//保存到手机
saveAudio() {
let audioSaveOptions = new picker.AudioSaveOptions();
// 保存文件名(可选)
audioSaveOptions.newFileNames = [this.fileName];
let uri: string = '';
// 请确保 getContext(this) 返回结果为 UIAbilityContext
let context = getContext(this) as common.Context;
const audioViewPicker = new picker.AudioViewPicker(context);
//用户选择目标文件夹,用户选择与文件类型相对应的文件夹,即可完成文件保存操作。保存成功后,返回保存文档的uri。
audioViewPicker.save(audioSaveOptions).then((audioSelectResult: Array<string>) => {
uri = audioSelectResult[0];
console.info('kevin audioViewPicker.save to file succeed and uri is:' + uri);
//这里需要注意接口权限参数是fileIo.OpenMode.READ_WRITE。
fileIo.open(uri, fileIo.OpenMode.READ_WRITE).then((file) => {
fileIo.open(this.filePath, fileIo.OpenMode.READ_ONLY).then((srcFile) => {
let size = fileIo.statSync(srcFile.fd).size
console.log('kevin', 'srcFile size=' + size)
fileIo.copyFile(srcFile.fd, file.fd).then(() => {
console.info('kevin copyFile succeed and size is:' + fileIo.statSync(file.fd).size);
fileIo.closeSync(file);
fileIo.closeSync(srcFile);
}).catch((err: BusinessError) => {
console.error(`kevin [copyFile] failed, code is ${err.code}, message is ${err.message}`);
})
}).catch((err: BusinessError) => {
console.error(`kevin open [srcFile] failed, code is ${err.code}, message is ${err.message}`);
})
}).catch((err: BusinessError) => {
console.error(`kevin open [file] failed, code is ${err.code}, message is ${err.message}`);
})
}).catch((err: BusinessError) => {
console.error(`kevin save failed, code is ${err.code}, message is ${err.message}`);
})
}
// 销毁实例,释放资源
release() {
if (this.audioCapturer !== undefined) {
// 采集器状态不是STATE_RELEASED或STATE_NEW状态,才能release
if ((this.audioCapturer as audio.AudioCapturer).state.valueOf() === audio.AudioState.STATE_RELEASED ||
(this.audioCapturer as audio.AudioCapturer).state.valueOf() === audio.AudioState.STATE_NEW) {
console.info(TAG, 'Capturer already released');
return;
}
//释放资源
(this.audioCapturer as audio.AudioCapturer).release((err: BusinessError) => {
if (err) {
console.error(TAG, 'Capturer release failed.');
} else {
console.info(TAG, 'Capturer release success.');
}
});
}
}
playAudio() {
let bufferSize: number = 0;
let audioStreamInfo: audio.AudioStreamInfo = {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率
channels: audio.AudioChannel.CHANNEL_2, // 通道
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式
}
let audioRendererInfo: audio.AudioRendererInfo = {
usage: audio.StreamUsage.STREAM_USAGE_MUSIC, // 音频流使用类型
rendererFlags: 0 // 音频渲染器标志
}
let audioRendererOptions: audio.AudioRendererOptions = {
streamInfo: audioStreamInfo,
rendererInfo: audioRendererInfo
}
//确保该路径下存在该资源
let file: fileIo.File = fileIo.openSync(this.filePath, fileIo.OpenMode.READ_ONLY);
let size = fileIo.statSync(file.fd).size
let writeDataCallback = (buffer: ArrayBuffer) => {
console.log('kevin', 'play audio writeDataCallback--' + buffer.byteLength + ',bufferSize=' + bufferSize)
if (bufferSize < size) {
fileIo.readSync(file.fd, buffer, {
offset: bufferSize,
length: buffer.byteLength
});
bufferSize += buffer.byteLength;
} else {
this.stopPlay()
}
}
audio.createAudioRenderer(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);
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('kevin Renderer start failed.');
} else {
console.info('kevin Renderer start success.');
}
});
}
} else {
console.info(`${TAG}: creating AudioRenderer failed, error: ${err.message}`);
}
});
}
// 暂停渲染
pauseAudio() {
if (this.renderModel !== undefined) {
// 只有渲染器状态为running的时候才能暂停
if ((this.renderModel as audio.AudioRenderer).state.valueOf() !== audio.AudioState.STATE_RUNNING) {
console.info('kevin Renderer is not running');
return;
}
// 暂停渲染
(this.renderModel as audio.AudioRenderer).pause((err: BusinessError) => {
if (err) {
console.error('kevin Renderer pause failed.');
} else {
console.info('kevin Renderer pause success.');
}
});
}
}
// 停止渲染
stopPlay() {
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('kevin Renderer is not running or paused.');
return;
}
// 停止渲染
(this.renderModel as audio.AudioRenderer).stop((err: BusinessError) => {
if (err) {
console.error('kevin Renderer stop failed.');
} else {
// fileIo.close(file);//todo close
console.info('kevin Renderer stop success.');
this.releasePlay()
}
});
}
}
// 销毁实例,释放资源
releasePlay() {
if (this.renderModel !== undefined) {
// 渲染器状态不是released状态,才能release
if (this.renderModel.state.valueOf() === audio.AudioState.STATE_RELEASED) {
console.info('kevin Renderer already released');
return;
}
// 释放资源
(this.renderModel as audio.AudioRenderer).release((err: BusinessError) => {
if (err) {
console.error('kevin Renderer release failed.');
} else {
console.info('kevin Renderer release success.');
}
});
}
}
}
2 回复
保存的wav 提示不能播放,文件已损坏 AudioCapturer是音频采集器,用于录制PCM(Pulse Code Modulation)音频数据, 得到的pcm 格式不支持预览,可以将文件导出并使用 pcm 转换 wav 或 mp3 工具,以此验证录制是否有问题。
针对HarmonyOS 鸿蒙Next音频录制并保存至用户文件目录后无法播放的问题,这通常是由于音频文件的格式或编码问题导致的。
在HarmonyOS中,使用AudioCapturer录制的音频文件默认是PCM格式,这种格式的原始数据流通常无法被普通音频播放器直接识别或播放。因此,即使文件被成功保存到用户文件目录,也可能因为格式不兼容而无法播放。
要解决这个问题,你可以尝试将PCM格式的音频文件转换为WAV或MP3等更通用的音频格式。这通常需要使用专门的音频转换工具或库来实现。
此外,还需要确保在保存音频文件时,文件的扩展名与其实际格式相匹配。例如,如果文件是PCM格式,但保存时使用了“.mp3”扩展名,这可能会导致播放器在尝试播放时出错。
如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html 。