feat(录音): AmrRecorder AVRecorder(AMR-NB) 封装 start/stop/cancel

This commit is contained in:
lanterngamescn
2026-06-27 17:00:09 +08:00
parent 2e81c0ee47
commit 9f79bd1d54
2 changed files with 105 additions and 0 deletions
+1
View File
@@ -19,6 +19,7 @@ export { ScanProvider } from './src/main/ets/providers/ScanProvider';
export { CameraProvider } from './src/main/ets/providers/CameraProvider';
export { LocationProvider } from './src/main/ets/providers/LocationProvider';
export { AudioProvider } from './src/main/ets/providers/AudioProvider';
export { AmrRecorder } from './src/main/ets/providers/AmrRecorder';
export { NavProvider } from './src/main/ets/providers/NavProvider';
// 微信 SDK 封装(EntryAbility 处理回调 want 需用)
@@ -0,0 +1,104 @@
/**
* AVRecorder 录音封装(设计 §6):AMR-NBCFT_AMR + AUDIO_AMR_NB / 8kHz / 单声道 / 12.2kbps),
* 对齐 Android RAW_AMR + AMR_NB。文件落 cacheDir/<uuid>.amrkey=<uuid>.amr。无 UI。
*/
import { media } from '@kit.MediaKit';
import { fileIo } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { util } from '@kit.ArkTS';
import { Logger } from 'common';
export class AmrRecorder {
private readonly log: Logger = Logger.tag('AmrRecorder');
private recorder: media.AVRecorder | undefined = undefined;
private fd: number = -1;
private filePath: string = '';
private fileKey: string = '';
isActive(): boolean {
return this.recorder !== undefined;
}
key(): string {
return this.fileKey;
}
async start(context: common.Context): Promise<boolean> {
if (this.recorder !== undefined) {
return false;
}
const uuid: string = util.generateRandomUUID(true);
this.fileKey = `${uuid}.amr`;
this.filePath = `${context.cacheDir}/${this.fileKey}`;
try {
const file: fileIo.File = fileIo.openSync(this.filePath,
fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
this.fd = file.fd;
const recorder: media.AVRecorder = await media.createAVRecorder();
this.recorder = recorder;
recorder.on('error', (e: BusinessError) => this.log.w(`recorder error: ${e.code} ${e.message}`));
const profile: media.AVRecorderProfile = {
audioBitrate: 12200,
audioChannels: 1,
audioCodec: media.CodecMimeType.AUDIO_AMR_NB,
audioSampleRate: 8000,
fileFormat: media.ContainerFormatType.CFT_AMR,
};
const config: media.AVRecorderConfig = {
audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
profile: profile,
url: `fd://${this.fd}`,
};
await recorder.prepare(config);
await recorder.start();
return true;
} catch (e) {
this.log.w(`start failed: ${(e as BusinessError).message}`);
await this.cleanup(true);
return false;
}
}
async stop(): Promise<string> {
const path: string = this.filePath;
await this.cleanup(false);
return path;
}
async cancel(): Promise<void> {
await this.cleanup(true);
}
private async cleanup(deleteFile: boolean): Promise<void> {
const recorder: media.AVRecorder | undefined = this.recorder;
this.recorder = undefined;
if (recorder !== undefined) {
try {
if (recorder.state === 'started' || recorder.state === 'paused') {
await recorder.stop();
}
await recorder.release();
} catch (e) {
this.log.w(`cleanup recorder failed: ${(e as BusinessError).message}`);
}
}
if (this.fd >= 0) {
try {
fileIo.closeSync(this.fd);
} catch (e) {
this.log.w(`close fd failed: ${(e as BusinessError).message}`);
}
this.fd = -1;
}
if (deleteFile && this.filePath !== '') {
try {
fileIo.unlinkSync(this.filePath);
} catch (e) {
this.log.w(`unlink failed: ${(e as BusinessError).message}`);
}
this.filePath = '';
this.fileKey = '';
}
}
}