Implementing Audio and Video Recording in HarmonyOS Applications

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:

  1. Audio Output Management: Detection and switching of output devices based on hardware changes
  2. Audio Focus Handling: Priority-based audio playback management
  3. Audio Effects Configuration: Both preset and real-time audio effect manipulation
  4. Standard Audio Recording: Basic microphone capture functionality
  5. 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

  1. This implementation is designed for standard HarmonyOS systems and has been tested on RK3568 devices
  2. Compatible with API 10 SDK (Version 10 Release) and system image (4.0 Release)
  3. Requires DevEco Studio version 4.0 or higher for compilation and execution
  4. Audio recording functionality requires explicit user permission for microphone access
  5. Screen recording requires the application to have apropriate system permissions

Tags: HarmonyOS Audio Recording Video Recording multimedia ArkTS

Posted on Wed, 07 Oct 2026 16:50:21 +0000 by luminous