Function Introduction:
Record audio and save it in m4a format, then play the audio. Refer to the documentation for developing audio recording functionality using AVRecorder (ArkTS). For more detailed interface information, please refer to the API documentation: @ohos.multimedia.media (Media Service).
Key Knowledge Points:
- Familiar with using AVRecorder to record audio and save it locally.
- Familiar with using AVPlayer to play local audio files.
- Familiar with the dynamic permission application method for sensitive permissions. The sensitive permission required in this project is
MICROPHONE.
Usage Environment:
- API 9
- DevEco Studio 4.0 Release
- Windows 11
- Stage Model
- ArkTS Language
Required Permissions:
- ohos.permission.MICROPHONE
Effect Diagram:

Core Code:
src/main/ets/utils/Permission.ets is a utility for dynamic permission application:
import bundleManager from '@ohos.bundle.bundleManager';
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
async function checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
let atManager = abilityAccessCtrl.createAtManager();
let grantStatus: abilityAccessCtrl.GrantStatus;
// Obtain the access token ID of the application
let tokenId: number;
try {
let bundleInfo: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
tokenId = appInfo.accessTokenId;
} catch (err) {
console.error(`getBundleInfoForSelf failed, code is ${err.code}, message is ${err.message}`);
}
// Verify if the application has been granted the permission
try {
grantStatus = await atManager.checkAccessToken(tokenId, permission);
} catch (err) {
console.error(`checkAccessToken failed, code is ${err.code}, message is ${err.message}`);
}
return grantStatus;
}
export async function checkPermissions(permission: Permissions): Promise<boolean> {
let grantStatus: abilityAccessCtrl.GrantStatus = await checkAccessToken(permission);
if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
return true;
} else {
return false;
}
}
src/main/ets/utils/Recorder.ets is an audio recording utility class for recording and obtaining recording data:
import media from '@ohos.multimedia.media';
import fs from '@ohos.file.fs';
import promptAction from '@ohos.promptAction';
import audio from '@ohos.multimedia.audio';
export default class AudioRecorder {
private audioFile = null;
private avRecorder: media.AVRecorder | undefined = undefined;
private avProfile: media.AVRecorderProfile = {
audioBitrate: 48000, // Audio bitrate
audioChannels: audio.AudioChannel.CHANNEL_1, // Number of audio channels
audioCodec: media.CodecMimeType.AUDIO_AAC, // Audio encoding format, currently only AAC is supported
audioSampleRate: audio.AudioSamplingRate.SAMPLE_RATE_16000, // Audio sampling rate
fileFormat: media.ContainerFormatType.CFT_MPEG_4A, // Container format, currently only M4A is supported
};
private avConfig: media.AVRecorderConfig = {
audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC, // Audio input source, set to microphone here
profile: this.avProfile,
url: '', // URL of the recording file
};
// Register audio recorder callback functions
setAudioRecorderCallback() {
if (this.avRecorder != undefined) {
// Error reporting callback function
this.avRecorder.on('error', (err) => {
console.error(`An error occurred in the recorder, error code: ${err.code}, error message: ${err.message}`);
});
}
}
// Start recording
async startRecord(audioPath: string) {
// 1. Create a recording instance
this.avRecorder = await media.createAVRecorder();
this.setAudioRecorderCallback();
// Create and open the recording file
this.audioFile = fs.openSync(audioPath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
// 2. Get the recording file descriptor and assign it to the url in avConfig
this.avConfig.url = `fd://${this.audioFile.fd}`;
// 3. Configure recording parameters to complete preparation
await this.avRecorder.prepare(this.avConfig);
// 4. Start recording
await this.avRecorder.start();
console.info('Recording in progress...');
}
// Pause recording
async pauseRecord() {
// Only call pause when in the started state for a valid state transition
if (this.avRecorder != undefined && this.avRecorder.state === 'started') {
await this.avRecorder.pause();
}
}
// Resume recording
async resumeRecord() {
// Only call resume when in the paused state for a valid state transition
if (this.avRecorder != undefined && this.avRecorder.state === 'paused') {
await this.avRecorder.resume();
}
}
// Stop recording
async stopRecord() {
if (this.avRecorder != undefined) {
// 1. Stop recording
// Only call stop when in the started or paused state for a valid state transition
if (this.avRecorder.state === 'started' || this.avRecorder.state === 'paused') {
await this.avRecorder.stop();
}
// 2. Reset
await this.avRecorder.reset();
// 3. Release the recording instance
await this.avRecorder.release();
// 4. Close the recording file descriptor
fs.closeSync(this.audioFile);
promptAction.showToast({ message: "Recording successful!" });
}
}
}
Add the required permissions in src/main/module.json5 under the module section. For field descriptions, add them to the respective string.json files:
"requestPermissions": [
{
"name": "ohos.permission.MICROPHONE",
"reason": "$string:record_reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "always"
}
}
]
The page code is as follows:
import { checkPermissions } from '../utils/Permission';
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
import common from '@ohos.app.ability.common';
import AudioRecorder from '../utils/Recorder';
import promptAction from '@ohos.promptAction';
import media from '@ohos.multimedia.media';
import fs from '@ohos.file.fs';
// Permissions requiring dynamic application
const permissions: Array<Permissions> = ['ohos.permission.MICROPHONE'];
// Obtain the application context
const context = getContext(this) as common.UIAbilityContext;
// Obtain the project's files directory
const filesDir = context.filesDir;
// Create the directory if it does not exist
fs.access(filesDir, (err, res: boolean) => {
if (!res) {
fs.mkdirSync(filesDir);
}
});
// Audio file path
let audioPath = filesDir + "/audio.m4a";
@Entry
@Component
struct Index {
@State recordBtnText: string = 'Press to Record'
@State playBtnText: string = 'Play Audio'
// Recorder
private audioRecorder?: AudioRecorder;
// Player
private avPlayer;
private playIng: boolean = false
// When the page is displayed
async onPageShow() {
// Check if already authorized
let promise = checkPermissions(permissions[0]);
promise.then((result) => {
if (result) {
// Initialize the recorder
if (this.audioRecorder == null) {
this.audioRecorder = new AudioRecorder();
}
} else {
this.reqPermissionsAndRecord(permissions);
}
});
// Create AVPlayer instance
this.avPlayer = await media.createAVPlayer();
// Set up AVPlayer callback
this.setAVPlayerCallback();
console.info('Player initialized');
}
build() {
Row() {
RelativeContainer() {
// Record button
Button(this.recordBtnText)
.id('btn1')
.width('90%')
.margin({ bottom: 10 })
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
.onTouch((event) => {
switch (event.type) {
case TouchType.Down:
console.info('Button pressed');
// Check permission
let promise = checkPermissions(permissions[0]);
promise.then((result) => {
if (result) {
// Start recording
this.audioRecorder?.startRecord(audioPath);
this.recordBtnText = 'Recording...';
} else {
// Request permission
this.reqPermissionsAndRecord(permissions);
}
});
break;
case TouchType.Up:
console.info('Button released');
if (this.audioRecorder != null) {
// Stop recording
this.audioRecorder.stopRecord();
}
this.recordBtnText = 'Press to Record';
break;
}
})
// Play button
Button(this.playBtnText)
.id('btn2')
.width('90%')
.margin({ bottom: 10 })
.alignRules({
bottom: { anchor: 'btn1', align: VerticalAlign.Top },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
.onClick(() => {
if (!this.playIng) {
this.playBtnText = 'Playing...';
// Play audio
this.playAudio(audioPath);
} else {
// Stop playing
this.stopPlay();
}
})
}
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
}
// Play audio
async playAudio(path: string) {
this.playIng = true;
let fdPath = 'fd://';
let res = fs.accessSync(path);
if (!res) {
console.error(`Audio file does not exist: ${path}`);
this.playIng = false;
return;
}
console.info(`Playing audio file: ${path}`);
// Open the resource file to get the file descriptor
let file = await fs.open(path);
fdPath = fdPath + file.fd;
// Assign URL to trigger the 'initialized' state change
this.avPlayer.url = fdPath;
}
// Stop playing
stopPlay() {
this.avPlayer.reset();
}
// Set up AVPlayer callback functions
setAVPlayerCallback() {
this.avPlayer.on('error', (err) => {
this.playIng = false;
this.playBtnText = 'Play Audio';
console.error(`Player error, error code: ${err.code}, error message: ${err.message}`);
// Call reset to release resources and transition to 'idle' state
this.avPlayer.reset();
});
// State change callback function
this.avPlayer.on('stateChange', async (state) => {
switch (state) {
case 'initialized':
// Resource initialization complete, start preparing the file
this.avPlayer.prepare();
break;
case 'prepared':
// Resource preparation complete, start playing the file
this.avPlayer.play();
break;
case 'completed':
// Reset resources to transition back to 'idle' state
this.avPlayer.reset();
break;
case 'idle':
this.playIng = false;
this.playBtnText = 'Play Audio';
break;
}
});
}
// Request permissions and start recording
reqPermissionsAndRecord(permissions: Array<Permissions>): void {
let atManager = abilityAccessCtrl.createAtManager();
// requestPermissionsFromUser will determine the authorization status and prompt the user if needed
atManager.requestPermissionsFromUser(context, permissions).then((data) => {
let grantStatus: Array<number> = data.authResults;
let length: number = grantStatus.length;
for (let i = 0; i < length; i++) {
if (grantStatus[i] === 0) {
// Permission granted, proceed with target operation
console.info('Permission granted successfully');
if (this.audioRecorder == null) {
this.audioRecorder = new AudioRecorder();
}
} else {
promptAction.showToast({ message: 'Permission denied, recording requires authorization' });
return;
}
}
}).catch((err) => {
console.error(`requestPermissionsFromUser failed, code: ${err.code}, message: ${err.message}`);
});
}
}