Overview
This technical guide demonstrates audio and video recording capabilities in HarmonyOS applications using the @ohos.multimedia.audio framework. The implemantation covers device switching, audio focus management, audio effects configuration, and various recording modes.
Functionality Overview
The application provides several audio-related features:
- Audio Output Management: Detection and switching of output devices based on hardware changes
- Audio Focus Handling: Priority-based audio playback management
- Audio Effects Configuration: Both preset and real-time audio effect manipulation
- Standard Audio Recording: Basic microphone capture functionality
- Parallel Recording: Simultaneous microphone and screen capture
Project Structure
entry/src/main/ets/
|---pages
|---|---Index.ets // Main application entry
|---|---OutputDeviceManager.ets // Output device selection interface
|---|---AudioFocusController.ets // Audio focus management
|---|---AudioEffectManager.ets // Audio effect configuration
|---|---BasicAudioRecorder.ets // Standard audio recording
|---|---MultiStreamRecorder.ets // Parallel recording functionality
library/
|---SystemLogger.ts // Logging utilities
Technical Implementation
Audio Output Device Management
The output device functionality is implemented in the OutputDeviceManager component:
import audio from '@ohos.multimedia.audio';
class OutputDeviceManager {
private audioManager: audio.AudioManager;
private routingManager: audio.AudioRoutingManager;
private audioRenderer: audio.AudioRenderer;
private currentDevice: audio.AudioDevice | null = null;
constructor() {
this.audioManager = audio.getAudioManager();
this.routingManager = this.audioManager.getRoutingManager();
this.initializeAudioRenderer();
this.setupDeviceChangeListener();
}
private initializeAudioRenderer() {
const rendererOptions: audio.AudioRendererOptions = {
streamInfo: {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,
channels: audio.AudioChannel.CHANNEL_2,
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
},
rendererInfo: {
content: audio.ContentType.CONTENT_TYPE_VOICE_CALL,
usage: audio.StreamUsage.STREAM_USAGE_VOICE_COMMUNICATION
}
};
this.audioRenderer = audio.createAudioRenderer(rendererOptions);
this.audioRenderer.start();
}
private setupDeviceChangeListener() {
this.routingManager.on('deviceChange', audio.DeviceFlag.OUTPUT_DEVICES_FLAG, () => {
this.updateCurrentDevice();
});
}
private updateCurrentDevice() {
this.currentDevice = this.routingManager.getPreferOutputDeviceForRendererInfo(
this.audioRenderer.getRendererInfo()
);
this.notifyDeviceChange(this.currentDevice);
}
public getCurrentDevice(): audio.AudioDevice | null {
return this.currentDevice;
}
}
Audio Focus Management
Audio focus functionality is implemented in the AudioFocusController:
class AudioFocusController {
private musicRenderer: audio.AudioRenderer;
private ringtoneRenderer: audio.AudioRenderer;
private focusCallback: (state: audio.AudioInterruptEvent) => void;
constructor() {
this.musicRenderer = this.createRenderer(
audio.ContentType.CONTENT_TYPE_MUSIC,
audio.StreamUsage.STREAM_USAGE_MEDIA
);
this.ringtoneRenderer = this.createRenderer(
audio.ContentType.CONTENT_TYPE_RINGTONE,
audio.StreamUsage.STREAM_USAGE_NOTIFICATION_RINGTONE
);
this.focusCallback = this.handleAudioInterrupt.bind(this);
this.musicRenderer.on('audioInterrupt', this.focusCallback);
this.ringtoneRenderer.on('audioInterrupt', this.focusCallback);
}
private createRenderer(content: audio.ContentType, usage: audio.StreamUsage): audio.AudioRenderer {
const options: audio.AudioRendererOptions = {
streamInfo: {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,
channels: audio.AudioChannel.CHANNEL_2,
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
},
rendererInfo: {
content: content,
usage: usage
}
};
const renderer = audio.createAudioRenderer(options);
renderer.setInterruptMode(audio.InterruptMode.INDEPENDENT_MODE);
renderer.on('stateChange', this.handleStateChange.bind(this));
return renderer;
}
private handleAudioInterrupt(event: audio.AudioInterruptEvent) {
if (event.interruptType === audio.InterruptType.INTERRUPT_TYPE_BEGIN) {
this.handleFocusLoss(event.renderer);
} else {
this.handleFocusGain(event.renderer);
}
}
private handleFocusLoss(renderer: audio.AudioRenderer) {
renderer.pause();
this.updateUIState(renderer, 'paused');
}
private handleFocusGain(renderer: audio.AudioRenderer) {
renderer.start();
this.writeAudioData(renderer);
this.updateUIState(renderer, 'playing');
}
private writeAudioData(renderer: audio.AudioRenderer) {
const bufferSize = renderer.getBufferSize();
const audioData = this.readAudioFile(bufferSize);
renderer.write(audioData);
}
private readAudioFile(bufferSize: number): ArrayBuffer {
// Implementation for reading audio file data
return new ArrayBuffer(bufferSize);
}
}
Audio Effect Management
Audio effect functionality is divided into preset and real-time implementations:
Preset Audio Effects
class PresetAudioEffectManager {
private streamManager: audio.AudioStreamManager;
constructor() {
const audioManager = audio.getAudioManager();
this.streamManager = audioManager.getStreamManager();
}
public queryEffects(contentType: audio.ContentType, streamUsage: audio.StreamUsage): Promise<Array<audio.AudioEffectInfo>> {
return this.streamManager.getAudioEffectInfoArray(contentType, streamUsage);
}
}
Real-time Audio Effects
class RealtimeAudioEffectManager {
private audioRenderer: audio.AudioRenderer | null = null;
private isPlaying: boolean = false;
public startPlayback(contentType: audio.ContentType, streamUsage: audio.StreamUsage): void {
if (this.isPlaying) return;
const options: audio.AudioRendererOptions = {
streamInfo: {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,
channels: audio.AudioChannel.CHANNEL_2,
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
},
rendererInfo: {
content: contentType,
usage: streamUsage
}
};
this.audioRenderer = audio.createAudioRenderer(options);
this.audioRenderer.start();
this.isPlaying = true;
this.writeAudioData();
}
public setEffectMode(mode: audio.AudioEffectMode): void {
if (!this.audioRenderer) return;
this.audioRenderer.setAudioEffectMode(mode);
}
public getCurrentEffectMode(): audio.AudioEffectMode {
if (!this.audioRenderer) return audio.AudioEffectMode.EFFECT_MODE_NONE;
return this.audioRenderer.getAudioEffectMode();
}
public stopPlayback(): void {
if (!this.audioRenderer || !this.isPlaying) return;
this.audioRenderer.stop();
this.audioRenderer.release();
this.audioRenderer = null;
this.isPlaying = false;
}
private writeAudioData(): void {
if (!this.audioRenderer || !this.isPlaying) return;
const bufferSize = this.audioRenderer.getBufferSize();
const audioData = this.readAudioFile(bufferSize);
this.audioRenderer.write(audioData);
}
}
Audio Recording Functionality
Standard Audio Recording
class BasicAudioRecorder {
private audioCapturer: audio.AudioCapturer | null = null;
private recordingFile: fs.File | null = null;
private recordingTimer: number = -1;
private isRecording: boolean = false;
private isPaused: boolean = false;
async startRecording(): Promise<void> {
if (this.isRecording) return;
const capturerOptions: audio.AudioCapturerOptions = {
streamInfo: {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,
channels: audio.AudioChannel.CHANNEL_2,
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
},
source: audio.SourceType.SOURCE_TYPE_MIC
};
this.audioCapturer = audio.createAudioCapturer(capturerOptions);
this.recordingFile = await this.createRecordingFile();
this.audioCapturer.start();
this.isRecording = true;
this.startRecordingTimer();
this.readAudioData();
}
pauseRecording(): void {
if (!this.isRecording || this.isPaused) return;
this.audioCapturer?.stop();
this.isPaused = true;
this.clearRecordingTimer();
}
resumeRecording(): void {
if (!this.isRecording || !this.isPaused) return;
this.audioCapturer?.start();
this.isPaused = false;
this.startRecordingTimer();
this.readAudioData();
}
stopRecording(): void {
if (!this.isRecording) return;
this.audioCapturer?.stop();
this.isRecording = false;
this.isPaused = false;
this.clearRecordingTimer();
if (this.recordingFile) {
this.recordingFile.closeSync();
this.recordingFile = null;
}
this.audioCapturer?.release();
this.audioCapturer = null;
}
private async createRecordingFile(): Promise<fs.File> {
const file = fs.openSync('/path/to/recording.pcm', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
return file;
}
private readAudioData(): void {
if (!this.audioCapturer || !this.isRecording || this.isPaused) return;
const bufferSize = this.audioCapturer.getBufferSize();
const buffer = new ArrayBuffer(bufferSize);
const readBytes = this.audioCapturer.read(buffer);
if (readBytes > 0 && this.recordingFile) {
fs.writeSync(this.recordingFile.fd, buffer, 0, readBytes);
}
if (this.isRecording && !this.isPaused) {
setTimeout(() => this.readAudioData(), 10);
}
}
private startRecordingTimer(): void {
this.recordingTimer = setInterval(() => {
this.updateRecordingTime();
}, 1000);
}
private clearRecordingTimer(): void {
if (this.recordingTimer !== -1) {
clearInterval(this.recordingTimer);
this.recordingTimer = -1;
}
}
}
Parallel Recording (Audio + Screen)
class MultiStreamRecorder {
private musicRenderer: audio.AudioRenderer;
private microphoneCapturer: audio.AudioCapturer;
private screenCapturer: audio.AudioCapturer;
private micRecordingFile: fs.File | null = null;
private screenRecordingFile: fs.File | null = null;
private isRecording: boolean = false;
private recordingTimer: number = -1;
constructor() {
this.musicRenderer = this.createMusicRenderer();
this.microphoneCapturer = this.createMicrophoneCapturer();
this.screenCapturer = this.createScreenCapturer();
}
async startRecording(): Promise<void> {
if (this.isRecording) return;
this.micRecordingFile = await this.createRecordingFile('microphone.pcm');
this.screenRecordingFile = await this.createRecordingFile('screen.pcm');
this.microphoneCapturer.start();
this.screenCapturer.start();
this.musicRenderer.start();
this.isRecording = true;
this.startRecordingTimer();
this.readMicrophoneData();
this.readScreenData();
}
stopRecording(): void {
if (!this.isRecording) return;
this.microphoneCapturer.stop();
this.screenCapturer.stop();
this.musicRenderer.stop();
this.isRecording = false;
this.clearRecordingTimer();
if (this.micRecordingFile) {
this.micRecordingFile.closeSync();
this.micRecordingFile = null;
}
if (this.screenRecordingFile) {
this.screenRecordingFile.closeSync();
this.screenRecordingFile = null;
}
this.microphoneCapturer.release();
this.screenCapturer.release();
}
private createMusicRenderer(): audio.AudioRenderer {
const options: audio.AudioRendererOptions = {
streamInfo: {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,
channels: audio.AudioChannel.CHANNEL_2,
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
},
rendererInfo: {
content: audio.ContentType.CONTENT_TYPE_MUSIC,
usage: audio.StreamUsage.STREAM_USAGE_MEDIA
},
privacyType: 0 // Required for screen recording
};
return audio.createAudioRenderer(options);
}
private createMicrophoneCapturer(): audio.AudioCapturer {
const options: audio.AudioCapturerOptions = {
streamInfo: {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,
channels: audio.AudioChannel.CHANNEL_2,
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
},
source: audio.SourceType.SOURCE_TYPE_MIC
};
return audio.createAudioCapturer(options);
}
private createScreenCapturer(): audio.AudioCapturer {
const options: audio.AudioCapturerOptions = {
streamInfo: {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100,
channels: audio.AudioChannel.CHANNEL_2,
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
},
source: audio.SourceType.SOURCE_TYPE_SCREEN
};
return audio.createAudioCapturer(options);
}
private async createRecordingFile(filename: string): Promise<fs.File> {
const file = fs.openSync(`/path/to/${filename}`, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
return file;
}
private readMicrophoneData(): void {
if (!this.isRecording || !this.microphoneCapturer) return;
const bufferSize = this.microphoneCapturer.getBufferSize();
const buffer = new ArrayBuffer(bufferSize);
const readBytes = this.microphoneCapturer.read(buffer);
if (readBytes > 0 && this.micRecordingFile) {
fs.writeSync(this.micRecordingFile.fd, buffer, 0, readBytes);
}
if (this.isRecording) {
setTimeout(() => this.readMicrophoneData(), 10);
}
}
private readScreenData(): void {
if (!this.isRecording || !this.screenCapturer) return;
const bufferSize = this.screenCapturer.getBufferSize();
const buffer = new ArrayBuffer(bufferSize);
const readBytes = this.screenCapturer.read(buffer);
if (readBytes > 0 && this.screenRecordingFile) {
fs.writeSync(this.screenRecordingFile.fd, buffer, 0, readBytes);
}
if (this.isRecording) {
setTimeout(() => this.readScreenData(), 10);
}
}
private startRecordingTimer(): void {
this.recordingTimer = setInterval(() => {
this.updateRecordingTime();
}, 1000);
}
private clearRecordingTimer(): void {
if (this.recordingTimer !== -1) {
clearInterval(this.recordingTimer);
this.recordingTimer = -1;
}
}
}
Required Permissions
The following permissions are required for audio recording functionality:
{
"name": "ohos.permission.MICROPHONE",
"reason": "$string:permission_microphone_reason",
"usedScene": {
"abilities": [
"com.example.audiorecorder.MainAbility"
],
"when": "inuse"
}
}
Constraints and Limitations
- This implementation is designed for standard HarmonyOS systems and has been tested on RK3568 devices
- Compatible with API 10 SDK (Version 10 Release) and system image (4.0 Release)
- Requires DevEco Studio version 4.0 or higher for compilation and execution
- Audio recording functionality requires explicit user permission for microphone access
- Screen recording requires the application to have apropriate system permissions