Skip to content

feat: ✨ Add audio effects support for Android recordings - #499

Open
lavigarg-simform wants to merge 2 commits into
fix/I488_fix_android_release_crashfrom
fix/I442_and_I381_android_audio_noise_supression
Open

lavigarg-simform wants to merge 2 commits into
fix/I488_fix_android_release_crashfrom
fix/I442_and_I381_android_audio_noise_supression

Conversation

@lavigarg-simform

@lavigarg-simform lavigarg-simform commented Jun 24, 2026 •

Copy link
Copy Markdown

Description

On Android, recordings made with this package often contain noticeable echo and background noise compared to iOS, which sounds clean for the same environment. The root cause is that Android capture always used the raw MIC source with no device audio processing, while iOS routes through a voice pipeline that suppresses noise and echo by default.

This PR gives Android recordings access to the same kind of device-level audio processing, all opt-in so existing behavior is unchanged.

  • Adds a configurable Android audio input source (AndroidAudioSource) so callers can route capture through the device voice pipeline (e.g. voiceCommunication) instead of the raw mic.
  • Adds opt-in noise suppression, acoustic echo cancellation, and automatic gain control, each attached only when the caller enables it and the device supports it.
  • Effects fail safely: an unsupported or unavailable effect is skipped without aborting the recording, and audio effects are released when the recorder is released.
  • Audio sources that require newer Android versions fall back to the default source on older devices instead of producing unusable audio.
  • Fails loudly when a requested source/sample-rate/channel combination cannot initialize, instead of recording garbage PCM (loud noise).
  • Updates the example app to demonstrate the recommended voice-recording configuration.

All new settings default to off / mic, so projects that do not opt in keep their current recording behavior. Note that automatic gain control alters captured amplitude and therefore the rendered waveform, so it is best avoided for music or level-sensitive recording.

Checklist

  • The title of my PR starts with a Conventional Commit prefix (fix:, feat:, docs: etc).
  • I have followed the Contributor Guide when preparing my PR.
  • I have updated/added tests for ALL new/updated/fixed functionality.
  • I have updated/added relevant documentation in docs and added dartdoc comments with ///.
  • I have updated/added relevant examples in examples or docs.

Breaking Change?

  • Yes, this PR is a breaking change.
  • No, this PR is not a breaking change.

Related Issues

Closes #442
Closes #381

@vasu-nageshri-simform vasu-nageshri-simform left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this. It's opt-in and the defaults are unchanged.

What I checked

  • Built it and ran the example on an API 35 arm64 emulator across all 7 AndroidAudioSource values, with and without effects, plus the WAV encoder.
  • Invalid sample rate fails loudly with a PlatformException, as described.
  • flutter analyze is clean. A small test of the Dart JSON mapping passes (included below).

What I could not check

  • The emulator exposes no NS/AEC/AGC, so isAvailable() is false. Only the "effect unavailable, skip" path ran. The attach, enable and release path, and the actual noise/echo reduction, still need a check on a real device before merge.
  • I did not reproduce the noise/echo from #442 / #381 on main, and I did not hit the new STATE_INITIALIZED guard.

Note

  • This is stacked on fix/I488_fix_android_release_crash, so that PR needs to land first.

Unrelated to this PR, worth a separate issue: during testing, stop() for AAC recordings intermittently never returned. It also happened with the default mic and no effects, and this PR doesn't touch encoders/, so it is likely pre-existing. I did not rerun on main. The likely cause is CommonEncoder.signalToStop() only setting isEncodingComplete. If the codec has already handed out an idle input buffer (currentInputBufferIndex >= 0), no further onInputBufferAvailable fires, so end-of-stream is never queued and the completion callback never runs.

Details are in the inline comments (none blocking). The tests box in the checklist is unchecked and the repo has no test/ directory, so here is a small test that could be added as test/android_audio_source_test.dart:

android_audio_source_test.dart
import 'package:audio_waveforms/audio_waveforms.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  test('native values match MediaRecorder.AudioSource', () {
    expect(AndroidAudioSource.values.map((e) => e.toNativeValue()).toList(),
        [0, 1, 5, 6, 7, 9, 10]);
  });
  test('defaults keep legacy behaviour', () {
    final j = const RecorderSettings().androidToJson(path: 'p');
    expect(j['audioSource'], 1);
    expect(j['useNoiseSuppressor'], false);
    expect(j['useEchoCanceler'], false);
    expect(j['useAutoGainControl'], false);
  });
  test('opt-in flags serialised', () {
    final j = const RecorderSettings(
      androidEncoderSettings: AndroidEncoderSettings(
        audioSource: AndroidAudioSource.voiceCommunication,
        useNoiseSuppressor: true,
        useEchoCanceler: true,
        useAutoGainControl: true,
      ),
    ).androidToJson();
    expect(j['audioSource'], 7);
    expect([j['useNoiseSuppressor'], j['useEchoCanceler'], j['useAutoGainControl']], [true, true, true]);
  });
  test('iOS json unchanged (no android keys leak)', () {
    final j = const RecorderSettings().iosToJson(path: 'p');
    expect(j.containsKey('audioSource'), false);
  });
}

Comment thread example/lib/main.dart Resolved
Comment thread doc/documentation.md Resolved
Comment thread lib/src/models/recorder_settings.dart Resolved
lavigarg-simform and others added 2 commits October 29, 2026 10:08
- Store each audio effect before enabling it so it is always released,
  and share one helper between the three effects
- Release existing effects at the start of attachAudioEffects so a
  repeated init does not leak them
- Fall back to MIC instead of DEFAULT when UNPROCESSED or
  VOICE_PERFORMANCE are unavailable, and use the MediaRecorder constants
- Use 16000 Hz in the example for the voiceCommunication source
- Document the PlatformException for unsupported source/sample rate
- Apply formatter line collapsing in RecorderSettings.androidToJson
@vasu-nageshri-simform
vasu-nageshri-simform force-pushed the fix/I442_and_I381_android_audio_noise_supression branch from 01e8203 to 289d2b6 Compare October 29, 2026 04:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Android recordings have echo/noise compared to iOS Audio Recording Issue in audio_waveforms v1.0.5(Android)

2 participants