Voice messages are the most technically demanding feature among chat media. Recording, encoding, uploading, playback with waveform visualization, accelerated playback — each step requires precise handling of the platform’s audio API. On average, we save you up to 40 hours of in-house development, and the implementation budget is calculated individually. Our approach, based on AAC, compresses audio 10 times better than WAV while preserving speech intelligibility. Getting smooth waveform visualization with no lag and correct audio session switching during screen rotation is particularly tricky. Our experience with 30+ projects shows that without a systematic approach, you easily run into session conflicts, corrupted files, or UX gaps. We implement voice messages turnkey: from designing the audio pipeline to testing on real devices. Get a free assessment of your project — contact our engineers.
How to Implement Voice Messages in a Mobile Chat?
-
Prepare permissions: request microphone access (
NSMicrophoneUsageDescriptionon iOS,RECORD_AUDIOon Android) with a clear explanation to the user. On iOS — viaAVAudioSession.requestRecordPermission(), on Android — viaActivityResultContracts.RequestPermission(). -
Configure audio session: on iOS, set category
.recordor.playAndRecordwith option.defaultToSpeaker. On Android, initializeMediaRecorderwith the correct call order. -
Record: on iOS —
AVAudioRecorderwith AAC, 16 kHz, mono. On Android —MediaRecorderwithAudioSource.MIC,OutputFormat.MPEG_4,AudioEncoder.AAC. -
Waveform visualization: on iOS, get amplitude via
averagePower(forChannel:)and draw usingCAShapeLayeror SwiftUI Canvas. On Android — viagetMaxAmplitude()and a customViewor Compose Canvas. - Send: upload the compressed M4A file to the server via REST or GraphQL.
-
Playback with caching: download and save to
Library/Caches(iOS) orgetCacheDir()(Android), play with speed control (1.5×, 2×) viaAVPlayer.rateorExoPlayer.setPlaybackParameters.
Why Waveform Visualization Matters for UX
Waveform visualization is what distinguishes a good implementation from a mediocre one. The user sees that recording is in progress, can estimate the length and dynamics of the message. Without the waveform, the message feels blind — it’s unclear if there’s silence or active speech. We draw the waveform in real time during recording and statically with a playhead during playback. On iOS, we get amplitude via AVAudioRecorder.averagePower(forChannel: 0) with updateMeters() calls on a timer every 50–100 ms. The value is in dB from -160 to 0, normalized to 0..1: pow(10, power / 20). Draw using CAShapeLayer or SwiftUI Canvas — the latter is easier to animate without setNeedsDisplay. On Android, MediaRecorder.getMaxAmplitude() returns a value 0–32767. We collect it into an array via Handler.postDelayed() and draw using Canvas.drawRect() in a custom View or through Compose Canvas.
Recording Audio: iOS vs Android
| Parameter | iOS (AVAudioRecorder) | Android (MediaRecorder) |
|---|---|---|
| Format | AAC (MPEG4AAC) | AAC (MPEG_4) |
| Sample rate | 16000 Hz | 16000 Hz |
| Channels | Mono | Mono |
| Quality | medium | (default) |
| Permission | NSMicrophoneUsageDescription | RECORD_AUDIO (ActivityResultContracts) |
| Session | AVAudioSession (.record/.playAndRecord) | Handle prepare() errors |
iOS
Optimal parameters:
AVFormatIDKey: kAudioFormatMPEG4AAC AVSampleRateKey: 16000 // sufficient for speech AVNumberOfChannelsKey: 1 // mono AVEncoderAudioQualityKey: AVAudioQuality.medium AAC mono 16 kHz gives ~20–30 KB per minute — compact and decodable on Android and in the browser. The M4A container (for AAC) is natively supported on both platforms. Permission for microphone should be requested in advance via AVAudioSession.requestRecordPermission(), not at the moment the record button is pressed. If the user declines, Info.plist must contain NSMicrophoneUsageDescription with a clear explanation. An important point with AVAudioSession: before starting recording, activate the session with category .record or .playAndRecord with option .defaultToSpeaker. If you don't make this switch explicitly, recording may conflict with music playback through AirPods. According to Apple’s documentation, you also need to handle interruptions (e.g., a phone call).
Android
MediaRecorder with AudioSource.MIC, OutputFormat.MPEG_4, AudioEncoder.AAC. On Android 10+, permission RECORD_AUDIO is required via ActivityResultContracts.RequestPermission(). MediaRecorder requires a precise order of calls: setAudioSource → setOutputFormat → setAudioEncoder → prepare → start — mixing up the order means an IllegalStateException at runtime, not compile time.
How to Accelerate Playback Without Losing Quality?
Accelerated playback (1.5×, 2×) is done via AVPlayer.rate = 1.5 on iOS and ExoPlayer.setPlaybackParameters(PlaybackParameters(1.5f)) on Android. Both APIs work without artifacts on speech thanks to pitch correction. Voice messages are typically 5–60 seconds — that’s 2–200 KB in AAC. Upload as a regular file, but with one nuance: on iOS, when playing from a URL, you need to switch the AVAudioSession back to category .playback or .playAndRecord, otherwise the sound will go to the earpiece, not the speaker. Caching on the client is mandatory. Requesting the server again at every playback is poor UX. Save to Library/Caches (iOS) or getCacheDir() (Android) with a limit on total cache size.
Common Mistakes
| Mistake | Consequence | Solution |
|---|---|---|
| Not calling stop() before upload | Corrupted file | Call stop() + release() before reading |
| Using AudioRecord instead of MediaRecorder | Huge uncompressed files | Use MediaRecorder with AAC |
| Not switching audio session to playback | Sound in earpiece | Explicitly set category to playback |
For voice compression, AAC provides 10 times better compression than WAV with virtually indistinguishable quality for speech. OPUS is even more efficient but requires an additional library on iOS. We choose AAC for its native support.
What’s Included
- Analysis of current chat architecture
- Design of audio pipeline (record → encode → upload → cache → playback)
- Integration of recording/playback with waveform visualization
- Configuration of accelerated playback and progress indication
- Testing on real devices (iOS + Android)
- Delivery of documentation and source code
- Post-deployment support (2 weeks)
Timeline
Basic implementation (recording, encoding, upload, playback with progress) — 2–3 days. Real-time waveform + waveform during playback — additional 1–2 days. Cost is calculated individually. Get a consultation on voice message integration — our engineers will help assess the scope of work.







