Recording audio and video with MediaRecorder on Android
Android’s MediaRecorder API provides a straightforward way to capture microphone input, camera footage, or both. It is useful for voice notes, interview tools, training apps, evidence logs, and short-form video features where the app needs a compressed file rather than a live media stream.
This tutorial builds a Kotlin example that records audio and video, saves the result in the app’s private storage, and releases hardware resources safely. The same principles apply to an audio-only recorder, although video requires camera permissions, a preview surface, and more careful lifecycle handling.
Choose the recording architecture
Start by deciding whether the feature needs audio, video, or both. An audio recorder can use MediaRecorder.AudioSource.MIC, while a video recorder normally combines the microphone with Camera input. For advanced camera controls, high frame rates, or image analysis, CameraX and Media3 may be better choices, but MediaRecorder remains suitable for a compact beginner project.
The output format must match the selected encoders. A common combination is MPEG-4 with H.264 video and AAC audio. Save files with a .mp4 extension and create a unique name so a new recording never overwrites an earlier one.
private var recorder: MediaRecorder? = null
private var outputFile: File? = null
private fun createOutputFile(): File {
val folder = File(filesDir, "recordings").apply { mkdirs() }
return File(folder, "clip_${System.currentTimeMillis()}.mp4")
}
Private app storage is a sensible default for voice memos and drafts. If users need to open the clip in Gallery or share it with another application, publish it through MediaStore instead of writing directly to an arbitrary public path.
Request permissions at runtime
Add the required permissions to AndroidManifest.xml. Recording audio needs RECORD_AUDIO, and camera capture needs CAMERA.
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
Android requires runtime approval on supported versions, so check both permissions immediately before recording. Asking for camera and microphone access together is appropriate when the screen clearly explains the video feature. The app should handle denial without crashing and should not start a recorder with incomplete access.
private val permissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { grants ->
val allowed = grants[Manifest.permission.CAMERA] == true &&
grants[Manifest.permission.RECORD_AUDIO] == true
if (allowed) startRecording()
}
private fun requestRecordingPermissions() {
permissionLauncher.launch(
arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO
)
)
}
A user can revoke access later from Settings. Check permission status again when the Activity resumes, particularly if the recorder is used in a long-running workflow.
Prepare the preview and recorder
For video, place a SurfaceView or TextureView in the layout. A SurfaceView is simple and works well for a basic camera preview. The older Camera API is used here because it integrates directly with MediaRecorder; production applications should evaluate CameraX for newer camera behaviour.
private var camera: Camera? = null
private lateinit var preview: SurfaceView
private fun openCamera() {
camera = Camera.open().apply {
setDisplayOrientation(90)
setPreviewDisplay(preview.holder)
startPreview()
}
}
The recorder must be configured in a strict order. Calling methods out of sequence can produce IllegalStateException, an empty file, or a recording that cannot be played.
private fun startRecording() {
val activeCamera = camera ?: return
val file = createOutputFile()
outputFile = file
activeCamera.unlock()
recorder = MediaRecorder().apply {
setCamera(activeCamera)
setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
setVideoSource(MediaRecorder.VideoSource.CAMERA)
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
setVideoEncoder(MediaRecorder.VideoEncoder.H264)
setVideoSize(1280, 720)
setVideoFrameRate(30)
setOutputFile(file.absolutePath)
setPreviewDisplay(preview.holder.surface)
prepare()
start()
}
}
Some Android devices reject a selected resolution or frame rate. Begin with conservative settings such as 1280×720 and 30 frames per second, then test on several handsets before exposing quality controls.
Stop safely and save the result
Stopping a recording is more than calling stop(). Release the recorder, lock the camera again, and stop its preview so another screen or application can access the hardware. A short recording can also trigger an error when stop() is called, so catch RuntimeException and remove the incomplete file.
private fun stopRecording() {
val currentRecorder = recorder ?: return
try {
currentRecorder.stop()
} catch (error: RuntimeException) {
outputFile?.delete()
} finally {
currentRecorder.reset()
currentRecorder.release()
recorder = null
camera?.apply {
lock()
stopPreview()
release()
}
camera = null
}
}
Disable the record button while a stop operation is running and update the interface with the saved file name. For an audio-only mode, omit camera setup, setVideoSource(), video encoder settings, and the preview surface, then use a format such as THREE_GPP with an AMR encoder if broad legacy compatibility is required.
Compare storage and capture choices
The correct implementation depends on whether the file is temporary, user-visible, or intended for immediate upload. The following choices cover common Android projects:
| Requirement | Practical choice | Important detail |
|---|---|---|
| Private draft | filesDir |
Removed when the app is uninstalled |
| Cache recording | cacheDir |
Android may delete it when storage is low |
| Gallery-visible video | MediaStore.Video |
Prefer scoped storage on modern Android |
| Audio-only memo | MPEG-4 with AAC | Easy to share and play on current devices |
| Advanced camera workflow | CameraX with a recorder use case | Better lifecycle and device support |
For Australians recording interviews in Sydney, Melbourne, Brisbane, or regional areas, offline storage can be valuable because mobile coverage varies during travel and at large outdoor events. Keep the original file locally until an upload has completed, and show its size so users understand how much space a long recording consumes.
If the app shares files, create a FileProvider content URI rather than exposing a file:// URI. A share intent can then send the clip to Messages, email, cloud storage, or an editing application without weakening file access protections.
Manage lifecycle and interruptions
Camera and microphone resources should be released when the Activity stops, especially when a user switches applications or locks the phone. For a short recorder, stopping in onStop() is predictable. For a longer capture session, use a foreground service with a visible notification and clearly communicate that recording continues.
override fun onStop() {
super.onStop()
if (recorder != null) stopRecording()
}
Handle interruptions such as an incoming call, another application taking the microphone, low storage, and screen rotation. Locking the orientation during a simple project reduces complexity, while a larger application can retain the recorder in a ViewModel or service and recreate the preview surface separately.
Australian users often record while commuting, at community events, or outdoors in bright sunlight. Test pause and stop controls with one hand, use large accessible buttons, and avoid relying on colour alone to show that capture is active. A persistent timer and microphone indicator make the state obvious.
Test playback, privacy, and compliance
Test the complete path: permission approval, denial, a one-second clip, a long clip, rotation, app backgrounding, low storage, headset connection, and playback after a process restart. Try several Android phones commonly sold in the Australian market rather than relying only on an emulator. Check portrait and landscape orientation, echo, wind noise, exposure, and whether the output is accepted by the device’s default player.
Recording people brings legal and ethical responsibilities. The Privacy Act 1988 and the Australian Privacy Principles may apply when an organisation collects identifiable audio or video, while state and territory surveillance laws can regulate private conversations and consent. Provide a clear recording notice, obtain permission where required, restrict access to stored files, and explain retention and deletion.
For an app distributed through Google Play, disclose microphone and camera use accurately and avoid recording in the background without a prominent user-facing reason. A short privacy screen, visible recording indicator, encrypted server upload, and a delete action help establish trust. Developers looking for broader Android implementation patterns can also consult Android coding references while comparing platform-specific behaviour.
Improve reliability before release
Add a maximum duration or storage check before starting. A video file can grow quickly, particularly when users capture school performances, sporting events, or long business meetings. Display remaining storage and stop gracefully if the filesystem becomes full.
Keep recording state in one place, make the start and stop methods idempotent, and log failures without including private audio or video data. Verify that a generated file exists and has a sensible size before presenting it as complete. When uploads are involved, use a background transfer mechanism that can retry after the user moves between Wi-Fi and mobile data.
A dependable MediaRecorder feature is built around permission checks, compatible encoder settings, careful resource cleanup, and transparent handling of personal content. Those foundations make it easier to extend a basic sample into an audio diary, inspection tool, interview recorder, or video note application.